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