View Javadoc

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;
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                  * 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) 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[] methods = getAccessibleMethods(clazz);
140 
141         /*
142          * map and cache them
143          */
144 
145         for (int i = 0; i < methods.length; i++) {
146             Method 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 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 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              * 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 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          * 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[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          * 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 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 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[] 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 getPublicMethod(Method method) {
371         Class 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 getPublicMethod(Class clazz, String name, Class[] 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 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 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          * and interfaces
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 }