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