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