    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
    018package org.apache.commons.jexl3.internal.introspection;
    019
    020
    021import org.apache.commons.jexl3.introspection.JexlPropertyGet;
    022import java.lang.reflect.Method;
    023import java.beans.IntrospectionException;
    024
    025/**
    026 * Abstract an indexed property container.
    027 * <p>This allows getting properties from expressions like <code>var.container.property</code>.
    028 * This stores the container name and class as well as the list of available getter and setter methods.
    029 * It implements JexlPropertyGet since such a container can only be accessed from its owning instance (not set).
    030 */
    031public final class IndexedType implements JexlPropertyGet {
    032     /** The container name. */
    033    private final String container;
    034    /** The container class. */
    035    private final Class<?> clazz;
    036    /** The array of getter methods. */
    037    private final Method[] getters;
    038    /** Last get method used. */
    039    private volatile Method get = null;
    040    /** The array of setter methods. */
    041    private final Method[] setters;
    042    /** Last set method used. */
    043    private volatile Method set = null;
    044
    045    /**
    046     * Attempts to find an indexed-property getter in an object.
    047     * The code attempts to find the list of methods getXXX() and setXXX().
    048     * Note that this is not equivalent to the strict bean definition of indexed properties; the type of the key
    049     * is not necessarily an int and the set/get arrays are not resolved.
    050     *
    051     * @param is the introspector
    052     * @param object the object
    053     * @param name the container name
    054     * @return a JexlPropertyGet is successful, null otherwise
    055     */
    056    public static JexlPropertyGet discover(final Introspector is, final Object object, final String name) {
    057        if (object != null && name != null && !name.isEmpty()) {
    058            final String base = name.substring(0, 1).toUpperCase() + name.substring(1);
    059            final String container = name;
    060            final Class<?> clazz = object.getClass();
    061            final Method[] getters = is.getMethods(object.getClass(), "get" + base);
    062            final Method[] setters = is.getMethods(object.getClass(), "set" + base);
    063            if (getters != null) {
    064                return new IndexedType(container, clazz, getters, setters);
    065            }
    066        }
    067        return null;
    068    }
    069
    070    /**
    071     * A generic indexed property container, exposes get(key) and set(key, value)
    072     * and solves method call dynamically based on arguments.
    073     * <p>Must remain public for introspection purpose.</p>
    074     */
    075    public static final class IndexedContainer {
    076        /** The container instance. */
    077        private final Object container;
    078        /** The container type instance. */
    079        private final IndexedType type;
    080
    081        /**
    082         * Creates a new duck container.
    083         * @param theType the container type
    084         * @param theContainer the container instance
    085         */
    086        private IndexedContainer(final IndexedType theType, final Object theContainer) {
    087            this.type = theType;
    088            this.container = theContainer;
    089        }
    090
    091        /**
    092         * Gets the property container name.
    093         * @return the container name
    094         */
    095        public String getContainerName() {
    096            return type.container;
    097        }
    098
    099        /**
    100         * Gets the property container class.
    101         * @return the container class
    102         */
    103        public Class<?> getContainerClass() {
    104            return type.clazz;
    105        }
    106
    107        /**
    108         * Gets a property from this indexed container.
    109         * @param key the property key
    110         * @return the property value
    111         * @throws Exception if inner invocation fails
    112         */
    113        public Object get(final Object key) throws Exception {
    114            return type.invokeGet(container, key);
    115        }
    116
    117        /**
    118         * Sets a property in this indexed container.
    119         * @param key the property key
    120         * @param value the property value
    121         * @return the invocation result (frequently null)
    122         * @throws Exception if inner invocation fails
    123         */
    124        public Object set(final Object key, final Object value) throws Exception {
    125            return type.invokeSet(container, key, value);
    126        }
    127    }
    128
    129    /**
    130     * Creates a new indexed property container type.
    131     * @param name the container name
    132     * @param c the owning class
    133     * @param gets the array of getter methods
    134     * @param sets the array of setter methods
    135     */
    136    private IndexedType(final String name, final Class<?> c, final Method[] gets, final Method[] sets) {
    137        this.container = name;
    138        this.clazz = c;
    139        this.getters = gets;
    140        this.setters = sets;
    141    }
    142
    143    @Override
    144    public Object invoke(final Object obj) throws Exception {
    145        if (obj != null && clazz.equals(obj.getClass())) {
    146            return new IndexedContainer(this, obj);
    147        }
    148        throw new IntrospectionException("property resolution error");
    149    }
    150
    151    @Override
    152    public Object tryInvoke(final Object obj, final Object key) {
    153        if (obj != null && key != null
    154            && clazz.equals(obj.getClass())
    155            && container.equals(key.toString())) {
    156            return new IndexedContainer(this, obj);
    157        }
    158        return Uberspect.TRY_FAILED;
    159    }
    160
    161    @Override
    162    public boolean tryFailed(final Object rval) {
    163        return rval == Uberspect.TRY_FAILED;
    164    }
    165
    166    @Override
    167    public boolean isCacheable() {
    168        return true;
    169    }
    170
    171    /**
    172     * Gets the value of a property from a container.
    173     * @param object the container instance (not null)
    174     * @param key the property key (not null)
    175     * @return the property value
    176     * @throws Exception if invocation failed;
    177     *         IntrospectionException if a property getter could not be found
    178     */
    179    private Object invokeGet(final Object object, final Object key) throws Exception {
    180        if (getters != null && getters.length > 0) {
    181            Method jm = get;
    182            if (jm != null) {
    183                final Class<?>[] ptypes = jm.getParameterTypes();
    184                if (ptypes[0].isAssignableFrom(key.getClass())) {
    185                    return jm.invoke(object, key);
    186                }
    187            }
    188            final Object[] args = {key};
    189            final String mname = getters[0].getName();
    190            final MethodKey km = new MethodKey(mname, args);
    191            jm = km.getMostSpecificMethod(getters);
    192            if (jm != null) {
    193                final Object invoked = jm.invoke(object, args);
    194                get = jm;
    195                return invoked;
    196            }
    197        }
    198        throw new IntrospectionException("property get error: "
    199                + object.getClass().toString()
    200                + "@" + key.toString());
    201    }
    202
    203    /**
    204     * Sets the value of a property in a container.
    205     * @param object the container instance (not null)
    206     * @param key the property key (not null)
    207     * @param value the property value (not null)
    208     * @return the result of the method invocation (frequently null)
    209     * @throws Exception if invocation failed;
    210     *         IntrospectionException if a property setter could not be found
    211     */
    212    private Object invokeSet(final Object object, final Object key, final Object value) throws Exception {
    213        if (setters != null && setters.length > 0) {
    214            Method jm = set;
    215            if (jm != null) {
    216                final Class<?>[] ptypes = jm.getParameterTypes();
    217                if (ptypes[0].isAssignableFrom(key.getClass())
    218                    && (value == null
    219                        || ptypes[1].isAssignableFrom(value.getClass()))) {
    220                    return jm.invoke(object, key, value);
    221                }
    222            }
    223            final Object[] args = {key, value};
    224            final String mname = setters[0].getName();
    225            final MethodKey km = new MethodKey(mname, args);
    226            jm = km.getMostSpecificMethod(setters);
    227            if (jm != null) {
    228                final Object invoked = jm.invoke(object, args);
    229                set = jm;
    230                return invoked;
    231            }
    232        }
    233        throw new IntrospectionException("property set error: "
    234                + object.getClass().toString()
    235                + "@" + key.toString());
    236    }
    237
    238}