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 1144929 2011-07-10 18:26:16Z 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 if (method.getName().equals(methodName)) {
521 // compare parameters
522 if (ClassUtils.isAssignable(parameterTypes, method
523 .getParameterTypes(), true)) {
524 // get accessible version of method
525 Method accessibleMethod = getAccessibleMethod(method);
526 if (accessibleMethod != null) {
527 if (bestMatch == null
528 || MemberUtils.compareParameterTypes(
529 accessibleMethod.getParameterTypes(),
530 bestMatch.getParameterTypes(),
531 parameterTypes) < 0) {
532 bestMatch = accessibleMethod;
533 }
534 }
535 }
536 }
537 }
538 if (bestMatch != null) {
539 MemberUtils.setAccessibleWorkaround(bestMatch);
540 }
541 return bestMatch;
542 }
543 }