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