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