KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > commons > jexl > util > introspection > ClassMap


1 /*
2  * Copyright 2001-2002,2004 The Apache Software Foundation.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */

16
17 package org.apache.commons.jexl.util.introspection;
18
19 import java.lang.reflect.Method JavaDoc;
20 import java.lang.reflect.Modifier JavaDoc;
21 import java.util.Hashtable JavaDoc;
22 import java.util.Map JavaDoc;
23
24 /**
25  * Taken from the Velocity tree so we can be self-sufficient
26  *
27  * A cache of introspection information for a specific class instance. Keys
28  * {@link java.lang.Method} objects by a concatenation of the method name and
29  * the names of classes that make up the parameters.
30  *
31  * @since 1.0
32  * @author <a HREF="mailto:jvanzyl@apache.org">Jason van Zyl</a>
33  * @author <a HREF="mailto:bob@werken.com">Bob McWhirter</a>
34  * @author <a HREF="mailto:szegedia@freemail.hu">Attila Szegedi</a>
35  * @author <a HREF="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
36  * @version $Id: ClassMap.java 398459 2006-04-30 23:14:30Z dion $
37  */

38 public class ClassMap {
39     /** represents a miss on the cached data. */
40     private static final class CacheMiss {
41     }
42
43     /** constant for a miss on the cached data. */
44     private static final CacheMiss CACHE_MISS = new CacheMiss();
45
46     /** represents null or missing arguments. */
47     private static final Object JavaDoc OBJECT = new Object JavaDoc();
48
49     /**
50      * Class passed into the constructor used to as the basis for the Method
51      * map.
52      */

53
54     private Class JavaDoc clazz;
55
56     /**
57      * Cache of Methods, or CACHE_MISS, keyed by method name and actual
58      * arguments used to find it.
59      */

60     private final Map JavaDoc methodCache = new Hashtable JavaDoc();
61
62     /** map from method name and args to a {@link Method}. */
63     private final MethodMap methodMap = new MethodMap();
64
65     /**
66      * Standard constructor.
67      * @param aClass the class to deconstruct.
68      */

69     public ClassMap(Class JavaDoc aClass) {
70         clazz = aClass;
71         populateMethodCache();
72     }
73
74     /**
75      * @return the class object whose methods are cached by this map.
76      */

77     Class JavaDoc getCachedClass() {
78         return clazz;
79     }
80
81     /**
82      * Find a Method using the methodKey provided.
83      *
84      * Look in the methodMap for an entry. If found, it'll either be a
85      * CACHE_MISS, in which case we simply give up, or it'll be a Method, in
86      * which case, we return it.
87      *
88      * If nothing is found, then we must actually go and introspect the method
89      * from the MethodMap.
90      *
91      * @param name method name
92      * @param params method parameters
93      * @return CACHE_MISS or a {@link Method}
94      * @throws MethodMap.AmbiguousException if the method and parameters are ambiguous.
95      */

96     public Method JavaDoc findMethod(String JavaDoc name, Object JavaDoc[] params) throws MethodMap.AmbiguousException {
97         String JavaDoc methodKey = makeMethodKey(name, params);
98         Object JavaDoc cacheEntry = methodCache.get(methodKey);
99
100         if (cacheEntry == CACHE_MISS) {
101             return null;
102         }
103
104         if (cacheEntry == null) {
105             try {
106                 cacheEntry = methodMap.find(name, params);
107             } catch (MethodMap.AmbiguousException ae) {
108                 /*
109                  * that's a miss :)
110                  */

111
112                 methodCache.put(methodKey, CACHE_MISS);
113
114                 throw ae;
115             }
116
117             if (cacheEntry == null) {
118                 methodCache.put(methodKey, CACHE_MISS);
119             } else {
120                 methodCache.put(methodKey, cacheEntry);
121             }
122         }
123
124         // Yes, this might just be null.
125

126         return (Method JavaDoc) cacheEntry;
127     }
128
129     /**
130      * Populate the Map of direct hits. These are taken from all the public
131      * methods that our class provides.
132      */

133     private void populateMethodCache() {
134
135         /*
136          * get all publicly accessible methods
137          */

138
139         Method JavaDoc[] methods = getAccessibleMethods(clazz);
140
141         /*
142          * map and cache them
143          */

144
145         for (int i = 0; i < methods.length; i++) {
146             Method JavaDoc method = methods[i];
147
148             /*
149              * now get the 'public method', the method declared by a public
150              * interface or class. (because the actual implementing class may be
151              * a facade...
152              */

153
154             Method JavaDoc publicMethod = getPublicMethod(method);
155
156             /*
157              * it is entirely possible that there is no public method for the
158              * methods of this class (i.e. in the facade, a method that isn't on
159              * any of the interfaces or superclass in which case, ignore it.
160              * Otherwise, map and cache
161              */

162
163             if (publicMethod != null) {
164                 methodMap.add(publicMethod);
165                 methodCache.put(makeMethodKey(publicMethod), publicMethod);
166             }
167         }
168     }
169
170     /**
171      * Make a methodKey for the given method using the concatenation of the name
172      * and the types of the method parameters.
173      */

174     private String JavaDoc makeMethodKey(Method JavaDoc method) {
175         Class JavaDoc[] parameterTypes = method.getParameterTypes();
176
177         StringBuffer JavaDoc methodKey = new StringBuffer JavaDoc(method.getName());
178
179         for (int j = 0; j < parameterTypes.length; j++) {
180             /*
181              * If the argument type is primitive then we want to convert our
182              * primitive type signature to the corresponding Object type so
183              * introspection for methods with primitive types will work
184              * correctly.
185              */

186             if (parameterTypes[j].isPrimitive()) {
187                 if (parameterTypes[j].equals(Boolean.TYPE))
188                     methodKey.append("java.lang.Boolean");
189                 else if (parameterTypes[j].equals(Byte.TYPE))
190                     methodKey.append("java.lang.Byte");
191                 else if (parameterTypes[j].equals(Character.TYPE))
192                     methodKey.append("java.lang.Character");
193                 else if (parameterTypes[j].equals(Double.TYPE))
194                     methodKey.append("java.lang.Double");
195                 else if (parameterTypes[j].equals(Float.TYPE))
196                     methodKey.append("java.lang.Float");
197                 else if (parameterTypes[j].equals(Integer.TYPE))
198                     methodKey.append("java.lang.Integer");
199                 else if (parameterTypes[j].equals(Long.TYPE))
200                     methodKey.append("java.lang.Long");
201                 else if (parameterTypes[j].equals(Short.TYPE))
202                     methodKey.append("java.lang.Short");
203             } else {
204                 methodKey.append(parameterTypes[j].getName());
205             }
206         }
207
208         return methodKey.toString();
209     }
210
211     private static String JavaDoc makeMethodKey(String JavaDoc method, Object JavaDoc[] params) {
212         StringBuffer JavaDoc methodKey = new StringBuffer JavaDoc().append(method);
213
214         for (int j = 0; j < params.length; j++) {
215             Object JavaDoc arg = params[j];
216
217             if (arg == null) {
218                 arg = OBJECT;
219             }
220
221             methodKey.append(arg.getClass().getName());
222         }
223
224         return methodKey.toString();
225     }
226
227     /**
228      * Retrieves public methods for a class. In case the class is not public,
229      * retrieves methods with same signature as its public methods from public
230      * superclasses and interfaces (if they exist). Basically upcasts every
231      * method to the nearest acccessible method.
232      */

233     private static Method JavaDoc[] getAccessibleMethods(Class JavaDoc clazz) {
234         Method JavaDoc[] methods = clazz.getMethods();
235
236         /*
237          * Short circuit for the (hopefully) majority of cases where the clazz
238          * is public
239          */

240
241         if (Modifier.isPublic(clazz.getModifiers())) {
242             return methods;
243         }
244
245         /*
246          * No luck - the class is not public, so we're going the longer way.
247          */

248
249         MethodInfo[] methodInfos = new MethodInfo[methods.length];
250
251         for (int i = methods.length; i-- > 0;) {
252             methodInfos[i] = new MethodInfo(methods[i]);
253         }
254
255         int upcastCount = getAccessibleMethods(clazz, methodInfos, 0);
256
257         /*
258          * Reallocate array in case some method had no accessible counterpart.
259          */

260
261         if (upcastCount < methods.length) {
262             methods = new Method JavaDoc[upcastCount];
263         }
264
265         int j = 0;
266         for (int i = 0; i < methodInfos.length; ++i) {
267             MethodInfo methodInfo = methodInfos[i];
268             if (methodInfo.upcast) {
269                 methods[j++] = methodInfo.method;
270             }
271         }
272         return methods;
273     }
274
275     /**
276      * Recursively finds a match for each method, starting with the class, and
277      * then searching the superclass and interfaces.
278      *
279      * @param clazz Class to check
280      * @param methodInfos array of methods we are searching to match
281      * @param upcastCount current number of methods we have matched
282      * @return count of matched methods
283      */

284     private static int getAccessibleMethods(Class JavaDoc clazz, MethodInfo[] methodInfos, int upcastCount) {
285         int l = methodInfos.length;
286
287         /*
288          * if this class is public, then check each of the currently
289          * 'non-upcasted' methods to see if we have a match
290          */

291
292         if (Modifier.isPublic(clazz.getModifiers())) {
293             for (int i = 0; i < l && upcastCount < l; ++i) {
294                 try {
295                     MethodInfo methodInfo = methodInfos[i];
296
297                     if (!methodInfo.upcast) {
298                         methodInfo.tryUpcasting(clazz);
299                         upcastCount++;
300                     }
301                 } catch (NoSuchMethodException JavaDoc e) {
302                     /*
303                      * Intentionally ignored - it means it wasn't found in the
304                      * current class
305                      */

306                 }
307             }
308
309             /*
310              * Short circuit if all methods were upcast
311              */

312
313             if (upcastCount == l) {
314                 return upcastCount;
315             }
316         }
317
318         /*
319          * Examine superclass
320          */

321
322         Class JavaDoc superclazz = clazz.getSuperclass();
323
324         if (superclazz != null) {
325             upcastCount = getAccessibleMethods(superclazz, methodInfos, upcastCount);
326
327             /*
328              * Short circuit if all methods were upcast
329              */

330
331             if (upcastCount == l) {
332                 return upcastCount;
333             }
334         }
335
336         /*
337          * Examine interfaces. Note we do it even if superclazz == null. This is
338          * redundant as currently java.lang.Object does not implement any
339          * interfaces, however nothing guarantees it will not in future.
340          */

341
342         Class JavaDoc[] interfaces = clazz.getInterfaces();
343
344         for (int i = interfaces.length; i-- > 0;) {
345             upcastCount = getAccessibleMethods(interfaces[i], methodInfos, upcastCount);
346
347             /*
348              * Short circuit if all methods were upcast
349              */

350
351             if (upcastCount == l) {
352                 return upcastCount;
353             }
354         }
355
356         return upcastCount;
357     }
358
359     /**
360      * For a given method, retrieves its publicly accessible counterpart. This
361      * method will look for a method with same name and signature declared in a
362      * public superclass or implemented interface of this method's declaring
363      * class. This counterpart method is publicly callable.
364      *
365      * @param method a method whose publicly callable counterpart is requested.
366      * @return the publicly callable counterpart method. Note that if the
367      * parameter method is itself declared by a public class, this
368      * method is an identity function.
369      */

370     public static Method JavaDoc getPublicMethod(Method JavaDoc method) {
371         Class JavaDoc clazz = method.getDeclaringClass();
372
373         /*
374          * Short circuit for (hopefully the majority of) cases where the
375          * declaring class is public.
376          */

377
378         if ((clazz.getModifiers() & Modifier.PUBLIC) != 0) {
379             return method;
380         }
381
382         return getPublicMethod(clazz, method.getName(), method.getParameterTypes());
383     }
384
385     /**
386      * Looks up the method with specified name and signature in the first public
387      * superclass or implemented interface of the class.
388      *
389      * @param class the class whose method is sought
390      * @param name the name of the method
391      * @param paramTypes the classes of method parameters
392      */

393     private static Method JavaDoc getPublicMethod(Class JavaDoc clazz, String JavaDoc name, Class JavaDoc[] paramTypes) {
394         /*
395          * if this class is public, then try to get it
396          */

397
398         if ((clazz.getModifiers() & Modifier.PUBLIC) != 0) {
399             try {
400                 return clazz.getMethod(name, paramTypes);
401             } catch (NoSuchMethodException JavaDoc e) {
402                 /*
403                  * If the class does not have the method, then neither its
404                  * superclass nor any of its interfaces has it so quickly return
405                  * null.
406                  */

407                 return null;
408             }
409         }
410
411         /*
412          * try the superclass
413          */

414
415         Class JavaDoc superclazz = clazz.getSuperclass();
416
417         if (superclazz != null) {
418             Method JavaDoc superclazzMethod = getPublicMethod(superclazz, name, paramTypes);
419
420             if (superclazzMethod != null) {
421                 return superclazzMethod;
422             }
423         }
424
425         /*
426          * and interfaces
427          */

428
429         Class JavaDoc[] interfaces = clazz.getInterfaces();
430
431         for (int i = 0; i < interfaces.length; ++i) {
432             Method JavaDoc interfaceMethod = getPublicMethod(interfaces[i], name, paramTypes);
433
434             if (interfaceMethod != null) {
435                 return interfaceMethod;
436             }
437         }
438
439         return null;
440     }
441
442     /**
443      * Used for the iterative discovery process for public methods.
444      */

445     private static final class MethodInfo {
446         Method JavaDoc method;
447
448         String JavaDoc name;
449
450         Class JavaDoc[] parameterTypes;
451
452         boolean upcast;
453
454         MethodInfo(Method JavaDoc method) {
455             this.method = null;
456             name = method.getName();
457             parameterTypes = method.getParameterTypes();
458             upcast = false;
459         }
460
461         void tryUpcasting(Class JavaDoc clazz) throws NoSuchMethodException JavaDoc {
462             method = clazz.getMethod(name, parameterTypes);
463             name = null;
464             parameterTypes = null;
465             upcast = true;
466         }
467     }
468 }
469
Popular Tags