    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    }