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