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 }