1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17 package org.apache.commons.jexl2.internal;
18
19 import java.lang.ref.SoftReference;
20 import java.lang.reflect.Method;
21 import java.lang.reflect.Constructor;
22 import java.lang.reflect.Field;
23
24 import org.apache.commons.jexl2.internal.introspection.IntrospectorBase;
25 import org.apache.commons.jexl2.internal.introspection.MethodKey;
26
27 import org.apache.commons.logging.Log;
28
29 /**
30 * Default introspection services.
31 * <p>Finding methods as well as property getters & setters.</p>
32 * @since 1.0
33 */
34 public class Introspector {
35 /** The logger to use for all warnings & errors. */
36 protected final Log rlog;
37 /** The soft reference to the introspector currently in use. */
38 private volatile SoftReference<IntrospectorBase> ref;
39
40 /**
41 * Creates an introspector.
42 * @param log the logger to use for warnings.
43 */
44 protected Introspector(Log log) {
45 rlog = log;
46 ref = new SoftReference<IntrospectorBase>(null);
47 }
48
49 /**
50 * Coerce an Object to an Integer.
51 * @param arg the Object to coerce
52 * @return an Integer if it can be converted, null otherwise
53 */
54 protected Integer toInteger(Object arg) {
55 if (arg == null) {
56 return null;
57 }
58 if (arg instanceof Number) {
59 return Integer.valueOf(((Number) arg).intValue());
60 }
61 try {
62 return Integer.valueOf(arg.toString());
63 } catch (NumberFormatException xnumber) {
64 return null;
65 }
66 }
67
68 /**
69 * Coerce an Object to a String.
70 * @param arg the Object to coerce
71 * @return a String if it can be converted, null otherwise
72 */
73 protected String toString(Object arg) {
74 return arg == null ? null : arg.toString();
75 }
76
77 /**
78 * Gets the current introspector base.
79 * <p>If the reference has been collected, this method will recreate the underlying introspector.</p>
80 * @return the introspector
81 */
82 // CSOFF: DoubleCheckedLocking
83 protected final IntrospectorBase base() {
84 IntrospectorBase intro = ref.get();
85 if (intro == null) {
86 // double checked locking is ok (fixed by Java 5 memory model).
87 synchronized(this) {
88 intro = ref.get();
89 if (intro == null) {
90 intro = new IntrospectorBase(rlog);
91 ref = new SoftReference<IntrospectorBase>(intro);
92 }
93 }
94 }
95 return intro;
96 }
97 // CSON: DoubleCheckedLocking
98
99 /**
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 }