1. /*
  2. * @(#)ReflectionUtils.java 1.7 03/12/19
  3. *
  4. * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
  5. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
  6. */
  7. package java.beans;
  8. import java.lang.reflect.Constructor;
  9. import java.lang.reflect.Field;
  10. import java.lang.reflect.Method;
  11. import java.lang.reflect.Modifier;
  12. import java.lang.ref.Reference;
  13. import java.lang.ref.SoftReference;
  14. import java.util.*;
  15. import com.sun.beans.ObjectHandler;
  16. /**
  17. * A utility class for reflectively finding methods, constuctors and fields
  18. * using reflection.
  19. */
  20. class ReflectionUtils {
  21. private static Reference methodCacheRef;
  22. public static Class typeToClass(Class type) {
  23. return type.isPrimitive() ? ObjectHandler.typeNameToClass(type.getName()) : type;
  24. }
  25. public static boolean isPrimitive(Class type) {
  26. return primitiveTypeFor(type) != null;
  27. }
  28. public static Class primitiveTypeFor(Class wrapper) {
  29. if (wrapper == Boolean.class) return Boolean.TYPE;
  30. if (wrapper == Byte.class) return Byte.TYPE;
  31. if (wrapper == Character.class) return Character.TYPE;
  32. if (wrapper == Short.class) return Short.TYPE;
  33. if (wrapper == Integer.class) return Integer.TYPE;
  34. if (wrapper == Long.class) return Long.TYPE;
  35. if (wrapper == Float.class) return Float.TYPE;
  36. if (wrapper == Double.class) return Double.TYPE;
  37. if (wrapper == Void.class) return Void.TYPE;
  38. return null;
  39. }
  40. /**
  41. * Tests each element on the class arrays for assignability.
  42. *
  43. * @param argClasses arguments to be tested
  44. * @param argTypes arguments from Method
  45. * @return true if each class in argTypes is assignable from the
  46. * corresponding class in argClasses.
  47. */
  48. private static boolean matchArguments(Class[] argClasses, Class[] argTypes) {
  49. return matchArguments(argClasses, argTypes, false);
  50. }
  51. /**
  52. * Tests each element on the class arrays for equality.
  53. *
  54. * @param argClasses arguments to be tested
  55. * @param argTypes arguments from Method
  56. * @return true if each class in argTypes is equal to the
  57. * corresponding class in argClasses.
  58. */
  59. private static boolean matchExplicitArguments(Class[] argClasses, Class[] argTypes) {
  60. return matchArguments(argClasses, argTypes, true);
  61. }
  62. private static boolean matchArguments(Class[] argClasses,
  63. Class[] argTypes, boolean explicit) {
  64. boolean match = (argClasses.length == argTypes.length);
  65. for(int j = 0; j < argClasses.length && match; j++) {
  66. Class argType = argTypes[j];
  67. if (argType.isPrimitive()) {
  68. argType = typeToClass(argType);
  69. }
  70. if (explicit) {
  71. // Test each element for equality
  72. if (argClasses[j] != argType) {
  73. match = false;
  74. }
  75. } else {
  76. // Consider null an instance of all classes.
  77. if (argClasses[j] != null &&
  78. !(argType.isAssignableFrom(argClasses[j]))) {
  79. match = false;
  80. }
  81. }
  82. }
  83. return match;
  84. }
  85. /**
  86. * @return the method which best matches the signature or null if it cant be found or
  87. * the method is ambiguous.
  88. */
  89. public static Method findPublicMethod(Class declaringClass, String methodName,
  90. Class[] argClasses) {
  91. // Many methods are "getters" which take no arguments.
  92. // This permits the following optimisation which
  93. // avoids the expensive call to getMethods().
  94. if (argClasses.length == 0) {
  95. try {
  96. return declaringClass.getMethod(methodName, argClasses);
  97. }
  98. catch (NoSuchMethodException e) {
  99. return null;
  100. }
  101. }
  102. Method[] methods = declaringClass.getMethods();
  103. List list = new ArrayList();
  104. for(int i = 0; i < methods.length; i++) {
  105. // Collect all the methods which match the signature.
  106. Method method = methods[i];
  107. if (method.getName().equals(methodName)) {
  108. if (matchArguments(argClasses, method.getParameterTypes())) {
  109. list.add(method);
  110. }
  111. }
  112. }
  113. if (list.size() > 0) {
  114. if (list.size() == 1) {
  115. return (Method)list.get(0);
  116. }
  117. else {
  118. ListIterator iterator = list.listIterator();
  119. Method method;
  120. while (iterator.hasNext()) {
  121. method = (Method)iterator.next();
  122. if (matchExplicitArguments(argClasses, method.getParameterTypes())) {
  123. return method;
  124. }
  125. }
  126. // There are more than one method which matches this signature.
  127. // try to return the most specific method.
  128. return getMostSpecificMethod(list, argClasses);
  129. }
  130. }
  131. return null;
  132. }
  133. /**
  134. * Return the most specific method from the list of methods which
  135. * matches the args. The most specific method will have the most
  136. * number of equal parameters or will be closest in the inheritance
  137. * heirarchy to the runtime execution arguments.
  138. * <p>
  139. * See the JLS section 15.12
  140. * http://java.sun.com/docs/books/jls/second_edition/html/expressions.doc.html#20448
  141. *
  142. * @param methods List of methods which already have the same param length
  143. * and arg types are assignable to param types
  144. * @param args an array of param types to match
  145. * @return method or null if a specific method cannot be determined
  146. */
  147. private static Method getMostSpecificMethod(List methods, Class[] args) {
  148. Method method = null;
  149. int matches = 0;
  150. int lastMatch = matches;
  151. ListIterator iterator = methods.listIterator();
  152. while (iterator.hasNext()) {
  153. Method m = (Method)iterator.next();
  154. Class[] mArgs = m.getParameterTypes();
  155. matches = 0;
  156. for (int i = 0; i < args.length; i++) {
  157. Class mArg = mArgs[i];
  158. if (mArg.isPrimitive()) {
  159. mArg = typeToClass(mArg);
  160. }
  161. if (args[i] == mArg) {
  162. matches++;
  163. }
  164. }
  165. if (matches == 0 && lastMatch == 0) {
  166. if (method == null) {
  167. method = m;
  168. } else {
  169. // Test existing method. We already know that the args can
  170. // be assigned to all the method params. However, if the
  171. // current method parameters is higher in the inheritance
  172. // hierarchy then replace it.
  173. if (!matchArguments(method.getParameterTypes(),
  174. m.getParameterTypes())) {
  175. method = m;
  176. }
  177. }
  178. } else if (matches > lastMatch) {
  179. lastMatch = matches;
  180. method = m;
  181. } else if (matches == lastMatch) {
  182. // ambiguous method selection.
  183. method = null;
  184. }
  185. }
  186. return method;
  187. }
  188. /**
  189. * @return the method or null if it can't be found or is ambiguous.
  190. */
  191. public static Method findMethod(Class targetClass, String methodName,
  192. Class[] argClasses) {
  193. Method m = findPublicMethod(targetClass, methodName, argClasses);
  194. if (m != null && Modifier.isPublic(m.getDeclaringClass().getModifiers())) {
  195. return m;
  196. }
  197. /*
  198. Search the interfaces for a public version of this method.
  199. Example: the getKeymap() method of a JTextField
  200. returns a package private implementation of the
  201. of the public Keymap interface. In the Keymap
  202. interface there are a number of "properties" one
  203. being the "resolveParent" property implied by the
  204. getResolveParent() method. This getResolveParent()
  205. cannot be called reflectively because the class
  206. itself is not public. Instead we search the class's
  207. interfaces and find the getResolveParent()
  208. method of the Keymap interface - on which invoke
  209. may be applied without error.
  210. So in :-
  211. JTextField o = new JTextField("Hello, world");
  212. Keymap km = o.getKeymap();
  213. Method m1 = km.getClass().getMethod("getResolveParent", new Class[0]);
  214. Method m2 = Keymap.class.getMethod("getResolveParent", new Class[0]);
  215. Methods m1 and m2 are different. The invocation of method
  216. m1 unconditionally throws an IllegalAccessException where
  217. the invocation of m2 will invoke the implementation of the
  218. method. Note that (ignoring the overloading of arguments)
  219. there is only one implementation of the named method which
  220. may be applied to this target.
  221. */
  222. for(Class type = targetClass; type != null; type = type.getSuperclass()) {
  223. Class[] interfaces = type.getInterfaces();
  224. for(int i = 0; i < interfaces.length; i++) {
  225. m = findPublicMethod(interfaces[i], methodName, argClasses);
  226. if (m != null) {
  227. return m;
  228. }
  229. }
  230. }
  231. return null;
  232. }
  233. /**
  234. * A class that represents the unique elements of a method that will be a
  235. * key in the method cache.
  236. */
  237. private static class Signature {
  238. private Class targetClass;
  239. private String methodName;
  240. private Class[] argClasses;
  241. private volatile int hashCode = 0;
  242. public Signature(Class targetClass, String methodName, Class[] argClasses) {
  243. this.targetClass = targetClass;
  244. this.methodName = methodName;
  245. this.argClasses = argClasses;
  246. }
  247. public boolean equals(Object o2) {
  248. if (this == o2) {
  249. return true;
  250. }
  251. Signature that = (Signature)o2;
  252. if (!(targetClass == that.targetClass)) {
  253. return false;
  254. }
  255. if (!(methodName.equals(that.methodName))) {
  256. return false;
  257. }
  258. if (argClasses.length != that.argClasses.length) {
  259. return false;
  260. }
  261. for (int i = 0; i < argClasses.length; i++) {
  262. if (!(argClasses[i] == that.argClasses[i])) {
  263. return false;
  264. }
  265. }
  266. return true;
  267. }
  268. /**
  269. * Hash code computed using algorithm suggested in
  270. * Effective Java, Item 8.
  271. */
  272. public int hashCode() {
  273. if (hashCode == 0) {
  274. int result = 17;
  275. result = 37 * result + targetClass.hashCode();
  276. result = 37 * result + methodName.hashCode();
  277. if (argClasses != null) {
  278. for (int i = 0; i < argClasses.length; i++) {
  279. result = 37 * result + ((argClasses[i] == null) ? 0 :
  280. argClasses[i].hashCode());
  281. }
  282. }
  283. hashCode = result;
  284. }
  285. return hashCode;
  286. }
  287. }
  288. /**
  289. * A wrapper to findMethod(), which will search or populate the method
  290. * in a cache.
  291. * @throws exception if the method is ambiguios.
  292. */
  293. public static synchronized Method getMethod(Class targetClass,
  294. String methodName,
  295. Class[] argClasses) {
  296. Object signature = new Signature(targetClass, methodName, argClasses);
  297. Method method = null;
  298. Map methodCache = null;
  299. if (methodCacheRef != null &&
  300. (methodCache = (Map)methodCacheRef.get()) != null) {
  301. method = (Method)methodCache.get(signature);
  302. if (method != null) {
  303. return method;
  304. }
  305. }
  306. method = findMethod(targetClass, methodName, argClasses);
  307. if (method != null) {
  308. if (methodCache == null) {
  309. methodCache = new HashMap();
  310. methodCacheRef = new SoftReference(methodCache);
  311. }
  312. methodCache.put(signature, method);
  313. }
  314. return method;
  315. }
  316. /**
  317. * Return a constructor on the class with the arguments.
  318. *
  319. * @throws exception if the method is ambiguios.
  320. */
  321. public static Constructor getConstructor(Class cls, Class[] args) {
  322. Constructor constructor = null;
  323. // PENDING: Implement the resolutuion of ambiguities properly.
  324. Constructor[] ctors = cls.getConstructors();
  325. for(int i = 0; i < ctors.length; i++) {
  326. if (matchArguments(args, ctors[i].getParameterTypes())) {
  327. constructor = ctors[i];
  328. }
  329. }
  330. return constructor;
  331. }
  332. public static Object getPrivateField(Object instance, Class cls, String name) {
  333. return getPrivateField(instance, cls, name);
  334. }
  335. /**
  336. * Returns the value of a private field.
  337. *
  338. * @param instance object instance
  339. * @param cls class
  340. * @param name name of the field
  341. * @param el an exception listener to handle exceptions; or null
  342. * @return value of the field; null if not found or an error is encountered
  343. */
  344. public static Object getPrivateField(Object instance, Class cls,
  345. String name, ExceptionListener el) {
  346. try {
  347. Field f = cls.getDeclaredField(name);
  348. f.setAccessible(true);
  349. return f.get(instance);
  350. }
  351. catch (Exception e) {
  352. if (el != null) {
  353. el.exceptionThrown(e);
  354. }
  355. }
  356. return null;
  357. }
  358. }