Source code
Description: source: package: org.apache.commons.lang3.reflect, class: MethodUtils

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