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