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