Coverage Report - org.codehaus.plexus.interpolation.reflection.ClassMap
 
Classes in this File Line Coverage Branch Coverage Complexity
ClassMap
51%
59/114
43%
33/76
5.417
ClassMap$1
N/A
N/A
5.417
ClassMap$CacheMiss
100%
1/1
N/A
5.417
ClassMap$MethodInfo
0%
0/11
N/A
5.417
 
 1  
 package org.codehaus.plexus.interpolation.reflection;
 2  
 
 3  
 /* ====================================================================
 4  
  *   Copyright 2001-2004 The Apache Software 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  
 
 20  
 import java.lang.reflect.Method;
 21  
 import java.lang.reflect.Modifier;
 22  
 import java.util.Hashtable;
 23  
 import java.util.Map;
 24  
 
 25  
 /**
 26  
  * <b>NOTE:</b> This class was copied from plexus-utils, to allow this library
 27  
  * to stand completely self-contained.
 28  
  * <p>A cache of introspection information for a specific class instance.
 29  
  * Keys {@link Method} objects by a concatenation of the
 30  
  * method name and the names of classes that make up the parameters.</p>
 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  
 public class ClassMap
 38  
 {
 39  2
     private static final class CacheMiss
 40  
     {
 41  
     }
 42  
 
 43  1
     private static final CacheMiss CACHE_MISS = new CacheMiss();
 44  1
     private static final Object OBJECT = new Object();
 45  
 
 46  
     /**
 47  
      * Class passed into the constructor used to as
 48  
      * the basis for the Method map.
 49  
      */
 50  
     private Class<?> clazz;
 51  
 
 52  
     /**
 53  
      * Cache of Methods, or CACHE_MISS, keyed by method
 54  
      * name and actual arguments used to find it.
 55  
      */
 56  9
     private Map<String, Object> methodCache = new Hashtable<String, Object>();
 57  
 
 58  9
     private MethodMap methodMap = new MethodMap();
 59  
 
 60  
     /**
 61  
      * Standard constructor
 62  
      * @param clazz The class to be analyzed.
 63  
      */
 64  
     public ClassMap( Class<?> clazz )
 65  9
     {
 66  9
         this.clazz = clazz;
 67  9
         populateMethodCache();
 68  9
     }
 69  
 
 70  
     /**
 71  
      * @return the class object whose methods are cached by this map.
 72  
      */
 73  
     Class<?> getCachedClass()
 74  
     {
 75  0
         return clazz;
 76  
     }
 77  
 
 78  
     /**
 79  
      * <p>Find a Method using the methodKey
 80  
      * provided.</p>
 81  
      * <p>Look in the methodMap for an entry.  If found,
 82  
      * it'll either be a CACHE_MISS, in which case we
 83  
      * simply give up, or it'll be a Method, in which
 84  
      * case, we return it.</p>
 85  
      * <p>If nothing is found, then we must actually go
 86  
      * and introspect the method from the MethodMap.</p>
 87  
      * @param name name of the method.
 88  
      * @param params The parameters for the method.
 89  
      * @return {@link Method}.
 90  
      * @throws MethodMap.AmbiguousException in case of an error.
 91  
      */
 92  
     public Method findMethod( String name, Object[] params )
 93  
         throws MethodMap.AmbiguousException
 94  
     {
 95  19
         String methodKey = makeMethodKey( name, params );
 96  19
         Object cacheEntry = methodCache.get( methodKey );
 97  
 
 98  19
         if ( cacheEntry == CACHE_MISS )
 99  
         {
 100  0
             return null;
 101  
         }
 102  
 
 103  19
         if ( cacheEntry == null )
 104  
         {
 105  
             try
 106  
             {
 107  2
                 cacheEntry = methodMap.find( name, params );
 108  
             }
 109  0
             catch ( MethodMap.AmbiguousException ae )
 110  
             {
 111  
                 /*
 112  
                  *  that's a miss :)
 113  
                  */
 114  
 
 115  0
                 methodCache.put( methodKey, CACHE_MISS );
 116  
 
 117  0
                 throw ae;
 118  2
             }
 119  
 
 120  2
             if ( cacheEntry == null )
 121  
             {
 122  0
                 methodCache.put( methodKey, CACHE_MISS );
 123  
             }
 124  
             else
 125  
             {
 126  2
                 methodCache.put( methodKey, cacheEntry );
 127  
             }
 128  
         }
 129  
 
 130  
         // Yes, this might just be null.
 131  
 
 132  19
         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  
          *  get all publicly accessible methods
 144  
          */
 145  
 
 146  9
         Method[] methods = getAccessibleMethods( clazz );
 147  
 
 148  
         /*
 149  
          * map and cache them
 150  
          */
 151  
 
 152  368
         for ( Method method : methods )
 153  
         {
 154  
             /*
 155  
              *  now get the 'public method', the method declared by a
 156  
              *  public interface or class. (because the actual implementing
 157  
              *  class may be a facade...
 158  
              */
 159  
 
 160  359
             Method publicMethod = getPublicMethod( method );
 161  
 
 162  
             /*
 163  
              *  it is entirely possible that there is no public method for
 164  
              *  the methods of this class (i.e. in the facade, a method
 165  
              *  that isn't on any of the interfaces or superclass
 166  
              *  in which case, ignore it.  Otherwise, map and cache
 167  
              */
 168  
 
 169  359
             if ( publicMethod != null )
 170  
             {
 171  359
                 methodMap.add( publicMethod );
 172  359
                 methodCache.put( makeMethodKey( publicMethod ), publicMethod );
 173  
             }
 174  
         }
 175  9
     }
 176  
 
 177  
     /**
 178  
      * Make a methodKey for the given method using
 179  
      * the concatenation of the name and the
 180  
      * types of the method parameters.
 181  
      */
 182  
     private String makeMethodKey( Method method )
 183  
     {
 184  359
         Class<?>[] parameterTypes = method.getParameterTypes();
 185  
 
 186  359
         StringBuilder methodKey = new StringBuilder( method.getName() );
 187  
 
 188  683
         for ( Class<?> parameterType : parameterTypes )
 189  
         {
 190  
             /*
 191  
              * If the argument type is primitive then we want
 192  
              * to convert our primitive type signature to the
 193  
              * corresponding Object type so introspection for
 194  
              * methods with primitive types will work correctly.
 195  
              */
 196  324
             if ( parameterType.isPrimitive() )
 197  
             {
 198  125
                 if ( parameterType.equals( Boolean.TYPE ) )
 199  
                 {
 200  16
                     methodKey.append( "java.lang.Boolean" );
 201  
                 }
 202  109
                 else if ( parameterType.equals( Byte.TYPE ) )
 203  
                 {
 204  8
                     methodKey.append( "java.lang.Byte" );
 205  
                 }
 206  101
                 else if ( parameterType.equals( Character.TYPE ) )
 207  
                 {
 208  8
                     methodKey.append( "java.lang.Character" );
 209  
                 }
 210  93
                 else if ( parameterType.equals( Double.TYPE ) )
 211  
                 {
 212  12
                     methodKey.append( "java.lang.Double" );
 213  
                 }
 214  81
                 else if ( parameterType.equals( Float.TYPE ) )
 215  
                 {
 216  12
                     methodKey.append( "java.lang.Float" );
 217  
                 }
 218  69
                 else if ( parameterType.equals( Integer.TYPE ) )
 219  
                 {
 220  35
                     methodKey.append( "java.lang.Integer" );
 221  
                 }
 222  34
                 else if ( parameterType.equals( Long.TYPE ) )
 223  
                 {
 224  26
                     methodKey.append( "java.lang.Long" );
 225  
                 }
 226  8
                 else if ( parameterType.equals( Short.TYPE ) )
 227  
                 {
 228  8
                     methodKey.append( "java.lang.Short" );
 229  
                 }
 230  
             }
 231  
             else
 232  
             {
 233  199
                 methodKey.append( parameterType.getName() );
 234  
             }
 235  
         }
 236  
 
 237  359
         return methodKey.toString();
 238  
     }
 239  
 
 240  
     private static String makeMethodKey( String method, Object[] params )
 241  
     {
 242  19
         if (params.length == 0)
 243  
         {
 244  15
             return method;
 245  
         }
 246  
 
 247  4
         StringBuilder methodKey = new StringBuilder().append( method );
 248  
 
 249  8
         for ( Object arg : params )
 250  
         {
 251  4
             if ( arg == null )
 252  
             {
 253  0
                 arg = OBJECT;
 254  
             }
 255  
 
 256  4
             methodKey.append( arg.getClass().getName() );
 257  
         }
 258  
 
 259  4
         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  9
         Method[] methods = clazz.getMethods();
 271  
 
 272  
         /*
 273  
          *  Short circuit for the (hopefully) majority of cases where the
 274  
          *  clazz is public
 275  
          */
 276  
 
 277  9
         if ( Modifier.isPublic( clazz.getModifiers() ) )
 278  
         {
 279  9
             return methods;
 280  
         }
 281  
 
 282  
         /*
 283  
          *  No luck - the class is not public, so we're going the longer way.
 284  
          */
 285  
 
 286  0
         MethodInfo[] methodInfos = new MethodInfo[methods.length];
 287  
 
 288  0
         for ( int i = methods.length; i-- > 0; )
 289  
         {
 290  0
             methodInfos[i] = new MethodInfo( methods[i] );
 291  
         }
 292  
 
 293  0
         int upcastCount = getAccessibleMethods( clazz, methodInfos, 0 );
 294  
 
 295  
         /*
 296  
          *  Reallocate array in case some method had no accessible counterpart.
 297  
          */
 298  
 
 299  0
         if ( upcastCount < methods.length )
 300  
         {
 301  0
             methods = new Method[upcastCount];
 302  
         }
 303  
 
 304  0
         int j = 0;
 305  0
         for ( MethodInfo methodInfo : methodInfos )
 306  
         {
 307  0
             if ( methodInfo.upcast )
 308  
             {
 309  0
                 methods[j++] = methodInfo.method;
 310  
             }
 311  
         }
 312  0
         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  0
         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  0
         if ( Modifier.isPublic( clazz.getModifiers() ) )
 334  
         {
 335  0
             for ( int i = 0; i < l && upcastCount < l; ++i )
 336  
             {
 337  
                 try
 338  
                 {
 339  0
                     MethodInfo methodInfo = methodInfos[i];
 340  
 
 341  0
                     if ( !methodInfo.upcast )
 342  
                     {
 343  0
                         methodInfo.tryUpcasting( clazz );
 344  0
                         upcastCount++;
 345  
                     }
 346  
                 }
 347  0
                 catch ( NoSuchMethodException e )
 348  
                 {
 349  
                     /*
 350  
                      *  Intentionally ignored - it means
 351  
                      *  it wasn't found in the current class
 352  
                      */
 353  0
                 }
 354  
             }
 355  
 
 356  
             /*
 357  
              *  Short circuit if all methods were upcast
 358  
              */
 359  
 
 360  0
             if ( upcastCount == l )
 361  
             {
 362  0
                 return upcastCount;
 363  
             }
 364  
         }
 365  
 
 366  
         /*
 367  
          *   Examine superclass
 368  
          */
 369  
 
 370  0
         Class<?> superclazz = clazz.getSuperclass();
 371  
 
 372  0
         if ( superclazz != null )
 373  
         {
 374  0
             upcastCount = getAccessibleMethods( superclazz, methodInfos, upcastCount );
 375  
 
 376  
             /*
 377  
              *  Short circuit if all methods were upcast
 378  
              */
 379  
 
 380  0
             if ( upcastCount == l )
 381  
             {
 382  0
                 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  0
         Class<?>[] interfaces = clazz.getInterfaces();
 393  
 
 394  0
         for ( int i = interfaces.length; i-- > 0; )
 395  
         {
 396  0
             upcastCount = getAccessibleMethods( interfaces[i], methodInfos, upcastCount );
 397  
 
 398  
             /*
 399  
              *  Short circuit if all methods were upcast
 400  
              */
 401  
 
 402  0
             if ( upcastCount == l )
 403  
             {
 404  0
                 return upcastCount;
 405  
             }
 406  
         }
 407  
 
 408  0
         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  
     public static Method getPublicMethod( Method method )
 423  
     {
 424  359
         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  359
         if ( ( clazz.getModifiers() & Modifier.PUBLIC ) != 0 )
 432  
         {
 433  359
             return method;
 434  
         }
 435  
 
 436  0
         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 class      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  0
         if ( ( clazz.getModifiers() & Modifier.PUBLIC ) != 0 )
 454  
         {
 455  
             try
 456  
             {
 457  0
                 return clazz.getMethod( name, paramTypes );
 458  
             }
 459  0
             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  0
                 return null;
 467  
             }
 468  
         }
 469  
 
 470  
         /*
 471  
          *  try the superclass
 472  
          */
 473  
 
 474  
 
 475  0
         Class<?> superclazz = clazz.getSuperclass();
 476  
 
 477  0
         if ( superclazz != null )
 478  
         {
 479  0
             Method superclazzMethod = getPublicMethod( superclazz, name, paramTypes );
 480  
 
 481  0
             if ( superclazzMethod != null )
 482  
             {
 483  0
                 return superclazzMethod;
 484  
             }
 485  
         }
 486  
 
 487  
         /*
 488  
          *  and interfaces
 489  
          */
 490  
 
 491  0
         for ( Class<?> interface_ : clazz.getInterfaces() )
 492  
         {
 493  0
             Method interfaceMethod = getPublicMethod( interface_, name, paramTypes );
 494  
 
 495  0
             if ( interfaceMethod != null )
 496  
             {
 497  0
                 return interfaceMethod;
 498  
             }
 499  
         }
 500  
 
 501  0
         return null;
 502  
     }
 503  
 
 504  
     /**
 505  
      * Used for the iterative discovery process for public methods.
 506  
      */
 507  
     private static final class MethodInfo
 508  
     {
 509  
         Method method;
 510  
         String name;
 511  
         Class<?>[] parameterTypes;
 512  
         boolean upcast;
 513  
 
 514  
         MethodInfo( Method method )
 515  0
         {
 516  0
             this.method = null;
 517  0
             name = method.getName();
 518  0
             parameterTypes = method.getParameterTypes();
 519  0
             upcast = false;
 520  0
         }
 521  
 
 522  
         void tryUpcasting( Class<?> clazz )
 523  
             throws NoSuchMethodException
 524  
         {
 525  0
             method = clazz.getMethod( name, parameterTypes );
 526  0
             name = null;
 527  0
             parameterTypes = null;
 528  0
             upcast = true;
 529  0
         }
 530  
     }
 531  
 }