You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by di...@apache.org on 2007/10/28 09:08:10 UTC

svn commit: r589292 - /commons/proper/jexl/branches/2.0/src/java/org/apache/commons/jexl/util/introspection/ClassMap.java

Author: dion
Date: Sun Oct 28 01:08:09 2007
New Revision: 589292

URL: http://svn.apache.org/viewvc?rev=589292&view=rev
Log:
JEXL-25 bring across code from Peters update

Modified:
    commons/proper/jexl/branches/2.0/src/java/org/apache/commons/jexl/util/introspection/ClassMap.java

Modified: commons/proper/jexl/branches/2.0/src/java/org/apache/commons/jexl/util/introspection/ClassMap.java
URL: http://svn.apache.org/viewvc/commons/proper/jexl/branches/2.0/src/java/org/apache/commons/jexl/util/introspection/ClassMap.java?rev=589292&r1=589291&r2=589292&view=diff
==============================================================================
--- commons/proper/jexl/branches/2.0/src/java/org/apache/commons/jexl/util/introspection/ClassMap.java (original)
+++ commons/proper/jexl/branches/2.0/src/java/org/apache/commons/jexl/util/introspection/ClassMap.java Sun Oct 28 01:08:09 2007
@@ -19,56 +19,51 @@
 
 import java.lang.reflect.Method;
 import java.lang.reflect.Modifier;
-import java.util.Hashtable;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
 import java.util.Map;
 
