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