All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.commons.jexl.util.introspection.ClassMap Maven / Gradle / Ivy

Go to download

Jexl is an implementation of the JSTL Expression Language with extensions.

There is a newer version: 1.1-hudson-20090508
Show newest version
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.commons.jexl.util.introspection;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
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 Method} objects by a concatenation of the method name and
 * the names of classes that make up the parameters.
 *
 * @author Jason van Zyl
 * @author Bob McWhirter
 * @author Attila Szegedi
 * @author Geir Magnusson Jr.
 * @author Henning P. Schmiedehausen
 * @version $Id: ClassMap.java 584046 2007-10-12 05:14:37Z proyal $
 * @since 1.0
 */
public class ClassMap {
    /**
     * Class passed into the constructor used to as the basis for the Method
     * map.
     */

    private final Class clazz;
    private final Log rlog;

    private final MethodCache methodCache;

    /**
     * Standard constructor.
     *
     * @param aClass the class to deconstruct.
     */
    public ClassMap(Class aClass, Log rlog) {
        clazz = aClass;
        this.rlog = rlog;
        methodCache = new MethodCache();

        populateMethodCache();
    }

    /**
     * @return the class object whose methods are cached by this map.
     */
    Class getCachedClass() {
        return clazz;
    }

    /**
     * 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, its parents and their implemented interfaces provide.
     */
    private void populateMethodCache() {
        //
        // 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]);
                }
            }
        }

        for (Iterator it = classesToReflect.iterator(); it.hasNext();) {
            Class classToReflect = (Class) it.next();

            try {
                Method[] methods = classToReflect.getMethods();

                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]);
                        }
                    }
                }
            }
            catch (SecurityException se) // Everybody feels better with...
            {
                if (rlog.isDebugEnabled()) {
                    rlog.debug("While accessing methods of " + classToReflect + ": ", se);
                }
            }
        }
    }

    /**
     * This is the cache to store and look up the method information.
     *
     * @author Henning P. Schmiedehausen
     * @version $Id: ClassMap.java 584046 2007-10-12 05:14:37Z proyal $
     */
    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);

            Object cacheEntry = cache.get(methodKey);

            // We looked this up before and failed.
            if (cacheEntry == CACHE_MISS) {
                return null;
            }

            if (cacheEntry == null) {
                try {
                    // That one is expensive...
                    cacheEntry = methodMap.find(name, params);
                }
                catch (MethodMap.AmbiguousException ae) {
                    /*
                     *  that's a miss :-)
                     */
                    cache.put(methodKey, CACHE_MISS);
                    throw ae;
                }

                cache.put(methodKey,
                        (cacheEntry != null) ? cacheEntry : CACHE_MISS);
            }

            // Yes, this might just be null.

            return (Method) cacheEntry;
        }

        public synchronized void put(Method method) {
            String methodKey = makeMethodKey(method);

            // 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);
            }
        }

        /**
         * 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();

            StringBuffer methodKey = new StringBuffer(method.getName());

            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.
                 *
                 * 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.
                 */
                if (parameterTypes[j].isPrimitive()) {
                    methodKey.append((String) convertPrimitives.get(parameterTypes[j]));
                } else {
                    methodKey.append(parameterTypes[j].getName());
                }
            }

            return methodKey.toString();
        }

        private 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;
                }

                methodKey.append(arg.getClass().getName());
            }

            return methodKey.toString();
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy