%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. 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 | * that's a miss :) |
|
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 | // Yes, this might just be null. |
|
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 | * get all publicly accessible methods |
|
137 | */ |
|
138 | ||
139 | Method[] methods = getAccessibleMethods(clazz); |
|
140 | ||
141 | /* |
|
142 | * map and cache them |
|
143 | */ |
|
144 | ||
145 | for (int i = 0; i < methods.length; i++) { |
|
146 | Method method = methods[i]; |
|
147 | ||
148 | /* |
|
149 | * now get the 'public method', the method declared by a public |
|
150 | * interface or class. (because the actual implementing class may be |
|
151 | * a facade... |
|
152 | */ |
|
153 | ||
154 | Method publicMethod = getPublicMethod(method); |
|
155 | ||
156 | /* |
|
157 | * it is entirely possible that there is no public method for the |
|
158 | * methods of this class (i.e. in the facade, a method that isn't on |
|
159 | * any of the interfaces or superclass in which case, ignore it. |
|
160 | * Otherwise, map and cache |
|
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 | * If the argument type is primitive then we want to convert our |
|
182 | * primitive type signature to the corresponding Object type so |
|
183 | * introspection for methods with primitive types will work |
|
184 | * correctly. |
|
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 | * Short circuit for the (hopefully) majority of cases where the clazz |
|
238 | * is public |
|
239 | */ |
|
240 | ||
241 | if (Modclass="keyword">ifier.isPublic(clazz.getModclass="keyword">ifiers())) { |
|
242 | return methods; |
|
243 | } |
|
244 | ||
245 | /* |
|
246 | * No luck - the class is not public, so we're going the longer way. |
|
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 | * Reallocate array in case some method had no accessible counterpart. |
|
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, class="keyword">int upcastCount) { |
|
285 | int l = methodInfos.length; |
|
286 | ||
287 | /* |
|
288 | * if this class is public, then check each of the currently |
|
289 | * 'non-upcasted' methods to see if we have a match |
|
290 | */ |
|
291 | ||
292 | if (Modclass="keyword">ifier.isPublic(clazz.getModclass="keyword">ifiers())) { |
|
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 | * Intentionally ignored - it means it wasn't found in the |
|
304 | * current class |
|
305 | */ |
|
306 | } |
|
307 | } |
|
308 | ||
309 | /* |
|
310 | * Short circuit if all methods were upcast |
|
311 | */ |
|
312 | ||
313 | if (upcastCount == l) { |
|
314 | return upcastCount; |
|
315 | } |
|
316 | } |
|
317 | ||
318 | /* |
|
319 | * Examine superclass |
|
320 | */ |
|
321 | ||
322 | Class superclazz = clazz.getSuperclass(); |
|
323 | ||
324 | if (superclazz != null) { |
|
325 | upcastCount = getAccessibleMethods(superclazz, methodInfos, upcastCount); |
|
326 | ||
327 | /* |
|
328 | * Short circuit if all methods were upcast |
|
329 | */ |
|
330 | ||
331 | if (upcastCount == l) { |
|
332 | return upcastCount; |
|
333 | } |
|
334 | } |
|
335 | ||
336 | /* |
|
337 | * Examine interfaces. Note we do it even if superclazz == null. This is |
|
338 | * redundant as currently java.lang.Object does not implement any |
|
339 | * interfaces, however nothing guarantees it will not in future. |
|
340 | */ |
|
341 | ||
342 | Class[] interfaces = clazz.getInterfaces(); |
|
343 | ||
344 | for (int i = class="keyword">interfaces.length; i-- > 0;) { |
|
345 | upcastCount = getAccessibleMethods(interfaces[i], methodInfos, upcastCount); |
|
346 | ||
347 | /* |
|
348 | * Short circuit if all methods were upcast |
|
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 | * Short circuit for (hopefully the majority of) cases where the |
|
375 | * declaring class is public. |
|
376 | */ |
|
377 | ||
378 | if ((clazz.getModclass="keyword">ifiers() & Modclass="keyword">ifier.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 | * if this class is public, then try to get it |
|
396 | */ |
|
397 | ||
398 | if ((clazz.getModclass="keyword">ifiers() & Modclass="keyword">ifier.PUBLIC) != 0) { |
|
399 | try { |
|
400 | return clazz.getMethod(name, paramTypes); |
|
401 | } catch (NoSuchMethodException e) { |
|
402 | /* |
|
403 | * If the class does not have the method, then neither its |
|
404 | * superclass nor any of its interfaces has it so quickly return |
|
405 | * null. |
|
406 | */ |
|
407 | return null; |
|
408 | } |
|
409 | } |
|
410 | ||
411 | /* |
|
412 | * try the superclass |
|
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 | * and interfaces |
|
427 | */ |
|
428 | ||
429 | Class[] interfaces = clazz.getInterfaces(); |
|
430 | ||
431 | for (int i = 0; i < class="keyword">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 | 0 | MethodInfo(Method method) { |
455 | 0 | this.method = null; |
456 | 0 | name = method.getName(); |
457 | 0 | parameterTypes = method.getParameterTypes(); |
458 | 0 | upcast = false; |
459 | 0 | } |
460 | ||
461 | void tryUpcasting(Class clazz) throws NoSuchMethodException { |
|
462 | 0 | method = clazz.getMethod(name, parameterTypes); |
463 | 0 | name = null; |
464 | 0 | parameterTypes = null; |
465 | 0 | upcast = true; |
466 | 0 | } |
467 | } |
|
468 | } |
This report is generated by jcoverage, Maven and Maven JCoverage Plugin. |