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