KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > velocity > util > introspection > ClassMap


1 package org.apache.velocity.util.introspection;
2
3 /*
4  * Copyright 2001,2004 The Apache Software Foundation.
5  *
6  * Licensed under the Apache License, Version 2.0 (the "License");
7  * you may not use this file except in compliance with the License.
8  * You may obtain a copy of the License at
9  *
10  * http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS,
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  * See the License for the specific language governing permissions and
16  * limitations under the License.
17  */

18
19 import java.util.Map JavaDoc;
20 import java.util.List JavaDoc;
21 import java.util.Hashtable JavaDoc;
22
23 import java.lang.reflect.Method JavaDoc;
24 import java.lang.reflect.Modifier JavaDoc;
25
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.20.8.1 2004/03/03 23:23:08 geirm 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 JavaDoc OBJECT = new Object JavaDoc();
42
43     /**
44      * Class passed into the constructor used to as
45      * the basis for the Method map.
46      */

47
48     private Class JavaDoc 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 JavaDoc methodCache = new Hashtable JavaDoc();
55
56     private MethodMap methodMap = new MethodMap();
57
58     /**
59      * Standard constructor
60      */

61     public ClassMap( Class JavaDoc 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 JavaDoc 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 JavaDoc findMethod(String JavaDoc name, Object JavaDoc[] params)
92         throws MethodMap.AmbiguousException
93     {
94         String JavaDoc methodKey = makeMethodKey(name, params);
95         Object JavaDoc 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 JavaDoc) 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         StringBuffer JavaDoc methodKey;
146
147         /*
148          * get all publicly accessible methods
149          */

150
151         Method JavaDoc[] methods = getAccessibleMethods(clazz);
152
153         /*
154          * map and cache them
155          */

156
157         for (int i = 0; i < methods.length; i++)
158         {
159             Method JavaDoc method = methods[i];
160
161             /*
162              * now get the 'public method', the method declared by a
163              * public interface or class. (because the actual implementing
164              * class may be a facade...
165              */

166
167             Method JavaDoc publicMethod = getPublicMethod( method );
168
169             /*
170              * it is entirely possible that there is no public method for
171              * the methods of this class (i.e. in the facade, a method
172              * that isn't on any of the interfaces or superclass
173              * in which case, ignore it. Otherwise, map and cache
174              */

175
176             if ( publicMethod != null)
177             {
178                 methodMap.add( publicMethod );
179                 methodCache.put( makeMethodKey( publicMethod), publicMethod);
180             }
181         }
182     }
183
184     /**
185      * Make a methodKey for the given method using
186      * the concatenation of the name and the
187      * types of the method parameters.
188      */

189     private String JavaDoc makeMethodKey(Method JavaDoc method)
190     {
191         Class JavaDoc[] parameterTypes = method.getParameterTypes();
192         
193         StringBuffer JavaDoc methodKey = new StringBuffer JavaDoc(method.getName());
194         
195         for (int j = 0; j < parameterTypes.length; j++)
196         {
197             /*
198              * If the argument type is primitive then we want
199              * to convert our primitive type signature to the
200              * corresponding Object type so introspection for
201              * methods with primitive types will work correctly.
202              */

203             if (parameterTypes[j].isPrimitive())
204             {
205                 if (parameterTypes[j].equals(Boolean.TYPE))
206                     methodKey.append("java.lang.Boolean");
207                 else if (parameterTypes[j].equals(Byte.TYPE))
208                     methodKey.append("java.lang.Byte");
209                 else if (parameterTypes[j].equals(Character.TYPE))
210                     methodKey.append("java.lang.Character");
211                 else if (parameterTypes[j].equals(Double.TYPE))
212                     methodKey.append("java.lang.Double");
213                 else if (parameterTypes[j].equals(Float.TYPE))
214                     methodKey.append("java.lang.Float");
215                 else if (parameterTypes[j].equals(Integer.TYPE))
216                     methodKey.append("java.lang.Integer");
217                 else if (parameterTypes[j].equals(Long.TYPE))
218                     methodKey.append("java.lang.Long");
219                 else if (parameterTypes[j].equals(Short.TYPE))
220                     methodKey.append("java.lang.Short");
221             }
222             else
223             {
224                 methodKey.append(parameterTypes[j].getName());
225             }
226         }
227         
228         return methodKey.toString();
229     }
230
231     private static String JavaDoc makeMethodKey(String JavaDoc method, Object JavaDoc[] params)
232     {
233         StringBuffer JavaDoc methodKey = new StringBuffer JavaDoc().append(method);
234
235         for (int j = 0; j < params.length; j++)
236         {
237             Object JavaDoc arg = params[j];
238
239             if (arg == null)
240             {
241                 arg = OBJECT;
242             }
243
244             methodKey.append(arg.getClass().getName());
245         }
246         
247         return methodKey.toString();
248     }
249
250     /**
251      * Retrieves public methods for a class. In case the class is not
252      * public, retrieves methods with same signature as its public methods
253      * from public superclasses and interfaces (if they exist). Basically
254      * upcasts every method to the nearest acccessible method.
255      */

