View Javadoc

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 }