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