0001 /*
0002 * Licensed to the Apache Software Foundation (ASF) under one or more
0003 * contributor license agreements. See the NOTICE file distributed with
0004 * this work for additional information regarding copyright ownership.
0005 * The ASF licenses this file to You under the Apache License, Version 2.0
0006 * (the "License"); you may not use this file except in compliance with
0007 * the License. You may obtain a copy of the License at
0008 *
0009 * http://www.apache.org/licenses/LICENSE-2.0
0010 *
0011 * Unless required by applicable law or agreed to in writing, software
0012 * distributed under the License is distributed on an "AS IS" BASIS,
0013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0014 * See the License for the specific language governing permissions and
0015 * limitations under the License.
0016 */
0017
0018 package griffon.util;
0019
0020
0021 import java.lang.ref.Reference;
0022 import java.lang.ref.WeakReference;
0023 import java.lang.reflect.InvocationTargetException;
0024 import java.lang.reflect.Method;
0025 import java.lang.reflect.Modifier;
0026 import java.util.Collections;
0027 import java.util.Map;
0028 import java.util.WeakHashMap;
0029
0030
0031 /**
0032 * <p> Utility reflection methods focussed on methods in general rather than properties in particular. </p>
0033 * <p/>
0034 * <p><b>Copied from commons-beanutils, removed dependencies to commons-logging</b></p>
0035 * <p/>
0036 * <h3>Known Limitations</h3>
0037 * <h4>Accessing Public Methods In A Default Access Superclass</h4>
0038 * <p>There is an issue when invoking public methods contained in a default access superclass.
0039 * Reflection locates these methods fine and correctly assigns them as public.
0040 * However, an <code>IllegalAccessException</code> is thrown if the method is invoked.</p>
0041 * <p/>
0042 * <p><code>MethodUtils</code> contains a workaround for this situation.
0043 * It will attempt to call <code>setAccessible</code> on this method.
0044 * If this call succeeds, then the method can be invoked as normal.
0045 * This call will only succeed when the application has sufficient security privilages.
0046 *
0047 * @author Craig R. McClanahan
0048 * @author Ralph Schaer
0049 * @author Chris Audley
0050 * @author Rey François
0051 * @author Gregor Raýman
0052 * @author Jan Sorensen
0053 * @author Robert Burrell Donkin
0054 */
0055
0056 @SuppressWarnings({"unchecked", "rawtypes"})
0057 public class MethodUtils {
0058
0059 // --------------------------------------------------------- Private Methods
0060
0061 /**
0062 * Indicates whether methods should be cached for improved performance.
0063 * <p/>
0064 * Note that when this class is deployed via a shared classloader in
0065 * a container, this will affect all webapps. However making this
0066 * configurable per webapp would mean having a map keyed by context classloader
0067 * which may introduce memory-leak problems.
0068 */
0069 private static boolean CACHE_METHODS = true;
0070
0071 /**
0072 * An empty class array
0073 */
0074 private static final Class[] EMPTY_CLASS_PARAMETERS = new Class[0];
0075 /**
0076 * An empty object array
0077 */
0078 private static final Object[] EMPTY_OBJECT_ARRAY = new Object[0];
0079
0080 /**
0081 * Stores a cache of MethodDescriptor -> Method in a WeakHashMap.
0082 * <p/>
0083 * The keys into this map only ever exist as temporary variables within
0084 * methods of this class, and are never exposed to users of this class.
0085 * This means that the WeakHashMap is used only as a mechanism for
0086 * limiting the size of the cache, ie a way to tell the garbage collector
0087 * that the contents of the cache can be completely garbage-collected
0088 * whenever it needs the memory. Whether this is a good approach to
0089 * this problem is doubtful; something like the commons-collections
0090 * LRUMap may be more appropriate (though of course selecting an
0091 * appropriate size is an issue).
0092 * <p/>
0093 * This static variable is safe even when this code is deployed via a
0094 * shared classloader because it is keyed via a MethodDescriptor object
0095 * which has a Class as one of its members and that member is used in
0096 * the MethodDescriptor.equals method. So two components that load the same
0097 * class via different classloaders will generate non-equal MethodDescriptor
0098 * objects and hence end up with different entries in the map.
0099 */
0100 private static final Map cache = Collections.synchronizedMap(new WeakHashMap());
0101
0102 // --------------------------------------------------------- Public Methods
0103
0104 /**
0105 * Set whether methods should be cached for greater performance or not,
0106 * default is <code>true</code>.
0107 *
0108 * @param cacheMethods <code>true</code> if methods should be
0109 * cached for greater performance, otherwise <code>false</code>
0110 * @since 1.8.0
0111 */
0112 public static synchronized void setCacheMethods(boolean cacheMethods) {
0113 CACHE_METHODS = cacheMethods;
0114 if (!CACHE_METHODS) {
0115 clearCache();
0116 }
0117 }
0118
0119 /**
0120 * Clear the method cache.
0121 *
0122 * @return the number of cached methods cleared
0123 * @since 1.8.0
0124 */
0125 public static synchronized int clearCache() {
0126 int size = cache.size();
0127 cache.clear();
0128 return size;
0129 }
0130
0131 /**
0132 * <p>Invoke a named method whose parameter type matches the object type.</p>
0133 * <p/>
0134 * <p>The behaviour of this method is less deterministic
0135 * than <code>invokeExactMethod()</code>.
0136 * It loops through all methods with names that match
0137 * and then executes the first it finds with compatable parameters.</p>
0138 * <p/>
0139 * <p>This method supports calls to methods taking primitive parameters
0140 * via passing in wrapping classes. So, for example, a <code>Boolean</code> class
0141 * would match a <code>boolean</code> primitive.</p>
0142 * <p/>
0143 * <p> This is a convenient wrapper for
0144 * {@link #invokeMethod(Object object, String methodName, Object [] args)}.
0145 * </p>
0146 *
0147 * @param object invoke method on this object
0148 * @param methodName get method with this name
0149 * @param arg use this argument
0150 * @return The value returned by the invoked method
0151 * @throws NoSuchMethodException if there is no such accessible method
0152 * @throws InvocationTargetException wraps an exception thrown by the
0153 * method invoked
0154 * @throws IllegalAccessException if the requested method is not accessible
0155 * via reflection
0156 */
0157 public static Object invokeMethod(
0158 Object object,
0159 String methodName,
0160 Object arg)
0161 throws
0162 NoSuchMethodException,
0163 IllegalAccessException,
0164 InvocationTargetException {
0165
0166 Object[] args = {arg};
0167 return invokeMethod(object, methodName, args);
0168
0169 }
0170
0171
0172 /**
0173 * <p>Invoke a named method whose parameter type matches the object type.</p>
0174 * <p/>
0175 * <p>The behaviour of this method is less deterministic
0176 * than {@link #invokeExactMethod(Object object, String methodName, Object [] args)}.
0177 * It loops through all methods with names that match
0178 * and then executes the first it finds with compatable parameters.</p>
0179 * <p/>
0180 * <p>This method supports calls to methods taking primitive parameters
0181 * via passing in wrapping classes. So, for example, a <code>Boolean</code> class
0182 * would match a <code>boolean</code> primitive.</p>
0183 * <p/>
0184 * <p> This is a convenient wrapper for
0185 * {@link #invokeMethod(Object object, String methodName, Object [] args, Class[] parameterTypes)}.
0186 * </p>
0187 *
0188 * @param object invoke method on this object
0189 * @param methodName get method with this name
0190 * @param args use these arguments - treat null as empty array
0191 * @return The value returned by the invoked method
0192 * @throws NoSuchMethodException if there is no such accessible method
0193 * @throws InvocationTargetException wraps an exception thrown by the
0194 * method invoked
0195 * @throws IllegalAccessException if the requested method is not accessible
0196 * via reflection
0197 */
0198 public static Object invokeMethod(
0199 Object object,
0200 String methodName,
0201 Object[] args)
0202 throws
0203 NoSuchMethodException,
0204 IllegalAccessException,
0205 InvocationTargetException {
0206
0207 if (args == null) {
0208 args = EMPTY_OBJECT_ARRAY;
0209 }
0210 int arguments = args.length;
0211 Class[] parameterTypes = new Class[arguments];
0212 for (int i = 0; i < arguments; i++) {
0213 parameterTypes[i] = args[i] != null ? args[i].getClass() : Object.class;
0214 }
0215 return invokeMethod(object, methodName, args, parameterTypes);
0216
0217 }
0218
0219
0220 /**
0221 * <p>Invoke a named method whose parameter type matches the object type.</p>
0222 * <p/>
0223 * <p>The behaviour of this method is less deterministic
0224 * than {@link
0225 * #invokeExactMethod(Object object, String methodName, Object [] args, Class[] parameterTypes)}.
0226 * It loops through all methods with names that match
0227 * and then executes the first it finds with compatable parameters.</p>
0228 * <p/>
0229 * <p>This method supports calls to methods taking primitive parameters
0230 * via passing in wrapping classes. So, for example, a <code>Boolean</code> class
0231 * would match a <code>boolean</code> primitive.</p>
0232 *
0233 * @param object invoke method on this object
0234 * @param methodName get method with this name
0235 * @param args use these arguments - treat null as empty array
0236 * @param parameterTypes match these parameters - treat null as empty array
0237 * @return The value returned by the invoked method
0238 * @throws NoSuchMethodException if there is no such accessible method
0239 * @throws InvocationTargetException wraps an exception thrown by the
0240 * method invoked
0241 * @throws IllegalAccessException if the requested method is not accessible
0242 * via reflection
0243 */
0244 public static Object invokeMethod(
0245 Object object,
0246 String methodName,
0247 Object[] args,
0248 Class[] parameterTypes)
0249 throws
0250 NoSuchMethodException,
0251 IllegalAccessException,
0252 InvocationTargetException {
0253
0254 if (parameterTypes == null) {
0255 parameterTypes = EMPTY_CLASS_PARAMETERS;
0256 }
0257 if (args == null) {
0258 args = EMPTY_OBJECT_ARRAY;
0259 }
0260
0261 Method method = getMatchingAccessibleMethod(
0262 object.getClass(),
0263 methodName,
0264 parameterTypes);
0265 if (method == null) {
0266 throw new NoSuchMethodException("No such accessible method: " +
0267 methodName + "() on object: " + object.getClass().getName());
0268 }
0269 return method.invoke(object, args);
0270 }
0271
0272
0273 /**
0274 * <p>Invoke a method whose parameter type matches exactly the object
0275 * type.</p>
0276 * <p/>
0277 * <p> This is a convenient wrapper for
0278 * {@link #invokeExactMethod(Object object, String methodName, Object [] args)}.
0279 * </p>
0280 *
0281 * @param object invoke method on this object
0282 * @param methodName get method with this name
0283 * @param arg use this argument
0284 * @return The value returned by the invoked method
0285 * @throws NoSuchMethodException if there is no such accessible method
0286 * @throws InvocationTargetException wraps an exception thrown by the
0287 * method invoked
0288 * @throws IllegalAccessException if the requested method is not accessible
0289 * via reflection
0290 */
0291 public static Object invokeExactMethod(
0292 Object object,
0293 String methodName,
0294 Object arg)
0295 throws
0296 NoSuchMethodException,
0297 IllegalAccessException,
0298 InvocationTargetException {
0299
0300 Object[] args = {arg};
0301 return invokeExactMethod(object, methodName, args);
0302
0303 }
0304
0305
0306 /**
0307 * <p>Invoke a method whose parameter types match exactly the object
0308 * types.</p>
0309 * <p/>
0310 * <p> This uses reflection to invoke the method obtained from a call to
0311 * <code>getAccessibleMethod()</code>.</p>
0312 *
0313 * @param object invoke method on this object
0314 * @param methodName get method with this name
0315 * @param args use these arguments - treat null as empty array
0316 * @return The value returned by the invoked method
0317 * @throws NoSuchMethodException if there is no such accessible method
0318 * @throws InvocationTargetException wraps an exception thrown by the
0319 * method invoked
0320 * @throws IllegalAccessException if the requested method is not accessible
0321 * via reflection
0322 */
0323 public static Object invokeExactMethod(
0324 Object object,
0325 String methodName,
0326 Object[] args)
0327 throws
0328 NoSuchMethodException,
0329 IllegalAccessException,
0330 InvocationTargetException {
0331 if (args == null) {
0332 args = EMPTY_OBJECT_ARRAY;
0333 }
0334 int arguments = args.length;
0335 Class[] parameterTypes = new Class[arguments];
0336 for (int i = 0; i < arguments; i++) {
0337 parameterTypes[i] = args[i] != null ? args[i].getClass() : Object.class;
0338 }
0339 return invokeExactMethod(object, methodName, args, parameterTypes);
0340
0341 }
0342
0343
0344 /**
0345 * <p>Invoke a method whose parameter types match exactly the parameter
0346 * types given.</p>
0347 * <p/>
0348 * <p>This uses reflection to invoke the method obtained from a call to
0349 * <code>getAccessibleMethod()</code>.</p>
0350 *
0351 * @param object invoke method on this object
0352 * @param methodName get method with this name
0353 * @param args use these arguments - treat null as empty array
0354 * @param parameterTypes match these parameters - treat null as empty array
0355 * @return The value returned by the invoked method
0356 * @throws NoSuchMethodException if there is no such accessible method
0357 * @throws InvocationTargetException wraps an exception thrown by the
0358 * method invoked
0359 * @throws IllegalAccessException if the requested method is not accessible
0360 * via reflection
0361 */
0362 public static Object invokeExactMethod(
0363 Object object,
0364 String methodName,
0365 Object[] args,
0366 Class[] parameterTypes)
0367 throws
0368 NoSuchMethodException,
0369 IllegalAccessException,
0370 InvocationTargetException {
0371
0372 if (args == null) {
0373 args = EMPTY_OBJECT_ARRAY;
0374 }
0375
0376 if (parameterTypes == null) {
0377 parameterTypes = EMPTY_CLASS_PARAMETERS;
0378 }
0379
0380 Method method = getAccessibleMethod(
0381 object.getClass(),
0382 methodName,
0383 parameterTypes);
0384 if (method == null) {
0385 throw new NoSuchMethodException("No such accessible method: " +
0386 methodName + "() on object: " + object.getClass().getName());
0387 }
0388 return method.invoke(object, args);
0389
0390 }
0391
0392 /**
0393 * <p>Invoke a static method whose parameter types match exactly the parameter
0394 * types given.</p>
0395 * <p/>
0396 * <p>This uses reflection to invoke the method obtained from a call to
0397 * {@link #getAccessibleMethod(Class, String, Class[])}.</p>
0398 *
0399 * @param objectClass invoke static method on this class
0400 * @param methodName get method with this name
0401 * @param args use these arguments - treat null as empty array
0402 * @param parameterTypes match these parameters - treat null as empty array
0403 * @return The value returned by the invoked method
0404 * @throws NoSuchMethodException if there is no such accessible method
0405 * @throws InvocationTargetException wraps an exception thrown by the
0406 * method invoked
0407 * @throws IllegalAccessException if the requested method is not accessible
0408 * via reflection
0409 * @since 1.8.0
0410 */
0411 public static Object invokeExactStaticMethod(
0412 Class objectClass,
0413 String methodName,
0414 Object[] args,
0415 Class[] parameterTypes)
0416 throws
0417 NoSuchMethodException,
0418 IllegalAccessException,
0419 InvocationTargetException {
0420
0421 if (args == null) {
0422 args = EMPTY_OBJECT_ARRAY;
0423 }
0424
0425 if (parameterTypes == null) {
0426 parameterTypes = EMPTY_CLASS_PARAMETERS;
0427 }
0428
0429 Method method = getAccessibleMethod(
0430 objectClass,
0431 methodName,
0432 parameterTypes);
0433 if (method == null) {
0434 throw new NoSuchMethodException("No such accessible method: " +
0435 methodName + "() on class: " + objectClass.getName());
0436 }
0437 return method.invoke(null, args);
0438
0439 }
0440
0441 /**
0442 * <p>Invoke a named static method whose parameter type matches the object type.</p>
0443 * <p/>
0444 * <p>The behaviour of this method is less deterministic
0445 * than {@link #invokeExactMethod(Object, String, Object[], Class[])}.
0446 * It loops through all methods with names that match
0447 * and then executes the first it finds with compatable parameters.</p>
0448 * <p/>
0449 * <p>This method supports calls to methods taking primitive parameters
0450 * via passing in wrapping classes. So, for example, a <code>Boolean</code> class
0451 * would match a <code>boolean</code> primitive.</p>
0452 * <p/>
0453 * <p> This is a convenient wrapper for
0454 * {@link #invokeStaticMethod(Class objectClass, String methodName, Object [] args)}.
0455 * </p>
0456 *
0457 * @param objectClass invoke static method on this class
0458 * @param methodName get method with this name
0459 * @param arg use this argument
0460 * @return The value returned by the invoked method
0461 * @throws NoSuchMethodException if there is no such accessible method
0462 * @throws InvocationTargetException wraps an exception thrown by the
0463 * method invoked
0464 * @throws IllegalAccessException if the requested method is not accessible
0465 * via reflection
0466 * @since 1.8.0
0467 */
0468 public static Object invokeStaticMethod(
0469 Class objectClass,
0470 String methodName,
0471 Object arg)
0472 throws
0473 NoSuchMethodException,
0474 IllegalAccessException,
0475 InvocationTargetException {
0476
0477 Object[] args = {arg};
0478 return invokeStaticMethod(objectClass, methodName, args);
0479
0480 }
0481
0482
0483 /**
0484 * <p>Invoke a named static method whose parameter type matches the object type.</p>
0485 * <p/>
0486 * <p>The behaviour of this method is less deterministic
0487 * than {@link #invokeExactMethod(Object object, String methodName, Object [] args)}.
0488 * It loops through all methods with names that match
0489 * and then executes the first it finds with compatable parameters.</p>
0490 * <p/>
0491 * <p>This method supports calls to methods taking primitive parameters
0492 * via passing in wrapping classes. So, for example, a <code>Boolean</code> class
0493 * would match a <code>boolean</code> primitive.</p>
0494 * <p/>
0495 * <p> This is a convenient wrapper for
0496 * {@link #invokeStaticMethod(Class objectClass, String methodName, Object [] args, Class[] parameterTypes)}.
0497 * </p>
0498 *
0499 * @param objectClass invoke static method on this class
0500 * @param methodName get method with this name
0501 * @param args use these arguments - treat null as empty array
0502 * @return The value returned by the invoked method
0503 * @throws NoSuchMethodException if there is no such accessible method
0504 * @throws InvocationTargetException wraps an exception thrown by the
0505 * method invoked
0506 * @throws IllegalAccessException if the requested method is not accessible
0507 * via reflection
0508 * @since 1.8.0
0509 */
0510 public static Object invokeStaticMethod(
0511 Class objectClass,
0512 String methodName,
0513 Object[] args)
0514 throws
0515 NoSuchMethodException,
0516 IllegalAccessException,
0517 InvocationTargetException {
0518
0519 if (args == null) {
0520 args = EMPTY_OBJECT_ARRAY;
0521 }
0522 int arguments = args.length;
0523 Class[] parameterTypes = new Class[arguments];
0524 for (int i = 0; i < arguments; i++) {
0525 parameterTypes[i] = args[i] != null ? args[i].getClass() : Object.class;
0526 }
0527 return invokeStaticMethod(objectClass, methodName, args, parameterTypes);
0528
0529 }
0530
0531
0532 /**
0533 * <p>Invoke a named static method whose parameter type matches the object type.</p>
0534 * <p/>
0535 * <p>The behaviour of this method is less deterministic
0536 * than {@link
0537 * #invokeExactStaticMethod(Class objectClass, String methodName, Object [] args, Class[] parameterTypes)}.
0538 * It loops through all methods with names that match
0539 * and then executes the first it finds with compatable parameters.</p>
0540 * <p/>
0541 * <p>This method supports calls to methods taking primitive parameters
0542 * via passing in wrapping classes. So, for example, a <code>Boolean</code> class
0543 * would match a <code>boolean</code> primitive.</p>
0544 *
0545 * @param objectClass invoke static method on this class
0546 * @param methodName get method with this name
0547 * @param args use these arguments - treat null as empty array
0548 * @param parameterTypes match these parameters - treat null as empty array
0549 * @return The value returned by the invoked method
0550 * @throws NoSuchMethodException if there is no such accessible method
0551 * @throws InvocationTargetException wraps an exception thrown by the
0552 * method invoked
0553 * @throws IllegalAccessException if the requested method is not accessible
0554 * via reflection
0555 * @since 1.8.0
0556 */
0557 public static Object invokeStaticMethod(
0558 Class objectClass,
0559 String methodName,
0560 Object[] args,
0561 Class[] parameterTypes)
0562 throws
0563 NoSuchMethodException,
0564 IllegalAccessException,
0565 InvocationTargetException {
0566
0567 if (parameterTypes == null) {
0568 parameterTypes = EMPTY_CLASS_PARAMETERS;
0569 }
0570 if (args == null) {
0571 args = EMPTY_OBJECT_ARRAY;
0572 }
0573
0574 Method method = getMatchingAccessibleMethod(
0575 objectClass,
0576 methodName,
0577 parameterTypes);
0578 if (method == null) {
0579 throw new NoSuchMethodException("No such accessible method: " +
0580 methodName + "() on class: " + objectClass.getName());
0581 }
0582 return method.invoke(null, args);
0583 }
0584
0585
0586 /**
0587 * <p>Invoke a static method whose parameter type matches exactly the object
0588 * type.</p>
0589 * <p/>
0590 * <p> This is a convenient wrapper for
0591 * {@link #invokeExactStaticMethod(Class objectClass, String methodName, Object [] args)}.
0592 * </p>
0593 *
0594 * @param objectClass invoke static method on this class
0595 * @param methodName get method with this name
0596 * @param arg use this argument
0597 * @return The value returned by the invoked method
0598 * @throws NoSuchMethodException if there is no such accessible method
0599 * @throws InvocationTargetException wraps an exception thrown by the
0600 * method invoked
0601 * @throws IllegalAccessException if the requested method is not accessible
0602 * via reflection
0603 * @since 1.8.0
0604 */
0605 public static Object invokeExactStaticMethod(
0606 Class objectClass,
0607 String methodName,
0608 Object arg)
0609 throws
0610 NoSuchMethodException,
0611 IllegalAccessException,
0612 InvocationTargetException {
0613
0614 Object[] args = {arg};
0615 return invokeExactStaticMethod(objectClass, methodName, args);
0616
0617 }
0618
0619
0620 /**
0621 * <p>Invoke a static method whose parameter types match exactly the object
0622 * types.</p>
0623 * <p/>
0624 * <p> This uses reflection to invoke the method obtained from a call to
0625 * {@link #getAccessibleMethod(Class, String, Class[])}.</p>
0626 *
0627 * @param objectClass invoke static method on this class
0628 * @param methodName get method with this name
0629 * @param args use these arguments - treat null as empty array
0630 * @return The value returned by the invoked method
0631 * @throws NoSuchMethodException if there is no such accessible method
0632 * @throws InvocationTargetException wraps an exception thrown by the
0633 * method invoked
0634 * @throws IllegalAccessException if the requested method is not accessible
0635 * via reflection
0636 * @since 1.8.0
0637 */
0638 public static Object invokeExactStaticMethod(
0639 Class objectClass,
0640 String methodName,
0641 Object[] args)
0642 throws
0643 NoSuchMethodException,
0644 IllegalAccessException,
0645 InvocationTargetException {
0646 if (args == null) {
0647 args = EMPTY_OBJECT_ARRAY;
0648 }
0649 int arguments = args.length;
0650 Class[] parameterTypes = new Class[arguments];
0651 for (int i = 0; i < arguments; i++) {
0652 parameterTypes[i] = args[i] != null ? args[i].getClass() : Object.class;
0653 }
0654 return invokeExactStaticMethod(objectClass, methodName, args, parameterTypes);
0655
0656 }
0657
0658
0659 /**
0660 * <p>Return an accessible method (that is, one that can be invoked via
0661 * reflection) with given name and a single parameter. If no such method
0662 * can be found, return <code>null</code>.
0663 * Basically, a convenience wrapper that constructs a <code>Class</code>
0664 * array for you.</p>
0665 *
0666 * @param clazz get method from this class
0667 * @param methodName get method with this name
0668 * @param parameterType taking this type of parameter
0669 * @return The accessible method
0670 */
0671 public static Method getAccessibleMethod(
0672 Class clazz,
0673 String methodName,
0674 Class parameterType) {
0675
0676 Class[] parameterTypes = {parameterType};
0677 return getAccessibleMethod(clazz, methodName, parameterTypes);
0678
0679 }
0680
0681
0682 /**
0683 * <p>Return an accessible method (that is, one that can be invoked via
0684 * reflection) with given name and parameters. If no such method
0685 * can be found, return <code>null</code>.
0686 * This is just a convenient wrapper for
0687 * {@link #getAccessibleMethod(Method method)}.</p>
0688 *
0689 * @param clazz get method from this class
0690 * @param methodName get method with this name
0691 * @param parameterTypes with these parameters types
0692 * @return The accessible method
0693 */
0694 public static Method getAccessibleMethod(
0695 Class clazz,
0696 String methodName,
0697 Class[] parameterTypes) {
0698
0699 try {
0700 MethodDescriptor md = new MethodDescriptor(clazz, methodName, parameterTypes, true);
0701 // Check the cache first
0702 Method method = getCachedMethod(md);
0703 if (method != null) {
0704 return method;
0705 }
0706
0707 method = getAccessibleMethod
0708 (clazz, clazz.getMethod(methodName, parameterTypes));
0709 cacheMethod(md, method);
0710 return method;
0711 } catch (NoSuchMethodException e) {
0712 return (null);
0713 }
0714
0715 }
0716
0717
0718 /**
0719 * <p>Return an accessible method (that is, one that can be invoked via
0720 * reflection) that implements the specified Method. If no such method
0721 * can be found, return <code>null</code>.</p>
0722 *
0723 * @param method The method that we wish to call
0724 * @return The accessible method
0725 */
0726 public static Method getAccessibleMethod(Method method) {
0727
0728 // Make sure we have a method to check
0729 if (method == null) {
0730 return (null);
0731 }
0732
0733 return getAccessibleMethod(method.getDeclaringClass(), method);
0734
0735 }
0736
0737
0738 /**
0739 * <p>Return an accessible method (that is, one that can be invoked via
0740 * reflection) that implements the specified Method. If no such method
0741 * can be found, return <code>null</code>.</p>
0742 *
0743 * @param clazz The class of the object
0744 * @param method The method that we wish to call
0745 * @return The accessible method
0746 * @since 1.8.0
0747 */
0748 public static Method getAccessibleMethod(Class clazz, Method method) {
0749
0750 // Make sure we have a method to check
0751 if (method == null) {
0752 return (null);
0753 }
0754
0755 // If the requested method is not public we cannot call it
0756 if (!Modifier.isPublic(method.getModifiers())) {
0757 return (null);
0758 }
0759
0760 boolean sameClass = true;
0761 if (clazz == null) {
0762 clazz = method.getDeclaringClass();
0763 } else {
0764 sameClass = clazz.equals(method.getDeclaringClass());
0765 if (!method.getDeclaringClass().isAssignableFrom(clazz)) {
0766 throw new IllegalArgumentException(clazz.getName() +
0767 " is not assignable from " + method.getDeclaringClass().getName());
0768 }
0769 }
0770
0771 // If the class is public, we are done
0772 if (Modifier.isPublic(clazz.getModifiers())) {
0773 if (!sameClass && !Modifier.isPublic(method.getDeclaringClass().getModifiers())) {
0774 setMethodAccessible(method); // Default access superclass workaround
0775 }
0776 return (method);
0777 }
0778
0779 String methodName = method.getName();
0780 Class[] parameterTypes = method.getParameterTypes();
0781
0782 // Check the implemented interfaces and subinterfaces
0783 method =
0784 getAccessibleMethodFromInterfaceNest(clazz,
0785 methodName,
0786 parameterTypes);
0787
0788 // Check the superclass chain
0789 if (method == null) {
0790 method = getAccessibleMethodFromSuperclass(clazz,
0791 methodName,
0792 parameterTypes);
0793 }
0794
0795 return (method);
0796 }
0797
0798
0799 // -------------------------------------------------------- Private Methods
0800
0801 /**
0802 * <p>Return an accessible method (that is, one that can be invoked via
0803 * reflection) by scanning through the superclasses. If no such method
0804 * can be found, return <code>null</code>.</p>
0805 *
0806 * @param clazz Class to be checked
0807 * @param methodName Method name of the method we wish to call
0808 * @param parameterTypes The parameter type signatures
0809 */
0810 private static Method getAccessibleMethodFromSuperclass
0811 (Class clazz, String methodName, Class[] parameterTypes) {
0812
0813 Class parentClazz = clazz.getSuperclass();
0814 while (parentClazz != null) {
0815 if (Modifier.isPublic(parentClazz.getModifiers())) {
0816 try {
0817 return parentClazz.getMethod(methodName, parameterTypes);
0818 } catch (NoSuchMethodException e) {
0819 return null;
0820 }
0821 }
0822 parentClazz = parentClazz.getSuperclass();
0823 }
0824 return null;
0825 }
0826
0827 /**
0828 * <p>Return an accessible method (that is, one that can be invoked via
0829 * reflection) that implements the specified method, by scanning through
0830 * all implemented interfaces and subinterfaces. If no such method
0831 * can be found, return <code>null</code>.</p>
0832 * <p/>
0833 * <p> There isn't any good reason why this method must be private.
0834 * It is because there doesn't seem any reason why other classes should
0835 * call this rather than the higher level methods.</p>
0836 *
0837 * @param clazz Parent class for the interfaces to be checked
0838 * @param methodName Method name of the method we wish to call
0839 * @param parameterTypes The parameter type signatures
0840 */
0841 private static Method getAccessibleMethodFromInterfaceNest
0842 (Class clazz, String methodName, Class[] parameterTypes) {
0843
0844 Method method = null;
0845
0846 // Search up the superclass chain
0847 for (; clazz != null; clazz = clazz.getSuperclass()) {
0848
0849 // Check the implemented interfaces of the parent class
0850 Class[] interfaces = clazz.getInterfaces();
0851 for (int i = 0; i < interfaces.length; i++) {
0852
0853 // Is this interface public?
0854 if (!Modifier.isPublic(interfaces[i].getModifiers())) {
0855 continue;
0856 }
0857
0858 // Does the method exist on this interface?
0859 try {
0860 method = interfaces[i].getDeclaredMethod(methodName,
0861 parameterTypes);
0862 } catch (NoSuchMethodException e) {
0863 /* Swallow, if no method is found after the loop then this
0864 * method returns null.
0865 */
0866 }
0867 if (method != null) {
0868 return method;
0869 }
0870
0871 // Recursively check our parent interfaces
0872 method =
0873 getAccessibleMethodFromInterfaceNest(interfaces[i],
0874 methodName,
0875 parameterTypes);
0876 if (method != null) {
0877 return method;
0878 }
0879
0880 }
0881
0882 }
0883
0884 // We did not find anything
0885 return (null);
0886
0887 }
0888
0889 /**
0890 * <p>Find an accessible method that matches the given name and has compatible parameters.
0891 * Compatible parameters mean that every method parameter is assignable from
0892 * the given parameters.
0893 * In other words, it finds a method with the given name
0894 * that will take the parameters given.<p>
0895 * <p/>
0896 * <p>This method is slightly undeterminstic since it loops
0897 * through methods names and return the first matching method.</p>
0898 * <p/>
0899 * <p>This method is used by
0900 * {@link
0901 * #invokeMethod(Object object, String methodName, Object [] args, Class[] parameterTypes)}.
0902 * <p/>
0903 * <p>This method can match primitive parameter by passing in wrapper classes.
0904 * For example, a <code>Boolean</code> will match a primitive <code>boolean</code>
0905 * parameter.
0906 *
0907 * @param clazz find method in this class
0908 * @param methodName find method with this name
0909 * @param parameterTypes find method with compatible parameters
0910 * @return The accessible method
0911 */
0912 public static Method getMatchingAccessibleMethod(
0913 Class clazz,
0914 String methodName,
0915 Class[] parameterTypes) {
0916 MethodDescriptor md = new MethodDescriptor(clazz, methodName, parameterTypes, false);
0917
0918 // see if we can find the method directly
0919 // most of the time this works and it's much faster
0920 try {
0921 // Check the cache first
0922 Method method = getCachedMethod(md);
0923 if (method != null) {
0924 return method;
0925 }
0926
0927 method = clazz.getMethod(methodName, parameterTypes);
0928
0929 setMethodAccessible(method); // Default access superclass workaround
0930
0931 cacheMethod(md, method);
0932 return method;
0933
0934 } catch (NoSuchMethodException e) { /* SWALLOW */ }
0935
0936 // search through all methods
0937 int paramSize = parameterTypes.length;
0938 Method bestMatch = null;
0939 Method[] methods = clazz.getMethods();
0940 float bestMatchCost = Float.MAX_VALUE;
0941 float myCost = Float.MAX_VALUE;
0942 for (int i = 0, size = methods.length; i < size; i++) {
0943 if (methods[i].getName().equals(methodName)) {
0944 // compare parameters
0945 Class[] methodsParams = methods[i].getParameterTypes();
0946 int methodParamSize = methodsParams.length;
0947 if (methodParamSize == paramSize) {
0948 boolean match = true;
0949 for (int n = 0; n < methodParamSize; n++) {
0950 if (!isAssignmentCompatible(methodsParams[n], parameterTypes[n])) {
0951 match = false;
0952 break;
0953 }
0954 }
0955
0956 if (match) {
0957 // get accessible version of method
0958 Method method = getAccessibleMethod(clazz, methods[i]);
0959 if (method != null) {
0960 setMethodAccessible(method); // Default access superclass workaround
0961 myCost = getTotalTransformationCost(parameterTypes, method.getParameterTypes());
0962 if (myCost < bestMatchCost) {
0963 bestMatch = method;
0964 bestMatchCost = myCost;
0965 }
0966 }
0967 }
0968 }
0969 }
0970 }
0971 if (bestMatch != null) { //find a match
0972 cacheMethod(md, bestMatch);
0973 }
0974 return bestMatch;
0975 }
0976
0977 /**
0978 * Try to make the method accessible
0979 *
0980 * @param method The source arguments
0981 */
0982 private static void setMethodAccessible(Method method) {
0983 try {
0984 //
0985 // XXX Default access superclass workaround
0986 //
0987 // When a public class has a default access superclass
0988 // with public methods, these methods are accessible.
0989 // Calling them from compiled code works fine.
0990 //
0991 // Unfortunately, using reflection to invoke these methods
0992 // seems to (wrongly) to prevent access even when the method
0993 // modifer is public.
0994 //
0995 // The following workaround solves the problem but will only
0996 // work from sufficiently privilages code.
0997 //
0998 // Better workarounds would be greatfully accepted.
0999 //
1000 if (!method.isAccessible()) {
1001 method.setAccessible(true);
1002 }
1003
1004 } catch (SecurityException se) {
1005 // ignore
1006 }
1007 }
1008
1009 /**
1010 * Returns the sum of the object transformation cost for each class in the source
1011 * argument list.
1012 *
1013 * @param srcArgs The source arguments
1014 * @param destArgs The destination arguments
1015 * @return The total transformation cost
1016 */
1017 private static float getTotalTransformationCost(Class[] srcArgs, Class[] destArgs) {
1018
1019 float totalCost = 0.0f;
1020 for (int i = 0; i < srcArgs.length; i++) {
1021 Class srcClass, destClass;
1022 srcClass = srcArgs[i];
1023 destClass = destArgs[i];
1024 totalCost += getObjectTransformationCost(srcClass, destClass);
1025 }
1026
1027 return totalCost;
1028 }
1029
1030 /**
1031 * Gets the number of steps required needed to turn the source class into the
1032 * destination class. This represents the number of steps in the object hierarchy
1033 * graph.
1034 *
1035 * @param srcClass The source class
1036 * @param destClass The destination class
1037 * @return The cost of transforming an object
1038 */
1039 private static float getObjectTransformationCost(Class srcClass, Class destClass) {
1040 float cost = 0.0f;
1041 while (destClass != null && !destClass.equals(srcClass)) {
1042 if (destClass.isInterface() && isAssignmentCompatible(destClass, srcClass)) {
1043 // slight penalty for interface match.
1044 // we still want an exact match to override an interface match, but
1045 // an interface match should override anything where we have to get a
1046 // superclass.
1047 cost += 0.25f;
1048 break;
1049 }
1050 cost++;
1051 destClass = destClass.getSuperclass();
1052 }
1053
1054 /*
1055 * If the destination class is null, we've travelled all the way up to
1056 * an Object match. We'll penalize this by adding 1.5 to the cost.
1057 */
1058 if (destClass == null) {
1059 cost += 1.5f;
1060 }
1061
1062 return cost;
1063 }
1064
1065
1066 /**
1067 * <p>Determine whether a type can be used as a parameter in a method invocation.
1068 * This method handles primitive conversions correctly.</p>
1069 * <p/>
1070 * <p>In order words, it will match a <code>Boolean</code> to a <code>boolean</code>,
1071 * a <code>Long</code> to a <code>long</code>,
1072 * a <code>Float</code> to a <code>float</code>,
1073 * a <code>Integer</code> to a <code>int</code>,
1074 * and a <code>Double</code> to a <code>double</code>.
1075 * Now logic widening matches are allowed.
1076 * For example, a <code>Long</code> will not match a <code>int</code>.
1077 *
1078 * @param parameterType the type of parameter accepted by the method
1079 * @param parameterization the type of parameter being tested
1080 * @return true if the assignement is compatible.
1081 */
1082 public static boolean isAssignmentCompatible(Class<?> parameterType, Class<?> parameterization) {
1083 // try plain assignment
1084 if (parameterType.isAssignableFrom(parameterization)) {
1085 return true;
1086 }
1087
1088 if (parameterType.isPrimitive()) {
1089 // this method does *not* do widening - you must specify exactly
1090 // is this the right behaviour?
1091 Class parameterWrapperClazz = getPrimitiveWrapper(parameterType);
1092 if (parameterWrapperClazz != null) {
1093 return parameterWrapperClazz.equals(parameterization);
1094 }
1095 }
1096
1097 return false;
1098 }
1099
1100 /**
1101 * Gets the wrapper object class for the given primitive type class.
1102 * For example, passing <code>boolean.class</code> returns <code>Boolean.class</code>
1103 *
1104 * @param primitiveType the primitive type class for which a match is to be found
1105 * @return the wrapper type associated with the given primitive
1106 * or null if no match is found
1107 */
1108 public static Class getPrimitiveWrapper(Class primitiveType) {
1109 // does anyone know a better strategy than comparing names?
1110 if (boolean.class.equals(primitiveType)) {
1111 return Boolean.class;
1112 } else if (float.class.equals(primitiveType)) {
1113 return Float.class;
1114 } else if (long.class.equals(primitiveType)) {
1115 return Long.class;
1116 } else if (int.class.equals(primitiveType)) {
1117 return Integer.class;
1118 } else if (short.class.equals(primitiveType)) {
1119 return Short.class;
1120 } else if (byte.class.equals(primitiveType)) {
1121 return Byte.class;
1122 } else if (double.class.equals(primitiveType)) {
1123 return Double.class;
1124 } else if (char.class.equals(primitiveType)) {
1125 return Character.class;
1126 } else {
1127
1128 return null;
1129 }
1130 }
1131
1132 /**
1133 * Gets the class for the primitive type corresponding to the primitive wrapper class given.
1134 * For example, an instance of <code>Boolean.class</code> returns a <code>boolean.class</code>.
1135 *
1136 * @param wrapperType the
1137 * @return the primitive type class corresponding to the given wrapper class,
1138 * null if no match is found
1139 */
1140 public static Class getPrimitiveType(Class wrapperType) {
1141 // does anyone know a better strategy than comparing names?
1142 if (Boolean.class.equals(wrapperType)) {
1143 return boolean.class;
1144 } else if (Float.class.equals(wrapperType)) {
1145 return float.class;
1146 } else if (Long.class.equals(wrapperType)) {
1147 return long.class;
1148 } else if (Integer.class.equals(wrapperType)) {
1149 return int.class;
1150 } else if (Short.class.equals(wrapperType)) {
1151 return short.class;
1152 } else if (Byte.class.equals(wrapperType)) {
1153 return byte.class;
1154 } else if (Double.class.equals(wrapperType)) {
1155 return double.class;
1156 } else if (Character.class.equals(wrapperType)) {
1157 return char.class;
1158 } else {
1159 return null;
1160 }
1161 }
1162
1163 /**
1164 * Find a non primitive representation for given primitive class.
1165 *
1166 * @param clazz the class to find a representation for, not null
1167 * @return the original class if it not a primitive. Otherwise the wrapper class. Not null
1168 */
1169 public static Class toNonPrimitiveClass(Class clazz) {
1170 if (clazz.isPrimitive()) {
1171 Class primitiveClazz = MethodUtils.getPrimitiveWrapper(clazz);
1172 // the above method returns
1173 if (primitiveClazz != null) {
1174 return primitiveClazz;
1175 } else {
1176 return clazz;
1177 }
1178 } else {
1179 return clazz;
1180 }
1181 }
1182
1183
1184 /**
1185 * Return the method from the cache, if present.
1186 *
1187 * @param md The method descriptor
1188 * @return The cached method
1189 */
1190 private static Method getCachedMethod(MethodDescriptor md) {
1191 if (CACHE_METHODS) {
1192 Reference methodRef = (Reference) cache.get(md);
1193 if (methodRef != null) {
1194 return (Method) methodRef.get();
1195 }
1196 }
1197 return null;
1198 }
1199
1200 /**
1201 * Add a method to the cache.
1202 *
1203 * @param md The method descriptor
1204 * @param method The method to cache
1205 */
1206 private static void cacheMethod(MethodDescriptor md, Method method) {
1207 if (CACHE_METHODS) {
1208 if (method != null) {
1209 cache.put(md, new WeakReference(method));
1210 }
1211 }
1212 }
1213
1214 public static Object invokeSafe(Method method, Object instance, Object[] args) {
1215 try {
1216 return method.invoke(instance, args);
1217 } catch (IllegalAccessException e) {
1218 // ignore
1219 } catch (InvocationTargetException e) {
1220 // ignore
1221 }
1222 return null;
1223 }
1224
1225 /**
1226 * Represents the key to looking up a Method by reflection.
1227 */
1228 private static class MethodDescriptor {
1229 private Class cls;
1230 private String methodName;
1231 private Class[] paramTypes;
1232 private boolean exact;
1233 private int hashCode;
1234
1235 /**
1236 * The sole constructor.
1237 *
1238 * @param cls the class to reflect, must not be null
1239 * @param methodName the method name to obtain
1240 * @param paramTypes the array of classes representing the paramater types
1241 * @param exact whether the match has to be exact.
1242 */
1243 public MethodDescriptor(Class cls, String methodName, Class[] paramTypes, boolean exact) {
1244 if (cls == null) {
1245 throw new IllegalArgumentException("Class must not be null");
1246 }
1247 if (methodName == null) {
1248 throw new IllegalArgumentException("Method Name must not be null");
1249 }
1250 if (paramTypes == null) {
1251 paramTypes = EMPTY_CLASS_PARAMETERS;
1252 }
1253
1254 this.cls = cls;
1255 this.methodName = methodName;
1256 this.paramTypes = paramTypes;
1257 this.exact = exact;
1258
1259 this.hashCode = methodName.length();
1260 }
1261
1262 /**
1263 * Checks for equality.
1264 *
1265 * @param obj object to be tested for equality
1266 * @return true, if the object describes the same Method.
1267 */
1268 public boolean equals(Object obj) {
1269 if (!(obj instanceof MethodDescriptor)) {
1270 return false;
1271 }
1272 MethodDescriptor md = (MethodDescriptor) obj;
1273
1274 return (
1275 exact == md.exact &&
1276 methodName.equals(md.methodName) &&
1277 cls.equals(md.cls) &&
1278 java.util.Arrays.equals(paramTypes, md.paramTypes)
1279 );
1280 }
1281
1282 /**
1283 * Returns the string length of method name. I.e. if the
1284 * hashcodes are different, the objects are different. If the
1285 * hashcodes are the same, need to use the equals method to
1286 * determine equality.
1287 *
1288 * @return the string length of method name.
1289 */
1290 public int hashCode() {
1291 return hashCode;
1292 }
1293 }
1294 }
|