    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.introspection;
    018
    019    import java.beans.IntrospectionException;
    020    import org.apache.commons.jexl2.internal.Introspector;
    021    import java.lang.reflect.Constructor;
    022    import java.lang.reflect.Field;
    023    import java.lang.reflect.Modifier;
    024    import java.lang.reflect.InvocationTargetException;
    025
    026    import java.lang.reflect.Method;
    027    import java.util.Arrays;
    028    import java.util.Enumeration;
    029    import java.util.Iterator;
    030    import java.util.Map;
    031
    032    import org.apache.commons.jexl2.JexlInfo;
    033    import org.apache.commons.jexl2.JexlException;
    034    import org.apache.commons.jexl2.internal.AbstractExecutor;
    035    import org.apache.commons.jexl2.internal.ArrayIterator;
    036    import org.apache.commons.jexl2.internal.EnumerationIterator;
    037    import org.apache.commons.jexl2.internal.introspection.MethodKey;
    038    import org.apache.commons.logging.Log;
    039
    040    /**
    041     * Implementation of Uberspect to provide the default introspective
    042     * functionality of JEXL.
    043     * <p>This is the class to derive to customize introspection.</p>
    044     *
    045     * @since 1.0
    046     */
    047    public class UberspectImpl extends Introspector implements Uberspect {
    048        /**
    049         * Publicly exposed special failure object returned by tryInvoke.
    050         */
    051        public static final Object TRY_FAILED = AbstractExecutor.TRY_FAILED;
    052
    053        /**
    054         * Creates a new UberspectImpl.
    055         * @param runtimeLogger the logger used for all logging needs
    056         */
    057        public UberspectImpl(Log runtimeLogger) {
    058            super(runtimeLogger);
    059        }
    060
    061        /**
    062         * Resets this Uberspect class loader.
    063         * @param cloader the class loader to use
    064         * @since 2.1
    065         */
    066        public void setLoader(ClassLoader cloader) {
    067            base().setLoader(cloader);
    068        }
    069
    070        /**
    071         * {@inheritDoc}
    072         */
    073        @SuppressWarnings("unchecked")
    074        public Iterator<?> getIterator(Object obj, JexlInfo info) {
    075            if (obj instanceof Iterator<?>) {
    076                return ((Iterator<?>) obj);
    077            }
    078            if (obj.getClass().isArray()) {
    079                return new ArrayIterator(obj);
    080            }
    081            if (obj instanceof Map<?, ?>) {
    082                return ((Map<?, ?>) obj).values().iterator();
    083            }
    084            if (obj instanceof Enumeration<?>) {
    085                return new EnumerationIterator<Object>((Enumeration<Object>) obj);
    086            }
    087            if (obj instanceof Iterable<?>) {
    088                return ((Iterable<?>) obj).iterator();
    089            }
    090            try {
    091                // look for an iterator() method to support the JDK5 Iterable
    092                // interface or any user tools/DTOs that want to work in
    093                // foreach without implementing the Collection interface
    094                AbstractExecutor.Method it = getMethodExecutor(obj, "iterator", null);
    095                if (it != null && Iterator.class.isAssignableFrom(it.getReturnType())) {
    096                    return (Iterator<Object>) it.execute(obj, null);
    097                }
    098            } catch (Exception xany) {
    099                throw new JexlException(info, "unable to generate iterator()", xany);
    100            }
    101            return null;
    102        }
    103
    104        /**
    105         * {@inheritDoc}
    106         */
    107        public JexlMethod getMethod(Object obj, String method, Object[] args, JexlInfo info) {
    108            return getMethodExecutor(obj, method, args);
    109        }
    110
    111        /**
    112         * {@inheritDoc}
    113         */
    114        @Deprecated
    115        public Constructor<?> getConstructor(Object ctorHandle, Object[] args, JexlInfo info) {
    116            return getConstructor(ctorHandle, args);
    117        }
    118
    119        /**
    120         * {@inheritDoc}
    121         * @since 2.1
    122         */
    123        public JexlMethod getConstructorMethod(Object ctorHandle, Object[] args, JexlInfo info) {
    124            final Constructor<?> ctor = getConstructor(ctorHandle, args);
    125            if (ctor != null) {
    126                return new ConstructorMethod(ctor);
    127            } else {
    128                return null;
    129            }
    130        }
    131
    132        /**
    133         * {@inheritDoc}
    134         */
    135        public JexlPropertyGet getPropertyGet(Object obj, Object identifier, JexlInfo info) {
    136            JexlPropertyGet get = getGetExecutor(obj, identifier);
    137            if (get == null && obj != null && identifier != null) {
    138                get = getIndexedGet(obj, identifier.toString());
    139                if (get == null) {
    140                    Field field = getField(obj, identifier.toString(), info);
    141                    if (field != null) {
    142                        return new FieldPropertyGet(field);
    143                    }
    144                }
    145            }
    146            return get;
    147        }
    148
    149        /**
    150         * {@inheritDoc}
    151         */
    152        public JexlPropertySet getPropertySet(final Object obj, final Object identifier, Object arg, JexlInfo info) {
    153            JexlPropertySet set = getSetExecutor(obj, identifier, arg);
    154            if (set == null && obj != null && identifier != null) {
    155                Field field = getField(obj, identifier.toString(), info);
    156                if (field != null
    157                        && !Modifier.isFinal(field.getModifiers())
    158                        && (arg == null || MethodKey.isInvocationConvertible(field.getType(), arg.getClass(), false))) {
    159                    return new FieldPropertySet(field);
    160                }
    161            }
    162            return set;
    163        }
    164
    165        /**
    166         * Returns a class field.
    167         * Only for use by sub-classes, will be made protected in a later version
    168         * @param obj the object
    169         * @param name the field name
    170         * @param info debug info
    171         * @return a {@link Field}.
    172         */
    173        public Field getField(Object obj, String name, JexlInfo info) {
    174            final Class<?> clazz = obj instanceof Class<?> ? (Class<?>) obj : obj.getClass();
    175            return getField(clazz, name);
    176        }
    177
    178        /**
    179         * Attempts to find an indexed-property getter in an object.
    180         * The code attempts to find the list of methods getXXX() and setXXX().
    181         * Note that this is not equivalent to the strict bean definition of indexed properties; the type of the key
    182         * is not necessarily an int and the set/get arrays are not resolved.
    183         * @param object the object
    184         * @param name the container name
    185         * @return a JexlPropertyGet is successfull, null otherwise
    186         * @since 2.1
    187         */
    188        protected JexlPropertyGet getIndexedGet(Object object, String name) {
    189            if (object != null && name != null) {
    190                String base = name.substring(0, 1).toUpperCase() + name.substring(1);
    191                final String container = name;
    192                final Class<?> clazz = object.getClass();
    193                final Method[] getters = getMethods(object.getClass(), "get" + base);
    194                final Method[] setters = getMethods(object.getClass(), "set" + base);
    195                if (getters != null) {
    196                    return new IndexedType(container, clazz, getters, setters);
    197                }
    198            }
    199            return null;
    200        }
    201
    202        /**
    203         * Abstract an indexed property container.
    204         * This stores the container name and owning class as well as the list of available getter and setter methods.
    205         * It implements JexlPropertyGet since such a container can only be accessed from its owning instance (not set).
    206         * @since 2.1
    207         */
    208        private static final class IndexedType implements JexlPropertyGet {
    209            /** The container name. */
    210            private final String container;
    211            /** The owning class. */
    212            private final Class<?> clazz;
    213            /** The array of getter methods. */
    214            private final Method[] getters;
    215            /** The array of setter methods. */
    216            private final Method[] setters;
    217
    218            /**
    219             * Creates a new indexed type.
    220             * @param name the container name
    221             * @param c the owning class
    222             * @param gets the array of getter methods
    223             * @param sets the array of setter methods
    224             */
    225            IndexedType(String name, Class<?> c, Method[] gets, Method[] sets) {
    226                this.container = name;
    227                this.clazz = c;
    228                this.getters = gets;
    229                this.setters = sets;
    230            }
    231
    232            /**
    233             * {@inheritDoc}
    234             */
    235            public Object invoke(Object obj) throws Exception {
    236                if (obj != null && clazz.equals(obj.getClass())) {
    237                    return new IndexedContainer(this, obj);
    238                } else {
    239                    throw new IntrospectionException("property resolution error");
    240                }
    241            }
    242
    243            /**
    244             * {@inheritDoc}
    245             */
    246            public Object tryInvoke(Object obj, Object key) {
    247                if (obj != null && key != null && clazz.equals(obj.getClass()) && container.equals(key.toString())) {
    248                    return new IndexedContainer(this, obj);
    249                } else {
    250                    return TRY_FAILED;
    251                }
    252            }
    253
    254            /**
    255             * {@inheritDoc}
    256             */
    257            public boolean tryFailed(Object rval) {
    258                return rval == TRY_FAILED;
    259            }
    260
    261            /**
    262             * {@inheritDoc}
    263             */
    264            public boolean isCacheable() {
    265                return true;
    266            }
    267
    268            /**
    269             * Gets the value of a property from a container.
    270             * @param object the instance owning the container (not null)
    271             * @param key the property key (not null)
    272             * @return the property value
    273             * @throws Exception if invocation failed; IntrospectionException if a property getter could not be found
    274             */
    275            private Object invokeGet(Object object, Object key) throws Exception {
    276                if (getters != null) {
    277                    final Object[] args = {key};
    278                    final Method jm;
    279                    if (getters.length == 1) {
    280                        jm = getters[0];
    281                    } else {
    282                        jm = new MethodKey(getters[0].getName(), args).getMostSpecificMethod(Arrays.asList(getters));
    283                    }
    284                    if (jm != null) {
    285                        return jm.invoke(object, args);
    286                    }
    287                }
    288                throw new IntrospectionException("property get error: "
    289                        + object.getClass().toString() + "@" + key.toString());
    290            }
    291
    292            /**
    293             * Sets the value of a property in a container.
    294             * @param object the instance owning the container (not null)
    295             * @param key the property key (not null)
    296             * @param value the property value (not null)
    297             * @return the result of the method invocation (frequently null)
    298             * @throws Exception if invocation failed; IntrospectionException if a property setter could not be found
    299             */
    300            private Object invokeSet(Object object, Object key, Object value) throws Exception {
    301                if (setters != null) {
    302                    final Object[] args = {key, value};
    303                    final Method jm;
    304                    if (setters.length == 1) {
    305                        jm = setters[0];
    306                    } else {
    307                        jm = new MethodKey(setters[0].getName(), args).getMostSpecificMethod(Arrays.asList(setters));
    308                    }
    309                    if (jm != null) {
    310                        return jm.invoke(object, args);
    311                    }
    312                }
    313                throw new IntrospectionException("property set error: "
    314                        + object.getClass().toString() + "@" + key.toString());
    315            }
    316        }
    317
    318        /**
    319         * A generic indexed property container, exposes get(key) and set(key, value) and solves method call dynamically
    320         * based on arguments.
    321         * @since 2.1
    322         */
    323        public static final class IndexedContainer {
    324            /** The instance owning the container. */
    325            private final Object object;
    326            /** The container type instance. */
    327            private final IndexedType type;
    328
    329            /**
    330             * Creates a new duck container.
    331             * @param theType the container type
    332             * @param theObject the instance owning the container
    333             */
    334            private IndexedContainer(IndexedType theType, Object theObject) {
    335                this.type = theType;
    336                this.object = theObject;
    337            }
    338
    339            /**
    340             * Gets a property from a container.
    341             * @param key the property key
    342             * @return the property value
    343             * @throws Exception if inner invocation fails
    344             */
    345            public Object get(Object key) throws Exception {
    346                return type.invokeGet(object, key);
    347            }
    348
    349            /**
    350             * Sets a property in a container.
    351             * @param key the property key
    352             * @param value the property value
    353             * @return the invocation result (frequently null)
    354             * @throws Exception if inner invocation fails
    355             */
    356            public Object set(Object key, Object value) throws Exception {
    357                return type.invokeSet(object, key, value);
    358            }
    359        }
    360
    361        /**
    362         * A JexlMethod that wraps constructor.
    363         * @since 2.1
    364         */
    365        private final class ConstructorMethod implements JexlMethod {
    366            /** The wrapped constructor. */
    367            private final Constructor<?> ctor;
    368
    369            /**
    370             * Creates a constructor method.
    371             * @param theCtor the constructor to wrap
    372             */
    373            private ConstructorMethod(Constructor<?> theCtor) {
    374                this.ctor = theCtor;
    375            }
    376
    377            /**
    378             * {@inheritDoc}
    379             */
    380            public Object invoke(Object obj, Object[] params) throws Exception {
    381                Class<?> clazz = null;
    382                if (obj instanceof Class<?>) {
    383                    clazz = (Class<?>) obj;
    384                } else if (obj != null) {
    385                    clazz = getClassByName(obj.toString());
    386                } else {
    387                    clazz = ctor.getDeclaringClass();
    388                }
    389                if (clazz.equals(ctor.getDeclaringClass())) {
    390                    return ctor.newInstance(params);
    391                } else {
    392                    throw new IntrospectionException("constructor resolution error");
    393                }
    394            }
    395
    396            /**
    397             * {@inheritDoc}
    398             */
    399            public Object tryInvoke(String name, Object obj, Object[] params) {
    400                Class<?> clazz = null;
    401                if (obj instanceof Class<?>) {
    402                    clazz = (Class<?>) obj;
    403                } else if (obj != null) {
    404                    clazz = getClassByName(obj.toString());
    405                } else {
    406                    clazz = ctor.getDeclaringClass();
    407                }
    408                if (clazz.equals(ctor.getDeclaringClass())
    409                        && (name == null || name.equals(clazz.getName()))) {
    410                    try {
    411                        return ctor.newInstance(params);
    412                    } catch (InstantiationException xinstance) {
    413                        return TRY_FAILED;
    414                    } catch (IllegalAccessException xaccess) {
    415                        return TRY_FAILED;
    416                    } catch (IllegalArgumentException xargument) {
    417                        return TRY_FAILED;
    418                    } catch (InvocationTargetException xinvoke) {
    419                        return TRY_FAILED;
    420                    }
    421                }
    422                return TRY_FAILED;
    423            }
    424
    425            /**
    426             * {@inheritDoc}
    427             */
    428            public boolean tryFailed(Object rval) {
    429                return rval == TRY_FAILED;
    430            }
    431
    432            /**
    433             * {@inheritDoc}
    434             */
    435            public boolean isCacheable() {
    436                return true;
    437            }
    438
    439            /**
    440             * {@inheritDoc}
    441             */
    442            public Class<?> getReturnType() {
    443                return ctor.getDeclaringClass();
    444            }
    445        }
    446
    447        /**
    448         * A JexlPropertyGet for public fields.
    449         * @deprecated Do not use externally - will be made private in a later version
    450         */
    451        @Deprecated
    452        public static final class FieldPropertyGet implements JexlPropertyGet {
    453            /**
    454             * The public field.
    455             */
    456            private final Field field;
    457
    458            /**
    459             * Creates a new instance of FieldPropertyGet.
    460             * @param theField the class public field
    461             */
    462            public FieldPropertyGet(Field theField) {
    463                field = theField;
    464            }
    465
    466            /**
    467             * {@inheritDoc}
    468             */
    469            public Object invoke(Object obj) throws Exception {
    470                return field.get(obj);
    471            }
    472
    473            /**
    474             * {@inheritDoc}
    475             */
    476            public Object tryInvoke(Object obj, Object key) {
    477                if (obj.getClass().equals(field.getDeclaringClass()) && key.equals(field.getName())) {
    478                    try {
    479                        return field.get(obj);
    480                    } catch (IllegalAccessException xill) {
    481                        return TRY_FAILED;
    482                    }
    483                }
    484                return TRY_FAILED;
    485            }
    486
    487            /**
    488             * {@inheritDoc}
    489             */
    490            public boolean tryFailed(Object rval) {
    491                return rval == TRY_FAILED;
    492            }
    493
    494            /**
    495             * {@inheritDoc}
    496             */
    497            public boolean isCacheable() {
    498                return true;
    499            }
    500        }
    501
    502        /**
    503         * A JexlPropertySet for public fields.
    504        * @deprecated Do not use externally - will be made private in a later version
    505         */
    506        @Deprecated
    507        public static final class FieldPropertySet implements JexlPropertySet {
    508            /**
    509             * The public field.
    510             */
    511            private final Field field;
    512
    513            /**
    514             * Creates a new instance of FieldPropertySet.
    515             * @param theField the class public field
    516             */
    517            public FieldPropertySet(Field theField) {
    518                field = theField;
    519            }
    520
    521            /**
    522             * {@inheritDoc}
    523             */
    524            public Object invoke(Object obj, Object arg) throws Exception {
    525                field.set(obj, arg);
    526                return arg;
    527            }
    528
    529            /**
    530             * {@inheritDoc}
    531             */
    532            public Object tryInvoke(Object obj, Object key, Object value) {
    533                if (obj.getClass().equals(field.getDeclaringClass())
    534                        && key.equals(field.getName())
    535                        && (value == null || MethodKey.isInvocationConvertible(field.getType(), value.getClass(), false))) {
    536                    try {
    537                        field.set(obj, value);
    538                        return value;
    539                    } catch (IllegalAccessException xill) {
    540                        return TRY_FAILED;
    541                    }
    542                }
    543                return TRY_FAILED;
    544            }
    545
    546            /**
    547             * {@inheritDoc}
    548             */
    549            public boolean tryFailed(Object rval) {
    550                return rval == TRY_FAILED;
    551            }
    552
    553            /**
    554             * {@inheritDoc}
    555             */
    556            public boolean isCacheable() {
    557                return true;
    558            }
    559        }
    560    }