View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      https://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.lang3.reflect;
18  
19  import java.lang.annotation.Annotation;
20  import java.lang.reflect.Array;
21  import java.lang.reflect.Executable;
22  import java.lang.reflect.InvocationTargetException;
23  import java.lang.reflect.Method;
24  import java.lang.reflect.Type;
25  import java.lang.reflect.TypeVariable;
26  import java.util.ArrayList;
27  import java.util.Arrays;
28  import java.util.Collections;
29  import java.util.Comparator;
30  import java.util.Iterator;
31  import java.util.LinkedHashSet;
32  import java.util.List;
33  import java.util.Map;
34  import java.util.Objects;
35  import java.util.Set;
36  import java.util.TreeMap;
37  import java.util.stream.Collectors;
38  import java.util.stream.Stream;
39  
40  import org.apache.commons.lang3.ArrayUtils;
41  import org.apache.commons.lang3.ClassUtils;
42  import org.apache.commons.lang3.ClassUtils.Interfaces;
43  import org.apache.commons.lang3.Validate;
44  import org.apache.commons.lang3.stream.LangCollectors;
45  
46  /**
47   * Utility reflection methods focused on {@link Method}s, originally from Commons BeanUtils.
48   * Differences from the BeanUtils version may be noted, especially where similar functionality
49   * already existed within Lang.
50   *
51   * <h2>Known Limitations</h2>
52   * <h3>Accessing Public Methods In A Default Access Superclass</h3>
53   * <p>There is an issue when invoking {@code public} methods contained in a default access superclass on JREs prior to 1.4.
54   * Reflection locates these methods fine and correctly assigns them as {@code public}.
55   * However, an {@link IllegalAccessException} is thrown if the method is invoked.</p>
56   *
57   * <p>
58   * {@link MethodUtils} contains a workaround for this situation.
59   * It will attempt to call {@link java.lang.reflect.AccessibleObject#setAccessible(boolean)} on this method.
60   * If this call succeeds, then the method can be invoked as normal.
61   * This call will only succeed when the application has sufficient security privileges.
62   * If this call fails then the method may fail.
63   * </p>
64   *
65   * @since 2.5
66   */
67  public class MethodUtils {
68  
69      private static final Comparator<Method> METHOD_BY_SIGNATURE = Comparator.comparing(Method::toString);
70  
71      /**
72       * Computes the aggregate number of inheritance hops between assignable argument class types.  Returns -1
73       * if the arguments aren't assignable.  Fills a specific purpose for getMatchingMethod and is not generalized.
74       *
75       * @param fromClassArray the Class array to calculate the distance from.
76       * @param toClassArray the Class array to calculate the distance to.
77       * @return the aggregate number of inheritance hops between assignable argument class types.
78       */
79      private static int distance(final Class<?>[] fromClassArray, final Class<?>[] toClassArray) {
80          int answer = 0;
81          if (!ClassUtils.isAssignable(fromClassArray, toClassArray, true)) {
82              return -1;
83          }
84          for (int offset = 0; offset < fromClassArray.length; offset++) {
85              // Note InheritanceUtils.distance() uses different scoring system.
86              final Class<?> aClass = fromClassArray[offset];
87              final Class<?> toClass = toClassArray[offset];
88              if (aClass == null || aClass.equals(toClass)) {
89                  continue;
90              }
91              if (ClassUtils.isAssignable(aClass, toClass, true) && !ClassUtils.isAssignable(aClass, toClass, false)) {
92                  answer++;
93              } else {
94                  answer += 2;
95              }
96          }
97          return answer;
98      }
99  
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 }