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