256     private static Method JavaDoc[] getAccessibleMethods(Class JavaDoc clazz)
257     {
258         Method JavaDoc[] methods = clazz.getMethods();
259         
260         /*
261          * Short circuit for the (hopefully) majority of cases where the
262          * clazz is public
263          */

264         
265         if (Modifier.isPublic(clazz.getModifiers()))
266         {
267             return methods;
268         }
269
270         /*
271          * No luck - the class is not public, so we're going the longer way.
272          */

273
274         MethodInfo[] methodInfos = new MethodInfo[methods.length];
275
276         for(int i = methods.length; i-- > 0; )
277         {
278             methodInfos[i] = new MethodInfo(methods[i]);
279         }
280
281         int upcastCount = getAccessibleMethods(clazz, methodInfos, 0);
282
283         /*
284          * Reallocate array in case some method had no accessible counterpart.
285          */

286
287         if(upcastCount < methods.length)
288         {
289             methods = new Method JavaDoc[upcastCount];
290         }
291
292         int j = 0;
293         for(int i = 0; i < methodInfos.length; ++i)
294         {
295             MethodInfo methodInfo = methodInfos[i];
296             if(methodInfo.upcast)
297             {
298                 methods[j++] = methodInfo.method;
299             }
300         }
301         return methods;
302     }
303
304     /**
305      * Recursively finds a match for each method, starting with the class, and then
306      * searching the superclass and interfaces.
307      *
308      * @param clazz Class to check
309      * @param methodInfos array of methods we are searching to match
310      * @param upcastCount current number of methods we have matched
311      * @return count of matched methods
312      */

313     private static int getAccessibleMethods( Class JavaDoc clazz, MethodInfo[] methodInfos, int upcastCount)
314     {
315         int l = methodInfos.length;
316         
317         /*
318          * if this class is public, then check each of the currently
319          * 'non-upcasted' methods to see if we have a match
320          */

321
322         if( Modifier.isPublic(clazz.getModifiers()) )
323         {
324             for(int i = 0; i < l && upcastCount < l; ++i)
325             {
326                 try
327                 {
328                     MethodInfo methodInfo = methodInfos[i];
329
330                     if(!methodInfo.upcast)
331                     {
332                         methodInfo.tryUpcasting(clazz);
333                         upcastCount++;
334                     }
335                 }
336                 catch(NoSuchMethodException JavaDoc e)
337                 {
338                     /*
339                      * Intentionally ignored - it means
340                      * it wasn't found in the current class
341                      */

342                 }
343             }
344
345             /*
346              * Short circuit if all methods were upcast
347              */

348
349             if(upcastCount == l)
350             {
351                 return upcastCount;
352             }
353         }
354
355         /*
356          * Examine superclass
357          */

358
359         Class JavaDoc superclazz = clazz.getSuperclass();
360
361         if(superclazz != null)
362         {
363             upcastCount = getAccessibleMethods(superclazz , methodInfos, upcastCount);
364
365             /*
366              * Short circuit if all methods were upcast
367              */

368
369             if(upcastCount == l)
370             {
371                 return upcastCount;
372             }
373         }
374
375         /*
376          * Examine interfaces. Note we do it even if superclazz == null.
377          * This is redundant as currently java.lang.Object does not implement
378          * any interfaces, however nothing guarantees it will not in future.
379          */

380
381         Class JavaDoc[] interfaces = clazz.getInterfaces();
382
383         for(int i = interfaces.length; i-- > 0; )
384         {
385             upcastCount = getAccessibleMethods(interfaces[i], methodInfos, upcastCount);
386
387             /*
388              * Short circuit if all methods were upcast
389              */

390
391             if(upcastCount == l)
392             {
393                 return upcastCount;
394             }
395         }
396
397         return upcastCount;
398     }
399     
400     /**
401      * For a given method, retrieves its publicly accessible counterpart.
402      * This method will look for a method with same name
403      * and signature declared in a public superclass or implemented interface of this
404      * method's declaring class. This counterpart method is publicly callable.
405      *
406      * @param method a method whose publicly callable counterpart is requested.
407      * @return the publicly callable counterpart method. Note that if the parameter
408      * method is itself declared by a public class, this method is an identity
409      * function.
410      */

