001: /*
002: * Copyright 2001-2002,2004 The Apache Software Foundation.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016:
017: package org.apache.commons.jexl.util.introspection;
018:
019: import java.lang.reflect.Method;
020: import java.lang.reflect.Modifier;
021: import java.util.Hashtable;
022: import java.util.Map;
023:
024: /**
025: * Taken from the Velocity tree so we can be self-sufficient
026: *
027: * A cache of introspection information for a specific class instance. Keys
028: * {@link java.lang.Method} objects by a concatenation of the method name and
029: * the names of classes that make up the parameters.
030: *
031: * @since 1.0
032: * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
033: * @author <a href="mailto:bob@werken.com">Bob McWhirter</a>
034: * @author <a href="mailto:szegedia@freemail.hu">Attila Szegedi</a>
035: * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
036: * @version $Id: ClassMap.java 398459 2006-04-30 23:14:30Z dion $
037: */
038: public class ClassMap {
039: /** represents a miss on the cached data. */
040: private static final class CacheMiss {
041: }
042:
043: /** constant for a miss on the cached data. */
044: private static final CacheMiss CACHE_MISS = new CacheMiss();
045:
046: /** represents null or missing arguments. */
047: private static final Object OBJECT = new Object();
048:
049: /**
050: * Class passed into the constructor used to as the basis for the Method
051: * map.
052: */
053:
054: private Class clazz;
055:
056: /**
057: * Cache of Methods, or CACHE_MISS, keyed by method name and actual
058: * arguments used to find it.
059: */
060: private final Map methodCache = new Hashtable();
061:
062: /** map from method name and args to a {@link Method}. */
063: private final MethodMap methodMap = new MethodMap();
064:
065: /**
066: * Standard constructor.
067: * @param aClass the class to deconstruct.
068: */
069: public ClassMap(Class aClass) {
070: clazz = aClass;
071: populateMethodCache();
072: }
073:
074: /**
075: * @return the class object whose methods are cached by this map.
076: */
077: Class getCachedClass() {
078: return clazz;
079: }
080:
081: /**
082: * Find a Method using the methodKey provided.
083: *
084: * Look in the methodMap for an entry. If found, it'll either be a
085: * CACHE_MISS, in which case we simply give up, or it'll be a Method, in
086: * which case, we return it.
087: *
088: * If nothing is found, then we must actually go and introspect the method
089: * from the MethodMap.
090: *
091: * @param name method name
092: * @param params method parameters
093: * @return CACHE_MISS or a {@link Method}
094: * @throws MethodMap.AmbiguousException if the method and parameters are ambiguous.
095: */
096: public Method findMethod(String name, Object[] params)
097: throws MethodMap.AmbiguousException {
098: String methodKey = makeMethodKey(name, params);
099: Object cacheEntry = methodCache.get(methodKey);
100:
101: if (cacheEntry == CACHE_MISS) {
102: return null;
103: }
104:
105: if (cacheEntry == null) {
106: try {
107: cacheEntry = methodMap.find(name, params);
108: } catch (MethodMap.AmbiguousException ae) {
109: /*
110: * that's a miss :)
111: */
112:
113: methodCache.put(methodKey, CACHE_MISS);
114:
115: throw ae;
116: }
117:
118: if (cacheEntry == null) {
119: methodCache.put(methodKey, CACHE_MISS);
120: } else {
121: methodCache.put(methodKey, cacheEntry);
122: }
123: }
124:
125: // Yes, this might just be null.
126:
127: return (Method) cacheEntry;
128: }
129:
130: /**
131: * Populate the Map of direct hits. These are taken from all the public
132: * methods that our class provides.
133: */
134: private void populateMethodCache() {
135:
136: /*
137: * get all publicly accessible methods
138: */
139:
140: Method[] methods = getAccessibleMethods(clazz);
141:
142: /*
143: * map and cache them
144: */
145:
146: for (int i = 0; i < methods.length; i++) {
147: Method method = methods[i];
148:
149: /*
150: * now get the 'public method', the method declared by a public
151: * interface or class. (because the actual implementing class may be
152: * a facade...
153: */
154:
155: Method publicMethod = getPublicMethod(method);
156:
157: /*
158: * it is entirely possible that there is no public method for the
159: * methods of this class (i.e. in the facade, a method that isn't on
160: * any of the interfaces or superclass in which case, ignore it.
161: * Otherwise, map and cache
162: */
163:
164: if (publicMethod != null) {
165: methodMap.add(publicMethod);
166: methodCache.put(makeMethodKey(publicMethod),
167: publicMethod);
168: }
169: }
170: }
171:
172: /**
173: * Make a methodKey for the given method using the concatenation of the name
174: * and the types of the method parameters.
175: */
176: private String makeMethodKey(Method method) {
177: Class[] parameterTypes = method.getParameterTypes();
178:
179: StringBuffer methodKey = new StringBuffer(method.getName());
180:
181: for (int j = 0; j < parameterTypes.length; j++) {
182: /*
183: * If the argument type is primitive then we want to convert our
184: * primitive type signature to the corresponding Object type so
185: * introspection for methods with primitive types will work
186: * correctly.
187: */
188: if (parameterTypes[j].isPrimitive()) {
189: if (parameterTypes[j].equals(Boolean.TYPE))
190: methodKey.append("java.lang.Boolean");
191: else if (parameterTypes[j].equals(Byte.TYPE))
192: methodKey.append("java.lang.Byte");
193: else if (parameterTypes[j].equals(Character.TYPE))
194: methodKey.append("java.lang.Character");
195: else if (parameterTypes[j].equals(Double.TYPE))
196: methodKey.append("java.lang.Double");
197: else if (parameterTypes[j].equals(Float.TYPE))
198: methodKey.append("java.lang.Float");
199: else if (parameterTypes[j].equals(Integer.TYPE))
200: methodKey.append("java.lang.Integer");
201: else if (parameterTypes[j].equals(Long.TYPE))
202: methodKey.append("java.lang.Long");
203: else if (parameterTypes[j].equals(Short.TYPE))
204: methodKey.append("java.lang.Short");
205: } else {
206: methodKey.append(parameterTypes[j].getName());
207: }
208: }
209:
210: return methodKey.toString();
211: }
212:
213: private static String makeMethodKey(String method, Object[] params) {
214: StringBuffer methodKey = new StringBuffer().append(method);
215:
216: for (int j = 0; j < params.length; j++) {
217: Object arg = params[j];
218:
219: if (arg == null) {
220: arg = OBJECT;
221: }
222:
223: methodKey.append(arg.getClass().getName());
224: }
225:
226: return methodKey.toString();
227: }
228:
229: /**
230: * Retrieves public methods for a class. In case the class is not public,
231: * retrieves methods with same signature as its public methods from public
232: * superclasses and interfaces (if they exist). Basically upcasts every
233: * method to the nearest acccessible method.
234: */
235: private static Method[] getAccessibleMethods(Class clazz) {
236: Method[] methods = clazz.getMethods();
237:
238: /*
239: * Short circuit for the (hopefully) majority of cases where the clazz
240: * is public
241: */
242:
243: if (Modifier.isPublic(clazz.getModifiers())) {
244: return methods;
245: }
246:
247: /*
248: * No luck - the class is not public, so we're going the longer way.
249: */
250:
251: MethodInfo[] methodInfos = new MethodInfo[methods.length];
252:
253: for (int i = methods.length; i-- > 0;) {
254: methodInfos[i] = new MethodInfo(methods[i]);
255: }
256:
257: int upcastCount = getAccessibleMethods(clazz, methodInfos, 0);
258:
259: /*
260: * Reallocate array in case some method had no accessible counterpart.
261: */
262:
263: if (upcastCount < methods.length) {
264: methods = new Method[upcastCount];
265: }
266:
267: int j = 0;
268: for (int i = 0; i < methodInfos.length; ++i) {
269: MethodInfo methodInfo = methodInfos[i];
270: if (methodInfo.upcast) {
271: methods[j++] = methodInfo.method;
272: }
273: }
274: return methods;
275: }
276:
277: /**
278: * Recursively finds a match for each method, starting with the class, and
279: * then searching the superclass and interfaces.
280: *
281: * @param clazz Class to check
282: * @param methodInfos array of methods we are searching to match
283: * @param upcastCount current number of methods we have matched
284: * @return count of matched methods
285: */
286: private static int getAccessibleMethods(Class clazz,
287: MethodInfo[] methodInfos, int upcastCount) {
288: int l = methodInfos.length;
289:
290: /*
291: * if this class is public, then check each of the currently
292: * 'non-upcasted' methods to see if we have a match
293: */
294:
295: if (Modifier.isPublic(clazz.getModifiers())) {
296: for (int i = 0; i < l && upcastCount < l; ++i) {
297: try {
298: MethodInfo methodInfo = methodInfos[i];
299:
300: if (!methodInfo.upcast) {
301: methodInfo.tryUpcasting(clazz);
302: upcastCount++;
303: }
304: } catch (NoSuchMethodException e) {
305: /*
306: * Intentionally ignored - it means it wasn't found in the
307: * current class
308: */
309: }
310: }
311:
312: /*
313: * Short circuit if all methods were upcast
314: */
315:
316: if (upcastCount == l) {
317: return upcastCount;
318: }
319: }
320:
321: /*
322: * Examine superclass
323: */
324:
325: Class super clazz = clazz.getSuperclass();
326:
327: if (super clazz != null) {
328: upcastCount = getAccessibleMethods(super clazz, methodInfos,
329: upcastCount);
330:
331: /*
332: * Short circuit if all methods were upcast
333: */
334:
335: if (upcastCount == l) {
336: return upcastCount;
337: }
338: }
339:
340: /*
341: * Examine interfaces. Note we do it even if superclazz == null. This is
342: * redundant as currently java.lang.Object does not implement any
343: * interfaces, however nothing guarantees it will not in future.
344: */
345:
346: Class[] interfaces = clazz.getInterfaces();
347:
348: for (int i = interfaces.length; i-- > 0;) {
349: upcastCount = getAccessibleMethods(interfaces[i],
350: methodInfos, upcastCount);
351:
352: /*
353: * Short circuit if all methods were upcast
354: */
355:
356: if (upcastCount == l) {
357: return upcastCount;
358: }
359: }
360:
361: return upcastCount;
362: }
363:
364: /**
365: * For a given method, retrieves its publicly accessible counterpart. This
366: * method will look for a method with same name and signature declared in a
367: * public superclass or implemented interface of this method's declaring
368: * class. This counterpart method is publicly callable.
369: *
370: * @param method a method whose publicly callable counterpart is requested.
371: * @return the publicly callable counterpart method. Note that if the
372: * parameter method is itself declared by a public class, this
373: * method is an identity function.
374: */
375: public static Method getPublicMethod(Method method) {
376: Class clazz = method.getDeclaringClass();
377:
378: /*
379: * Short circuit for (hopefully the majority of) cases where the
380: * declaring class is public.
381: */
382:
383: if ((clazz.getModifiers() & Modifier.PUBLIC) != 0) {
384: return method;
385: }
386:
387: return getPublicMethod(clazz, method.getName(), method
388: .getParameterTypes());
389: }
390:
391: /**
392: * Looks up the method with specified name and signature in the first public
393: * superclass or implemented interface of the class.
394: *
395: * @param class the class whose method is sought
396: * @param name the name of the method
397: * @param paramTypes the classes of method parameters
398: */
399: private static Method getPublicMethod(Class clazz, String name,
400: Class[] paramTypes) {
401: /*
402: * if this class is public, then try to get it
403: */
404:
405: if ((clazz.getModifiers() & Modifier.PUBLIC) != 0) {
406: try {
407: return clazz.getMethod(name, paramTypes);
408: } catch (NoSuchMethodException e) {
409: /*
410: * If the class does not have the method, then neither its
411: * superclass nor any of its interfaces has it so quickly return
412: * null.
413: */
414: return null;
415: }
416: }
417:
418: /*
419: * try the superclass
420: */
421:
422: Class super clazz = clazz.getSuperclass();
423:
424: if (super clazz != null) {
425: Method super clazzMethod = getPublicMethod(super clazz, name,
426: paramTypes);
427:
428: if (super clazzMethod != null) {
429: return super clazzMethod;
430: }
431: }
432:
433: /*
434: * and interfaces
435: */
436:
437: Class[] interfaces = clazz.getInterfaces();
438:
439: for (int i = 0; i < interfaces.length; ++i) {
440: Method interfaceMethod = getPublicMethod(interfaces[i],
441: name, paramTypes);
442:
443: if (interfaceMethod != null) {
444: return interfaceMethod;
445: }
446: }
447:
448: return null;
449: }
450:
451: /**
452: * Used for the iterative discovery process for public methods.
453: */
454: private static final class MethodInfo {
455: Method method;
456:
457: String name;
458:
459: Class[] parameterTypes;
460:
461: boolean upcast;
462:
463: MethodInfo(Method method) {
464: this .method = null;
465: name = method.getName();
466: parameterTypes = method.getParameterTypes();
467: upcast = false;
468: }
469:
470: void tryUpcasting(Class clazz) throws NoSuchMethodException {
471: method = clazz.getMethod(name, parameterTypes);
472: name = null;
473: parameterTypes = null;
474: upcast = true;
475: }
476: }
477: }
|