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