    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    package org.apache.commons.jexl2.internal.introspection;
    018
    019    import java.lang.reflect.Method;
    020    import java.lang.reflect.Constructor;
    021    import java.lang.reflect.Field;
    022    import java.util.Map;
    023    import java.util.HashMap;
    024    import java.util.Iterator;
    025    import java.util.LinkedList;
    026    import java.util.List;
    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.
    049     * @since 1.0
    050     */
    051    public class IntrospectorBase {
    052        /** the logger. */
    053        protected final Log rlog;
    054        /**
    055         * Holds the method maps for the classes we know about, keyed by Class.
    056         */
    057        private final Map<Class<?>, ClassMap> classMethodMaps = new HashMap<Class<?>, ClassMap>();
    058        /**
    059         * The class loader used to solve constructors if needed.
    060         */
    061        private ClassLoader loader;
    062        /**
    063         * Holds the map of classes ctors we know about as well as unknown ones.
    064         */
    065        private final Map<MethodKey, Constructor<?>> constructorsMap = new HashMap<MethodKey, Constructor<?>>();
    066        /**
    067         * Holds the set of classes we have introspected.
    068         */
    069        private final Map<String, Class<?>> constructibleClasses = new HashMap<String, Class<?>>();
    070
    071        /**
    072         * Create the introspector.
    073         * @param log the logger to use
    074         */
    075        public IntrospectorBase(Log log) {
    076            this.rlog = log;
    077            loader = getClass().getClassLoader();
    078        }
    079
    080        /**
    081         * Gets a class by name through this introspector class loader.
    082         * @param className the class name
    083         * @return the class instance or null if it could not be found
    084         */
    085        public Class<?> getClassByName(String className) {
    086            try {
    087                return Class.forName(className, false, loader);
    088            } catch (ClassNotFoundException xignore) {
    089                return null;
    090            }
    091        }
    092
    093        /**
    094         * Gets the method defined by the <code>MethodKey</code> for the class <code>c</code>.
    095         *
    096         * @param c     Class in which the method search is taking place
    097         * @param key   Key of the method being searched for
    098         * @return The desired method object
    099         * @throws MethodKey.AmbiguousException if no unambiguous method could be found through introspection
    100         */
    101        public Method getMethod(Class<?> c, MethodKey key) {
    102            try {
    103                ClassMap classMap = getMap(c);
    104                return classMap.findMethod(key);
    105            } catch (MethodKey.AmbiguousException xambiguous) {
    106                // whoops.  Ambiguous.  Make a nice log message and return null...
    107                if (rlog != null && rlog.isInfoEnabled()) {
    108                    rlog.info("ambiguous method invocation: "
    109                            + c.getName() + "."
    110                            + key.debugString(), xambiguous);
    111                }
    112                return null;
    113            }
    114        }
    115
    116        /**
    117         * Gets the field named by <code>key</code> for the class <code>c</code>.
    118         *
    119         * @param c     Class in which the field search is taking place
    120         * @param key   Name of the field being searched for
    121         * @return the desired field or null if it does not exist or is not accessible
    122         * */
    123        public Field getField(Class<?> c, String key) {
    124            ClassMap classMap = getMap(c);
    125            return classMap.findField(c, key);
    126        }
    127
    128        /**
    129         * Gets the array of accessible field names known for a given class.
    130         * @param c the class
    131         * @return the class field names
    132         */
    133        public String[] getFieldNames(Class<?> c) {
    134            if (c == null) {
    135                return new String[0];
    136            }
    137            ClassMap classMap = getMap(c);
    138            return classMap.getFieldNames();
    139        }
    140
    141        /**
    142         * Gets the array of accessible methods names known for a given class.
    143         * @param c the class
    144         * @return the class method names
    145         */
    146        public String[] getMethodNames(Class<?> c) {
    147            if (c == null) {
    148                return new String[0];
    149            }
    150            ClassMap classMap = getMap(c);
    151            return classMap.getMethodNames();
    152        }
    153
    154        /**
    155         * Gets the array of accessible method known for a given class.
    156         * @param c the class
    157         * @param methodName the method name
    158         * @return the array of methods (null or not empty)
    159         */
    160        public Method[] getMethods(Class<?> c, String methodName) {
    161            if (c == null) {
    162                return null;
    163            }
    164            ClassMap classMap = getMap(c);
    165            return classMap.get(methodName);
    166        }
    167
    168        /**
    169         * A Constructor get cache-miss.
    170         */
    171        private static class CacheMiss {
    172            /** The constructor used as cache-miss. */
    173            @SuppressWarnings("unused")
    174            public CacheMiss() {}
    175        }
    176
    177        /** The cache-miss marker for the constructors map. */
    178        private static final Constructor<?> CTOR_MISS = CacheMiss.class.getConstructors()[0];
    179
    180        /**
    181         * Sets the class loader used to solve constructors.
    182         * <p>Also cleans the constructors and methods caches.</p>
    183         * @param cloader the class loader; if null, use this instance class loader
    184         */
    185        public void setLoader(ClassLoader cloader) {
    186            ClassLoader previous = loader;
    187            if (cloader == null) {
    188                cloader = getClass().getClassLoader();
    189            }
    190            if (!cloader.equals(loader)) {
    191                // clean up constructor and class maps
    192                synchronized (constructorsMap) {
    193                    Iterator<Map.Entry<MethodKey, Constructor<?>>> entries = constructorsMap.entrySet().iterator();
    194                    while (entries.hasNext()) {
    195                        Map.Entry<MethodKey, Constructor<?>> entry = entries.next();
    196                        Class<?> clazz = entry.getValue().getDeclaringClass();
    197                        if (isLoadedBy(previous, clazz)) {
    198                            entries.remove();
    199                            // the method name is the name of the class
    200                            constructibleClasses.remove(entry.getKey().getMethod());
    201                        }
    202                    }
    203                }
    204                // clean up method maps
    205                synchronized (classMethodMaps) {
    206                    Iterator<Map.Entry<Class<?>, ClassMap>> entries = classMethodMaps.entrySet().iterator();
    207                    while (entries.hasNext()) {
    208                        Map.Entry<Class<?>, ClassMap> entry = entries.next();
    209                        Class<?> clazz = entry.getKey();
    210                        if (isLoadedBy(previous, clazz)) {
    211                            entries.remove();
    212                        }
    213                    }
    214                }
    215                loader = cloader;
    216            }
    217        }
    218
    219        /**
    220         * Checks whether a class is loaded through a given class loader or one of its ascendants.
    221         * @param loader the class loader
    222         * @param clazz the class to check
    223         * @return true if clazz was loaded through the loader, false otherwise
    224         */
    225        private static boolean isLoadedBy(ClassLoader loader, Class<?> clazz) {
    226            if (loader != null) {
    227                ClassLoader cloader = clazz.getClassLoader();
    228                while (cloader != null) {
    229                    if (cloader.equals(loader)) {
    230                        return true;
    231                    } else {
    232                        cloader = cloader.getParent();
    233                    }
    234                }
    235            }
    236            return false;
    237        }
    238
    239        /**
    240         * Gets the constructor defined by the <code>MethodKey</code>.
    241         *
    242         * @param key   Key of the constructor being searched for
    243         * @return The desired constructor object
    244         * or null if no unambiguous constructor could be found through introspection.
    245         */
    246        public Constructor<?> getConstructor(final MethodKey key) {
    247            return getConstructor(null, key);
    248        }
    249
    250        /**
    251         * Gets the constructor defined by the <code>MethodKey</code>.
    252         * @param c the class we want to instantiate
    253         * @param key   Key of the constructor being searched for
    254         * @return The desired constructor object
    255         * or null if no unambiguous constructor could be found through introspection.
    256         */
    257        public Constructor<?> getConstructor(final Class<?> c, final MethodKey key) {
    258            Constructor<?> ctor = null;
    259            synchronized (constructorsMap) {
    260                ctor = constructorsMap.get(key);
    261                // that's a clear miss
    262                if (CTOR_MISS.equals(ctor)) {
    263                    return null;
    264                }
    265                // let's introspect...
    266                if (ctor == null) {
    267                    final String cname = key.getMethod();
    268                    // do we know about this class?
    269                    Class<?> clazz = constructibleClasses.get(cname);
    270                    try {
    271                        // do find the most specific ctor
    272                        if (clazz == null) {
    273                            if (c != null && c.getName().equals(key.getMethod())) {
    274                                clazz = c;
    275                            } else {
    276                                clazz = loader.loadClass(cname);
    277                            }
    278                            // add it to list of known loaded classes
    279                            constructibleClasses.put(cname, clazz);
    280                        }
    281                        List<Constructor<?>> l = new LinkedList<Constructor<?>>();
    282                        for (Constructor<?> ictor : clazz.getConstructors()) {
    283                            l.add(ictor);
    284                        }
    285                        // try to find one
    286                        ctor = key.getMostSpecificConstructor(l);
    287                        if (ctor != null) {
    288                            constructorsMap.put(key, ctor);
    289                        } else {
    290                            constructorsMap.put(key, CTOR_MISS);
    291                        }
    292                    } catch (ClassNotFoundException xnotfound) {
    293                        if (rlog != null && rlog.isInfoEnabled()) {
    294                            rlog.info("unable to find class: "
    295                                    + cname + "."
    296                                    + key.debugString(), xnotfound);
    297                        }
    298                        ctor = null;
    299                    } catch (MethodKey.AmbiguousException xambiguous) {
    300                        if (rlog != null && rlog.isInfoEnabled()) {
    301                            rlog.info("ambiguous constructor invocation: "
    302                                    + cname + "."
    303                                    + key.debugString(), xambiguous);
    304                        }
    305                        ctor = null;
    306                    }
    307                }
    308                return ctor;
    309            }
    310        }
    311
    312        /**
    313         * Gets the ClassMap for a given class.
    314         * @param c the class
    315         * @return the class map
    316         */
    317        private ClassMap getMap(Class<?> c) {
    318            synchronized (classMethodMaps) {
    319                ClassMap classMap = classMethodMaps.get(c);
    320                if (classMap == null) {
    321                    classMap = new ClassMap(c, rlog);
    322                    classMethodMaps.put(c, classMap);
    323                }
    324                return classMap;
    325            }
    326        }
    327    }