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