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