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   *
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       * <p>Find a Method using the methodKey provided.</p>
83       * <p>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.</p>
87       * <p>If nothing is found, then we must actually go
88       * and introspect the method from the MethodMap.</p>
89       * @param name Method name.
90       * @param params Method parameters.
91       * @return The found method.
92       * @throws MethodMap.AmbiguousException in case of duplicate methods.
93       */
94      public Method findMethod( String name, Object... params )
95          throws MethodMap.AmbiguousException
96      {
97          String methodKey = makeMethodKey( name, params );
98          Object cacheEntry = methodCache.get( methodKey );
99  
100         if ( cacheEntry == CACHE_MISS )
101         {
102             return null;
103         }
104 
105         if ( cacheEntry == null )
106         {
107             try
108             {
109                 cacheEntry = methodMap.find( name, params );
110             }
111             catch ( MethodMap.AmbiguousException ae )
112             {
113                 /*
114                  *  that's a miss :)
115                  */
116 
117                 methodCache.put( methodKey, CACHE_MISS );
118 
119                 throw ae;
120             }
121 
122             if ( cacheEntry == null )
123             {
124                 methodCache.put( methodKey, CACHE_MISS );
125             }
126             else
127             {
128                 methodCache.put( methodKey, cacheEntry );
129             }
130         }
131 
132         // Yes, this might just be null.
133 
134         return (Method) cacheEntry;
135     }
136 
137     /**
138      * Populate the Map of direct hits. These
139      * are taken from all the public methods
140      * that our class provides.
141      */
142     private void populateMethodCache()
143     {
144 
145         /*
146          *  get all publicly accessible methods
147          */
148 
149         Method[] methods = getAccessibleMethods( clazz );
150 
151         /*
152          * map and cache them
153          */
154 
155         for ( Method method : methods )
156         {
157             /*
158              *  now get the 'public method', the method declared by a
159              *  public interface or class. (because the actual implementing
160              *  class may be a facade...
161              */
162 
163             Method publicMethod = getPublicMethod( method );
164 
165             /*
166              *  it is entirely possible that there is no public method for
167              *  the methods of this class (i.e. in the facade, a method
168              *  that isn't on any of the interfaces or superclass
169              *  in which case, ignore it.  Otherwise, map and cache
170              */
171 
172             if ( publicMethod != null )
173             {
174                 methodMap.add( publicMethod );
175                 methodCache.put( makeMethodKey( publicMethod ), publicMethod );
176             }
177         }
178     }
179 
180     /**
181      * Make a methodKey for the given method using
182      * the concatenation of the name and the
183      * types of the method parameters.
184      */
185     private String makeMethodKey( Method method )
186     {
187         Class<?>[] parameterTypes = method.getParameterTypes();
188 
189         StringBuilder methodKey = new StringBuilder( method.getName() );
190 
191         for ( Class<?> parameterType : parameterTypes )
192         {
193             /*
194              * If the argument type is primitive then we want
195              * to convert our primitive type signature to the
196              * corresponding Object type so introspection for
197              * methods with primitive types will work correctly.
198              */
199             if ( parameterType.isPrimitive() )
200             {
201                 if ( parameterType.equals( Boolean.TYPE ) )
202                 {
203                     methodKey.append( "java.lang.Boolean" );
204                 }
205                 else if ( parameterType.equals( Byte.TYPE ) )
206                 {
207                     methodKey.append( "java.lang.Byte" );
208                 }
209                 else if ( parameterType.equals( Character.TYPE ) )
210                 {
211                     methodKey.append( "java.lang.Character" );
212                 }
213                 else if ( parameterType.equals( Double.TYPE ) )
214                 {
215                     methodKey.append( "java.lang.Double" );
216                 }
217                 else if ( parameterType.equals( Float.TYPE ) )
218                 {
219                     methodKey.append( "java.lang.Float" );
220                 }
221                 else if ( parameterType.equals( Integer.TYPE ) )
222                 {
223                     methodKey.append( "java.lang.Integer" );
224                 }
225                 else if ( parameterType.equals( Long.TYPE ) )
226                 {
227                     methodKey.append( "java.lang.Long" );
228                 }
229                 else if ( parameterType.equals( Short.TYPE ) )
230                 {
231                     methodKey.append( "java.lang.Short" );
232                 }
233             }
234             else
235             {
236                 methodKey.append( parameterType.getName() );
237             }
238         }
239 
240         return methodKey.toString();
241     }
242 
243     private static String makeMethodKey( String method, Object... params )
244     {
245         StringBuilder methodKey = new StringBuilder().append( method );
246 
247         for ( Object param : params )
248         {
249             Object arg = param;
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     private 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 clazz      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         Class<?> superclazz = clazz.getSuperclass();
475 
476         if ( superclazz != null )
477         {
478             Method superclazzMethod = getPublicMethod( superclazz, name, paramTypes );
479 
480             if ( superclazzMethod != null )
481             {
482                 return superclazzMethod;
483             }
484         }
485 
486         /*
487          *  and interfaces
488          */
489 
490         Class<?>[] interfaces = clazz.getInterfaces();
491 
492         for ( Class<?> anInterface : interfaces )
493         {
494             Method interfaceMethod = getPublicMethod( anInterface, name, paramTypes );
495 
496             if ( interfaceMethod != null )
497             {
498                 return interfaceMethod;
499             }
500         }
501 
502         return null;
503     }
504 
505     /**
506      * Used for the iterative discovery process for public methods.
507      */
508     private static final class MethodInfo
509     {
510         Method method;
511 
512         String name;
513 
514         Class<?>[] parameterTypes;
515 
516         boolean upcast;
517 
518         MethodInfo( Method method )
519         {
520             this.method = null;
521             name = method.getName();
522             parameterTypes = method.getParameterTypes();
523             upcast = false;
524         }
525 
526         void tryUpcasting( Class<?> clazz )
527             throws NoSuchMethodException
528         {
529             method = clazz.getMethod( name, parameterTypes );
530             name = null;
531             parameterTypes = null;
532             upcast = true;
533         }
534     }
535 }