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