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;
019
020import org.apache.commons.jexl3.internal.Engine;
021import org.apache.commons.jexl3.introspection.JexlSandbox;
022import org.apache.commons.jexl3.introspection.JexlUberspect;
023import org.apache.commons.logging.Log;
024
025import java.util.Map;
026import java.nio.charset.Charset;
027
028/**
029 * Configure and builds a JexlEngine.
030 *
031 * <p>The <code>setSilent</code> and <code>setStrict</code> methods allow to fine-tune an engine instance behavior
032 * according to various error control needs. The strict flag tells the engine when and if null as operand is
033 * considered an error, the silent flag tells the engine what to do with the error
034 * (log as warning or throw exception).</p>
035 *
036 * <ul>
037 * <li>When "silent" &amp; "not-strict":
038 * <p> 0 &amp; null should be indicators of "default" values so that even in an case of error,
039 * something meaningful can still be inferred; may be convenient for configurations.
040 * </p>
041 * </li>
042 * <li>When "silent" &amp; "strict":
043 * <p>One should probably consider using null as an error case - ie, every object
044 * manipulated by JEXL should be valued; the ternary operator, especially the '?:' form
045 * can be used to workaround exceptional cases.
046 * Use case could be configuration with no implicit values or defaults.
047 * </p>
048 * </li>
049 * <li>When "not-silent" &amp; "not-strict":
050 * <p>The error control grain is roughly on par with JEXL 1.0</p>
051 * </li>
052 * <li>When "not-silent" &amp; "strict":
053 * <p>The finest error control grain is obtained; it is the closest to Java code -
054 * still augmented by "script" capabilities regarding automated conversions and type matching.
055 * </p>
056 * </li>
057 * </ul>
058 */
059public class JexlBuilder {
060
061    /** The default maximum expression length to hit the expression cache. */
062    protected static final int CACHE_THRESHOLD = 64;
063
064    /** The JexlUberspect instance. */
065    private JexlUberspect uberspect = null;
066
067    /** The strategy strategy. */
068    private JexlUberspect.ResolverStrategy strategy = null;
069
070    /** The sandbox. */
071    private JexlSandbox sandbox = null;
072
073    /** The Log to which all JexlEngine messages will be logged. */
074    private Log logger = null;
075
076    /** Whether error messages will carry debugging information. */
077    private Boolean debug = null;
078
079    /** Whether interrupt throws JexlException.Cancel. */
080    private Boolean cancellable = null;
081
082    /** The options. */
083    private final JexlOptions options = new JexlOptions();
084
085    /** Whether getVariables considers all potential equivalent syntactic forms. */
086    private int collectMode = 1;
087
088    /** The {@link JexlArithmetic} instance. */
089    private JexlArithmetic arithmetic = null;
090
091    /** The cache size. */
092    private int cache = -1;
093
094    /** The stack overflow limit. */
095    private int stackOverflow = Integer.MAX_VALUE;
096
097    /** The maximum expression length to hit the expression cache. */
098    private int cacheThreshold = CACHE_THRESHOLD;
099
100    /** The charset. */
101    private Charset charset = Charset.defaultCharset();
102
103    /** The class loader. */
104    private ClassLoader loader = null;
105
106    /** The features. */
107    private JexlFeatures features = null;
108
109    /**
110     * Sets the JexlUberspect instance the engine will use.
111     *
112     * @param u the uberspect
113     * @return this builder
114     */
115    public JexlBuilder uberspect(final JexlUberspect u) {
116        this.uberspect = u;
117        return this;
118    }
119
120    /** @return the uberspect */
121    public JexlUberspect uberspect() {
122        return this.uberspect;
123    }
124
125    /**
126     * Sets the JexlUberspect strategy strategy the engine will use.
127     * <p>This is ignored if the uberspect has been set.
128     *
129     * @param rs the strategy
130     * @return this builder
131     */
132    public JexlBuilder strategy(final JexlUberspect.ResolverStrategy rs) {
133        this.strategy = rs;
134        return this;
135    }
136
137    /** @return the strategy strategy */
138    public JexlUberspect.ResolverStrategy strategy() {
139        return this.strategy;
140    }
141
142    /** @return the current set of options */
143    public JexlOptions options() {
144        return options;
145    }
146
147    /**
148     * Sets the JexlArithmetic instance the engine will use.
149     *
150     * @param a the arithmetic
151     * @return this builder
152     */
153    public JexlBuilder arithmetic(final JexlArithmetic a) {
154        this.arithmetic = a;
155        options.setStrictArithmetic(a.isStrict());
156        options.setMathContext(a.getMathContext());
157        options.setMathScale(a.getMathScale());
158        return this;
159    }
160
161    /** @return the arithmetic */
162    public JexlArithmetic arithmetic() {
163        return this.arithmetic;
164    }
165
166    /**
167     * Sets the sandbox the engine will use.
168     *
169     * @param box the sandbox
170     * @return this builder
171     */
172    public JexlBuilder sandbox(final JexlSandbox box) {
173        this.sandbox = box;
174        return this;
175    }
176
177    /** @return the sandbox */
178    public JexlSandbox sandbox() {
179        return this.sandbox;
180    }
181
182    /**
183     * Sets the features the engine will use as a base by default.
184     * <p>Note that the script flag will be ignored; the engine will be able to parse expressions and scripts.
185     * <p>Note also that these will apply to template expressions and scripts.
186     * <p>As a last remark, if lexical or lexicalShade are set as features, this
187     * method will also set the corresponding options.
188     * @param f the features
189     * @return this builder
190     */
191    public JexlBuilder features(final JexlFeatures f) {
192        this.features = f;
193        if (features != null) {
194            if (features.isLexical()) {
195                options.setLexical(true);
196            }
197            if (features.isLexicalShade()) {
198                options.setLexicalShade(true);
199            }
200        }
201        return this;
202    }
203
204    /** @return the features */
205    public JexlFeatures features() {
206        return this.features;
207    }
208
209    /**
210     * Sets the o.a.c.Log instance to use.
211     *
212     * @param log the logger
213     * @return this builder
214     */
215    public JexlBuilder logger(final Log log) {
216        this.logger = log;
217        return this;
218    }
219
220    /** @return the logger */
221    public Log logger() {
222        return this.logger;
223    }
224
225    /**
226     * Sets the class loader to use.
227     *
228     * @param l the class loader
229     * @return this builder
230     */
231    public JexlBuilder loader(final ClassLoader l) {
232        this.loader = l;
233        return this;
234    }
235
236    /** @return the class loader */
237    public ClassLoader loader() {
238        return loader;
239    }
240
241    /**
242     * Sets the charset to use.
243     *
244     * @param arg the charset
245     * @return this builder
246     * @deprecated since 3.1 use {@link #charset(Charset)} instead
247     */
248    @Deprecated
249    public JexlBuilder loader(final Charset arg) {
250        return charset(arg);
251    }
252
253    /**
254     * Sets the charset to use.
255     *
256     * @param arg the charset
257     * @return this builder
258     * @since 3.1
259     */
260    public JexlBuilder charset(final Charset arg) {
261        this.charset = arg;
262        return this;
263    }
264
265    /** @return the charset */
266    public Charset charset() {
267        return charset;
268    }
269
270   /**
271     * Sets whether the engine will resolve antish variable names.
272     *
273     * @param flag true means antish resolution is enabled, false disables it
274     * @return this builder
275     */
276    public JexlBuilder antish(final boolean flag) {
277        options.setAntish(flag);
278        return this;
279    }
280
281    /** @return whether antish resolution is enabled */
282    public boolean antish() {
283        return options.isAntish();
284    }
285
286    /**
287     * Sets whether the engine is in lexical mode.
288     *
289     * @param flag true means lexical function scope is in effect, false implies non-lexical scoping
290     * @return this builder
291     * @since 3.2
292     */
293    public JexlBuilder lexical(final boolean flag) {
294        options.setLexical(flag);
295        return this;
296    }
297
298    /** @return whether lexical scope is enabled */
299    public boolean lexical() {
300        return options.isLexical();
301    }
302
303    /**
304     * Sets whether the engine is in lexical shading mode.
305     *
306     * @param flag true means lexical shading is in effect, false implies no lexical shading
307     * @return this builder
308     * @since 3.2
309     */
310    public JexlBuilder lexicalShade(final boolean flag) {
311        options.setLexicalShade(flag);
312        return this;
313    }
314
315    /** @return whether lexical shading is enabled */
316    public boolean lexicalShade() {
317        return options.isLexicalShade();
318    }
319
320    /**
321     * Sets whether the engine will throw JexlException during evaluation when an error is triggered.
322     *
323     * @param flag true means no JexlException will occur, false allows them
324     * @return this builder
325     */
326    public JexlBuilder silent(final boolean flag) {
327        options.setSilent(flag);
328        return this;
329    }
330
331    /** @return the silent error handling flag */
332    public Boolean silent() {
333        return options.isSilent();
334    }
335
336    /**
337     * Sets whether the engine considers unknown variables, methods, functions and constructors as errors or
338     * evaluates them as null.
339     *
340     * @param flag true means strict error reporting, false allows them to be evaluated as null
341     * @return this builder
342     */
343    public JexlBuilder strict(final boolean flag) {
344        options.setStrict(flag);
345        return this;
346    }
347
348    /** @return true if strict, false otherwise */
349    public Boolean strict() {
350        return options.isStrict();
351    }
352
353    /**
354     * Sets whether the engine considers dereferencing null in navigation expressions
355     * as errors or evaluates them as null.
356     * <p><code>x.y()</code> if x is null throws an exception when not safe,
357     * return null and warns if it is.<p>
358     *
359     * @param flag true means safe navigation, false throws exception when dereferencing null
360     * @return this builder
361     */
362    public JexlBuilder safe(final boolean flag) {
363        options.setSafe(flag);
364        return this;
365    }
366
367    /** @return true if safe, false otherwise */
368    public Boolean safe() {
369        return options.isSafe();
370    }
371
372    /**
373     * Sets whether the engine will report debugging information when error occurs.
374     *
375     * @param flag true implies debug is on, false implies debug is off.
376     * @return this builder
377     */
378    public JexlBuilder debug(final boolean flag) {
379        this.debug = flag;
380        return this;
381    }
382
383    /** @return the debugging information flag */
384    public Boolean debug() {
385        return this.debug;
386    }
387
388    /**
389     * Sets the engine behavior upon interruption: throw an JexlException.Cancel or terminates the current evaluation
390     * and return null.
391     *
392     * @param flag true implies the engine throws the exception, false makes the engine return null.
393     * @return this builder
394     * @since 3.1
395     */
396    public JexlBuilder cancellable(final boolean flag) {
397        this.cancellable = flag;
398        options.setCancellable(flag);
399        return this;
400    }
401
402    /**
403     * @return the cancellable information flag
404     * @since 3.1
405     */
406    public Boolean cancellable() {
407        return this.cancellable;
408    }
409
410    /**
411     * Sets whether the engine variable collectors considers all potential forms of variable syntaxes.
412     *
413     * @param flag true means var collections considers constant array accesses equivalent to dotted references
414     * @return this builder
415     * @since 3.2
416     */
417    public JexlBuilder collectAll(final boolean flag) {
418        return collectMode(flag? 1 : 0);
419    }
420
421    /**
422     * Experimental collector mode setter.
423     *
424     * @param mode 0 or 1 as equivalents to false and true, other values are experimental
425     * @return this builder
426     * @since 3.2
427     */
428    public JexlBuilder collectMode(final int mode) {
429        this.collectMode = mode;
430        return this;
431    }
432
433    /**
434     * @return true if variable collection follows strict syntactic rule
435     * @since 3.2
436     */
437    public boolean collectAll() {
438        return this.collectMode != 0;
439    }
440
441    /**
442     * @return 0 if variable collection follows strict syntactic rule
443     * @since 3.2
444     */
445    public int collectMode() {
446        return this.collectMode;
447    }
448
449    /**
450     * Sets the default namespaces map the engine will use.
451     * <p>
452     * Each entry key is used as a prefix, each entry value used as a bean implementing
453     * methods; an expression like 'nsx:method(123)' will thus be solved by looking at
454     * a registered bean named 'nsx' that implements method 'method' in that map.
455     * If all methods are static, you may use the bean class instead of an instance as value.
456     * </p>
457     * <p>
458     * If the entry value is a class that has one constructor taking a JexlContext as argument, an instance
459     * of the namespace will be created at evaluation time. It might be a good idea to derive a JexlContext
460     * to carry the information used by the namespace to avoid variable space pollution and strongly type
461     * the constructor with this specialized JexlContext.
462     * </p>
463     * <p>
464     * The key or prefix allows to retrieve the bean that plays the role of the namespace.
465     * If the prefix is null, the namespace is the top-level namespace allowing to define
466     * top-level user defined namespaces ( ie: myfunc(...) )
467     * </p>
468     * <p>Note that the JexlContext is also used to try to solve top-level namespaces. This allows ObjectContext
469     * derived instances to call methods on the wrapped object.</p>
470     *
471     * @param ns the map of namespaces
472     * @return this builder
473     */
474    public JexlBuilder namespaces(final Map<String, Object> ns) {
475        options.setNamespaces(ns);
476        return this;
477    }
478
479    /**
480     * @return the map of namespaces.
481     */
482    public Map<String, Object> namespaces() {
483        return options.getNamespaces();
484    }
485
486    /**
487     * Sets the expression cache size the engine will use.
488     * <p>The cache will contain at most <code>size</code> expressions of at most <code>cacheThreshold</code> length.
489     * Note that all JEXL caches are held through SoftReferences and may be garbage-collected.</p>
490     *
491     * @param size if not strictly positive, no cache is used.
492     * @return this builder
493     */
494    public JexlBuilder cache(final int size) {
495        this.cache = size;
496        return this;
497    }
498
499    /**
500     * @return the cache size
501     */
502    public int cache() {
503        return cache;
504    }
505
506    /**
507     * Sets the maximum length for an expression to be cached.
508     * <p>Expression whose length is greater than this expression cache length threshold will
509     * bypass the cache.</p>
510     * <p>It is expected that a "long" script will be parsed once and its reference kept
511     * around in user-space structures; the jexl expression cache has no added-value in this case.</p>
512     *
513     * @param length if not strictly positive, the value is silently replaced by the default value (64).
514     * @return this builder
515     */
516    public JexlBuilder cacheThreshold(final int length) {
517        this.cacheThreshold = length > 0? length : CACHE_THRESHOLD;
518        return this;
519    }
520
521    /**
522     * @return the cache threshold
523     */
524    public int cacheThreshold() {
525        return cacheThreshold;
526    }
527
528    /**
529     * Sets the number of script/expression evaluations that can be stacked.
530     * @param size if not strictly positive, limit is reached when java StackOverflow is thrown.
531     * @return this builder
532     */
533    public JexlBuilder stackOverflow(final int size) {
534        this.stackOverflow = size;
535        return this;
536    }
537
538    /**
539     * @return the cache size
540     */
541    public int stackOverflow() {
542        return stackOverflow;
543    }
544
545    /**
546     * @return a {@link JexlEngine} instance
547     */
548    public JexlEngine create() {
549        return new Engine(this);
550    }
551}