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 }