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 * @author Apache Software Foundation 045 * @author Craig R. McClanahan 046 * @author Ralph Schaer 047 * @author Chris Audley 048 * @author Rey François 049 * @author Gregor Raýman 050 * @author Jan Sorensen 051 * @author Robert Burrell Donkin 052 * @author Matt Benson 053 * @since 2.5 054 * @version $Id: MethodUtils.java 925961 2010-03-22 05:59:31Z bayard $ 055 */ 056 public class MethodUtils { 057 058 /** 059 * <p>MethodUtils instances should NOT be constructed in standard programming. 060 * Instead, the class should be used as 061 * <code>MethodUtils.getAccessibleMethod(method)</code>.</p> 062 * 063 * <p>This constructor is public to permit tools that require a JavaBean 064 * instance to operate.</p> 065 */ 066 public MethodUtils() { 067 super(); 068 } 069 070 /** 071 * <p>Invoke 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</code> object 077 * would match a <code>boolean</code> 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(Object object, String methodName, 093 Object... args) throws NoSuchMethodException, 094 IllegalAccessException, InvocationTargetException { 095 if (args == null) { 096 args = ArrayUtils.EMPTY_OBJECT_ARRAY; 097 } 098 int arguments = args.length; 099 Class<?>[] parameterTypes = new Class[arguments]; 100 for (int i = 0; i < arguments; i++) { 101 parameterTypes[i] = args[i].getClass(); 102 } 103 return invokeMethod(object, methodName, args, parameterTypes); 104 } 105 106 /** 107 * <p>Invoke a named method whose parameter type matches the object type.</p> 108 * 109 * <p>This method delegates the method search to {@link #getMatchingAccessibleMethod(Class, String, Class[])}.</p> 110 * 111 * <p>This method supports calls to methods taking primitive parameters 112 * via passing in wrapping classes. So, for example, a <code>Boolean</code> object 113 * would match a <code>boolean</code> primitive.</p> 114 * 115 * @param object invoke method on this object 116 * @param methodName get method with this name 117 * @param args use these arguments - treat null as empty array 118 * @param parameterTypes match these parameters - treat null as empty array 119 * @return The value returned by the invoked method 120 * 121 * @throws NoSuchMethodException if there is no such accessible method 122 * @throws InvocationTargetException wraps an exception thrown by the method invoked 123 * @throws IllegalAccessException if the requested method is not accessible via reflection 124 */ 125 public static Object invokeMethod(Object object, String methodName, 126 Object[] args, Class<?>[] parameterTypes) 127 throws NoSuchMethodException, IllegalAccessException, 128 InvocationTargetException { 129 if (parameterTypes == null) { 130 parameterTypes = ArrayUtils.EMPTY_CLASS_ARRAY; 131 } 132 if (args == null) { 133 args = ArrayUtils.EMPTY_OBJECT_ARRAY; 134 } 135 Method method = getMatchingAccessibleMethod(object.getClass(), 136 methodName, parameterTypes); 137 if (method == null) { 138 throw new NoSuchMethodException("No such accessible method: " 139 + methodName + "() on object: " 140 + object.getClass().getName()); 141 } 142 return method.invoke(object, args); 143 } 144 145 /** 146 * <p>Invoke a method whose parameter types match exactly the object 147 * types.</p> 148 * 149 * <p> This uses reflection to invoke the method obtained from a call to 150 * <code>getAccessibleMethod()</code>.</p> 151 * 152 * @param object invoke method on this object 153 * @param methodName get method with this name 154 * @param args use these arguments - treat null as empty array 155 * @return The value returned by the invoked method 156 * 157 * @throws NoSuchMethodException if there is no such accessible method 158 * @throws InvocationTargetException wraps an exception thrown by the 159 * method invoked 160 * @throws IllegalAccessException if the requested method is not accessible 161 * via reflection 162 */ 163 public static Object invokeExactMethod(Object object, String methodName, 164 Object... args) throws NoSuchMethodException, 165 IllegalAccessException, InvocationTargetException { 166 if (args == null) { 167 args = ArrayUtils.EMPTY_OBJECT_ARRAY; 168 } 169 int arguments = args.length; 170 Class<?>[] parameterTypes = new Class[arguments]; 171 for (int i = 0; i < arguments; i++) { 172 parameterTypes[i] = args[i].getClass(); 173 } 174 return invokeExactMethod(object, methodName, args, parameterTypes); 175 } 176 177 /** 178 * <p>Invoke a method whose parameter types match exactly the parameter 179 * types given.</p> 180 * 181 * <p>This uses reflection to invoke the method obtained from a call to 182 * <code>getAccessibleMethod()</code>.</p> 183 * 184 * @param object invoke method on this object 185 * @param methodName get method with this name 186 * @param args use these arguments - treat null as empty array 187 * @param parameterTypes match these parameters - treat null as empty array 188 * @return The value returned by the invoked method 189 * 190 * @throws NoSuchMethodException if there is no such accessible method 191 * @throws InvocationTargetException wraps an exception thrown by the 192 * method invoked 193 * @throws IllegalAccessException if the requested method is not accessible 194 * via reflection 195 */ 196 public static Object invokeExactMethod(Object object, String methodName, 197 Object[] args, Class<?>[] parameterTypes) 198 throws NoSuchMethodException, IllegalAccessException, 199 InvocationTargetException { 200 if (args == null) { 201 args = ArrayUtils.EMPTY_OBJECT_ARRAY; 202 } 203 if (parameterTypes == null) { 204 parameterTypes = ArrayUtils.EMPTY_CLASS_ARRAY; 205 } 206 Method method = getAccessibleMethod(object.getClass(), methodName, 207 parameterTypes); 208 if (method == null) { 209 throw new NoSuchMethodException("No such accessible method: " 210 + methodName + "() on object: " 211 + object.getClass().getName()); 212 } 213 return method.invoke(object, args); 214 } 215 216 /** 217 * <p>Invoke a static method whose parameter types match exactly the parameter 218 * types given.</p> 219 * 220 * <p>This uses reflection to invoke the method obtained from a call to 221 * {@link #getAccessibleMethod(Class, String, Class[])}.</p> 222 * 223 * @param cls invoke static method on this class 224 * @param methodName get method with this name 225 * @param args use these arguments - treat null as empty array 226 * @param parameterTypes match these parameters - treat null as empty array 227 * @return The value returned by the invoked method 228 * 229 * @throws NoSuchMethodException if there is no such accessible method 230 * @throws InvocationTargetException wraps an exception thrown by the 231 * method invoked 232 * @throws IllegalAccessException if the requested method is not accessible 233 * via reflection 234 */ 235 public static Object invokeExactStaticMethod(Class<?> cls, String methodName, 236 Object[] args, Class<?>[] parameterTypes) 237 throws NoSuchMethodException, IllegalAccessException, 238 InvocationTargetException { 239 if (args == null) { 240 args = ArrayUtils.EMPTY_OBJECT_ARRAY; 241 } 242 if (parameterTypes == null) { 243 parameterTypes = ArrayUtils.EMPTY_CLASS_ARRAY; 244 } 245 Method method = getAccessibleMethod(cls, methodName, parameterTypes); 246 if (method == null) { 247 throw new NoSuchMethodException("No such accessible method: " 248 + methodName + "() on class: " + cls.getName()); 249 } 250 return method.invoke(null, args); 251 } 252 253 /** 254 * <p>Invoke a named static method whose parameter type matches the object type.</p> 255 * 256 * <p>This method delegates the method search to {@link #getMatchingAccessibleMethod(Class, String, Class[])}.</p> 257 * 258 * <p>This method supports calls to methods taking primitive parameters 259 * via passing in wrapping classes. So, for example, a <code>Boolean</code> class 260 * would match a <code>boolean</code> primitive.</p> 261 * 262 * <p> This is a convenient wrapper for 263 * {@link #invokeStaticMethod(Class objectClass,String methodName,Object [] args,Class[] parameterTypes)}. 264 * </p> 265 * 266 * @param cls invoke static method on this class 267 * @param methodName get method with this name 268 * @param args use these arguments - treat null as empty array 269 * @return The value returned by the invoked method 270 * 271 * @throws NoSuchMethodException if there is no such accessible method 272 * @throws InvocationTargetException wraps an exception thrown by the 273 * method invoked 274 * @throws IllegalAccessException if the requested method is not accessible 275 * via reflection 276 */ 277 public static Object invokeStaticMethod(Class<?> cls, String methodName, 278 Object... args) throws NoSuchMethodException, 279 IllegalAccessException, InvocationTargetException { 280 if (args == null) { 281 args = ArrayUtils.EMPTY_OBJECT_ARRAY; 282 } 283 int arguments = args.length; 284 Class<?>[] parameterTypes = new Class[arguments]; 285 for (int i = 0; i < arguments; i++) { 286 parameterTypes[i] = args[i].getClass(); 287 } 288 return invokeStaticMethod(cls, methodName, args, parameterTypes); 289 } 290 291 /** 292 * <p>Invoke a named static method whose parameter type matches the object type.</p> 293 * 294 * <p>This method delegates the method search to {@link #getMatchingAccessibleMethod(Class, String, Class[])}.</p> 295 * 296 * <p>This method supports calls to methods taking primitive parameters 297 * via passing in wrapping classes. So, for example, a <code>Boolean</code> class 298 * would match a <code>boolean</code> primitive.</p> 299 * 300 * 301 * @param cls invoke static method on this class 302 * @param methodName get method with this name 303 * @param args use these arguments - treat null as empty array 304 * @param parameterTypes match these parameters - treat null as empty array 305 * @return The value returned by the invoked method 306 * 307 * @throws NoSuchMethodException if there is no such accessible method 308 * @throws InvocationTargetException wraps an exception thrown by the 309 * method invoked 310 * @throws IllegalAccessException if the requested method is not accessible 311 * via reflection 312 */ 313 public static Object invokeStaticMethod(Class<?> cls, String methodName, 314 Object[] args, Class<?>[] parameterTypes) 315 throws NoSuchMethodException, IllegalAccessException, 316 InvocationTargetException { 317 if (parameterTypes == null) { 318 parameterTypes = ArrayUtils.EMPTY_CLASS_ARRAY; 319 } 320 if (args == null) { 321 args = ArrayUtils.EMPTY_OBJECT_ARRAY; 322 } 323 Method method = getMatchingAccessibleMethod(cls, methodName, 324 parameterTypes); 325 if (method == null) { 326 throw new NoSuchMethodException("No such accessible method: " 327 + methodName + "() on class: " + cls.getName()); 328 } 329 return method.invoke(null, args); 330 } 331 332 /** 333 * <p>Invoke a static method whose parameter types match exactly the object 334 * types.</p> 335 * 336 * <p> This uses reflection to invoke the method obtained from a call to 337 * {@link #getAccessibleMethod(Class, String, Class[])}.</p> 338 * 339 * @param cls invoke static method on this class 340 * @param methodName get method with this name 341 * @param args use these arguments - treat null as empty array 342 * @return The value returned by the invoked method 343 * 344 * @throws NoSuchMethodException if there is no such accessible method 345 * @throws InvocationTargetException wraps an exception thrown by the 346 * method invoked 347 * @throws IllegalAccessException if the requested method is not accessible 348 * via reflection 349 */ 350 public static Object invokeExactStaticMethod(Class<?> cls, String methodName, 351 Object... args) throws NoSuchMethodException, 352 IllegalAccessException, InvocationTargetException { 353 if (args == null) { 354 args = ArrayUtils.EMPTY_OBJECT_ARRAY; 355 } 356 int arguments = args.length; 357 Class<?>[] parameterTypes = new Class[arguments]; 358 for (int i = 0; i < arguments; i++) { 359 parameterTypes[i] = args[i].getClass(); 360 } 361 return invokeExactStaticMethod(cls, methodName, args, parameterTypes); 362 } 363 364 /** 365 * <p>Return an accessible method (that is, one that can be invoked via 366 * reflection) with given name and parameters. If no such method 367 * can be found, return <code>null</code>. 368 * This is just a convenient wrapper for 369 * {@link #getAccessibleMethod(Method method)}.</p> 370 * 371 * @param cls get method from this class 372 * @param methodName get method with this name 373 * @param parameterTypes with these parameters types 374 * @return The accessible method 375 */ 376 public static Method getAccessibleMethod(Class<?> cls, String methodName, 377 Class<?>... parameterTypes) { 378 try { 379 return getAccessibleMethod(cls.getMethod(methodName, 380 parameterTypes)); 381 } catch (NoSuchMethodException e) { 382 return (null); 383 } 384 } 385 386 /** 387 * <p>Return an accessible method (that is, one that can be invoked via 388 * reflection) that implements the specified Method. If no such method 389 * can be found, return <code>null</code>.</p> 390 * 391 * @param method The method that we wish to call 392 * @return The accessible method 393 */ 394 public static Method getAccessibleMethod(Method method) { 395 if (!MemberUtils.isAccessible(method)) { 396 return null; 397 } 398 // If the declaring class is public, we are done 399 Class<?> cls = method.getDeclaringClass(); 400 if (Modifier.isPublic(cls.getModifiers())) { 401 return method; 402 } 403 String methodName = method.getName(); 404 Class<?>[] parameterTypes = method.getParameterTypes(); 405 406 // Check the implemented interfaces and subinterfaces 407 method = getAccessibleMethodFromInterfaceNest(cls, methodName, 408 parameterTypes); 409 410 // Check the superclass chain 411 if (method == null) { 412 method = getAccessibleMethodFromSuperclass(cls, methodName, 413 parameterTypes); 414 } 415 return method; 416 } 417 418 /** 419 * <p>Return an accessible method (that is, one that can be invoked via 420 * reflection) by scanning through the superclasses. If no such method 421 * can be found, return <code>null</code>.</p> 422 * 423 * @param cls Class to be checked 424 * @param methodName Method name of the method we wish to call 425 * @param parameterTypes The parameter type signatures 426 * @return the accessible method or <code>null</code> if not found 427 */ 428 private static Method getAccessibleMethodFromSuperclass(Class<?> cls, 429 String methodName, Class<?>... parameterTypes) { 430 Class<?> parentClass = cls.getSuperclass(); 431 while (parentClass != null) { 432 if (Modifier.isPublic(parentClass.getModifiers())) { 433 try { 434 return parentClass.getMethod(methodName, parameterTypes); 435 } catch (NoSuchMethodException e) { 436 return null; 437 } 438 } 439 parentClass = parentClass.getSuperclass(); 440 } 441 return null; 442 } 443 444 /** 445 * <p>Return an accessible method (that is, one that can be invoked via 446 * reflection) that implements the specified method, by scanning through 447 * all implemented interfaces and subinterfaces. If no such method 448 * can be found, return <code>null</code>.</p> 449 * 450 * <p> There isn't any good reason why this method must be private. 451 * It is because there doesn't seem any reason why other classes should 452 * call this rather than the higher level methods.</p> 453 * 454 * @param cls Parent class for the interfaces to be checked 455 * @param methodName Method name of the method we wish to call 456 * @param parameterTypes The parameter type signatures 457 * @return the accessible method or <code>null</code> if not found 458 */ 459 private static Method getAccessibleMethodFromInterfaceNest(Class<?> cls, 460 String methodName, Class<?>... parameterTypes) { 461 Method method = null; 462 463 // Search up the superclass chain 464 for (; cls != null; cls = cls.getSuperclass()) { 465 466 // Check the implemented interfaces of the parent class 467 Class<?>[] interfaces = cls.getInterfaces(); 468 for (int i = 0; i < interfaces.length; i++) { 469 // Is this interface public? 470 if (!Modifier.isPublic(interfaces[i].getModifiers())) { 471 continue; 472 } 473 // Does the method exist on this interface? 474 try { 475 method = interfaces[i].getDeclaredMethod(methodName, 476 parameterTypes); 477 } catch (NoSuchMethodException e) { 478 /* 479 * Swallow, if no method is found after the loop then this 480 * method returns null. 481 */ 482 } 483 if (method != null) { 484 break; 485 } 486 // Recursively check our parent interfaces 487 method = getAccessibleMethodFromInterfaceNest(interfaces[i], 488 methodName, parameterTypes); 489 if (method != null) { 490 break; 491 } 492 } 493 } 494 return method; 495 } 496 497 /** 498 * <p>Find an accessible method that matches the given name and has compatible parameters. 499 * Compatible parameters mean that every method parameter is assignable from 500 * the given parameters. 501 * In other words, it finds a method with the given name 502 * that will take the parameters given.<p> 503 * 504 * <p>This method is used by 505 * {@link 506 * #invokeMethod(Object object, String methodName, Object[] args, Class[] parameterTypes)}. 507 * 508 * <p>This method can match primitive parameter by passing in wrapper classes. 509 * For example, a <code>Boolean</code> will match a primitive <code>boolean</code> 510 * parameter. 511 * 512 * @param cls find method in this class 513 * @param methodName find method with this name 514 * @param parameterTypes find method with most compatible parameters 515 * @return The accessible method 516 */ 517 public static Method getMatchingAccessibleMethod(Class<?> cls, 518 String methodName, Class<?>... parameterTypes) { 519 try { 520 Method method = cls.getMethod(methodName, parameterTypes); 521 MemberUtils.setAccessibleWorkaround(method); 522 return method; 523 } catch (NoSuchMethodException e) { /* SWALLOW */ 524 } 525 // search through all methods 526 Method bestMatch = null; 527 Method[] methods = cls.getMethods(); 528 for (int i = 0, size = methods.length; i < size; i++) { 529 if (methods[i].getName().equals(methodName)) { 530 // compare parameters 531 if (ClassUtils.isAssignable(parameterTypes, methods[i] 532 .getParameterTypes(), true)) { 533 // get accessible version of method 534 Method accessibleMethod = getAccessibleMethod(methods[i]); 535 if (accessibleMethod != null) { 536 if (bestMatch == null 537 || MemberUtils.compareParameterTypes( 538 accessibleMethod.getParameterTypes(), 539 bestMatch.getParameterTypes(), 540 parameterTypes) < 0) { 541 bestMatch = accessibleMethod; 542 } 543 } 544 } 545 } 546 } 547 if (bestMatch != null) { 548 MemberUtils.setAccessibleWorkaround(bestMatch); 549 } 550 return bestMatch; 551 } 552 }