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}