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    }