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