View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.commons.jexl.util.introspection;
19  
20  import java.lang.reflect.Method;
21  import java.lang.reflect.Modifier;
22  import java.util.ArrayList;
23  import java.util.HashMap;
24  import java.util.Iterator;
25  import java.util.List;
26  import java.util.Map;
27  
28  import org.apache.commons.logging.Log;
29  
30  
31  /**
32   * Taken from the Velocity tree so we can be self-sufficient
33   *
34   * A cache of introspection information for a specific class instance. Keys
35   * {@link Method} objects by a concatenation of the method name and
36   * the names of classes that make up the parameters.
37   *
38   * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
39   * @author <a href="mailto:bob@werken.com">Bob McWhirter</a>
40   * @author <a href="mailto:szegedia@freemail.hu">Attila Szegedi</a>
41   * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
42   * @author <a href="mailto:henning@apache.org">Henning P. Schmiedehausen</a>
43   * @version $Id: ClassMap.java 584046 2007-10-12 05:14:37Z proyal $
44   * @since 1.0
45   */
46  public class ClassMap {
47      /**
48       * Class passed into the constructor used to as the basis for the Method
49       * map.
50       */
51  
52      private final Class clazz;
53      private final Log rlog;
54  
55      private final MethodCache methodCache;
56  
57      /**
58       * Standard constructor.
59       *
60       * @param aClass the class to deconstruct.
61       */
62      public ClassMap(Class aClass, Log rlog) {
63          clazz = aClass;
64          this.rlog = rlog;
65          methodCache = new MethodCache();
66  
67          populateMethodCache();
68      }
69  
70      /**
71       * @return the class object whose methods are cached by this map.
72       */
73      Class getCachedClass() {
74          return clazz;
75      }
76  
77      /**
78       * Find a Method using the method name and parameter objects.
79       *
80       * @param name   The method name to look up.
81       * @param params An array of parameters for the method.
82       * @return A Method object representing the method to invoke or null.
83       * @throws MethodMap.AmbiguousException When more than one method is a match for the parameters.
84       */
85      public Method findMethod(final String name, final Object[] params)
86              throws MethodMap.AmbiguousException {
87          return methodCache.get(name, params);
88      }
89  
90      /**
91       * Populate the Map of direct hits. These
92       * are taken from all the public methods
93       * that our class, its parents and their implemented interfaces provide.
94       */
95      private void populateMethodCache() {
96          //
97          // Build a list of all elements in the class hierarchy. This one is bottom-first (i.e. we start
98          // with the actual declaring class and its interfaces and then move up (superclass etc.) until we
99          // hit java.lang.Object. That is important because it will give us the methods of the declaring class
100         // which might in turn be abstract further up the tree.
101         //
102         // We also ignore all SecurityExceptions that might happen due to SecurityManager restrictions (prominently
103         // hit with Tomcat 5.5).
104         //
105         // We can also omit all that complicated getPublic, getAccessible and upcast logic that the class map had up
106         // until Velocity 1.4. As we always reflect all elements of the tree (that's what we have a cache for), we will
107         // hit the public elements sooner or later because we reflect all the public elements anyway.
108         //
109         List classesToReflect = new ArrayList();
110 
111         // Ah, the miracles of Java for(;;) ...
112         for (Class classToReflect = getCachedClass(); classToReflect != null; classToReflect = classToReflect.getSuperclass())
113         {
114             if (Modifier.isPublic(classToReflect.getModifiers())) {
115                 classesToReflect.add(classToReflect);
116             }
117             Class[] interfaces = classToReflect.getInterfaces();
118             for (int i = 0; i < interfaces.length; i++) {
119                 if (Modifier.isPublic(interfaces[i].getModifiers())) {
120                     classesToReflect.add(interfaces[i]);
121                 }
122             }
123         }
124 
125         for (Iterator it = classesToReflect.iterator(); it.hasNext();) {
126             Class classToReflect = (Class) it.next();
127 
128             try {
129                 Method[] methods = classToReflect.getMethods();
130 
131                 for (int i = 0; i < methods.length; i++) {
132                     // Strictly spoken that check shouldn't be necessary
133                     // because getMethods only returns public methods.
134                     int modifiers = methods[i].getModifiers();
135                     if (Modifier.isPublic(modifiers)) //  && !)
136                     {
137                         // Some of the interfaces contain abstract methods. That is fine, because the actual object must
138                         // implement them anyway (else it wouldn't be implementing the interface). If we find an abstract
139                         // method in a non-interface, we skip it, because we do want to make sure that no abstract methods end up in
140                         // the cache.
141                         if (classToReflect.isInterface() || !Modifier.isAbstract(modifiers)) {
142                             methodCache.put(methods[i]);
143                         }
144                     }
145                 }
146             }
147             catch (SecurityException se) // Everybody feels better with...
148             {
149                 if (rlog.isDebugEnabled()) {
150                     rlog.debug("While accessing methods of " + classToReflect + ": ", se);
151                 }
152             }
153         }
154     }
155 
156     /**
157      * This is the cache to store and look up the method information.
158      *
159      * @author <a href="mailto:henning@apache.org">Henning P. Schmiedehausen</a>
160      * @version $Id: ClassMap.java 584046 2007-10-12 05:14:37Z proyal $
161      */
162     private static final class MethodCache {
163         private static final class CacheMiss {
164         }
165 
166         private static final CacheMiss CACHE_MISS = new CacheMiss();
167 
168         private static final Object OBJECT = new Object();
169 
170         private static final Map convertPrimitives = new HashMap();
171 
172         static {
173             convertPrimitives.put(Boolean.TYPE, Boolean.class.getName());
174             convertPrimitives.put(Byte.TYPE, Byte.class.getName());
175             convertPrimitives.put(Character.TYPE, Character.class.getName());
176             convertPrimitives.put(Double.TYPE, Double.class.getName());
177             convertPrimitives.put(Float.TYPE, Float.class.getName());
178             convertPrimitives.put(Integer.TYPE, Integer.class.getName());
179             convertPrimitives.put(Long.TYPE, Long.class.getName());
180             convertPrimitives.put(Short.TYPE, Short.class.getName());
181         }
182 
183         /**
184          * Cache of Methods, or CACHE_MISS, keyed by method
185          * name and actual arguments used to find it.
186          */
187         private final Map cache = new HashMap();
188 
189         /**
190          * Map of methods that are searchable according to method parameters to find a match
191          */
192         private final MethodMap methodMap = new MethodMap();
193 
194         /**
195          * Find a Method using the method name and parameter objects.
196          *
197          * Look in the methodMap for an entry.  If found,
198          * it'll either be a CACHE_MISS, in which case we
199          * simply give up, or it'll be a Method, in which
200          * case, we return it.
201          *
202          * If nothing is found, then we must actually go
203          * and introspect the method from the MethodMap.
204          *
205          * @param name   The method name to look up.
206          * @param params An array of parameters for the method.
207          * @return A Method object representing the method to invoke or null.
208          * @throws MethodMap.AmbiguousException When more than one method is a match for the parameters.
209          */
210         public synchronized Method get(final String name, final Object[] params)
211                 throws MethodMap.AmbiguousException {
212             String methodKey = makeMethodKey(name, params);
213 
214             Object cacheEntry = cache.get(methodKey);
215 
216             // We looked this up before and failed.
217             if (cacheEntry == CACHE_MISS) {
218                 return null;
219             }
220 
221             if (cacheEntry == null) {
222                 try {
223                     // That one is expensive...
224                     cacheEntry = methodMap.find(name, params);
225                 }
226                 catch (MethodMap.AmbiguousException ae) {
227                     /*
228                      *  that's a miss :-)
229                      */
230                     cache.put(methodKey, CACHE_MISS);
231                     throw ae;
232                 }
233 
234                 cache.put(methodKey,
235                         (cacheEntry != null) ? cacheEntry : CACHE_MISS);
236             }
237 
238             // Yes, this might just be null.
239 
240             return (Method) cacheEntry;
241         }
242 
243         public synchronized void put(Method method) {
244             String methodKey = makeMethodKey(method);
245 
246             // We don't overwrite methods. Especially not if we fill the
247             // cache from defined class towards java.lang.Object because
248             // abstract methods in superclasses would else overwrite concrete
249             // classes further down the hierarchy.
250             if (cache.get(methodKey) == null) {
251                 cache.put(methodKey, method);
252                 methodMap.add(method);
253             }
254         }
255 
256         /**
257          * Make a methodKey for the given method using
258          * the concatenation of the name and the
259          * types of the method parameters.
260          *
261          * @param method to be stored as key
262          * @return key for ClassMap
263          */
264         private String makeMethodKey(final Method method) {
265             Class[] parameterTypes = method.getParameterTypes();
266 
267             StringBuffer methodKey = new StringBuffer(method.getName());
268 
269             for (int j = 0; j < parameterTypes.length; j++) {
270                 /*
271                  * If the argument type is primitive then we want
272                  * to convert our primitive type signature to the
273                  * corresponding Object type so introspection for
274                  * methods with primitive types will work correctly.
275                  *
276                  * The lookup map (convertPrimitives) contains all eight
277                  * primitives (boolean, byte, char, double, float, int, long, short)
278                  * known to Java. So it should never return null for the key passed in.
279                  */
280                 if (parameterTypes[j].isPrimitive()) {
281                     methodKey.append((String) convertPrimitives.get(parameterTypes[j]));
282                 } else {
283                     methodKey.append(parameterTypes[j].getName());
284                 }
285             }
286 
287             return methodKey.toString();
288         }
289 
290         private String makeMethodKey(String method, Object[] params) {
291             StringBuffer methodKey = new StringBuffer().append(method);
292 
293             for (int j = 0; j < params.length; j++) {
294                 Object arg = params[j];
295 
296                 if (arg == null) {
297                     arg = OBJECT;
298                 }
299 
300                 methodKey.append(arg.getClass().getName());
301             }
302 
303             return methodKey.toString();
304         }
305     }
306 }