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