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.reflect.InvocationTargetException; 020import java.lang.reflect.Method; 021import java.lang.reflect.Modifier; 022import java.lang.reflect.Type; 023import java.lang.reflect.TypeVariable; 024import java.util.Arrays; 025import java.util.Iterator; 026import java.util.LinkedHashSet; 027import java.util.Map; 028import java.util.Set; 029 030import org.apache.commons.lang3.ArrayUtils; 031import org.apache.commons.lang3.ClassUtils; 032import org.apache.commons.lang3.ClassUtils.Interfaces; 033import org.apache.commons.lang3.Validate; 034 035/** 036 * <p>Utility reflection methods focused on {@link Method}s, originally from Commons BeanUtils. 037 * Differences from the BeanUtils version may be noted, especially where similar functionality 038 * already existed within Lang. 039 * </p> 040 * 041 * <h3>Known Limitations</h3> 042 * <h4>Accessing Public Methods In A Default Access Superclass</h4> 043 * <p>There is an issue when invoking {@code public} methods contained in a default access superclass on JREs prior to 1.4. 044 * Reflection locates these methods fine and correctly assigns them as {@code public}. 045 * However, an {@link IllegalAccessException} is thrown if the method is invoked.</p> 046 * 047 * <p>{@link MethodUtils} contains a workaround for this situation. 048 * It will attempt to call {@link java.lang.reflect.AccessibleObject#setAccessible(boolean)} on this method. 049 * If this call succeeds, then the method can be invoked as normal. 050 * This call will only succeed when the application has sufficient security privileges. 051 * If this call fails then the method may fail.</p> 052 * 053 * @since 2.5 054 * @version $Id: MethodUtils.java 1583482 2014-03-31 22:54:57Z niallp $ 055 */ 056public class MethodUtils { 057 058 /** 059 * <p>{@link MethodUtils} instances should NOT be constructed in standard programming. 060 * Instead, the class should be used as 061 * {@code MethodUtils.getAccessibleMethod(method)}.</p> 062 * 063 * <p>This constructor is {@code public} to permit tools that require a JavaBean 064 * instance to operate.</p> 065 */ 066 public MethodUtils() { 067 super(); 068 } 069 070 /** 071 * <p>Invokes a named method whose parameter type matches the object type.</p> 072 * 073 * <p>This method delegates the method search to {@link #getMatchingAccessibleMethod(Class, String, Class[])}.</p> 074 * 075 * <p>This method supports calls to methods taking primitive parameters 076 * via passing in wrapping classes. So, for example, a {@code Boolean} object 077 * would match a {@code boolean} primitive.</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 * @param args use these arguments - treat null as empty array 086 * @return The value returned by the invoked method 087 * 088 * @throws NoSuchMethodException if there is no such accessible method 089 * @throws InvocationTargetException wraps an exception thrown by the method invoked 090 * @throws IllegalAccessException if the requested method is not accessible via reflection 091 */ 092 public static Object invokeMethod(final Object object, final String methodName, 093 Object... args) throws NoSuchMethodException, 094 IllegalAccessException, InvocationTargetException { 095 args = ArrayUtils.nullToEmpty(args); 096 final Class<?>[] parameterTypes = ClassUtils.toClass(args); 097 return invokeMethod(object, methodName, args, parameterTypes); 098 } 099 100 /** 101 * <p>Invokes a named method whose parameter type matches the object type.</p> 102 * 103 * <p>This method delegates the method search to {@link #getMatchingAccessibleMethod(Class, String, Class[])}.</p> 104 * 105 * <p>This method supports calls to methods taking primitive parameters 106 * via passing in wrapping classes. So, for example, a {@code Boolean} object 107 * would match a {@code boolean} primitive.</p> 108 * 109 * @param object invoke method on this object 110 * @param methodName get method with this name 111 * @param args use these arguments - treat null as empty array 112 * @param parameterTypes match these parameters - treat null as empty array 113 * @return The value returned by the invoked method 114 * 115 * @throws NoSuchMethodException if there is no such accessible method 116 * @throws InvocationTargetException wraps an exception thrown by the method invoked 117 * @throws IllegalAccessException if the requested method is not accessible via reflection 118 */ 119 public static Object invokeMethod(final Object object, final String methodName, 120 Object[] args, Class<?>[] parameterTypes) 121 throws NoSuchMethodException, IllegalAccessException, 122 InvocationTargetException { 123 parameterTypes = ArrayUtils.nullToEmpty(parameterTypes); 124 args = ArrayUtils.nullToEmpty(args); 125 final Method method = getMatchingAccessibleMethod(object.getClass(), 126 methodName, parameterTypes); 127 if (method == null) { 128 throw new NoSuchMethodException("No such accessible method: " 129 + methodName + "() on object: " 130 + object.getClass().getName()); 131 } 132 return method.invoke(object, args); 133 } 134 135 /** 136 * <p>Invokes a method whose parameter types match exactly the object 137 * types.</p> 138 * 139 * <p>This uses reflection to invoke the method obtained from a call to 140 * {@link #getAccessibleMethod}(Class,String,Class[])}.</p> 141 * 142 * @param object invoke method on this object 143 * @param methodName get method with this name 144 * @param args use these arguments - treat {@code null} as empty array 145 * @return The value returned by the invoked method 146 * 147 * @throws NoSuchMethodException if there is no such accessible method 148 * @throws InvocationTargetException wraps an exception thrown by the 149 * method invoked 150 * @throws IllegalAccessException if the requested method is not accessible 151 * via reflection 152 */ 153 public static Object invokeExactMethod(final Object object, final String methodName, 154 Object... args) throws NoSuchMethodException, 155 IllegalAccessException, InvocationTargetException { 156 args = ArrayUtils.nullToEmpty(args); 157 final Class<?>[] parameterTypes = ClassUtils.toClass(args); 158 return invokeExactMethod(object, methodName, args, parameterTypes); 159 } 160 161 /** 162 * <p>Invokes a method whose parameter types match exactly the parameter 163 * types given.</p> 164 * 165 * <p>This uses reflection to invoke the method obtained from a call to 166 * {@link #getAccessibleMethod(Class,String,Class[])}.</p> 167 * 168 * @param object invoke method on this object 169 * @param methodName get method with this name 170 * @param args use these arguments - treat null as empty array 171 * @param parameterTypes match these parameters - treat {@code null} as empty array 172 * @return The value returned by the invoked method 173 * 174 * @throws NoSuchMethodException if there is no such accessible method 175 * @throws InvocationTargetException wraps an exception thrown by the 176 * method invoked 177 * @throws IllegalAccessException if the requested method is not accessible 178 * via reflection 179 */ 180 public static Object invokeExactMethod(final Object object, final String methodName, 181 Object[] args, Class<?>[] parameterTypes) 182 throws NoSuchMethodException, IllegalAccessException, 183 InvocationTargetException { 184 args = ArrayUtils.nullToEmpty(args); 185 parameterTypes = ArrayUtils.nullToEmpty(parameterTypes); 186 final Method method = getAccessibleMethod(object.getClass(), methodName, 187 parameterTypes); 188 if (method == null) { 189 throw new NoSuchMethodException("No such accessible method: " 190 + methodName + "() on object: " 191 + object.getClass().getName()); 192 } 193 return method.invoke(object, args); 194 } 195 196 /** 197 * <p>Invokes a {@code static} method whose parameter types match exactly the parameter 198 * types given.</p> 199 * 200 * <p>This uses reflection to invoke the method obtained from a call to 201 * {@link #getAccessibleMethod(Class, String, Class[])}.</p> 202 * 203 * @param cls invoke static method on this class 204 * @param methodName get method with this name 205 * @param args use these arguments - treat {@code null} as empty array 206 * @param parameterTypes match these parameters - treat {@code null} as empty array 207 * @return The value returned by the invoked method 208 * 209 * @throws NoSuchMethodException if there is no such accessible method 210 * @throws InvocationTargetException wraps an exception thrown by the 211 * method invoked 212 * @throws IllegalAccessException if the requested method is not accessible 213 * via reflection 214 */ 215 public static Object invokeExactStaticMethod(final Class<?> cls, final String methodName, 216 Object[] args, Class<?>[] parameterTypes) 217 throws NoSuchMethodException, IllegalAccessException, 218 InvocationTargetException { 219 args = ArrayUtils.nullToEmpty(args); 220 parameterTypes = ArrayUtils.nullToEmpty(parameterTypes); 221 final Method method = getAccessibleMethod(cls, methodName, parameterTypes); 222 if (method == null) { 223 throw new NoSuchMethodException("No such accessible method: " 224 + methodName + "() on class: " + cls.getName()); 225 } 226 return method.invoke(null, args); 227 } 228 229 /** 230 * <p>Invokes a named {@code static} method whose parameter type matches the object type.</p> 231 * 232 * <p>This method delegates the method search to {@link #getMatchingAccessibleMethod(Class, String, Class[])}.</p> 233 * 234 * <p>This method supports calls to methods taking primitive parameters 235 * via passing in wrapping classes. So, for example, a {@code Boolean} class 236 * would match a {@code boolean} primitive.</p> 237 * 238 * <p>This is a convenient wrapper for 239 * {@link #invokeStaticMethod(Class, String, Object[], Class[])}. 240 * </p> 241 * 242 * @param cls invoke static method on this class 243 * @param methodName get method with this name 244 * @param args use these arguments - treat {@code null} as empty array 245 * @return The value returned by the invoked method 246 * 247 * @throws NoSuchMethodException if there is no such accessible method 248 * @throws InvocationTargetException wraps an exception thrown by the 249 * method invoked 250 * @throws IllegalAccessException if the requested method is not accessible 251 * via reflection 252 */ 253 public static Object invokeStaticMethod(final Class<?> cls, final String methodName, 254 Object... args) throws NoSuchMethodException, 255 IllegalAccessException, InvocationTargetException { 256 args = ArrayUtils.nullToEmpty(args); 257 final Class<?>[] parameterTypes = ClassUtils.toClass(args); 258 return invokeStaticMethod(cls, methodName, args, parameterTypes); 259 } 260 261 /** 262 * <p>Invokes a named {@code static} method whose parameter type matches the object type.</p> 263 * 264 * <p>This method delegates the method search to {@link #getMatchingAccessibleMethod(Class, String, Class[])}.</p> 265 * 266 * <p>This method supports calls to methods taking primitive parameters 267 * via passing in wrapping classes. So, for example, a {@code Boolean} class 268 * would match a {@code boolean} primitive.</p> 269 * 270 * 271 * @param cls invoke static method on this class 272 * @param methodName get method with this name 273 * @param args use these arguments - treat {@code null} as empty array 274 * @param parameterTypes match these parameters - treat {@code null} as empty array 275 * @return The value returned by the invoked method 276 * 277 * @throws NoSuchMethodException if there is no such accessible method 278 * @throws InvocationTargetException wraps an exception thrown by the 279 * method invoked 280 * @throws IllegalAccessException if the requested method is not accessible 281 * via reflection 282 */ 283 public static Object invokeStaticMethod(final Class<?> cls, final String methodName, 284 Object[] args, Class<?>[] parameterTypes) 285 throws NoSuchMethodException, IllegalAccessException, 286 InvocationTargetException { 287 args = ArrayUtils.nullToEmpty(args); 288 parameterTypes = ArrayUtils.nullToEmpty(parameterTypes); 289 final Method method = getMatchingAccessibleMethod(cls, methodName, 290 parameterTypes); 291 if (method == null) { 292 throw new NoSuchMethodException("No such accessible method: " 293 + methodName + "() on class: " + cls.getName()); 294 } 295 return method.invoke(null, args); 296 } 297 298 /** 299 * <p>Invokes a {@code static} method whose parameter types match exactly the object 300 * types.</p> 301 * 302 * <p>This uses reflection to invoke the method obtained from a call to 303 * {@link #getAccessibleMethod(Class, String, Class[])}.</p> 304 * 305 * @param cls invoke static method on this class 306 * @param methodName get method with this name 307 * @param args use these arguments - treat {@code null} as empty array 308 * @return The value returned by the invoked method 309 * 310 * @throws NoSuchMethodException if there is no such accessible method 311 * @throws InvocationTargetException wraps an exception thrown by the 312 * method invoked 313 * @throws IllegalAccessException if the requested method is not accessible 314 * via reflection 315 */ 316 public static Object invokeExactStaticMethod(final Class<?> cls, final String methodName, 317 Object... args) throws NoSuchMethodException, 318 IllegalAccessException, InvocationTargetException { 319 args = ArrayUtils.nullToEmpty(args); 320 final Class<?>[] parameterTypes = ClassUtils.toClass(args); 321 return invokeExactStaticMethod(cls, methodName, args, parameterTypes); 322 } 323 324 /** 325 * <p>Returns an accessible method (that is, one that can be invoked via 326 * reflection) with given name and parameters. If no such method 327 * can be found, return {@code null}. 328 * This is just a convenience wrapper for 329 * {@link #getAccessibleMethod(Method)}.</p> 330 * 331 * @param cls get method from this class 332 * @param methodName get method with this name 333 * @param parameterTypes with these parameters types 334 * @return The accessible method 335 */ 336 public static Method getAccessibleMethod(final Class<?> cls, final String methodName, 337 final Class<?>... parameterTypes) { 338 try { 339 return getAccessibleMethod(cls.getMethod(methodName, 340 parameterTypes)); 341 } catch (final NoSuchMethodException e) { 342 return null; 343 } 344 } 345 346 /** 347 * <p>Returns an accessible method (that is, one that can be invoked via 348 * reflection) that implements the specified Method. If no such method 349 * can be found, return {@code null}.</p> 350 * 351 * @param method The method that we wish to call 352 * @return The accessible method 353 */ 354 public static Method getAccessibleMethod(Method method) { 355 if (!MemberUtils.isAccessible(method)) { 356 return null; 357 } 358 // If the declaring class is public, we are done 359 final Class<?> cls = method.getDeclaringClass(); 360 if (Modifier.isPublic(cls.getModifiers())) { 361 return method; 362 } 363 final String methodName = method.getName(); 364 final Class<?>[] parameterTypes = method.getParameterTypes(); 365 366 // Check the implemented interfaces and subinterfaces 367 method = getAccessibleMethodFromInterfaceNest(cls, methodName, 368 parameterTypes); 369 370 // Check the superclass chain 371 if (method == null) { 372 method = getAccessibleMethodFromSuperclass(cls, methodName, 373 parameterTypes); 374 } 375 return method; 376 } 377 378 /** 379 * <p>Returns an accessible method (that is, one that can be invoked via 380 * reflection) by scanning through the superclasses. If no such method 381 * can be found, return {@code null}.</p> 382 * 383 * @param cls Class to be checked 384 * @param methodName Method name of the method we wish to call 385 * @param parameterTypes The parameter type signatures 386 * @return the accessible method or {@code null} if not found 387 */ 388 private static Method getAccessibleMethodFromSuperclass(final Class<?> cls, 389 final String methodName, final Class<?>... parameterTypes) { 390 Class<?> parentClass = cls.getSuperclass(); 391 while (parentClass != null) { 392 if (Modifier.isPublic(parentClass.getModifiers())) { 393 try { 394 return parentClass.getMethod(methodName, parameterTypes); 395 } catch (final NoSuchMethodException e) { 396 return null; 397 } 398 } 399 parentClass = parentClass.getSuperclass(); 400 } 401 return null; 402 } 403 404 /** 405 * <p>Returns an accessible method (that is, one that can be invoked via 406 * reflection) that implements the specified method, by scanning through 407 * all implemented interfaces and subinterfaces. If no such method 408 * can be found, return {@code null}.</p> 409 * 410 * <p>There isn't any good reason why this method must be {@code private}. 411 * It is because there doesn't seem any reason why other classes should 412 * call this rather than the higher level methods.</p> 413 * 414 * @param cls Parent class for the interfaces to be checked 415 * @param methodName Method name of the method we wish to call 416 * @param parameterTypes The parameter type signatures 417 * @return the accessible method or {@code null} if not found 418 */ 419 private static Method getAccessibleMethodFromInterfaceNest(Class<?> cls, 420 final String methodName, final Class<?>... parameterTypes) { 421 // Search up the superclass chain 422 for (; cls != null; cls = cls.getSuperclass()) { 423 424 // Check the implemented interfaces of the parent class 425 final Class<?>[] interfaces = cls.getInterfaces(); 426 for (int i = 0; i < interfaces.length; i++) { 427 // Is this interface public? 428 if (!Modifier.isPublic(interfaces[i].getModifiers())) { 429 continue; 430 } 431 // Does the method exist on this interface? 432 try { 433 return interfaces[i].getDeclaredMethod(methodName, 434 parameterTypes); 435 } catch (final NoSuchMethodException e) { // NOPMD 436 /* 437 * Swallow, if no method is found after the loop then this 438 * method returns null. 439 */ 440 } 441 // Recursively check our parent interfaces 442 Method method = getAccessibleMethodFromInterfaceNest(interfaces[i], 443 methodName, parameterTypes); 444 if (method != null) { 445 return method; 446 } 447 } 448 } 449 return null; 450 } 451 452 /** 453 * <p>Finds an accessible method that matches the given name and has compatible parameters. 454 * Compatible parameters mean that every method parameter is assignable from 455 * the given parameters. 456 * In other words, it finds a method with the given name 457 * that will take the parameters given.</p> 458 * 459 * <p>This method is used by 460 * {@link 461 * #invokeMethod(Object object, String methodName, Object[] args, Class[] parameterTypes)}. 462 * </p> 463 * 464 * <p>This method can match primitive parameter by passing in wrapper classes. 465 * For example, a {@code Boolean} will match a primitive {@code boolean} 466 * parameter. 467 * </p> 468 * 469 * @param cls find method in this class 470 * @param methodName find method with this name 471 * @param parameterTypes find method with most compatible parameters 472 * @return The accessible method 473 */ 474 public static Method getMatchingAccessibleMethod(final Class<?> cls, 475 final String methodName, final Class<?>... parameterTypes) { 476 try { 477 final Method method = cls.getMethod(methodName, parameterTypes); 478 MemberUtils.setAccessibleWorkaround(method); 479 return method; 480 } catch (final NoSuchMethodException e) { // NOPMD - Swallow the exception 481 } 482 // search through all methods 483 Method bestMatch = null; 484 final Method[] methods = cls.getMethods(); 485 for (final Method method : methods) { 486 // compare name and parameters 487 if (method.getName().equals(methodName) && ClassUtils.isAssignable(parameterTypes, method.getParameterTypes(), true)) { 488 // get accessible version of method 489 final Method accessibleMethod = getAccessibleMethod(method); 490 if (accessibleMethod != null && (bestMatch == null || MemberUtils.compareParameterTypes( 491 accessibleMethod.getParameterTypes(), 492 bestMatch.getParameterTypes(), 493 parameterTypes) < 0)) { 494 bestMatch = accessibleMethod; 495 } 496 } 497 } 498 if (bestMatch != null) { 499 MemberUtils.setAccessibleWorkaround(bestMatch); 500 } 501 return bestMatch; 502 } 503 504 /** 505 * Get the hierarchy of overridden methods down to {@code result} respecting generics. 506 * @param method lowest to consider 507 * @param interfacesBehavior whether to search interfaces, {@code null} {@code implies} false 508 * @return Set<Method> in ascending order from sub- to superclass 509 * @throws NullPointerException if the specified method is {@code null} 510 * @since 3.2 511 */ 512 public static Set<Method> getOverrideHierarchy(final Method method, Interfaces interfacesBehavior) { 513 Validate.notNull(method); 514 final Set<Method> result = new LinkedHashSet<Method>(); 515 result.add(method); 516 517 final Class<?>[] parameterTypes = method.getParameterTypes(); 518 519 final Class<?> declaringClass = method.getDeclaringClass(); 520 521 final Iterator<Class<?>> hierarchy = ClassUtils.hierarchy(declaringClass, interfacesBehavior).iterator(); 522 //skip the declaring class :P 523 hierarchy.next(); 524 hierarchyTraversal: while (hierarchy.hasNext()) { 525 final Class<?> c = hierarchy.next(); 526 final Method m = getMatchingAccessibleMethod(c, method.getName(), parameterTypes); 527 if (m == null) { 528 continue; 529 } 530 if (Arrays.equals(m.getParameterTypes(), parameterTypes)) { 531 // matches without generics 532 result.add(m); 533 continue; 534 } 535 // necessary to get arguments every time in the case that we are including interfaces 536 final Map<TypeVariable<?>, Type> typeArguments = TypeUtils.getTypeArguments(declaringClass, m.getDeclaringClass()); 537 for (int i = 0; i < parameterTypes.length; i++) { 538 final Type childType = TypeUtils.unrollVariables(typeArguments, method.getGenericParameterTypes()[i]); 539 final Type parentType = TypeUtils.unrollVariables(typeArguments, m.getGenericParameterTypes()[i]); 540 if (!TypeUtils.equals(childType, parentType)) { 541 continue hierarchyTraversal; 542 } 543 } 544 result.add(m); 545 } 546 return result; 547 } 548 549}