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 */
017package org.apache.commons.jexl2.internal;
018
019import java.lang.ref.SoftReference;
020import java.lang.reflect.Method;
021import java.lang.reflect.Constructor;
022import java.lang.reflect.Field;
023
024import org.apache.commons.jexl2.internal.introspection.IntrospectorBase;
025import org.apache.commons.jexl2.internal.introspection.MethodKey;
026
027import org.apache.commons.logging.Log;
028
029/**
030 *  Default introspection services.
031 *  <p>Finding methods as well as property getters &amp; setters.</p>
032 * @since 1.0
033 */
034public class Introspector {
035    /** The logger to use for all warnings &amp; 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}