    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.Collection;
    020import java.util.Collections;
    021import java.util.Set;
    022import java.util.TreeSet;
    023import java.util.Objects;
    024import java.util.function.Predicate;
    025
    026/**
    027 * A set of language feature options.
    028 * These control <em>syntactical</em> constructs that will throw JexlException.Feature exceptions (a
    029 * subclass of JexlException.Parsing) when disabled.
    030 * <ul>
    031 * <li>Registers: register syntax (#number), used internally for {g,s}etProperty
    032 * <li>Reserved Names: a set of reserved variable names that can not be used as local variable (or parameter) names
    033 * <li>Global Side Effect : assigning/modifying values on global variables (=, += , -=, ...)
    034 * <li>Lexical: lexical scope, prevents redefining local variables
    035 * <li>Lexical Shade: local variables shade globals, prevents confusing a global variable with a local one
    036 * <li>Side Effect : assigning/modifying values on any variables or left-value
    037 * <li>Constant Array Reference: ensures array references only use constants;they should be statically solvable.
    038 * <li>New Instance: creating an instance using new(...)
    039 * <li>Loops: loop constructs (while(true), for(...))
    040 * <li>Lambda: function definitions (()-&gt;{...}, function(...) ).
    041 * <li>Method calls: calling methods (obj.method(...) or obj['method'](...)); when disabled, leaves function calls
    042 * - including namespace prefixes - available
    043 * <li>Structured literals: arrays, lists, maps, sets, ranges
    044 * <li>Pragmas: #pragma x y
    045 * <li>Annotation: @annotation statement;
    046 * </ul>
    047 * @since 3.2
    048 */
    049public final class JexlFeatures {
    050    /** The feature flags. */
    051    private long flags;
    052    /** The set of reserved names, aka global variables that can not be masked by local variables or parameters. */
    053    private Set<String> reservedNames;
    054    /** The namespace names. */
    055    private Predicate<String> nameSpaces;
    056    /** The false predicate. */
    057    public static final Predicate<String> TEST_STR_FALSE = (s)->false;
    058    /** Te feature names (for toString()). */
    059    private static final String[] F_NAMES = {
    060        "register", "reserved variable", "local variable", "assign/modify",
    061        "global assign/modify", "array reference", "create instance", "loop", "function",
    062        "method call", "set/map/array literal", "pragma", "annotation", "script", "lexical", "lexicalShade"
    063    };
    064    /** Registers feature ordinal. */
    065    private static final int REGISTER = 0;
    066    /** Reserved name feature ordinal. */
    067    public static final int RESERVED = 1;
    068    /** Locals feature ordinal. */
    069    public static final int LOCAL_VAR = 2;
    070    /** Side-effects feature ordinal. */
    071    public static final int SIDE_EFFECT = 3;
    072    /** Global side-effects feature ordinal. */
    073    public static final int SIDE_EFFECT_GLOBAL = 4;
    074    /** Array get is allowed on expr. */
    075    public static final int ARRAY_REF_EXPR = 5;
    076    /** New-instance feature ordinal. */
    077    public static final int NEW_INSTANCE = 6;
    078    /** Loops feature ordinal. */
    079    public static final int LOOP = 7;
    080    /** Lambda feature ordinal. */
    081    public static final int LAMBDA = 8;
    082    /** Lambda feature ordinal. */
    083    public static final int METHOD_CALL = 9;
    084    /** Structured literal feature ordinal. */
    085    public static final int STRUCTURED_LITERAL = 10;
    086    /** Pragma feature ordinal. */
    087    public static final int PRAGMA = 11;
    088    /** Annotation feature ordinal. */
    089    public static final int ANNOTATION = 12;
    090    /** Script feature ordinal. */
    091    public static final int SCRIPT = 13;
    092    /** Lexical feature ordinal. */
    093    public static final int LEXICAL = 14;
    094    /** Lexical shade feature ordinal. */
    095    public static final int LEXICAL_SHADE = 15;
    096
    097    /**
    098     * Creates an all-features-enabled instance.
    099     */
    100    public JexlFeatures() {
    101        flags = (1L << LOCAL_VAR)
    102                | (1L << SIDE_EFFECT)
    103                | (1L << SIDE_EFFECT_GLOBAL)
    104                | (1L << ARRAY_REF_EXPR)
    105                | (1L << NEW_INSTANCE)
    106                | (1L << LOOP)
    107                | (1L << LAMBDA)
    108                | (1L << METHOD_CALL)
    109                | (1L << STRUCTURED_LITERAL)
    110                | (1L << PRAGMA)
    111                | (1L << ANNOTATION)
    112                | (1L << SCRIPT);
    113        reservedNames = Collections.emptySet();
    114        nameSpaces = TEST_STR_FALSE;
    115    }
    116
    117    /**
    118     * Copy constructor.
    119     * @param features the feature to copy from
    120     */
    121    public JexlFeatures(final JexlFeatures features) {
    122        this.flags = features.flags;
    123        this.reservedNames = features.reservedNames;
    124        this.nameSpaces = features.nameSpaces;
    125    }
    126
    127    @Override
    128    public int hashCode() { //CSOFF: MagicNumber
    129        int hash = 3;
    130        hash = 53 * hash + (int) (this.flags ^ (this.flags >>> 32));
    131        hash = 53 * hash + (this.reservedNames != null ? this.reservedNames.hashCode() : 0);
    132        return hash;
    133    }
    134
    135    @Override
    136    public boolean equals(final Object obj) {
    137        if (this == obj) {
    138            return true;
    139        }
    140        if (obj == null) {
    141            return false;
    142        }
    143        if (getClass() != obj.getClass()) {
    144            return false;
    145        }
    146        final JexlFeatures other = (JexlFeatures) obj;
    147        if (this.flags != other.flags) {
    148            return false;
    149        }
    150        if (!Objects.equals(this.reservedNames, other.reservedNames)) {
    151            return false;
    152        }
    153        return true;
    154    }
    155
    156    /**
    157     * The text corresponding to a feature code.
    158     * @param feature the feature number
    159     * @return the feature name
    160     */
    161    public static String stringify(final int feature) {
    162        return feature >= 0 && feature < F_NAMES.length ? F_NAMES[feature] : "unsupported feature";
    163    }
    164
    165    /**
    166     * Sets a collection of reserved names precluding those to be used as local variables or parameter names.
    167     * @param names the names to reserve
    168     * @return this features instance
    169     */
    170    public JexlFeatures reservedNames(final Collection<String> names) {
    171        if (names == null || names.isEmpty()) {
    172            reservedNames = Collections.emptySet();
    173        } else {
    174            reservedNames = Collections.unmodifiableSet(new TreeSet<String>(names));
    175        }
    176        setFeature(RESERVED, !reservedNames.isEmpty());
    177        return this;
    178    }
    179
    180    /**
    181     * @return the (unmodifiable) set of reserved names.
    182     */
    183    public Set<String> getReservedNames() {
    184        return reservedNames;
    185    }
    186
    187    /**
    188     * Checks whether a name is reserved.
    189     * @param name the name to check
    190     * @return true if reserved, false otherwise
    191     */
    192    public boolean isReservedName(final String name) {
    193        return name != null && reservedNames.contains(name);
    194    }
    195
    196    /**
    197     * Sets a test to determine namespace declaration.
    198     * @param names the name predicate
    199     * @return this features instance
    200     */
    201    public JexlFeatures namespaceTest(final Predicate<String> names) {
    202        nameSpaces = names == null? TEST_STR_FALSE : names;
    203        return this;
    204    }
    205
    206    /**
    207     * @return the declared namespaces test.
    208     */
    209    public Predicate<String> namespaceTest() {
    210        return nameSpaces;
    211    }
    212
    213    /**
    214     * Sets a feature flag.
    215     * @param feature the feature ordinal
    216     * @param flag    turn-on, turn off
    217     */
    218    private void setFeature(final int feature, final boolean flag) {
    219        if (flag) {
    220            flags |= (1 << feature);
    221        } else {
    222            flags &= ~(1L << feature);
    223        }
    224    }
    225
    226    /**
    227     * Gets a feature flag value.
    228     * @param feature feature ordinal
    229     * @return true if on, false if off
    230     */
    231    private boolean getFeature(final int feature) {
    232        return (flags & (1L << feature)) != 0L;
    233    }
    234
    235    /**
    236     * Sets whether register are enabled.
    237     * <p>
    238     * This is mostly used internally during execution of JexlEngine.{g,s}etProperty.
    239     * <p>
    240     * When disabled, parsing a script/expression using the register syntax will throw a parsing exception.
    241     * @param flag true to enable, false to disable
    242     * @return this features instance
    243     */
    244    public JexlFeatures register(final boolean flag) {
    245        setFeature(REGISTER, flag);
    246        return this;
    247    }
    248
    249    /**
    250     * @return true if register syntax is enabled
    251     */
    252    public boolean supportsRegister() {
    253        return getFeature(REGISTER);
    254    }
    255
    256    /**
    257     * Sets whether local variables are enabled.
    258     * <p>
    259     * When disabled, parsing a script/expression using a local variable or parameter syntax
    260     * will throw a parsing exception.
    261     * @param flag true to enable, false to disable
    262     * @return this features instance
    263     */
    264    public JexlFeatures localVar(final boolean flag) {
    265        setFeature(LOCAL_VAR, flag);
    266        return this;
    267    }
    268
    269    /**
    270     * @return true if local variables syntax is enabled
    271     */
    272    public boolean supportsLocalVar() {
    273        return getFeature(LOCAL_VAR);
    274    }
    275
    276    /**
    277     * Sets whether side effect expressions on global variables (aka non local) are enabled.
    278     * <p>
    279     * When disabled, parsing a script/expression using syntactical constructs modifying variables
    280     * <em>including all potentially ant-ish variables</em> will throw a parsing exception.
    281     * @param flag true to enable, false to disable
    282     * @return this features instance
    283     */
    284    public JexlFeatures sideEffectGlobal(final boolean flag) {
    285        setFeature(SIDE_EFFECT_GLOBAL, flag);
    286        return this;
    287    }
    288
    289    /**
    290     * @return true if global variables can be assigned
    291     */
    292    public boolean supportsSideEffectGlobal() {
    293        return getFeature(SIDE_EFFECT_GLOBAL);
    294    }
    295
    296    /**
    297     * Sets whether side effect expressions are enabled.
    298     * <p>
    299     * When disabled, parsing a script/expression using syntactical constructs modifying variables
    300     * or members will throw a parsing exception.
    301     * @param flag true to enable, false to disable
    302     * @return this features instance
    303     */
    304    public JexlFeatures sideEffect(final boolean flag) {
    305        setFeature(SIDE_EFFECT, flag);
    306        return this;
    307    }
    308
    309    /**
    310     * @return true if side effects are enabled, false otherwise
    311     */
    312    public boolean supportsSideEffect() {
    313        return getFeature(SIDE_EFFECT);
    314    }
    315
    316    /**
    317     * Sets whether array references expressions are enabled.
    318     * <p>
    319     * When disabled, parsing a script/expression using 'obj[ ref ]' where ref is not a string or integer literal
    320     * will throw a parsing exception;
    321     * @param flag true to enable, false to disable
    322     * @return this features instance
    323     */
    324    public JexlFeatures arrayReferenceExpr(final boolean flag) {
    325        setFeature(ARRAY_REF_EXPR, flag);
    326        return this;
    327    }
    328
    329    /**
    330     * @return true if array references can contain method call expressions, false otherwise
    331     */
    332    public boolean supportsArrayReferenceExpr() {
    333        return getFeature(ARRAY_REF_EXPR);
    334    }
    335
    336    /**
    337     * Sets whether method calls expressions are enabled.
    338     * <p>
    339     * When disabled, parsing a script/expression using 'obj.method()'
    340     * will throw a parsing exception;
    341     * @param flag true to enable, false to disable
    342     * @return this features instance
    343     */
    344    public JexlFeatures methodCall(final boolean flag) {
    345        setFeature(METHOD_CALL, flag);
    346        return this;
    347    }
    348
    349    /**
    350     * @return true if array references can contain expressions, false otherwise
    351     */
    352    public boolean supportsMethodCall() {
    353        return getFeature(METHOD_CALL);
    354    }
    355
    356    /**
    357     * Sets whether array/map/set literal expressions are enabled.
    358     * <p>
    359     * When disabled, parsing a script/expression creating one of these literals
    360     * will throw a parsing exception;
    361     * @param flag true to enable, false to disable
    362     * @return this features instance
    363     */
    364    public JexlFeatures structuredLiteral(final boolean flag) {
    365        setFeature(STRUCTURED_LITERAL, flag);
    366        return this;
    367    }
    368
    369    /**
    370     * @return true if array/map/set literal expressions are supported, false otherwise
    371     */
    372    public boolean supportsStructuredLiteral() {
    373        return getFeature(STRUCTURED_LITERAL);
    374    }
    375
    376    /**
    377     * Sets whether creating new instances is enabled.
    378     * <p>
    379     * When disabled, parsing a script/expression using 'new(...)' will throw a parsing exception;
    380     * using a class as functor will fail at runtime.
    381     * @param flag true to enable, false to disable
    382     * @return this features instance
    383     */
    384    public JexlFeatures newInstance(final boolean flag) {
    385        setFeature(NEW_INSTANCE, flag);
    386        return this;
    387    }
    388
    389    /**
    390     * @return true if creating new instances is enabled, false otherwise
    391     */
    392    public boolean supportsNewInstance() {
    393        return getFeature(NEW_INSTANCE);
    394    }
    395
    396    /**
    397     * Sets whether looping constructs are enabled.
    398     * <p>
    399     * When disabled, parsing a script/expression using syntactic looping constructs (for,while)
    400     * will throw a parsing exception.
    401     * @param flag true to enable, false to disable
    402     * @return this features instance
    403     */
    404    public JexlFeatures loops(final boolean flag) {
    405        setFeature(LOOP, flag);
    406        return this;
    407    }
    408
    409    /**
    410     * @return true if loops are enabled, false otherwise
    411     */
    412    public boolean supportsLoops() {
    413        return getFeature(LOOP);
    414    }
    415
    416    /**
    417     * Sets whether lambda/function constructs are enabled.
    418     * <p>
    419     * When disabled, parsing a script/expression using syntactic lambda constructs (-&gt;,function)
    420     * will throw a parsing exception.
    421     * @param flag true to enable, false to disable
    422     * @return this features instance
    423     */
    424    public JexlFeatures lambda(final boolean flag) {
    425        setFeature(LAMBDA, flag);
    426        return this;
    427    }
    428
    429    /**
    430     * @return true if lambda are enabled, false otherwise
    431     */
    432    public boolean supportsLambda() {
    433        return getFeature(LAMBDA);
    434    }
    435
    436    /**
    437     * Sets whether pragma constructs are enabled.
    438     * <p>
    439     * When disabled, parsing a script/expression using syntactic pragma constructs (#pragma)
    440     * will throw a parsing exception.
    441     * @param flag true to enable, false to disable
    442     * @return this features instance
    443     */
    444    public JexlFeatures pragma(final boolean flag) {
    445        setFeature(PRAGMA, flag);
    446        return this;
    447    }
    448
    449    /**
    450     * @return true if pragma are enabled, false otherwise
    451     */
    452    public boolean supportsPragma() {
    453        return getFeature(PRAGMA);
    454    }
    455
    456    /**
    457     * Sets whether annotation constructs are enabled.
    458     * <p>
    459     * When disabled, parsing a script/expression using syntactic annotation constructs (@annotation)
    460     * will throw a parsing exception.
    461     * @param flag true to enable, false to disable
    462     * @return this features instance
    463     */
    464    public JexlFeatures annotation(final boolean flag) {
    465        setFeature(ANNOTATION, flag);
    466        return this;
    467    }
    468
    469    /**
    470     * @return true if annotation are enabled, false otherwise
    471     */
    472    public boolean supportsAnnotation() {
    473        return getFeature(ANNOTATION);
    474    }
    475
    476    /**
    477     * Sets whether scripts constructs are enabled.
    478     * <p>
    479     * When disabled, parsing a script using syntactic script constructs (statements, ...)
    480     * will throw a parsing exception.
    481     * @param flag true to enable, false to disable
    482     * @return this features instance
    483     */
    484    public JexlFeatures script(final boolean flag) {
    485        setFeature(SCRIPT, flag);
    486        return this;
    487    }
    488
    489    /**
    490     * @return true if scripts are enabled, false otherwise
    491     */
    492    public boolean supportsScript() {
    493        return getFeature(SCRIPT);
    494    }
    495
    496    /**
    497     *
    498     * @return true if expressions (aka not scripts) are enabled, false otherwise
    499     */
    500    public boolean supportsExpression() {
    501        return !getFeature(SCRIPT);
    502    }
    503
    504    /**
    505     * Sets whether syntactic lexical mode is enabled.
    506     *
    507     * @param flag true means syntactic lexical function scope is in effect, false implies non-lexical scoping
    508     * @return this features instance
    509     */
    510    public JexlFeatures lexical(final boolean flag) {
    511        setFeature(LEXICAL, flag);
    512        return this;
    513    }
    514
    515
    516    /** @return whether lexical scope feature is enabled */
    517    public boolean isLexical() {
    518        return getFeature(LEXICAL);
    519    }
    520
    521    /**
    522     * Sets whether syntactic lexical shade is enabled.
    523     *
    524     * @param flag true means syntactic lexical shade is in effect and implies lexical scope
    525     * @return this features instance
    526     */
    527    public JexlFeatures lexicalShade(final boolean flag) {
    528        setFeature(LEXICAL_SHADE, flag);
    529        if (flag) {
    530            setFeature(LEXICAL, true);
    531        }
    532        return this;
    533    }
    534
    535
    536    /** @return whether lexical shade feature is enabled */
    537    public boolean isLexicalShade() {
    538        return getFeature(LEXICAL_SHADE);
    539    }
    540}