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