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