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}