    001    /*
    002     * Licensed to the Apache Software Foundation (ASF) under one or more
    003     * contributor license agreements.  See the NOTICE file distributed with
    004     * this work for additional information regarding copyright ownership.
    005     * The ASF licenses this file to You under the Apache License, Version 2.0
    006     * (the "License"); you may not use this file except in compliance with
    007     * the License.  You may obtain a copy of the License at
    008     *
    009     *      http://www.apache.org/licenses/LICENSE-2.0
    010     *
    011     * Unless required by applicable law or agreed to in writing, software
    012     * distributed under the License is distributed on an "AS IS" BASIS,
    013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014     * See the License for the specific language governing permissions and
    015     * limitations under the License.
    016     */
    017    package org.apache.commons.lang3.reflect;
    018
    019    import java.lang.reflect.Constructor;
    020    import java.lang.reflect.InvocationTargetException;
    021    import java.lang.reflect.Modifier;
    022
    023    import org.apache.commons.lang3.ArrayUtils;
    024    import org.apache.commons.lang3.ClassUtils;
    025
    026    /**
    027     * <p> Utility reflection methods focussed on constructors, modelled after {@link MethodUtils}. </p>
    028     *
    029     * <h3>Known Limitations</h3>
    030     * <h4>Accessing Public Constructors In A Default Access Superclass</h4>
    031     * <p>There is an issue when invoking public constructors contained in a default access superclass.
    032     * Reflection locates these constructors fine and correctly assigns them as public.
    033     * However, an <code>IllegalAccessException</code> is thrown if the constructors is invoked.</p>
    034     *
    035     * <p><code>ConstructorUtils</code> contains a workaround for this situation.
    036     * It will attempt to call <code>setAccessible</code> on this constructor.
    037     * If this call succeeds, then the method can be invoked as normal.
    038     * This call will only succeed when the application has sufficient security privilages.
    039     * If this call fails then a warning will be logged and the method may fail.</p>
    040     *
    041     * @author Apache Software Foundation
    042     * @author Craig R. McClanahan
    043     * @author Ralph Schaer
    044     * @author Chris Audley
    045     * @author Rey Francois
    046     * @author Gregor Rayman
    047     * @author Jan Sorensen
    048     * @author Robert Burrell Donkin
    049     * @author Rodney Waldhoff
    050     * @since 2.5
    051     * @version $Id: ConstructorUtils.java 925970 2010-03-22 06:22:28Z bayard $
    052     */
    053    public class ConstructorUtils {
    054
    055        /**
    056         * <p>ConstructorUtils instances should NOT be constructed in standard programming.
    057         * Instead, the class should be used as
    058         * <code>ConstructorUtils.invokeConstructor(cls, args)</code>.</p>
    059         *
    060         * <p>This constructor is public to permit tools that require a JavaBean
    061         * instance to operate.</p>
    062         */
    063        public ConstructorUtils() {
    064            super();
    065        }
    066
    067        /**
    068         * <p>Returns new instance of <code>klazz</code> created using the actual arguments <code>args</code>.
    069         * The formal parameter types are inferred from the actual values of <code>args</code>.
    070         * See {@link #invokeExactConstructor(Class, Object[], Class[])} for more details.</p>
    071         *
    072         * <p>The signatures should be assignment compatible.</p>
    073         *
    074         * @param cls the class to be constructed.
    075         * @param args actual argument array
    076         * @return new instance of <code>klazz</code>
    077         *
    078         * @throws NoSuchMethodException If the constructor cannot be found
    079         * @throws IllegalAccessException If an error occurs accessing the constructor
    080         * @throws InvocationTargetException If an error occurs invoking the constructor
    081         * @throws InstantiationException If an error occurs instantiating the class
    082         *
    083         * @see #invokeConstructor(java.lang.Class, java.lang.Object[], java.lang.Class[])
    084         */
    085        public static <T> T invokeConstructor(Class<T> cls, Object... args)
    086                throws NoSuchMethodException, IllegalAccessException,
    087                InvocationTargetException, InstantiationException {
    088            if (null == args) {
    089                args = ArrayUtils.EMPTY_OBJECT_ARRAY;
    090            }
    091            Class<?> parameterTypes[] = new Class[args.length];
    092            for (int i = 0; i < args.length; i++) {
    093                parameterTypes[i] = args[i].getClass();
    094            }
    095            return invokeConstructor(cls, args, parameterTypes);
    096        }
    097
    098        /**
    099         * <p>Returns new instance of <code>klazz</code> created using constructor
    100         * with signature <code>parameterTypes</code> and actual arguments <code>args</code>.</p>
    101         *
    102         * <p>The signatures should be assignment compatible.</p>
    103         *
    104         * @param cls the class to be constructed.
    105         * @param args actual argument array
    106         * @param parameterTypes parameter types array
    107         * @return new instance of <code>klazz</code>
    108         *
    109         * @throws NoSuchMethodException if matching constructor cannot be found
    110         * @throws IllegalAccessException thrown on the constructor's invocation
    111         * @throws InvocationTargetException thrown on the constructor's invocation
    112         * @throws InstantiationException thrown on the constructor's invocation
    113         * @see Constructor#newInstance
    114         */
    115        public static <T> T invokeConstructor(Class<T> cls, Object[] args,
    116                Class<?>[] parameterTypes) throws NoSuchMethodException,
    117                IllegalAccessException, InvocationTargetException,
    118                InstantiationException {
    119            if (parameterTypes == null) {
    120                parameterTypes = ArrayUtils.EMPTY_CLASS_ARRAY;
    121            }
    122            if (args == null) {
    123                args = ArrayUtils.EMPTY_OBJECT_ARRAY;
    124            }
    125            Constructor<T> ctor = getMatchingAccessibleConstructor(cls, parameterTypes);
    126            if (null == ctor) {
    127                throw new NoSuchMethodException(
    128                        "No such accessible constructor on object: "
    129                                + cls.getName());
    130            }
    131            return ctor.newInstance(args);
    132        }
    133
    134        /**
    135         * <p>Returns new instance of <code>klazz</code> created using the actual arguments <code>args</code>.
    136         * The formal parameter types are inferred from the actual values of <code>args</code>.
    137         * See {@link #invokeExactConstructor(Class, Object[], Class[])} for more details.</p>
    138         *
    139         * <p>The signatures should match exactly.</p>
    140         *
    141         * @param cls the class to be constructed.
    142         * @param args actual argument array
    143         * @return new instance of <code>klazz</code>
    144         *
    145         * @throws NoSuchMethodException If the constructor cannot be found
    146         * @throws IllegalAccessException If an error occurs accessing the constructor
    147         * @throws InvocationTargetException If an error occurs invoking the constructor
    148         * @throws InstantiationException If an error occurs instantiating the class
    149         *
    150         * @see #invokeExactConstructor(java.lang.Class, java.lang.Object[], java.lang.Class[])
    151         */
    152        public static <T> T invokeExactConstructor(Class<T> cls, Object... args)
    153                throws NoSuchMethodException, IllegalAccessException,
    154                InvocationTargetException, InstantiationException {
    155            if (null == args) {
    156                args = ArrayUtils.EMPTY_OBJECT_ARRAY;
    157            }
    158            int arguments = args.length;
    159            Class<?> parameterTypes[] = new Class[arguments];
    160            for (int i = 0; i < arguments; i++) {
    161                parameterTypes[i] = args[i].getClass();
    162            }
    163            return invokeExactConstructor(cls, args, parameterTypes);
    164        }
    165
    166        /**
    167         * <p>Returns new instance of <code>klazz</code> created using constructor
    168         * with signature <code>parameterTypes</code> and actual arguments
    169         * <code>args</code>.</p>
    170         *
    171         * <p>The signatures should match exactly.</p>
    172         *
    173         * @param cls the class to be constructed.
    174         * @param args actual argument array
    175         * @param parameterTypes parameter types array
    176         * @return new instance of <code>klazz</code>
    177         *
    178         * @throws NoSuchMethodException if matching constructor cannot be found
    179         * @throws IllegalAccessException thrown on the constructor's invocation
    180         * @throws InvocationTargetException thrown on the constructor's invocation
    181         * @throws InstantiationException thrown on the constructor's invocation
    182         * @see Constructor#newInstance
    183         */
    184        public static <T> T invokeExactConstructor(Class<T> cls, Object[] args,
    185                Class<?>[] parameterTypes) throws NoSuchMethodException,
    186                IllegalAccessException, InvocationTargetException,
    187                InstantiationException {
    188            if (args == null) {
    189                args = ArrayUtils.EMPTY_OBJECT_ARRAY;
    190            }
    191            if (parameterTypes == null) {
    192                parameterTypes = ArrayUtils.EMPTY_CLASS_ARRAY;
    193            }
    194            Constructor<T> ctor = getAccessibleConstructor(cls, parameterTypes);
    195            if (null == ctor) {
    196                throw new NoSuchMethodException(
    197                        "No such accessible constructor on object: "
    198                                + cls.getName());
    199            }
    200            return ctor.newInstance(args);
    201        }
    202
    203        /**
    204         * Returns a constructor given a class and signature.
    205         * @param cls the class to be constructed
    206         * @param parameterTypes the parameter array
    207         * @return null if matching accessible constructor can not be found
    208         * @see Class#getConstructor
    209         * @see #getAccessibleConstructor(java.lang.reflect.Constructor)
    210         */
    211        public static <T> Constructor<T> getAccessibleConstructor(Class<T> cls,
    212                Class<?>... parameterTypes) {
    213            try {
    214                return getAccessibleConstructor(cls.getConstructor(parameterTypes));
    215            } catch (NoSuchMethodException e) {
    216                return (null);
    217            }
    218        }
    219
    220        /**
    221         * Returns accessible version of the given constructor.
    222         * @param ctor prototype constructor object.
    223         * @return <code>null</code> if accessible constructor can not be found.
    224         * @see java.lang.SecurityManager
    225         */
    226        public static <T> Constructor<T> getAccessibleConstructor(Constructor<T> ctor) {
    227            return MemberUtils.isAccessible(ctor)
    228                    && Modifier.isPublic(ctor.getDeclaringClass().getModifiers()) ? ctor
    229                    : null;
    230        }
    231
    232        /**
    233         * <p>Find an accessible constructor with compatible parameters.
    234         * Compatible parameters mean that every method parameter is assignable from
    235         * the given parameters. In other words, it finds constructor that will take
    236         * the parameters given.</p>
    237         *
    238         * <p>First it checks if there is constructor matching the exact signature.
    239         * If no such, all the constructors of the class are tested if their signatures
    240         * are assignment compatible with the parameter types.
    241         * The first matching constructor is returned.</p>
    242         *
    243         * @param cls find constructor for this class
    244         * @param parameterTypes find method with compatible parameters
    245         * @return a valid Constructor object. If there's no matching constructor, returns <code>null</code>.
    246         */
    247        @SuppressWarnings("unchecked")
    248        public static <T> Constructor<T> getMatchingAccessibleConstructor(Class<T> cls,
    249                Class<?>... parameterTypes) {
    250            // see if we can find the constructor directly
    251            // most of the time this works and it's much faster
    252            try {
    253                Constructor<T> ctor = cls.getConstructor(parameterTypes);
    254                MemberUtils.setAccessibleWorkaround(ctor);
    255                return ctor;
    256            } catch (NoSuchMethodException e) { /* SWALLOW */
    257            }
    258            Constructor<T> result = null;
    259            // search through all constructors
    260            Constructor<?>[] ctors = cls.getConstructors();
    261            for (int i = 0; i < ctors.length; i++) {
    262                // compare parameters
    263                if (ClassUtils.isAssignable(parameterTypes, ctors[i]
    264                        .getParameterTypes(), true)) {
    265                    // get accessible version of method
    266                    Constructor<T> ctor = getAccessibleConstructor((Constructor<T>) ctors[i]);
    267                    if (ctor != null) {
    268                        MemberUtils.setAccessibleWorkaround(ctor);
    269                        if (result == null
    270                                || MemberUtils.compareParameterTypes(ctor
    271                                        .getParameterTypes(), result
    272                                        .getParameterTypes(), parameterTypes) < 0) {
    273                            result = ctor;
    274                        }
    275                    }
    276                }
    277            }
    278            return result;
    279        }
    280
    281    }