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    }