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 }