001: package murlen.util.fscript.introspection;
002:
003: /*
004: * The Apache Software License, Version 1.1
005: *
006: * Copyright (c) 2001 The Apache Software Foundation. All rights
007: * reserved.
008: *
009: * Redistribution and use in source and binary forms, with or without
010: * modification, are permitted provided that the following conditions
011: * are met:
012: *
013: * 1. Redistributions of source code must retain the above copyright
014: * notice, this list of conditions and the following disclaimer.
015: *
016: * 2. Redistributions in binary form must reproduce the above copyright
017: * notice, this list of conditions and the following disclaimer in
018: * the documentation and/or other materials provided with the
019: * distribution.
020: *
021: * 3. The end-user documentation included with the redistribution, if
022: * any, must include the following acknowlegement:
023: * "This product includes software developed by the
024: * Apache Software Foundation (http://www.apache.org/)."
025: * Alternately, this acknowlegement may appear in the software itself,
026: * if and wherever such third-party acknowlegements normally appear.
027: *
028: * 4. The names "The Jakarta Project", "Velocity", and "Apache Software
029: * Foundation" must not be used to endorse or promote products derived
030: * from this software without prior written permission. For written
031: * permission, please contact apache@apache.org.
032: *
033: * 5. Products derived from this software may not be called "Apache"
034: * nor may "Apache" appear in their names without prior written
035: * permission of the Apache Group.
036: *
037: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
038: * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
039: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
040: * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
041: * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
042: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
043: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
044: * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
045: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
046: * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
047: * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
048: * SUCH DAMAGE.
049: * ====================================================================
050: *
051: * This software consists of voluntary contributions made by many
052: * individuals on behalf of the Apache Software Foundation. For more
053: * information on the Apache Software Foundation, please see
054: * <http://www.apache.org/>.
055: */
056:
057: import murlen.util.fscript.FSObject;
058:
059: import java.util.Map;
060: import java.util.Hashtable;
061:
062: import java.lang.reflect.Method;
063: import java.lang.reflect.Modifier;
064:
065: /**
066: * A cache of introspection information for a specific class instance.
067: * Keys {@link java.lang.reflect.Method} objects by a concatenation of the
068: * method name and the names of classes that make up the parameters.
069: *
070: * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
071: * @author <a href="mailto:bob@werken.com">Bob McWhirter</a>
072: * @author <a href="mailto:szegedia@freemail.hu">Attila Szegedi</a>
073: * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
074: * @author <a href="mailto:joachim@triathlon98.be">Joachim Van der Auwera</a>
075: * @version $Id: ClassMap.java,v 1.1.1.1 2004/07/28 01:28:03 murlen Exp $
076: */
077: public class ClassMap {
078: private static final class CacheMiss {
079: }
080:
081: private static final CacheMiss CACHE_MISS = new CacheMiss();
082:
083: /**
084: * Class passed into the constructor used to as
085: * the basis for the Method map.
086: */
087:
088: private Class clazz;
089:
090: /**
091: * Cache of Methods, or CACHE_MISS, keyed by method
092: * name and actual arguments used to find it.
093: */
094: private Map methodCache = new Hashtable();
095:
096: private MethodMap methodMap = new MethodMap();
097:
098: /**
099: * Standard constructor
100: */
101: public ClassMap(Class clazz) {
102: this .clazz = clazz;
103: populateMethodCache();
104: }
105:
106: /**
107: * @return the class object whose methods are cached by this map.
108: */
109: Class getCachedClass() {
110: return clazz;
111: }
112:
113: /**
114: * Find a Method using the methodKey
115: * provided.
116: *
117: * Look in the methodMap for an entry. If found,
118: * it'll either be a CACHE_MISS, in which case we
119: * simply give up, or it'll be a Method, in which
120: * case, we return it.
121: *
122: * If nothing is found, then we must actually go
123: * and introspect the method from the MethodMap.
124: */
125: public Method findMethod(String name, Object[] params)
126: throws MethodMap.AmbiguousException {
127: String methodKey = makeMethodKey(name, params);
128: Object cacheEntry = methodCache.get(methodKey);
129:
130: if (cacheEntry == CACHE_MISS) {
131: return null;
132: }
133:
134: if (cacheEntry == null) {
135: try {
136: cacheEntry = methodMap.find(name, params);
137: } catch (MethodMap.AmbiguousException ae) {
138: /*
139: * that's a miss :)
140: */
141:
142: methodCache.put(methodKey, CACHE_MISS);
143:
144: throw ae;
145: }
146:
147: if (cacheEntry == null) {
148: methodCache.put(methodKey, CACHE_MISS);
149: } else {
150: methodCache.put(methodKey, cacheEntry);
151: }
152: }
153:
154: // Yes, this might just be null.
155:
156: return (Method) cacheEntry;
157: }
158:
159: /**
160: * Populate the Map of direct hits. These
161: * are taken from all the public methods
162: * that our class provides.
163: */
164: private void populateMethodCache() {
165: /*
166: * get all publicly accessible methods
167: */
168:
169: Method[] methods = getAccessibleMethods(clazz);
170:
171: /*
172: * map and cache them
173: */
174:
175: for (int i = 0; i < methods.length; i++) {
176: Method method = methods[i];
177:
178: /*
179: * now get the 'public method', the method declared by a
180: * public interface or class. (because the actual implementing
181: * class may be a facade...
182: */
183:
184: Method publicMethod = getPublicMethod(method);
185:
186: /*
187: * it is entirely possible that there is no public method for
188: * the methods of this class (i.e. in the facade, a method
189: * that isn't on any of the interfaces or superclass
190: * in which case, ignore it. Otherwise, map and cache
191: */
192:
193: if (publicMethod != null) {
194: methodMap.add(publicMethod);
195: methodCache.put(makeMethodKey(publicMethod),
196: publicMethod);
197: }
198: }
199: }
200:
201: /**
202: * Make a methodKey for the given method using
203: * the concatenation of the name and the
204: * types of the method parameters.
205: */
206: private String makeMethodKey(Method method) {
207: Class[] parameterTypes = method.getParameterTypes();
208:
209: StringBuffer methodKey = new StringBuffer(method.getName());
210:
211: for (int j = 0; j < parameterTypes.length; j++) {
212: /*
213: * If the argument type is primitive then we want
214: * to convert our primitive type signature to the
215: * corresponding Object type so introspection for
216: * methods with primitive types will work correctly.
217: */
218: if (parameterTypes[j].isPrimitive()) {
219: if (parameterTypes[j].equals(Boolean.TYPE))
220: methodKey.append("java.lang.Boolean");
221: else if (parameterTypes[j].equals(Byte.TYPE))
222: methodKey.append("java.lang.Byte");
223: else if (parameterTypes[j].equals(Character.TYPE))
224: methodKey.append("java.lang.Character");
225: else if (parameterTypes[j].equals(Double.TYPE))
226: methodKey.append("java.lang.Double");
227: else if (parameterTypes[j].equals(Float.TYPE))
228: methodKey.append("java.lang.Float");
229: else if (parameterTypes[j].equals(Integer.TYPE))
230: methodKey.append("java.lang.Integer");
231: else if (parameterTypes[j].equals(Long.TYPE))
232: methodKey.append("java.lang.Long");
233: else if (parameterTypes[j].equals(Short.TYPE))
234: methodKey.append("java.lang.Short");
235: } else {
236: methodKey.append(parameterTypes[j].getName());
237: }
238: }
239:
240: return methodKey.toString();
241: }
242:
243: private static String makeMethodKey(String method, Object[] params) {
244: StringBuffer methodKey = new StringBuffer().append(method);
245:
246: for (int j = 0; j < params.length; j++) {
247: Object arg = params[j];
248:
249: if (arg == null) {
250: arg = Object.class;
251: } else {
252: if (arg instanceof FSObject) {
253: arg = ((FSObject) arg).getNullClass();
254: if (arg == null)
255: arg = Object.class;
256: } else {
257: arg = arg.getClass();
258: }
259: }
260:
261: methodKey.append(((Class) arg).getName());
262: }
263:
264: return methodKey.toString();
265: }
266:
267: /**
268: * Retrieves public methods for a class. In case the class is not
269: * public, retrieves methods with same signature as its public methods
270: * from public superclasses and interfaces (if they exist). Basically
271: * upcasts every method to the nearest acccessible method.
272: */
273: private static Method[] getAccessibleMethods(Class clazz) {
274: Method[] methods = clazz.getMethods();
275:
276: /*
277: * Short circuit for the (hopefully) majority of cases where the
278: * clazz is public
279: */
280:
281: if (Modifier.isPublic(clazz.getModifiers())) {
282: return methods;
283: }
284:
285: /*
286: * No luck - the class is not public, so we're going the longer way.
287: */
288:
289: MethodInfo[] methodInfos = new MethodInfo[methods.length];
290:
291: for (int i = methods.length; i-- > 0;) {
292: methodInfos[i] = new MethodInfo(methods[i]);
293: }
294:
295: int upcastCount = getAccessibleMethods(clazz, methodInfos, 0);
296:
297: /*
298: * Reallocate array in case some method had no accessible counterpart.
299: */
300:
301: if (upcastCount < methods.length) {
302: methods = new Method[upcastCount];
303: }
304:
305: int j = 0;
306: for (int i = 0; i < methodInfos.length; ++i) {
307: MethodInfo methodInfo = methodInfos[i];
308: if (methodInfo.upcast) {
309: methods[j++] = methodInfo.method;
310: }
311: }
312: return methods;
313: }
314:
315: /**
316: * Recursively finds a match for each method, starting with the class, and then
317: * searching the superclass and interfaces.
318: *
319: * @param clazz Class to check
320: * @param methodInfos array of methods we are searching to match
321: * @param upcastCount current number of methods we have matched
322: * @return count of matched methods
323: */
324: private static int getAccessibleMethods(Class clazz,
325: MethodInfo[] methodInfos, int upcastCount) {
326: int l = methodInfos.length;
327:
328: /*
329: * if this class is public, then check each of the currently
330: * 'non-upcasted' methods to see if we have a match
331: */
332:
333: if (Modifier.isPublic(clazz.getModifiers())) {
334: for (int i = 0; i < l && upcastCount < l; ++i) {
335: try {
336: MethodInfo methodInfo = methodInfos[i];
337:
338: if (!methodInfo.upcast) {
339: methodInfo.tryUpcasting(clazz);
340: upcastCount++;
341: }
342: } catch (NoSuchMethodException e) {
343: /*
344: * Intentionally ignored - it means
345: * it wasn't found in the current class
346: */
347: }
348: }
349:
350: /*
351: * Short circuit if all methods were upcast
352: */
353:
354: if (upcastCount == l) {
355: return upcastCount;
356: }
357: }
358:
359: /*
360: * Examine superclass
361: */
362:
363: Class super clazz = clazz.getSuperclass();
364:
365: if (super clazz != null) {
366: upcastCount = getAccessibleMethods(super clazz, methodInfos,
367: upcastCount);
368:
369: /*
370: * Short circuit if all methods were upcast
371: */
372:
373: if (upcastCount == l) {
374: return upcastCount;
375: }
376: }
377:
378: /*
379: * Examine interfaces. Note we do it even if superclazz == null.
380: * This is redundant as currently java.lang.Object does not implement
381: * any interfaces, however nothing guarantees it will not in future.
382: */
383:
384: Class[] interfaces = clazz.getInterfaces();
385:
386: for (int i = interfaces.length; i-- > 0;) {
387: upcastCount = getAccessibleMethods(interfaces[i],
388: methodInfos, upcastCount);
389:
390: /*
391: * Short circuit if all methods were upcast
392: */
393:
394: if (upcastCount == l) {
395: return upcastCount;
396: }
397: }
398:
399: return upcastCount;
400: }
401:
402: /**
403: * For a given method, retrieves its publicly accessible counterpart.
404: * This method will look for a method with same name
405: * and signature declared in a public superclass or implemented interface of this
406: * method's declaring class. This counterpart method is publicly callable.
407: *
408: * @param method a method whose publicly callable counterpart is requested.
409: * @return the publicly callable counterpart method. Note that if the parameter
410: * method is itself declared by a public class, this method is an identity
411: * function.
412: */
413: public static Method getPublicMethod(Method method) {
414: Class clazz = method.getDeclaringClass();
415:
416: /*
417: * Short circuit for (hopefully the majority of) cases where the declaring
418: * class is public.
419: */
420:
421: if ((clazz.getModifiers() & Modifier.PUBLIC) != 0) {
422: return method;
423: }
424:
425: return getPublicMethod(clazz, method.getName(), method
426: .getParameterTypes());
427: }
428:
429: /**
430: * Looks up the method with specified name and signature in the first public
431: * superclass or implemented interface of the class.
432: *
433: * @param clazz the class whose method is sought
434: * @param name the name of the method
435: * @param paramTypes the classes of method parameters
436: */
437: private static Method getPublicMethod(Class clazz, String name,
438: Class[] paramTypes) {
439: /*
440: * if this class is public, then try to get it
441: */
442:
443: if ((clazz.getModifiers() & Modifier.PUBLIC) != 0) {
444: try {
445: return clazz.getMethod(name, paramTypes);
446: } catch (NoSuchMethodException e) {
447: /*
448: * If the class does not have the method, then neither its
449: * superclass nor any of its interfaces has it so quickly return
450: * null.
451: */
452: return null;
453: }
454: }
455:
456: /*
457: * try the superclass
458: */
459:
460: Class super clazz = clazz.getSuperclass();
461:
462: if (super clazz != null) {
463: Method super clazzMethod = getPublicMethod(super clazz, name,
464: paramTypes);
465:
466: if (super clazzMethod != null) {
467: return super clazzMethod;
468: }
469: }
470:
471: /*
472: * and interfaces
473: */
474:
475: Class[] interfaces = clazz.getInterfaces();
476:
477: for (int i = 0; i < interfaces.length; ++i) {
478: Method interfaceMethod = getPublicMethod(interfaces[i],
479: name, paramTypes);
480:
481: if (interfaceMethod != null) {
482: return interfaceMethod;
483: }
484: }
485:
486: return null;
487: }
488:
489: /**
490: * Used for the iterative discovery process for public methods.
491: */
492: private static final class MethodInfo {
493: Method method;
494: String name;
495: Class[] parameterTypes;
496: boolean upcast;
497:
498: MethodInfo(Method method) {
499: this .method = null;
500: name = method.getName();
501: parameterTypes = method.getParameterTypes();
502: upcast = false;
503: }
504:
505: void tryUpcasting(Class clazz) throws NoSuchMethodException {
506: method = clazz.getMethod(name, parameterTypes);
507: name = null;
508: parameterTypes = null;
509: upcast = true;
510: }
511: }
512: }
|