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.annotation.Annotation;
020import java.lang.reflect.Array;
021import java.lang.reflect.Executable;
022import java.lang.reflect.InvocationTargetException;
023import java.lang.reflect.Method;
024import java.lang.reflect.Type;
025import java.lang.reflect.TypeVariable;
026import java.util.ArrayList;
027import java.util.Arrays;
028import java.util.Collections;
029import java.util.Comparator;
030import java.util.Iterator;
031import java.util.LinkedHashSet;
032import java.util.List;
033import java.util.Map;
034import java.util.Objects;
035import java.util.Set;
036import java.util.TreeMap;
037import java.util.stream.Collectors;
038import java.util.stream.Stream;
039
040import org.apache.commons.lang3.ArrayUtils;
041import org.apache.commons.lang3.ClassUtils;
042import org.apache.commons.lang3.ClassUtils.Interfaces;
043import org.apache.commons.lang3.Validate;
044import org.apache.commons.lang3.stream.LangCollectors;
045import org.apache.commons.lang3.stream.Streams;
046
047/**
048 * Utility reflection methods focused on {@link Method}s, originally from Commons BeanUtils.
049 * Differences from the BeanUtils version may be noted, especially where similar functionality
050 * already existed within Lang.
051 *
052 * <h2>Known Limitations</h2>
053 * <h3>Accessing Public Methods In A Default Access Superclass</h3>
054 * <p>There is an issue when invoking {@code public} methods contained in a default access superclass on JREs prior to 1.4.
055 * Reflection locates these methods fine and correctly assigns them as {@code public}.
056 * However, an {@link IllegalAccessException} is thrown if the method is invoked.</p>
057 *
058 * <p>
059 * {@link MethodUtils} contains a workaround for this situation.
060 * It will attempt to call {@link java.lang.reflect.AccessibleObject#setAccessible(boolean)} on this method.
061 * If this call succeeds, then the method can be invoked as normal.
062 * This call will only succeed when the application has sufficient security privileges.
063 * If this call fails then the method may fail.
064 * </p>
065 *
066 * @since 2.5
067 */
068public class MethodUtils {
069
070    private static final Comparator<Method> METHOD_BY_SIGNATURE = Comparator.comparing(Method::toString);
071
072    /**
073     * Computes the aggregate number of inheritance hops between assignable argument class types.  Returns -1
074     * if the arguments aren't assignable.  Fills a specific purpose for getMatchingMethod and is not generalized.
075     *
076     * @param fromClassArray the Class array to calculate the distance from.
077     * @param toClassArray the Class array to calculate the distance to.
078     * @return the aggregate number of inheritance hops between assignable argument class types.
079     */
080    private static int distance(final Class<?>[] fromClassArray, final Class<?>[] toClassArray) {
081        int answer = 0;
082        if (!ClassUtils.isAssignable(fromClassArray, toClassArray, true)) {
083            return -1;
084        }
085        for (int offset = 0; offset < fromClassArray.length; offset++) {
086            // Note InheritanceUtils.distance() uses different scoring system.
087            final Class<?> aClass = fromClassArray[offset];
088            final Class<?> toClass = toClassArray[offset];
089            if (aClass == null || aClass.equals(toClass)) {
090                continue;
091            }
092            if (ClassUtils.isAssignable(aClass, toClass, true) && !ClassUtils.isAssignable(aClass, toClass, false)) {
093                answer++;
094            } else {
095                answer += 2;
096            }
097        }
098        return answer;
099    }
100
101    /**
102     * Gets an accessible method (that is, one that can be invoked via reflection) that implements the specified Method. If no such method can be found, return
103     * {@code null}.
104     *
105     * @param cls The implementing class, may be null.
106     * @param method The method that we wish to call, may be null.
107     * @return The accessible method or null.
108     * @since 3.19.0
109     */
110    public static Method getAccessibleMethod(final Class<?> cls, final Method method) {
111        if (!MemberUtils.isPublic(method)) {
112            return null;
113        }
114        // If the declaring class is public, we are done
115        if (ClassUtils.isPublic(cls)) {
116            return method;
117        }
118        final String methodName = method.getName();
119        final Class<?>[] parameterTypes = method.getParameterTypes();
120        // Check the implemented interfaces and subinterfaces
121        final Method method2 = getAccessibleMethodFromInterfaceNest(cls, methodName, parameterTypes);
122        // Check the superclass chain
123        return method2 != null ? method2 : getAccessibleMethodFromSuperclass(cls, methodName, parameterTypes);
124    }
125
126    /**
127     * Gets an accessible method (that is, one that can be invoked via reflection) with given name and parameters. If no such method can be found, return
128     * {@code null}. This is just a convenience wrapper for {@link #getAccessibleMethod(Method)}.
129     *
130     * @param cls            get method from this class.
131     * @param methodName     get method with this name.
132     * @param parameterTypes with these parameters types.
133     * @return The accessible method.
134     */
135    public static Method getAccessibleMethod(final Class<?> cls, final String methodName, final Class<?>... parameterTypes) {
136        return getAccessibleMethod(getMethodObject(cls, methodName, parameterTypes));
137    }
138
139    /**
140     * Gets an accessible method (that is, one that can be invoked via reflection) that implements the specified Method. If no such method can be found, return
141     * {@code null}.
142     *
143     * @param method The method that we wish to call, may be null.
144     * @return The accessible method
145     */
146    public static Method getAccessibleMethod(final Method method) {
147        return method != null ? getAccessibleMethod(method.getDeclaringClass(), method) : null;
148    }
149
150    /**
151     * Gets an accessible method (that is, one that can be invoked via
152     * reflection) that implements the specified method, by scanning through
153     * all implemented interfaces and subinterfaces. If no such method
154     * can be found, return {@code null}.
155     *
156     * <p>
157     * There isn't any good reason why this method must be {@code private}.
158     * It is because there doesn't seem any reason why other classes should
159     * call this rather than the higher level methods.
160     * </p>
161     *
162     * @param cls Parent class for the interfaces to be checked.
163     * @param methodName Method name of the method we wish to call.
164     * @param parameterTypes The parameter type signatures.
165     * @return the accessible method or {@code null} if not found.
166     */
167    private static Method getAccessibleMethodFromInterfaceNest(Class<?> cls, final String methodName, final Class<?>... parameterTypes) {
168        // Search up the superclass chain
169        for (; cls != null; cls = cls.getSuperclass()) {
170            // Check the implemented interfaces of the parent class
171            final Class<?>[] interfaces = cls.getInterfaces();
172            for (final Class<?> anInterface : interfaces) {
173                // Is this interface public?
174                if (!ClassUtils.isPublic(anInterface)) {
175                    continue;
176                }
177                // Does the method exist on this interface?
178                try {
179                    return anInterface.getDeclaredMethod(methodName, parameterTypes);
180                } catch (final NoSuchMethodException ignored) {
181                    /*
182                     * Swallow, if no method is found after the loop then this method returns null.
183                     */
184                }
185                // Recursively check our parent interfaces
186                final Method method = getAccessibleMethodFromInterfaceNest(anInterface, methodName, parameterTypes);
187                if (method != null) {
188                    return method;
189                }
190            }
191        }
192        return null;
193    }
194
195    /**
196     * Gets an accessible method (that is, one that can be invoked via
197     * reflection) by scanning through the superclasses. If no such method
198     * can be found, return {@code null}.
199     *
200     * @param cls Class to be checked.
201     * @param methodName Method name of the method we wish to call.
202     * @param parameterTypes The parameter type signatures.
203     * @return the accessible method or {@code null} if not found.
204     */
205    private static Method getAccessibleMethodFromSuperclass(final Class<?> cls, final String methodName, final Class<?>... parameterTypes) {
206        Class<?> parentClass = cls.getSuperclass();
207        while (parentClass != null) {
208            if (ClassUtils.isPublic(parentClass)) {
209                return getMethodObject(parentClass, methodName, parameterTypes);
210            }
211            parentClass = parentClass.getSuperclass();
212        }
213        return null;
214    }
215
216    /**
217     * Gets a combination of {@link ClassUtils#getAllSuperclasses(Class)} and {@link ClassUtils#getAllInterfaces(Class)}, one from superclasses, one from
218     * interfaces, and so on in a breadth first way.
219     *
220     * @param cls the class to look up, may be {@code null}.
221     * @return the combined {@link List} of superclasses and interfaces in order going up from this one {@code null} if null input.
222     */
223    private static List<Class<?>> getAllSuperclassesAndInterfaces(final Class<?> cls) {
224        if (cls == null) {
225            return null;
226        }
227        final List<Class<?>> allSuperClassesAndInterfaces = new ArrayList<>();
228        final List<Class<?>> allSuperclasses = ClassUtils.getAllSuperclasses(cls);
229        int superClassIndex = 0;
230        final List<Class<?>> allInterfaces = ClassUtils.getAllInterfaces(cls);
231        int interfaceIndex = 0;
232        while (interfaceIndex < allInterfaces.size() || superClassIndex < allSuperclasses.size()) {
233            final Class<?> acls;
234            if (interfaceIndex >= allInterfaces.size() || superClassIndex < allSuperclasses.size() && superClassIndex < interfaceIndex) {
235                acls = allSuperclasses.get(superClassIndex++);
236            } else {
237                acls = allInterfaces.get(interfaceIndex++);
238            }
239            allSuperClassesAndInterfaces.add(acls);
240        }
241        return allSuperClassesAndInterfaces;
242    }
243
244    /**
245     * Gets the annotation object with the given annotation type that is present on the given method or optionally on any equivalent method in super classes and
246     * interfaces. Returns null if the annotation type was not present.
247     *
248     * <p>
249     * Stops searching for an annotation once the first annotation of the specified type has been found. Additional annotations of the specified type will be
250     * silently ignored.
251     * </p>
252     *
253     * @param <A>           the annotation type.
254     * @param method        the {@link Method} to query, may be null.
255     * @param annotationCls the {@link Annotation} to check if is present on the method.
256     * @param searchSupers  determines if a lookup in the entire inheritance hierarchy of the given class is performed if the annotation was not directly
257     *                      present.
258     * @param ignoreAccess  determines if underlying method has to be accessible.
259     * @return the first matching annotation, or {@code null} if not found.
260     * @throws NullPointerException if either the method or annotation class is {@code null}.
261     * @throws SecurityException    if an underlying accessible object's method denies the request.
262     * @see SecurityManager#checkPermission
263     * @since 3.6
264     */
265    public static <A extends Annotation> A getAnnotation(final Method method, final Class<A> annotationCls, final boolean searchSupers,
266            final boolean ignoreAccess) {
267        Objects.requireNonNull(method, "method");
268        Objects.requireNonNull(annotationCls, "annotationCls");
269        if (!ignoreAccess && !MemberUtils.isAccessible(method)) {
270            return null;
271        }
272        A annotation = method.getAnnotation(annotationCls);
273        if (annotation == null && searchSupers) {
274            final Class<?> mcls = method.getDeclaringClass();
275            final List<Class<?>> classes = getAllSuperclassesAndInterfaces(mcls);
276            for (final Class<?> acls : classes) {
277                final Method equivalentMethod = ignoreAccess ? getMatchingMethod(acls, method.getName(), method.getParameterTypes())
278                        : getMatchingAccessibleMethod(acls, method.getName(), method.getParameterTypes());
279                if (equivalentMethod != null) {
280                    annotation = equivalentMethod.getAnnotation(annotationCls);
281                    if (annotation != null) {
282                        break;
283                    }
284                }
285            }
286        }
287        return annotation;
288    }
289
290    private static Method getInvokeMethod(final boolean forceAccess, final String methodName, final Class<?>[] parameterTypes, final Class<? extends Object> cls) {
291        final Method method;
292        if (forceAccess) {
293            method = getMatchingMethod(cls, methodName, parameterTypes);
294            AccessibleObjects.setAccessible(method);
295        } else {
296            method = getMatchingAccessibleMethod(cls, methodName, parameterTypes);
297        }
298        return method;
299    }
300
301    /**
302     * Gets an accessible method that matches the given name and has compatible parameters. Compatible parameters mean that every method parameter is assignable
303     * from the given parameters. In other words, it finds a method with the given name that will take the parameters given.
304     *
305     * <p>
306     * This method is used by {@link #invokeMethod(Object object, String methodName, Object[] args, Class[] parameterTypes)}.
307     * </p>
308     * <p>
309     * This method can match primitive parameter by passing in wrapper classes. For example, a {@link Boolean} will match a primitive {@code boolean} parameter.
310     * </p>
311     *
312     * @param cls            find method in this class.
313     * @param methodName     find method with this name.
314     * @param requestTypes find method with most compatible parameters.
315     * @return The accessible method or null.
316     * @throws SecurityException if an underlying accessible object's method denies the request.
317     * @see SecurityManager#checkPermission
318     */
319    public static Method getMatchingAccessibleMethod(final Class<?> cls, final String methodName, final Class<?>... requestTypes) {
320        final Method candidate = getMethodObject(cls, methodName, requestTypes);
321        if (candidate != null) {
322            return MemberUtils.setAccessibleWorkaround(candidate);
323        }
324        // search through all methods
325        final Method[] methods = cls.getMethods();
326        final List<Method> matchingMethods = Stream.of(methods)
327                .filter(method -> method.getName().equals(methodName) && MemberUtils.isMatchingMethod(method, requestTypes)).collect(Collectors.toList());
328        // Sort methods by signature to force deterministic result
329        matchingMethods.sort(METHOD_BY_SIGNATURE);
330        Method bestMatch = null;
331        for (final Method method : matchingMethods) {
332            // get accessible version of method
333            final Method accessibleMethod = getAccessibleMethod(method);
334            if (accessibleMethod != null && (bestMatch == null || MemberUtils.compareMethodFit(accessibleMethod, bestMatch, requestTypes) < 0)) {
335                bestMatch = accessibleMethod;
336            }
337        }
338        if (bestMatch != null) {
339            MemberUtils.setAccessibleWorkaround(bestMatch);
340            if (bestMatch.isVarArgs()) {
341                final Class<?>[] bestMatchParameterTypes = bestMatch.getParameterTypes();
342                final Class<?> varArgType = bestMatchParameterTypes[bestMatchParameterTypes.length - 1].getComponentType();
343                for (int paramIdx = bestMatchParameterTypes.length - 1; paramIdx < requestTypes.length; paramIdx++) {
344                    final Class<?> parameterType = requestTypes[paramIdx];
345                    if (!ClassUtils.isAssignable(parameterType, varArgType, true)) {
346                        return null;
347                    }
348                }
349            }
350        }
351        return bestMatch;
352    }
353
354    /**
355     * Gets a method whether or not it's accessible. If no such method can be found, return {@code null}.
356     *
357     * @param cls            The class that will be subjected to the method search.
358     * @param methodName     The method that we wish to call.
359     * @param parameterTypes Argument class types.
360     * @throws IllegalStateException if there is no unique result.
361     * @throws NullPointerException  if the class is {@code null}.
362     * @return The method.
363     * @since 3.5
364     */
365    public static Method getMatchingMethod(final Class<?> cls, final String methodName, final Class<?>... parameterTypes) {
366        Objects.requireNonNull(cls, "cls");
367        Validate.notEmpty(methodName, "methodName");
368        final List<Method> methods = Stream.of(cls.getDeclaredMethods())
369                .filter(method -> method.getName().equals(methodName))
370                .collect(Collectors.toList());
371        final List<Class<?>> allSuperclassesAndInterfaces = getAllSuperclassesAndInterfaces(cls);
372        Collections.reverse(allSuperclassesAndInterfaces);
373        allSuperclassesAndInterfaces.stream()
374                .map(Class::getDeclaredMethods)
375                .flatMap(Stream::of)
376                .filter(method -> method.getName().equals(methodName))
377                .forEach(methods::add);
378        for (final Method method : methods) {
379            if (Arrays.deepEquals(method.getParameterTypes(), parameterTypes)) {
380                return method;
381            }
382        }
383        final TreeMap<Integer, List<Method>> candidates = new TreeMap<>();
384        methods.stream()
385            .filter(method -> ClassUtils.isAssignable(parameterTypes, method.getParameterTypes(), true))
386            .forEach(method -> {
387                 final int distance = distance(parameterTypes, method.getParameterTypes());
388                 final List<Method> candidatesAtDistance = candidates.computeIfAbsent(distance, k -> new ArrayList<>());
389                 candidatesAtDistance.add(method);
390        });
391        if (candidates.isEmpty()) {
392            return null;
393        }
394        final List<Method> bestCandidates = candidates.values().iterator().next();
395        if (bestCandidates.size() == 1 || !Objects.equals(bestCandidates.get(0).getDeclaringClass(),
396                bestCandidates.get(1).getDeclaringClass())) {
397            return bestCandidates.get(0);
398        }
399        throw new IllegalStateException(String.format("Found multiple candidates for method %s on class %s : %s",
400                methodName + Stream.of(parameterTypes).map(String::valueOf).collect(Collectors.joining(",", "(", ")")), cls.getName(),
401                bestCandidates.stream().map(Method::toString).collect(Collectors.joining(",", "[", "]"))));
402    }
403
404    /**
405     * Gets a Method, or {@code null} if a checked {@link Class#getMethod(String, Class...) } exception is thrown.
406     *
407     * @param cls            Receiver for {@link Class#getMethod(String, Class...)}.
408     * @param name           the name of the method.
409     * @param parameterTypes the list of parameters.
410     * @return a Method or {@code null}.
411     * @see SecurityManager#checkPermission
412     * @see Class#getMethod(String, Class...)
413     * @since 3.15.0
414     */
415    public static Method getMethodObject(final Class<?> cls, final String name, final Class<?>... parameterTypes) {
416        try {
417            return name != null && cls != null ? cls.getMethod(name, parameterTypes) : null;
418        } catch (final NoSuchMethodException | SecurityException e) {
419            return null;
420        }
421    }
422
423    /**
424     * Gets all class level public methods of the given class that are annotated with the given annotation.
425     *
426     * @param cls           the {@link Class} to query.
427     * @param annotationCls the {@link Annotation} that must be present on a method to be matched.
428     * @return a list of Methods (possibly empty).
429     * @throws NullPointerException if the class or annotation are {@code null}.
430     * @since 3.4
431     */
432    public static List<Method> getMethodsListWithAnnotation(final Class<?> cls, final Class<? extends Annotation> annotationCls) {
433        return getMethodsListWithAnnotation(cls, annotationCls, false, false);
434    }
435
436    /**
437     * Gets all methods of the given class that are annotated with the given annotation.
438     *
439     * @param cls           the {@link Class} to query.
440     * @param annotationCls the {@link Annotation} that must be present on a method to be matched.
441     * @param searchSupers  determines if a lookup in the entire inheritance hierarchy of the given class should be performed.
442     * @param ignoreAccess  determines if non-public methods should be considered.
443     * @return a list of Methods (possibly empty).
444     * @throws NullPointerException if either the class or annotation class is {@code null}.
445     * @since 3.6
446     */
447    public static List<Method> getMethodsListWithAnnotation(final Class<?> cls, final Class<? extends Annotation> annotationCls, final boolean searchSupers,
448            final boolean ignoreAccess) {
449        Objects.requireNonNull(cls, "cls");
450        Objects.requireNonNull(annotationCls, "annotationCls");
451        final List<Class<?>> classes = searchSupers ? getAllSuperclassesAndInterfaces(cls) : new ArrayList<>();
452        classes.add(0, cls);
453        final List<Method> annotatedMethods = new ArrayList<>();
454        classes.forEach(acls -> {
455            final Method[] methods = ignoreAccess ? acls.getDeclaredMethods() : acls.getMethods();
456            Stream.of(methods).filter(method -> method.isAnnotationPresent(annotationCls)).forEachOrdered(annotatedMethods::add);
457        });
458        return annotatedMethods;
459    }
460
461    /**
462     * Gets all class level public methods of the given class that are annotated with the given annotation.
463     *
464     * @param cls           the {@link Class} to query.
465     * @param annotationCls the {@link java.lang.annotation.Annotation} that must be present on a method to be matched.
466     * @return an array of Methods (possibly empty).
467     * @throws NullPointerException if the class or annotation are {@code null}
468     * @since 3.4
469     */
470    public static Method[] getMethodsWithAnnotation(final Class<?> cls, final Class<? extends Annotation> annotationCls) {
471        return getMethodsWithAnnotation(cls, annotationCls, false, false);
472    }
473
474    /**
475     * Gets all methods of the given class that are annotated with the given annotation.
476     *
477     * @param cls           the {@link Class} to query.
478     * @param annotationCls the {@link java.lang.annotation.Annotation} that must be present on a method to be matched.
479     * @param searchSupers  determines if a lookup in the entire inheritance hierarchy of the given class should be performed.
480     * @param ignoreAccess  determines if non-public methods should be considered.
481     * @return an array of Methods (possibly empty).
482     * @throws NullPointerException if the class or annotation are {@code null}.
483     * @since 3.6
484     */
485    public static Method[] getMethodsWithAnnotation(final Class<?> cls, final Class<? extends Annotation> annotationCls, final boolean searchSupers,
486            final boolean ignoreAccess) {
487        return getMethodsListWithAnnotation(cls, annotationCls, searchSupers, ignoreAccess).toArray(ArrayUtils.EMPTY_METHOD_ARRAY);
488    }
489
490    /**
491     * Gets the hierarchy of overridden methods down to {@code result} respecting generics.
492     *
493     * @param method lowest to consider.
494     * @param interfacesBehavior whether to search interfaces, {@code null} {@code implies} false.
495     * @return a {@code Set<Method>} in ascending order from subclass to superclass.
496     * @throws NullPointerException if the specified method is {@code null}.
497     * @throws SecurityException if an underlying accessible object's method denies the request.
498     * @see SecurityManager#checkPermission
499     * @since 3.2
500     */
501    public static Set<Method> getOverrideHierarchy(final Method method, final Interfaces interfacesBehavior) {
502        Objects.requireNonNull(method, "method");
503        final Set<Method> result = new LinkedHashSet<>();
504        result.add(method);
505        final Class<?>[] parameterTypes = method.getParameterTypes();
506        final Class<?> declaringClass = method.getDeclaringClass();
507        final Iterator<Class<?>> hierarchy = ClassUtils.hierarchy(declaringClass, interfacesBehavior).iterator();
508        //skip the declaring class :P
509        hierarchy.next();
510        hierarchyTraversal: while (hierarchy.hasNext()) {
511            final Class<?> c = hierarchy.next();
512            final Method m = getMatchingAccessibleMethod(c, method.getName(), parameterTypes);
513            if (m == null) {
514                continue;
515            }
516            if (Arrays.equals(m.getParameterTypes(), parameterTypes)) {
517                // matches without generics
518                result.add(m);
519                continue;
520            }
521            // necessary to get arguments every time in the case that we are including interfaces
522            final Map<TypeVariable<?>, Type> typeArguments = TypeUtils.getTypeArguments(declaringClass, m.getDeclaringClass());
523            for (int i = 0; i < parameterTypes.length; i++) {
524                final Type childType = TypeUtils.unrollVariables(typeArguments, method.getGenericParameterTypes()[i]);
525                final Type parentType = TypeUtils.unrollVariables(typeArguments, m.getGenericParameterTypes()[i]);
526                if (!TypeUtils.equals(childType, parentType)) {
527                    continue hierarchyTraversal;
528                }
529            }
530            result.add(m);
531        }
532        return result;
533    }
534
535    /**
536     * Invokes a method whose parameter types match exactly the object type.
537     *
538     * <p>
539     * This uses reflection to invoke the method obtained from a call to {@link #getAccessibleMethod(Class, String, Class[])}.
540     * </p>
541     *
542     * @param object     invoke method on this object.
543     * @param methodName get method with this name.
544     * @return The value returned by the invoked method.
545     * @throws NoSuchMethodException       Thrown if there is no such accessible method.
546     * @throws IllegalAccessException      Thrown if this found {@code Method} is enforcing Java language access control and the underlying method is
547     *                                     inaccessible.
548     * @throws IllegalArgumentException    Thrown if:
549     *                                     <ul>
550     *                                     <li>the found {@code Method} is an instance method and the specified {@code object} argument is not an instance of
551     *                                     the class or interface declaring the underlying method (or of a subclass or interface implementor);</li>
552     *                                     <li>the number of actual and formal parameters differ;</li>
553     *                                     </ul>
554     * @throws InvocationTargetException   Thrown if the underlying method throws an exception.
555     * @throws NullPointerException        Thrown if the specified {@code object} is null.
556     * @throws ExceptionInInitializerError Thrown if the initialization provoked by this method fails.
557     * @since 3.4
558     */
559    public static Object invokeExactMethod(final Object object, final String methodName)
560            throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
561        return invokeExactMethod(object, methodName, ArrayUtils.EMPTY_OBJECT_ARRAY, null);
562    }
563
564    /**
565     * Invokes a method whose parameter types match exactly the object types.
566     *
567     * <p>
568     * This uses reflection to invoke the method obtained from a call to {@link #getAccessibleMethod(Class, String, Class[])}.
569     * </p>
570     *
571     * @param object     invoke method on this object.
572     * @param methodName get method with this name.
573     * @param args       use these arguments - treat null as empty array.
574     * @return The value returned by the invoked method.
575     * @throws NoSuchMethodException       Thrown if there is no such accessible method.
576     * @throws IllegalAccessException      Thrown if this found {@code Method} is enforcing Java language access control and the underlying method is
577     *                                     inaccessible.
578     * @throws IllegalArgumentException    Thrown if:
579     *                                     <ul>
580     *                                     <li>the found {@code Method} is an instance method and the specified {@code object} argument is not an instance of
581     *                                     the class or interface declaring the underlying method (or of a subclass or interface implementor);</li>
582     *                                     <li>the number of actual and formal parameters differ;</li>
583     *                                     <li>an unwrapping conversion for primitive arguments fails; or</li>
584     *                                     <li>after possible unwrapping, a parameter value can't be converted to the corresponding formal parameter type by a
585     *                                     method invocation conversion.</li>
586     *                                     </ul>
587     * @throws InvocationTargetException   Thrown if the underlying method throws an exception.
588     * @throws NullPointerException        Thrown if the specified {@code object} is null.
589     * @throws ExceptionInInitializerError Thrown if the initialization provoked by this method fails.
590     */
591    public static Object invokeExactMethod(final Object object, final String methodName, final Object... args)
592            throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
593        final Object[] actuals = ArrayUtils.nullToEmpty(args);
594        return invokeExactMethod(object, methodName, actuals, ClassUtils.toClass(actuals));
595    }
596
597    /**
598     * Invokes a method whose parameter types match exactly the parameter types given.
599     *
600     * <p>
601     * This uses reflection to invoke the method obtained from a call to {@link #getAccessibleMethod(Class, String, Class[])}.
602     * </p>
603     *
604     * @param object         Invokes a method on this object.
605     * @param methodName     Gets a method with this name.
606     * @param args           Method arguments - treat null as empty array.
607     * @param parameterTypes Match these parameters - treat {@code null} as empty array.
608     * @return The value returned by the invoked method.
609     * @throws NoSuchMethodException       Thrown if there is no such accessible method.
610     * @throws IllegalAccessException      Thrown if this found {@code Method} is enforcing Java language access control and the underlying method is
611     *                                     inaccessible.
612     * @throws IllegalArgumentException    Thrown if:
613     *                                     <ul>
614     *                                     <li>the found {@code Method} is an instance method and the specified {@code object} argument is not an instance of
615     *                                     the class or interface declaring the underlying method (or of a subclass or interface implementor);</li>
616     *                                     <li>the number of actual and formal parameters differ;</li>
617     *                                     <li>an unwrapping conversion for primitive arguments fails; or</li>
618     *                                     <li>after possible unwrapping, a parameter value can't be converted to the corresponding formal parameter type by a
619     *                                     method invocation conversion.</li>
620     *                                     </ul>
621     * @throws InvocationTargetException   Thrown if the underlying method throws an exception.
622     * @throws NullPointerException        Thrown if the specified {@code object} is null.
623     * @throws ExceptionInInitializerError Thrown if the initialization provoked by this method fails.
624     */
625    public static Object invokeExactMethod(final Object object, final String methodName, final Object[] args, final Class<?>[] parameterTypes)
626            throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
627        final Class<?> cls = Objects.requireNonNull(object, "object").getClass();
628        final Class<?>[] paramTypes = ArrayUtils.nullToEmpty(parameterTypes);
629        final Method method = getAccessibleMethod(cls, methodName, paramTypes);
630        requireNonNull(method, cls, methodName, paramTypes);
631        return method.invoke(object, ArrayUtils.nullToEmpty(args));
632    }
633
634    /**
635     * Invokes a {@code static} method whose parameter types match exactly the object types.
636     *
637     * <p>
638     * This uses reflection to invoke the method obtained from a call to {@link #getAccessibleMethod(Class, String, Class[])}.
639     * </p>
640     *
641     * @param cls        invoke static method on this class.
642     * @param methodName get method with this name.
643     * @param args       use these arguments - treat {@code null} as empty array.
644     * @return The value returned by the invoked method.
645     * @throws NoSuchMethodException       Thrown if there is no such accessible method.
646     * @throws IllegalAccessException      Thrown if this found {@code Method} is enforcing Java language access control and the underlying method is
647     *                                     inaccessible.
648     * @throws IllegalArgumentException    Thrown if:
649     *                                     <ul>
650     *                                     <li>the found {@code Method} is an instance method and the specified {@code object} argument is not an instance of
651     *                                     the class or interface declaring the underlying method (or of a subclass or interface implementor);</li>
652     *                                     <li>the number of actual and formal parameters differ;</li>
653     *                                     <li>an unwrapping conversion for primitive arguments fails; or</li>
654     *                                     <li>after possible unwrapping, a parameter value can't be converted to the corresponding formal parameter type by a
655     *                                     method invocation conversion.</li>
656     *                                     </ul>
657     * @throws InvocationTargetException   Thrown if the underlying method throws an exception.
658     * @throws ExceptionInInitializerError Thrown if the initialization provoked by this method fails.
659     */
660    public static Object invokeExactStaticMethod(final Class<?> cls, final String methodName, final Object... args)
661            throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
662        final Object[] actuals = ArrayUtils.nullToEmpty(args);
663        return invokeExactStaticMethod(cls, methodName, actuals, ClassUtils.toClass(actuals));
664    }
665
666    /**
667     * Invokes a {@code static} method whose parameter types match exactly the parameter types given.
668     *
669     * <p>
670     * This uses reflection to invoke the method obtained from a call to {@link #getAccessibleMethod(Class, String, Class[])}.
671     * </p>
672     *
673     * @param cls            invoke static method on this class.
674     * @param methodName     get method with this name.
675     * @param args           use these arguments - treat {@code null} as empty array.
676     * @param parameterTypes match these parameters - treat {@code null} as empty array.
677     * @return The value returned by the invoked method.
678     * @throws NoSuchMethodException       Thrown if there is no such accessible method.
679     * @throws IllegalAccessException      Thrown if this found {@code Method} is enforcing Java language access control and the underlying method is
680     *                                     inaccessible.
681     * @throws IllegalArgumentException    Thrown if:
682     *                                     <ul>
683     *                                     <li>the found {@code Method} is an instance method and the specified {@code object} argument is not an instance of
684     *                                     the class or interface declaring the underlying method (or of a subclass or interface implementor);</li>
685     *                                     <li>the number of actual and formal parameters differ;</li>
686     *                                     <li>an unwrapping conversion for primitive arguments fails; or</li>
687     *                                     <li>after possible unwrapping, a parameter value can't be converted to the corresponding formal parameter type by a
688     *                                     method invocation conversion.</li>
689     *                                     </ul>
690     * @throws InvocationTargetException   Thrown if the underlying method throws an exception.
691     * @throws ExceptionInInitializerError Thrown if the initialization provoked by this method fails.
692     */
693    public static Object invokeExactStaticMethod(final Class<?> cls, final String methodName, final Object[] args, final Class<?>[] parameterTypes)
694            throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
695        final Class<?>[] paramTypes = ArrayUtils.nullToEmpty(parameterTypes);
696        final Method method = getAccessibleMethod(cls, methodName, ArrayUtils.nullToEmpty(paramTypes));
697        requireNonNull(method, cls, methodName, paramTypes);
698        return method.invoke(null, ArrayUtils.nullToEmpty(args));
699    }
700
701    /**
702     * Invokes a named method without parameters.
703     *
704     * <p>
705     * This is a convenient wrapper for
706     * {@link #invokeMethod(Object object, boolean forceAccess, String methodName, Object[] args, Class[] parameterTypes)}.
707     * </p>
708     *
709     * @param object invoke method on this object.
710     * @param forceAccess force access to invoke method even if it's not accessible.
711     * @param methodName get method with this name.
712     * @return The value returned by the invoked method.
713     * @throws NoSuchMethodException       Thrown if there is no such accessible method.
714     * @throws IllegalAccessException      Thrown if this found {@code Method} is enforcing Java language access control and the underlying method is
715     *                                     inaccessible.
716     * @throws IllegalArgumentException    Thrown if:
717     *                                     <ul>
718     *                                     <li>the found {@code Method} is an instance method and the specified {@code object} argument is not an instance of
719     *                                     the class or interface declaring the underlying method (or of a subclass or interface implementor);</li>
720     *                                     <li>the number of actual and formal parameters differ;</li>
721     *                                     <li>an unwrapping conversion for primitive arguments fails; or</li>
722     *                                     <li>after possible unwrapping, a parameter value can't be converted to the corresponding formal parameter type by a
723     *                                     method invocation conversion.</li>
724     *                                     </ul>
725     * @throws InvocationTargetException   Thrown if the underlying method throws an exception.
726     * @throws NullPointerException        Thrown if the specified {@code object} is null.
727     * @throws ExceptionInInitializerError Thrown if the initialization provoked by this method fails.
728     * @see SecurityManager#checkPermission
729     * @since 3.5
730     */
731    public static Object invokeMethod(final Object object, final boolean forceAccess, final String methodName)
732            throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
733        return invokeMethod(object, forceAccess, methodName, ArrayUtils.EMPTY_OBJECT_ARRAY, null);
734    }
735
736    /**
737     * Invokes a named method whose parameter type matches the object type.
738     *
739     * <p>
740     * This method supports calls to methods taking primitive parameters
741     * via passing in wrapping classes. So, for example, a {@link Boolean} object
742     * would match a {@code boolean} primitive.
743     * </p>
744     * <p>
745     * This is a convenient wrapper for
746     * {@link #invokeMethod(Object object, boolean forceAccess, String methodName, Object[] args, Class[] parameterTypes)}.
747     * </p>
748     *
749     * @param object invoke method on this object.
750     * @param forceAccess force access to invoke method even if it's not accessible.
751     * @param methodName get method with this name.
752     * @param args use these arguments - treat null as empty array.
753     * @return The value returned by the invoked method.
754     * @throws NoSuchMethodException       Thrown if there is no such accessible method.
755     * @throws IllegalAccessException      Thrown if this found {@code Method} is enforcing Java language access control and the underlying method is
756     *                                     inaccessible.
757     * @throws IllegalArgumentException    Thrown if:
758     *                                     <ul>
759     *                                     <li>the found {@code Method} is an instance method and the specified {@code object} argument is not an instance of
760     *                                     the class or interface declaring the underlying method (or of a subclass or interface implementor);</li>
761     *                                     <li>the number of actual and formal parameters differ;</li>
762     *                                     <li>an unwrapping conversion for primitive arguments fails; or</li>
763     *                                     <li>after possible unwrapping, a parameter value can't be converted to the corresponding formal parameter type by a
764     *                                     method invocation conversion.</li>
765     *                                     </ul>
766     * @throws InvocationTargetException   Thrown if the underlying method throws an exception.
767     * @throws NullPointerException        Thrown if the specified {@code object} is null.
768     * @throws ExceptionInInitializerError Thrown if the initialization provoked by this method fails.
769     * @see SecurityManager#checkPermission
770     * @since 3.5
771     */
772    public static Object invokeMethod(final Object object, final boolean forceAccess, final String methodName, final Object... args)
773            throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
774        final Object[] actuals = ArrayUtils.nullToEmpty(args);
775        return invokeMethod(object, forceAccess, methodName, actuals, ClassUtils.toClass(actuals));
776    }
777
778    /**
779     * Invokes a named method whose parameter type matches the object type.
780     *
781     * <p>
782     * This method supports calls to methods taking primitive parameters
783     * via passing in wrapping classes. So, for example, a {@link Boolean} object
784     * would match a {@code boolean} primitive.
785     * </p>
786     *
787     * @param object invoke method on this object.
788     * @param forceAccess force access to invoke method even if it's not accessible.
789     * @param methodName get method with this name.
790     * @param args use these arguments - treat null as empty array.
791     * @param parameterTypes match these parameters - treat null as empty array.
792     * @return The value returned by the invoked method.
793     * @throws NoSuchMethodException       Thrown if there is no such accessible method.
794     * @throws IllegalAccessException      Thrown if this found {@code Method} is enforcing Java language access control and the underlying method is
795     *                                     inaccessible.
796     * @throws IllegalArgumentException    Thrown if:
797     *                                     <ul>
798     *                                     <li>the found {@code Method} is an instance method and the specified {@code object} argument is not an instance of
799     *                                     the class or interface declaring the underlying method (or of a subclass or interface implementor);</li>
800     *                                     <li>the number of actual and formal parameters differ;</li>
801     *                                     <li>an unwrapping conversion for primitive arguments fails; or</li>
802     *                                     <li>after possible unwrapping, a parameter value can't be converted to the corresponding formal parameter type by a
803     *                                     method invocation conversion.</li>
804     *                                     </ul>
805     * @throws InvocationTargetException   Thrown if the underlying method throws an exception.
806     * @throws NullPointerException        Thrown if the specified {@code object} is null.
807     * @throws ExceptionInInitializerError Thrown if the initialization provoked by this method fails.
808     * @see SecurityManager#checkPermission
809     * @since 3.5
810     */
811    public static Object invokeMethod(final Object object, final boolean forceAccess, final String methodName, final Object[] args, final Class<?>[] parameterTypes)
812            throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
813        final Class<? extends Object> cls = Objects.requireNonNull(object, "object").getClass();
814        final Class<?>[] paramTypes = ArrayUtils.nullToEmpty(parameterTypes);
815        final Method method = getInvokeMethod(forceAccess, methodName, paramTypes, cls);
816        requireNonNull(method, cls, methodName, paramTypes);
817        return method.invoke(object, toVarArgs(method, ArrayUtils.nullToEmpty(args)));
818    }
819
820    /**
821     * Invokes a named method without parameters.
822     *
823     * <p>
824     * This method delegates the method search to {@link #getMatchingAccessibleMethod(Class, String, Class[])}.
825     * </p>
826     * <p>
827     * This is a convenient wrapper for
828     * {@link #invokeMethod(Object object, String methodName, Object[] args, Class[] parameterTypes)}.
829     * </p>
830     *
831     * @param object invoke method on this object.
832     * @param methodName get method with this name.
833     * @return The value returned by the invoked method.
834     * @throws NoSuchMethodException if there is no such accessible method.
835     * @throws InvocationTargetException wraps an exception thrown by the method invoked.
836     * @throws IllegalAccessException if the requested method is not accessible via reflection.
837     * @throws SecurityException if an underlying accessible object's method denies the request.
838     * @see SecurityManager#checkPermission
839     * @since 3.4
840     */
841    public static Object invokeMethod(final Object object, final String methodName) throws NoSuchMethodException,
842            IllegalAccessException, InvocationTargetException {
843        return invokeMethod(object, methodName, ArrayUtils.EMPTY_OBJECT_ARRAY, null);
844    }
845
846    /**
847     * Invokes a named method whose parameter type matches the object type.
848     *
849     * <p>
850     * This method delegates the method search to {@link #getMatchingAccessibleMethod(Class, String, Class[])}.
851     * </p>
852     * <p>
853     * This method supports calls to methods taking primitive parameters
854     * via passing in wrapping classes. So, for example, a {@link Boolean} object
855     * would match a {@code boolean} primitive.
856     * </p>
857     * <p>
858     * This is a convenient wrapper for
859     * {@link #invokeMethod(Object object, String methodName, Object[] args, Class[] parameterTypes)}.
860     * </p>
861     *
862     * @param object invoke method on this object.
863     * @param methodName get method with this name.
864     * @param args use these arguments - treat null as empty array.
865     * @return The value returned by the invoked method.
866     * @throws NoSuchMethodException if there is no such accessible method.
867     * @throws InvocationTargetException wraps an exception thrown by the method invoked.
868     * @throws IllegalAccessException if the requested method is not accessible via reflection.
869     * @throws NullPointerException if the object or method name are {@code null}.
870     * @throws SecurityException if an underlying accessible object's method denies the request.
871     * @see SecurityManager#checkPermission
872     */
873    public static Object invokeMethod(final Object object, final String methodName, final Object... args)
874            throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
875        final Object[] actuals = ArrayUtils.nullToEmpty(args);
876        return invokeMethod(object, methodName, actuals, ClassUtils.toClass(actuals));
877    }
878
879    /**
880     * Invokes a named method whose parameter type matches the object type.
881     *
882     * <p>
883     * This method delegates the method search to {@link #getMatchingAccessibleMethod(Class, String, Class[])}.
884     * </p>
885     * <p>
886     * This method supports calls to methods taking primitive parameters
887     * via passing in wrapping classes. So, for example, a {@link Boolean} object
888     * would match a {@code boolean} primitive.
889     * </p>
890     *
891     * @param object invoke method on this object.
892     * @param methodName get method with this name.
893     * @param args use these arguments - treat null as empty array.
894     * @param parameterTypes match these parameters - treat null as empty array.
895     * @return The value returned by the invoked method.
896     * @throws NoSuchMethodException       Thrown if there is no such accessible method.
897     * @throws IllegalAccessException      Thrown if this found {@code Method} is enforcing Java language access control and the underlying method is
898     *                                     inaccessible.
899     * @throws IllegalArgumentException    Thrown if:
900     *                                     <ul>
901     *                                     <li>the found {@code Method} is an instance method and the specified {@code object} argument is not an instance of
902     *                                     the class or interface declaring the underlying method (or of a subclass or interface implementor);</li>
903     *                                     <li>the number of actual and formal parameters differ;</li>
904     *                                     <li>an unwrapping conversion for primitive arguments fails; or</li>
905     *                                     <li>after possible unwrapping, a parameter value can't be converted to the corresponding formal parameter type by a
906     *                                     method invocation conversion.</li>
907     *                                     </ul>
908     * @throws InvocationTargetException   Thrown if the underlying method throws an exception.
909     * @throws NullPointerException        Thrown if the specified {@code object} is null.
910     * @throws ExceptionInInitializerError Thrown if the initialization provoked by this method fails.
911     * @see SecurityManager#checkPermission
912     */
913    public static Object invokeMethod(final Object object, final String methodName, final Object[] args, final Class<?>[] parameterTypes)
914            throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
915        return invokeMethod(object, false, methodName, args, parameterTypes);
916    }
917
918    /**
919     * Invokes a named {@code static} method whose parameter type matches the object type.
920     *
921     * <p>
922     * This method delegates the method search to {@link #getMatchingAccessibleMethod(Class, String, Class[])}.
923     * </p>
924     * <p>
925     * This method supports calls to methods taking primitive parameters
926     * via passing in wrapping classes. So, for example, a {@link Boolean} class
927     * would match a {@code boolean} primitive.
928     * </p>
929     * <p>
930     * This is a convenient wrapper for
931     * {@link #invokeStaticMethod(Class, String, Object[], Class[])}.
932     * </p>
933     *
934     * @param cls invoke static method on this class.
935     * @param methodName get method with this name.
936     * @param args use these arguments - treat {@code null} as empty array.
937     * @return The value returned by the invoked method.
938     * @throws NoSuchMethodException       Thrown if there is no such accessible method.
939     * @throws IllegalAccessException      Thrown if this found {@code Method} is enforcing Java language access control and the underlying method is
940     *                                     inaccessible.
941     * @throws IllegalArgumentException    Thrown if:
942     *                                     <ul>
943     *                                     <li>the found {@code Method} is an instance method and the specified {@code object} argument is not an instance of
944     *                                     the class or interface declaring the underlying method (or of a subclass or interface implementor);</li>
945     *                                     <li>the number of actual and formal parameters differ;</li>
946     *                                     <li>an unwrapping conversion for primitive arguments fails; or</li>
947     *                                     <li>after possible unwrapping, a parameter value can't be converted to the corresponding formal parameter type by a
948     *                                     method invocation conversion.</li>
949     *                                     </ul>
950     * @throws InvocationTargetException   Thrown if the underlying method throws an exception.
951     * @throws ExceptionInInitializerError Thrown if the initialization provoked by this method fails.
952     * @see SecurityManager#checkPermission
953     */
954    public static Object invokeStaticMethod(final Class<?> cls, final String methodName, final Object... args)
955            throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
956        final Object[] actuals = ArrayUtils.nullToEmpty(args);
957        return invokeStaticMethod(cls, methodName, actuals, ClassUtils.toClass(actuals));
958    }
959
960    /**
961     * Invokes a named {@code static} method whose parameter type matches the object type.
962     *
963     * <p>
964     * This method delegates the method search to {@link #getMatchingAccessibleMethod(Class, String, Class[])}.
965     * </p>
966     * <p>
967     * This method supports calls to methods taking primitive parameters
968     * via passing in wrapping classes. So, for example, a {@link Boolean} class
969     * would match a {@code boolean} primitive.
970     * </p>
971     *
972     * @param cls invoke static method on this class.
973     * @param methodName get method with this name.
974     * @param args use these arguments - treat {@code null} as empty array.
975     * @param parameterTypes match these parameters - treat {@code null} as empty array.
976     * @return The value returned by the invoked method.
977     * @throws NoSuchMethodException       Thrown if there is no such accessible method.
978     * @throws IllegalAccessException      Thrown if this found {@code Method} is enforcing Java language access control and the underlying method is
979     *                                     inaccessible.
980     * @throws IllegalArgumentException    Thrown if:
981     *                                     <ul>
982     *                                     <li>the found {@code Method} is an instance method and the specified {@code object} argument is not an instance of
983     *                                     the class or interface declaring the underlying method (or of a subclass or interface implementor);</li>
984     *                                     <li>the number of actual and formal parameters differ;</li>
985     *                                     <li>an unwrapping conversion for primitive arguments fails; or</li>
986     *                                     <li>after possible unwrapping, a parameter value can't be converted to the corresponding formal parameter type by a
987     *                                     method invocation conversion.</li>
988     *                                     </ul>
989     * @throws InvocationTargetException   Thrown if the underlying method throws an exception.
990     * @throws ExceptionInInitializerError Thrown if the initialization provoked by this method fails.
991     * @see SecurityManager#checkPermission
992     */
993    public static Object invokeStaticMethod(final Class<?> cls, final String methodName, final Object[] args, final Class<?>[] parameterTypes)
994            throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
995        final Class<?>[] paramTypes = ArrayUtils.nullToEmpty(parameterTypes);
996        final Method method = getMatchingAccessibleMethod(cls, methodName, paramTypes);
997        requireNonNull(method, cls, methodName, paramTypes);
998        return method.invoke(null, toVarArgs(method, ArrayUtils.nullToEmpty(args)));
999    }
1000
1001    private static Method requireNonNull(final Method method, final Class<?> cls, final String methodName, final Class<?>[] parameterTypes)
1002            throws NoSuchMethodException {
1003        if (method == null) {
1004            throw new NoSuchMethodException(String.format("No method: %s.%s(%s)", ClassUtils.getName(cls), methodName,
1005                    Streams.of(parameterTypes).map(ClassUtils::getName).collect(LangCollectors.joining(", "))));
1006        }
1007        return method;
1008    }
1009
1010    static Object[] toVarArgs(final Executable executable, final Object[] args)
1011            throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
1012        return executable.isVarArgs() ? toVarArgs(args, executable.getParameterTypes()) : args;
1013    }
1014
1015    /**
1016     * Gets an array of arguments in the canonical form, given an arguments array passed to a varargs method, for example an array with the declared number of
1017     * parameters, and whose last parameter is an array of the varargs type.
1018     * <p>
1019     * We follow the <a href="https://docs.oracle.com/javase/specs/jls/se21/html/jls-5.html#jls-5.1.2">JLS 5.1.2. Widening Primitive Conversion</a> rules.
1020     * </p>
1021     *
1022     * @param args                 the array of arguments passed to the varags method.
1023     * @param methodParameterTypes the declared array of method parameter types.
1024     * @return an array of the variadic arguments passed to the method.
1025     * @throws NoSuchMethodException       Thrown if the constructor could not be found.
1026     * @throws IllegalAccessException      Thrown if this {@code Constructor} object is enforcing Java language access control and the underlying constructor is
1027     *                                     inaccessible.
1028     * @throws IllegalArgumentException    Thrown if the number of actual and formal parameters differ; if an unwrapping conversion for primitive arguments
1029     *                                     fails; or if, after possible unwrapping, a parameter value cannot be converted to the corresponding formal parameter
1030     *                                     type by a method invocation conversion; if this constructor pertains to an enum type.
1031     * @throws InstantiationException      Thrown if a class that declares the underlying constructor represents an abstract class.
1032     * @throws InvocationTargetException   Thrown if an underlying constructor throws an exception.
1033     * @throws ExceptionInInitializerError Thrown if an initialization provoked by this method fails.
1034     * @see <a href="https://docs.oracle.com/javase/specs/jls/se21/html/jls-5.html#jls-5.1.2">JLS 5.1.2. Widening Primitive Conversion</a>
1035     */
1036    private static Object[] toVarArgs(final Object[] args, final Class<?>[] methodParameterTypes)
1037            throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
1038        final int mptLength = methodParameterTypes.length;
1039        if (args.length == mptLength) {
1040            final Object lastArg = args[args.length - 1];
1041            if (lastArg == null || lastArg.getClass().equals(methodParameterTypes[mptLength - 1])) {
1042                // The args array is already in the canonical form for the method.
1043                return args;
1044            }
1045        }
1046        // Construct a new array matching the method's declared parameter types.
1047        // Copy the normal (non-varargs) parameters
1048        final Object[] newArgs = ArrayUtils.arraycopy(args, 0, 0, mptLength - 1, () -> new Object[mptLength]);
1049        // Construct a new array for the variadic parameters
1050        final Class<?> varArgComponentType = methodParameterTypes[mptLength - 1].getComponentType();
1051        final Class<?> varArgComponentWrappedType = ClassUtils.primitiveToWrapper(varArgComponentType);
1052        final int varArgLength = args.length - mptLength + 1;
1053        // Copy the variadic arguments into the varargs array, converting types if needed.
1054        Object varArgsArray = Array.newInstance(varArgComponentWrappedType, varArgLength);
1055        final boolean primitiveOrWrapper = ClassUtils.isPrimitiveOrWrapper(varArgComponentWrappedType);
1056        for (int i = 0; i < varArgLength; i++) {
1057            final Object arg = args[mptLength - 1 + i];
1058            try {
1059                Array.set(varArgsArray, i, primitiveOrWrapper
1060                        ? varArgComponentWrappedType.getConstructor(ClassUtils.wrapperToPrimitive(varArgComponentWrappedType)).newInstance(arg)
1061                        : varArgComponentWrappedType.cast(arg));
1062            } catch (final InstantiationException e) {
1063                throw new IllegalArgumentException("Cannot convert vararg #" + i, e);
1064            }
1065        }
1066        if (varArgComponentType.isPrimitive()) {
1067            // unbox from wrapper type to primitive type
1068            varArgsArray = ArrayUtils.toPrimitive(varArgsArray);
1069        }
1070        // Store the varargs array in the last position of the array to return
1071        newArgs[mptLength - 1] = varArgsArray;
1072        // Return the canonical varargs array.
1073        return newArgs;
1074    }
1075
1076    /**
1077     * {@link MethodUtils} instances should NOT be constructed in standard programming. Instead, the class should be used as
1078     * {@code MethodUtils.getAccessibleMethod(method)}.
1079     *
1080     * <p>
1081     * This constructor is {@code public} to permit tools that require a JavaBean instance to operate.
1082     * </p>
1083     *
1084     * @deprecated TODO Make private in 4.0.
1085     */
1086    @Deprecated
1087    public MethodUtils() {
1088        // empty
1089    }
1090}