View Javadoc

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