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    }