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 }