+import org.apache.commons.logging.Log;
+
+
 /**
  * Taken from the Velocity tree so we can be self-sufficient
- * 
+ *
  * A cache of introspection information for a specific class instance. Keys
- * {@link java.lang.Method} objects by a concatenation of the method name and
+ * {@link Method} objects by a concatenation of the method name and
  * the names of classes that make up the parameters.
- * 
- * @since 1.0
+ *
  * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
  * @author <a href="mailto:bob@werken.com">Bob McWhirter</a>
  * @author <a href="mailto:szegedia@freemail.hu">Attila Szegedi</a>
  * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
+ * @author <a href="mailto:henning@apache.org">Henning P. Schmiedehausen</a>
  * @version $Id$
+ * @since 1.0
  */
 public class ClassMap {
-    /** represents a miss on the cached data. */
-    private static final class CacheMiss {
-    }
-
-    /** constant for a miss on the cached data. */
-    private static final CacheMiss CACHE_MISS = new CacheMiss();
-
-    /** represents null or missing arguments. */
-    private static final Object OBJECT = new Object();
-
     /**
-     * Class passed into the constructor used to as the basis for the Method
-     * map.
+     * Class passed into the constructor used to as the basis for the Method map.
      */
-
-    private Class clazz;
-
-    /**
-     * Cache of Methods, or CACHE_MISS, keyed by method name and actual
-     * arguments used to find it.
-     */
-    private final Map methodCache = new Hashtable();
-
-    /** map from method name and args to a {@link Method}. */
-    private final MethodMap methodMap = new MethodMap();
+    private final Class clazz;
+    /** logger. */
+    private final Log rlog;
+    /** cache of methods. */
+    private final MethodCache methodCache;
 
     /**
      * Standard constructor.
-     * @param aClass the class to deconstruct. 
+     *
+     * @param aClass the class to deconstruct.
+     * @param log the logger.
      */
-    public ClassMap(Class aClass) {
+    public ClassMap(Class aClass, Log log) {
         clazz = aClass;
+        this.rlog = log;
+        methodCache = new MethodCache();
+
         populateMethodCache();
     }
 
@@ -80,390 +75,232 @@
     }
 
     /**
-     * Find a Method using the methodKey provided.
-     * 
-     * Look in the methodMap for an entry. If found, it'll either be a
-     * CACHE_MISS, in which case we simply give up, or it'll be a Method, in
-     * which case, we return it.
-     * 
-     * If nothing is found, then we must actually go and introspect the method
-     * from the MethodMap.
-     * 
-     * @param name method name
-     * @param params method parameters
-     * @return CACHE_MISS or a {@link Method}
-     * @throws MethodMap.AmbiguousException if the method and parameters are ambiguous.
-     */
-    public Method findMethod(String name, Object[] params) throws MethodMap.AmbiguousException {
-        String methodKey = makeMethodKey(name, params);
-        Object cacheEntry = methodCache.get(methodKey);
-
-        if (cacheEntry == CACHE_MISS) {
-            return null;
-        }
-
-        if (cacheEntry == null) {
-            try {
-                cacheEntry = methodMap.find(name, params);
-            } catch (MethodMap.AmbiguousException ae) {
-                /*
-                 * that's a miss :)
-                 */
-
-                methodCache.put(methodKey, CACHE_MISS);
-
-                throw ae;
-            }
-
-            if (cacheEntry == null) {
-                methodCache.put(methodKey, CACHE_MISS);
-            } else {
-                methodCache.put(methodKey, cacheEntry);
-            }
-        }
-
-        // Yes, this might just be null.
-
-        return (Method) cacheEntry;
+     * Find a Method using the method name and parameter objects.
+     *
+     * @param name   The method name to look up.
+     * @param params An array of parameters for the method.
+     * @return A Method object representing the method to invoke or null.
+     * @throws MethodMap.AmbiguousException When more than one method is a match for the parameters.
+     */
+    public Method findMethod(final String name, final Object[] params)
+            throws MethodMap.AmbiguousException {
+        return methodCache.get(name, params);
     }
 
     /**
-     * Populate the Map of direct hits. These are taken from all the public
-     * methods that our class provides.
+     * Populate the Map of direct hits. These
+     * are taken from all the public methods
+     * that our class, its parents and their implemented interfaces provide.
      */
     private void populateMethodCache() {
-
-        /*
-         * get all publicly accessible methods
-         */
-
-        Method[] methods = getAccessibleMethods(clazz);
-
-        /*
-         * map and cache them
-         */
-
-        for (int i = 0; i < methods.length; i++) {
-            Method method = methods[i];
-
-            /*
-             * now get the 'public method', the method declared by a public
-             * interface or class. (because the actual implementing class may be
-             * a facade...
-             */
-
-            Method publicMethod = getPublicMethod(method);
-
-            /*
-             * it is entirely possible that there is no public method for the
-             * methods of this class (i.e. in the facade, a method that isn't on
-             * any of the interfaces or superclass in which case, ignore it.
-             * Otherwise, map and cache
-             */
-
-            if (publicMethod != null) {
-                methodMap.add(publicMethod);
-                methodCache.put(makeMethodKey(publicMethod), publicMethod);
+        //
+        // Build a list of all elements in the class hierarchy. This one is bottom-first (i.e. we start
+        // with the actual declaring class and its interfaces and then move up (superclass etc.) until we
+        // hit java.lang.Object. That is important because it will give us the methods of the declaring class
+        // which might in turn be abstract further up the tree.
+        //
+        // We also ignore all SecurityExceptions that might happen due to SecurityManager restrictions (prominently
+        // hit with Tomcat 5.5).
+        //
+        // We can also omit all that complicated getPublic, getAccessible and upcast logic that the class map had up
+        // until Velocity 1.4. As we always reflect all elements of the tree (that's what we have a cache for), we will
+        // hit the public elements sooner or later because we reflect all the public elements anyway.
+        //
+        List classesToReflect = new ArrayList();
+
+        // Ah, the miracles of Java for(;;) ...
+        for (Class classToReflect = getCachedClass(); classToReflect != null; 
+        classToReflect = classToReflect.getSuperclass()) {
+            if (Modifier.isPublic(classToReflect.getModifiers())) {
+                classesToReflect.add(classToReflect);
+            }
+            Class[] interfaces = classToReflect.getInterfaces();
+            for (int i = 0; i < interfaces.length; i++) {
+                if (Modifier.isPublic(interfaces[i].getModifiers())) {
+                    classesToReflect.add(interfaces[i]);
+                }
             }
         }
-    }
 
-    /**
-     * Make a methodKey for the given method using the concatenation of the name
-     * and the types of the method parameters.
-     */
-    private String makeMethodKey(Method method) {
-        Class[] parameterTypes = method.getParameterTypes();
+        for (Iterator it = classesToReflect.iterator(); it.hasNext();) {
+            Class classToReflect = (Class) it.next();
 
-        StringBuffer methodKey = new StringBuffer(method.getName());
+            try {
+                Method[] methods = classToReflect.getMethods();
 
-        for (int j = 0; j < parameterTypes.length; j++) {
-            /*
-             * If the argument type is primitive then we want to convert our
-             * primitive type signature to the corresponding Object type so
-             * introspection for methods with primitive types will work
-             * correctly.
-             */
-            if (parameterTypes[j].isPrimitive()) {
-                if (parameterTypes[j].equals(Boolean.TYPE))
-                    methodKey.append("java.lang.Boolean");
-                else if (parameterTypes[j].equals(Byte.TYPE))
-                    methodKey.append("java.lang.Byte");
-                else if (parameterTypes[j].equals(Character.TYPE))
-                    methodKey.append("java.lang.Character");
-                else if (parameterTypes[j].equals(Double.TYPE))
-                    methodKey.append("java.lang.Double");
-                else if (parameterTypes[j].equals(Float.TYPE))
-                    methodKey.append("java.lang.Float");
-                else if (parameterTypes[j].equals(Integer.TYPE))
-                    methodKey.append("java.lang.Integer");
-                else if (parameterTypes[j].equals(Long.TYPE))
-                    methodKey.append("java.lang.Long");
-                else if (parameterTypes[j].equals(Short.TYPE))
-                    methodKey.append("java.lang.Short");
-            } else {
-                methodKey.append(parameterTypes[j].getName());
+                for (int i = 0; i < methods.length; i++) {
+                    // Strictly spoken that check shouldn't be necessary
+                    // because getMethods only returns public methods.
+                    int modifiers = methods[i].getModifiers();
+                    if (Modifier.isPublic(modifiers)) //  && !)
+                    {
+                        // Some of the interfaces contain abstract methods. That is fine, because the actual object must
+                        // implement them anyway (else it wouldn't be implementing the interface).
+                        // If we find an abstract method in a non-interface, we skip it, because we do want to make sure
+                        // that no abstract methods end up in  the cache.
+                        if (classToReflect.isInterface() || !Modifier.isAbstract(modifiers)) {
+                            methodCache.put(methods[i]);
+                        }
+                    }
+                }
             }
-        }
-
-        return methodKey.toString();
-    }
-
-    private static String makeMethodKey(String method, Object[] params) {
-        StringBuffer methodKey = new StringBuffer().append(method);
-
-        for (int j = 0; j < params.length; j++) {
-            Object arg = params[j];
-
-            if (arg == null) {
-                arg = OBJECT;
+            catch (SecurityException se) // Everybody feels better with...
+            {
+                if (rlog.isDebugEnabled()) {
+                    rlog.debug("While accessing methods of " + classToReflect + ": ", se);
+                }
             }
-
-            methodKey.append(arg.getClass().getName());
         }
-
-        return methodKey.toString();
     }
 
     /**
-     * Retrieves public methods for a class. In case the class is not public,
-     * retrieves methods with same signature as its public methods from public
-     * superclasses and interfaces (if they exist). Basically upcasts every
-     * method to the nearest acccessible method.
-     */
-    private static Method[] getAccessibleMethods(Class clazz) {
-        Method[] methods = clazz.getMethods();
-
-        /*
-         * Short circuit for the (hopefully) majority of cases where the clazz
-         * is public
-         */
-
-        if (Modifier.isPublic(clazz.getModifiers())) {
-            return methods;
-        }
-
-        /*
-         * No luck - the class is not public, so we're going the longer way.
-         */
-
-        MethodInfo[] methodInfos = new MethodInfo[methods.length];
-
-        for (int i = methods.length; i-- > 0;) {
-            methodInfos[i] = new MethodInfo(methods[i]);
-        }
+     * This is the cache to store and look up the method information.
+     *
+     * @author <a href="mailto:henning@apache.org">Henning P. Schmiedehausen</a>
+     * @version $Id$
+     */
+    private static final class MethodCache {
+        private static final class CacheMiss {
+        }
+
+        private static final CacheMiss CACHE_MISS = new CacheMiss();
+
+        private static final Object OBJECT = new Object();
+
+        private static final Map convertPrimitives = new HashMap();
+
+        static {
+            convertPrimitives.put(Boolean.TYPE, Boolean.class.getName());
+            convertPrimitives.put(Byte.TYPE, Byte.class.getName());
+            convertPrimitives.put(Character.TYPE, Character.class.getName());
+            convertPrimitives.put(Double.TYPE, Double.class.getName());
+            convertPrimitives.put(Float.TYPE, Float.class.getName());
+            convertPrimitives.put(Integer.TYPE, Integer.class.getName());
+            convertPrimitives.put(Long.TYPE, Long.class.getName());
+            convertPrimitives.put(Short.TYPE, Short.class.getName());
+        }
+
+        /**
+         * Cache of Methods, or CACHE_MISS, keyed by method
+         * name and actual arguments used to find it.
+         */
+        private final Map cache = new HashMap();
+
+        /**
+         * Map of methods that are searchable according to method parameters to find a match
+         */
+        private final MethodMap methodMap = new MethodMap();
+
+        /**
+         * Find a Method using the method name and parameter objects.
+         *
+         * Look in the methodMap for an entry.  If found,
+         * it'll either be a CACHE_MISS, in which case we
+         * simply give up, or it'll be a Method, in which
+         * case, we return it.
+         *
+         * If nothing is found, then we must actually go
+         * and introspect the method from the MethodMap.
+         *
+         * @param name   The method name to look up.
+         * @param params An array of parameters for the method.
+         * @return A Method object representing the method to invoke or null.
+         * @throws MethodMap.AmbiguousException When more than one method is a match for the parameters.
+         */
+        public synchronized Method get(final String name, final Object[] params)
+                throws MethodMap.AmbiguousException {
+            String methodKey = makeMethodKey(name, params);
 
-        int upcastCount = getAccessibleMethods(clazz, methodInfos, 0);
+            Object cacheEntry = cache.get(methodKey);
 
-        /*
-         * Reallocate array in case some method had no accessible counterpart.
-         */
-
-        if (upcastCount < methods.length) {
-            methods = new Method[upcastCount];
-        }
-
-        int j = 0;
-        for (int i = 0; i < methodInfos.length; ++i) {
-            MethodInfo methodInfo = methodInfos[i];
-            if (methodInfo.upcast) {
-                methods[j++] = methodInfo.method;
+            // We looked this up before and failed.
+            if (cacheEntry == CACHE_MISS) {
+                return null;
             }
-        }
-        return methods;
-    }
-
-    /**
-     * Recursively finds a match for each method, starting with the class, and
-     * then searching the superclass and interfaces.
-     * 
-     * @param clazz Class to check
-     * @param methodInfos array of methods we are searching to match
-     * @param upcastCount current number of methods we have matched
-     * @return count of matched methods
-     */
-    private static int getAccessibleMethods(Class clazz, MethodInfo[] methodInfos, int upcastCount) {
-        int l = methodInfos.length;
-
-        /*
-         * if this class is public, then check each of the currently
-         * 'non-upcasted' methods to see if we have a match
-         */
 
-        if (Modifier.isPublic(clazz.getModifiers())) {
-            for (int i = 0; i < l && upcastCount < l; ++i) {
+            if (cacheEntry == null) {
                 try {
-                    MethodInfo methodInfo = methodInfos[i];
-
-                    if (!methodInfo.upcast) {
-                        methodInfo.tryUpcasting(clazz);
-                        upcastCount++;
-                    }
-                } catch (NoSuchMethodException e) {
+                    // That one is expensive...
+                    cacheEntry = methodMap.find(name, params);
+                }
+                catch (MethodMap.AmbiguousException ae) {
                     /*
-                     * Intentionally ignored - it means it wasn't found in the
-                     * current class
+                     *  that's a miss :-)
                      */
+                    cache.put(methodKey, CACHE_MISS);
+                    throw ae;
                 }
-            }
-
-            /*
-             * Short circuit if all methods were upcast
-             */
 
-            if (upcastCount == l) {
-                return upcastCount;
+                cache.put(methodKey,
+                        (cacheEntry != null) ? cacheEntry : CACHE_MISS);
             }
-        }
-
-        /*
-         * Examine superclass
-         */
-
-        Class superclazz = clazz.getSuperclass();
 
-        if (superclazz != null) {
-            upcastCount = getAccessibleMethods(superclazz, methodInfos, upcastCount);
+            // Yes, this might just be null.
 
-            /*
-             * Short circuit if all methods were upcast
-             */
-
-            if (upcastCount == l) {
-                return upcastCount;
-            }
+            return (Method) cacheEntry;
         }
 
-        /*
-         * Examine interfaces. Note we do it even if superclazz == null. This is
-         * redundant as currently java.lang.Object does not implement any
-         * interfaces, however nothing guarantees it will not in future.
-         */
-
-        Class[] interfaces = clazz.getInterfaces();
-
-        for (int i = interfaces.length; i-- > 0;) {
-            upcastCount = getAccessibleMethods(interfaces[i], methodInfos, upcastCount);
-
-            /*
-             * Short circuit if all methods were upcast
-             */
+        public synchronized void put(Method method) {
+            String methodKey = makeMethodKey(method);
 
-            if (upcastCount == l) {
-                return upcastCount;
+            // We don't overwrite methods. Especially not if we fill the
+            // cache from defined class towards java.lang.Object because
+            // abstract methods in superclasses would else overwrite concrete
+            // classes further down the hierarchy.
+            if (cache.get(methodKey) == null) {
+                cache.put(methodKey, method);
+                methodMap.add(method);
             }
         }
 
-        return upcastCount;
-    }
-
-    /**
-     * For a given method, retrieves its publicly accessible counterpart. This
-     * method will look for a method with same name and signature declared in a
-     * public superclass or implemented interface of this method's declaring
-     * class. This counterpart method is publicly callable.
-     * 
-     * @param method a method whose publicly callable counterpart is requested.
-     * @return the publicly callable counterpart method. Note that if the
-     *         parameter method is itself declared by a public class, this
-     *         method is an identity function.
-     */
-    public static Method getPublicMethod(Method method) {
-        Class clazz = method.getDeclaringClass();
-
-        /*
-         * Short circuit for (hopefully the majority of) cases where the
-         * declaring class is public.
+        /**
+         * Make a methodKey for the given method using
+         * the concatenation of the name and the
+         * types of the method parameters.
+         *
+         * @param method to be stored as key
+         * @return key for ClassMap
          */
+        private String makeMethodKey(final Method method) {
+            Class[] parameterTypes = method.getParameterTypes();
 
-        if ((clazz.getModifiers() & Modifier.PUBLIC) != 0) {
-            return method;
-        }
-
-        return getPublicMethod(clazz, method.getName(), method.getParameterTypes());
-    }
-
-    /**
-     * Looks up the method with specified name and signature in the first public
-     * superclass or implemented interface of the class.
-     * 
-     * @param class the class whose method is sought
-     * @param name the name of the method
-     * @param paramTypes the classes of method parameters
-     */
-    private static Method getPublicMethod(Class clazz, String name, Class[] paramTypes) {
-        /*
-         * if this class is public, then try to get it
-         */
+            StringBuffer methodKey = new StringBuffer(method.getName());
 
-        if ((clazz.getModifiers() & Modifier.PUBLIC) != 0) {
-            try {
-                return clazz.getMethod(name, paramTypes);
-            } catch (NoSuchMethodException e) {
+            for (int j = 0; j < parameterTypes.length; j++) {
                 /*
-                 * If the class does not have the method, then neither its
-                 * superclass nor any of its interfaces has it so quickly return
-                 * null.
+                 * If the argument type is primitive then we want
+                 * to convert our primitive type signature to the
+                 * corresponding Object type so introspection for
+                 * methods with primitive types will work correctly.
+                 *
+                 * The lookup map (convertPrimitives) contains all eight
+                 * primitives (boolean, byte, char, double, float, int, long, short)
+                 * known to Java. So it should never return null for the key passed in.
                  */
-                return null;
+                if (parameterTypes[j].isPrimitive()) {
+                    methodKey.append((String) convertPrimitives.get(parameterTypes[j]));
+                } else {
+                    methodKey.append(parameterTypes[j].getName());
+                }
             }
-        }
-
-        /*
-         * try the superclass
-         */
-
-        Class superclazz = clazz.getSuperclass();
 
-        if (superclazz != null) {
-            Method superclazzMethod = getPublicMethod(superclazz, name, paramTypes);
-
-            if (superclazzMethod != null) {
-                return superclazzMethod;
-            }
+            return methodKey.toString();
         }
 
-        /*
-         * and interfaces
-         */
+        private String makeMethodKey(String method, Object[] params) {
+            StringBuffer methodKey = new StringBuffer().append(method);
 
-        Class[] interfaces = clazz.getInterfaces();
+            for (int j = 0; j < params.length; j++) {
+                Object arg = params[j];
 
-        for (int i = 0; i < interfaces.length; ++i) {
-            Method interfaceMethod = getPublicMethod(interfaces[i], name, paramTypes);
+                if (arg == null) {
+                    arg = OBJECT;
+                }
 
-            if (interfaceMethod != null) {
-                return interfaceMethod;
+                methodKey.append(arg.getClass().getName());
             }
-        }
-
-        return null;
-    }
-
-    /**
-     * Used for the iterative discovery process for public methods.
-     */
-    private static final class MethodInfo {
-        Method method;
-
-        String name;
-
-        Class[] parameterTypes;
-
-        boolean upcast;
-
-        MethodInfo(Method method) {
-            this.method = null;
-            name = method.getName();
-            parameterTypes = method.getParameterTypes();
-            upcast = false;
-        }
 
-        void tryUpcasting(Class clazz) throws NoSuchMethodException {
-            method = clazz.getMethod(name, parameterTypes);
-            name = null;
-            parameterTypes = null;
-            upcast = true;
+            return methodKey.toString();
         }
     }
 }