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;
018
019 import java.lang.ref.SoftReference;
020 import java.lang.reflect.Method;
021 import java.lang.reflect.Constructor;
022 import java.lang.reflect.Field;
023
024 import org.apache.commons.jexl2.internal.introspection.IntrospectorBase;
025 import org.apache.commons.jexl2.internal.introspection.MethodKey;
026
027 import org.apache.commons.logging.Log;
028
029 /**
030 * Default introspection services.
031 * <p>Finding methods as well as property getters & setters.</p>
032 * @since 1.0
033 */
034 public class Introspector {
035 /** The logger to use for all warnings & errors. */
036 protected final Log rlog;
037 /** The soft reference to the introspector currently in use. */
038 private volatile SoftReference<IntrospectorBase> ref;
039
040 /**
041 * Creates an introspector.
042 * @param log the logger to use for warnings.
043 */
044 protected Introspector(Log log) {
045 rlog = log;
046 ref = new SoftReference<IntrospectorBase>(null);
047 }
048
049 /**
050 * Coerce an Object to an Integer.
051 * @param arg the Object to coerce
052 * @return an Integer if it can be converted, null otherwise
053 */
054 protected Integer toInteger(Object arg) {
055 if (arg == null) {
056 return null;
057 }
058 if (arg instanceof Number) {
059 return Integer.valueOf(((Number) arg).intValue());
060 }
061 try {
062 return Integer.valueOf(arg.toString());
063 } catch (NumberFormatException xnumber) {
064 return null;
065 }
066 }
067
068 /**
069 * Coerce an Object to a String.
070 * @param arg the Object to coerce
071 * @return a String if it can be converted, null otherwise
072 */
073 protected String toString(Object arg) {
074 return arg == null ? null : arg.toString();
075 }
076
077 /**
078 * Gets the current introspector base.
079 * <p>If the reference has been collected, this method will recreate the underlying introspector.</p>
080 * @return the introspector
081 */
082 // CSOFF: DoubleCheckedLocking
083 protected final IntrospectorBase base() {
084 IntrospectorBase intro = ref.get();
085 if (intro == null) {
086 // double checked locking is ok (fixed by Java 5 memory model).
087 synchronized(this) {
088 intro = ref.get();
089 if (intro == null) {
090 intro = new IntrospectorBase(rlog);
091 ref = new SoftReference<IntrospectorBase>(intro);
092 }
093 }
094 }
095 return intro;
096 }
097 // CSON: DoubleCheckedLocking
098
099 /**
100 * Sets the underlying class loader for class solving resolution.
101 * @param loader the loader to use
102 */
103 public void setClassLoader(ClassLoader loader) {
104 base().setLoader(loader);
105 }
106
107 /**
108 * Gets a class by name through this introspector class loader.
109 * @param className the class name
110 * @return the class instance or null if it could not be found
111 */
112 public Class<?> getClassByName(String className) {
113 return base().getClassByName(className);
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 a {@link java.lang.reflect.Field} or null if it does not exist or is not accessible
122 * */
123 public final Field getField(Class<?> c, String key) {
124 return base().getField(c, key);
125 }
126
127 /**
128 * Gets the accessible field names known for a given class.
129 * @param c the class
130 * @return the class field names
131 */
132 public final String[] getFieldNames(Class<?> c) {
133 return base().getFieldNames(c);
134 }
135
136 /**
137 * Gets the method defined by <code>name</code> and
138 * <code>params</code> for the Class <code>c</code>.
139 *
140 * @param c Class in which the method search is taking place
141 * @param name Name of the method being searched for
142 * @param params An array of Objects (not Classes) that describe the
143 * the parameters
144 *
145 * @return a {@link java.lang.reflect.Method}
146 * or null if no unambiguous method could be found through introspection.
147 */
148 public final Method getMethod(Class<?> c, String name, Object[] params) {
149 return base().getMethod(c, new MethodKey(name, params));
150 }
151
152 /**
153 * Gets the method defined by <code>key</code> and for the Class <code>c</code>.
154 *
155 * @param c Class in which the method search is taking place
156 * @param key MethodKey of the method being searched for
157 *
158 * @return a {@link java.lang.reflect.Method}
159 * or null if no unambiguous method could be found through introspection.
160 */
161 public final Method getMethod(Class<?> c, MethodKey key) {
162 return base().getMethod(c, key);
163 }
164
165
166 /**
167 * Gets the accessible methods names known for a given class.
168 * @param c the class
169 * @return the class method names
170 */
171 public final String[] getMethodNames(Class<?> c) {
172 return base().getMethodNames(c);
173 }
174
175 /**
176 * Gets all the methods with a given name from this map.
177 * @param c the class
178 * @param methodName the seeked methods name
179 * @return the array of methods
180 */
181 public final Method[] getMethods(Class<?> c, final String methodName) {
182 return base().getMethods(c, methodName);
183 }
184
185 /**
186 * Returns a general constructor.
187 * @param ctorHandle the object
188 * @param args contructor arguments
189 * @return a {@link java.lang.reflect.Constructor}
190 * or null if no unambiguous contructor could be found through introspection.
191 */
192 public final Constructor<?> getConstructor(Object ctorHandle, Object[] args) {
193 String className = null;
194 Class<?> clazz = null;
195 if (ctorHandle instanceof Class<?>) {
196 clazz = (Class<?>) ctorHandle;
197 className = clazz.getName();
198 } else if (ctorHandle != null) {
199 className = ctorHandle.toString();
200 } else {
201 return null;
202 }
203 return base().getConstructor(clazz, new MethodKey(className, args));
204 }
205
206 /**
207 * Returns a general method.
208 * @param obj the object
209 * @param name the method name
210 * @param args method arguments
211 * @return a {@link AbstractExecutor.Method}.
212 */
213 public final AbstractExecutor.Method getMethodExecutor(Object obj, String name, Object[] args) {
214 AbstractExecutor.Method me = new MethodExecutor(this, obj, name, args);
215 return me.isAlive() ? me : null;
216 }
217
218 /**
219 * Return a property getter.
220 * @param obj the object to base the property from.
221 * @param identifier property name
222 * @return a {@link AbstractExecutor.Get}.
223 */
224 public final AbstractExecutor.Get getGetExecutor(Object obj, Object identifier) {
225 final Class<?> claz = obj.getClass();
226 final String property = toString(identifier);
227 AbstractExecutor.Get executor;
228 // first try for a getFoo() type of property (also getfoo() )
229 if (property != null) {
230 executor = new PropertyGetExecutor(this, claz, property);
231 if (executor.isAlive()) {
232 return executor;
233 }
234 //}
235 // look for boolean isFoo()
236 //if (property != null) {
237 executor = new BooleanGetExecutor(this, claz, property);
238 if (executor.isAlive()) {
239 return executor;
240 }
241 }
242 // let's see if we are a map...
243 executor = new MapGetExecutor(this, claz, identifier);
244 if (executor.isAlive()) {
245 return executor;
246 }
247 // let's see if we can convert the identifier to an int,
248 // if obj is an array or a list, we can still do something
249 Integer index = toInteger(identifier);
250 if (index != null) {
251 executor = new ListGetExecutor(this, claz, index);
252 if (executor.isAlive()) {
253 return executor;
254 }
255 }
256 // if that didn't work, look for set("foo")
257 executor = new DuckGetExecutor(this, claz, identifier);
258 if (executor.isAlive()) {
259 return executor;
260 }
261 // if that didn't work, look for set("foo")
262 executor = new DuckGetExecutor(this, claz, property);
263 if (executor.isAlive()) {
264 return executor;
265 }
266 return null;
267 }
268
269 /**
270 * Return a property setter.
271 * @param obj the object to base the property from.
272 * @param identifier property name (or identifier)
273 * @param arg value to set
274 * @return a {@link AbstractExecutor.Set}.
275 */
276 public final AbstractExecutor.Set getSetExecutor(final Object obj, final Object identifier, Object arg) {
277 final Class<?> claz = obj.getClass();
278 final String property = toString(identifier);
279 AbstractExecutor.Set executor;
280 // first try for a setFoo() type of property (also setfoo() )
281 if (property != null) {
282 executor = new PropertySetExecutor(this, claz, property, arg);
283 if (executor.isAlive()) {
284 return executor;
285 }
286 }
287 // let's see if we are a map...
288 executor = new MapSetExecutor(this, claz, identifier, arg);
289 if (executor.isAlive()) {
290 return executor;
291 }
292 // let's see if we can convert the identifier to an int,
293 // if obj is an array or a list, we can still do something
294 Integer index = toInteger(identifier);
295 if (index != null) {
296 executor = new ListSetExecutor(this, claz, index, arg);
297 if (executor.isAlive()) {
298 return executor;
299 }
300 }
301 // if that didn't work, look for set(foo)
302 executor = new DuckSetExecutor(this, claz, identifier, arg);
303 if (executor.isAlive()) {
304 return executor;
305 }
306 // if that didn't work, look for set("foo")
307 executor = new DuckSetExecutor(this, claz, property, arg);
308 if (executor.isAlive()) {
309 return executor;
310 }
311 return null;
312 }
313 }