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.
28   * Keys {@link java.lang.Method} objects by a concatenation of the
29   * method name and the names of classes that make up the parameters.
30   *
31   * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
32   * @author <a href="mailto:bob@werken.com">Bob McWhirter</a>
33   * @author <a href="mailto:szegedia@freemail.hu">Attila Szegedi</a>
34   * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
35   * @version $Id: ClassMap.java,v 1.5 2004/08/19 17:15:59 dion Exp $
36   */
37  public class ClassMap
38  {
39      private static final class CacheMiss { }
40      private static final CacheMiss CACHE_MISS = new CacheMiss();
41      private static final Object OBJECT = new Object();
42  
43      /*** 
44       * Class passed into the constructor used to as
45       * the basis for the Method map.
46       */
47  
48      private Class clazz;
49  
50      /***
51       * Cache of Methods, or CACHE_MISS, keyed by method
52       * name and actual arguments used to find it.
53       */
54      private Map methodCache = new Hashtable();
55  
56      private MethodMap methodMap = new MethodMap();
57  
58      /***
59       * Standard constructor
60       */
61      public ClassMap( Class clazz)
62      {
63          this.clazz = clazz;
64          populateMethodCache();
65      }
66  
67      private ClassMap()
68      {
69      }
70  
71      /***
72       * @return the class object whose methods are cached by this map.
73       */
74       Class getCachedClass()
75       {
76           return clazz;
77       }
78      
79      /***
80       * Find a Method using the methodKey
81       * provided.
82       *
83       * Look in the methodMap for an entry.  If found,
84       * it'll either be a CACHE_MISS, in which case we
85       * simply give up, or it'll be a Method, in which
86       * case, we return it.
87       *
88       * If nothing is found, then we must actually go
89       * and introspect the method from the MethodMap.
90       */
91      public Method findMethod(String name, Object[] params)
92          throws MethodMap.AmbiguousException
93      {
94          String methodKey = makeMethodKey(name, params);
95          Object cacheEntry = methodCache.get( methodKey );
96  
97          if (cacheEntry == CACHE_MISS)
98          {
99              return null;
100         }
101 
102         if (cacheEntry == null)
103         {
104             try
105             {
106                 cacheEntry = methodMap.find( name,
107                                              params );
108             }
109             catch( MethodMap.AmbiguousException ae )
110             {
111                 /*
112                  *  that's a miss :)
113                  */
114 
115                 methodCache.put( methodKey,
116                                  CACHE_MISS );
117 
118                 throw ae;
119             }
120 
121             if ( cacheEntry == null )
122             {
123                 methodCache.put( methodKey,
124                                  CACHE_MISS );
125             }
126             else
127             {
128                 methodCache.put( methodKey,
129                                  cacheEntry );
130             }
131         }
132 
133         // Yes, this might just be null.
134         
135         return (Method) cacheEntry;
136     }
137     
138     /***
139      * Populate the Map of direct hits. These
140      * are taken from all the public methods
141      * that our class provides.
142      */
143     private void populateMethodCache()
144     {
145 
146         /*
147          *  get all publicly accessible methods
148          */
149 
150         Method[] methods = getAccessibleMethods(clazz);
151 
152         /*
153          * map and cache them
154          */
155 
156         for (int i = 0; i < methods.length; i++)
157         {
158             Method method = methods[i];
159 
160             /*
161              *  now get the 'public method', the method declared by a 
162              *  public interface or class. (because the actual implementing
163              *  class may be a facade...
164              */
165 
166             Method publicMethod = getPublicMethod( method );
167 
168             /*
169              *  it is entirely possible that there is no public method for
170              *  the methods of this class (i.e. in the facade, a method
171              *  that isn't on any of the interfaces or superclass
172              *  in which case, ignore it.  Otherwise, map and cache
173              */
174 
175             if ( publicMethod != null)
176             {
177                 methodMap.add( publicMethod );
178                 methodCache.put(  makeMethodKey( publicMethod), publicMethod);
179             }
180         }            
181     }
182 
183     /***
184      * Make a methodKey for the given method using
185      * the concatenation of the name and the
186      * types of the method parameters.
187      */
188     private String makeMethodKey(Method method)
189     {
190         Class[] parameterTypes = method.getParameterTypes();
191         
192         StringBuffer methodKey = new StringBuffer(method.getName());
193         
194         for (int j = 0; j < parameterTypes.length; j++)
195         {
196             /*
197              * If the argument type is primitive then we want
198              * to convert our primitive type signature to the 
199              * corresponding Object type so introspection for
200              * methods with primitive types will work correctly.
201              */
202             if (parameterTypes[j].isPrimitive())
203             {
204                 if (parameterTypes[j].equals(Boolean.TYPE))
205                     methodKey.append("java.lang.Boolean");
206                 else if (parameterTypes[j].equals(Byte.TYPE))
207                     methodKey.append("java.lang.Byte");
208                 else if (parameterTypes[j].equals(Character.TYPE))
209                     methodKey.append("java.lang.Character");
210                 else if (parameterTypes[j].equals(Double.TYPE))
211                     methodKey.append("java.lang.Double");
212                 else if (parameterTypes[j].equals(Float.TYPE))
213                     methodKey.append("java.lang.Float");
214                 else if (parameterTypes[j].equals(Integer.TYPE))
215                     methodKey.append("java.lang.Integer");
216                 else if (parameterTypes[j].equals(Long.TYPE))
217                     methodKey.append("java.lang.Long");
218                 else if (parameterTypes[j].equals(Short.TYPE))
219                     methodKey.append("java.lang.Short");
220             }                
221             else
222             {
223                 methodKey.append(parameterTypes[j].getName());
224             }
225         }            
226         
227         return methodKey.toString();
228     }
229 
230     private static String makeMethodKey(String method, Object[] params)
231     {
232         StringBuffer methodKey = new StringBuffer().append(method);
233 
234         for (int j = 0; j < params.length; j++)
235         {
236             Object arg = params[j];
237 
238             if (arg == null)
239             {
240                 arg = OBJECT;
241             }
242 
243             methodKey.append(arg.getClass().getName());
244         }
245         
246         return methodKey.toString();
247     }
248 
249     /***
250      * Retrieves public methods for a class. In case the class is not
251      * public, retrieves methods with same signature as its public methods
252      * from public superclasses and interfaces (if they exist). Basically
253      * upcasts every method to the nearest acccessible method.
254      */
255     private static Method[] getAccessibleMethods(Class clazz)
256     {
257         Method[] methods = clazz.getMethods();
258         
259         /*
260          *  Short circuit for the (hopefully) majority of cases where the
261          *  clazz is public
262          */
263         
264         if (Modifier.isPublic(clazz.getModifiers()))
265         {
266             return methods;
267         }
268 
269         /*
270          *  No luck - the class is not public, so we're going the longer way.
271          */
272 
273         MethodInfo[] methodInfos = new MethodInfo[methods.length];
274 
275         for(int i = methods.length; i-- > 0; )
276         {
277             methodInfos[i] = new MethodInfo(methods[i]);
278         }
279 
280         int upcastCount = getAccessibleMethods(clazz, methodInfos, 0);
281 
282         /*
283          *  Reallocate array in case some method had no accessible counterpart.
284          */
285 
286         if(upcastCount < methods.length)
287         {
288             methods = new Method[upcastCount];
289         }
290 
291         int j = 0;
292         for(int i = 0; i < methodInfos.length; ++i)
293         {
294             MethodInfo methodInfo = methodInfos[i];
295             if(methodInfo.upcast)
296             {
297                 methods[j++] = methodInfo.method;
298             }
299         }
300         return methods;
301     }
302 
303     /***
304      *  Recursively finds a match for each method, starting with the class, and then
305      *  searching the superclass and interfaces.
306      *
307      *  @param clazz Class to check
308      *  @param methodInfos array of methods we are searching to match
309      *  @param upcastCount current number of methods we have matched
310      *  @return count of matched methods
311      */
312     private static int getAccessibleMethods( Class clazz, MethodInfo[] methodInfos, int upcastCount)
313     {
314         int l = methodInfos.length;
315         
316         /*
317          *  if this class is public, then check each of the currently
318          *  'non-upcasted' methods to see if we have a match
319          */
320 
321         if( Modifier.isPublic(clazz.getModifiers()) )
322         {
323             for(int i = 0; i < l && upcastCount < l; ++i)
324             {
325                 try
326                 {
327                     MethodInfo methodInfo = methodInfos[i];
328 
329                     if(!methodInfo.upcast)
330                     {
331                         methodInfo.tryUpcasting(clazz);
332                         upcastCount++;
333                     }
334                 }
335                 catch(NoSuchMethodException e)
336                 {
337                     /*
338                      *  Intentionally ignored - it means
339                      *  it wasn't found in the current class
340                      */
341                 }
342             }
343 
344             /*
345              *  Short circuit if all methods were upcast
346              */
347 
348             if(upcastCount == l)
349             {
350                 return upcastCount;
351             }
352         }
353 
354         /*
355          *   Examine superclass
356          */
357 
358         Class superclazz = clazz.getSuperclass();
359 
360         if(superclazz != null)
361         {
362             upcastCount = getAccessibleMethods(superclazz , methodInfos, upcastCount);
363 
364             /*
365              *  Short circuit if all methods were upcast
366              */
367 
368             if(upcastCount == l)
369             {
370                 return upcastCount;
371             }
372         }
373 
374         /*
375          *  Examine interfaces. Note we do it even if superclazz == null.
376          *  This is redundant as currently java.lang.Object does not implement
377          *  any interfaces, however nothing guarantees it will not in future.
378          */
379 
380         Class[] interfaces = clazz.getInterfaces();
381 
382         for(int i = interfaces.length; i-- > 0; )
383         {
384             upcastCount = getAccessibleMethods(interfaces[i], methodInfos, upcastCount);
385 
386             /*
387              *  Short circuit if all methods were upcast
388              */
389 
390             if(upcastCount == l)
391             {
392                 return upcastCount;
393             }
394         }
395 
396         return upcastCount;
397     }
398     
399     /***
400      *  For a given method, retrieves its publicly accessible counterpart. 
401      *  This method will look for a method with same name
402      *  and signature declared in a public superclass or implemented interface of this 
403      *  method's declaring class. This counterpart method is publicly callable.
404      *
405      *  @param method a method whose publicly callable counterpart is requested.
406      *  @return the publicly callable counterpart method. Note that if the parameter
407      *  method is itself declared by a public class, this method is an identity
408      *  function.
409      */
410     public static Method getPublicMethod(Method method)
411     {
412         Class clazz = method.getDeclaringClass();
413         
414         /*
415          *   Short circuit for (hopefully the majority of) cases where the declaring
416          *   class is public.
417          */
418 
419         if((clazz.getModifiers() & Modifier.PUBLIC) != 0)
420         {
421             return method;
422         }
423 
424         return getPublicMethod(clazz, method.getName(), method.getParameterTypes());
425     }
426 
427     /***
428      *  Looks up the method with specified name and signature in the first public
429      *  superclass or implemented interface of the class. 
430      * 
431      *  @param class the class whose method is sought
432      *  @param name the name of the method
433      *  @param paramTypes the classes of method parameters
434      */
435     private static Method getPublicMethod(Class clazz, String name, Class[] paramTypes)
436     {
437         /*
438          *  if this class is public, then try to get it
439          */
440 
441         if((clazz.getModifiers() & Modifier.PUBLIC) != 0)
442         {
443             try
444             {
445                 return clazz.getMethod(name, paramTypes);
446             }
447             catch(NoSuchMethodException e)
448             {
449                 /*
450                  *  If the class does not have the method, then neither its
451                  *  superclass nor any of its interfaces has it so quickly return
452                  *  null.
453                  */
454                  return null;
455             }
456         }
457 
458         /*
459          *  try the superclass
460          */
461 
462  
463         Class superclazz = clazz.getSuperclass();
464 
465         if ( superclazz != null )
466         {
467             Method superclazzMethod = getPublicMethod(superclazz, name, paramTypes);
468             
469             if(superclazzMethod != null)
470             {
471                 return superclazzMethod;
472             }
473         }
474 
475         /*
476          *  and interfaces
477          */
478 
479         Class[] interfaces = clazz.getInterfaces();
480 
481         for(int i = 0; i < interfaces.length; ++i)
482         {
483             Method interfaceMethod = getPublicMethod(interfaces[i], name, paramTypes);
484             
485             if(interfaceMethod != null)
486             {
487                 return interfaceMethod;
488             }
489         }
490 
491         return null;
492     }
493 
494     /***
495      *  Used for the iterative discovery process for public methods.
496      */
497     private static final class MethodInfo
498     {
499         Method method;
500         String name;
501         Class[] parameterTypes;
502         boolean upcast;
503         
504         MethodInfo(Method method)
505         {
506             this.method = null;
507             name = method.getName();
508             parameterTypes = method.getParameterTypes();
509             upcast = false;
510         }
511         
512         void tryUpcasting(Class clazz)
513             throws NoSuchMethodException
514         {
515             method = clazz.getMethod(name, parameterTypes);
516             name = null;
517             parameterTypes = null;
518             upcast = true;
519         }
520     }
521 }