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    *      https://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  
18  package org.apache.commons.jexl3.introspection;
19  
20  import java.util.Arrays;
21  import java.util.Collection;
22  import java.util.Collections;
23  import java.util.Iterator;
24  import java.util.List;
25  import java.util.Map;
26  
27  import org.apache.commons.jexl3.JexlArithmetic;
28  import org.apache.commons.jexl3.JexlOperator;
29  
30  /**
31   * 'Federated' introspection/reflection interface to allow JEXL introspection
32   * behavior to be customized.
33   *
34   * @since 1.0
35   */
36  public interface JexlUberspect {
37  
38      /**
39       * The various builtin property resolvers.
40       * <p>
41       * Each resolver discovers how to set/get a property with different techniques; seeking
42       * method names or field names, etc.
43       *
44       * @since 3.0
45       */
46      enum JexlResolver implements PropertyResolver {
47  
48          /** Seeks methods named get{P,p}property and is{P,p}property. */
49          PROPERTY,
50  
51          /** Seeks map methods get/put. */
52          MAP,
53  
54          /** Seeks list methods to get/set. */
55          LIST,
56  
57          /** Seeks any get/{set,put} method (quacking like a list or a map). */
58          DUCK,
59  
60          /**  Seeks public instance members.*/
61          FIELD,
62  
63          /** Seeks a getContainer(property) and setContainer(property, value) as in {@code x.container.property}. */
64          CONTAINER;
65  
66          @Override
67          public final JexlPropertyGet getPropertyGet(final JexlUberspect uber,
68                                                      final Object obj,
69                                                      final Object identifier) {
70              return uber.getPropertyGet(Collections.singletonList(this), obj, identifier);
71          }
72  
73          @Override
74          public final JexlPropertySet getPropertySet(final JexlUberspect uber,
75                                                      final Object obj,
76                                                      final Object identifier,
77                                                      final Object arg) {
78              return uber.getPropertySet(Collections.singletonList(this), obj, identifier, arg);
79          }
80      }
81  
82      /**
83       * A marker interface that solves a simple class name into a fully qualified one.
84       * <p>The base implementation uses imports.</p>
85       *
86       * @since 3.6.0
87       */
88      interface ClassNameResolver {
89  
90          /**
91           * Resolves a class name.
92           *
93           * @param name the simple class name
94           * @return the fully qualified class name
95           */
96          String resolveClassName(String name);
97      }
98  
99      /**
100      * A marker interface that solves a class constant by name.
101      * <p>The base implementation uses imports to solve enums and public static final fields.</p>
102      *
103      * @since 3.6.0
104      */
105     interface ClassConstantResolver extends ClassNameResolver {
106 
107         /**
108          * Resolves a constant by its name.
109          *
110          * @param name the constant name, a qualified name
111          * @return the constant value or TRY_FAILED if not found
112          */
113         Object resolveConstant(String name);
114     }
115 
116     /**
117      * The factory type for creating constant resolvers.
118      *
119      * @since 3.6.0
120      */
121     interface ConstantResolverFactory {
122 
123         /**
124          * Creates a constant resolver.
125          *
126          * @param imports the collection of imports (packages and classes) to use
127          * @return a constant resolver
128          */
129         ClassConstantResolver createConstantResolver(Collection<String> imports);
130     }
131 
132     /**
133      * Abstracts getting property setter and getter.
134      * <p>
135      * These are used through 'strategies' to solve properties; a strategy orders a list of resolver types,
136      * and each resolver type is tried in sequence; the first resolver that discovers a non-null {s,g}etter
137      * stops the search.
138      *
139      * @see JexlResolver
140      * @see JexlUberspect#getPropertyGet
141      * @see JexlUberspect#getPropertySet
142      * @since 3.0
143      */
144     interface PropertyResolver {
145 
146         /**
147          * Gets a property getter.
148          *
149          * @param uber       the uberspect
150          * @param obj        the object
151          * @param identifier the property identifier
152          * @return the property getter or null
153          */
154         JexlPropertyGet getPropertyGet(JexlUberspect uber, Object obj, Object identifier);
155 
156         /**
157          * Gets a property setter.
158          *
159          * @param uber       the uberspect
160          * @param obj        the object
161          * @param identifier the property identifier
162          * @param arg        the property value
163          * @return the property setter or null
164          */
165         JexlPropertySet getPropertySet(JexlUberspect uber, Object obj, Object identifier, Object arg);
166     }
167 
168     /**
169      * Determines property resolution strategy.
170      *
171      * <p>To use a strategy, you have to set it at engine creation using
172      * {@link org.apache.commons.jexl3.JexlBuilder#strategy(JexlUberspect.ResolverStrategy)}
173      * as in:</p>
174      *
175      * {@code JexlEngine jexl = new JexlBuilder().strategy(MY_STRATEGY).create();}
176      *
177      * @since 3.0
178      */
179     interface ResolverStrategy {
180 
181         /**
182          * Applies this strategy to a list of resolver types.
183          *
184          * @param operator the property access operator, can be null
185          * @param obj      the instance we seek to obtain a property setter/getter from, cannot be null
186          * @return the ordered list of resolver types, cannot be null
187          */
188         List<PropertyResolver> apply(JexlOperator operator, Object obj);
189     }
190 
191     /**
192      * A resolver types list tailored for POJOs, favors '.' over '[]'.
193      */
194     List<PropertyResolver> POJO = Collections.unmodifiableList(Arrays.asList(
195             JexlResolver.PROPERTY,
196             JexlResolver.MAP,
197             JexlResolver.LIST,
198             JexlResolver.DUCK,
199             JexlResolver.FIELD,
200             JexlResolver.CONTAINER
201     ));
202 
203     /**
204      * A resolver types list tailored for Maps, favors '[]' over '.'.
205      */
206     List<PropertyResolver> MAP = Collections.unmodifiableList(Arrays.asList(
207             JexlResolver.MAP,
208             JexlResolver.LIST,
209             JexlResolver.DUCK,
210             JexlResolver.PROPERTY,
211             JexlResolver.FIELD,
212             JexlResolver.CONTAINER
213     ));
214 
215     /**
216      * The default strategy.
217      * <p>
218      * If the operator is '[]' or if the operator is null and the object is a map, use the MAP list of resolvers;
219      * Other cases use the POJO list of resolvers.
220      */
221     ResolverStrategy JEXL_STRATEGY = (op, obj) -> {
222         if (op == JexlOperator.ARRAY_GET) {
223             return MAP;
224         }
225         if (op == JexlOperator.ARRAY_SET) {
226             return MAP;
227         }
228         if (op == null && obj instanceof Map) {
229             return MAP;
230         }
231         return POJO;
232     };
233 
234     /**
235      * The map strategy.
236      *
237      * <p>If the operator is '[]' or if the object is a map, use the MAP list of resolvers.
238      * Otherwise, use the POJO list of resolvers.</p>
239      */
240     ResolverStrategy MAP_STRATEGY = (op, obj) -> {
241         if (op == JexlOperator.ARRAY_GET) {
242             return MAP;
243         }
244         if (op == JexlOperator.ARRAY_SET) {
245             return MAP;
246         }
247         if (obj instanceof Map) {
248             return MAP;
249         }
250         return POJO;
251     };
252 
253     /**
254      * Gets an arithmetic operator resolver for a given arithmetic instance.
255      *
256      * @param arithmetic the arithmetic instance
257      * @return the arithmetic uberspect or null if no operator method were overridden
258      * @since 3.0
259      * @see #getOperator(JexlArithmetic)
260      */
261     JexlArithmetic.Uberspect getArithmetic(JexlArithmetic arithmetic);
262 
263     /**
264      * Gets an arithmetic operator executor for a given arithmetic instance.
265      *
266      * @param arithmetic the arithmetic instance
267      * @return an operator uberspect instance
268      * @since 3.5.0
269      */
270     default JexlOperator.Uberspect getOperator(final JexlArithmetic arithmetic) {
271         return null;
272     }
273 
274     /**
275      * Seeks a class by name using this uberspect class-loader.
276      *
277      * @param className the class name
278      * @return the class instance or null if the class cannot be located by this uberspect class loader or if
279      * permissions deny access to the class
280      */
281     default Class<?> getClassByName(final String className) {
282         try {
283             return Class.forName(className, false, getClassLoader());
284         } catch (final ClassNotFoundException ignore) {
285             return null;
286         }
287     }
288 
289     /**
290      * Gets the current class loader.
291      *
292      * @return the class loader
293      */
294     ClassLoader getClassLoader();
295 
296     /**
297      * Returns a class constructor.
298      *
299      * @param ctorHandle a class or class name
300      * @param args       constructor arguments
301      * @return a {@link JexlMethod}
302      * @since 3.0
303      */
304     JexlMethod getConstructor(Object ctorHandle, Object... args);
305 
306     /**
307      * Gets an iterator from an object.
308      *
309      * @param obj to get the iterator from
310      * @return an iterator over obj or null
311      */
312     Iterator<?> getIterator(Object obj);
313 
314     /**
315      * Returns a JexlMethod.
316      *
317      * @param obj    the object
318      * @param method the method name
319      * @param args   method arguments
320      * @return a {@link JexlMethod}
321      */
322     JexlMethod getMethod(Object obj, String method, Object... args);
323 
324     /**
325      * Property getter.
326      * <p>
327      * Seeks a JexlPropertyGet apropos to an expression like {@code bar.woogie}.</p>
328      * See {@link ResolverStrategy#apply(JexlOperator, Object)}
329      *
330      * @param resolvers  the list of property resolvers to try
331      * @param obj        the object to get the property from
332      * @param identifier property name
333      * @return a {@link JexlPropertyGet} or null
334      * @since 3.0
335      */
336     JexlPropertyGet getPropertyGet(List<PropertyResolver> resolvers, Object obj, Object identifier);
337 
338     /**
339      * Property getter.
340      *
341      * <p>returns a JelPropertySet apropos to an expression like {@code bar.woogie}.</p>
342      *
343      * @param obj        the object to get the property from
344      * @param identifier property name
345      * @return a {@link JexlPropertyGet} or null
346      */
347     JexlPropertyGet getPropertyGet(Object obj, Object identifier);
348 
349     /**
350      * Property setter.
351      * <p>
352      * Seeks a JelPropertySet apropos to an expression like {@code foo.bar = "geir"}.</p>
353      * See {@link ResolverStrategy#apply(JexlOperator, Object)}
354      *
355      * @param resolvers  the list of property resolvers to try,
356      * @param obj        the object to get the property from
357      * @param identifier property name
358      * @param arg        value to set
359      * @return a {@link JexlPropertySet} or null
360      * @since 3.0
361      */
362     JexlPropertySet getPropertySet(List<PropertyResolver> resolvers, Object obj, Object identifier, Object arg);
363 
364     /**
365      * Property setter.
366      * <p>
367      * Seeks a JelPropertySet apropos to an expression like  {@code foo.bar = "geir"}.</p>
368      *
369      * @param obj        the object to get the property from.
370      * @param identifier property name
371      * @param arg        value to set
372      * @return a {@link JexlPropertySet} or null
373      */
374     JexlPropertySet getPropertySet(Object obj, Object identifier, Object arg);
375 
376     /**
377      * Applies this uberspect property resolver strategy.
378      *
379      * @param op the operator
380      * @param obj the object
381      * @return the applied strategy resolver list
382      */
383     List<PropertyResolver> getResolvers(JexlOperator op, Object obj);
384 
385     /**
386      * Gets this uberspect version.
387      *
388      * @return the class loader modification count
389      */
390     int getVersion();
391 
392     /**
393      * Sets the class loader to use.
394      *
395      * <p>This increments the version.</p>
396      *
397      * @param loader the class loader
398      */
399     void setClassLoader(ClassLoader loader);
400 
401 }