View Javadoc

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