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       */
66      public ClassMap( Class<?> clazz )
67      {
68          this.clazz = clazz;
69          populateMethodCache();
70      }
71  
72      /**
73       * @return the class object whose methods are cached by this map.
74       */
75      Class<?> getCachedClass()
76      {
77          return clazz;
78      }
79  
80      /**
81       * Find a Method using the methodKey
82       * provided.
83       * <p/>
84       * Look in the methodMap for an entry.  If found,
85       * it'll either be a CACHE_MISS, in which case we
86       * simply give up, or it'll be a Method, in which
87       * case, we return it.
88       * <p/>
89       * If nothing is found, then we must actually go
90       * and introspect the method from the MethodMap.
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         /*
144          *  get all publicly accessible methods
145          */
146 
147         Method[] methods = getAccessibleMethods( clazz );
148 
149         /*
150          * map and cache them
151          */
152 
153         for ( Method method : methods )
154         {
155             /*
156              *  now get the 'public method', the method declared by a
157              *  public interface or class. (because the actual implementing
158              *  class may be a facade...
159              */
160 
161             Method publicMethod = getPublicMethod( method );
162 
163             /*
164              *  it is entirely possible that there is no public method for
165              *  the methods of this class (i.e. in the facade, a method
166              *  that isn't on any of the interfaces or superclass
167              *  in which case, ignore it.  Otherwise, map and cache
168              */
169 
170             if ( publicMethod != null )
171             {
172                 methodMap.add( publicMethod );
173                 methodCache.put( makeMethodKey( publicMethod ), publicMethod );
174             }
175         }
176     }
177 
178     /**
179      * Make a methodKey for the given method using
180      * the concatenation of the name and the
181      * types of the method parameters.
182      */
183     private String makeMethodKey( Method method )
184     {
185         Class<?>[] parameterTypes = method.getParameterTypes();
186 
187         StringBuilder methodKey = new StringBuilder( method.getName() );
188 
189         for ( Class<?> parameterType : parameterTypes )
190         {
191             /*
192              * If the argument type is primitive then we want
193              * to convert our primitive type signature to the
194              * corresponding Object type so introspection for
195              * methods with primitive types will work correctly.
196              */
197             if ( parameterType.isPrimitive() )
198             {
199                 if ( parameterType.equals( Boolean.TYPE ) )
200                 {
201                     methodKey.append( "java.lang.Boolean" );
202                 }
203                 else if ( parameterType.equals( Byte.TYPE ) )
204                 {
205                     methodKey.append( "java.lang.Byte" );
206                 }
207                 else if ( parameterType.equals( Character.TYPE ) )
208                 {
209                     methodKey.append( "java.lang.Character" );
210                 }
211                 else if ( parameterType.equals( Double.TYPE ) )
212                 {
213                     methodKey.append( "java.lang.Double" );
214                 }
215                 else if ( parameterType.equals( Float.TYPE ) )
216                 {
217                     methodKey.append( "java.lang.Float" );
218                 }
219                 else if ( parameterType.equals( Integer.TYPE ) )
220                 {
221                     methodKey.append( "java.lang.Integer" );
222                 }
223                 else if ( parameterType.equals( Long.TYPE ) )
224                 {
225                     methodKey.append( "java.lang.Long" );
226                 }
227                 else if ( parameterType.equals( Short.TYPE ) )
228                 {
229                     methodKey.append( "java.lang.Short" );
230                 }
231             }
232             else
233             {
234                 methodKey.append( parameterType.getName() );
235             }
236         }
237 
238         return methodKey.toString();
239     }
240 
241     private static String makeMethodKey( String method, Object... params )
242     {
243         StringBuilder methodKey = new StringBuilder().append( method );
244 
245         for ( Object param : params )
246         {
247             Object arg = param;
248 
249             if ( arg == null )
250             {
251                 arg = OBJECT;
252             }
253 
254             methodKey.append( arg.getClass().getName() );
255         }
256 
257         return methodKey.toString();
258     }
259 
260     /**
261      * Retrieves public methods for a class. In case the class is not
262      * public, retrieves methods with same signature as its public methods
263      * from public superclasses and interfaces (if they exist). Basically
264      * upcasts every method to the nearest acccessible method.
265      */
266     private static Method[] getAccessibleMethods( Class<?> clazz )
267     {
268         Method[] methods = clazz.getMethods();
269 
270         /*
271          *  Short circuit for the (hopefully) majority of cases where the
272          *  clazz is public
273          */
274 
275         if ( Modifier.isPublic( clazz.getModifiers() ) )
276         {
277             return methods;
278         }
279 
280         /*
281          *  No luck - the class is not public, so we're going the longer way.
282          */
283 
284         MethodInfo[] methodInfos = new MethodInfo[methods.length];
285 
286         for ( int i = methods.length; i-- > 0; )
287         {
288             methodInfos[i] = new MethodInfo( methods[i] );
289         }
290 
291         int upcastCount = getAccessibleMethods( clazz, methodInfos, 0 );
292 
293         /*
294          *  Reallocate array in case some method had no accessible counterpart.
295          */
296 
297         if ( upcastCount < methods.length )
298         {
299             methods = new Method[upcastCount];
300         }
301 
302         int j = 0;
303         for ( MethodInfo methodInfo : methodInfos )
304         {
305             if ( methodInfo.upcast )
306             {
307                 methods[j++] = methodInfo.method;
308             }
309         }
310         return methods;
311     }
312 
313     /**
314      * Recursively finds a match for each method, starting with the class, and then
315      * searching the superclass and interfaces.
316      *
317      * @param clazz       Class to check
318      * @param methodInfos array of methods we are searching to match
319      * @param upcastCount current number of methods we have matched
320      * @return count of matched methods
321      */
322     private static int getAccessibleMethods( Class<?> clazz, MethodInfo[] methodInfos, int upcastCount )
323     {
324         int l = methodInfos.length;
325 
326         /*
327          *  if this class is public, then check each of the currently
328          *  'non-upcasted' methods to see if we have a match
329          */
330 
331         if ( Modifier.isPublic( clazz.getModifiers() ) )
332         {
333             for ( int i = 0; i < l && upcastCount < l; ++i )
334             {
335                 try
336                 {
337                     MethodInfo methodInfo = methodInfos[i];
338 
339                     if ( !methodInfo.upcast )
340                     {
341                         methodInfo.tryUpcasting( clazz );
342                         upcastCount++;
343                     }
344                 }
345                 catch ( NoSuchMethodException e )
346                 {
347                     /*
348                      *  Intentionally ignored - it means
349                      *  it wasn't found in the current class
350                      */
351                 }
352             }
353 
354             /*
355              *  Short circuit if all methods were upcast
356              */
357 
358             if ( upcastCount == l )
359             {
360                 return upcastCount;
361             }
362         }
363 
364         /*
365          *   Examine superclass
366          */
367 
368         Class<?> superclazz = clazz.getSuperclass();
369 
370         if ( superclazz != null )
371         {
372             upcastCount = getAccessibleMethods( superclazz, 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         /*
385          *  Examine interfaces. Note we do it even if superclazz == null.
386          *  This is redundant as currently java.lang.Object does not implement
387          *  any interfaces, however nothing guarantees it will not in future.
388          */
389 
390         Class<?>[] interfaces = clazz.getInterfaces();
391 
392         for ( int i = interfaces.length; i-- > 0; )
393         {
394             upcastCount = getAccessibleMethods( interfaces[i], methodInfos, upcastCount );
395 
396             /*
397              *  Short circuit if all methods were upcast
398              */
399 
400             if ( upcastCount == l )
401             {
402                 return upcastCount;
403             }
404         }
405 
406         return upcastCount;
407     }
408 
409     /**
410      * For a given method, retrieves its publicly accessible counterpart.
411      * This method will look for a method with same name
412      * and signature declared in a public superclass or implemented interface of this
413      * method's declaring class. This counterpart method is publicly callable.
414      *
415      * @param method a method whose publicly callable counterpart is requested.
416      * @return the publicly callable counterpart method. Note that if the parameter
417      *         method is itself declared by a public class, this method is an identity
418      *         function.
419      */
420     private static Method getPublicMethod( Method method )
421     {
422         Class<?> clazz = method.getDeclaringClass();
423 
424         /*
425          *   Short circuit for (hopefully the majority of) cases where the declaring
426          *   class is public.
427          */
428 
429         if ( ( clazz.getModifiers() & Modifier.PUBLIC ) != 0 )
430         {
431             return method;
432         }
433 
434         return getPublicMethod( clazz, method.getName(), method.getParameterTypes() );
435     }
436 
437     /**
438      * Looks up the method with specified name and signature in the first public
439      * superclass or implemented interface of the class.
440      *
441      * @param clazz      the class whose method is sought
442      * @param name       the name of the method
443      * @param paramTypes the classes of method parameters
444      */
445     private static Method getPublicMethod( Class<?> clazz, String name, Class<?>... paramTypes )
446     {
447         /*
448          *  if this class is public, then try to get it
449          */
450 
451         if ( ( clazz.getModifiers() & Modifier.PUBLIC ) != 0 )
452         {
453             try
454             {
455                 return clazz.getMethod( name, paramTypes );
456             }
457             catch ( NoSuchMethodException e )
458             {
459                 /*
460                  *  If the class does not have the method, then neither its
461                  *  superclass nor any of its interfaces has it so quickly return
462                  *  null.
463                  */
464                 return null;
465             }
466         }
467 
468         /*
469          *  try the superclass
470          */
471 
472         Class<?> superclazz = clazz.getSuperclass();
473 
474         if ( superclazz != null )
475         {
476             Method superclazzMethod = getPublicMethod( superclazz, name, paramTypes );
477 
478             if ( superclazzMethod != null )
479             {
480                 return superclazzMethod;
481             }
482         }
483 
484         /*
485          *  and interfaces
486          */
487 
488         Class<?>[] interfaces = clazz.getInterfaces();
489 
490         for ( Class<?> anInterface : interfaces )
491         {
492             Method interfaceMethod = getPublicMethod( anInterface, name, paramTypes );
493 
494             if ( interfaceMethod != null )
495             {
496                 return interfaceMethod;
497             }
498         }
499 
500         return null;
501     }
502 
503     /**
504      * Used for the iterative discovery process for public methods.
505      */
506     private static final class MethodInfo
507     {
508         Method method;
509 
510         String name;
511 
512         Class<?>[] parameterTypes;
513 
514         boolean upcast;
515 
516         MethodInfo( Method method )
517         {
518             this.method = null;
519             name = method.getName();
520             parameterTypes = method.getParameterTypes();
521             upcast = false;
522         }
523 
524         void tryUpcasting( Class<?> clazz )
525             throws NoSuchMethodException
526         {
527             method = clazz.getMethod( name, parameterTypes );
528             name = null;
529             parameterTypes = null;
530             upcast = true;
531         }
532     }
533 }