%line | %branch | |||||||||
---|---|---|---|---|---|---|---|---|---|---|
org.apache.commons.jexl.util.introspection.ClassMap$MethodInfo |
|
|
1 | /* |
|
2 | * Copyright 2001-2002,2004 The Apache Software Foundation. |
|
3 | * |
|
4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
|
5 | * you may not use this file except in compliance with the License. |
|
6 | * You may obtain a copy of the License at |
|
7 | * |
|
8 | * http://www.apache.org/licenses/LICENSE-2.0 |
|
9 | * |
|
10 | * Unless required by applicable law or agreed to in writing, software |
|
11 | * distributed under the License is distributed on an "AS IS" BASIS, |
|
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
13 | * See the License for the specific language governing permissions and |
|
14 | * limitations under the License. |
|
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 | * that's a miss :) |
|
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 | // Yes, this might just be null. |
|
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 | * get all publicly accessible methods |
|
148 | */ |
|
149 | ||
150 | Method[] methods = getAccessibleMethods(clazz); |
|
151 | ||
152 | /* |
|
153 | * map and cache them |
|
154 | */ |
|
155 | ||
156 | for (int i = 0; i < methods.length; i++) |
|
157 | { |
|
158 | Method method = methods[i]; |
|
159 | ||
160 | /* |
|
161 | * now get the 'public method', the method declared by a |
|
162 | * public interface or class. (because the actual implementing |
|
163 | * class may be a facade... |
|
164 | */ |
|
165 | ||
166 | Method publicMethod = getPublicMethod( method ); |
|
167 | ||
168 | /* |
|
169 | * it is entirely possible that there is no public method for |
|
170 | * the methods of this class (i.e. in the facade, a method |
|
171 | * that isn't on any of the interfaces or superclass |
|
172 | * in which case, ignore it. Otherwise, map and cache |
|
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 | * If the argument type is primitive then we want |
|
198 | * to convert our primitive type signature to the |
|
199 | * corresponding Object type so introspection for |
|
200 | * methods with primitive types will work correctly. |
|
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 | * Short circuit for the (hopefully) majority of cases where the |
|
261 | * clazz is public |
|
262 | */ |
|
263 | ||
264 | if (Modclass="keyword">ifier.isPublic(clazz.getModclass="keyword">ifiers())) |
|
265 | { |
|
266 | return methods; |
|
267 | } |
|
268 | ||
269 | /* |
|
270 | * No luck - the class is not public, so we're going the longer way. |
|
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 | * Reallocate array in case some method had no accessible counterpart. |
|
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, class="keyword">int upcastCount) |
|
313 | { |
|
314 | int l = methodInfos.length; |
|
315 | ||
316 | /* |
|
317 | * if this class is public, then check each of the currently |
|
318 | * 'non-upcasted' methods to see if we have a match |
|
319 | */ |
|
320 | ||
321 | if( Modclass="keyword">ifier.isPublic(clazz.getModclass="keyword">ifiers()) ) |
|
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 | * Intentionally ignored - it means |
|
339 | * it wasn't found in the current class |
|
340 | */ |
|
341 | } |
|
342 | } |
|
343 | ||
344 | /* |
|
345 | * Short circuit if all methods were upcast |
|
346 | */ |
|
347 | ||
348 | if(upcastCount == l) |
|
349 | { |
|
350 | return upcastCount; |
|
351 | } |
|
352 | } |
|
353 | ||
354 | /* |
|
355 | * Examine superclass |
|
356 | */ |
|
357 | ||
358 | Class superclazz = clazz.getSuperclass(); |
|
359 | ||
360 | if(superclazz != null) |
|
361 | { |
|
362 | upcastCount = getAccessibleMethods(superclazz , methodInfos, upcastCount); |
|
363 | ||
364 | /* |
|
365 | * Short circuit if all methods were upcast |
|
366 | */ |
|
367 | ||
368 | if(upcastCount == l) |
|
369 | { |
|
370 | return upcastCount; |
|
371 | } |
|
372 | } |
|
373 | ||
374 | /* |
|
375 | * Examine interfaces. Note we do it even if superclazz == null. |
|
376 | * This is redundant as currently java.lang.Object does not implement |
|
377 | * any interfaces, however nothing guarantees it will not in future. |
|
378 | */ |
|
379 | ||
380 | Class[] interfaces = clazz.getInterfaces(); |
|
381 | ||
382 | for(int i = class="keyword">interfaces.length; i-- > 0; ) |
|
383 | { |
|
384 | upcastCount = getAccessibleMethods(interfaces[i], methodInfos, upcastCount); |
|
385 | ||
386 | /* |
|
387 | * Short circuit if all methods were upcast |
|
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 | * Short circuit for (hopefully the majority of) cases where the declaring |
|
416 | * class is public. |
|
417 | */ |
|
418 | ||
419 | if((clazz.getModclass="keyword">ifiers() & Modclass="keyword">ifier.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 | * if this class is public, then try to get it |
|
439 | */ |
|
440 | ||
441 | if((clazz.getModclass="keyword">ifiers() & Modclass="keyword">ifier.PUBLIC) != 0) |
|
442 | { |
|
443 | try |
|
444 | { |
|
445 | return clazz.getMethod(name, paramTypes); |
|
446 | } |
|
447 | catch(NoSuchMethodException e) |
|
448 | { |
|
449 | /* |
|
450 | * If the class does not have the method, then neither its |
|
451 | * superclass nor any of its interfaces has it so quickly return |
|
452 | * null. |
|
453 | */ |
|
454 | return null; |
|
455 | } |
|
456 | } |
|
457 | ||
458 | /* |
|
459 | * try the superclass |
|
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 | * and interfaces |
|
477 | */ |
|
478 | ||
479 | Class[] interfaces = clazz.getInterfaces(); |
|
480 | ||
481 | for(int i = 0; i < class="keyword">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 | 0 | { |
506 | 0 | this.method = null; |
507 | 0 | name = method.getName(); |
508 | 0 | parameterTypes = method.getParameterTypes(); |
509 | 0 | upcast = false; |
510 | 0 | } |
511 | ||
512 | void tryUpcasting(Class clazz) |
|
513 | throws NoSuchMethodException |
|
514 | { |
|
515 | 0 | method = clazz.getMethod(name, parameterTypes); |
516 | 0 | name = null; |
517 | 0 | parameterTypes = null; |
518 | 0 | upcast = true; |
519 | 0 | } |
520 | } |
|
521 | } |
This report is generated by jcoverage, Maven and Maven JCoverage Plugin. |