View Javadoc

1   package org.apache.velocity.util.introspection;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.    
20   */
21  
22  import java.lang.reflect.Method;
23  import java.lang.reflect.Modifier;
24  import java.util.ArrayList;
25  import java.util.HashMap;
26  import java.util.Iterator;
27  import java.util.List;
28  import java.util.Map;
29  
30  import org.apache.velocity.runtime.log.Log;
31  
32  /**
33   * A cache of introspection information for a specific class instance.
34   * Keys {@link java.lang.reflect.Method} objects by a concatenation of the
35   * method name and the names of classes that make up the parameters.
36   *
37   * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
38   * @author <a href="mailto:bob@werken.com">Bob McWhirter</a>
39   * @author <a href="mailto:szegedia@freemail.hu">Attila Szegedi</a>
40   * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
41   * @author <a href="mailto:henning@apache.org">Henning P. Schmiedehausen</a>
42   * @version $Id: ClassMap.java 477003 2006-11-20 01:14:22Z henning $
43   */
44  public class ClassMap
45  {
46      /** Set true if you want to debug the reflection code */
47      private static final boolean debugReflection = false;
48  
49      /** Class logger */
50      private final Log log;
51      
52      /**
53       * Class passed into the constructor used to as
54       * the basis for the Method map.
55       */
56      private final Class clazz;
57  
58      private final MethodCache methodCache;
59  
60      /**
61       * Standard constructor
62       * @param clazz The class for which this ClassMap gets constructed.
63       */
64      public ClassMap(final Class clazz, final Log log)
65      {
66          this.clazz = clazz;
67          this.log = log;
68          
69          if (debugReflection && log.isDebugEnabled())
70          {
71              log.debug("=================================================================");
72              log.debug("== Class: " + clazz);
73          }
74          
75          methodCache = new MethodCache(log);
76          
77          populateMethodCache();
78  
79          if (debugReflection && log.isDebugEnabled())
80          {
81              log.debug("=================================================================");
82          }
83      }
84  
85      /**
86       * Returns the class object whose methods are cached by this map.
87       *
88       * @return The class object whose methods are cached by this map.
89       */
90      public Class getCachedClass()
91      {
92          return clazz;
93      }
94  
95      /**
96       * Find a Method using the method name and parameter objects.
97       *
98       * @param name The method name to look up.
99       * @param params An array of parameters for the method.
100      * @return A Method object representing the method to invoke or null.
101      * @throws MethodMap.AmbiguousException When more than one method is a match for the parameters.
102      */
103     public Method findMethod(final String name, final Object[] params)
104             throws MethodMap.AmbiguousException
105     {
106         return methodCache.get(name, params);
107     }
108 
109     /**
110      * Populate the Map of direct hits. These
111      * are taken from all the public methods
112      * that our class, its parents and their implemented interfaces provide.
113      */
114     private void populateMethodCache()
115     {
116 	//
117 	// Build a list of all elements in the class hierarchy. This one is bottom-first (i.e. we start
118 	// with the actual declaring class and its interfaces and then move up (superclass etc.) until we
119 	// hit java.lang.Object. That is important because it will give us the methods of the declaring class
120 	// which might in turn be abstract further up the tree.
121 	//
122 	// We also ignore all SecurityExceptions that might happen due to SecurityManager restrictions (prominently 
123 	// hit with Tomcat 5.5).
124 	//
125 	// We can also omit all that complicated getPublic, getAccessible and upcast logic that the class map had up
126 	// until Velocity 1.4. As we always reflect all elements of the tree (that's what we have a cache for), we will
127 	// hit the public elements sooner or later because we reflect all the public elements anyway.
128 	//
129         List classesToReflect = new ArrayList();
130         
131         // Ah, the miracles of Java for(;;) ... 
132         for (Class classToReflect = getCachedClass(); classToReflect != null ; classToReflect = classToReflect.getSuperclass())
133         {
134             if (Modifier.isPublic(classToReflect.getModifiers()))
135             {
136                 classesToReflect.add(classToReflect);
137                 if (debugReflection && log.isDebugEnabled())
138                 {
139                     log.debug("Adding " + classToReflect + " for reflection");
140                 }
141             }
142             Class [] interfaces = classToReflect.getInterfaces();
143             for (int i = 0; i < interfaces.length; i++)
144             {
145                 if (Modifier.isPublic(interfaces[i].getModifiers()))
146                 {
147                     classesToReflect.add(interfaces[i]);
148                     if (debugReflection && log.isDebugEnabled())
149                     {
150                         log.debug("Adding " + interfaces[i] + " for reflection");
151                     }
152                 }
153             }
154         }
155 
156         for (Iterator it = classesToReflect.iterator(); it.hasNext(); )
157         {
158             Class classToReflect = (Class) it.next();
159             if (debugReflection && log.isDebugEnabled())
160             {
161                 log.debug("Reflecting " + classToReflect);
162             }
163             
164 
165             try
166             {
167                 Method[] methods = classToReflect.getMethods();
168 
169                 for (int i = 0; i < methods.length; i++)
170                 {
171                     // Strictly spoken that check shouldn't be necessary
172                     // because getMethods only returns public methods.
173                     int modifiers = methods[i].getModifiers();
174                     if (Modifier.isPublic(modifiers)) //  && !)
175             	    {
176                         // Some of the interfaces contain abstract methods. That is fine, because the actual object must 
177                         // implement them anyway (else it wouldn't be implementing the interface). If we find an abstract
178                         // method in a non-interface, we skip it, because we do want to make sure that no abstract methods end up in
179                         // the cache.                       
180                         if (classToReflect.isInterface() || !Modifier.isAbstract(modifiers))
181                         {
182                             methodCache.put(methods[i]);
183                         }
184                     }
185                 }
186             }
187             catch (SecurityException se) // Everybody feels better with...
188             {
189         	if (log.isDebugEnabled())
190         	{
191         	    log.debug("While accessing methods of " + classToReflect + ": ", se);
192         	}
193             }
194         }
195     }
196 
197     /**
198      * This is the cache to store and look up the method information. 
199      * 
200      * @author <a href="mailto:henning@apache.org">Henning P. Schmiedehausen</a>
201      * @version $Id: ClassMap.java 477003 2006-11-20 01:14:22Z henning $
202      */
203     private static final class MethodCache
204     {
205         private static final class CacheMiss { }
206         
207         private static final CacheMiss CACHE_MISS = new CacheMiss();
208 
209         private static final Object OBJECT = new Object();
210 
211         private static final Map convertPrimitives = new HashMap();
212 
213         static
214         {
215             convertPrimitives.put(Boolean.TYPE,   Boolean.class.getName());
216             convertPrimitives.put(Byte.TYPE,      Byte.class.getName());
217             convertPrimitives.put(Character.TYPE, Character.class.getName());
218             convertPrimitives.put(Double.TYPE,    Double.class.getName());
219             convertPrimitives.put(Float.TYPE,     Float.class.getName());
220             convertPrimitives.put(Integer.TYPE,   Integer.class.getName());
221             convertPrimitives.put(Long.TYPE,      Long.class.getName());
222             convertPrimitives.put(Short.TYPE,     Short.class.getName());
223         }
224 
225     	/** Class logger */
226 	private final Log log;
227 
228         /**
229          * Cache of Methods, or CACHE_MISS, keyed by method
230          * name and actual arguments used to find it.
231          */
232         private final Map cache = new HashMap();
233 
234         /** Map of methods that are searchable according to method parameters to find a match */
235         private final MethodMap methodMap = new MethodMap();
236 
237         private MethodCache(Log log)
238         {
239             this.log = log;
240         }
241 
242         /**
243          * Find a Method using the method name and parameter objects.
244          *
245          * Look in the methodMap for an entry.  If found,
246          * it'll either be a CACHE_MISS, in which case we
247          * simply give up, or it'll be a Method, in which
248          * case, we return it.
249          *
250          * If nothing is found, then we must actually go
251          * and introspect the method from the MethodMap.
252          *
253          * @param name The method name to look up.
254          * @param params An array of parameters for the method.
255          * @return A Method object representing the method to invoke or null.
256          * @throws MethodMap.AmbiguousException When more than one method is a match for the parameters.
257          */
258         public synchronized Method get(final String name, final Object [] params)
259                 throws MethodMap.AmbiguousException
260         {
261             String methodKey = makeMethodKey(name, params);
262 
263             Object cacheEntry = cache.get(methodKey);
264 
265             // We looked this up before and failed. 
266             if (cacheEntry == CACHE_MISS)
267             {
268                 return null;
269             }
270 
271             if (cacheEntry == null)
272             {
273                 try
274                 {
275                     // That one is expensive...
276                     cacheEntry = methodMap.find(name, params);
277                 }
278                 catch(MethodMap.AmbiguousException ae)
279                 {
280                     /*
281                      *  that's a miss :-)
282                      */
283                     cache.put(methodKey, CACHE_MISS);
284                     throw ae;
285                 }
286 
287                 cache.put(methodKey, 
288                         (cacheEntry != null) ? cacheEntry : CACHE_MISS);
289             }
290 
291             // Yes, this might just be null.
292 
293             return (Method) cacheEntry;
294         }
295 
296         public synchronized void put(Method method)
297         {
298             String methodKey = makeMethodKey(method);
299             
300             // We don't overwrite methods. Especially not if we fill the
301             // cache from defined class towards java.lang.Object because 
302             // abstract methods in superclasses would else overwrite concrete
303             // classes further down the hierarchy.
304             if (cache.get(methodKey) == null)
305             {
306         	cache.put(methodKey, method);
307         	methodMap.add(method);
308         	if (debugReflection && log.isDebugEnabled())
309         	{
310         	    log.debug("Adding " + method);
311         	}
312             }
313         }
314 
315         /**
316          * Make a methodKey for the given method using
317          * the concatenation of the name and the
318          * types of the method parameters.
319          * 
320          * @param method to be stored as key
321          * @return key for ClassMap
322          */
323         private String makeMethodKey(final Method method)
324         {
325             Class[] parameterTypes = method.getParameterTypes();
326 
327             StringBuffer methodKey = new StringBuffer(method.getName());
328 
329             for (int j = 0; j < parameterTypes.length; j++)
330             {
331                 /*
332                  * If the argument type is primitive then we want
333                  * to convert our primitive type signature to the
334                  * corresponding Object type so introspection for
335                  * methods with primitive types will work correctly.
336                  *
337                  * The lookup map (convertPrimitives) contains all eight
338                  * primitives (boolean, byte, char, double, float, int, long, short)
339                  * known to Java. So it should never return null for the key passed in.
340                  */
341                 if (parameterTypes[j].isPrimitive())
342                 {
343                     methodKey.append((String) convertPrimitives.get(parameterTypes[j]));
344                 }
345                 else
346                 {
347                     methodKey.append(parameterTypes[j].getName());
348                 }
349             }
350 
351             return methodKey.toString();
352         }
353 
354         private String makeMethodKey(String method, Object[] params)
355         {
356             StringBuffer methodKey = new StringBuffer().append(method);
357 
358             for (int j = 0; j < params.length; j++)
359             {
360                 Object arg = params[j];
361 
362                 if (arg == null)
363                 {
364                     arg = OBJECT;
365                 }
366 
367                 methodKey.append(arg.getClass().getName());
368             }
369 
370             return methodKey.toString();
371         }
372     }
373 }