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 public class MethodUtils {
0057
0058 // --------------------------------------------------------- Private Methods
0059
0060 /**
0061 * Indicates whether methods should be cached for improved performance.
0062 * <p/>
0063 * Note that when this class is deployed via a shared classloader in
0064 * a container, this will affect all webapps. However making this
0065 * configurable per webapp would mean having a map keyed by context classloader
0066 * which may introduce memory-leak problems.
0067 */
0068 private static boolean CACHE_METHODS = true;
0069
0070 /**
0071 * An empty class array
0072 */
0073 private static final Class[] EMPTY_CLASS_PARAMETERS = new Class[0];
0074 /**
0075 * An empty object array
0076 */
0077 private static final Object[] EMPTY_OBJECT_ARRAY = new Object[0];
0078
0079 /**
0080 * Stores a cache of MethodDescriptor -> Method in a WeakHashMap.
0081 * <p/>
0082 * The keys into this map only ever exist as temporary variables within
0083 * methods of this class, and are never exposed to users of this class.
0084 * This means that the WeakHashMap is used only as a mechanism for
0085 * limiting the size of the cache, ie a way to tell the garbage collector
0086 * that the contents of the cache can be completely garbage-collected
0087 * whenever it needs the memory. Whether this is a good approach to
0088 * this problem is doubtful; something like the commons-collections
0089 * LRUMap may be more appropriate (though of course selecting an
0090 * appropriate size is an issue).
0091 * <p/>
0092 * This static variable is safe even when this code is deployed via a
0093 * shared classloader because it is keyed via a MethodDescriptor object
0094 * which has a Class as one of its members and that member is used in
0095 * the MethodDescriptor.equals method. So two components that load the same
0096 * class via different classloaders will generate non-equal MethodDescriptor
0097 * objects and hence end up with different entries in the map.
0098 */
0099 private static final Map cache = Collections.synchronizedMap(new WeakHashMap());
0100
0101 // --------------------------------------------------------- Public Methods
0102
0103 /**
0104 * Set whether methods should be cached for greater performance or not,
0105 * default is <code>true</code>.
0106 *
0107 * @param cacheMethods <code>true</code> if methods should be
0108 * cached for greater performance, otherwise <code>false</code>
0109 * @since 1.8.0
0110 */
0111 public static synchronized void setCacheMethods(boolean cacheMethods) {
0112 CACHE_METHODS = cacheMethods;
0113 if (!CACHE_METHODS) {
0114 clearCache();
0115 }
0116 }
0117
0118 /**
0119 * Clear the method cache.
0120 *
0121 * @return the number of cached methods cleared
0122 * @since 1.8.0
0123 */
0124 public static synchronized int clearCache() {
0125 int size = cache.size();
0126 cache.clear();
0127 return size;
0128 }
0129
0130 /**
0131 * <p>Invoke a named method whose parameter type matches the object type.</p>
0132 * <p/>
0133 * <p>The behaviour of this method is less deterministic
0134 * than <code>invokeExactMethod()</code>.
0135 * It loops through all methods with names that match
0136 * and then executes the first it finds with compatable parameters.</p>
0137 * <p/>
0138 * <p>This method supports calls to methods taking primitive parameters
0139 * via passing in wrapping classes. So, for example, a <code>Boolean</code> class
0140 * would match a <code>boolean</code> primitive.</p>
0141 * <p/>
0142 * <p> This is a convenient wrapper for
0143 * {@link #invokeMethod(Object object, String methodName, Object [] args)}.
0144 * </p>
0145 *
0146 * @param object invoke method on this object
0147 * @param methodName get method with this name
0148 * @param arg use this argument
0149 * @return The value returned by the invoked method
0150 * @throws NoSuchMethodException if there is no such accessible method
0151 * @throws InvocationTargetException wraps an exception thrown by the
0152 * method invoked
0153 * @throws IllegalAccessException if the requested method is not accessible
0154 * via reflection
0155 */
0156 public static Object invokeMethod(
0157 Object object,
0158 String methodName,
0159 Object arg)
0160 throws
0161 NoSuchMethodException,
0162 IllegalAccessException,
0163 InvocationTargetException {
0164
0165 Object[] args = {arg};
0166 return invokeMethod(object, methodName, args);
0167
0168 }
0169
0170
0171 /**
0172 * <p>Invoke a named method whose parameter type matches the object type.</p>
0173 * <p/>
0174 * <p>The behaviour of this method is less deterministic
0175 * than {@link #invokeExactMethod(Object object, String methodName, Object [] args)}.
0176 * It loops through all methods with names that match
0177 * and then executes the first it finds with compatable parameters.</p>
0178 * <p/>
0179 * <p>This method supports calls to methods taking primitive parameters
0180 * via passing in wrapping classes. So, for example, a <code>Boolean</code> class
0181 * would match a <code>boolean</code> primitive.</p>
0182 * <p/>
0183 * <p> This is a convenient wrapper for
0184 * {@link #invokeMethod(Object object, String methodName, Object [] args, Class[] parameterTypes)}.
0185 * </p>
0186 *
0187 * @param object invoke method on this object
0188 * @param methodName get method with this name
0189 * @param args use these arguments - treat null as empty array
0190 * @return The value returned by the invoked method
0191 * @throws NoSuchMethodException if there is no such accessible method
0192 * @throws InvocationTargetException wraps an exception thrown by the
0193 * method invoked
0194 * @throws IllegalAccessException if the requested method is not accessible
0195 * via reflection
0196 */
0197 public static Object invokeMethod(
0198 Object object,
0199 String methodName,
0200 Object[] args)
0201 throws
0202 NoSuchMethodException,
0203 IllegalAccessException,
0204 InvocationTargetException {
0205
0206 if (args == null) {
0207 args = EMPTY_OBJECT_ARRAY;
0208 }
0209 int arguments = args.length;
0210 Class[] parameterTypes = new Class[arguments];
0211 for (int i = 0; i < arguments; i++) {
0212 parameterTypes[i] = args[i].getClass();
0213 }
0214 return invokeMethod(object, methodName, args, parameterTypes);
0215
0216 }
0217
0218
0219 /**
0220 * <p>Invoke a named method whose parameter type matches the object type.</p>
0221 * <p/>
0222 * <p>The behaviour of this method is less deterministic
0223 * than {@link
0224 * #invokeExactMethod(Object object, String methodName, Object [] args, Class[] parameterTypes)}.
0225 * It loops through all methods with names that match
0226 * and then executes the first it finds with compatable parameters.</p>
0227 * <p/>
0228 * <p>This method supports calls to methods taking primitive parameters
0229 * via passing in wrapping classes. So, for example, a <code>Boolean</code> class
0230 * would match a <code>boolean</code> primitive.</p>
0231 *
0232 * @param object invoke method on this object
0233 * @param methodName get method with this name
0234 * @param args use these arguments - treat null as empty array
0235 * @param parameterTypes match these parameters - treat null as empty array
0236 * @return The value returned by the invoked method
0237 * @throws NoSuchMethodException if there is no such accessible method
0238 * @throws InvocationTargetException wraps an exception thrown by the
0239 * method invoked
0240 * @throws IllegalAccessException if the requested method is not accessible
0241 * via reflection
0242 */
0243 public static Object invokeMethod(
0244 Object object,
0245 String methodName,
0246 Object[] args,
0247 Class[] parameterTypes)
0248 throws
0249 NoSuchMethodException,
0250 IllegalAccessException,
0251 InvocationTargetException {
0252
0253 if (parameterTypes == null) {
0254 parameterTypes = EMPTY_CLASS_PARAMETERS;
0255 }
0256 if (args == null) {
0257 args = EMPTY_OBJECT_ARRAY;
0258 }
0259
0260 Method method = getMatchingAccessibleMethod(
0261 object.getClass(),
0262 methodName,
0263 parameterTypes);
0264 if (method == null) {
0265 throw new NoSuchMethodException("No such accessible method: " +
0266 methodName + "() on object: " + object.getClass().getName());
0267 }
0268 return method.invoke(object, args);
0269 }
0270
0271
0272 /**
0273 * <p>Invoke a method whose parameter type matches exactly the object
0274 * type.</p>
0275 * <p/>
0276 * <p> This is a convenient wrapper for
0277 * {@link #invokeExactMethod(Object object, String methodName, Object [] args)}.
0278 * </p>
0279 *
0280 * @param object invoke method on this object
0281 * @param methodName get method with this name
0282 * @param arg use this argument
0283 * @return The value returned by the invoked method
0284 * @throws NoSuchMethodException if there is no such accessible method
0285 * @throws InvocationTargetException wraps an exception thrown by the
0286 * method invoked
0287 * @throws IllegalAccessException if the requested method is not accessible
0288 * via reflection
0289 */
0290 public static Object invokeExactMethod(
0291 Object object,
0292 String methodName,
0293 Object arg)
0294 throws
0295 NoSuchMethodException,
0296 IllegalAccessException,
0297 InvocationTargetException {
0298
0299 Object[] args = {arg};
0300 return invokeExactMethod(object, methodName, args);
0301
0302 }
0303
0304
0305 /**
0306 * <p>Invoke a method whose parameter types match exactly the object
0307 * types.</p>
0308 * <p/>
0309 * <p> This uses reflection to invoke the method obtained from a call to
0310 * <code>getAccessibleMethod()</code>.</p>
0311 *
0312 * @param object invoke method on this object
0313 * @param methodName get method with this name
0314 * @param args use these arguments - treat null as empty array
0315 * @return The value returned by the invoked method
0316 * @throws NoSuchMethodException if there is no such accessible method
0317 * @throws InvocationTargetException wraps an exception thrown by the
0318 * method invoked
0319 * @throws IllegalAccessException if the requested method is not accessible
0320 * via reflection
0321 */
0322 public static Object invokeExactMethod(
0323 Object object,
0324 String methodName,
0325 Object[] args)
0326 throws
0327 NoSuchMethodException,
0328 IllegalAccessException,
0329 InvocationTargetException {
0330 if (args == null) {
0331 args = EMPTY_OBJECT_ARRAY;
0332 }
0333 int arguments = args.length;
0334 Class[] parameterTypes = new Class[arguments];
0335 for (int i = 0; i < arguments; i++) {
0336 parameterTypes[i] = args[i].getClass();
0337 }
0338 return invokeExactMethod(object, methodName, args, parameterTypes);
0339
0340 }
0341
0342
0343 /**
0344 * <p>Invoke a method whose parameter types match exactly the parameter
0345 * types given.</p>
0346 * <p/>
0347 * <p>This uses reflection to invoke the method obtained from a call to
0348 * <code>getAccessibleMethod()</code>.</p>
0349 *
0350 * @param object invoke method on this object
0351 * @param methodName get method with this name
0352 * @param args use these arguments - treat null as empty array
0353 * @param parameterTypes match these parameters - treat null as empty array
0354 * @return The value returned by the invoked method
0355 * @throws NoSuchMethodException if there is no such accessible method
0356 * @throws InvocationTargetException wraps an exception thrown by the
0357 * method invoked
0358 * @throws IllegalAccessException if the requested method is not accessible
0359 * via reflection
0360 */
0361 public static Object invokeExactMethod(
0362 Object object,
0363 String methodName,
0364 Object[] args,
0365 Class[] parameterTypes)
0366 throws
0367 NoSuchMethodException,
0368 IllegalAccessException,
0369 InvocationTargetException {
0370
0371 if (args == null) {
0372 args = EMPTY_OBJECT_ARRAY;
0373 }
0374
0375 if (parameterTypes == null) {
0376 parameterTypes = EMPTY_CLASS_PARAMETERS;
0377 }
0378
0379 Method method = getAccessibleMethod(
0380 object.getClass(),
0381 methodName,
0382 parameterTypes);
0383 if (method == null) {
0384 throw new NoSuchMethodException("No such accessible method: " +
0385 methodName + "() on object: " + object.getClass().getName());
0386 }
0387 return method.invoke(object, args);
0388
0389 }
0390
0391 /**
0392 * <p>Invoke a static method whose parameter types match exactly the parameter
0393 * types given.</p>
0394 * <p/>
0395 * <p>This uses reflection to invoke the method obtained from a call to
0396 * {@link #getAccessibleMethod(Class, String, Class[])}.</p>
0397 *
0398 * @param objectClass invoke static method on this class
0399 * @param methodName get method with this name
0400 * @param args use these arguments - treat null as empty array
0401 * @param parameterTypes match these parameters - treat null as empty array
0402 * @return The value returned by the invoked method
0403 * @throws NoSuchMethodException if there is no such accessible method
0404 * @throws InvocationTargetException wraps an exception thrown by the
0405 * method invoked
0406 * @throws IllegalAccessException if the requested method is not accessible
0407 * via reflection
0408 * @since 1.8.0
0409 */
0410 public static Object invokeExactStaticMethod(
0411 Class objectClass,
0412 String methodName,
0413 Object[] args,
0414 Class[] parameterTypes)
0415 throws
0416 NoSuchMethodException,
0417 IllegalAccessException,
0418 InvocationTargetException {
0419
0420 if (args == null) {
0421 args = EMPTY_OBJECT_ARRAY;
0422 }
0423
0424 if (parameterTypes == null) {
0425 parameterTypes = EMPTY_CLASS_PARAMETERS;
0426 }
0427
0428 Method method = getAccessibleMethod(
0429 objectClass,
0430 methodName,
0431 parameterTypes);
0432 if (method == null) {
0433 throw new NoSuchMethodException("No such accessible method: " +
0434 methodName + "() on class: " + objectClass.getName());
0435 }
0436 return method.invoke(null, args);
0437
0438 }
0439
0440 /**
0441 * <p>Invoke a named static method whose parameter type matches the object type.</p>
0442 * <p/>
0443 * <p>The behaviour of this method is less deterministic
0444 * than {@link #invokeExactMethod(Object, String, Object[], Class[])}.
0445 * It loops through all methods with names that match
0446 * and then executes the first it finds with compatable parameters.</p>
0447 * <p/>
0448 * <p>This method supports calls to methods taking primitive parameters
0449 * via passing in wrapping classes. So, for example, a <code>Boolean</code> class
0450 * would match a <code>boolean</code> primitive.</p>
0451 * <p/>
0452 * <p> This is a convenient wrapper for
0453 * {@link #invokeStaticMethod(Class objectClass, String methodName, Object [] args)}.
0454 * </p>
0455 *
0456 * @param objectClass invoke static method on this class
0457 * @param methodName get method with this name
0458 * @param arg use this argument
0459 * @return The value returned by the invoked method
0460 * @throws NoSuchMethodException if there is no such accessible method
0461 * @throws InvocationTargetException wraps an exception thrown by the
0462 * method invoked
0463 * @throws IllegalAccessException if the requested method is not accessible
0464 * via reflection
0465 * @since 1.8.0
0466 */
0467 public static Object invokeStaticMethod(
0468 Class objectClass,
0469 String methodName,
0470 Object arg)
0471 throws
0472 NoSuchMethodException,
0473 IllegalAccessException,
0474 InvocationTargetException {
0475
0476 Object[] args = {arg};
0477 return invokeStaticMethod(objectClass, methodName, args);
0478
0479 }
0480
0481
0482 /**
0483 * <p>Invoke a named static method whose parameter type matches the object type.</p>
0484 * <p/>
0485 * <p>The behaviour of this method is less deterministic
0486 * than {@link #invokeExactMethod(Object object, String methodName, Object [] args)}.
0487 * It loops through all methods with names that match
0488 * and then executes the first it finds with compatable parameters.</p>
0489 * <p/>
0490 * <p>This method supports calls to methods taking primitive parameters
0491 * via passing in wrapping classes. So, for example, a <code>Boolean</code> class
0492 * would match a <code>boolean</code> primitive.</p>
0493 * <p/>
0494 * <p> This is a convenient wrapper for
0495 * {@link #invokeStaticMethod(Class objectClass, String methodName, Object [] args, Class[] parameterTypes)}.
0496 * </p>
0497 *
0498 * @param objectClass invoke static method on this class
0499 * @param methodName get method with this name
0500 * @param args use these arguments - treat null as empty array
0501 * @return The value returned by the invoked method
0502 * @throws NoSuchMethodException if there is no such accessible method
0503 * @throws InvocationTargetException wraps an exception thrown by the
0504 * method invoked
0505 * @throws IllegalAccessException if the requested method is not accessible
0506 * via reflection
0507 * @since 1.8.0
0508 */
0509 public static Object invokeStaticMethod(
0510 Class objectClass,
0511 String methodName,
0512 Object[] args)
0513 throws
0514 NoSuchMethodException,
0515 IllegalAccessException,
0516 InvocationTargetException {
0517
0518 if (args == null) {
0519 args = EMPTY_OBJECT_ARRAY;
0520 }
0521 int arguments = args.length;
0522 Class[] parameterTypes = new Class[arguments];
0523 for (int i = 0; i < arguments; i++) {
0524 parameterTypes[i] = args[i].getClass();
0525 }
0526 return invokeStaticMethod(objectClass, methodName, args, parameterTypes);
0527
0528 }
0529
0530
0531 /**
0532 * <p>Invoke a named static method whose parameter type matches the object type.</p>
0533 * <p/>
0534 * <p>The behaviour of this method is less deterministic
0535 * than {@link
0536 * #invokeExactStaticMethod(Class objectClass, String methodName, Object [] args, Class[] parameterTypes)}.
0537 * It loops through all methods with names that match
0538 * and then executes the first it finds with compatable parameters.</p>
0539 * <p/>
0540 * <p>This method supports calls to methods taking primitive parameters
0541 * via passing in wrapping classes. So, for example, a <code>Boolean</code> class
0542 * would match a <code>boolean</code> primitive.</p>
0543 *
0544 * @param objectClass invoke static method on this class
0545 * @param methodName get method with this name
0546 * @param args use these arguments - treat null as empty array
0547 * @param parameterTypes match these parameters - treat null as empty array
0548 * @return The value returned by the invoked method
0549 * @throws NoSuchMethodException if there is no such accessible method
0550 * @throws InvocationTargetException wraps an exception thrown by the
0551 * method invoked
0552 * @throws IllegalAccessException if the requested method is not accessible
0553 * via reflection
0554 * @since 1.8.0
0555 */
0556 public static Object invokeStaticMethod(
0557 Class objectClass,
0558 String methodName,
0559 Object[] args,
0560 Class[] parameterTypes)
0561 throws
0562 NoSuchMethodException,
0563 IllegalAccessException,
0564 InvocationTargetException {
0565
0566 if (parameterTypes == null) {
0567 parameterTypes = EMPTY_CLASS_PARAMETERS;
0568 }
0569 if (args == null) {
0570 args = EMPTY_OBJECT_ARRAY;
0571 }
0572
0573 Method method = getMatchingAccessibleMethod(
0574 objectClass,
0575 methodName,
0576 parameterTypes);
0577 if (method == null) {
0578 throw new NoSuchMethodException("No such accessible method: " +
0579 methodName + "() on class: " + objectClass.getName());
0580 }
0581 return method.invoke(null, args);
0582 }
0583
0584
0585 /**
0586 * <p>Invoke a static method whose parameter type matches exactly the object
0587 * type.</p>
0588 * <p/>
0589 * <p> This is a convenient wrapper for
0590 * {@link #invokeExactStaticMethod(Class objectClass, String methodName, Object [] args)}.
0591 * </p>
0592 *
0593 * @param objectClass invoke static method on this class
0594 * @param methodName get method with this name
0595 * @param arg use this argument
0596 * @return The value returned by the invoked method
0597 * @throws NoSuchMethodException if there is no such accessible method
0598 * @throws InvocationTargetException wraps an exception thrown by the
0599 * method invoked
0600 * @throws IllegalAccessException if the requested method is not accessible
0601 * via reflection
0602 * @since 1.8.0
0603 */
0604 public static Object invokeExactStaticMethod(
0605 Class objectClass,
0606 String methodName,
0607 Object arg)
0608 throws
0609 NoSuchMethodException,
0610 IllegalAccessException,
0611 InvocationTargetException {
0612
0613 Object[] args = {arg};
0614 return invokeExactStaticMethod(objectClass, methodName, args);
0615
0616 }
0617
0618
0619 /**
0620 * <p>Invoke a static method whose parameter types match exactly the object
0621 * types.</p>
0622 * <p/>
0623 * <p> This uses reflection to invoke the method obtained from a call to
0624 * {@link #getAccessibleMethod(Class, String, Class[])}.</p>
0625 *
0626 * @param objectClass invoke static method on this class
0627 * @param methodName get method with this name
0628 * @param args use these arguments - treat null as empty array
0629 * @return The value returned by the invoked method
0630 * @throws NoSuchMethodException if there is no such accessible method
0631 * @throws InvocationTargetException wraps an exception thrown by the
0632 * method invoked
0633 * @throws IllegalAccessException if the requested method is not accessible
0634 * via reflection
0635 * @since 1.8.0
0636 */
0637 public static Object invokeExactStaticMethod(
0638 Class objectClass,
0639 String methodName,
0640 Object[] args)
0641 throws
0642 NoSuchMethodException,
0643 IllegalAccessException,
0644 InvocationTargetException {
0645 if (args == null) {
0646 args = EMPTY_OBJECT_ARRAY;
0647 }
0648 int arguments = args.length;
0649 Class[] parameterTypes = new Class[arguments];
0650 for (int i = 0; i < arguments; i++) {
0651 parameterTypes[i] = args[i].getClass();
0652 }
0653 return invokeExactStaticMethod(objectClass, methodName, args, parameterTypes);
0654
0655 }
0656
0657
0658 /**
0659 * <p>Return an accessible method (that is, one that can be invoked via
0660 * reflection) with given name and a single parameter. If no such method
0661 * can be found, return <code>null</code>.
0662 * Basically, a convenience wrapper that constructs a <code>Class</code>
0663 * array for you.</p>
0664 *
0665 * @param clazz get method from this class
0666 * @param methodName get method with this name
0667 * @param parameterType taking this type of parameter
0668 * @return The accessible method
0669 */
0670 public static Method getAccessibleMethod(
0671 Class clazz,
0672 String methodName,
0673 Class parameterType) {
0674
0675 Class[] parameterTypes = {parameterType};
0676 return getAccessibleMethod(clazz, methodName, parameterTypes);
0677
0678 }
0679
0680
0681 /**
0682 * <p>Return an accessible method (that is, one that can be invoked via
0683 * reflection) with given name and parameters. If no such method
0684 * can be found, return <code>null</code>.
0685 * This is just a convenient wrapper for
0686 * {@link #getAccessibleMethod(Method method)}.</p>
0687 *
0688 * @param clazz get method from this class
0689 * @param methodName get method with this name
0690 * @param parameterTypes with these parameters types
0691 * @return The accessible method
0692 */
0693 public static Method getAccessibleMethod(
0694 Class clazz,
0695 String methodName,
0696 Class[] parameterTypes) {
0697
0698 try {
0699 MethodDescriptor md = new MethodDescriptor(clazz, methodName, parameterTypes, true);
0700 // Check the cache first
0701 Method method = getCachedMethod(md);
0702 if (method != null) {
0703 return method;
0704 }
0705
0706 method = getAccessibleMethod
0707 (clazz, clazz.getMethod(methodName, parameterTypes));
0708 cacheMethod(md, method);
0709 return method;
0710 } catch (NoSuchMethodException e) {
0711 return (null);
0712 }
0713
0714 }
0715
0716
0717 /**
0718 * <p>Return an accessible method (that is, one that can be invoked via
0719 * reflection) that implements the specified Method. If no such method
0720 * can be found, return <code>null</code>.</p>
0721 *
0722 * @param method The method that we wish to call
0723 * @return The accessible method
0724 */
0725 public static Method getAccessibleMethod(Method method) {
0726
0727 // Make sure we have a method to check
0728 if (method == null) {
0729 return (null);
0730 }
0731
0732 return getAccessibleMethod(method.getDeclaringClass(), method);
0733
0734 }
0735
0736
0737 /**
0738 * <p>Return an accessible method (that is, one that can be invoked via
0739 * reflection) that implements the specified Method. If no such method
0740 * can be found, return <code>null</code>.</p>
0741 *
0742 * @param clazz The class of the object
0743 * @param method The method that we wish to call
0744 * @return The accessible method
0745 * @since 1.8.0
0746 */
0747 public static Method getAccessibleMethod(Class clazz, Method method) {
0748
0749 // Make sure we have a method to check
0750 if (method == null) {
0751 return (null);
0752 }
0753
0754 // If the requested method is not public we cannot call it
0755 if (!Modifier.isPublic(method.getModifiers())) {
0756 return (null);
0757 }
0758
0759 boolean sameClass = true;
0760 if (clazz == null) {
0761 clazz = method.getDeclaringClass();
0762 } else {
0763 sameClass = clazz.equals(method.getDeclaringClass());
0764 if (!method.getDeclaringClass().isAssignableFrom(clazz)) {
0765 throw new IllegalArgumentException(clazz.getName() +
0766 " is not assignable from " + method.getDeclaringClass().getName());
0767 }
0768 }
0769
0770 // If the class is public, we are done
0771 if (Modifier.isPublic(clazz.getModifiers())) {
0772 if (!sameClass && !Modifier.isPublic(method.getDeclaringClass().getModifiers())) {
0773 setMethodAccessible(method); // Default access superclass workaround
0774 }
0775 return (method);
0776 }
0777
0778 String methodName = method.getName();
0779 Class[] parameterTypes = method.getParameterTypes();
0780
0781 // Check the implemented interfaces and subinterfaces
0782 method =
0783 getAccessibleMethodFromInterfaceNest(clazz,
0784 methodName,
0785 parameterTypes);
0786
0787 // Check the superclass chain
0788 if (method == null) {
0789 method = getAccessibleMethodFromSuperclass(clazz,
0790 methodName,
0791 parameterTypes);
0792 }
0793
0794 return (method);
0795
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 final 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 /**
1215 * Represents the key to looking up a Method by reflection.
1216 */
1217 private static class MethodDescriptor {
1218 private Class cls;
1219 private String methodName;
1220 private Class[] paramTypes;
1221 private boolean exact;
1222 private int hashCode;
1223
1224 /**
1225 * The sole constructor.
1226 *
1227 * @param cls the class to reflect, must not be null
1228 * @param methodName the method name to obtain
1229 * @param paramTypes the array of classes representing the paramater types
1230 * @param exact whether the match has to be exact.
1231 */
1232 public MethodDescriptor(Class cls, String methodName, Class[] paramTypes, boolean exact) {
1233 if (cls == null) {
1234 throw new IllegalArgumentException("Class cannot be null");
1235 }
1236 if (methodName == null) {
1237 throw new IllegalArgumentException("Method Name cannot be null");
1238 }
1239 if (paramTypes == null) {
1240 paramTypes = EMPTY_CLASS_PARAMETERS;
1241 }
1242
1243 this.cls = cls;
1244 this.methodName = methodName;
1245 this.paramTypes = paramTypes;
1246 this.exact = exact;
1247
1248 this.hashCode = methodName.length();
1249 }
1250
1251 /**
1252 * Checks for equality.
1253 *
1254 * @param obj object to be tested for equality
1255 * @return true, if the object describes the same Method.
1256 */
1257 public boolean equals(Object obj) {
1258 if (!(obj instanceof MethodDescriptor)) {
1259 return false;
1260 }
1261 MethodDescriptor md = (MethodDescriptor) obj;
1262
1263 return (
1264 exact == md.exact &&
1265 methodName.equals(md.methodName) &&
1266 cls.equals(md.cls) &&
1267 java.util.Arrays.equals(paramTypes, md.paramTypes)
1268 );
1269 }
1270
1271 /**
1272 * Returns the string length of method name. I.e. if the
1273 * hashcodes are different, the objects are different. If the
1274 * hashcodes are the same, need to use the equals method to
1275 * determine equality.
1276 *
1277 * @return the string length of method name.
1278 */
1279 public int hashCode() {
1280 return hashCode;
1281 }
1282 }
1283 }
|