    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}