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