Coverage Report - org.codehaus.plexus.util.introspection.MethodMap
 
Classes in this File Line Coverage Branch Coverage Complexity
MethodMap
36%
39/107
10%
19/173
13.333
MethodMap$AmbiguousException
0%
0/1
N/A
13.333
 
 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.util.ArrayList;
 21  
 import java.util.Hashtable;
 22  
 import java.util.Iterator;
 23  
 import java.util.LinkedList;
 24  
 import java.util.List;
 25  
 import java.util.Map;
 26  
 
 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:Christoph.Reck@dlr.de">Christoph Reck</a>
 31  
  * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
 32  
  * @author <a href="mailto:szegedia@freemail.hu">Attila Szegedi</a>
 33  
  * @version $Id$
 34  
  */
 35  7
 public class MethodMap
 36  
 {
 37  
     private static final int MORE_SPECIFIC = 0;
 38  
 
 39  
     private static final int LESS_SPECIFIC = 1;
 40  
 
 41  
     private static final int INCOMPARABLE = 2;
 42  
 
 43  
     /**
 44  
      * Keep track of all methods with the same name.
 45  
      */
 46  7
     Map<String, List<Method>> methodByNameMap = new Hashtable<String, List<Method>>();
 47  
 
 48  
     /**
 49  
      * Add a method to a list of methods by name. For a particular class we are keeping track of all the methods with
 50  
      * the same name.
 51  
      * 
 52  
      * @param method The method
 53  
      */
 54  
     public void add( Method method )
 55  
     {
 56  286
         String methodName = method.getName();
 57  
 
 58  286
         List<Method> l = get( methodName );
 59  
 
 60  286
         if ( l == null )
 61  
         {
 62  267
             l = new ArrayList<Method>();
 63  267
             methodByNameMap.put( methodName, l );
 64  
         }
 65  
 
 66  286
         l.add( method );
 67  286
     }
 68  
 
 69  
     /**
 70  
      * Return a list of methods with the same name.
 71  
      *
 72  
      * @param key The name of the method.
 73  
      * @return List list of methods
 74  
      */
 75  
     public List<Method> get( String key )
 76  
     {
 77  293
         return methodByNameMap.get( key );
 78  
     }
 79  
 
 80  
     /**
 81  
      * <p>
 82  
      * Find a method. Attempts to find the most specific applicable method using the algorithm described in the JLS
 83  
      * section 15.12.2 (with the exception that it can't distinguish a primitive type argument from an object type
 84  
      * argument, since in reflection primitive type arguments are represented by their object counterparts, so for an
 85  
      * argument of type (say) java.lang.Integer, it will not be able to decide between a method that takes int and a
 86  
      * method that takes java.lang.Integer as a parameter.
 87  
      * </p>
 88  
      * <p>
 89  
      * This turns out to be a relatively rare case where this is needed - however, functionality like this is needed.
 90  
      * </p>
 91  
      *
 92  
      * @param methodName name of method
 93  
      * @param args the actual arguments with which the method is called
 94  
      * @return the most specific applicable method, or null if no method is applicable.
 95  
      * @throws AmbiguousException if there is more than one maximally specific applicable method
 96  
      */
 97  
     public Method find( String methodName, Object[] args )
 98  
         throws AmbiguousException
 99  
     {
 100  7
         List<Method> methodList = get( methodName );
 101  
 
 102  7
         if ( methodList == null )
 103  
         {
 104  6
             return null;
 105  
         }
 106  
 
 107  1
         int l = args.length;
 108  1
         Class[] classes = new Class[l];
 109  
 
 110  2
         for ( int i = 0; i < l; ++i )
 111  
         {
 112  1
             Object arg = args[i];
 113  
 
 114  
             /*
 115  
              * if we are careful down below, a null argument goes in there so we can know that the null was passed to
 116  
              * the method
 117  
              */
 118  1
             classes[i] = arg == null ? null : arg.getClass();
 119  
         }
 120  
 
 121  1
         return getMostSpecific( methodList, classes );
 122  
     }
 123  
 
 124  
     /**
 125  
      * simple distinguishable exception, used when we run across ambiguous overloading
 126  
      */
 127  7
     public static class AmbiguousException
 128  
         extends Exception
 129  
     {
 130  
     }
 131  
 
 132  
     private static Method getMostSpecific( List<Method> methods, Class[] classes )
 133  
         throws AmbiguousException
 134  
     {
 135  1
         LinkedList<Method> applicables = getApplicables( methods, classes );
 136  
 
 137  1
         if ( applicables.isEmpty() )
 138  
         {
 139  0
             return null;
 140  
         }
 141  
 
 142  1
         if ( applicables.size() == 1 )
 143  
         {
 144  1
             return applicables.getFirst();
 145  
         }
 146  
 
 147  
         /*
 148  
          * This list will contain the maximally specific methods. Hopefully at the end of the below loop, the list will
 149  
          * contain exactly one method, (the most specific method) otherwise we have ambiguity.
 150  
          */
 151  
 
 152  0
         LinkedList<Method> maximals = new LinkedList<Method>();
 153  
 
 154  0
         for ( Method app : applicables )
 155  
         {
 156  0
             Class[] appArgs = app.getParameterTypes();
 157  0
             boolean lessSpecific = false;
 158  
 
 159  0
             for ( Iterator<Method> maximal = maximals.iterator(); !lessSpecific && maximal.hasNext(); )
 160  
             {
 161  0
                 Method max = maximal.next();
 162  
 
 163  0
                 switch ( moreSpecific( appArgs, max.getParameterTypes() ) )
 164  
                 {
 165  
                     case MORE_SPECIFIC:
 166  
                     {
 167  
                         /*
 168  
                          * This method is more specific than the previously known maximally specific, so remove the old
 169  
                          * maximum.
 170  
                          */
 171  
 
 172  0
                         maximal.remove();
 173  0
                         break;
 174  
                     }
 175  
 
 176  
                     case LESS_SPECIFIC:
 177  
                     {
 178  
                         /*
 179  
                          * This method is less specific than some of the currently known maximally specific methods, so
 180  
                          * we won't add it into the set of maximally specific methods
 181  
                          */
 182  
 
 183  0
                         lessSpecific = true;
 184  
                         break;
 185  
                     }
 186  
                 }
 187  0
             }
 188  
 
 189  0
             if ( !lessSpecific )
 190  
             {
 191  0
                 maximals.addLast( app );
 192  
             }
 193  0
         }
 194  
 
 195  0
         if ( maximals.size() > 1 )
 196  
         {
 197  
             // We have more than one maximally specific method
 198  0
             throw new AmbiguousException();
 199  
         }
 200  
 
 201  0
         return maximals.getFirst();
 202  
     }
 203  
 
 204  
     /**
 205  
      * Determines which method signature (represented by a class array) is more specific. This defines a partial
 206  
      * ordering on the method signatures.
 207  
      * 
 208  
      * @param c1 first signature to compare
 209  
      * @param c2 second signature to compare
 210  
      * @return MORE_SPECIFIC if c1 is more specific than c2, LESS_SPECIFIC if c1 is less specific than c2, INCOMPARABLE
 211  
      *         if they are incomparable.
 212  
      */
 213  
     private static int moreSpecific( Class[] c1, Class[] c2 )
 214  
     {
 215  0
         boolean c1MoreSpecific = false;
 216  0
         boolean c2MoreSpecific = false;
 217  
 
 218  0
         for ( int i = 0; i < c1.length; ++i )
 219  
         {
 220  0
             if ( c1[i] != c2[i] )
 221  
             {
 222  0
                 c1MoreSpecific = c1MoreSpecific || isStrictMethodInvocationConvertible( c2[i], c1[i] );
 223  0
                 c2MoreSpecific = c2MoreSpecific || isStrictMethodInvocationConvertible( c1[i], c2[i] );
 224  
             }
 225  
         }
 226  
 
 227  0
         if ( c1MoreSpecific )
 228  
         {
 229  0
             if ( c2MoreSpecific )
 230  
             {
 231  
                 /*
 232  
                  * Incomparable due to cross-assignable arguments (i.e. foo(String, Object) vs. foo(Object, String))
 233  
                  */
 234  
 
 235  0
                 return INCOMPARABLE;
 236  
             }
 237  
 
 238  0
             return MORE_SPECIFIC;
 239  
         }
 240  
 
 241  0
         if ( c2MoreSpecific )
 242  
         {
 243  0
             return LESS_SPECIFIC;
 244  
         }
 245  
 
 246  
         /*
 247  
          * Incomparable due to non-related arguments (i.e. foo(Runnable) vs. foo(Serializable))
 248  
          */
 249  
 
 250  0
         return INCOMPARABLE;
 251  
     }
 252  
 
 253  
     /**
 254  
      * Returns all methods that are applicable to actual argument types.
 255  
      * 
 256  
      * @param methods list of all candidate methods
 257  
      * @param classes the actual types of the arguments
 258  
      * @return a list that contains only applicable methods (number of formal and actual arguments matches, and argument
 259  
      *         types are assignable to formal types through a method invocation conversion).
 260  
      */
 261  
     private static LinkedList<Method> getApplicables( List<Method> methods, Class[] classes )
 262  
     {
 263  1
         LinkedList<Method> list = new LinkedList<Method>();
 264  
 
 265  1
         for ( Object method1 : methods )
 266  
         {
 267  1
             Method method = (Method) method1;
 268  
 
 269  1
             if ( isApplicable( method, classes ) )
 270  
             {
 271  1
                 list.add( method );
 272  
             }
 273  
 
 274  1
         }
 275  1
         return list;
 276  
     }
 277  
 
 278  
     /**
 279  
      * Returns true if the supplied method is applicable to actual argument types.
 280  
      * 
 281  
      * @param method The method to check for applicability
 282  
      * @param classes The arguments
 283  
      * @return true if the method applies to the parameter types
 284  
      */
 285  
     private static boolean isApplicable( Method method, Class[] classes )
 286  
     {
 287  1
         Class[] methodArgs = method.getParameterTypes();
 288  
 
 289  1
         if ( methodArgs.length != classes.length )
 290  
         {
 291  0
             return false;
 292  
         }
 293  
 
 294  2
         for ( int i = 0; i < classes.length; ++i )
 295  
         {
 296  1
             if ( !isMethodInvocationConvertible( methodArgs[i], classes[i] ) )
 297  
             {
 298  0
                 return false;
 299  
             }
 300  
         }
 301  
 
 302  1
         return true;
 303  
     }
 304  
 
 305  
     /**
 306  
      * Determines whether a type represented by a class object is convertible to another type represented by a class
 307  
      * object using a method invocation conversion, treating object types of primitive types as if they were primitive
 308  
      * types (that is, a Boolean actual parameter type matches boolean primitive formal type). This behavior is because
 309  
      * this method is used to determine applicable methods for an actual parameter list, and primitive types are
 310  
      * represented by their object duals in reflective method calls.
 311  
      *
 312  
      * @param formal the formal parameter type to which the actual parameter type should be convertible
 313  
      * @param actual the actual parameter type.
 314  
      * @return true if either formal type is assignable from actual type, or formal is a primitive type and actual is
 315  
      *         its corresponding object type or an object type of a primitive type that can be converted to the formal
 316  
      *         type.
 317  
      */
 318  
     private static boolean isMethodInvocationConvertible( Class formal, Class actual )
 319  
     {
 320  
         /*
 321  
          * if it's a null, it means the arg was null
 322  
          */
 323  1
         if ( actual == null && !formal.isPrimitive() )
 324  
         {
 325  0
             return true;
 326  
         }
 327  
 
 328  
         /*
 329  
          * Check for identity or widening reference conversion
 330  
          */
 331  
 
 332  1
         if ( actual != null && formal.isAssignableFrom( actual ) )
 333  
         {
 334  1
             return true;
 335  
         }
 336  
 
 337  
         /*
 338  
          * Check for boxing with widening primitive conversion. Note that actual parameters are never primitives.
 339  
          */
 340  
 
 341  0
         if ( formal.isPrimitive() )
 342  
         {
 343  0
             if ( formal == Boolean.TYPE && actual == Boolean.class )
 344  0
                 return true;
 345  0
             if ( formal == Character.TYPE && actual == Character.class )
 346  0
                 return true;
 347  0
             if ( formal == Byte.TYPE && actual == Byte.class )
 348  0
                 return true;
 349  0
             if ( formal == Short.TYPE && ( actual == Short.class || actual == Byte.class ) )
 350  0
                 return true;
 351  0
             if ( formal == Integer.TYPE
 352  
                 && ( actual == Integer.class || actual == Short.class || actual == Byte.class ) )
 353  0
                 return true;
 354  0
             if ( formal == Long.TYPE && ( actual == Long.class || actual == Integer.class || actual == Short.class
 355  
                 || actual == Byte.class ) )
 356  0
                 return true;
 357  0
             if ( formal == Float.TYPE && ( actual == Float.class || actual == Long.class || actual == Integer.class
 358  
                 || actual == Short.class || actual == Byte.class ) )
 359  0
                 return true;
 360  0
             if ( formal == Double.TYPE && ( actual == Double.class || actual == Float.class || actual == Long.class
 361  
                 || actual == Integer.class || actual == Short.class || actual == Byte.class ) )
 362  0
                 return true;
 363  
         }
 364  
 
 365  0
         return false;
 366  
     }
 367  
 
 368  
     /**
 369  
      * Determines whether a type represented by a class object is convertible to another type represented by a class
 370  
      * object using a method invocation conversion, without matching object and primitive types. This method is used to
 371  
      * determine the more specific type when comparing signatures of methods.
 372  
      *
 373  
      * @param formal the formal parameter type to which the actual parameter type should be convertible
 374  
      * @param actual the actual parameter type.
 375  
      * @return true if either formal type is assignable from actual type, or formal and actual are both primitive types
 376  
      *         and actual can be subject to widening conversion to formal.
 377  
      */
 378  
     private static boolean isStrictMethodInvocationConvertible( Class formal, Class actual )
 379  
     {
 380  
         /*
 381  
          * we shouldn't get a null into, but if so
 382  
          */
 383  0
         if ( actual == null && !formal.isPrimitive() )
 384  
         {
 385  0
             return true;
 386  
         }
 387  
 
 388  
         /*
 389  
          * Check for identity or widening reference conversion
 390  
          */
 391  
 
 392  0
         if ( formal.isAssignableFrom( actual ) )
 393  
         {
 394  0
             return true;
 395  
         }
 396  
 
 397  
         /*
 398  
          * Check for widening primitive conversion.
 399  
          */
 400  
 
 401  0
         if ( formal.isPrimitive() )
 402  
         {
 403  0
             if ( formal == Short.TYPE && ( actual == Byte.TYPE ) )
 404  0
                 return true;
 405  0
             if ( formal == Integer.TYPE && ( actual == Short.TYPE || actual == Byte.TYPE ) )
 406  0
                 return true;
 407  0
             if ( formal == Long.TYPE && ( actual == Integer.TYPE || actual == Short.TYPE || actual == Byte.TYPE ) )
 408  0
                 return true;
 409  0
             if ( formal == Float.TYPE
 410  
                 && ( actual == Long.TYPE || actual == Integer.TYPE || actual == Short.TYPE || actual == Byte.TYPE ) )
 411  0
                 return true;
 412  0
             if ( formal == Double.TYPE && ( actual == Float.TYPE || actual == Long.TYPE || actual == Integer.TYPE
 413  
                 || actual == Short.TYPE || actual == Byte.TYPE ) )
 414  0
                 return true;
 415  
         }
 416  0
         return false;
 417  
     }
 418  
 }