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