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