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 }