411     public static Method JavaDoc getPublicMethod(Method JavaDoc method)
412     {
413         Class JavaDoc clazz = method.getDeclaringClass();
414         
415         /*
416          * Short circuit for (hopefully the majority of) cases where the declaring
417          * class is public.
418          */

419
420         if((clazz.getModifiers() & Modifier.PUBLIC) != 0)
421         {
422             return method;
423         }
424
425         return getPublicMethod(clazz, method.getName(), method.getParameterTypes());
426     }
427
428     /**
429      * Looks up the method with specified name and signature in the first public
430      * superclass or implemented interface of the class.
431      *
432      * @param class the class whose method is sought
433      * @param name the name of the method
434      * @param paramTypes the classes of method parameters
435      */

436     private static Method JavaDoc getPublicMethod(Class JavaDoc clazz, String JavaDoc name, Class JavaDoc[] paramTypes)
437     {
438         /*
439          * if this class is public, then try to get it
440          */

441
442         if((clazz.getModifiers() & Modifier.PUBLIC) != 0)
443         {
444             try
445             {
446                 return clazz.getMethod(name, paramTypes);
447             }
448             catch(NoSuchMethodException JavaDoc e)
449             {
450                 /*
451                  * If the class does not have the method, then neither its
452                  * superclass nor any of its interfaces has it so quickly return
453                  * null.
454                  */

455                  return null;
456             }
457         }
458
459         /*
460          * try the superclass
461          */

462
463  
464         Class JavaDoc superclazz = clazz.getSuperclass();
465
466         if ( superclazz != null )
467         {
468             Method JavaDoc superclazzMethod = getPublicMethod(superclazz, name, paramTypes);
469             
470             if(superclazzMethod != null)
471             {
472                 return superclazzMethod;
473             }
474         }
475
476         /*
477          * and interfaces
478          */

479
480         Class JavaDoc[] interfaces = clazz.getInterfaces();
481
482         for(int i = 0; i < interfaces.length; ++i)
483         {
484             Method JavaDoc interfaceMethod = getPublicMethod(interfaces[i], name, paramTypes);
485             
486             if(interfaceMethod != null)
487             {
488                 return interfaceMethod;
489             }
490         }
491
492         return null;
493     }
494
495     /**
496      * Used for the iterative discovery process for public methods.
497      */

498     private static final class MethodInfo
499     {
500         Method JavaDoc method;
501         String JavaDoc name;
502         Class JavaDoc[] parameterTypes;
503         boolean upcast;
504         
505         MethodInfo(Method JavaDoc method)
506         {
507             this.method = null;
508             name = method.getName();
509             parameterTypes = method.getParameterTypes();
510             upcast = false;
511         }
512         
513         void tryUpcasting(Class JavaDoc clazz)
514             throws NoSuchMethodException JavaDoc
515         {
516             method = clazz.getMethod(name, parameterTypes);
517             name = null;
518             parameterTypes = null;
519             upcast = true;
520         }
521     }
522 }
523
Popular Tags