    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.lang.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.lang.ArrayUtils;
    024    import org.apache.commons.lang.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 905636 2010-02-02 14:03:32Z niallp $
    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>Convenience method returning new instance of <code>klazz</code> using a single argument constructor.
    069         * The formal parameter type is inferred from the actual values of <code>arg</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 arg the actual argument
    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 Object invokeConstructor(Class cls, Object arg)
    086                throws NoSuchMethodException, IllegalAccessException,
    087                InvocationTargetException, InstantiationException {
    088            return invokeConstructor(cls, new Object[] { arg });
    089        }
    090
    091        /**
    092         * <p>Returns new instance of <code>klazz</code> created using the actual arguments <code>args</code>.
    093         * The formal parameter types are inferred from the actual values of <code>args</code>.
    094         * See {@link #invokeExactConstructor(Class, Object[], Class[])} for more details.</p>
    095         *
    096         * <p>The signatures should be assignment compatible.</p>
    097         *
    098         * @param cls the class to be constructed.
    099         * @param args actual argument array
    100         * @return new instance of <code>klazz</code>
    101         *
    102         * @throws NoSuchMethodException If the constructor cannot be found
    103         * @throws IllegalAccessException If an error occurs accessing the constructor
    104         * @throws InvocationTargetException If an error occurs invoking the constructor
    105         * @throws InstantiationException If an error occurs instantiating the class
    106         *
    107         * @see #invokeConstructor(java.lang.Class, java.lang.Object[], java.lang.Class[])
    108         */
    109        public static Object invokeConstructor(Class cls, Object[] args)
    110                throws NoSuchMethodException, IllegalAccessException,
    111                InvocationTargetException, InstantiationException {
    112            if (null == args) {
    113                args = ArrayUtils.EMPTY_OBJECT_ARRAY;
    114            }
    115            Class parameterTypes[] = new Class[args.length];
    116            for (int i = 0; i < args.length; i++) {
    117                parameterTypes[i] = args[i].getClass();
    118            }
    119            return invokeConstructor(cls, args, parameterTypes);
    120        }
    121
    122        /**
    123         * <p>Returns new instance of <code>klazz</code> created using constructor
    124         * with signature <code>parameterTypes</code> and actual arguments <code>args</code>.</p>
    125         *
    126         * <p>The signatures should be assignment compatible.</p>
    127         *
    128         * @param cls the class to be constructed.
    129         * @param args actual argument array
    130         * @param parameterTypes parameter types array
    131         * @return new instance of <code>klazz</code>
    132         *
    133         * @throws NoSuchMethodException if matching constructor cannot be found
    134         * @throws IllegalAccessException thrown on the constructor's invocation
    135         * @throws InvocationTargetException thrown on the constructor's invocation
    136         * @throws InstantiationException thrown on the constructor's invocation
    137         * @see Constructor#newInstance
    138         */
    139        public static Object invokeConstructor(Class cls, Object[] args,
    140                Class[] parameterTypes) throws NoSuchMethodException,
    141                IllegalAccessException, InvocationTargetException,
    142                InstantiationException {
    143            if (parameterTypes == null) {
    144                parameterTypes = ArrayUtils.EMPTY_CLASS_ARRAY;
    145            }
    146            if (args == null) {
    147                args = ArrayUtils.EMPTY_OBJECT_ARRAY;
    148            }
    149            Constructor ctor = getMatchingAccessibleConstructor(cls, parameterTypes);
    150            if (null == ctor) {
    151                throw new NoSuchMethodException(
    152                        "No such accessible constructor on object: "
    153                                + cls.getName());
    154            }
    155            return ctor.newInstance(args);
    156        }
    157
    158        /**
    159         * <p>Convenience method returning new instance of <code>klazz</code> using a single argument constructor.
    160         * The formal parameter type is inferred from the actual values of <code>arg</code>.
    161         * See {@link #invokeExactConstructor(Class, Object[], Class[])} for more details.</p>
    162         *
    163         * <p>The signatures should match exactly.</p>
    164         *
    165         * @param cls the class to be constructed.
    166         * @param arg the actual argument
    167         * @return new instance of <code>klazz</code>
    168         *
    169         * @throws NoSuchMethodException If the constructor cannot be found
    170         * @throws IllegalAccessException If an error occurs accessing the constructor
    171         * @throws InvocationTargetException If an error occurs invoking the constructor
    172         * @throws InstantiationException If an error occurs instantiating the class
    173         *
    174         * @see #invokeExactConstructor(java.lang.Class, java.lang.Object[], java.lang.Class[])
    175         */
    176        public static Object invokeExactConstructor(Class cls, Object arg)
    177                throws NoSuchMethodException, IllegalAccessException,
    178                InvocationTargetException, InstantiationException {
    179            return invokeExactConstructor(cls, new Object[] { arg });
    180        }
    181
    182        /**
    183         * <p>Returns new instance of <code>klazz</code> created using the actual arguments <code>args</code>.
    184         * The formal parameter types are inferred from the actual values of <code>args</code>.
    185         * See {@link #invokeExactConstructor(Class, Object[], Class[])} for more details.</p>
    186         *
    187         * <p>The signatures should match exactly.</p>
    188         *
    189         * @param cls the class to be constructed.
    190         * @param args actual argument array
    191         * @return new instance of <code>klazz</code>
    192         *
    193         * @throws NoSuchMethodException If the constructor cannot be found
    194         * @throws IllegalAccessException If an error occurs accessing the constructor
    195         * @throws InvocationTargetException If an error occurs invoking the constructor
    196         * @throws InstantiationException If an error occurs instantiating the class
    197         *
    198         * @see #invokeExactConstructor(java.lang.Class, java.lang.Object[], java.lang.Class[])
    199         */
    200        public static Object invokeExactConstructor(Class cls, Object[] args)
    201                throws NoSuchMethodException, IllegalAccessException,
    202                InvocationTargetException, InstantiationException {
    203            if (null == args) {
    204                args = ArrayUtils.EMPTY_OBJECT_ARRAY;
    205            }
    206            int arguments = args.length;
    207            Class parameterTypes[] = new Class[arguments];
    208            for (int i = 0; i < arguments; i++) {
    209                parameterTypes[i] = args[i].getClass();
    210            }
    211            return invokeExactConstructor(cls, args, parameterTypes);
    212        }
    213
    214        /**
    215         * <p>Returns new instance of <code>klazz</code> created using constructor
    216         * with signature <code>parameterTypes</code> and actual arguments
    217         * <code>args</code>.</p>
    218         *
    219         * <p>The signatures should match exactly.</p>
    220         *
    221         * @param cls the class to be constructed.
    222         * @param args actual argument array
    223         * @param parameterTypes parameter types array
    224         * @return new instance of <code>klazz</code>
    225         *
    226         * @throws NoSuchMethodException if matching constructor cannot be found
    227         * @throws IllegalAccessException thrown on the constructor's invocation
    228         * @throws InvocationTargetException thrown on the constructor's invocation
    229         * @throws InstantiationException thrown on the constructor's invocation
    230         * @see Constructor#newInstance
    231         */
    232        public static Object invokeExactConstructor(Class cls, Object[] args,
    233                Class[] parameterTypes) throws NoSuchMethodException,
    234                IllegalAccessException, InvocationTargetException,
    235                InstantiationException {
    236            if (args == null) {
    237                args = ArrayUtils.EMPTY_OBJECT_ARRAY;
    238            }
    239            if (parameterTypes == null) {
    240                parameterTypes = ArrayUtils.EMPTY_CLASS_ARRAY;
    241            }
    242            Constructor ctor = getAccessibleConstructor(cls, parameterTypes);
    243            if (null == ctor) {
    244                throw new NoSuchMethodException(
    245                        "No such accessible constructor on object: "
    246                                + cls.getName());
    247            }
    248            return ctor.newInstance(args);
    249        }
    250
    251        /**
    252         * Returns a constructor with single argument.
    253         * @param cls the class to be constructed
    254         * @param parameterType The constructor parameter type
    255         * @return null if matching accessible constructor can not be found.
    256         * @see Class#getConstructor
    257         * @see #getAccessibleConstructor(java.lang.reflect.Constructor)
    258         */
    259        public static Constructor getAccessibleConstructor(Class cls,
    260                Class parameterType) {
    261            return getAccessibleConstructor(cls, new Class[] { parameterType });
    262        }
    263
    264        /**
    265         * Returns a constructor given a class and signature.
    266         * @param cls the class to be constructed
    267         * @param parameterTypes the parameter array
    268         * @return null if matching accessible constructor can not be found
    269         * @see Class#getConstructor
    270         * @see #getAccessibleConstructor(java.lang.reflect.Constructor)
    271         */
    272        public static Constructor getAccessibleConstructor(Class cls,
    273                Class[] parameterTypes) {
    274            try {
    275                return getAccessibleConstructor(cls.getConstructor(parameterTypes));
    276            } catch (NoSuchMethodException e) {
    277                return (null);
    278            }
    279        }
    280
    281        /**
    282         * Returns accessible version of the given constructor.
    283         * @param ctor prototype constructor object.
    284         * @return <code>null</code> if accessible constructor can not be found.
    285         * @see java.lang.SecurityManager
    286         */
    287        public static Constructor getAccessibleConstructor(Constructor ctor) {
    288            return MemberUtils.isAccessible(ctor)
    289                    && Modifier.isPublic(ctor.getDeclaringClass().getModifiers()) ? ctor
    290                    : null;
    291        }
    292
    293        /**
    294         * <p>Find an accessible constructor with compatible parameters.
    295         * Compatible parameters mean that every method parameter is assignable from
    296         * the given parameters. In other words, it finds constructor that will take
    297         * the parameters given.</p>
    298         *
    299         * <p>First it checks if there is constructor matching the exact signature.
    300         * If no such, all the constructors of the class are tested if their signatures
    301         * are assignment compatible with the parameter types.
    302         * The first matching constructor is returned.</p>
    303         *
    304         * @param cls find constructor for this class
    305         * @param parameterTypes find method with compatible parameters
    306         * @return a valid Constructor object. If there's no matching constructor, returns <code>null</code>.
    307         */
    308        public static Constructor getMatchingAccessibleConstructor(Class cls,
    309                Class[] parameterTypes) {
    310            // see if we can find the constructor directly
    311            // most of the time this works and it's much faster
    312            try {
    313                Constructor ctor = cls.getConstructor(parameterTypes);
    314                MemberUtils.setAccessibleWorkaround(ctor);
    315                return ctor;
    316            } catch (NoSuchMethodException e) { /* SWALLOW */
    317            }
    318            Constructor result = null;
    319            // search through all constructors
    320            Constructor[] ctors = cls.getConstructors();
    321            for (int i = 0; i < ctors.length; i++) {
    322                // compare parameters
    323                if (ClassUtils.isAssignable(parameterTypes, ctors[i]
    324                        .getParameterTypes(), true)) {
    325                    // get accessible version of method
    326                    Constructor ctor = getAccessibleConstructor(ctors[i]);
    327                    if (ctor != null) {
    328                        MemberUtils.setAccessibleWorkaround(ctor);
    329                        if (result == null
    330                                || MemberUtils.compareParameterTypes(ctor
    331                                        .getParameterTypes(), result
    332                                        .getParameterTypes(), parameterTypes) < 0) {
    333                            result = ctor;
    334                        }
    335                    }
    336                }
    337            }
    338            return result;
    339        }
    340
    341    }