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