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