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 }