    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 *      https://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 */
    017package org.apache.commons.lang3.reflect;
    018
    019import java.lang.reflect.Constructor;
    020import java.lang.reflect.InvocationTargetException;
    021import java.util.Objects;
    022
    023import org.apache.commons.lang3.ArrayUtils;
    024import org.apache.commons.lang3.ClassUtils;
    025
    026/**
    027 *  Utility reflection methods focused on constructors, modeled after
    028 * {@link MethodUtils}.
    029 *
    030 * <h2>Known Limitations</h2>
    031 * <h3>Accessing Public Constructors In A Default Access Superclass</h3>
    032 * <p>There is an issue when invoking {@code public} constructors
    033 * contained in a default access superclass. Reflection correctly locates these
    034 * constructors and assigns them as {@code public}. However, an
    035 * {@link IllegalAccessException} is thrown if the constructor is
    036 * invoked.</p>
    037 *
    038 * <p>{@link ConstructorUtils} contains a workaround for this situation: it
    039 * will attempt to call {@link java.lang.reflect.AccessibleObject#setAccessible(boolean)} on this constructor. If this
    040 * call succeeds, then the method can be invoked as normal. This call will only
    041 * succeed when the application has sufficient security privileges. If this call
    042 * fails then a warning will be logged and the method may fail.</p>
    043 *
    044 * @since 2.5
    045 */
    046public class ConstructorUtils {
    047
    048    /**
    049     * Finds a constructor given a class and signature, checking accessibility.
    050     *
    051     * <p>This finds the constructor and ensures that it is accessible.
    052     * The constructor signature must match the parameter types exactly.</p>
    053     *
    054     * @param <T> the constructor type
    055     * @param cls the class to find a constructor for, not {@code null}
    056     * @param parameterTypes the array of parameter types, {@code null} treated as empty
    057     * @return the constructor, {@code null} if no matching accessible constructor found
    058     * @see Class#getConstructor
    059     * @see #getAccessibleConstructor(java.lang.reflect.Constructor)
    060     * @throws NullPointerException if {@code cls} is {@code null}
    061     */
    062    public static <T> Constructor<T> getAccessibleConstructor(final Class<T> cls,
    063            final Class<?>... parameterTypes) {
    064        Objects.requireNonNull(cls, "cls");
    065        try {
    066            return getAccessibleConstructor(cls.getConstructor(parameterTypes));
    067        } catch (final NoSuchMethodException e) {
    068            return null;
    069        }
    070    }
    071
    072    /**
    073     * Checks if the specified constructor is accessible.
    074     *
    075     * <p>This simply ensures that the constructor is accessible.</p>
    076     *
    077     * @param <T> the constructor type
    078     * @param ctor  the prototype constructor object, not {@code null}
    079     * @return the constructor, {@code null} if no matching accessible constructor found
    080     * @see SecurityManager
    081     * @throws NullPointerException if {@code ctor} is {@code null}
    082     */
    083    public static <T> Constructor<T> getAccessibleConstructor(final Constructor<T> ctor) {
    084        Objects.requireNonNull(ctor, "ctor");
    085        return MemberUtils.isAccessible(ctor)
    086                && isAccessible(ctor.getDeclaringClass()) ? ctor : null;
    087    }
    088
    089    /**
    090     * Finds an accessible constructor with compatible parameters.
    091     *
    092     * <p>This checks all the constructor and finds one with compatible parameters
    093     * This requires that every parameter is assignable from the given parameter types.
    094     * This is a more flexible search than the normal exact matching algorithm.</p>
    095     *
    096     * <p>First it checks if there is a constructor matching the exact signature.
    097     * If not then all the constructors of the class are checked to see if their
    098     * signatures are assignment-compatible with the parameter types.
    099     * The first assignment-compatible matching constructor is returned.</p>
    100     *
    101     * @param <T> the constructor type
    102     * @param cls  the class to find a constructor for, not {@code null}
    103     * @param parameterTypes find method with compatible parameters
    104     * @return the constructor, null if no matching accessible constructor found
    105     * @throws NullPointerException if {@code cls} is {@code null}
    106     * @throws SecurityException if an underlying accessible object's method denies the request.
    107     * @see SecurityManager#checkPermission
    108     */
    109    public static <T> Constructor<T> getMatchingAccessibleConstructor(final Class<T> cls,
    110            final Class<?>... parameterTypes) {
    111        Objects.requireNonNull(cls, "cls");
    112        // see if we can find the constructor directly
    113        // most of the time this works and it's much faster
    114        try {
    115            return MemberUtils.setAccessibleWorkaround(cls.getConstructor(parameterTypes));
    116        } catch (final NoSuchMethodException ignored) {
    117            // ignore
    118        }
    119        Constructor<T> result = null;
    120        /*
    121         * (1) Class.getConstructors() is documented to return Constructor<T> so as
    122         * long as the array is not subsequently modified, everything's fine.
    123         */
    124        final Constructor<?>[] ctors = cls.getConstructors();
    125
    126        // return best match:
    127        for (Constructor<?> ctor : ctors) {
    128            // compare parameters
    129            if (MemberUtils.isMatchingConstructor(ctor, parameterTypes)) {
    130                // get accessible version of constructor
    131                ctor = getAccessibleConstructor(ctor);
    132                if (ctor != null) {
    133                    MemberUtils.setAccessibleWorkaround(ctor);
    134                    if (result == null || MemberUtils.compareConstructorFit(ctor, result, parameterTypes) < 0) {
    135                        // temporary variable for annotation, see comment above (1)
    136                        @SuppressWarnings("unchecked")
    137                        final Constructor<T> constructor = (Constructor<T>) ctor;
    138                        result = constructor;
    139                    }
    140                }
    141            }
    142        }
    143        return result;
    144    }
    145
    146    /**
    147     * Returns a new instance of the specified class inferring the right constructor
    148     * from the types of the arguments.
    149     *
    150     * <p>This locates and calls a constructor.
    151     * The constructor signature must match the argument types by assignment compatibility.</p>
    152     *
    153     * @param <T> the type to be constructed
    154     * @param cls  the class to be constructed, not {@code null}
    155     * @param args  the array of arguments, {@code null} treated as empty
    156     * @return new instance of {@code cls}, not {@code null}
    157     * @throws NullPointerException if {@code cls} is {@code null}
    158     * @throws NoSuchMethodException if a matching constructor cannot be found
    159     * @throws IllegalAccessException if invocation is not permitted by security
    160     * @throws InvocationTargetException if an error occurs on invocation
    161     * @throws InstantiationException if an error occurs on instantiation
    162     * @throws SecurityException if an underlying accessible object's method denies the request.
    163     * @see SecurityManager#checkPermission
    164     * @see #invokeConstructor(Class, Object[], Class[])
    165     */
    166    public static <T> T invokeConstructor(final Class<T> cls, Object... args)
    167            throws NoSuchMethodException, IllegalAccessException, InvocationTargetException,
    168            InstantiationException {
    169        args = ArrayUtils.nullToEmpty(args);
    170        return invokeConstructor(cls, args, ClassUtils.toClass(args));
    171    }
    172
    173    /**
    174     * Returns a new instance of the specified class choosing the right constructor
    175     * from the list of parameter types.
    176     *
    177     * <p>This locates and calls a constructor.
    178     * The constructor signature must match the parameter types by assignment compatibility.</p>
    179     *
    180     * @param <T> the type to be constructed
    181     * @param cls  the class to be constructed, not {@code null}
    182     * @param args  the array of arguments, {@code null} treated as empty
    183     * @param parameterTypes  the array of parameter types, {@code null} treated as empty
    184     * @return new instance of {@code cls}, not {@code null}
    185     * @throws NullPointerException if {@code cls} is {@code null}
    186     * @throws NoSuchMethodException if a matching constructor cannot be found
    187     * @throws IllegalAccessException if invocation is not permitted by security
    188     * @throws InvocationTargetException if an error occurs on invocation
    189     * @throws InstantiationException if an error occurs on instantiation
    190     * @throws SecurityException if an underlying accessible object's method denies the request.
    191     * @see SecurityManager#checkPermission
    192     * @see Constructor#newInstance
    193     */
    194    public static <T> T invokeConstructor(final Class<T> cls, Object[] args, Class<?>[] parameterTypes)
    195            throws NoSuchMethodException, IllegalAccessException, InvocationTargetException,
    196            InstantiationException {
    197        args = ArrayUtils.nullToEmpty(args);
    198        parameterTypes = ArrayUtils.nullToEmpty(parameterTypes);
    199        final Constructor<T> ctor = getMatchingAccessibleConstructor(cls, parameterTypes);
    200        if (ctor == null) {
    201            throw new NoSuchMethodException(
    202                "No such accessible constructor on object: " + cls.getName());
    203        }
    204        if (ctor.isVarArgs()) {
    205            final Class<?>[] methodParameterTypes = ctor.getParameterTypes();
    206            args = MethodUtils.getVarArgs(args, methodParameterTypes);
    207        }
    208        return ctor.newInstance(args);
    209    }
    210
    211    /**
    212     * Returns a new instance of the specified class inferring the right constructor
    213     * from the types of the arguments.
    214     *
    215     * <p>This locates and calls a constructor.
    216     * The constructor signature must match the argument types exactly.</p>
    217     *
    218     * @param <T> the type to be constructed
    219     * @param cls the class to be constructed, not {@code null}
    220     * @param args the array of arguments, {@code null} treated as empty
    221     * @return new instance of {@code cls}, not {@code null}
    222     * @throws NullPointerException if {@code cls} is {@code null}
    223     * @throws NoSuchMethodException if a matching constructor cannot be found
    224     * @throws IllegalAccessException if invocation is not permitted by security
    225     * @throws InvocationTargetException if an error occurs on invocation
    226     * @throws InstantiationException if an error occurs on instantiation
    227     * @see #invokeExactConstructor(Class, Object[], Class[])
    228     */
    229    public static <T> T invokeExactConstructor(final Class<T> cls, Object... args)
    230            throws NoSuchMethodException, IllegalAccessException, InvocationTargetException,
    231            InstantiationException {
    232        args = ArrayUtils.nullToEmpty(args);
    233        return invokeExactConstructor(cls, args, ClassUtils.toClass(args));
    234    }
    235
    236    /**
    237     * Returns a new instance of the specified class choosing the right constructor
    238     * from the list of parameter types.
    239     *
    240     * <p>This locates and calls a constructor.
    241     * The constructor signature must match the parameter types exactly.</p>
    242     *
    243     * @param <T> the type to be constructed
    244     * @param cls the class to be constructed, not {@code null}
    245     * @param args the array of arguments, {@code null} treated as empty
    246     * @param parameterTypes  the array of parameter types, {@code null} treated as empty
    247     * @return new instance of {@code cls}, not {@code null}
    248     * @throws NullPointerException if {@code cls} is {@code null}
    249     * @throws NoSuchMethodException if a matching constructor cannot be found
    250     * @throws IllegalAccessException if invocation is not permitted by security
    251     * @throws InvocationTargetException if an error occurs on invocation
    252     * @throws InstantiationException if an error occurs on instantiation
    253     * @see Constructor#newInstance
    254     */
    255    public static <T> T invokeExactConstructor(final Class<T> cls, Object[] args,
    256            Class<?>[] parameterTypes) throws NoSuchMethodException, IllegalAccessException,
    257            InvocationTargetException, InstantiationException {
    258        args = ArrayUtils.nullToEmpty(args);
    259        parameterTypes = ArrayUtils.nullToEmpty(parameterTypes);
    260        final Constructor<T> ctor = getAccessibleConstructor(cls, parameterTypes);
    261        if (ctor == null) {
    262            throw new NoSuchMethodException("No such accessible constructor on object: " + cls.getName());
    263        }
    264        return ctor.newInstance(args);
    265    }
    266
    267    /**
    268     * Tests whether the specified class is generally accessible, i.e. is
    269     * declared in an entirely {@code public} manner.
    270     * @param type to check
    271     * @return {@code true} if {@code type} and any enclosing classes are
    272     *         {@code public}.
    273     */
    274    private static boolean isAccessible(final Class<?> type) {
    275        Class<?> cls = type;
    276        while (cls != null) {
    277            if (!ClassUtils.isPublic(cls)) {
    278                return false;
    279            }
    280            cls = cls.getEnclosingClass();
    281        }
    282        return true;
    283    }
    284
    285    /**
    286     * ConstructorUtils instances should NOT be constructed in standard
    287     * programming. Instead, the class should be used as
    288     * {@code ConstructorUtils.invokeConstructor(cls, args)}.
    289     *
    290     * <p>This constructor is {@code public} to permit tools that require a JavaBean
    291     * instance to operate.</p>
    292     *
    293     * @deprecated TODO Make private in 4.0.
    294     */
    295    @Deprecated
    296    public ConstructorUtils() {
    297        // empty
    298    }
    299
    300}