001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      https://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018package org.apache.commons.jexl3.introspection;
019
020import java.util.Arrays;
021import java.util.Collection;
022import java.util.Collections;
023import java.util.Iterator;
024import java.util.List;
025import java.util.Map;
026
027import org.apache.commons.jexl3.JexlArithmetic;
028import org.apache.commons.jexl3.JexlOperator;
029
030/**
031 * 'Federated' introspection/reflection interface to allow JEXL introspection
032 * behavior to be customized.
033 *
034 * @since 1.0
035 */
036public interface JexlUberspect {
037
038    /**
039     * The various builtin property resolvers.
040     * <p>
041     * Each resolver discovers how to set/get a property with different techniques; seeking
042     * method names or field names, etc.
043     *
044     * @since 3.0
045     */
046    enum JexlResolver implements PropertyResolver {
047
048        /** Seeks methods named get{P,p}property and is{P,p}property. */
049        PROPERTY,
050
051        /** Seeks map methods get/put. */
052        MAP,
053
054        /** Seeks list methods to get/set. */
055        LIST,
056
057        /** Seeks any get/{set,put} method (quacking like a list or a map). */
058        DUCK,
059
060        /**  Seeks public instance members.*/
061        FIELD,
062
063        /** Seeks a getContainer(property) and setContainer(property, value) as in {@code x.container.property}. */
064        CONTAINER;
065
066        @Override
067        public final JexlPropertyGet getPropertyGet(final JexlUberspect uber,
068                                                    final Object obj,
069                                                    final Object identifier) {
070            return uber.getPropertyGet(Collections.singletonList(this), obj, identifier);
071        }
072
073        @Override
074        public final JexlPropertySet getPropertySet(final JexlUberspect uber,
075                                                    final Object obj,
076                                                    final Object identifier,
077                                                    final Object arg) {
078            return uber.getPropertySet(Collections.singletonList(this), obj, identifier, arg);
079        }
080    }
081
082    /**
083     * A marker interface that solves a simple class name into a fully qualified one.
084     * <p>The base implementation uses imports.</p>
085     *
086     * @since 3.6.0
087     */
088    interface ClassNameResolver {
089
090        /**
091         * Resolves a class name.
092         *
093         * @param name the simple class name
094         * @return the fully qualified class name
095         */
096        String resolveClassName(String name);
097    }
098
099    /**
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}