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 (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 /**
109 * Gets the field named by <code>key</code> for the class <code>c</code>.
110 *
111 * @param c Class in which the field search is taking place
112 * @param key Name of the field being searched for
113 * @return the desired field or null if it does not exist or is not accessible
114 * */
115 protected final Field getField(Class<?> c, String key) {
116 return base().getField(c, key);
117 }
118
119 /**
120 * Gets the accessible field names known for a given class.
121 * @param c the class
122 * @return the class field names
123 */
124 public final String[] getFieldNames(Class<?> c) {
125 return base().getFieldNames(c);
126 }
127
128 /**
129 * Gets the method defined by <code>name</code> and
130 * <code>params</code> for the Class <code>c</code>.
131 *
132 * @param c Class in which the method search is taking place
133 * @param name Name of the method being searched for
134 * @param params An array of Objects (not Classes) that describe the
135 * the parameters
136 *
137 * @return The desired Method object.
138 * @throws IllegalArgumentException When the parameters passed in can not be used for introspection.
139 * CSOFF: RedundantThrows
140 */
141 protected final Method getMethod(Class<?> c, String name, Object[] params) throws IllegalArgumentException {
142 return base().getMethod(c, new MethodKey(name, params));
143 }
144
145 /**
146 * Gets the method defined by <code>key</code> and for the Class <code>c</code>.
147 *
148 * @param c Class in which the method search is taking place
149 * @param key MethodKey of the method being searched for
150 *
151 * @return The desired Method object.
152 * @throws IllegalArgumentException When the parameters passed in can not be used for introspection.
153 * CSOFF: RedundantThrows
154 */
155 protected final Method getMethod(Class<?> c, MethodKey key) throws IllegalArgumentException {
156 return base().getMethod(c, key);
157 }
158
159
160 /**
161 * Gets the accessible methods names known for a given class.
162 * @param c the class
163 * @return the class method names
164 */
165 public final String[] getMethodNames(Class<?> c) {
166 return base().getMethodNames(c);
167 }
168
169 /**
170 * Returns a general constructor.
171 * @param ctorHandle the object
172 * @param args contrusctor arguments
173 * @return a {@link java.lang.reflect.Constructor}
174 */
175 public final Constructor<?> getConstructor(Object ctorHandle, Object[] args) {
176 String className = null;
177 Class<?> clazz = null;
178 if (ctorHandle instanceof Class<?>) {
179 clazz = (Class<?>) ctorHandle;
180 className = clazz.getName();
181 } else if (ctorHandle != null) {
182 className = ctorHandle.toString();
183 } else {
184 return null;
185 }
186 return base().getConstructor(clazz, new MethodKey(className, args));
187 }
188
189 /**
190 * Returns a general method.
191 * @param obj the object
192 * @param name the method name
193 * @param args method arguments
194 * @return a {@link AbstractExecutor.Method}.
195 */
196 public final AbstractExecutor.Method getMethodExecutor(Object obj, String name, Object[] args) {
197 AbstractExecutor.Method me = new MethodExecutor(this, obj, name, args);
198 return me.isAlive() ? me : null;
199 }
200
201 /**
202 * Return a property getter.
203 * @param obj the object to base the property from.
204 * @param identifier property name
205 * @return a {@link AbstractExecutor.Get}.
206 */
207 public final AbstractExecutor.Get getGetExecutor(Object obj, Object identifier) {
208 final Class<?> claz = obj.getClass();
209 final String property = toString(identifier);
210 AbstractExecutor.Get executor;
211 // first try for a getFoo() type of property (also getfoo() )
212 if (property != null) {
213 executor = new PropertyGetExecutor(this, claz, property);
214 if (executor.isAlive()) {
215 return executor;
216 }
217 }
218 // look for boolean isFoo()
219 if (property != null) {
220 executor = new BooleanGetExecutor(this, claz, property);
221 if (executor.isAlive()) {
222 return executor;
223 }
224 }
225 // let's see if we are a map...
226 executor = new MapGetExecutor(this, claz, identifier);
227 if (executor.isAlive()) {
228 return executor;
229 }
230 // let's see if we can convert the identifier to an int,
231 // if obj is an array or a list, we can still do something
232 Integer index = toInteger(identifier);
233 if (index != null) {
234 executor = new ListGetExecutor(this, claz, index);
235 if (executor.isAlive()) {
236 return executor;
237 }
238 }
239 // if that didn't work, look for set("foo")
240 executor = new DuckGetExecutor(this, claz, identifier);
241 if (executor.isAlive()) {
242 return executor;
243 }
244 return null;
245 }
246
247 /**
248 * Return a property setter.
249 * @param obj the object to base the property from.
250 * @param identifier property name (or identifier)
251 * @param arg value to set
252 * @return a {@link AbstractExecutor.Set}.
253 */
254 public final AbstractExecutor.Set getSetExecutor(final Object obj, final Object identifier, Object arg) {
255 final Class<?> claz = obj.getClass();
256 final String property = toString(identifier);
257 AbstractExecutor.Set executor;
258 // first try for a setFoo() type of property (also setfoo() )
259 if (property != null) {
260 executor = new PropertySetExecutor(this, claz, property, arg);
261 if (executor.isAlive()) {
262 return executor;
263 }
264 }
265 // let's see if we are a map...
266 executor = new MapSetExecutor(this, claz, identifier, arg);
267 if (executor.isAlive()) {
268 return executor;
269 }
270 // let's see if we can convert the identifier to an int,
271 // if obj is an array or a list, we can still do something
272 Integer index = toInteger(identifier);
273 if (index != null) {
274 executor = new ListSetExecutor(this, claz, index, arg);
275 if (executor.isAlive()) {
276 return executor;
277 }
278 }
279 // if that didn't work, look for set("foo")
280 executor = new DuckSetExecutor(this, claz, property, arg);
281 if (executor.isAlive()) {
282 return executor;
283 }
284 return null;
285 }
286 }