View Javadoc
1   package org.codehaus.plexus.util.introspection;
2   
3   /*
4    * Copyright The Codehaus 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.lang.reflect.Method;
20  import java.lang.reflect.Modifier;
21  import java.util.Hashtable;
22  import java.util.Map;
23  
24  /**
25   * A cache of introspection information for a specific class instance. Keys {@link java.lang.reflect.Method} objects by
26   * a concatenation of the method name and the names of classes that make up the parameters.
27   *
28   * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
29   * @author <a href="mailto:bob@werken.com">Bob McWhirter</a>
30   * @author <a href="mailto:szegedia@freemail.hu">Attila Szegedi</a>
31   * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
32   * @version $Id$
33   */
34  public class ClassMap
35  {
36      private static final class CacheMiss
37      {
38      }
39  
40      private static final CacheMiss CACHE_MISS = new CacheMiss();
41  
42      private static final Object OBJECT = new Object();
43  
44      /**
45       * Class passed into the constructor used to as the basis for the Method map.
46       */
47  
48      private final Class clazz;
49  
50      /**
51       * Cache of Methods, or CACHE_MISS, keyed by method name and actual arguments used to find it.
52       */
53      private Map<String, Object> methodCache = new Hashtable<String, Object>();
54  
55      private final MethodMap methodMap = new MethodMap();
56  
57      /**
58       * Standard constructor
59       */
60      public ClassMap( Class clazz )
61      {
62          this.clazz = clazz;
63          populateMethodCache();
64      }
65  
66      /**
67       * @return the class object whose methods are cached by this map.
68       */
69      Class getCachedClass()
70      {
71          return clazz;
72      }
73  
74      /**
75       * Find a Method using the methodKey provided.
76       * <p/>
77       * Look in the methodMap for an entry. If found, it'll either be a CACHE_MISS, in which case we simply give up, or
78       * it'll be a Method, in which case, we return it.
79       * <p/>
80       * If nothing is found, then we must actually go and introspect the method from the MethodMap.
81       */
82      public Method findMethod( String name, Object[] params )
83          throws MethodMap.AmbiguousException
84      {
85          String methodKey = makeMethodKey( name, params );
86          Object cacheEntry = methodCache.get( methodKey );
87  
88          if ( cacheEntry == CACHE_MISS )
89          {
90              return null;
91          }
92  
93          if ( cacheEntry == null )
94          {
95              try
96              {
97                  cacheEntry = methodMap.find( name, params );
98              }
99              catch ( MethodMap.AmbiguousException ae )
100             {
101                 /*
102                  * that's a miss :)
103                  */
104 
105                 methodCache.put( methodKey, CACHE_MISS );
106 
107                 throw ae;
108             }
109 
110             if ( cacheEntry == null )
111             {
112                 methodCache.put( methodKey, CACHE_MISS );
113             }
114             else
115             {
116                 methodCache.put( methodKey, cacheEntry );
117             }
118         }
119 
120         // Yes, this might just be null.
121 
122         return (Method) cacheEntry;
123     }
124 
125     /**
126      * Populate the Map of direct hits. These are taken from all the public methods that our class provides.
127      */
128     private void populateMethodCache()
129     {
130         StringBuffer methodKey;
131 
132         /*
133          * get all publicly accessible methods
134          */
135 
136         Method[] methods = getAccessibleMethods( clazz );
137 
138         /*
139          * map and cache them
140          */
141 
142         for ( Method method : methods )
143         {
144             /*
145              * now get the 'public method', the method declared by a public interface or class. (because the actual
146              * implementing class may be a facade...
147              */
148 
149             Method publicMethod = getPublicMethod( method );
150 
151             /*
152              * it is entirely possible that there is no public method for the methods of this class (i.e. in the facade,
153              * a method that isn't on any of the interfaces or superclass in which case, ignore it. Otherwise, map and
154              * cache
155              */
156 
157             if ( publicMethod != null )
158             {
159                 methodMap.add( publicMethod );
160                 methodCache.put( makeMethodKey( publicMethod ), publicMethod );
161             }
162         }
163     }
164 
165     /**
166      * Make a methodKey for the given method using the concatenation of the name and the types of the method parameters.
167      */
168     private String makeMethodKey( Method method )
169     {
170         Class[] parameterTypes = method.getParameterTypes();
171 
172         StringBuilder methodKey = new StringBuilder( method.getName() );
173 
174         for ( Class parameterType : parameterTypes )
175         {
176             /*
177              * If the argument type is primitive then we want to convert our primitive type signature to the
178              * corresponding Object type so introspection for methods with primitive types will work correctly.
179              */
180             if ( parameterType.isPrimitive() )
181             {
182                 if ( parameterType.equals( Boolean.TYPE ) )
183                 {
184                     methodKey.append( "java.lang.Boolean" );
185                 }
186                 else if ( parameterType.equals( Byte.TYPE ) )
187                 {
188                     methodKey.append( "java.lang.Byte" );
189                 }
190                 else if ( parameterType.equals( Character.TYPE ) )
191                 {
192                     methodKey.append( "java.lang.Character" );
193                 }
194                 else if ( parameterType.equals( Double.TYPE ) )
195                 {
196                     methodKey.append( "java.lang.Double" );
197                 }
198                 else if ( parameterType.equals( Float.TYPE ) )
199                 {
200                     methodKey.append( "java.lang.Float" );
201                 }
202                 else if ( parameterType.equals( Integer.TYPE ) )
203                 {
204                     methodKey.append( "java.lang.Integer" );
205                 }
206                 else if ( parameterType.equals( Long.TYPE ) )
207                 {
208                     methodKey.append( "java.lang.Long" );
209                 }
210                 else if ( parameterType.equals( Short.TYPE ) )
211                 {
212                     methodKey.append( "java.lang.Short" );
213                 }
214             }
215             else
216             {
217                 methodKey.append( parameterType.getName() );
218             }
219         }
220 
221         return methodKey.toString();
222     }
223 
224     private static String makeMethodKey( String method, Object[] params )
225     {
226         StringBuilder methodKey = new StringBuilder().append( method );
227 
228         for ( Object param : params )
229         {
230             Object arg = param;
231 
232             if ( arg == null )
233             {
234                 arg = OBJECT;
235             }
236 
237             methodKey.append( arg.getClass().getName() );
238         }
239 
240         return methodKey.toString();
241     }
242 
243     /**
244      * Retrieves public methods for a class. In case the class is not public, retrieves methods with same signature as
245      * its public methods from public superclasses and interfaces (if they exist). Basically upcasts every method to the
246      * nearest accessible method.
247      */
248     private static Method[] getAccessibleMethods( Class clazz )
249     {
250         Method[] methods = clazz.getMethods();
251 
252         /*
253          * Short circuit for the (hopefully) majority of cases where the clazz is public
254          */
255 
256         if ( Modifier.isPublic( clazz.getModifiers() ) )
257         {
258             return methods;
259         }
260 
261         /*
262          * No luck - the class is not public, so we're going the longer way.
263          */
264 
265         MethodInfo[] methodInfos = new MethodInfo[methods.length];
266 
267         for ( int i = methods.length; i-- > 0; )
268         {
269             methodInfos[i] = new MethodInfo( methods[i] );
270         }
271 
272         int upcastCount = getAccessibleMethods( clazz, methodInfos, 0 );
273 
274         /*
275          * Reallocate array in case some method had no accessible counterpart.
276          */
277 
278         if ( upcastCount < methods.length )
279         {
280             methods = new Method[upcastCount];
281         }
282 
283         int j = 0;
284         for ( MethodInfo methodInfo : methodInfos )
285         {
286             if ( methodInfo.upcast )
287             {
288                 methods[j++] = methodInfo.method;
289             }
290         }
291         return methods;
292     }
293 
294     /**
295      * Recursively finds a match for each method, starting with the class, and then searching the superclass and
296      * interfaces.
297      *
298      * @param clazz Class to check
299      * @param methodInfos array of methods we are searching to match
300      * @param upcastCount current number of methods we have matched
301      * @return count of matched methods
302      */
303     private static int getAccessibleMethods( Class clazz, MethodInfo[] methodInfos, int upcastCount )
304     {
305         int l = methodInfos.length;
306 
307         /*
308          * if this class is public, then check each of the currently 'non-upcasted' methods to see if we have a match
309          */
310 
311         if ( Modifier.isPublic( clazz.getModifiers() ) )
312         {
313             for ( int i = 0; i < l && upcastCount < l; ++i )
314             {
315                 try
316                 {
317                     MethodInfo methodInfo = methodInfos[i];
318 
319                     if ( !methodInfo.upcast )
320                     {
321                         methodInfo.tryUpcasting( clazz );
322                         upcastCount++;
323                     }
324                 }
325                 catch ( NoSuchMethodException e )
326                 {
327                     /*
328                      * Intentionally ignored - it means it wasn't found in the current class
329                      */
330                 }
331             }
332 
333             /*
334              * Short circuit if all methods were upcast
335              */
336 
337             if ( upcastCount == l )
338             {
339                 return upcastCount;
340             }
341         }
342 
343         /*
344          * Examine superclass
345          */
346 
347         Class superclazz = clazz.getSuperclass();
348 
349         if ( superclazz != null )
350         {
351             upcastCount = getAccessibleMethods( superclazz, methodInfos, upcastCount );
352 
353             /*
354              * Short circuit if all methods were upcast
355              */
356 
357             if ( upcastCount == l )
358             {
359                 return upcastCount;
360             }
361         }
362 
363         /*
364          * Examine interfaces. Note we do it even if superclazz == null. This is redundant as currently java.lang.Object
365          * does not implement any interfaces, however nothing guarantees it will not in future.
366          */
367 
368         Class[] interfaces = clazz.getInterfaces();
369 
370         for ( int i = interfaces.length; i-- > 0; )
371         {
372             upcastCount = getAccessibleMethods( interfaces[i], methodInfos, upcastCount );
373 
374             /*
375              * Short circuit if all methods were upcast
376              */
377 
378             if ( upcastCount == l )
379             {
380                 return upcastCount;
381             }
382         }
383 
384         return upcastCount;
385     }
386 
387     /**
388      * For a given method, retrieves its publicly accessible counterpart. This method will look for a method with same
389      * name and signature declared in a public superclass or implemented interface of this method's declaring class.
390      * This counterpart method is publicly callable.
391      *
392      * @param method a method whose publicly callable counterpart is requested.
393      * @return the publicly callable counterpart method. Note that if the parameter method is itself declared by a
394      *         public class, this method is an identity function.
395      */
396     public static Method getPublicMethod( Method method )
397     {
398         Class clazz = method.getDeclaringClass();
399 
400         /*
401          * Short circuit for (hopefully the majority of) cases where the declaring class is public.
402          */
403 
404         if ( ( clazz.getModifiers() & Modifier.PUBLIC ) != 0 )
405         {
406             return method;
407         }
408 
409         return getPublicMethod( clazz, method.getName(), method.getParameterTypes() );
410     }
411 
412     /**
413      * Looks up the method with specified name and signature in the first public superclass or implemented interface of
414      * the class.
415      *
416      * @param clazz the class whose method is sought
417      * @param name the name of the method
418      * @param paramTypes the classes of method parameters
419      */
420     private static Method getPublicMethod( Class clazz, String name, Class[] paramTypes )
421     {
422         /*
423          * if this class is public, then try to get it
424          */
425 
426         if ( ( clazz.getModifiers() & Modifier.PUBLIC ) != 0 )
427         {
428             try
429             {
430                 return clazz.getMethod( name, paramTypes );
431             }
432             catch ( NoSuchMethodException e )
433             {
434                 /*
435                  * If the class does not have the method, then neither its superclass nor any of its interfaces has it
436                  * so quickly return null.
437                  */
438                 return null;
439             }
440         }
441 
442         /*
443          * try the superclass
444          */
445 
446         Class superclazz = clazz.getSuperclass();
447 
448         if ( superclazz != null )
449         {
450             Method superclazzMethod = getPublicMethod( superclazz, name, paramTypes );
451 
452             if ( superclazzMethod != null )
453             {
454                 return superclazzMethod;
455             }
456         }
457 
458         /*
459          * and interfaces
460          */
461 
462         Class[] interfaces = clazz.getInterfaces();
463 
464         for ( Class anInterface : interfaces )
465         {
466             Method interfaceMethod = getPublicMethod( anInterface, name, paramTypes );
467 
468             if ( interfaceMethod != null )
469             {
470                 return interfaceMethod;
471             }
472         }
473 
474         return null;
475     }
476 
477     /**
478      * Used for the iterative discovery process for public methods.
479      */
480     private static final class MethodInfo
481     {
482         Method method;
483 
484         String name;
485 
486         Class[] parameterTypes;
487 
488         boolean upcast;
489 
490         MethodInfo( Method method )
491         {
492             this.method = null;
493             name = method.getName();
494             parameterTypes = method.getParameterTypes();
495             upcast = false;
496         }
497 
498         void tryUpcasting( Class clazz )
499             throws NoSuchMethodException
500         {
501             method = clazz.getMethod( name, parameterTypes );
502             name = null;
503             parameterTypes = null;
504             upcast = true;
505         }
506     }
507 }