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 *      http://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 org.apache.commons.jexl3.JexlArithmetic;
021import org.apache.commons.jexl3.JexlOperator;
022
023import java.util.Arrays;
024import java.util.Collections;
025import java.util.Iterator;
026import java.util.List;
027import java.util.Map;
028
029/**
030 * 'Federated' introspection/reflection interface to allow JEXL introspection
031 * behavior to be customized.
032 *
033 * @since 1.0
034 */
035public interface JexlUberspect {
036    /**
037     * Abstracts getting property setter and getter.
038     * <p>
039     * These are used through 'strategies' to solve properties; a strategy orders a list of resolver types,
040     * and each resolver type is tried in sequence; the first resolver that discovers a non-null {s,g}etter
041     * stops the search.
042     *
043     * @see JexlResolver
044     * @see JexlUberspect#getPropertyGet
045     * @see JexlUberspect#getPropertySet
046     * @since 3.0
047     */
048    interface PropertyResolver {
049
050        /**
051         * Gets a property getter.
052         *
053         * @param uber       the uberspect
054         * @param obj        the object
055         * @param identifier the property identifier
056         * @return the property getter or null
057         */
058        JexlPropertyGet getPropertyGet(JexlUberspect uber, Object obj, Object identifier);
059
060        /**
061         * Gets a property setter.
062         *
063         * @param uber       the uberspect
064         * @param obj        the object
065         * @param identifier the property identifier
066         * @param arg        the property value
067         * @return the property setter or null
068         */
069        JexlPropertySet getPropertySet(JexlUberspect uber, Object obj, Object identifier, Object arg);
070    }
071
072    /**
073     * The various builtin property resolvers.
074     * <p>
075     * Each resolver discovers how to set/get a property with different techniques; seeking
076     * method names or field names, etc.
077     *
078     * @since 3.0
079     */
080    enum JexlResolver implements PropertyResolver {
081        /** Seeks methods named get{P,p}property and is{P,p}property. */
082        PROPERTY,
083
084        /** Seeks map methods get/put. */
085        MAP,
086
087        /** Seeks list methods get/set. */
088        LIST,
089
090        /** Seeks any get/{set,put} method (quacking like a list or a map). */
091        DUCK,
092
093        /**  Seeks public instance members.*/
094        FIELD,
095
096        /** Seeks a getContainer(property) and setContainer(property, value) as in <code>x.container.property</code>. */
097        CONTAINER;
098
099        @Override
100        public final JexlPropertyGet getPropertyGet(final JexlUberspect uber,
101                                                    final Object obj,
102                                                    final Object identifier) {
103            return uber.getPropertyGet(Collections.singletonList(this), obj, identifier);
104        }
105
106        @Override
107        public final JexlPropertySet getPropertySet(final JexlUberspect uber,
108                                                    final Object obj,
109                                                    final Object identifier,
110                                                    final Object arg) {
111            return uber.getPropertySet(Collections.singletonList(this), obj, identifier, arg);
112        }
113    }
114
115    /**
116     * A resolver types list tailored for POJOs, favors '.' over '[]'.
117     */
118    List<PropertyResolver> POJO = Collections.unmodifiableList(Arrays.asList(
119            JexlResolver.PROPERTY,
120            JexlResolver.MAP,
121            JexlResolver.LIST,
122            JexlResolver.DUCK,
123            JexlResolver.FIELD,
124            JexlResolver.CONTAINER
125    ));
126
127
128    /**
129     * A resolver types list tailored for Maps, favors '[]' over '.'.
130     */
131    List<PropertyResolver> MAP = Collections.unmodifiableList(Arrays.asList(
132            JexlResolver.MAP,
133            JexlResolver.LIST,
134            JexlResolver.DUCK,
135            JexlResolver.PROPERTY,
136            JexlResolver.FIELD,
137            JexlResolver.CONTAINER
138    ));
139
140    /**
141     * Determines property resolution strategy.
142     *
143     * <p>To use a strategy instance, you have to set it at engine creation using
144     * {@link org.apache.commons.jexl3.JexlBuilder#strategy(JexlUberspect.ResolverStrategy)}
145     * as in:</p>
146     *
147     * <code>JexlEngine jexl = new JexlBuilder().strategy(MY_STRATEGY).create();</code>
148     *
149     * @since 3.0
150     */
151    interface ResolverStrategy {
152        /**
153         * Applies this strategy to a list of resolver types.
154         *
155         * @param operator the property access operator, may be null
156         * @param obj      the instance we seek to obtain a property setter/getter from, can not be null
157         * @return the ordered list of resolvers types, must not be null
158         */
159        List<PropertyResolver> apply(JexlOperator operator, Object obj);
160    }
161
162    /**
163     * The default strategy.
164     * <p>
165     * If the operator is '[]' or if the operator is null and the object is a map, use the MAP list of resolvers;
166     * Other cases use the POJO list of resolvers.
167     */
168    ResolverStrategy JEXL_STRATEGY = (op, obj) -> {
169        if (op == JexlOperator.ARRAY_GET) {
170            return MAP;
171        }
172        if (op == JexlOperator.ARRAY_SET) {
173            return MAP;
174        }
175        if (op == null && obj instanceof Map) {
176            return MAP;
177        }
178        return POJO;
179    };
180
181    /**
182     * The map strategy.
183     *
184     * <p>If the operator is '[]' or if the object is a map, use the MAP list of resolvers.
185     * Otherwise, use the POJO list of resolvers.</p>
186     */
187    ResolverStrategy MAP_STRATEGY = (op, obj) -> {
188        if (op == JexlOperator.ARRAY_GET) {
189            return MAP;
190        }
191        if (op == JexlOperator.ARRAY_SET) {
192            return MAP;
193        }
194        if (obj instanceof Map) {
195            return MAP;
196        }
197        return POJO;
198    };
199
200    /**
201     * Applies this uberspect property resolver strategy.
202     *
203     * @param op the operator
204     * @param obj the object
205     * @return the applied strategy resolver list
206     */
207    List<PropertyResolver> getResolvers(JexlOperator op, Object obj);
208
209    /**
210     * Sets the class loader to use.
211     *
212     * <p>This increments the version.</p>
213     *
214     * @param loader the class loader
215     */
216    void setClassLoader(ClassLoader loader);
217
218    /**
219     * Gets the current class loader.
220     * @return the class loader
221     */
222    ClassLoader getClassLoader();
223
224    /**
225     * Gets this uberspect version.
226     *
227     * @return the class loader modification count
228     */
229    int getVersion();
230
231    /**
232     * Seeks a class by name using this uberspect class-loader.
233     * @param className the class name
234     * @return the class instance or null if the class cannot be located by this uberspect class loader or if
235     * permissions deny access to the class
236     */
237    default Class<?> getClassByName(final String className) {
238        try {
239            return Class.forName(className, false, getClassLoader());
240        } catch (final ClassNotFoundException xignore) {
241            return null;
242        }
243    }
244
245    /**
246     * Returns a class constructor.
247     *
248     * @param ctorHandle a class or class name
249     * @param args       constructor arguments
250     * @return a {@link JexlMethod}
251     * @since 3.0
252     */
253    JexlMethod getConstructor(Object ctorHandle, Object... args);
254
255    /**
256     * Returns a JexlMethod.
257     *
258     * @param obj    the object
259     * @param method the method name
260     * @param args   method arguments
261     * @return a {@link JexlMethod}
262     */
263    JexlMethod getMethod(Object obj, String method, Object... args);
264
265    /**
266     * Property getter.
267     *
268     * <p>returns a JelPropertySet apropos to an expression like <code>bar.woogie</code>.</p>
269     *
270     * @param obj        the object to get the property from
271     * @param identifier property name
272     * @return a {@link JexlPropertyGet} or null
273     */
274    JexlPropertyGet getPropertyGet(Object obj, Object identifier);
275
276    /**
277     * Property getter.
278     * <p>
279     * Seeks a JexlPropertyGet apropos to an expression like <code>bar.woogie</code>.</p>
280     * See {@link ResolverStrategy#apply(JexlOperator, java.lang.Object) }
281     *
282     * @param resolvers  the list of property resolvers to try
283     * @param obj        the object to get the property from
284     * @param identifier property name
285     * @return a {@link JexlPropertyGet} or null
286     * @since 3.0
287     */
288    JexlPropertyGet getPropertyGet(List<PropertyResolver> resolvers, Object obj, Object identifier);
289
290    /**
291     * Property setter.
292     * <p>
293     * Seeks a JelPropertySet apropos to an expression like  <code>foo.bar = "geir"</code>.</p>
294     *
295     * @param obj        the object to get the property from.
296     * @param identifier property name
297     * @param arg        value to set
298     * @return a {@link JexlPropertySet} or null
299     */
300    JexlPropertySet getPropertySet(Object obj, Object identifier, Object arg);
301
302    /**
303     * Property setter.
304     * <p>
305     * Seeks a JelPropertySet apropos to an expression like <code>foo.bar = "geir"</code>.</p>
306     * See {@link ResolverStrategy#apply(JexlOperator, java.lang.Object) }
307     *
308     * @param resolvers  the list of property resolvers to try,
309     * @param obj        the object to get the property from
310     * @param identifier property name
311     * @param arg        value to set
312     * @return a {@link JexlPropertySet} or null
313     * @since 3.0
314     */
315    JexlPropertySet getPropertySet(List<PropertyResolver> resolvers, Object obj, Object identifier, Object arg);
316
317    /**
318     * Gets an iterator from an object.
319     *
320     * @param obj to get the iterator from
321     * @return an iterator over obj or null
322     */
323    Iterator<?> getIterator(Object obj);
324
325    /**
326     * Gets an arithmetic operator resolver for a given arithmetic instance.
327     *
328     * @param arithmetic the arithmetic instance
329     * @return the arithmetic uberspect or null if no operator method were overridden
330     * @since 3.0
331     */
332    JexlArithmetic.Uberspect getArithmetic(JexlArithmetic arithmetic);
333
334}