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