• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 org.slf4j.Logger;
23 
24 import java.lang.reflect.Method;
25 import java.lang.reflect.Modifier;
26 import java.util.HashMap;
27 import java.util.Map;
28 import java.util.concurrent.ConcurrentHashMap;
29 
30 /**
31  * A cache of introspection information for a specific class instance.
32  * Keys {@link java.lang.reflect.Method} objects by a concatenation of the
33  * method name and the names of classes that make up the parameters.
34  *
35  * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
36  * @author <a href="mailto:bob@werken.com">Bob McWhirter</a>
37  * @author <a href="mailto:szegedia@freemail.hu">Attila Szegedi</a>
38  * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
39  * @author <a href="mailto:henning@apache.org">Henning P. Schmiedehausen</a>
40  * @author Nathan Bubna
41  * @author <a href="mailto:claude.brisson@gmail.com">Claude Brisson</a>
42  * @version $Id$
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 Logger 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      * @param log logger
64      */
ClassMap(final Class<?> clazz, final Logger log)65     public ClassMap(final Class<?> clazz, final Logger log)
66     {
67         this(clazz, log, null);
68     }
69 
70     /**
71      * Standard constructor
72      * @param clazz The class for which this ClassMap gets constructed.
73      * @param log logger
74      * @param conversionHandler conversion handler
75      * @since 2.0
76      */
ClassMap(final Class<?> clazz, final Logger log, final TypeConversionHandler conversionHandler)77     public ClassMap(final Class<?> clazz, final Logger log, final TypeConversionHandler conversionHandler)
78     {
79         this.clazz = clazz;
80         this.log = log;
81 
82         if (debugReflection)
83         {
84             log.debug("=================================================================");
85             log.debug("== Class: {}", clazz);
86         }
87 
88         methodCache = createMethodCache(conversionHandler);
89 
90         if (debugReflection)
91         {
92             log.debug("=================================================================");
93         }
94     }
95 
96     /**
97      * Returns the class object whose methods are cached by this map.
98      *
99      * @return The class object whose methods are cached by this map.
100      */
getCachedClass()101     public Class<?> getCachedClass()
102     {
103         return clazz;
104     }
105 
106     /**
107      * Find a Method using the method name and parameter objects.
108      *
109      * @param name The method name to look up.
110      * @param params An array of parameters for the method.
111      * @return A Method object representing the method to invoke or null.
112      * @throws MethodMap.AmbiguousException When more than one method is a match for the parameters.
113      */
findMethod(final String name, final Object[] params)114     public Method findMethod(final String name, final Object[] params)
115             throws MethodMap.AmbiguousException
116     {
117         return methodCache.get(name, params);
118     }
119 
120     /**
121      * Populate the Map of direct hits. These
122      * are taken from all the public methods
123      * that our class, its parents and their implemented interfaces provide.
124      */
createMethodCache(TypeConversionHandler conversionHandler)125     private MethodCache createMethodCache(TypeConversionHandler conversionHandler)
126     {
127         MethodCache methodCache = new MethodCache(log, conversionHandler);
128 	//
129 	// Looks through all elements in the class hierarchy. This one is bottom-first (i.e. we start
130 	// with the actual declaring class and its interfaces and then move up (superclass etc.) until we
131 	// hit java.lang.Object. That is important because it will give us the methods of the declaring class
132 	// which might in turn be abstract further up the tree.
133 	//
134 	// We also ignore all SecurityExceptions that might happen due to SecurityManager restrictions (prominently
135 	// hit with Tomcat 5.5).
136 	//
137 	// We can also omit all that complicated getPublic, getAccessible and upcast logic that the class map had up
138 	// until Velocity 1.4. As we always reflect all elements of the tree (that's what we have a cache for), we will
139 	// hit the public elements sooner or later because we reflect all the public elements anyway.
140 	//
141         // Ah, the miracles of Java for(;;) ...
142         for (Class<?> classToReflect = getCachedClass(); classToReflect != null ; classToReflect = classToReflect.getSuperclass())
143         {
144             if (Modifier.isPublic(classToReflect.getModifiers()))
145             {
146                 populateMethodCacheWith(methodCache, classToReflect);
147             }
148             Class<?> [] interfaces = classToReflect.getInterfaces();
149             for (Class<?> anInterface : interfaces)
150             {
151                 populateMethodCacheWithInterface(methodCache, anInterface);
152             }
153         }
154         // return the already initialized cache
155         return methodCache;
156     }
157 
158     /* recurses up interface heirarchy to get all super interfaces (VELOCITY-689) */
populateMethodCacheWithInterface(MethodCache methodCache, Class<?> iface)159     private void populateMethodCacheWithInterface(MethodCache methodCache, Class<?> iface)
160     {
161         if (Modifier.isPublic(iface.getModifiers()))
162         {
163             populateMethodCacheWith(methodCache, iface);
164         }
165         Class<?>[] supers = iface.getInterfaces();
166         for (Class<?> aSuper : supers)
167         {
168             populateMethodCacheWithInterface(methodCache, aSuper);
169         }
170     }
171 
populateMethodCacheWith(MethodCache methodCache, Class<?> classToReflect)172     private void populateMethodCacheWith(MethodCache methodCache, Class<?> classToReflect)
173     {
174         if (debugReflection)
175         {
176             log.debug("Reflecting {}", classToReflect);
177         }
178 
179         try
180         {
181             Method[] methods = classToReflect.getDeclaredMethods();
182             for (Method method : methods)
183             {
184                 int modifiers = method.getModifiers();
185                 if (Modifier.isPublic(modifiers))
186                 {
187                     methodCache.put(method);
188                 }
189             }
190         }
191         catch (SecurityException se) // Everybody feels better with...
192         {
193             log.debug("While accessing methods of {}:", classToReflect, se);
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$
202      */
203     private static final class MethodCache
204     {
205         private static final Object CACHE_MISS = new Object();
206 
207         private static final String NULL_ARG = Object.class.getName();
208 
209         private static final Map<Class<?>, String> convertPrimitives = new HashMap();
210 
211         static
212         {
convertPrimitives.put(Boolean.TYPE, Boolean.class.getName())213             convertPrimitives.put(Boolean.TYPE,   Boolean.class.getName());
convertPrimitives.put(Byte.TYPE, Byte.class.getName())214             convertPrimitives.put(Byte.TYPE,      Byte.class.getName());
convertPrimitives.put(Character.TYPE, Character.class.getName())215             convertPrimitives.put(Character.TYPE, Character.class.getName());
convertPrimitives.put(Double.TYPE, Double.class.getName())216             convertPrimitives.put(Double.TYPE,    Double.class.getName());
convertPrimitives.put(Float.TYPE, Float.class.getName())217             convertPrimitives.put(Float.TYPE,     Float.class.getName());
convertPrimitives.put(Integer.TYPE, Integer.class.getName())218             convertPrimitives.put(Integer.TYPE,   Integer.class.getName());
convertPrimitives.put(Long.TYPE, Long.class.getName())219             convertPrimitives.put(Long.TYPE,      Long.class.getName());
convertPrimitives.put(Short.TYPE, Short.class.getName())220             convertPrimitives.put(Short.TYPE,     Short.class.getName());
221         }
222 
223     	/** Class logger */
224 	    private final Logger log;
225 
226         /**
227          * Cache of Methods, or CACHE_MISS, keyed by method
228          * name and actual arguments used to find it.
229          */
230         private final Map<Object, Object> cache = new ConcurrentHashMap<>();
231 
232         /** Map of methods that are searchable according to method parameters to find a match */
233         private final MethodMap methodMap;
234 
MethodCache(Logger log, TypeConversionHandler conversionHandler)235         private MethodCache(Logger log, TypeConversionHandler conversionHandler)
236         {
237             this.log = log;
238             methodMap = new MethodMap(conversionHandler);
239         }
240 
241         /**
242          * Find a Method using the method name and parameter objects.
243          *
244          * Look in the methodMap for an entry.  If found,
245          * it'll either be a CACHE_MISS, in which case we
246          * simply give up, or it'll be a Method, in which
247          * case, we return it.
248          *
249          * If nothing is found, then we must actually go
250          * and introspect the method from the MethodMap.
251          *
252          * @param name The method name to look up.
253          * @param params An array of parameters for the method.
254          * @return A Method object representing the method to invoke or null.
255          * @throws MethodMap.AmbiguousException When more than one method is a match for the parameters.
256          */
get(final String name, final Object [] params)257         public Method get(final String name, final Object [] params)
258                 throws MethodMap.AmbiguousException
259         {
260             String methodKey = makeMethodKey(name, params);
261 
262             Object cacheEntry = cache.get(methodKey);
263             if (cacheEntry == CACHE_MISS)
264             {
265                 // We looked this up before and failed.
266                 return null;
267             }
268 
269             if (cacheEntry == null)
270             {
271                 try
272                 {
273                     // That one is expensive...
274                     cacheEntry = methodMap.find(name, params);
275                 }
276                 catch(MethodMap.AmbiguousException ae)
277                 {
278                     /*
279                      *  that's a miss :-)
280                      */
281                     cache.put(methodKey, CACHE_MISS);
282                     throw ae;
283                 }
284 
285                 cache.put(methodKey,
286                         (cacheEntry != null) ? cacheEntry : CACHE_MISS);
287             }
288 
289             // Yes, this might just be null.
290             return (Method) cacheEntry;
291         }
292 
put(Method method)293         private void put(Method method)
294         {
295             String methodKey = makeMethodKey(method);
296 
297             // We don't overwrite methods because we fill the
298             // cache from defined class towards java.lang.Object
299             // and that would cause overridden methods to appear
300             // as if they were not overridden.
301             if (cache.get(methodKey) == null)
302             {
303                 cache.put(methodKey, method);
304                 methodMap.add(method);
305                 if (debugReflection)
306                 {
307                     log.debug("Adding {}", method);
308                 }
309             }
310         }
311 
312         /**
313          * Make a methodKey for the given method using
314          * the concatenation of the name and the
315          * types of the method parameters.
316          *
317          * @param method to be stored as key
318          * @return key for ClassMap
319          */
makeMethodKey(final Method method)320         private String makeMethodKey(final Method method)
321         {
322             Class<?>[] parameterTypes = method.getParameterTypes();
323             int args = parameterTypes.length;
324             if (args == 0)
325             {
326                 return method.getName();
327             }
328 
329             StringBuilder methodKey = new StringBuilder((args+1)*16).append(method.getName());
330 
331             for (Class<?> parameterType : parameterTypes)
332             {
333                 /*
334                  * If the argument type is primitive then we want
335                  * to convert our primitive type signature to the
336                  * corresponding Object type so introspection for
337                  * methods with primitive types will work correctly.
338                  *
339                  * The lookup map (convertPrimitives) contains all eight
340                  * primitives (boolean, byte, char, double, float, int, long, short)
341                  * known to Java. So it should never return null for the key passed in.
342                  */
343                 if (parameterType.isPrimitive())
344                 {
345                     methodKey.append((String) convertPrimitives.get(parameterType));
346                 } else
347                 {
348                     methodKey.append(parameterType.getName());
349                 }
350             }
351 
352             return methodKey.toString();
353         }
354 
makeMethodKey(String method, Object[] params)355         private String makeMethodKey(String method, Object[] params)
356         {
357             int args = params.length;
358             if (args == 0)
359             {
360                 return method;
361             }
362 
363             StringBuilder methodKey = new StringBuilder((args+1)*16).append(method);
364 
365             for (Object arg : params)
366             {
367                 if (arg == null)
368                 {
369                     methodKey.append(NULL_ARG);
370                 }
371                 else
372                 {
373                     methodKey.append(arg.getClass().getName());
374                 }
375             }
376 
377             return methodKey.toString();
378         }
379     }
380 }
381