001 /*
002 * Copyright 2003-2006 Sun Microsystems, Inc. All Rights Reserved.
003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004 *
005 * This code is free software; you can redistribute it and/or modify it
006 * under the terms of the GNU General Public License version 2 only, as
007 * published by the Free Software Foundation. Sun designates this
008 * particular file as subject to the "Classpath" exception as provided
009 * by Sun in the LICENSE file that accompanied this code.
010 *
011 * This code is distributed in the hope that it will be useful, but WITHOUT
012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
014 * version 2 for more details (a copy is included in the LICENSE file that
015 * accompanied this code).
016 *
017 * You should have received a copy of the GNU General Public License version
018 * 2 along with this work; if not, write to the Free Software Foundation,
019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020 *
021 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
022 * CA 95054 USA or visit www.sun.com if you need additional information or
023 * have any questions.
024 */
025 package java.beans;
026
027 import java.lang.reflect.Constructor;
028 import java.lang.reflect.Field;
029 import java.lang.reflect.Method;
030 import java.lang.reflect.Modifier;
031
032 import java.lang.ref.Reference;
033 import java.lang.ref.SoftReference;
034
035 import java.util.*;
036
037 import com.sun.beans.ObjectHandler;
038 import sun.reflect.misc.MethodUtil;
039 import sun.reflect.misc.ConstructorUtil;
040 import sun.reflect.misc.ReflectUtil;
041
042 /**
043 * A utility class for reflectively finding methods, constuctors and fields
044 * using reflection.
045 */
046 class ReflectionUtils {
047
048 private static Reference methodCacheRef;
049
050 public static Class typeToClass(Class type) {
051 return type.isPrimitive() ? ObjectHandler.typeNameToClass(type
052 .getName()) : type;
053 }
054
055 public static boolean isPrimitive(Class type) {
056 return primitiveTypeFor(type) != null;
057 }
058
059 public static Class primitiveTypeFor(Class wrapper) {
060 if (wrapper == Boolean.class)
061 return Boolean.TYPE;
062 if (wrapper == Byte.class)
063 return Byte.TYPE;
064 if (wrapper == Character.class)
065 return Character.TYPE;
066 if (wrapper == Short.class)
067 return Short.TYPE;
068 if (wrapper == Integer.class)
069 return Integer.TYPE;
070 if (wrapper == Long.class)
071 return Long.TYPE;
072 if (wrapper == Float.class)
073 return Float.TYPE;
074 if (wrapper == Double.class)
075 return Double.TYPE;
076 if (wrapper == Void.class)
077 return Void.TYPE;
078 return null;
079 }
080
081 /**
082 * Tests each element on the class arrays for assignability.
083 *
084 * @param argClasses arguments to be tested
085 * @param argTypes arguments from Method
086 * @return true if each class in argTypes is assignable from the
087 * corresponding class in argClasses.
088 */
089 private static boolean matchArguments(Class[] argClasses,
090 Class[] argTypes) {
091 return matchArguments(argClasses, argTypes, false);
092 }
093
094 /**
095 * Tests each element on the class arrays for equality.
096 *
097 * @param argClasses arguments to be tested
098 * @param argTypes arguments from Method
099 * @return true if each class in argTypes is equal to the
100 * corresponding class in argClasses.
101 */
102 private static boolean matchExplicitArguments(Class[] argClasses,
103 Class[] argTypes) {
104 return matchArguments(argClasses, argTypes, true);
105 }
106
107 private static boolean matchArguments(Class[] argClasses,
108 Class[] argTypes, boolean explicit) {
109
110 boolean match = (argClasses.length == argTypes.length);
111 for (int j = 0; j < argClasses.length && match; j++) {
112 Class argType = argTypes[j];
113 if (argType.isPrimitive()) {
114 argType = typeToClass(argType);
115 }
116 if (explicit) {
117 // Test each element for equality
118 if (argClasses[j] != argType) {
119 match = false;
120 }
121 } else {
122 // Consider null an instance of all classes.
123 if (argClasses[j] != null
124 && !(argType.isAssignableFrom(argClasses[j]))) {
125 match = false;
126 }
127 }
128 }
129 return match;
130 }
131
132 /**
133 * @return the method which best matches the signature or throw an exception
134 * if it can't be found or the method is ambiguous.
135 */
136 static Method getPublicMethod(Class declaringClass,
137 String methodName, Class[] argClasses)
138 throws NoSuchMethodException {
139 Method m;
140
141 m = findPublicMethod(declaringClass, methodName, argClasses);
142 if (m == null)
143 throw new NoSuchMethodException(declaringClass.getName()
144 + "." + methodName);
145 return m;
146 }
147
148 /**
149 * @return the method which best matches the signature or null if it cant be found or
150 * the method is ambiguous.
151 */
152 public static Method findPublicMethod(Class declaringClass,
153 String methodName, Class[] argClasses) {
154 // Many methods are "getters" which take no arguments.
155 // This permits the following optimisation which
156 // avoids the expensive call to getMethods().
157 if (argClasses.length == 0) {
158 try {
159 return MethodUtil.getMethod(declaringClass, methodName,
160 argClasses);
161 } catch (NoSuchMethodException e) {
162 return null;
163 } catch (SecurityException se) {
164 // fall through
165 }
166 }
167 Method[] methods = MethodUtil.getPublicMethods(declaringClass);
168 List list = new ArrayList();
169 for (int i = 0; i < methods.length; i++) {
170 // Collect all the methods which match the signature.
171 Method method = methods[i];
172 if (method.getName().equals(methodName)) {
173 if (matchArguments(argClasses, method
174 .getParameterTypes())) {
175 list.add(method);
176 }
177 }
178 }
179 if (list.size() > 0) {
180 if (list.size() == 1) {
181 return (Method) list.get(0);
182 } else {
183 ListIterator iterator = list.listIterator();
184 Method method;
185 while (iterator.hasNext()) {
186 method = (Method) iterator.next();
187 if (matchExplicitArguments(argClasses, method
188 .getParameterTypes())) {
189 return method;
190 }
191 }
192 // There are more than one method which matches this signature.
193 // try to return the most specific method.
194 return getMostSpecificMethod(list, argClasses);
195 }
196 }
197 return null;
198 }
199
200 /**
201 * Return the most specific method from the list of methods which
202 * matches the args. The most specific method will have the most
203 * number of equal parameters or will be closest in the inheritance
204 * heirarchy to the runtime execution arguments.
205 * <p>
206 * See the JLS section 15.12
207 * http://java.sun.com/docs/books/jls/second_edition/html/expressions.doc.html#20448
208 *
209 * @param methods List of methods which already have the same param length
210 * and arg types are assignable to param types
211 * @param args an array of param types to match
212 * @return method or null if a specific method cannot be determined
213 */
214 private static Method getMostSpecificMethod(List methods,
215 Class[] args) {
216 Method method = null;
217
218 int matches = 0;
219 int lastMatch = matches;
220
221 ListIterator iterator = methods.listIterator();
222 while (iterator.hasNext()) {
223 Method m = (Method) iterator.next();
224 Class[] mArgs = m.getParameterTypes();
225 matches = 0;
226 for (int i = 0; i < args.length; i++) {
227 Class mArg = mArgs[i];
228 if (mArg.isPrimitive()) {
229 mArg = typeToClass(mArg);
230 }
231 if (args[i] == mArg) {
232 matches++;
233 }
234 }
235 if (matches == 0 && lastMatch == 0) {
236 if (method == null) {
237 method = m;
238 } else {
239 // Test existing method. We already know that the args can
240 // be assigned to all the method params. However, if the
241 // current method parameters is higher in the inheritance
242 // hierarchy then replace it.
243 if (!matchArguments(method.getParameterTypes(), m
244 .getParameterTypes())) {
245 method = m;
246 }
247 }
248 } else if (matches > lastMatch) {
249 lastMatch = matches;
250 method = m;
251 } else if (matches == lastMatch) {
252 // ambiguous method selection.
253 method = null;
254 }
255 }
256 return method;
257 }
258
259 /**
260 * @return the method or null if it can't be found or is ambiguous.
261 */
262 public static Method findMethod(Class targetClass,
263 String methodName, Class[] argClasses) {
264 Method m = findPublicMethod(targetClass, methodName, argClasses);
265 if (m != null
266 && Modifier.isPublic(m.getDeclaringClass()
267 .getModifiers())) {
268 return m;
269 }
270
271 /*
272 Search the interfaces for a public version of this method.
273
274 Example: the getKeymap() method of a JTextField
275 returns a package private implementation of the
276 of the public Keymap interface. In the Keymap
277 interface there are a number of "properties" one
278 being the "resolveParent" property implied by the
279 getResolveParent() method. This getResolveParent()
280 cannot be called reflectively because the class
281 itself is not public. Instead we search the class's
282 interfaces and find the getResolveParent()
283 method of the Keymap interface - on which invoke
284 may be applied without error.
285
286 So in :-
287
288 JTextField o = new JTextField("Hello, world");
289 Keymap km = o.getKeymap();
290 Method m1 = km.getClass().getMethod("getResolveParent", new Class[0]);
291 Method m2 = Keymap.class.getMethod("getResolveParent", new Class[0]);
292
293 Methods m1 and m2 are different. The invocation of method
294 m1 unconditionally throws an IllegalAccessException where
295 the invocation of m2 will invoke the implementation of the
296 method. Note that (ignoring the overloading of arguments)
297 there is only one implementation of the named method which
298 may be applied to this target.
299 */
300 for (Class type = targetClass; type != null; type = type
301 .getSuperclass()) {
302 Class[] interfaces = type.getInterfaces();
303 for (int i = 0; i < interfaces.length; i++) {
304 m = findPublicMethod(interfaces[i], methodName,
305 argClasses);
306 if (m != null) {
307 return m;
308 }
309 }
310 }
311 return null;
312 }
313
314 /**
315 * A class that represents the unique elements of a method that will be a
316 * key in the method cache.
317 */
318 private static class Signature {
319 private Class targetClass;
320 private String methodName;
321 private Class[] argClasses;
322
323 private volatile int hashCode = 0;
324
325 public Signature(Class targetClass, String methodName,
326 Class[] argClasses) {
327 this .targetClass = targetClass;
328 this .methodName = methodName;
329 this .argClasses = argClasses;
330 }
331
332 public boolean equals(Object o2) {
333 if (this == o2) {
334 return true;
335 }
336 Signature that = (Signature) o2;
337 if (!(targetClass == that.targetClass)) {
338 return false;
339 }
340 if (!(methodName.equals(that.methodName))) {
341 return false;
342 }
343 if (argClasses.length != that.argClasses.length) {
344 return false;
345 }
346 for (int i = 0; i < argClasses.length; i++) {
347 if (!(argClasses[i] == that.argClasses[i])) {
348 return false;
349 }
350 }
351 return true;
352 }
353
354 /**
355 * Hash code computed using algorithm suggested in
356 * Effective Java, Item 8.
357 */
358 public int hashCode() {
359 if (hashCode == 0) {
360 int result = 17;
361 result = 37 * result + targetClass.hashCode();
362 result = 37 * result + methodName.hashCode();
363 if (argClasses != null) {
364 for (int i = 0; i < argClasses.length; i++) {
365 result = 37
366 * result
367 + ((argClasses[i] == null) ? 0
368 : argClasses[i].hashCode());
369 }
370 }
371 hashCode = result;
372 }
373 return hashCode;
374 }
375 }
376
377 /**
378 * A wrapper to findMethod(), which will search or populate the method
379 * in a cache.
380 * @throws exception if the method is ambiguios.
381 */
382 public static synchronized Method getMethod(Class targetClass,
383 String methodName, Class[] argClasses) {
384 Object signature = new Signature(targetClass, methodName,
385 argClasses);
386
387 Method method = null;
388 Map methodCache = null;
389 boolean cache = false;
390 if (ReflectUtil.isPackageAccessible(targetClass)) {
391 cache = true;
392 }
393
394 if (cache && methodCacheRef != null
395 && (methodCache = (Map) methodCacheRef.get()) != null) {
396 method = (Method) methodCache.get(signature);
397 if (method != null) {
398 return method;
399 }
400 }
401 method = findMethod(targetClass, methodName, argClasses);
402 if (cache && method != null) {
403 if (methodCache == null) {
404 methodCache = new HashMap();
405 methodCacheRef = new SoftReference(methodCache);
406 }
407 methodCache.put(signature, method);
408 }
409 return method;
410 }
411
412 /**
413 * Return a constructor on the class with the arguments.
414 *
415 * @throws exception if the method is ambiguios.
416 */
417 public static Constructor getConstructor(Class cls, Class[] args) {
418 Constructor constructor = null;
419
420 // PENDING: Implement the resolutuion of ambiguities properly.
421 Constructor[] ctors = ConstructorUtil.getConstructors(cls);
422 for (int i = 0; i < ctors.length; i++) {
423 if (matchArguments(args, ctors[i].getParameterTypes())) {
424 constructor = ctors[i];
425 }
426 }
427 return constructor;
428 }
429
430 public static Object getPrivateField(Object instance, Class cls,
431 String name) {
432 return getPrivateField(instance, cls, name, null);
433 }
434
435 /**
436 * Returns the value of a private field.
437 *
438 * @param instance object instance
439 * @param cls class
440 * @param name name of the field
441 * @param el an exception listener to handle exceptions; or null
442 * @return value of the field; null if not found or an error is encountered
443 */
444 public static Object getPrivateField(Object instance, Class cls,
445 String name, ExceptionListener el) {
446 try {
447 Field f = cls.getDeclaredField(name);
448 f.setAccessible(true);
449 return f.get(instance);
450 } catch (Exception e) {
451 if (el != null) {
452 el.exceptionThrown(e);
453 }
454 }
455 return null;
456 }
457 }
|