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