1   
2   
3   
4   
5   
6   
7   
8   
9   
10  
11  
12  
13  
14  
15  
16  
17  package org.apache.commons.jexl.util.introspection;
18  
19  import java.lang.reflect.Method;
20  import java.lang.reflect.Modifier;
21  import java.util.Hashtable;
22  import java.util.Map;
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 OBJECT = new Object();
48  
49      /***
50       * Class passed into the constructor used to as the basis for the Method
51       * map.
52       */
53  
54      private Class 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 methodCache = new Hashtable();
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 aClass) {
70          clazz = aClass;
71          populateMethodCache();
72      }
73  
74      /***
75       * @return the class object whose methods are cached by this map.
76       */
77      Class 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 findMethod(String name, Object[] params) throws MethodMap.AmbiguousException {
97          String methodKey = makeMethodKey(name, params);
98          Object 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 
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         
125 
126         return (Method) 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 
137 
138 
139         Method[] methods = getAccessibleMethods(clazz);
140 
141         
142 
143 
144 
145         for (int i = 0; i < methods.length; i++) {
146             Method method = methods[i];
147 
148             
149 
150 
151 
152 
153 
154             Method publicMethod = getPublicMethod(method);
155 
156             
157 
158 
159 
160 
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 makeMethodKey(Method method) {
175         Class[] parameterTypes = method.getParameterTypes();
176 
177         StringBuffer methodKey = new StringBuffer(method.getName());
178 
179         for (int j = 0; j < parameterTypes.length; j++) {
180             
181 
182 
183 
184 
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 makeMethodKey(String method, Object[] params) {
212         StringBuffer methodKey = new StringBuffer().append(method);
213 
214         for (int j = 0; j < params.length; j++) {
215             Object 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[] getAccessibleMethods(Class clazz) {
234         Method[] methods = clazz.getMethods();
235 
236         
237 
238 
239 
240 
241         if (Modifier.isPublic(clazz.getModifiers())) {
242             return methods;
243         }
244 
245         
246 
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 
259 
260 
261         if (upcastCount < methods.length) {
262             methods = new Method[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 clazz, MethodInfo[] methodInfos, int upcastCount) {
285         int l = methodInfos.length;
286 
287         
288 
289 
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 e) {
302                     
303 
304 
305 
306                 }
307             }
308 
309             
310 
311 
312 
313             if (upcastCount == l) {
314                 return upcastCount;
315             }
316         }
317 
318         
319 
320 
321 
322         Class superclazz = clazz.getSuperclass();
323 
324         if (superclazz != null) {
325             upcastCount = getAccessibleMethods(superclazz, methodInfos, upcastCount);
326 
327             
328 
329 
330 
331             if (upcastCount == l) {
332                 return upcastCount;
333             }
334         }
335 
336         
337 
338 
339 
340 
341 
342         Class[] interfaces = clazz.getInterfaces();
343 
344         for (int i = interfaces.length; i-- > 0;) {
345             upcastCount = getAccessibleMethods(interfaces[i], methodInfos, upcastCount);
346 
347             
348 
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 getPublicMethod(Method method) {
371         Class clazz = method.getDeclaringClass();
372 
373         
374 
375 
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 getPublicMethod(Class clazz, String name, Class[] paramTypes) {
394         
395 
396 
397 
398         if ((clazz.getModifiers() & Modifier.PUBLIC) != 0) {
399             try {
400                 return clazz.getMethod(name, paramTypes);
401             } catch (NoSuchMethodException e) {
402                 
403 
404 
405 
406 
407                 return null;
408             }
409         }
410 
411         
412 
413 
414 
415         Class superclazz = clazz.getSuperclass();
416 
417         if (superclazz != null) {
418             Method superclazzMethod = getPublicMethod(superclazz, name, paramTypes);
419 
420             if (superclazzMethod != null) {
421                 return superclazzMethod;
422             }
423         }
424 
425         
426 
427 
428 
429         Class[] interfaces = clazz.getInterfaces();
430 
431         for (int i = 0; i < interfaces.length; ++i) {
432             Method 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 method;
447 
448         String name;
449 
450         Class[] parameterTypes;
451 
452         boolean upcast;
453 
454         MethodInfo(Method method) {
455             this.method = null;
456             name = method.getName();
457             parameterTypes = method.getParameterTypes();
458             upcast = false;
459         }
460 
461         void tryUpcasting(Class clazz) throws NoSuchMethodException {
462             method = clazz.getMethod(name, parameterTypes);
463             name = null;
464             parameterTypes = null;
465             upcast = true;
466         }
467     }
468 }