1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.commons.jexl.util.introspection;
18
19 import java.lang.reflect.Method;
20 import java.lang.reflect.Modifier;
21 import java.util.Hashtable;
22 import java.util.Map;
23
24 /***
25 * Taken from the Velocity tree so we can be self-sufficient
26 *
27 * A cache of introspection information for a specific class instance. Keys
28 * {@link java.lang.Method} objects by a concatenation of the method name and
29 * the names of classes that make up the parameters.
30 *
31 * @since 1.0
32 * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
33 * @author <a href="mailto:bob@werken.com">Bob McWhirter</a>
34 * @author <a href="mailto:szegedia@freemail.hu">Attila Szegedi</a>
35 * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
36 * @version $Id: ClassMap.java 398459 2006-04-30 23:14:30Z dion $
37 */
38 public class ClassMap {
39 /*** represents a miss on the cached data. */
40 private static final class CacheMiss {
41 }
42
43 /*** constant for a miss on the cached data. */
44 private static final CacheMiss CACHE_MISS = new CacheMiss();
45
46 /*** represents null or missing arguments. */
47 private static final Object OBJECT = new Object();
48
49 /***
50 * Class passed into the constructor used to as the basis for the Method
51 * map.
52 */
53
54 private Class clazz;
55
56 /***
57 * Cache of Methods, or CACHE_MISS, keyed by method name and actual
58 * arguments used to find it.
59 */
60 private final Map methodCache = new Hashtable();
61
62 /*** map from method name and args to a {@link Method}. */
63 private final MethodMap methodMap = new MethodMap();
64
65 /***
66 * Standard constructor.
67 * @param aClass the class to deconstruct.
68 */
69 public ClassMap(Class aClass) {
70 clazz = aClass;
71 populateMethodCache();
72 }
73
74 /***
75 * @return the class object whose methods are cached by this map.
76 */
77 Class getCachedClass() {
78 return clazz;
79 }
80
81 /***
82 * Find a Method using the methodKey provided.
83 *
84 * Look in the methodMap for an entry. If found, it'll either be a
85 * CACHE_MISS, in which case we simply give up, or it'll be a Method, in
86 * which case, we return it.
87 *
88 * If nothing is found, then we must actually go and introspect the method
89 * from the MethodMap.
90 *
91 * @param name method name
92 * @param params method parameters
93 * @return CACHE_MISS or a {@link Method}
94 * @throws MethodMap.AmbiguousException if the method and parameters are ambiguous.
95 */
96 public Method findMethod(String name, Object[] params) throws MethodMap.AmbiguousException {
97 String methodKey = makeMethodKey(name, params);
98 Object cacheEntry = methodCache.get(methodKey);
99
100 if (cacheEntry == CACHE_MISS) {
101 return null;
102 }
103
104 if (cacheEntry == null) {
105 try {
106 cacheEntry = methodMap.find(name, params);
107 } catch (MethodMap.AmbiguousException ae) {
108
109
110
111
112 methodCache.put(methodKey, CACHE_MISS);
113
114 throw ae;
115 }
116
117 if (cacheEntry == null) {
118 methodCache.put(methodKey, CACHE_MISS);
119 } else {
120 methodCache.put(methodKey, cacheEntry);
121 }
122 }
123
124
125
126 return (Method) cacheEntry;
127 }
128
129 /***
130 * Populate the Map of direct hits. These are taken from all the public
131 * methods that our class provides.
132 */
133 private void populateMethodCache() {
134
135
136
137
138
139 Method[] methods = getAccessibleMethods(clazz);
140
141
142
143
144
145 for (int i = 0; i < methods.length; i++) {
146 Method method = methods[i];
147
148
149
150
151
152
153
154 Method publicMethod = getPublicMethod(method);
155
156
157
158
159
160
161
162
163 if (publicMethod != null) {
164 methodMap.add(publicMethod);
165 methodCache.put(makeMethodKey(publicMethod), publicMethod);
166 }
167 }
168 }
169
170 /***
171 * Make a methodKey for the given method using the concatenation of the name
172 * and the types of the method parameters.
173 */
174 private String makeMethodKey(Method method) {
175 Class[] parameterTypes = method.getParameterTypes();
176
177 StringBuffer methodKey = new StringBuffer(method.getName());
178
179 for (int j = 0; j < parameterTypes.length; j++) {
180
181
182
183
184
185
186 if (parameterTypes[j].isPrimitive()) {
187 if (parameterTypes[j].equals(Boolean.TYPE))
188 methodKey.append("java.lang.Boolean");
189 else if (parameterTypes[j].equals(Byte.TYPE))
190 methodKey.append("java.lang.Byte");
191 else if (parameterTypes[j].equals(Character.TYPE))
192 methodKey.append("java.lang.Character");
193 else if (parameterTypes[j].equals(Double.TYPE))
194 methodKey.append("java.lang.Double");
195 else if (parameterTypes[j].equals(Float.TYPE))
196 methodKey.append("java.lang.Float");
197 else if (parameterTypes[j].equals(Integer.TYPE))
198 methodKey.append("java.lang.Integer");
199 else if (parameterTypes[j].equals(Long.TYPE))
200 methodKey.append("java.lang.Long");
201 else if (parameterTypes[j].equals(Short.TYPE))
202 methodKey.append("java.lang.Short");
203 } else {
204 methodKey.append(parameterTypes[j].getName());
205 }
206 }
207
208 return methodKey.toString();
209 }
210
211 private static String makeMethodKey(String method, Object[] params) {
212 StringBuffer methodKey = new StringBuffer().append(method);
213
214 for (int j = 0; j < params.length; j++) {
215 Object arg = params[j];
216
217 if (arg == null) {
218 arg = OBJECT;
219 }
220
221 methodKey.append(arg.getClass().getName());
222 }
223
224 return methodKey.toString();
225 }
226
227 /***
228 * Retrieves public methods for a class. In case the class is not public,
229 * retrieves methods with same signature as its public methods from public
230 * superclasses and interfaces (if they exist). Basically upcasts every
231 * method to the nearest acccessible method.
232 */
233 private static Method[] getAccessibleMethods(Class clazz) {
234 Method[] methods = clazz.getMethods();
235
236
237
238
239
240
241 if (Modifier.isPublic(clazz.getModifiers())) {
242 return methods;
243 }
244
245
246
247
248
249 MethodInfo[] methodInfos = new MethodInfo[methods.length];
250
251 for (int i = methods.length; i-- > 0;) {
252 methodInfos[i] = new MethodInfo(methods[i]);
253 }
254
255 int upcastCount = getAccessibleMethods(clazz, methodInfos, 0);
256
257
258
259
260
261 if (upcastCount < methods.length) {
262 methods = new Method[upcastCount];
263 }
264
265 int j = 0;
266 for (int i = 0; i < methodInfos.length; ++i) {
267 MethodInfo methodInfo = methodInfos[i];
268 if (methodInfo.upcast) {
269 methods[j++] = methodInfo.method;
270 }
271 }
272 return methods;
273 }
274
275 /***
276 * Recursively finds a match for each method, starting with the class, and
277 * then searching the superclass and interfaces.
278 *
279 * @param clazz Class to check
280 * @param methodInfos array of methods we are searching to match
281 * @param upcastCount current number of methods we have matched
282 * @return count of matched methods
283 */
284 private static int getAccessibleMethods(Class clazz, MethodInfo[] methodInfos, int upcastCount) {
285 int l = methodInfos.length;
286
287
288
289
290
291
292 if (Modifier.isPublic(clazz.getModifiers())) {
293 for (int i = 0; i < l && upcastCount < l; ++i) {
294 try {
295 MethodInfo methodInfo = methodInfos[i];
296
297 if (!methodInfo.upcast) {
298 methodInfo.tryUpcasting(clazz);
299 upcastCount++;
300 }
301 } catch (NoSuchMethodException e) {
302
303
304
305
306 }
307 }
308
309
310
311
312
313 if (upcastCount == l) {
314 return upcastCount;
315 }
316 }
317
318
319
320
321
322 Class superclazz = clazz.getSuperclass();
323
324 if (superclazz != null) {
325 upcastCount = getAccessibleMethods(superclazz, methodInfos, upcastCount);
326
327
328
329
330
331 if (upcastCount == l) {
332 return upcastCount;
333 }
334 }
335
336
337
338
339
340
341
342 Class[] interfaces = clazz.getInterfaces();
343
344 for (int i = interfaces.length; i-- > 0;) {
345 upcastCount = getAccessibleMethods(interfaces[i], methodInfos, upcastCount);
346
347
348
349
350
351 if (upcastCount == l) {
352 return upcastCount;
353 }
354 }
355
356 return upcastCount;
357 }
358
359 /***
360 * For a given method, retrieves its publicly accessible counterpart. This
361 * method will look for a method with same name and signature declared in a
362 * public superclass or implemented interface of this method's declaring
363 * class. This counterpart method is publicly callable.
364 *
365 * @param method a method whose publicly callable counterpart is requested.
366 * @return the publicly callable counterpart method. Note that if the
367 * parameter method is itself declared by a public class, this
368 * method is an identity function.
369 */
370 public static Method getPublicMethod(Method method) {
371 Class clazz = method.getDeclaringClass();
372
373
374
375
376
377
378 if ((clazz.getModifiers() & Modifier.PUBLIC) != 0) {
379 return method;
380 }
381
382 return getPublicMethod(clazz, method.getName(), method.getParameterTypes());
383 }
384
385 /***
386 * Looks up the method with specified name and signature in the first public
387 * superclass or implemented interface of the class.
388 *
389 * @param class the class whose method is sought
390 * @param name the name of the method
391 * @param paramTypes the classes of method parameters
392 */
393 private static Method getPublicMethod(Class clazz, String name, Class[] paramTypes) {
394
395
396
397
398 if ((clazz.getModifiers() & Modifier.PUBLIC) != 0) {
399 try {
400 return clazz.getMethod(name, paramTypes);
401 } catch (NoSuchMethodException e) {
402
403
404
405
406
407 return null;
408 }
409 }
410
411
412
413
414
415 Class superclazz = clazz.getSuperclass();
416
417 if (superclazz != null) {
418 Method superclazzMethod = getPublicMethod(superclazz, name, paramTypes);
419
420 if (superclazzMethod != null) {
421 return superclazzMethod;
422 }
423 }
424
425
426
427
428
429 Class[] interfaces = clazz.getInterfaces();
430
431 for (int i = 0; i < interfaces.length; ++i) {
432 Method interfaceMethod = getPublicMethod(interfaces[i], name, paramTypes);
433
434 if (interfaceMethod != null) {
435 return interfaceMethod;
436 }
437 }
438
439 return null;
440 }
441
442 /***
443 * Used for the iterative discovery process for public methods.
444 */
445 private static final class MethodInfo {
446 Method method;
447
448 String name;
449
450 Class[] parameterTypes;
451
452 boolean upcast;
453
454 MethodInfo(Method method) {
455 this.method = null;
456 name = method.getName();
457 parameterTypes = method.getParameterTypes();
458 upcast = false;
459 }
460
461 void tryUpcasting(Class clazz) throws NoSuchMethodException {
462 method = clazz.getMethod(name, parameterTypes);
463 name = null;
464 parameterTypes = null;
465 upcast = true;
466 }
467 }
468 }