    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    }