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 }