    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 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 * </ul>
    040 * The sensible default is cancellable, strict and strictArithmetic.
    041 * <p>This interface replaces the now deprecated JexlEngine.Options.
    042 * @since 3.2
    043 */
    044public final class JexlOptions {
    045    /** The shared instance bit. */
    046    private static final int SHARED = 7;
    047    /** The local shade bit. */
    048    private static final int SHADE = 6;
    049    /** The antish var bit. */
    050    private static final int ANTISH = 5;
    051    /** The lexical scope bit. */
    052    private static final int LEXICAL = 4;
    053    /** The safe bit. */
    054    private static final int SAFE = 3;
    055    /** The silent bit. */
    056    private static final int SILENT = 2;
    057    /** The strict bit. */
    058    private static final int STRICT = 1;
    059    /** The cancellable bit. */
    060    private static final int CANCELLABLE = 0;
    061    /** The flag names ordered. */
    062    private static final String[] NAMES = {
    063        "cancellable", "strict", "silent", "safe", "lexical", "antish", "lexicalShade", "sharedInstance"
    064    };
    065    /** Default mask .*/
    066    private static int DEFAULT = 1 /*<< CANCELLABLE*/ | 1 << STRICT | 1 << ANTISH | 1 << SAFE;
    067    /** The arithmetic math context. */
    068    private MathContext mathContext = null;
    069    /** The arithmetic math scale. */
    070    private int mathScale = Integer.MIN_VALUE;
    071    /** The arithmetic strict math flag. */
    072    private boolean strictArithmetic = true;
    073    /** The default flags, all but safe. */
    074    private int flags = DEFAULT;
    075    /** The namespaces .*/
    076    private Map<String, Object> namespaces = Collections.emptyMap();
    077    /** The imports. */
    078    private Collection<String> imports = Collections.emptySet();
    079
    080    /**
    081     * Sets the value of a flag in a mask.
    082     * @param ordinal the flag ordinal
    083     * @param mask the flags mask
    084     * @param value true or false
    085     * @return the new flags mask value
    086     */
    087    private static int set(final int ordinal, final int mask, final boolean value) {
    088        return value? mask | (1 << ordinal) : mask & ~(1 << ordinal);
    089    }
    090
    091    /**
    092     * Checks the value of a flag in the mask.
    093     * @param ordinal the flag ordinal
    094     * @param mask the flags mask
    095     * @return the mask value with this flag or-ed in
    096     */
    097    private static boolean isSet(final int ordinal, final int mask) {
    098        return (mask & 1 << ordinal) != 0;
    099    }
    100
    101    /**
    102     * Default ctor.
    103     */
    104    public JexlOptions() {
    105        // all inits in members declarations
    106    }
    107
    108    /**
    109     * Sets the default (static, shared) option flags.
    110     * <p>
    111     * Whenever possible, we recommend using JexlBuilder methods to unambiguously instantiate a JEXL
    112     * engine; this method should only be used for testing / validation.
    113     * <p>A '+flag' or 'flag' will set the option named 'flag' as true, '-flag' set as false.
    114     * The possible flag names are:
    115     * cancellable, strict, silent, safe, lexical, antish, lexicalShade
    116     * <p>Calling JexlBuilder.setDefaultOptions("+safe") once before JEXL engine creation
    117     * may ease validating JEXL3.2 in your environment.
    118     * @param flags the flags to set
    119     */
    120    public static void setDefaultFlags(final String...flags) {
    121        DEFAULT = parseFlags(DEFAULT, flags);
    122    }
    123
    124    /**
    125     * Parses flags by name.
    126     * <p>A '+flag' or 'flag' will set flag as true, '-flag' set as false.
    127     * The possible flag names are:
    128     * cancellable, strict, silent, safe, lexical, antish, lexicalShade
    129     * @param initial the initial mask state
    130     * @param flags the flags to set
    131     * @return the flag mask updated
    132     */
    133    public static int parseFlags(final int initial, final String... flags) {
    134        int mask = initial;
    135        for (final String flag : flags) {
    136            boolean b = true;
    137            final String name;
    138            if (flag.charAt(0) == '+') {
    139                name = flag.substring(1);
    140            } else if (flag.charAt(0) == '-') {
    141                name = flag.substring(1);
    142                b = false;
    143            } else {
    144                name = flag;
    145            }
    146            for (int f = 0; f < NAMES.length; ++f) {
    147                if (NAMES[f].equals(name)) {
    148                    if (b) {
    149                        mask |= (1 << f);
    150                    } else {
    151                        mask &= ~(1 << f);
    152                    }
    153                    break;
    154                }
    155            }
    156        }
    157        return mask;
    158    }
    159
    160    /**
    161     * Sets this option flags using the +/- syntax.
    162     * @param opts the option flags
    163     */
    164    public void setFlags(final String... opts) {
    165        flags = parseFlags(flags, opts);
    166    }
    167
    168    /**
    169     * The MathContext instance used for +,-,/,*,% operations on big decimals.
    170     * @return the math context
    171     */
    172    public MathContext getMathContext() {
    173        return mathContext;
    174    }
    175
    176    /**
    177     * The BigDecimal scale used for comparison and coercion operations.
    178     * @return the scale
    179     */
    180    public int getMathScale() {
    181        return mathScale;
    182    }
    183
    184    /**
    185     * Checks whether evaluation will attempt resolving antish variable names.
    186     * @return true if antish variables are solved, false otherwise
    187     */
    188    public boolean isAntish() {
    189        return isSet(ANTISH, flags);
    190    }
    191
    192    /**
    193     * Checks whether evaluation will throw JexlException.Cancel (true) or
    194     * return null (false) if interrupted.
    195     * @return true when cancellable, false otherwise
    196     */
    197    public boolean isCancellable() {
    198        return isSet(CANCELLABLE, flags);
    199    }
    200
    201    /**
    202     * Checks whether runtime variable scope is lexical.
    203     * <p>If true, lexical scope applies to local variables and parameters.
    204     * Redefining a variable in the same lexical unit will generate errors.
    205     * @return true if scope is lexical, false otherwise
    206     */
    207    public boolean isLexical() {
    208        return isSet(LEXICAL, flags);
    209    }
    210
    211    /**
    212     * Checks whether local variables shade global ones.
    213     * <p>After a symbol is defined as local, dereferencing it outside its
    214     * scope will trigger an error instead of seeking a global variable of the
    215     * same name. To further reduce potential naming ambiguity errors,
    216     * global variables (ie non-local) must be declared to be assigned (@link JexlContext#has(String) )
    217     * when this flag is on; attempting to set an undeclared global variables will
    218     * raise an error.
    219     * @return true if lexical shading is applied, false otherwise
    220     */
    221    public boolean isLexicalShade() {
    222        return isSet(SHADE, flags);
    223    }
    224
    225    /**
    226     * Checks whether the engine considers null in navigation expression as
    227     * errors during evaluation..
    228     * @return true if safe, false otherwise
    229     */
    230    public boolean isSafe() {
    231        return isSet(SAFE, flags);
    232    }
    233
    234    /**
    235     * Checks whether the engine will throw a {@link JexlException} when an
    236     * error is encountered during evaluation.
    237     * @return true if silent, false otherwise
    238     */
    239    public boolean isSilent() {
    240        return isSet(SILENT, flags);
    241    }
    242
    243    /**
    244     * Checks whether the engine considers unknown variables, methods and
    245     * constructors as errors during evaluation.
    246     * @return true if strict, false otherwise
    247     */
    248    public boolean isStrict() {
    249        return isSet(STRICT, flags);
    250    }
    251
    252    /**
    253     * Checks whether the arithmetic triggers errors during evaluation when null
    254     * is used as an operand.
    255     * @return true if strict, false otherwise
    256     */
    257    public boolean isStrictArithmetic() {
    258        return strictArithmetic;
    259    }
    260
    261    /**
    262     * Sets whether the engine will attempt solving antish variable names from
    263     * context.
    264     * @param flag true if antish variables are solved, false otherwise
    265     */
    266    public void setAntish(final boolean flag) {
    267        flags = set(ANTISH, flags, flag);
    268    }
    269
    270    /**
    271     * Sets whether the engine will throw JexlException.Cancel (true) or return
    272     * null (false) when interrupted during evaluation.
    273     * @param flag true when cancellable, false otherwise
    274     */
    275    public void setCancellable(final boolean flag) {
    276        flags = set(CANCELLABLE, flags, flag);
    277    }
    278
    279    /**
    280     * Sets whether the engine uses a strict block lexical scope during
    281     * evaluation.
    282     * @param flag true if lexical scope is used, false otherwise
    283     */
    284    public void setLexical(final boolean flag) {
    285        flags = set(LEXICAL, flags, flag);
    286    }
    287
    288    /**
    289     * Sets whether the engine strictly shades global variables.
    290     * Local symbols shade globals after definition and creating global
    291     * variables is prohibited during evaluation.
    292     * If setting to lexical shade, lexical scope is also set.
    293     * @param flag true if creation is allowed, false otherwise
    294     */
    295    public void setLexicalShade(final boolean flag) {
    296        flags = set(SHADE, flags, flag);
    297        if (flag) {
    298            flags = set(LEXICAL, flags, true);
    299        }
    300    }
    301
    302    /**
    303     * Sets the arithmetic math context.
    304     * @param mcontext the context
    305     */
    306    public void setMathContext(final MathContext mcontext) {
    307        this.mathContext = mcontext;
    308    }
    309
    310    /**
    311     * Sets the arithmetic math scale.
    312     * @param mscale the scale
    313     */
    314    public void setMathScale(final int mscale) {
    315        this.mathScale = mscale;
    316    }
    317
    318    /**
    319     * Sets whether the engine considers null in navigation expression as null or as errors
    320     * during evaluation.
    321     * <p>If safe, encountering null during a navigation expression - dereferencing a method or a field through a null
    322     * object or property - will <em>not</em> be considered an error but evaluated as <em>null</em>. It is recommended
    323     * to use <em>setSafe(false)</em> as an explicit default.</p>
    324     * @param flag true if safe, false otherwise
    325     */
    326    public void setSafe(final boolean flag) {
    327        flags = set(SAFE, flags, flag);
    328    }
    329
    330    /**
    331     * Sets whether the engine will throw a {@link JexlException} when an error
    332     * is encountered during evaluation.
    333     * @param flag true if silent, false otherwise
    334     */
    335    public void setSilent(final boolean flag) {
    336        flags = set(SILENT, flags, flag);
    337    }
    338
    339    /**
    340     * Sets whether the engine considers unknown variables, methods and
    341     * constructors as errors during evaluation.
    342     * @param flag true if strict, false otherwise
    343     */
    344    public void setStrict(final boolean flag) {
    345        flags = set(STRICT, flags, flag);
    346    }
    347
    348    /**
    349     * Sets the strict arithmetic flag.
    350     * @param stricta true or false
    351     */
    352    public void setStrictArithmetic(final boolean stricta) {
    353        this.strictArithmetic = stricta;
    354    }
    355
    356    /**
    357     * Whether these options are immutable at runtime.
    358     * <p>Expert mode; allows instance handled through context to be shared
    359     * instead of copied.
    360     * @param flag true if shared, false if not
    361     */
    362    public void setSharedInstance(final boolean flag) {
    363        flags = set(SHARED, flags, flag);
    364    }
    365
    366    /**
    367     * @return false if a copy of these options is used during execution,
    368     * true if those can potentially be modified
    369     */
    370    public boolean isSharedInstance() {
    371        return isSet(SHARED, flags);
    372    }
    373
    374    /**
    375     * Set options from engine.
    376     * @param jexl the engine
    377     * @return this instance
    378     */
    379    public JexlOptions set(final JexlEngine jexl) {
    380        if (jexl instanceof Engine) {
    381            ((Engine) jexl).optionsSet(this);
    382        }
    383        return this;
    384    }
    385
    386    /**
    387     * Set options from options.
    388     * @param src the options
    389     * @return this instance
    390     */
    391    public JexlOptions set(final JexlOptions src) {
    392        mathContext = src.mathContext;
    393        mathScale = src.mathScale;
    394        strictArithmetic = src.strictArithmetic;
    395        flags = src.flags;
    396        namespaces = src.namespaces;
    397        imports = src.imports;
    398        return this;
    399    }
    400
    401    /**
    402     * Gets the optional map of namespaces.
    403     * @return the map of namespaces, may be empty, not null
    404     */
    405    public Map<String, Object> getNamespaces() {
    406        return namespaces;
    407    }
    408
    409    /**
    410     * Sets the optional map of namespaces.
    411     * @param ns a namespaces map
    412     */
    413    public void setNamespaces(final Map<String, Object> ns) {
    414        this.namespaces = ns == null || ns.isEmpty()? Collections.emptyMap() : ns;
    415    }
    416
    417    /**
    418     * Gets the optional set of imported packages.
    419     * @return the set of imports, may be empty, not null
    420     */
    421    public Collection<String> getImports() {
    422        return imports;
    423    }
    424
    425    /**
    426     * Sets the optional set of imports.
    427     * @param imports the imported packages
    428     */
    429    public void setImports(final Collection<String> imports) {
    430        this.imports = imports == null || imports.isEmpty()? Collections.emptySet() : imports;
    431    }
    432
    433    /**
    434     * Creates a copy of this instance.
    435     * @return a copy
    436     */
    437    public JexlOptions copy() {
    438        return new JexlOptions().set(this);
    439    }
    440
    441    @Override public String toString() {
    442        final StringBuilder strb = new StringBuilder();
    443        for(int i = 0; i < NAMES.length; ++i) {
    444            if (i > 0) {
    445                strb.append(' ');
    446            }
    447            strb.append((flags & (1 << i)) != 0? '+':'-');
    448            strb.append(NAMES[i]);
    449        }
    450        return strb.toString();
    451    }
    452
    453}