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