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