    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 */
    017package org.apache.commons.jexl3;
    018
    019import java.util.Arrays;
    020import java.util.Collection;
    021import java.util.Collections;
    022import java.util.HashSet;
    023import java.util.Objects;
    024import java.util.Set;
    025import java.util.TreeSet;
    026import java.util.function.Predicate;
    027
    028/**
    029 * A set of language feature options.
    030 * <p>
    031 * These control <em>syntactical</em> constructs that will throw JexlException.Feature exceptions (a
    032 * subclass of JexlException.Parsing) when disabled.
    033 * </p>
    034 * <p>It is recommended to be explicit in choosing the features you need rather than rely on the default
    035 * constructor: the 2 convenience methods {@link JexlFeatures#createNone()} and {@link JexlFeatures#createAll()}
    036 * are the recommended starting points to selectively enable or disable chosen features.</p>
    037 * <ul>
    038 * <li>Registers: register syntax (#number), used internally for {g,s}etProperty
    039 * <li>Reserved Names: a set of reserved variable names that cannot be used as local variable (or parameter) names
    040 * <li>Global Side Effect : assigning/modifying values on global variables (=, += , -=, ...)
    041 * <li>Lexical: lexical scope, prevents redefining local variables
    042 * <li>Lexical Shade: local variables shade globals, prevents confusing a global variable with a local one
    043 * <li>Side Effect : assigning/modifying values on any variables or left-value
    044 * <li>Constant Array Reference: ensures array references only use constants;they should be statically solvable.
    045 * <li>New Instance: creating an instance using new(...)
    046 * <li>Loops: loop constructs (while(true), for(...))
    047 * <li>Lambda: function definitions (()-&gt;{...}, function(...) ).
    048 * <li>Method calls: calling methods (obj.method(...) or obj['method'](...)); when disabled, leaves function calls
    049 * - including namespace prefixes - available
    050 * <li>Structured literals: arrays, lists, maps, sets, ranges
    051 * <li>Pragma: pragma construct as in {@code #pragma x y}
    052 * <li>Annotation: @annotation statement;
    053 * <li>Thin-arrow: use the thin-arrow, ie {@code ->} for lambdas as in {@code x -> x + x}
    054 * <li>Fat-arrow: use the  fat-arrow, ie {@code =>} for lambdas as in {@code x => x + x}
    055 * <li>Namespace pragma: whether the {@code #pragma jexl.namespace.ns namespace} syntax is allowed</li>
    056 * <li>Namespace identifier: whether the {@code ns:fun(...)} parser treats the ns:fun as one identifier, no spaces allowed</li>
    057 * <li>Import pragma: whether the {@code #pragma jexl.import fully.qualified.class.name} syntax is allowed</li>
    058 * <li>Comparator names: whether the comparator operator names can be used (as in {@code gt} for &gt;,
    059 * {@code lt} for &lt;, ...)</li>
    060 * <li>Pragma anywhere: whether pragma, that are <em>not</em> statements and handled before execution begins,
    061 * can appear anywhere in the source or before any statements - ie at the beginning of a script.</li>
    062 * <li>Const Capture: whether variables captured by lambdas are read-only (aka const, same as Java) or read-write.</li>
    063 * <li>Reference Capture: whether variables captured by lambdas are pass-by-reference or pass-by-value.</li>
    064 * </ul>
    065 * @since 3.2
    066 */
    067public final class JexlFeatures {
    068    /** The false predicate. */
    069    public static final Predicate<String> TEST_STR_FALSE = s -> false;
    070    /** Te feature names (for toString()). */
    071    private static final String[] F_NAMES = {
    072        "register", "reserved variable", "local variable", "assign/modify",
    073        "global assign/modify", "array reference", "create instance", "loop", "function",
    074        "method call", "set/map/array literal", "pragma", "annotation", "script", "lexical", "lexicalShade",
    075        "thin-arrow", "fat-arrow", "namespace pragma", "namespace identifier", "import pragma", "comparator names", "pragma anywhere",
    076        "const capture", "ref capture"
    077    };
    078    /** Registers feature ordinal. */
    079    private static final int REGISTER = 0;
    080    /** Reserved future feature ordinal (unused as of 3.3.1). */
    081    public static final int RESERVED = 1;
    082    /** Locals feature ordinal. */
    083    public static final int LOCAL_VAR = 2;
    084    /** Side effects feature ordinal. */
    085    public static final int SIDE_EFFECT = 3;
    086    /** Global side effects feature ordinal. */
    087    public static final int SIDE_EFFECT_GLOBAL = 4;
    088    /** Expressions allowed in array reference ordinal. */
    089    public static final int ARRAY_REF_EXPR = 5;
    090    /** New-instance feature ordinal. */
    091    public static final int NEW_INSTANCE = 6;
    092    /** Loops feature ordinal. */
    093    public static final int LOOP = 7;
    094    /** Lambda feature ordinal. */
    095    public static final int LAMBDA = 8;
    096    /** Lambda feature ordinal. */
    097    public static final int METHOD_CALL = 9;
    098    /** Structured literal feature ordinal. */
    099    public static final int STRUCTURED_LITERAL = 10;
    100    /** Pragma feature ordinal. */
    101    public static final int PRAGMA = 11;
    102    /** Annotation feature ordinal. */
    103    public static final int ANNOTATION = 12;
    104    /** Script feature ordinal. */
    105    public static final int SCRIPT = 13;
    106    /** Lexical feature ordinal. */
    107    public static final int LEXICAL = 14;
    108    /** Lexical shade feature ordinal. */
    109    public static final int LEXICAL_SHADE = 15;
    110    /** Thin-arrow lambda syntax. */
    111    public static final int THIN_ARROW = 16;
    112    /** Fat-arrow lambda syntax. */
    113    public static final int FAT_ARROW = 17;
    114    /** Namespace pragma feature ordinal. */
    115    public static final int NS_PRAGMA = 18;
    116    /** Namespace syntax as an identifier (no space). */
    117    public static final int NS_IDENTIFIER = 19;
    118    /** Import pragma feature ordinal. */
    119    public static final int IMPORT_PRAGMA = 20;
    120    /** Comparator names (legacy) syntax. */
    121    public static final int COMPARATOR_NAMES = 21;
    122    /** The pragma anywhere feature ordinal. */
    123    public static final int PRAGMA_ANYWHERE = 22;
    124    /** Captured variables are const. */
    125    public static final int CONST_CAPTURE = 23;
    126    /** Captured variables are reference. */
    127    public static final int REF_CAPTURE = 24;
    128    /** Captured variables are reference. */
    129    public static final int STRICT_STATEMENT = 25;
    130    /**
    131     * All features.
    132     * Ensure this is updated if additional features are added.
    133     */
    134    private static final long ALL_FEATURES = (1L << REF_CAPTURE + 1) - 1L; // MUST REMAIN PRIVATE
    135    /**
    136     * The default features flag mask.
    137     * <p>Meant for compatibility with scripts written before 3.3.1</p>
    138     */
    139    private static final long DEFAULT_FEATURES = // MUST REMAIN PRIVATE
    140        1L << LOCAL_VAR
    141        | 1L << SIDE_EFFECT
    142        | 1L << SIDE_EFFECT_GLOBAL
    143        | 1L << ARRAY_REF_EXPR
    144        | 1L << NEW_INSTANCE
    145        | 1L << LOOP
    146        | 1L << LAMBDA
    147        | 1L << METHOD_CALL
    148        | 1L << STRUCTURED_LITERAL
    149        | 1L << PRAGMA
    150        | 1L << ANNOTATION
    151        | 1L << SCRIPT
    152        | 1L << THIN_ARROW
    153        | 1L << NS_PRAGMA
    154        | 1L << IMPORT_PRAGMA
    155        | 1L << COMPARATOR_NAMES
    156        | 1L << PRAGMA_ANYWHERE;
    157    /**
    158     * The canonical scripting (since 3.3.1) features flag mask based on the original default.
    159     * <p>Adds lexical, lexical-shade and const-capture but removes comparator-names and pragma-anywhere</p>
    160     */
    161    private static final long SCRIPT_FEATURES = // MUST REMAIN PRIVATE
    162        (DEFAULT_FEATURES
    163        | 1L << LEXICAL
    164        | 1L << LEXICAL_SHADE
    165        | 1L << CONST_CAPTURE) // these parentheses are necessary :-)
    166        & ~(1L << COMPARATOR_NAMES)
    167        & ~(1L << PRAGMA_ANYWHERE);
    168
    169    /**
    170     * Protected future syntactic elements.
    171     * <p><em>throw, switch, case, default, class, instanceof, jexl, $jexl</em></p>
    172     * @since 3.3.1
    173     */
    174    private static final Set<String> RESERVED_WORDS =
    175        Collections.unmodifiableSet(
    176            new HashSet<>(Arrays.asList(
    177                "switch", "case", "default", "class", "jexl", "$jexl")));
    178
    179    /*
    180     * *WARNING*
    181     * Static fields may be inlined by the Java compiler, so their _values_ effectively form part of the external API.
    182     * Classes that reference them need to be recompiled to pick up new values.
    183     * This means that changes in value are not binary compatible.
    184     * Such fields must be private or problems may occur.
    185     */
    186
    187     /**
    188     * Creates an all features enabled set.
    189     * @return a new instance of all features set
    190     * @since 3.3.1
    191     */
    192    public static JexlFeatures createAll() {
    193        return new JexlFeatures(ALL_FEATURES, null, null);
    194    }
    195
    196    /**
    197     * Creates a default features set suitable for basic but complete scripting needs.
    198     * <p>Maximizes compatibility with older version scripts (before 3.3), new projects should
    199     * use {@link JexlFeatures#createScript()} or equivalent features as a base.</p>
    200     * <p>The following scripting features are enabled:</p>
    201     * <ul>
    202     *   <li>local variable, {@link JexlFeatures#supportsLocalVar()}</li>
    203     *   <li>side effect, {@link JexlFeatures#supportsSideEffect()}</li>
    204     *   <li>global side effect, {@link JexlFeatures#supportsSideEffectGlobal()}</li>
    205     *   <li>array reference expression, {@link JexlFeatures#supportsStructuredLiteral()}</li>
    206     *   <li>new instance, {@link JexlFeatures#supportsNewInstance()} </li>
    207     *   <li>loop, {@link JexlFeatures#supportsLoops()}</li>
    208     *   <li>lambda, {@link JexlFeatures#supportsLambda()}</li>
    209     *   <li>method call, {@link JexlFeatures#supportsMethodCall()}</li>
    210     *   <li>structured literal, {@link JexlFeatures#supportsStructuredLiteral()}</li>
    211     *   <li>pragma, {@link JexlFeatures#supportsPragma()}</li>
    212     *   <li>annotation, {@link JexlFeatures#supportsAnnotation()}</li>
    213     *   <li>script, {@link JexlFeatures#supportsScript()}</li>
    214     *   <li>comparator names,  {@link JexlFeatures#supportsComparatorNames()}</li>
    215     *   <li>namespace pragma,  {@link JexlFeatures#supportsNamespacePragma()}</li>
    216     *   <li>import pragma, {@link JexlFeatures#supportsImportPragma()}</li>
    217     *   <li>pragma anywhere, {@link JexlFeatures#supportsPragmaAnywhere()}</li>
    218     * </ul>
    219     * @return a new instance of a default scripting features set
    220     * @since 3.3.1
    221     */
    222    public static JexlFeatures createDefault() {
    223        return new JexlFeatures(DEFAULT_FEATURES, null, null);
    224    }
    225
    226    /**
    227     * Creates an empty feature set.
    228     * <p>This is the strictest base-set since no feature is allowed, suitable as-is only
    229     * for the simplest expressions.</p>
    230     * @return a new instance of an empty features set
    231     * @since 3.3.1
    232     */
    233    public static JexlFeatures createNone() {
    234        return new JexlFeatures(0L, null, null);
    235    }
    236
    237    /**
    238     * The modern scripting features set.
    239     * <p>This is the recommended set for new projects.</p>
    240     * <p>All default features with the following differences:</p>
    241     * <ul>
    242     * <li><em>disable</em> pragma-anywhere, {@link JexlFeatures#supportsPragmaAnywhere()}</li>
    243     * <li><em>disable</em> comparator-names, {@link JexlFeatures#supportsComparatorNames()}</li>
    244     * <li><em>enable</em> lexical, {@link JexlFeatures#isLexical()}</li>
    245     * <li><em>enable</em> lexical-shade, {@link JexlFeatures#isLexicalShade()} </li>
    246     * <li><em>enable</em> const-capture, {@link JexlFeatures#supportsConstCapture()}</li>
    247     * </ul>
    248     * <p>It also adds a set of reserved words to enable future unencumbered syntax evolution:
    249     * <em>try, catch, throw, finally, switch, case, default, class, instanceof</em>
    250     * </p>
    251     * @return a new instance of a modern scripting features set
    252     * @since 3.3.1
    253     */
    254    public static JexlFeatures createScript() {
    255        return new JexlFeatures(SCRIPT_FEATURES, RESERVED_WORDS, null);
    256    }
    257
    258    /**
    259     * The text corresponding to a feature code.
    260     * @param feature the feature number
    261     * @return the feature name
    262     */
    263    public static String stringify(final int feature) {
    264        return feature >= 0 && feature < F_NAMES.length ? F_NAMES[feature] : "unsupported feature";
    265    }
    266
    267    /** The feature flags. */
    268    private long flags;
    269
    270    /** The set of reserved names, aka global variables that cannot be masked by local variables or parameters. */
    271    private Set<String> reservedNames;
    272
    273    /** The namespace names. */
    274    private Predicate<String> nameSpaces;
    275
    276    /**
    277     * Creates default instance, equivalent to the result of calling the preferred alternative
    278     * {@link JexlFeatures#createDefault()}
    279     */
    280    public JexlFeatures() {
    281        this(DEFAULT_FEATURES, null, null);
    282    }
    283
    284    /**
    285     * Copy constructor.
    286     * @param features the feature to copy from
    287     */
    288    public JexlFeatures(final JexlFeatures features) {
    289        this(features.flags, features.reservedNames, features.nameSpaces);
    290    }
    291
    292    /**
    293     * An all member constructor for derivation.
    294     * <p>Not respecting immutability or thread-safety constraints for this class constructor arguments will
    295     * likely result in unexpected behavior.</p>
    296     * @param f flag
    297     * @param r reserved variable names; must be an immutable Set or thread-safe (concurrent or synchronized set)
    298     * @param n namespace predicate; must be stateless or thread-safe
    299     */
    300    protected JexlFeatures(final long f, final Set<String> r, final Predicate<String> n) {
    301        this.flags = f;
    302        this.reservedNames = r == null? Collections.emptySet() : r;
    303        this.nameSpaces = n == null? TEST_STR_FALSE : n;
    304    }
    305
    306    /**
    307     * Sets whether annotation constructs are enabled.
    308     * <p>
    309     * When disabled, parsing a script/expression using syntactic annotation constructs (@annotation)
    310     * will throw a parsing exception.
    311     * </p>
    312     * @param flag true to enable, false to disable
    313     * @return this features instance
    314     */
    315    public JexlFeatures annotation(final boolean flag) {
    316        setFeature(ANNOTATION, flag);
    317        return this;
    318    }
    319
    320    /**
    321     * Sets whether array references expressions are enabled.
    322     * <p>
    323     * When disabled, parsing a script/expression using 'obj[ ref ]' where ref is not a string or integer literal
    324     * will throw a parsing exception;
    325     * </p>
    326     * @param flag true to enable, false to disable
    327     * @return this features instance
    328     */
    329    public JexlFeatures arrayReferenceExpr(final boolean flag) {
    330        setFeature(ARRAY_REF_EXPR, flag);
    331        return this;
    332    }
    333
    334    /**
    335     * Sets whether the legacy comparison operator names syntax is enabled.
    336     * <p>
    337     * When disabled, comparison operators names (eq;ne;le;lt;ge;gt)
    338     * will be treated as plain identifiers.
    339     * </p>
    340     * @param flag true to enable, false to disable
    341     * @return this features instance
    342     * @since 3.3
    343     */
    344    public JexlFeatures comparatorNames(final boolean flag) {
    345        setFeature(COMPARATOR_NAMES, flag);
    346        return this;
    347    }
    348
    349    /**
    350     * Sets whether lambda captured-variables are constant or mutable.
    351     * <p>
    352     * When disabled, lambda-captured variables are implicitly converted to read-write local variable (let),
    353     * when enabled, those are implicitly converted to read-only local variables (const).
    354     * </p>
    355     * @param flag true to enable, false to disable
    356     * @return this features instance
    357     */
    358    public JexlFeatures constCapture(final boolean flag) {
    359        setFeature(CONST_CAPTURE, flag);
    360        return this;
    361    }
    362
    363    /**
    364     * Sets whether lambda captured-variables are references or values.
    365     * <p>When variables are pass-by-reference, side-effects are visible from inner lexical scopes
    366     * to outer-scope.</p>
    367     * <p>
    368     * When disabled, lambda-captured variables use pass-by-value semantic,
    369     * when enabled, those use pass-by-reference semantic.
    370     * </p>
    371     * @param flag true to enable, false to disable
    372     * @return this features instance
    373     */
    374    public JexlFeatures referenceCapture(final boolean flag) {
    375        setFeature(REF_CAPTURE, flag);
    376        return this;
    377    }
    378
    379    @Override
    380    public boolean equals(final Object obj) {
    381        if (this == obj) {
    382            return true;
    383        }
    384        if (obj == null) {
    385            return false;
    386        }
    387        if (getClass() != obj.getClass()) {
    388            return false;
    389        }
    390        final JexlFeatures other = (JexlFeatures) obj;
    391        if (this.flags != other.flags) {
    392            return false;
    393        }
    394        if (this.nameSpaces != other.nameSpaces) {
    395            return false;
    396        }
    397        if (!Objects.equals(this.reservedNames, other.reservedNames)) {
    398            return false;
    399        }
    400        return true;
    401    }
    402
    403    /**
    404     * Sets whether fat-arrow lambda syntax is enabled.
    405     * <p>
    406     * When disabled, parsing a script/expression using syntactic fat-arrow (=&lt;)
    407     * will throw a parsing exception.
    408     * </p>
    409     * @param flag true to enable, false to disable
    410     * @return this features instance
    411     * @since 3.3
    412     */
    413    public JexlFeatures fatArrow(final boolean flag) {
    414        setFeature(FAT_ARROW, flag);
    415        return this;
    416    }
    417
    418    /**
    419     * Gets a feature flag value.
    420     * @param feature feature ordinal
    421     * @return true if on, false if off
    422     */
    423    private boolean getFeature(final int feature) {
    424        return (flags & 1L << feature) != 0L;
    425    }
    426
    427    /**
    428     * Gets the feature flags
    429     * @return these features&quot;s flags
    430     */
    431    public long getFlags() {
    432        return flags;
    433    }
    434
    435    /**
    436     * Gets the immutable set of reserved names.
    437     * @return the (unmodifiable) set of reserved names.
    438     */
    439    public Set<String> getReservedNames() {
    440        return reservedNames;
    441    }
    442
    443    @Override
    444    public int hashCode() { //CSOFF: MagicNumber
    445        int hash = 3;
    446        hash = 53 * hash + (int) (this.flags ^ this.flags >>> 32);
    447        hash = 53 * hash + (this.reservedNames != null ? this.reservedNames.hashCode() : 0);
    448        return hash;
    449    }
    450
    451    /**
    452     * Sets whether import pragma constructs are enabled.
    453     * <p>
    454     * When disabled, parsing a script/expression using syntactic import pragma constructs
    455     * (#pragma jexl.import....) will throw a parsing exception.
    456     * </p>
    457     * @param flag true to enable, false to disable
    458     * @return this features instance
    459     * @since 3.3
    460     */
    461    public JexlFeatures importPragma(final boolean flag) {
    462        setFeature(IMPORT_PRAGMA, flag);
    463        return this;
    464    }
    465
    466    /**
    467     * Is the lexical scope feature enabled?
    468     * @return whether lexical scope feature is enabled */
    469    public boolean isLexical() {
    470        return getFeature(LEXICAL);
    471    }
    472
    473    /**
    474     * Is the lexical shade feature enabled?
    475     * @return whether lexical shade feature is enabled */
    476    public boolean isLexicalShade() {
    477        return getFeature(LEXICAL_SHADE);
    478    }
    479
    480    /**
    481     * Checks whether a name is reserved.
    482     * @param name the name to check
    483     * @return true if reserved, false otherwise
    484     */
    485    public boolean isReservedName(final String name) {
    486        return name != null && reservedNames.contains(name);
    487    }
    488
    489    /**
    490     * Sets whether lambda/function constructs are enabled.
    491     * <p>
    492     * When disabled, parsing a script/expression using syntactic lambda constructs (-&gt;,function)
    493     * will throw a parsing exception.
    494     * </p>
    495     * @param flag true to enable, false to disable
    496     * @return this features instance
    497     */
    498    public JexlFeatures lambda(final boolean flag) {
    499        setFeature(LAMBDA, flag);
    500        return this;
    501    }
    502
    503    /**
    504     * Sets whether syntactic lexical mode is enabled.
    505     *
    506     * @param flag true means syntactic lexical function scope is in effect, false implies non-lexical scoping
    507     * @return this features instance
    508     */
    509    public JexlFeatures lexical(final boolean flag) {
    510        setFeature(LEXICAL, flag);
    511        if (!flag) {
    512            setFeature(LEXICAL_SHADE, false);
    513        }
    514        return this;
    515    }
    516
    517    /**
    518     * Sets whether syntactic lexical shade is enabled.
    519     *
    520     * @param flag true means syntactic lexical shade is in effect and implies lexical scope
    521     * @return this features instance
    522     */
    523    public JexlFeatures lexicalShade(final boolean flag) {
    524        setFeature(LEXICAL_SHADE, flag);
    525        if (flag) {
    526            setFeature(LEXICAL, true);
    527        }
    528        return this;
    529    }
    530
    531    /**
    532     * Sets whether local variables are enabled.
    533     * <p>
    534     * When disabled, parsing a script/expression using a local variable or parameter syntax
    535     * will throw a parsing exception.
    536     * </p>
    537     * @param flag true to enable, false to disable
    538     * @return this features instance
    539     */
    540    public JexlFeatures localVar(final boolean flag) {
    541        setFeature(LOCAL_VAR, flag);
    542        return this;
    543    }
    544
    545    /**
    546     * Sets whether looping constructs are enabled.
    547     * <p>
    548     * When disabled, parsing a script/expression using syntactic looping constructs (for,while)
    549     * will throw a parsing exception.
    550     * </p>
    551     * @param flag true to enable, false to disable
    552     * @return this features instance
    553     */
    554    public JexlFeatures loops(final boolean flag) {
    555        setFeature(LOOP, flag);
    556        return this;
    557    }
    558
    559    /**
    560     * Sets whether method calls expressions are enabled.
    561     * <p>
    562     * When disabled, parsing a script/expression using 'obj.method()'
    563     * will throw a parsing exception;
    564     * </p>
    565     * @param flag true to enable, false to disable
    566     * @return this features instance
    567     */
    568    public JexlFeatures methodCall(final boolean flag) {
    569        setFeature(METHOD_CALL, flag);
    570        return this;
    571    }
    572
    573    /**
    574     * Sets whether namespace pragma constructs are enabled.
    575     * <p>
    576     * When disabled, parsing a script/expression using syntactic namespace pragma constructs
    577     * (#pragma jexl.namespace....) will throw a parsing exception.
    578     * </p>
    579     * @param flag true to enable, false to disable
    580     * @return this features instance
    581     * @since 3.3
    582     */
    583    public JexlFeatures namespacePragma(final boolean flag) {
    584        setFeature(NS_PRAGMA, flag);
    585        return this;
    586    }
    587
    588    /**
    589     * Sets whether namespace as identifier syntax is enabled.
    590     * <p>
    591     * When enabled, a namespace call must be of the form <code>ns:fun(...)</code> with no
    592     * spaces between the namespace name and the function.
    593     * </p>
    594     * @param flag true to enable, false to disable
    595     * @return this features instance
    596     * @since 3.5.0
    597     */
    598    public JexlFeatures namespaceIdentifier(final boolean flag) {
    599        setFeature(NS_IDENTIFIER, flag);
    600        return this;
    601    }
    602
    603    /**
    604     * Gets the declared namespaces test.
    605     * @return the declared namespaces test.
    606     */
    607    public Predicate<String> namespaceTest() {
    608        return nameSpaces;
    609    }
    610
    611    /**
    612     * Sets a test to determine namespace declaration.
    613     * @param names the name predicate
    614     * @return this features instance
    615     */
    616    public JexlFeatures namespaceTest(final Predicate<String> names) {
    617        nameSpaces = names == null ? TEST_STR_FALSE : names;
    618        return this;
    619    }
    620
    621    /**
    622     * Sets whether creating new instances is enabled.
    623     * <p>
    624     * When disabled, parsing a script/expression using 'new(...)' will throw a parsing exception;
    625     * using a class as functor will fail at runtime.
    626     * </p>
    627     * @param flag true to enable, false to disable
    628     * @return this features instance
    629     */
    630    public JexlFeatures newInstance(final boolean flag) {
    631        setFeature(NEW_INSTANCE, flag);
    632        return this;
    633    }
    634
    635    /**
    636     * Sets whether pragma constructs are enabled.
    637     * <p>
    638     * When disabled, parsing a script/expression using syntactic pragma constructs (#pragma)
    639     * will throw a parsing exception.
    640     * </p>
    641     * @param flag true to enable, false to disable
    642     * @return this features instance
    643     */
    644    public JexlFeatures pragma(final boolean flag) {
    645        setFeature(PRAGMA, flag);
    646        if (!flag) {
    647            setFeature(NS_PRAGMA, false);
    648            setFeature(IMPORT_PRAGMA, false);
    649        }
    650        return this;
    651    }
    652
    653    /**
    654     * Sets whether pragma constructs can appear anywhere in the code.
    655     *
    656     * @param flag true to enable, false to disable
    657     * @return this features instance
    658     * @since 3.3
    659     */
    660    public JexlFeatures pragmaAnywhere(final boolean flag) {
    661        setFeature(PRAGMA_ANYWHERE, flag);
    662        return this;
    663    }
    664
    665    /**
    666     * Sets whether register are enabled.
    667     * <p>
    668     * This is mostly used internally during execution of JexlEngine.{g,s}etProperty.
    669     * </p>
    670     * <p>
    671     * When disabled, parsing a script/expression using the register syntax will throw a parsing exception.
    672     * </p>
    673     * @param flag true to enable, false to disable
    674     * @return this features instance
    675     */
    676    public JexlFeatures register(final boolean flag) {
    677        setFeature(REGISTER, flag);
    678        return this;
    679    }
    680
    681    /**
    682     * Sets a collection of reserved r precluding those to be used as local variables or parameter r.
    683     * @param names the r to reserve
    684     * @return this features instance
    685     */
    686    public JexlFeatures reservedNames(final Collection<String> names) {
    687        if (names == null || names.isEmpty()) {
    688            reservedNames = Collections.emptySet();
    689        } else {
    690            reservedNames = Collections.unmodifiableSet(new TreeSet<>(names));
    691        }
    692        return this;
    693    }
    694
    695    /**
    696     * Sets whether scripts constructs are enabled.
    697     * <p>
    698     * When disabled, parsing a script using syntactic script constructs (statements, ...)
    699     * will throw a parsing exception.
    700     * </p>
    701     * @param flag true to enable, false to disable
    702     * @return this features instance
    703     */
    704    public JexlFeatures script(final boolean flag) {
    705        setFeature(SCRIPT, flag);
    706        return this;
    707    }
    708
    709    /**
    710     * Sets a feature flag.
    711     * @param feature the feature ordinal
    712     * @param flag    turn-on, turn off
    713     */
    714    private void setFeature(final int feature, final boolean flag) {
    715        if (flag) {
    716            flags |= 1L << feature;
    717        } else {
    718            flags &= ~(1L << feature);
    719        }
    720    }
    721
    722    /**
    723     * Sets whether side effect expressions are enabled.
    724     * <p>
    725     * When disabled, parsing a script/expression using syntactical constructs modifying variables
    726     * or members will throw a parsing exception.
    727     * </p>
    728     * @param flag true to enable, false to disable
    729     * @return this features instance
    730     */
    731    public JexlFeatures sideEffect(final boolean flag) {
    732        setFeature(SIDE_EFFECT, flag);
    733        return this;
    734    }
    735
    736    /**
    737     * Sets whether side effect expressions on global variables (aka non-local) are enabled.
    738     * <p>
    739     * When disabled, parsing a script/expression using syntactical constructs modifying variables
    740     * <em>including all potentially ant-ish variables</em> will throw a parsing exception.
    741     * </p>
    742     * @param flag true to enable, false to disable
    743     * @return this features instance
    744     */
    745    public JexlFeatures sideEffectGlobal(final boolean flag) {
    746        setFeature(SIDE_EFFECT_GLOBAL, flag);
    747        return this;
    748    }
    749
    750    /**
    751     * Sets whether array/map/set literal expressions are enabled.
    752     * <p>
    753     * When disabled, parsing a script/expression creating one of these literals
    754     * will throw a parsing exception;
    755     * </p>
    756     * @param flag true to enable, false to disable
    757     * @return this features instance
    758     */
    759    public JexlFeatures structuredLiteral(final boolean flag) {
    760        setFeature(STRUCTURED_LITERAL, flag);
    761        return this;
    762    }
    763
    764    /**
    765     * Does the engine support annotations?
    766     * @return true if annotation are enabled, false otherwise
    767     */
    768    public boolean supportsAnnotation() {
    769        return getFeature(ANNOTATION);
    770    }
    771
    772    /**
    773     * Does the engine support array references which contain method call expressions?
    774     * @return true if array references can contain method call expressions, false otherwise
    775     */
    776    public boolean supportsArrayReferenceExpr() {
    777        return getFeature(ARRAY_REF_EXPR);
    778    }
    779
    780    /**
    781     * Does the engine support legacy comparison operator names syntax?
    782     * @return true if legacy comparison operator names syntax is enabled, false otherwise
    783     * @since 3.3
    784     */
    785    public boolean supportsComparatorNames() {
    786        return getFeature(COMPARATOR_NAMES);
    787    }
    788
    789    /**
    790     * Does the engine support lambda captured-variables as const?
    791     * @return true if lambda captured-variables are const, false otherwise
    792     */
    793    public boolean supportsConstCapture() {
    794        return getFeature(CONST_CAPTURE);
    795    }
    796
    797    /**
    798     * Does the engine support lambda captured-variables as references?
    799     * @return true if lambda captured-variables are references, false otherwise
    800     */
    801    public boolean supportsReferenceCapture() {
    802        return getFeature(REF_CAPTURE);
    803    }
    804
    805    /**
    806     * Does the engine support expressions (aka not scripts)
    807     * @return true if expressions (aka not scripts) are enabled, false otherwise
    808     */
    809    public boolean supportsExpression() {
    810        return !getFeature(SCRIPT);
    811    }
    812
    813    /**
    814     * Does the engine support fat-arrow lambda syntax?
    815     * @return true if fat-arrow lambda syntax is enabled, false otherwise
    816     * @since 3.3
    817     */
    818    public boolean supportsFatArrow() {
    819        return getFeature(FAT_ARROW);
    820    }
    821
    822    /**
    823     * Does the engine support import pragma?
    824     * @return true if import pragma are enabled, false otherwise
    825     * @since 3.3
    826     */
    827    public boolean supportsImportPragma() {
    828        return getFeature(IMPORT_PRAGMA);
    829    }
    830
    831    /**
    832     * Does the engine support lambdas?
    833     * @return true if lambda are enabled, false otherwise
    834     */
    835    public boolean supportsLambda() {
    836        return getFeature(LAMBDA);
    837    }
    838
    839    /**
    840     * Is local variables syntax enabled?
    841     * @return true if local variables syntax is enabled
    842     */
    843    public boolean supportsLocalVar() {
    844        return getFeature(LOCAL_VAR);
    845    }
    846
    847    /**
    848     * Are loops enabled?
    849     * @return true if loops are enabled, false otherwise
    850     */
    851    public boolean supportsLoops() {
    852        return getFeature(LOOP);
    853    }
    854
    855    /**
    856     * Can array references contain expressions?
    857     * @return true if array references can contain expressions, false otherwise
    858     */
    859    public boolean supportsMethodCall() {
    860        return getFeature(METHOD_CALL);
    861    }
    862
    863    /**
    864     * Is namespace pragma enabled?
    865     * @return true if namespace pragma are enabled, false otherwise
    866     * @since 3.3
    867     */
    868    public boolean supportsNamespacePragma() {
    869        return getFeature(NS_PRAGMA);
    870    }
    871
    872    /**
    873     * Is namespace identifier syntax enabled?
    874     * @return true if namespace identifier syntax is enabled, false otherwise
    875     * @since 3.5.0
    876     */
    877    public boolean supportsNamespaceIdentifier() {
    878        return getFeature(NS_IDENTIFIER);
    879    }
    880
    881    /**
    882     * Is creating new instances enabled?
    883     * @return true if creating new instances is enabled, false otherwise
    884     */
    885    public boolean supportsNewInstance() {
    886        return getFeature(NEW_INSTANCE);
    887    }
    888
    889    /**
    890     * Is the namespace pragma enabled?
    891     * @return true if namespace pragma are enabled, false otherwise
    892     */
    893    public boolean supportsPragma() {
    894        return getFeature(PRAGMA);
    895    }
    896
    897    /**
    898     * Can pragma constructs appear anywhere in the code?
    899     * @return true if pragma constructs can appear anywhere in the code, false otherwise
    900     * @since 3.3
    901     */
    902    public boolean supportsPragmaAnywhere() {
    903        return getFeature(PRAGMA_ANYWHERE);
    904    }
    905
    906    /**
    907     * Is register syntax enabled?
    908     * @return true if register syntax is enabled
    909     */
    910    public boolean supportsRegister() {
    911        return getFeature(REGISTER);
    912    }
    913
    914    /**
    915     * Are scripts enabled?
    916     * @return true if scripts are enabled, false otherwise
    917     */
    918    public boolean supportsScript() {
    919        return getFeature(SCRIPT);
    920    }
    921
    922    /**
    923     * Are side effects enabled?
    924     * @return true if side effects are enabled, false otherwise
    925     */
    926    public boolean supportsSideEffect() {
    927        return getFeature(SIDE_EFFECT);
    928    }
    929
    930    /**
    931     * Can global variables be assigned?
    932     * @return true if global variables can be assigned
    933     */
    934    public boolean supportsSideEffectGlobal() {
    935        return getFeature(SIDE_EFFECT_GLOBAL);
    936    }
    937
    938    /**
    939     * Are array/map/set literal expressions supported?
    940     * @return true if array/map/set literal expressions are supported, false otherwise
    941     */
    942    public boolean supportsStructuredLiteral() {
    943        return getFeature(STRUCTURED_LITERAL);
    944    }
    945
    946    /**
    947     * Is thin-arrow lambda syntax enabled?
    948     * @return true if thin-arrow lambda syntax is enabled, false otherwise
    949     * @since 3.3
    950     */
    951    public boolean supportsThinArrow() {
    952        return getFeature(THIN_ARROW);
    953    }
    954
    955    /**
    956     * Sets whether thin-arrow lambda syntax is enabled.
    957     * <p>
    958     * When disabled, parsing a script/expression using syntactic thin-arrow (-&lt;)
    959     * will throw a parsing exception.
    960     * </p>
    961     * @param flag true to enable, false to disable
    962     * @return this features instance
    963     * @since 3.3
    964     */
    965    public JexlFeatures thinArrow(final boolean flag) {
    966        setFeature(THIN_ARROW, flag);
    967        return this;
    968    }
    969}