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