    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;
    019
    020import java.math.MathContext;
    021import java.util.Collection;
    022import java.util.Collections;
    023import java.util.Map;
    024
    025import org.apache.commons.jexl3.internal.Engine;
    026
    027/**
    028 * Flags and properties that can alter the evaluation behavior.
    029 * The flags, briefly explained, are the following:
    030 * <ul>
    031 * <li>silent: whether errors throw exception</li>
    032 * <li>safe: whether navigation through null is <em>not</em>an error</li>
    033 * <li>cancellable: whether thread interruption is an error</li>
    034 * <li>lexical: whether redefining local variables is an error</li>
    035 * <li>lexicalShade: whether local variables shade global ones even outside their scope</li>
    036 * <li>strict: whether unknown or unsolvable identifiers are errors</li>
    037 * <li>strictArithmetic: whether null as operand is an error</li>
    038 * <li>sharedInstance: whether these options can be modified at runtime during execution (expert)</li>
    039 * <li>constCapture: whether captured variables will throw an error if an attempt is made to change their value</li>
    040 * <li>strictInterpolation: whether interpolation strings always return a string or attempt to parse and return integer</li>
    041 * <li>booleanLogical: whether logical expressions (&quot;&quot; , ||) coerce their result to boolean</li>
    042 * </ul>
    043 * The sensible default is cancellable, strict and strictArithmetic.
    044 * <p>This interface replaces the now deprecated JexlEngine.Options.
    045 * @since 3.2
    046 */
    047public final class JexlOptions {
    048    /** The boolean logical flag. */
    049    private static final int BOOLEAN_LOGICAL = 10;
    050    /** The interpolation string bit. */
    051    private static final int STRICT_INTERPOLATION= 9;
    052    /** The const capture bit. */
    053    private static final int CONST_CAPTURE = 8;
    054    /** The shared instance bit. */
    055    private static final int SHARED = 7;
    056    /** The local shade bit. */
    057    private static final int SHADE = 6;
    058    /** The antish var bit. */
    059    private static final int ANTISH = 5;
    060    /** The lexical scope bit. */
    061    private static final int LEXICAL = 4;
    062    /** The safe bit. */
    063    private static final int SAFE = 3;
    064    /** The silent bit. */
    065    private static final int SILENT = 2;
    066    /** The strict bit. */
    067    private static final int STRICT = 1;
    068    /** The cancellable bit. */
    069    private static final int CANCELLABLE = 0;
    070    /** The flag names ordered. */
    071    private static final String[] NAMES = {
    072        "cancellable", "strict", "silent", "safe", "lexical", "antish",
    073        "lexicalShade", "sharedInstance", "constCapture", "strictInterpolation",
    074        "booleanShortCircuit"
    075    };
    076    /** Default mask .*/
    077    private static int DEFAULT = 1 /*<< CANCELLABLE*/ | 1 << STRICT | 1 << ANTISH | 1 << SAFE;
    078
    079    /**
    080     * Checks the value of a flag in the mask.
    081     * @param ordinal the flag ordinal
    082     * @param mask the flags mask
    083     * @return the mask value with this flag or-ed in
    084     */
    085    private static boolean isSet(final int ordinal, final int mask) {
    086        return (mask & 1 << ordinal) != 0;
    087    }
    088
    089    /**
    090     * Parses flags by name.
    091     * <p>A '+flag' or 'flag' will set flag as true, '-flag' set as false.
    092     * The possible flag names are:
    093     * cancellable, strict, silent, safe, lexical, antish, lexicalShade
    094     * @param initial the initial mask state
    095     * @param flags the flags to set
    096     * @return the flag mask updated
    097     */
    098    public static int parseFlags(final int initial, final String... flags) {
    099        int mask = initial;
    100        for (final String flag : flags) {
    101            boolean b = true;
    102            final String name;
    103            if (flag.charAt(0) == '+') {
    104                name = flag.substring(1);
    105            } else if (flag.charAt(0) == '-') {
    106                name = flag.substring(1);
    107                b = false;
    108            } else {
    109                name = flag;
    110            }
    111            for (int f = 0; f < NAMES.length; ++f) {
    112                if (NAMES[f].equals(name)) {
    113                    if (b) {
    114                        mask |= 1 << f;
    115                    } else {
    116                        mask &= ~(1 << f);
    117                    }
    118                    break;
    119                }
    120            }
    121        }
    122        return mask;
    123    }
    124
    125    /**
    126     * Sets the value of a flag in a mask.
    127     * @param ordinal the flag ordinal
    128     * @param mask the flags mask
    129     * @param value true or false
    130     * @return the new flags mask value
    131     */
    132    private static int set(final int ordinal, final int mask, final boolean value) {
    133        return value? mask | 1 << ordinal : mask & ~(1 << ordinal);
    134    }
    135
    136    /**
    137     * Sets the default (static, shared) option flags.
    138     * <p>
    139     * Whenever possible, we recommend using JexlBuilder methods to unambiguously instantiate a JEXL
    140     * engine; this method should only be used for testing / validation.
    141     * <p>A '+flag' or 'flag' will set the option named 'flag' as true, '-flag' set as false.
    142     * The possible flag names are:
    143     * cancellable, strict, silent, safe, lexical, antish, lexicalShade
    144     * <p>Calling JexlBuilder.setDefaultOptions("+safe") once before JEXL engine creation
    145     * may ease validating JEXL3.2 in your environment.
    146     * @param flags the flags to set
    147     */
    148    public static void setDefaultFlags(final String...flags) {
    149        DEFAULT = parseFlags(DEFAULT, flags);
    150    }
    151
    152    /** The arithmetic math context. */
    153    private MathContext mathContext;
    154
    155    /** The arithmetic math scale. */
    156    private int mathScale = Integer.MIN_VALUE;
    157
    158    /** The arithmetic strict math flag. */
    159    private boolean strictArithmetic = true;
    160
    161    /** The default flags, all but safe. */
    162    private int flags = DEFAULT;
    163
    164    /** The namespaces .*/
    165    private Map<String, Object> namespaces = Collections.emptyMap();
    166
    167    /** The imports. */
    168    private Collection<String> imports = Collections.emptySet();
    169
    170    /**
    171     * Default ctor.
    172     */
    173    public JexlOptions() {
    174        // all inits in members declarations
    175    }
    176
    177    /**
    178     * Creates a copy of this instance.
    179     * @return a copy
    180     */
    181    public JexlOptions copy() {
    182        return new JexlOptions().set(this);
    183    }
    184
    185    /**
    186     * Gets the optional set of imported packages.
    187     * @return the set of imports, may be empty, not null
    188     */
    189    public Collection<String> getImports() {
    190        return imports;
    191    }
    192
    193    /**
    194     * The MathContext instance used for +,-,/,*,% operations on big decimals.
    195     * @return the math context
    196     */
    197    public MathContext getMathContext() {
    198        return mathContext;
    199    }
    200
    201    /**
    202     * The BigDecimal scale used for comparison and coercion operations.
    203     * @return the scale
    204     */
    205    public int getMathScale() {
    206        return mathScale;
    207    }
    208
    209    /**
    210     * Gets the optional map of namespaces.
    211     * @return the map of namespaces, may be empty, not null
    212     */
    213    public Map<String, Object> getNamespaces() {
    214        return namespaces;
    215    }
    216
    217    /**
    218     * Checks whether evaluation will attempt resolving antish variable names.
    219     * @return true if antish variables are solved, false otherwise
    220     */
    221    public boolean isAntish() {
    222        return isSet(ANTISH, flags);
    223    }
    224
    225    /**
    226     * Gets whether logical expressions (&quot;&quot; , ||) coerce their result to boolean; if set,
    227     * an expression like (3 &quot;&quot; 4 &quot;&quot; 5) will evaluate to true. If not, it will evaluate to 5.
    228     * To preserve strict compatibility with 3.4, set the flag to true.
    229     * @return true if short-circuit logicals coerce their result to boolean, false otherwise
    230     * @since 3.5.0
    231     */
    232    public boolean isBooleanLogical() {
    233        return isSet(BOOLEAN_LOGICAL, flags);
    234    }
    235
    236    /**
    237     * Checks whether evaluation will throw JexlException.Cancel (true) or
    238     * return null (false) if interrupted.
    239     * @return true when cancellable, false otherwise
    240     */
    241    public boolean isCancellable() {
    242        return isSet(CANCELLABLE, flags);
    243    }
    244
    245    /**
    246     * Are lambda captured-variables const?
    247     * @return true if lambda captured-variables are const, false otherwise
    248     */
    249    public boolean isConstCapture() {
    250        return isSet(CONST_CAPTURE, flags);
    251    }
    252
    253    /**
    254     * Checks whether runtime variable scope is lexical.
    255     * <p>If true, lexical scope applies to local variables and parameters.
    256     * Redefining a variable in the same lexical unit will generate errors.
    257     * @return true if scope is lexical, false otherwise
    258     */
    259    public boolean isLexical() {
    260        return isSet(LEXICAL, flags);
    261    }
    262
    263    /**
    264     * Checks whether local variables shade global ones.
    265     * <p>After a symbol is defined as local, dereferencing it outside its
    266     * scope will trigger an error instead of seeking a global variable of the
    267     * same name. To further reduce potential naming ambiguity errors,
    268     * global variables (ie non-local) must be declared to be assigned {@link JexlContext#has(String)}
    269     * when this flag is on; attempting to set an undeclared global variables will
    270     * raise an error.
    271     * @return true if lexical shading is applied, false otherwise
    272     */
    273    public boolean isLexicalShade() {
    274        return isSet(SHADE, flags);
    275    }
    276
    277    /**
    278     * Checks whether the engine considers null in navigation expression as
    279     * errors during evaluation.
    280     * @return true if safe, false otherwise
    281     */
    282    public boolean isSafe() {
    283        return isSet(SAFE, flags);
    284    }
    285
    286    /**
    287     * Gets sharing state.
    288     * @return false if a copy of these options is used during execution,
    289     * true if those can potentially be modified
    290     */
    291    public boolean isSharedInstance() {
    292        return isSet(SHARED, flags);
    293    }
    294
    295    /**
    296     * Checks whether the engine will throw a {@link JexlException} when an
    297     * error is encountered during evaluation.
    298     * @return true if silent, false otherwise
    299     */
    300    public boolean isSilent() {
    301        return isSet(SILENT, flags);
    302    }
    303
    304    /**
    305     * Checks whether the engine considers unknown variables, methods and
    306     * constructors as errors during evaluation.
    307     * @return true if strict, false otherwise
    308     */
    309    public boolean isStrict() {
    310        return isSet(STRICT, flags);
    311    }
    312
    313    /**
    314     * Checks whether the arithmetic triggers errors during evaluation when null
    315     * is used as an operand.
    316     * @return true if strict, false otherwise
    317     */
    318    public boolean isStrictArithmetic() {
    319        return strictArithmetic;
    320    }
    321
    322    /**
    323     * Gets the strict-interpolation flag of this options instance.
    324     * @return true if interpolation strings always return string, false otherwise
    325     */
    326    public boolean isStrictInterpolation() {
    327        return isSet(STRICT_INTERPOLATION, flags);
    328    }
    329
    330    /**
    331     * Sets options from engine.
    332     * @param jexl the engine
    333     * @return {@code this} instance
    334     */
    335    public JexlOptions set(final JexlEngine jexl) {
    336        if (jexl instanceof Engine) {
    337            return ((Engine) jexl).optionsSet(this);
    338        }
    339        throw new UnsupportedOperationException("JexlEngine is not an Engine instance: " + jexl.getClass().getName());
    340    }
    341
    342    /**
    343     * Sets options from options.
    344     * @param src the options
    345     * @return {@code this} instance
    346     */
    347    public JexlOptions set(final JexlOptions src) {
    348        mathContext = src.mathContext;
    349        mathScale = src.mathScale;
    350        strictArithmetic = src.strictArithmetic;
    351        flags = src.flags;
    352        namespaces = src.namespaces;
    353        imports = src.imports;
    354        return this;
    355    }
    356
    357    /**
    358     * Sets whether the engine will attempt solving antish variable names from
    359     * context.
    360     * @param flag true if antish variables are solved, false otherwise
    361     */
    362    public void setAntish(final boolean flag) {
    363        flags = set(ANTISH, flags, flag);
    364    }
    365
    366    /**
    367     * Sets whether logical expressions (&quot;&quot; , ||) coerce their result to boolean.
    368     * @param flag true or false
    369     */
    370    public void setBooleanLogical(final boolean flag) {
    371        flags = set(BOOLEAN_LOGICAL, flags, flag);
    372    }
    373
    374    /**
    375     * Sets whether the engine will throw JexlException.Cancel (true) or return
    376     * null (false) when interrupted during evaluation.
    377     * @param flag true when cancellable, false otherwise
    378     */
    379    public void setCancellable(final boolean flag) {
    380        flags = set(CANCELLABLE, flags, flag);
    381    }
    382
    383    /**
    384     * Sets whether lambda captured-variables are const or not.
    385     * <p>
    386     * When disabled, lambda-captured variables are implicitly converted to read-write local variable (let),
    387     * when enabled, those are implicitly converted to read-only local variables (const).
    388     * </p>
    389     * @param flag true to enable, false to disable
    390     */
    391    public void setConstCapture(final boolean flag) {
    392        flags = set(CONST_CAPTURE, flags, flag);
    393    }
    394
    395    /**
    396     * Sets this option flags using the +/- syntax.
    397     * @param opts the option flags
    398     */
    399    public void setFlags(final String... opts) {
    400        flags = parseFlags(flags, opts);
    401    }
    402
    403    /**
    404     * Sets the optional set of imports.
    405     * @param imports the imported packages
    406     */
    407    public void setImports(final Collection<String> imports) {
    408        this.imports = imports == null || imports.isEmpty()? Collections.emptySet() : imports;
    409    }
    410
    411    /**
    412     * Sets whether the engine uses a strict block lexical scope during
    413     * evaluation.
    414     * @param flag true if lexical scope is used, false otherwise
    415     */
    416    public void setLexical(final boolean flag) {
    417        flags = set(LEXICAL, flags, flag);
    418    }
    419
    420    /**
    421     * Sets whether the engine strictly shades global variables.
    422     * Local symbols shade globals after definition and creating global
    423     * variables is prohibited during evaluation.
    424     * If setting to lexical shade, lexical scope is also set.
    425     * @param flag true if creation is allowed, false otherwise
    426     */
    427    public void setLexicalShade(final boolean flag) {
    428        flags = set(SHADE, flags, flag);
    429        if (flag) {
    430            flags = set(LEXICAL, flags, true);
    431        }
    432    }
    433
    434    /**
    435     * Sets the arithmetic math context.
    436     * @param mcontext the context
    437     */
    438    public void setMathContext(final MathContext mcontext) {
    439        this.mathContext = mcontext;
    440    }
    441
    442    /**
    443     * Sets the arithmetic math scale.
    444     * @param mscale the scale
    445     */
    446    public void setMathScale(final int mscale) {
    447        this.mathScale = mscale;
    448    }
    449
    450    /**
    451     * Sets the optional map of namespaces.
    452     * @param ns a namespaces map
    453     */
    454    public void setNamespaces(final Map<String, Object> ns) {
    455        this.namespaces = ns == null || ns.isEmpty()? Collections.emptyMap() : ns;
    456    }
    457
    458    /**
    459     * Sets whether the engine considers null in navigation expression as null or as errors
    460     * during evaluation.
    461     * <p>If safe, encountering null during a navigation expression - dereferencing a method or a field through a null
    462     * object or property - will <em>not</em> be considered an error but evaluated as <em>null</em>. It is recommended
    463     * to use <em>setSafe(false)</em> as an explicit default.</p>
    464     * @param flag true if safe, false otherwise
    465     */
    466    public void setSafe(final boolean flag) {
    467        flags = set(SAFE, flags, flag);
    468    }
    469
    470    /**
    471     * Sets wether these options are immutable at runtime.
    472     * <p>Expert mode; allows instance handled through context to be shared
    473     * instead of copied.
    474     * @param flag true if shared, false if not
    475     */
    476    public void setSharedInstance(final boolean flag) {
    477        flags = set(SHARED, flags, flag);
    478    }
    479
    480    /**
    481     * Sets whether the engine will throw a {@link JexlException} when an error
    482     * is encountered during evaluation.
    483     * @param flag true if silent, false otherwise
    484     */
    485    public void setSilent(final boolean flag) {
    486        flags = set(SILENT, flags, flag);
    487    }
    488
    489    /**
    490     * Sets whether the engine considers unknown variables, methods and
    491     * constructors as errors during evaluation.
    492     * @param flag true if strict, false otherwise
    493     */
    494    public void setStrict(final boolean flag) {
    495        flags = set(STRICT, flags, flag);
    496    }
    497
    498    /**
    499     * Sets the strict arithmetic flag.
    500     * @param stricta true or false
    501     */
    502    public void setStrictArithmetic(final boolean stricta) {
    503        this.strictArithmetic = stricta;
    504    }
    505
    506    /**
    507     * Sets the strict interpolation flag.
    508     * <p>When strict, interpolation strings composed only of an expression (ie `${...}`) are evaluated
    509     * as strings; when not strict, integer results are left untouched.</p>
    510     * This can affect the results of expressions like <code>map.`${key}`</code> when key is
    511     * an integer (or a string); it is almost always possible to use <code>map[key]</code> to ensure
    512     * that the key type is not altered.
    513     * @param strict true or false
    514     */
    515    public void setStrictInterpolation(final boolean strict) {
    516        flags = set(STRICT_INTERPOLATION, flags, strict);
    517    }
    518
    519    @Override public String toString() {
    520        final StringBuilder strb = new StringBuilder();
    521        for(int i = 0; i < NAMES.length; ++i) {
    522            if (i > 0) {
    523                strb.append(' ');
    524            }
    525            strb.append((flags & 1 << i) != 0? '+':'-');
    526            strb.append(NAMES[i]);
    527        }
    528        return strb.toString();
    529    }
    530
    531}