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 org.apache.commons.jexl2.internal.Introspector;
020    import java.lang.reflect.Constructor;
021    import java.lang.reflect.Field;
022    import java.lang.reflect.Modifier;
023    import java.util.Enumeration;
024    import java.util.Iterator;
025    
026    import java.util.Map;
027    import org.apache.commons.jexl2.JexlInfo;
028    import org.apache.commons.jexl2.JexlException;
029    import org.apache.commons.jexl2.internal.AbstractExecutor;
030    import org.apache.commons.jexl2.internal.ArrayIterator;
031    import org.apache.commons.jexl2.internal.EnumerationIterator;
032    import org.apache.commons.jexl2.internal.introspection.MethodKey;
033    import org.apache.commons.logging.Log;
034    
035    /**
036     * Implementation of Uberspect to provide the default introspective
037     * functionality of JEXL.
038     * <p>This is the class to derive to customize introspection.</p>
039     *
040     * @since 1.0
041     * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
042     * @author <a href="mailto:henning@apache.org">Henning P. Schmiedehausen</a>
043     * @version $Id: UberspectImpl.java 896952 2010-01-07 18:21:29Z henrib $
044     */
045    public class UberspectImpl extends Introspector implements Uberspect {
046        /**
047         * Publicly exposed special failure object returned by tryInvoke.
048         */
049        public static final Object TRY_FAILED = AbstractExecutor.TRY_FAILED;
050        
051        /**
052         * Creates a new UberspectImpl.
053         * @param runtimeLogger the logger used for all logging needs
054         */
055        public UberspectImpl(Log runtimeLogger) {
056            super(runtimeLogger);
057        }
058    
059        /**
060         * {@inheritDoc}
061         */
062        @SuppressWarnings("unchecked")
063        public Iterator<?> getIterator(Object obj, JexlInfo info) {
064            if (obj instanceof Iterator<?>) {
065                return ((Iterator<?>) obj);
066            }
067            if (obj.getClass().isArray()) {
068                return new ArrayIterator(obj);
069            }
070            if (obj instanceof Map<?,?>) {
071                return ((Map<?,?>) obj).values().iterator();
072            }
073            if (obj instanceof Enumeration<?>) {
074                return new EnumerationIterator<Object>((Enumeration<Object>) obj);
075            }
076            if (obj instanceof Iterable<?>) {
077                return ((Iterable<?>) obj).iterator();
078            }
079            try {
080                // look for an iterator() method to support the JDK5 Iterable
081                // interface or any user tools/DTOs that want to work in
082                // foreach without implementing the Collection interface
083                AbstractExecutor.Method it = getMethodExecutor(obj, "iterator", null);
084                if (it != null && Iterator.class.isAssignableFrom(it.getReturnType())) {
085                    return (Iterator<Object>) it.execute(obj, null);
086                }
087            } catch(Exception xany) {
088                throw new JexlException(info, "unable to generate iterator()", xany);
089            }
090            return null;
091        }
092    
093        /**
094         * Returns a class field.
095         * @param obj the object
096         * @param name the field name
097         * @param info debug info
098         * @return a {@link Field}.
099         */
100        public Field getField(Object obj, String name, JexlInfo info) {
101            final Class<?> clazz = obj instanceof Class<?>? (Class<?>) obj : obj.getClass();
102            return getField(clazz, name);
103        }
104    
105        /**
106         * {@inheritDoc}
107         */
108        public Constructor<?> getConstructor(Object ctorHandle, Object[] args, JexlInfo info) {
109            return getConstructor(ctorHandle, args);
110        }
111    
112        /**
113         * {@inheritDoc}
114         */
115        public JexlMethod getMethod(Object obj, String method, Object[] args, JexlInfo info) {
116            return getMethodExecutor(obj, method, args);
117        }
118    
119        /**
120         * A JexlPropertyGet for public fields.
121         */
122        public static final class FieldPropertyGet implements JexlPropertyGet {
123            /**
124             * The public field.
125             */
126            private final Field field;
127    
128            /**
129             * Creates a new instance of FieldPropertyGet.
130             * @param theField the class public field
131             */
132            public FieldPropertyGet(Field theField) {
133                field = theField;
134            }
135    
136            /**
137             * {@inheritDoc}
138             */
139            public Object invoke(Object obj) throws Exception {
140                return field.get(obj);
141            }
142    
143            /**
144             * {@inheritDoc}
145             */
146            public Object tryInvoke(Object obj, Object key) {
147                if (obj.getClass().equals(field.getDeclaringClass()) && key.equals(field.getName())) {
148                    try {
149                        return field.get(obj);
150                    } catch (IllegalAccessException xill) {
151                        return TRY_FAILED;
152                    }
153                }
154                return TRY_FAILED;
155            }
156    
157            /**
158             * {@inheritDoc}
159             */
160            public boolean tryFailed(Object rval) {
161                return rval == TRY_FAILED;
162            }
163    
164            /**
165             * {@inheritDoc}
166             */
167            public boolean isCacheable() {
168                return true;
169            }
170        }
171    
172        /**
173         * {@inheritDoc}
174         */
175        public JexlPropertyGet getPropertyGet(Object obj, Object identifier, JexlInfo info) {
176            JexlPropertyGet get = getGetExecutor(obj, identifier);
177            if (get == null && obj != null && identifier != null) {
178                Field field = getField(obj, identifier.toString(), info);
179                if (field != null) {
180                    return new FieldPropertyGet(field);
181                }
182            }
183            return get;
184        }
185    
186        /**
187         * A JexlPropertySet for public fields.
188         */
189        public static final class FieldPropertySet implements JexlPropertySet {
190            /**
191             * The public field.
192             */
193            private final Field field;
194    
195            /**
196             * Creates a new instance of FieldPropertySet.
197             * @param theField the class public field
198             */
199            public FieldPropertySet(Field theField) {
200                field = theField;
201            }
202    
203            /**
204             * {@inheritDoc}
205             */
206            public Object invoke(Object obj, Object arg) throws Exception {
207                field.set(obj, arg);
208                return arg;
209            }
210    
211            /**
212             * {@inheritDoc}
213             */
214            public Object tryInvoke(Object obj, Object key, Object value) {
215                if (obj.getClass().equals(field.getDeclaringClass())
216                    && key.equals(field.getName())
217                    && (value == null || MethodKey.isInvocationConvertible(field.getType(), value.getClass(), false))) {
218                    try {
219                        field.set(obj, value);
220                        return value;
221                    } catch (IllegalAccessException xill) {
222                        return TRY_FAILED;
223                    }
224                }
225                return TRY_FAILED;
226            }
227    
228            /**
229             * {@inheritDoc}
230             */
231            public boolean tryFailed(Object rval) {
232                return rval == TRY_FAILED;
233            }
234    
235            /**
236             * {@inheritDoc}
237             */
238            public boolean isCacheable() {
239                return true;
240            }
241        }
242    
243        /**
244         * {@inheritDoc}
245         */
246        public JexlPropertySet getPropertySet(final Object obj, final Object identifier, Object arg, JexlInfo info) {
247            JexlPropertySet set = getSetExecutor(obj, identifier, arg);
248            if (set == null && obj != null && identifier != null) {
249                Field field = getField(obj, identifier.toString(), info);
250                if (field != null
251                    && !Modifier.isFinal(field.getModifiers())
252                    && (arg == null || MethodKey.isInvocationConvertible(field.getType(), arg.getClass(), false))) {
253                    return new FieldPropertySet(field);
254                }
255            }
256            return set;
257        }
258    }