    001    /*
    002     * Licensed to the Apache Software Foundation (ASF) under one or more
    003     * contributor license agreements.  See the NOTICE file distributed with
    004     * this work for additional information regarding copyright ownership.
    005     * The ASF licenses this file to You under the Apache License, Version 2.0
    006     * (the "License"); you may not use this file except in compliance with
    007     * the License.  You may obtain a copy of the License at
    008     *
    009     *      http://www.apache.org/licenses/LICENSE-2.0
    010     *
    011     * Unless required by applicable law or agreed to in writing, software
    012     * distributed under the License is distributed on an "AS IS" BASIS,
    013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014     * See the License for the specific language governing permissions and
    015     * limitations under the License.
    016     */
    017
    018    package org.apache.commons.jexl2.internal.introspection;
    019
    020    import java.lang.reflect.Method;
    021    import java.lang.reflect.Constructor;
    022    import java.lang.reflect.Field;
    023    import java.util.Map;
    024    import java.util.HashMap;
    025    import java.util.List;
    026    import java.util.LinkedList;
    027
    028    import org.apache.commons.logging.Log;
    029
    030    /**
    031     * This basic function of this class is to return a Method object for a
    032     * particular class given the name of a method and the parameters to the method
    033     * in the form of an Object[]
    034     * <p/>
    035     * The first time the Introspector sees a class it creates a class method map
    036     * for the class in question. Basically the class method map is a Hastable where
    037     * Method objects are keyed by a concatenation of the method name and the names
    038     * of classes that make up the parameters.
    039     *
    040     * For example, a method with the following signature:
    041     *
    042     * public void method(String a, StringBuffer b)
    043     *
    044     * would be mapped by the key:
    045     *
    046     * "method" + "java.lang.String" + "java.lang.StringBuffer"
    047     *
    048     * This mapping is performed for all the methods in a class and stored for
    049     *
    050     * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
    051     * @author <a href="mailto:bob@werken.com">Bob McWhirter</a>
    052     * @author <a href="mailto:szegedia@freemail.hu">Attila Szegedi</a>
    053     * @author <a href="mailto:paulo.gaspar@krankikom.de">Paulo Gaspar</a>
    054     * @author <a href="mailto:henning@apache.org">Henning P. Schmiedehausen</a>
    055     * @version $Id: IntrospectorBase.java 896952 2010-01-07 18:21:29Z henrib $
    056     * @since 1.0
    057     */
    058    public class IntrospectorBase {
    059        /** the logger. */
    060        protected final Log rlog;
    061        /**
    062         * Holds the method maps for the classes we know about, keyed by Class.
    063         */
    064        private final Map<Class<?>, ClassMap> classMethodMaps = new HashMap<Class<?>, ClassMap>();
    065        /**
    066         * The class loader used to solve constructors if needed.
    067         */
    068        private ClassLoader loader;
    069        /**
    070         * Holds the map of classes ctors we know about as well as unknown ones.
    071         */
    072        private final Map<MethodKey, Constructor<?>> constructorsMap = new HashMap<MethodKey, Constructor<?>>();
    073        /**
    074         * Holds the set of classes we have introspected.
    075         */
    076        private final Map<String, Class<?>> constructibleClasses = new HashMap<String, Class<?>>();
    077
    078        /**
    079         * Create the introspector.
    080         * @param log the logger to use
    081         */
    082        public IntrospectorBase(Log log) {
    083            this.rlog = log;
    084            loader = getClass().getClassLoader();
    085        }
    086
    087        /**
    088         * Gets the method defined by the <code>MethodKey</code> for the class <code>c</code>.
    089         *
    090         * @param c     Class in which the method search is taking place
    091         * @param key   Key of the method being searched for
    092         * @return The desired Method object.
    093         * @throws IllegalArgumentException     When the parameters passed in can not be used for introspection.
    094         *
    095         */
    096        //CSOFF: RedundantThrows
    097        public Method getMethod(Class<?> c, MethodKey key) {
    098            try {
    099                ClassMap classMap = getMap(c);
    100                return classMap.findMethod(key);
    101            } catch (MethodKey.AmbiguousException ae) {
    102                // whoops.  Ambiguous.  Make a nice log message and return null...
    103                if (rlog != null) {
    104                    rlog.error("ambiguous method invocation: "
    105                               + c.getName() + "."
    106                               + key.debugString());
    107                }
    108            }
    109            return null;
    110
    111        }
    112        // CSON: RedundantThrows
    113
    114
    115        /**
    116         * Gets the field named by <code>key</code> for the class <code>c</code>.
    117         *
    118         * @param c     Class in which the field search is taking place
    119         * @param key   Name of the field being searched for
    120         * @return the desired field or null if it does not exist or is not accessible
    121         * */
    122        public Field getField(Class<?> c, String key) {
    123            ClassMap classMap = getMap(c);
    124            return classMap.findField(c, key);
    125        }
    126
    127        /**
    128         * Gets the array of accessible field names known for a given class.
    129         * @param c the class
    130         * @return the class field names
    131         */
    132        public String[] getFieldNames(Class<?> c) {
    133            if (c == null) {
    134                return new String[0];
    135            }
    136            ClassMap classMap = getMap(c);
    137            return classMap.getFieldNames();
    138        }
    139
    140        /**
    141         * Gets the array of accessible methods names known for a given class.
    142         * @param c the class
    143         * @return the class method names
    144         */
    145        public String[] getMethodNames(Class<?> c) {
    146            if (c == null) {
    147                return new String[0];
    148            }
    149            ClassMap classMap = getMap(c);
    150            return classMap.getMethodNames();
    151        }
    152
    153        /**
    154         * A Constructor get cache-miss.
    155         */
    156        private static class CacheMiss {
    157            /** The constructor used as cache-miss. */
    158            @SuppressWarnings("unused")
    159            public CacheMiss() {}
    160        }
    161        /** The cache-miss marker for the constructors map. */
    162        private static final Constructor<?> CTOR_MISS = CacheMiss.class.getConstructors()[0];
    163
    164        /**
    165         * Sets the class loader used to solve constructors.
    166         * <p>Also cleans the constructors cache.</p>
    167         * @param cloader the class loader; if null, use this instance class loader
    168         */
    169        public void setLoader(ClassLoader cloader) {
    170            if (cloader == null) {
    171                cloader = getClass().getClassLoader();
    172            }
    173            if (!cloader.equals(loader)) {
    174                synchronized(constructorsMap) {
    175                    loader = cloader;
    176                    constructorsMap.clear();
    177                    constructibleClasses.clear();
    178                }
    179            }
    180        }
    181
    182        /**
    183         * Gets the constructor defined by the <code>MethodKey</code>.
    184         *
    185         * @param key   Key of the constructor being searched for
    186         * @return The desired Constructor object.
    187         * @throws IllegalArgumentException     When the parameters passed in can not be used for introspection.
    188         */
    189        public Constructor<?> getConstructor(final MethodKey key) {
    190            return getConstructor(null, key);
    191        }
    192
    193        /**
    194         * Gets the constructor defined by the <code>MethodKey</code>.
    195         * @param c the class we want to instantiate
    196         * @param key   Key of the constructor being searched for
    197         * @return The desired Constructor object.
    198         * @throws IllegalArgumentException     When the parameters passed in can not be used for introspection.
    199         */
    200        //CSOFF: RedundantThrows
    201        public Constructor<?> getConstructor(final Class<?> c, final MethodKey key) {
    202            try {
    203                Constructor<?> ctor = null;
    204                synchronized(constructorsMap) {
    205                    ctor = constructorsMap.get(key);
    206                    // that's a clear miss
    207                    if (CTOR_MISS.equals(ctor)) {
    208                        return null;
    209                    }
    210                    // let's introspect...
    211                    if (ctor == null) {
    212                        final String cname = key.getMethod();
    213                        // do we know about this class?
    214                        Class<?> clazz = constructibleClasses.get(cname);
    215                        try {
    216                            // do find the most specific ctor
    217                            if (clazz == null) {
    218                                if (c != null && c.getName().equals(key.getMethod())) {
    219                                    clazz = c;
    220                                } else {
    221                                    clazz = loader.loadClass(cname);
    222                                }
    223                                // add it to list of known loaded classes
    224                                constructibleClasses.put(cname, clazz);
    225                            }
    226                            List<Constructor<?>> l = new LinkedList<Constructor<?>>();
    227                            for(Constructor<?> ictor : clazz.getConstructors()) {
    228                                l.add(ictor);
    229                            }
    230                            // try to find one
    231                            ctor = key.getMostSpecificConstructor(l);
    232                            if (ctor != null) {
    233                                constructorsMap.put(key, ctor);
    234                            } else {
    235                                constructorsMap.put(key, CTOR_MISS);
    236                            }
    237                        } catch(ClassNotFoundException xnotfound) {
    238                            if (rlog.isDebugEnabled()) {
    239                                rlog.debug("could not load class " + cname, xnotfound);
    240                            }
    241                            ctor = null;
    242                        } catch(MethodKey.AmbiguousException xambiguous) {
    243                            rlog.warn("ambiguous ctor detected for " + cname, xambiguous);
    244                            ctor = null;
    245                        }
    246                    }
    247                }
    248                return ctor;
    249            } catch (MethodKey.AmbiguousException ae) {
    250                // whoops.  Ambiguous.  Make a nice log message and return null...
    251                if (rlog != null) {
    252                    rlog.error("ambiguous constructor invocation: new "
    253                               + key.debugString());
    254                }
    255            }
    256            return null;
    257        }
    258        // CSON: RedundantThrows
    259
    260        /**
    261         * Gets the ClassMap for a given class.
    262         * @param c the class
    263         * @return the class map
    264         */
    265        private ClassMap getMap(Class<?> c) {
    266            synchronized (classMethodMaps) {
    267                ClassMap classMap = classMethodMaps.get(c);
    268                if (classMap == null) {
    269                    classMap = new ClassMap(c,rlog);
    270                    classMethodMaps.put(c, classMap);
    271                }
    272                return classMap;
    273            }
    274        }
    275    }