View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.jexl2.introspection;
18  
19  import java.beans.IntrospectionException;
20  import org.apache.commons.jexl2.internal.Introspector;
21  import java.lang.reflect.Constructor;
22  import java.lang.reflect.Field;
23  import java.lang.reflect.Modifier;
24  import java.lang.reflect.InvocationTargetException;
25  
26  import java.lang.reflect.Method;
27  import java.util.Arrays;
28  import java.util.Enumeration;
29  import java.util.Iterator;
30  import java.util.Map;
31  
32  import org.apache.commons.jexl2.JexlInfo;
33  import org.apache.commons.jexl2.JexlException;
34  import org.apache.commons.jexl2.internal.AbstractExecutor;
35  import org.apache.commons.jexl2.internal.ArrayIterator;
36  import org.apache.commons.jexl2.internal.EnumerationIterator;
37  import org.apache.commons.jexl2.internal.introspection.MethodKey;
38  import org.apache.commons.logging.Log;
39  
40  /**
41   * Implementation of Uberspect to provide the default introspective
42   * functionality of JEXL.
43   * <p>This is the class to derive to customize introspection.</p>
44   *
45   * @since 1.0
46   */
47  public class UberspectImpl extends Introspector implements Uberspect {
48      /**
49       * Publicly exposed special failure object returned by tryInvoke.
50       */
51      public static final Object TRY_FAILED = AbstractExecutor.TRY_FAILED;
52  
53      /**
54       * Creates a new UberspectImpl.
55       * @param runtimeLogger the logger used for all logging needs
56       */
57      public UberspectImpl(Log runtimeLogger) {
58          super(runtimeLogger);
59      }
60  
61      /**
62       * Resets this Uberspect class loader.
63       * @param cloader the class loader to use
64       * @since 2.1
65       */
66      public void setLoader(ClassLoader cloader) {
67          base().setLoader(cloader);
68      }
69  
70      /**
71       * {@inheritDoc}
72       */
73      @SuppressWarnings("unchecked")
74      public Iterator<?> getIterator(Object obj, JexlInfo info) {
75          if (obj instanceof Iterator<?>) {
76              return ((Iterator<?>) obj);
77          }
78          if (obj.getClass().isArray()) {
79              return new ArrayIterator(obj);
80          }
81          if (obj instanceof Map<?, ?>) {
82              return ((Map<?, ?>) obj).values().iterator();
83          }
84          if (obj instanceof Enumeration<?>) {
85              return new EnumerationIterator<Object>((Enumeration<Object>) obj);
86          }
87          if (obj instanceof Iterable<?>) {
88              return ((Iterable<?>) obj).iterator();
89          }
90          try {
91              // look for an iterator() method to support the JDK5 Iterable
92              // interface or any user tools/DTOs that want to work in
93              // foreach without implementing the Collection interface
94              AbstractExecutor.Method it = getMethodExecutor(obj, "iterator", null);
95              if (it != null && Iterator.class.isAssignableFrom(it.getReturnType())) {
96                  return (Iterator<Object>) it.execute(obj, null);
97              }
98          } catch (Exception xany) {
99              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 }