001: package org.apache.velocity.util.introspection;
002:
003: /*
004: * Licensed to the Apache Software Foundation (ASF) under one
005: * or more contributor license agreements. See the NOTICE file
006: * distributed with this work for additional information
007: * regarding copyright ownership. The ASF licenses this file
008: * to you under the Apache License, Version 2.0 (the
009: * "License"); you may not use this file except in compliance
010: * with the License. You may obtain a copy of the License at
011: *
012: * http://www.apache.org/licenses/LICENSE-2.0
013: *
014: * Unless required by applicable law or agreed to in writing,
015: * software distributed under the License is distributed on an
016: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017: * KIND, either express or implied. See the License for the
018: * specific language governing permissions and limitations
019: * under the License.
020: */
021:
022: import java.lang.reflect.Method;
023: import java.lang.reflect.Modifier;
024: import java.util.ArrayList;
025: import java.util.HashMap;
026: import java.util.Iterator;
027: import java.util.List;
028: import java.util.Map;
029:
030: import org.apache.velocity.runtime.log.Log;
031:
032: /**
033: * A cache of introspection information for a specific class instance.
034: * Keys {@link java.lang.reflect.Method} objects by a concatenation of the
035: * method name and the names of classes that make up the parameters.
036: *
037: * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
038: * @author <a href="mailto:bob@werken.com">Bob McWhirter</a>
039: * @author <a href="mailto:szegedia@freemail.hu">Attila Szegedi</a>
040: * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
041: * @author <a href="mailto:henning@apache.org">Henning P. Schmiedehausen</a>
042: * @version $Id: ClassMap.java 477003 2006-11-20 01:14:22Z henning $
043: */
044: public class ClassMap {
045: /** Set true if you want to debug the reflection code */
046: private static final boolean debugReflection = false;
047:
048: /** Class logger */
049: private final Log log;
050:
051: /**
052: * Class passed into the constructor used to as
053: * the basis for the Method map.
054: */
055: private final Class clazz;
056:
057: private final MethodCache methodCache;
058:
059: /**
060: * Standard constructor
061: * @param clazz The class for which this ClassMap gets constructed.
062: */
063: public ClassMap(final Class clazz, final Log log) {
064: this .clazz = clazz;
065: this .log = log;
066:
067: if (debugReflection && log.isDebugEnabled()) {
068: log
069: .debug("=================================================================");
070: log.debug("== Class: " + clazz);
071: }
072:
073: methodCache = new MethodCache(log);
074:
075: populateMethodCache();
076:
077: if (debugReflection && log.isDebugEnabled()) {
078: log
079: .debug("=================================================================");
080: }
081: }
082:
083: /**
084: * Returns the class object whose methods are cached by this map.
085: *
086: * @return The class object whose methods are cached by this map.
087: */
088: public Class getCachedClass() {
089: return clazz;
090: }
091:
092: /**
093: * Find a Method using the method name and parameter objects.
094: *
095: * @param name The method name to look up.
096: * @param params An array of parameters for the method.
097: * @return A Method object representing the method to invoke or null.
098: * @throws MethodMap.AmbiguousException When more than one method is a match for the parameters.
099: */
100: public Method findMethod(final String name, final Object[] params)
101: throws MethodMap.AmbiguousException {
102: return methodCache.get(name, params);
103: }
104:
105: /**
106: * Populate the Map of direct hits. These
107: * are taken from all the public methods
108: * that our class, its parents and their implemented interfaces provide.
109: */
110: private void populateMethodCache() {
111: //
112: // Build a list of all elements in the class hierarchy. This one is bottom-first (i.e. we start
113: // with the actual declaring class and its interfaces and then move up (superclass etc.) until we
114: // hit java.lang.Object. That is important because it will give us the methods of the declaring class
115: // which might in turn be abstract further up the tree.
116: //
117: // We also ignore all SecurityExceptions that might happen due to SecurityManager restrictions (prominently
118: // hit with Tomcat 5.5).
119: //
120: // We can also omit all that complicated getPublic, getAccessible and upcast logic that the class map had up
121: // until Velocity 1.4. As we always reflect all elements of the tree (that's what we have a cache for), we will
122: // hit the public elements sooner or later because we reflect all the public elements anyway.
123: //
124: List classesToReflect = new ArrayList();
125:
126: // Ah, the miracles of Java for(;;) ...
127: for (Class classToReflect = getCachedClass(); classToReflect != null; classToReflect = classToReflect
128: .getSuperclass()) {
129: if (Modifier.isPublic(classToReflect.getModifiers())) {
130: classesToReflect.add(classToReflect);
131: if (debugReflection && log.isDebugEnabled()) {
132: log.debug("Adding " + classToReflect
133: + " for reflection");
134: }
135: }
136: Class[] interfaces = classToReflect.getInterfaces();
137: for (int i = 0; i < interfaces.length; i++) {
138: if (Modifier.isPublic(interfaces[i].getModifiers())) {
139: classesToReflect.add(interfaces[i]);
140: if (debugReflection && log.isDebugEnabled()) {
141: log.debug("Adding " + interfaces[i]
142: + " for reflection");
143: }
144: }
145: }
146: }
147:
148: for (Iterator it = classesToReflect.iterator(); it.hasNext();) {
149: Class classToReflect = (Class) it.next();
150: if (debugReflection && log.isDebugEnabled()) {
151: log.debug("Reflecting " + classToReflect);
152: }
153:
154: try {
155: Method[] methods = classToReflect.getMethods();
156:
157: for (int i = 0; i < methods.length; i++) {
158: // Strictly spoken that check shouldn't be necessary
159: // because getMethods only returns public methods.
160: int modifiers = methods[i].getModifiers();
161: if (Modifier.isPublic(modifiers)) // && !)
162: {
163: // Some of the interfaces contain abstract methods. That is fine, because the actual object must
164: // implement them anyway (else it wouldn't be implementing the interface). If we find an abstract
165: // method in a non-interface, we skip it, because we do want to make sure that no abstract methods end up in
166: // the cache.
167: if (classToReflect.isInterface()
168: || !Modifier.isAbstract(modifiers)) {
169: methodCache.put(methods[i]);
170: }
171: }
172: }
173: } catch (SecurityException se) // Everybody feels better with...
174: {
175: if (log.isDebugEnabled()) {
176: log.debug("While accessing methods of "
177: + classToReflect + ": ", se);
178: }
179: }
180: }
181: }
182:
183: /**
184: * This is the cache to store and look up the method information.
185: *
186: * @author <a href="mailto:henning@apache.org">Henning P. Schmiedehausen</a>
187: * @version $Id: ClassMap.java 477003 2006-11-20 01:14:22Z henning $
188: */
189: private static final class MethodCache {
190: private static final class CacheMiss {
191: }
192:
193: private static final CacheMiss CACHE_MISS = new CacheMiss();
194:
195: private static final Object OBJECT = new Object();
196:
197: private static final Map convertPrimitives = new HashMap();
198:
199: static {
200: convertPrimitives
201: .put(Boolean.TYPE, Boolean.class.getName());
202: convertPrimitives.put(Byte.TYPE, Byte.class.getName());
203: convertPrimitives.put(Character.TYPE, Character.class
204: .getName());
205: convertPrimitives.put(Double.TYPE, Double.class.getName());
206: convertPrimitives.put(Float.TYPE, Float.class.getName());
207: convertPrimitives
208: .put(Integer.TYPE, Integer.class.getName());
209: convertPrimitives.put(Long.TYPE, Long.class.getName());
210: convertPrimitives.put(Short.TYPE, Short.class.getName());
211: }
212:
213: /** Class logger */
214: private final Log log;
215:
216: /**
217: * Cache of Methods, or CACHE_MISS, keyed by method
218: * name and actual arguments used to find it.
219: */
220: private final Map cache = new HashMap();
221:
222: /** Map of methods that are searchable according to method parameters to find a match */
223: private final MethodMap methodMap = new MethodMap();
224:
225: private MethodCache(Log log) {
226: this .log = log;
227: }
228:
229: /**
230: * Find a Method using the method name and parameter objects.
231: *
232: * Look in the methodMap for an entry. If found,
233: * it'll either be a CACHE_MISS, in which case we
234: * simply give up, or it'll be a Method, in which
235: * case, we return it.
236: *
237: * If nothing is found, then we must actually go
238: * and introspect the method from the MethodMap.
239: *
240: * @param name The method name to look up.
241: * @param params An array of parameters for the method.
242: * @return A Method object representing the method to invoke or null.
243: * @throws MethodMap.AmbiguousException When more than one method is a match for the parameters.
244: */
245: public synchronized Method get(final String name,
246: final Object[] params)
247: throws MethodMap.AmbiguousException {
248: String methodKey = makeMethodKey(name, params);
249:
250: Object cacheEntry = cache.get(methodKey);
251:
252: // We looked this up before and failed.
253: if (cacheEntry == CACHE_MISS) {
254: return null;
255: }
256:
257: if (cacheEntry == null) {
258: try {
259: // That one is expensive...
260: cacheEntry = methodMap.find(name, params);
261: } catch (MethodMap.AmbiguousException ae) {
262: /*
263: * that's a miss :-)
264: */
265: cache.put(methodKey, CACHE_MISS);
266: throw ae;
267: }
268:
269: cache.put(methodKey, (cacheEntry != null) ? cacheEntry
270: : CACHE_MISS);
271: }
272:
273: // Yes, this might just be null.
274:
275: return (Method) cacheEntry;
276: }
277:
278: public synchronized void put(Method method) {
279: String methodKey = makeMethodKey(method);
280:
281: // We don't overwrite methods. Especially not if we fill the
282: // cache from defined class towards java.lang.Object because
283: // abstract methods in superclasses would else overwrite concrete
284: // classes further down the hierarchy.
285: if (cache.get(methodKey) == null) {
286: cache.put(methodKey, method);
287: methodMap.add(method);
288: if (debugReflection && log.isDebugEnabled()) {
289: log.debug("Adding " + method);
290: }
291: }
292: }
293:
294: /**
295: * Make a methodKey for the given method using
296: * the concatenation of the name and the
297: * types of the method parameters.
298: *
299: * @param method to be stored as key
300: * @return key for ClassMap
301: */
302: private String makeMethodKey(final Method method) {
303: Class[] parameterTypes = method.getParameterTypes();
304:
305: StringBuffer methodKey = new StringBuffer(method.getName());
306:
307: for (int j = 0; j < parameterTypes.length; j++) {
308: /*
309: * If the argument type is primitive then we want
310: * to convert our primitive type signature to the
311: * corresponding Object type so introspection for
312: * methods with primitive types will work correctly.
313: *
314: * The lookup map (convertPrimitives) contains all eight
315: * primitives (boolean, byte, char, double, float, int, long, short)
316: * known to Java. So it should never return null for the key passed in.
317: */
318: if (parameterTypes[j].isPrimitive()) {
319: methodKey.append((String) convertPrimitives
320: .get(parameterTypes[j]));
321: } else {
322: methodKey.append(parameterTypes[j].getName());
323: }
324: }
325:
326: return methodKey.toString();
327: }
328:
329: private String makeMethodKey(String method, Object[] params) {
330: StringBuffer methodKey = new StringBuffer().append(method);
331:
332: for (int j = 0; j < params.length; j++) {
333: Object arg = params[j];
334:
335: if (arg == null) {
336: arg = OBJECT;
337: }
338:
339: methodKey.append(arg.getClass().getName());
340: }
341:
342: return methodKey.toString();
343: }
344: }
345: }
|