    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
    018    package org.apache.commons.beanutils;
    019
    020    import java.lang.reflect.Constructor;
    021    import java.lang.reflect.InvocationTargetException;
    022    import java.lang.reflect.Modifier;
    023
    024    /**
    025     * <p> Utility reflection methods focussed on constructors, modelled after {@link MethodUtils}. </p>
    026     *
    027     * <h3>Known Limitations</h3>
    028     * <h4>Accessing Public Constructors In A Default Access Superclass</h4>
    029     * <p>There is an issue when invoking public constructors contained in a default access superclass.
    030     * Reflection locates these constructors fine and correctly assigns them as public.
    031     * However, an <code>IllegalAccessException</code> is thrown if the constructors is invoked.</p>
    032     *
    033     * <p><code>ConstructorUtils</code> contains a workaround for this situation.
    034     * It will attempt to call <code>setAccessible</code> on this constructor.
    035     * If this call succeeds, then the method can be invoked as normal.
    036     * This call will only succeed when the application has sufficient security privilages.
    037     * If this call fails then a warning will be logged and the method may fail.</p>
    038     *
    039     * @author Craig R. McClanahan
    040     * @author Ralph Schaer
    041     * @author Chris Audley
    042     * @author Rey Francois
    043     * @author Gregor Rayman
    044     * @author Jan Sorensen
    045     * @author Robert Burrell Donkin
    046     * @author Rodney Waldhoff
    047     * @version $Revision: 555824 $ $Date: 2007-07-13 01:27:15 +0100 (Fri, 13 Jul 2007) $
    048     */
    049    public class ConstructorUtils {
    050
    051        // --------------------------------------------------------- Private Members
    052        /** An empty class array */
    053        private static final Class[] EMPTY_CLASS_PARAMETERS = new Class[0];
    054        /** An empty object array */
    055        private static final Object[] EMPTY_OBJECT_ARRAY = new Object[0];
    056
    057        // --------------------------------------------------------- Public Methods
    058
    059        /**
    060         * <p>Convenience method returning new instance of <code>klazz</code> using a single argument constructor.
    061         * The formal parameter type is inferred from the actual values of <code>arg</code>.
    062         * See {@link #invokeExactConstructor(Class, Object[], Class[])} for more details.</p>
    063         *
    064         * <p>The signatures should be assignment compatible.</p>
    065         *
    066         * @param klass the class to be constructed.
    067         * @param arg the actual argument
    068         * @return new instance of <code>klazz</code>
    069         *
    070         * @throws NoSuchMethodException If the constructor cannot be found
    071         * @throws IllegalAccessException If an error occurs accessing the constructor
    072         * @throws InvocationTargetException If an error occurs invoking the constructor
    073         * @throws InstantiationException If an error occurs instantiating the class
    074         *
    075         * @see #invokeConstructor(java.lang.Class, java.lang.Object[], java.lang.Class[])
    076         */
    077        public static Object invokeConstructor(Class klass, Object arg)
    078            throws
    079                NoSuchMethodException,
    080                IllegalAccessException,
    081                InvocationTargetException,
    082                InstantiationException {
    083
    084            Object[] args = { arg };
    085            return invokeConstructor(klass, args);
    086
    087        }
    088
    089        /**
    090         * <p>Returns new instance of <code>klazz</code> created using the actual arguments <code>args</code>.
    091         * The formal parameter types are inferred from the actual values of <code>args</code>.
    092         * See {@link #invokeExactConstructor(Class, Object[], Class[])} for more details.</p>
    093         *
    094         * <p>The signatures should be assignment compatible.</p>
    095         *
    096         * @param klass the class to be constructed.
    097         * @param args actual argument array
    098         * @return new instance of <code>klazz</code>
    099         *
    100         * @throws NoSuchMethodException If the constructor cannot be found
    101         * @throws IllegalAccessException If an error occurs accessing the constructor
    102         * @throws InvocationTargetException If an error occurs invoking the constructor
    103         * @throws InstantiationException If an error occurs instantiating the class
    104         *
    105         * @see #invokeConstructor(java.lang.Class, java.lang.Object[], java.lang.Class[])
    106         */
    107        public static Object invokeConstructor(Class klass, Object[] args)
    108            throws
    109                NoSuchMethodException,
    110                IllegalAccessException,
    111                InvocationTargetException,
    112                InstantiationException {
    113
    114            if (null == args) {
    115                args = EMPTY_OBJECT_ARRAY;
    116            }
    117            int arguments = args.length;
    118            Class parameterTypes[] = new Class[arguments];
    119            for (int i = 0; i < arguments; i++) {
    120                parameterTypes[i] = args[i].getClass();
    121            }
    122            return invokeConstructor(klass, args, parameterTypes);
    123
    124        }
    125
    126        /**
    127         * <p>Returns new instance of <code>klazz</code> created using constructor
    128         * with signature <code>parameterTypes</code> and actual arguments <code>args</code>.</p>
    129         *
    130         * <p>The signatures should be assignment compatible.</p>
    131         *
    132         * @param klass the class to be constructed.
    133         * @param args actual argument array
    134         * @param parameterTypes parameter types array
    135         * @return new instance of <code>klazz</code>
    136         *
    137         * @throws NoSuchMethodException if matching constructor cannot be found
    138         * @throws IllegalAccessException thrown on the constructor's invocation
    139         * @throws InvocationTargetException thrown on the constructor's invocation
    140         * @throws InstantiationException thrown on the constructor's invocation
    141         * @see Constructor#newInstance
    142         */
    143        public static Object invokeConstructor(
    144            Class klass,
    145            Object[] args,
    146            Class[] parameterTypes)
    147            throws
    148                NoSuchMethodException,
    149                IllegalAccessException,
    150                InvocationTargetException,
    151                InstantiationException {
    152
    153            if (parameterTypes == null) {
    154                parameterTypes = EMPTY_CLASS_PARAMETERS;
    155            }
    156            if (args == null) {
    157                args = EMPTY_OBJECT_ARRAY;
    158            }
    159
    160            Constructor ctor =
    161                getMatchingAccessibleConstructor(klass, parameterTypes);
    162            if (null == ctor) {
    163                throw new NoSuchMethodException(
    164                    "No such accessible constructor on object: " + klass.getName());
    165            }
    166            return ctor.newInstance(args);
    167        }
    168
    169
    170        /**
    171         * <p>Convenience method returning new instance of <code>klazz</code> using a single argument constructor.
    172         * The formal parameter type is inferred from the actual values of <code>arg</code>.
    173         * See {@link #invokeExactConstructor(Class, Object[], Class[])} for more details.</p>
    174         *
    175         * <p>The signatures should match exactly.</p>
    176         *
    177         * @param klass the class to be constructed.
    178         * @param arg the actual argument
    179         * @return new instance of <code>klazz</code>
    180         *
    181         * @throws NoSuchMethodException If the constructor cannot be found
    182         * @throws IllegalAccessException If an error occurs accessing the constructor
    183         * @throws InvocationTargetException If an error occurs invoking the constructor
    184         * @throws InstantiationException If an error occurs instantiating the class
    185         *
    186         * @see #invokeExactConstructor(java.lang.Class, java.lang.Object[], java.lang.Class[])
    187         */
    188        public static Object invokeExactConstructor(Class klass, Object arg)
    189            throws
    190                NoSuchMethodException,
    191                IllegalAccessException,
    192                InvocationTargetException,
    193                InstantiationException {
    194
    195            Object[] args = { arg };
    196            return invokeExactConstructor(klass, args);
    197
    198        }
    199
    200        /**
    201         * <p>Returns new instance of <code>klazz</code> created using the actual arguments <code>args</code>.
    202         * The formal parameter types are inferred from the actual values of <code>args</code>.
    203         * See {@link #invokeExactConstructor(Class, Object[], Class[])} for more details.</p>
    204         *
    205         * <p>The signatures should match exactly.</p>
    206         *
    207         * @param klass the class to be constructed.
    208         * @param args actual argument array
    209         * @return new instance of <code>klazz</code>
    210         *
    211         * @throws NoSuchMethodException If the constructor cannot be found
    212         * @throws IllegalAccessException If an error occurs accessing the constructor
    213         * @throws InvocationTargetException If an error occurs invoking the constructor
    214         * @throws InstantiationException If an error occurs instantiating the class
    215         *
    216         * @see #invokeExactConstructor(java.lang.Class, java.lang.Object[], java.lang.Class[])
    217         */
    218        public static Object invokeExactConstructor(Class klass, Object[] args)
    219            throws
    220                NoSuchMethodException,
    221                IllegalAccessException,
    222                InvocationTargetException,
    223                InstantiationException {
    224            if (null == args) {
    225                args = EMPTY_OBJECT_ARRAY;
    226            }
    227            int arguments = args.length;
    228            Class parameterTypes[] = new Class[arguments];
    229            for (int i = 0; i < arguments; i++) {
    230                parameterTypes[i] = args[i].getClass();
    231            }
    232            return invokeExactConstructor(klass, args, parameterTypes);
    233
    234        }
    235
    236        /**
    237         * <p>Returns new instance of <code>klazz</code> created using constructor
    238         * with signature <code>parameterTypes</code> and actual arguments
    239         * <code>args</code>.</p>
    240         *
    241         * <p>The signatures should match exactly.</p>
    242         *
    243         * @param klass the class to be constructed.
    244         * @param args actual argument array
    245         * @param parameterTypes parameter types array
    246         * @return new instance of <code>klazz</code>
    247         *
    248         * @throws NoSuchMethodException if matching constructor cannot be found
    249         * @throws IllegalAccessException thrown on the constructor's invocation
    250         * @throws InvocationTargetException thrown on the constructor's invocation
    251         * @throws InstantiationException thrown on the constructor's invocation
    252         * @see Constructor#newInstance
    253         */
    254        public static Object invokeExactConstructor(
    255            Class klass,
    256            Object[] args,
    257            Class[] parameterTypes)
    258            throws
    259                NoSuchMethodException,
    260                IllegalAccessException,
    261                InvocationTargetException,
    262                InstantiationException {
    263
    264            if (args == null) {
    265                args = EMPTY_OBJECT_ARRAY;
    266            }
    267
    268            if (parameterTypes == null) {
    269                parameterTypes = EMPTY_CLASS_PARAMETERS;
    270            }
    271
    272            Constructor ctor = getAccessibleConstructor(klass, parameterTypes);
    273            if (null == ctor) {
    274                throw new NoSuchMethodException(
    275                    "No such accessible constructor on object: " + klass.getName());
    276            }
    277            return ctor.newInstance(args);
    278
    279        }
    280
    281        /**
    282         * Returns a constructor with single argument.
    283         * @param klass the class to be constructed
    284         * @param parameterType The constructor parameter type
    285         * @return null if matching accessible constructor can not be found.
    286         * @see Class#getConstructor
    287         * @see #getAccessibleConstructor(java.lang.reflect.Constructor)
    288         */
    289        public static Constructor getAccessibleConstructor(
    290            Class klass,
    291            Class parameterType) {
    292
    293            Class[] parameterTypes = { parameterType };
    294            return getAccessibleConstructor(klass, parameterTypes);
    295
    296        }
    297
    298        /**
    299         * Returns a constructor given a class and signature.
    300         * @param klass the class to be constructed
    301         * @param parameterTypes the parameter array
    302         * @return null if matching accessible constructor can not be found
    303         * @see Class#getConstructor
    304         * @see #getAccessibleConstructor(java.lang.reflect.Constructor)
    305         */
    306        public static Constructor getAccessibleConstructor(
    307            Class klass,
    308            Class[] parameterTypes) {
    309
    310            try {
    311                return getAccessibleConstructor(
    312                    klass.getConstructor(parameterTypes));
    313            } catch (NoSuchMethodException e) {
    314                return (null);
    315            }
    316
    317        }
    318
    319        /**
    320         * Returns accessible version of the given constructor.
    321         * @param ctor prototype constructor object.
    322         * @return <code>null</code> if accessible constructor can not be found.
    323         * @see java.lang.SecurityManager
    324         */
    325        public static Constructor getAccessibleConstructor(Constructor ctor) {
    326
    327            // Make sure we have a method to check
    328            if (ctor == null) {
    329                return (null);
    330            }
    331
    332            // If the requested method is not public we cannot call it
    333            if (!Modifier.isPublic(ctor.getModifiers())) {
    334                return (null);
    335            }
    336
    337            // If the declaring class is public, we are done
    338            Class clazz = ctor.getDeclaringClass();
    339            if (Modifier.isPublic(clazz.getModifiers())) {
    340                return (ctor);
    341            }
    342
    343            // what else can we do?
    344            return null;
    345
    346        }
    347
    348        // -------------------------------------------------------- Private Methods
    349        /**
    350         * <p>Find an accessible constructor with compatible parameters.
    351         * Compatible parameters mean that every method parameter is assignable from
    352         * the given parameters. In other words, it finds constructor that will take
    353         * the parameters given.</p>
    354         *
    355         * <p>First it checks if there is constructor matching the exact signature.
    356         * If no such, all the constructors of the class are tested if their signatures
    357         * are assignment compatible with the parameter types.
    358         * The first matching constructor is returned.</p>
    359         *
    360         * @param clazz find constructor for this class
    361         * @param parameterTypes find method with compatible parameters
    362         * @return a valid Constructor object. If there's no matching constructor, returns <code>null</code>.
    363         */
    364        private static Constructor getMatchingAccessibleConstructor(
    365            Class clazz,
    366            Class[] parameterTypes) {
    367            // see if we can find the method directly
    368            // most of the time this works and it's much faster
    369            try {
    370                Constructor ctor = clazz.getConstructor(parameterTypes);
    371                try {
    372                    //
    373                    // XXX Default access superclass workaround
    374                    //
    375                    // When a public class has a default access superclass
    376                    // with public methods, these methods are accessible.
    377                    // Calling them from compiled code works fine.
    378                    //
    379                    // Unfortunately, using reflection to invoke these methods
    380                    // seems to (wrongly) to prevent access even when the method
    381                    // modifer is public.
    382                    //
    383                    // The following workaround solves the problem but will only
    384                    // work from sufficiently privilages code.
    385                    //
    386                    // Better workarounds would be greatfully accepted.
    387                    //
    388                    ctor.setAccessible(true);
    389                } catch (SecurityException se) {
    390                    /* SWALLOW, if workaround fails don't fret. */
    391                }
    392                return ctor;
    393
    394            } catch (NoSuchMethodException e) { /* SWALLOW */
    395            }
    396
    397            // search through all methods
    398            int paramSize = parameterTypes.length;
    399            Constructor[] ctors = clazz.getConstructors();
    400            for (int i = 0, size = ctors.length; i < size; i++) {
    401                // compare parameters
    402                Class[] ctorParams = ctors[i].getParameterTypes();
    403                int ctorParamSize = ctorParams.length;
    404                if (ctorParamSize == paramSize) {
    405                    boolean match = true;
    406                    for (int n = 0; n < ctorParamSize; n++) {
    407                        if (!MethodUtils
    408                            .isAssignmentCompatible(
    409                                ctorParams[n],
    410                                parameterTypes[n])) {
    411                            match = false;
    412                            break;
    413                        }
    414                    }
    415
    416                    if (match) {
    417                        // get accessible version of method
    418                        Constructor ctor = getAccessibleConstructor(ctors[i]);
    419                        if (ctor != null) {
    420                            try {
    421                                ctor.setAccessible(true);
    422                            } catch (SecurityException se) {
    423                                /* Swallow SecurityException
    424                                 * TODO: Why?
    425                                 */
    426                            }
    427                            return ctor;
    428                        }
    429                    }
    430                }
    431            }
    432
    433            return null;
    434        }
    435
    436    }