View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      https://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.jexl3;
18  
19  import java.util.Arrays;
20  import java.util.Collection;
21  import java.util.Collections;
22  import java.util.HashSet;
23  import java.util.Objects;
24  import java.util.Set;
25  import java.util.TreeSet;
26  import java.util.function.Predicate;
27  
28  /**
29   * A set of language feature options.
30   * <p>
31   * These control <em>syntactical</em> constructs that will throw JexlException.Feature exceptions (a
32   * subclass of JexlException.Parsing) when disabled.
33   * </p>
34   * <p>It is recommended to be explicit in choosing the features you need rather than rely on the default
35   * constructor: the 2 convenience methods {@link JexlFeatures#createNone()} and {@link JexlFeatures#createAll()}
36   * are the recommended starting points to selectively enable or disable chosen features.</p>
37   * <ul>
38   * <li>Registers: register syntax (#number), used internally for {g,s}etProperty</li>
39   * <li>Reserved Names: a set of reserved variable names that cannot be used as local variable (or parameter) names</li>
40   * <li>Global Side Effect : assigning/modifying values on global variables (=, += , -=, ...)</li>
41   * <li>Lexical: lexical scope, prevents redefining local variables</li>
42   * <li>Lexical Shade: local variables shade globals, prevents confusing a global variable with a local one</li>
43   * <li>Side Effect : assigning/modifying values on any variables or left-value</li>
44   * <li>Constant Array Reference: ensures array references only use constants;they should be statically solvable.</li>
45   * <li>New Instance: creating an instance using new(...)</li>
46   * <li>Loops: loop constructs (while(true), for(...))</li>
47   * <li>Lambda: function definitions (()-&gt;{...}, function(...) ).</li>
48   * <li>Method calls: calling methods (obj.method(...) or obj['method'](...)); when disabled, leaves function calls
49   * - including namespace prefixes - available</li>
50   * <li>Structured literals: arrays, lists, maps, sets, ranges</li>
51   * <li>Pragma: pragma construct as in {@code #pragma x y}</li>
52   * <li>Annotation: @annotation statement;</li>
53   * <li>Thin-arrow: use the thin-arrow, ie {@code ->} for lambdas as in {@code x -> x + x}</li>
54   * <li>Fat-arrow: use the  fat-arrow, ie {@code =>} for lambdas as in {@code x => x + x}
55   * <li>Namespace pragma: whether the {@code #pragma jexl.namespace.ns namespace} syntax is allowed</li>
56   * <li>Namespace identifier: whether the {@code ns:fun(...)} parser treats the ns:fun as one identifier, no spaces allowed</li>
57   * <li>Import pragma: whether the {@code #pragma jexl.import fully.qualified.class.name} syntax is allowed</li>
58   * <li>Comparator names: whether the comparator operator names can be used (as in {@code gt} for &gt;,
59   * {@code lt} for &lt;, ...)</li>
60   * <li>Pragma anywhere: whether pragma, that are <em>not</em> statements and handled before execution begins,
61   * can appear anywhere in the source or before any statements - ie at the beginning of a script.</li>
62   * <li>Const Capture: whether variables captured by lambdas are read-only (aka const, same as Java) or read-write.</li>
63   * <li>Reference Capture: whether variables captured by lambdas are pass-by-reference or pass-by-value.</li>
64   * </ul>
65   *
66   * @since 3.2
67   */
68  public final class JexlFeatures {
69  
70      /** The false predicate. */
71      public static final Predicate<String> TEST_STR_FALSE = s -> false;
72  
73      /**
74       * The true predicate.
75       *
76       * @since 3.7.1
77       */
78      public static final Predicate<String> TEST_STR_TRUE = s -> true;
79  
80      /** The feature names (for toString()). */
81      private static final String[] F_NAMES = {
82          "register", "reserved variable", "local variable", "assign/modify",
83          "global assign/modify", "array reference", "create instance", "loop", "function",
84          "method call", "set/map/array literal", "pragma", "annotation", "script", "lexical", "lexicalShade",
85          "thin-arrow", "fat-arrow", "namespace pragma", "namespace identifier", "import pragma", "comparator names", "pragma anywhere",
86          "const capture", "ref capture", "ambiguous statement", "ignore template prefix", "namespace instantiation"
87      };
88  
89      /** Registers feature ordinal. */
90      private static final int REGISTER = 0;
91  
92      /** Reserved future feature ordinal (unused as of 3.3.1). */
93      public static final int RESERVED = 1;
94  
95      /** Locals feature ordinal. */
96      public static final int LOCAL_VAR = 2;
97  
98      /** Side effects feature ordinal. */
99      public static final int SIDE_EFFECT = 3;
100 
101     /** Global side effects feature ordinal. */
102     public static final int SIDE_EFFECT_GLOBAL = 4;
103 
104     /** Expressions allowed in array reference ordinal. */
105     public static final int ARRAY_REF_EXPR = 5;
106 
107     /** New-instance feature ordinal. */
108     public static final int NEW_INSTANCE = 6;
109 
110     /** Loops feature ordinal. */
111     public static final int LOOP = 7;
112 
113     /** Lambda feature ordinal. */
114     public static final int LAMBDA = 8;
115 
116     /** Lambda feature ordinal. */
117     public static final int METHOD_CALL = 9;
118 
119     /** Structured literal feature ordinal. */
120     public static final int STRUCTURED_LITERAL = 10;
121 
122     /** Pragma feature ordinal. */
123     public static final int PRAGMA = 11;
124 
125     /** Annotation feature ordinal. */
126     public static final int ANNOTATION = 12;
127 
128     /** Script feature ordinal. */
129     public static final int SCRIPT = 13;
130 
131     /** Lexical feature ordinal. */
132     public static final int LEXICAL = 14;
133 
134     /** Lexical shade feature ordinal. */
135     public static final int LEXICAL_SHADE = 15;
136 
137     /** Thin-arrow lambda syntax. */
138     public static final int THIN_ARROW = 16;
139 
140     /** Fat-arrow lambda syntax. */
141     public static final int FAT_ARROW = 17;
142 
143     /** Namespace pragma feature ordinal. */
144     public static final int NS_PRAGMA = 18;
145 
146     /** Namespace syntax as an identifier (no space). */
147     public static final int NS_IDENTIFIER = 19;
148 
149     /** Import pragma feature ordinal. */
150     public static final int IMPORT_PRAGMA = 20;
151 
152     /** Comparator names (legacy) syntax. */
153     public static final int COMPARATOR_NAMES = 21;
154 
155     /** The pragma anywhere feature ordinal. */
156     public static final int PRAGMA_ANYWHERE = 22;
157 
158     /** Captured variables are const. */
159     public static final int CONST_CAPTURE = 23;
160 
161     /** Captured variables are reference. */
162     public static final int REF_CAPTURE = 24;
163 
164     /** Ambiguous or strict statement allowed. */
165     public static final int AMBIGUOUS_STATEMENT = 25;
166 
167     /** Ignore template prefix. */
168     public static final int IGNORE_TEMPLATE_PREFIX = 26;
169 
170     /** Bad naming, use AMBIGUOUS_STATEMENT.
171      * @deprecated 3.6
172      */
173     @Deprecated
174     public static final int STRICT_STATEMENT = 25;
175 
176     /** Allow reflective auto-instantiation of a namespace functor from a class/FQCN binding. */
177     public static final int NAMESPACE_INSTANTIATE = 27;
178 
179     /**
180      * All features.
181      * Ensure this is updated if additional features are added.
182      */
183     private static final long ALL_FEATURES = (1L << NAMESPACE_INSTANTIATE + 1) - 1L; // MUST REMAIN PRIVATE
184 
185     /**
186      * The default features flag mask.
187      * <p>Meant for compatibility with scripts written before 3.3.1</p>
188      */
189     private static final long DEFAULT_FEATURES = // MUST REMAIN PRIVATE
190         1L << LOCAL_VAR
191         | 1L << SIDE_EFFECT
192         | 1L << SIDE_EFFECT_GLOBAL
193         | 1L << ARRAY_REF_EXPR
194         | 1L << NEW_INSTANCE
195         | 1L << LOOP
196         | 1L << LAMBDA
197         | 1L << METHOD_CALL
198         | 1L << STRUCTURED_LITERAL
199         | 1L << PRAGMA
200         | 1L << ANNOTATION
201         | 1L << SCRIPT
202         | 1L << THIN_ARROW
203         | 1L << NS_PRAGMA
204         | 1L << IMPORT_PRAGMA
205         | 1L << COMPARATOR_NAMES
206         | 1L << PRAGMA_ANYWHERE
207         | 1L << NAMESPACE_INSTANTIATE;
208 
209     /**
210      * The canonical scripting (since 3.3.1) features flag mask based on the original default.
211      * <p>Adds lexical, lexical-shade and const-capture but removes comparator-names and pragma-anywhere</p>
212      */
213     private static final long SCRIPT_FEATURES = // MUST REMAIN PRIVATE
214         (DEFAULT_FEATURES
215         | 1L << LEXICAL
216         | 1L << LEXICAL_SHADE
217         | 1L << CONST_CAPTURE) // these parentheses are necessary :-)
218         & ~(1L << COMPARATOR_NAMES)
219         & ~(1L << PRAGMA_ANYWHERE);
220 
221     /**
222      * Protected future syntactic elements.
223      * <p><em>class, jexl, $jexl</em></p>
224      *
225      * @since 3.3.1
226      */
227     private static final Set<String> RESERVED_WORDS =
228         Collections.unmodifiableSet(new HashSet<>(Arrays.asList("class", "jexl", "$jexl")));
229 
230     /*
231      * *WARNING*
232      * Static fields may be inlined by the Java compiler, so their _values_ effectively form part of the external API.
233      * Classes that reference them need to be recompiled to pick up new values.
234      * This means that changes in value are not binary compatible.
235      * Such fields must be private or problems may occur.
236      */
237 
238      /**
239      * Creates an all features enabled set.
240      *
241      * @return A new instance of all features set
242      * @since 3.3.1
243      */
244     public static JexlFeatures createAll() {
245         return new JexlFeatures(ALL_FEATURES, null, null);
246     }
247 
248     /**
249      * Creates a default features set suitable for basic but complete scripting needs.
250      * <p>Maximizes compatibility with older version scripts (before 3.3), new projects should
251      * use {@link JexlFeatures#createScript()} or equivalent features as a base.</p>
252      * <p>The following scripting features are enabled:</p>
253      * <ul>
254      *   <li>local variable, {@link JexlFeatures#supportsLocalVar()}</li>
255      *   <li>side effect, {@link JexlFeatures#supportsSideEffect()}</li>
256      *   <li>global side effect, {@link JexlFeatures#supportsSideEffectGlobal()}</li>
257      *   <li>array reference expression, {@link JexlFeatures#supportsStructuredLiteral()}</li>
258      *   <li>new instance, {@link JexlFeatures#supportsNewInstance()} </li>
259      *   <li>loop, {@link JexlFeatures#supportsLoops()}</li>
260      *   <li>lambda, {@link JexlFeatures#supportsLambda()}</li>
261      *   <li>method call, {@link JexlFeatures#supportsMethodCall()}</li>
262      *   <li>structured literal, {@link JexlFeatures#supportsStructuredLiteral()}</li>
263      *   <li>pragma, {@link JexlFeatures#supportsPragma()}</li>
264      *   <li>annotation, {@link JexlFeatures#supportsAnnotation()}</li>
265      *   <li>script, {@link JexlFeatures#supportsScript()}</li>
266      *   <li>comparator names,  {@link JexlFeatures#supportsComparatorNames()}</li>
267      *   <li>namespace pragma,  {@link JexlFeatures#supportsNamespacePragma()}</li>
268      *   <li>import pragma, {@link JexlFeatures#supportsImportPragma()}</li>
269      *   <li>pragma anywhere, {@link JexlFeatures#supportsPragmaAnywhere()}</li>
270      * </ul>
271      *
272      * @return A new instance of a default scripting features set
273      * @since 3.3.1
274      */
275     public static JexlFeatures createDefault() {
276         return new JexlFeatures(DEFAULT_FEATURES, null, null);
277     }
278 
279     /**
280      * Creates an empty feature set.
281      * <p>This is the strictest base-set since no feature is allowed, suitable as-is only
282      * for the simplest expressions.</p>
283      *
284      * @return A new instance of an empty features set
285      * @since 3.3.1
286      */
287     public static JexlFeatures createNone() {
288         return new JexlFeatures(0L, null, null);
289     }
290 
291     /**
292      * The modern scripting features set.
293      * <p>This is the recommended set for new projects.</p>
294      * <p>All default features with the following differences:</p>
295      * <ul>
296      * <li><em>disable</em> pragma-anywhere, {@link JexlFeatures#supportsPragmaAnywhere()}</li>
297      * <li><em>disable</em> comparator-names, {@link JexlFeatures#supportsComparatorNames()}</li>
298      * <li><em>enable</em> lexical, {@link JexlFeatures#isLexical()}</li>
299      * <li><em>enable</em> lexical-shade, {@link JexlFeatures#isLexicalShade()} </li>
300      * <li><em>enable</em> const-capture, {@link JexlFeatures#supportsConstCapture()}</li>
301      * </ul>
302      * <p>It also adds a set of reserved words to enable future unencumbered syntax evolution:
303      * <em>try, catch, throw, finally, switch, case, default, class, instanceof</em>
304      * </p>
305      *
306      * @return A new instance of a modern scripting features set
307      * @since 3.3.1
308      */
309     public static JexlFeatures createScript() {
310         return new JexlFeatures(SCRIPT_FEATURES, RESERVED_WORDS, null);
311     }
312 
313     /**
314      * The text corresponding to a feature code.
315      *
316      * @param feature The feature number
317      * @return The feature name
318      */
319     public static String stringify(final int feature) {
320         return feature >= 0 && feature < F_NAMES.length ? F_NAMES[feature] : "unsupported feature";
321     }
322 
323     /** The feature flags. */
324     private long flags;
325 
326     /** The set of reserved names, aka global variables that cannot be masked by local variables or parameters. */
327     private Set<String> reservedNames;
328 
329     /** The namespace names. */
330     private Predicate<String> nameSpaces;
331 
332     /** The allowed pragma keys. */
333     private Predicate<String> pragmas = TEST_STR_TRUE;
334 
335     /**
336      * Creates default instance, equivalent to the result of calling the preferred alternative
337      * {@link JexlFeatures#createDefault()}
338      */
339     public JexlFeatures() {
340         this(DEFAULT_FEATURES, null, null);
341     }
342 
343     /**
344      * Copy constructor.
345      *
346      * @param features The feature to copy from
347      */
348     public JexlFeatures(final JexlFeatures features) {
349         this(features.flags, features.reservedNames, features.nameSpaces);
350         this.pragmas = features.pragmas;
351     }
352 
353     /**
354      * An all member constructor for derivation.
355      * <p>Not respecting immutability or thread-safety constraints for this class constructor arguments will
356      * likely result in unexpected behavior.</p>
357      *
358      * @param f flag
359      * @param r reserved variable names; must be an immutable Set or thread-safe (concurrent or synchronized set)
360      * @param n namespace predicate; must be stateless or thread-safe
361      */
362     protected JexlFeatures(final long f, final Set<String> r, final Predicate<String> n) {
363         this.flags = f;
364         this.reservedNames = r == null? Collections.emptySet() : r;
365         this.nameSpaces = n == null? TEST_STR_FALSE : n;
366     }
367 
368     /**
369      * Sets whether annotation constructs are enabled.
370      * <p>
371      * When disabled, parsing a script/expression using syntactic annotation constructs (@annotation)
372      * will throw a parsing exception.
373      * </p>
374      *
375      * @param flag true to enable, false to disable
376      * @return this features instance
377      */
378     public JexlFeatures annotation(final boolean flag) {
379         setFeature(ANNOTATION, flag);
380         return this;
381     }
382 
383     /**
384      * Sets whether array references expressions are enabled.
385      * <p>
386      * When disabled, parsing a script/expression using 'obj[ ref ]' where ref is not a string or integer literal
387      * will throw a parsing exception;
388      * </p>
389      *
390      * @param flag true to enable, false to disable
391      * @return this features instance
392      */
393     public JexlFeatures arrayReferenceExpr(final boolean flag) {
394         setFeature(ARRAY_REF_EXPR, flag);
395         return this;
396     }
397 
398     /**
399      * Sets whether the legacy comparison operator names syntax is enabled.
400      * <p>
401      * When disabled, comparison operators names (eq;ne;le;lt;ge;gt)
402      * will be treated as plain identifiers.
403      * </p>
404      *
405      * @param flag true to enable, false to disable
406      * @return this features instance
407      * @since 3.3
408      */
409     public JexlFeatures comparatorNames(final boolean flag) {
410         setFeature(COMPARATOR_NAMES, flag);
411         return this;
412     }
413 
414     /**
415      * Sets whether lambda captured-variables are constant or mutable.
416      * <p>
417      * When disabled, lambda-captured variables are implicitly converted to read-write local variable (let),
418      * when enabled, those are implicitly converted to read-only local variables (const).
419      * </p>
420      *
421      * @param flag true to enable, false to disable
422      * @return this features instance
423      */
424     public JexlFeatures constCapture(final boolean flag) {
425         setFeature(CONST_CAPTURE, flag);
426         return this;
427     }
428 
429     /**
430      * Sets whether lambda captured-variables are references or values.
431      * <p>When variables are pass-by-reference, side effects are visible from inner lexical scopes
432      * to outer-scope.</p>
433      * <p>
434      * When disabled, lambda-captured variables use pass-by-value semantic,
435      * when enabled, those use pass-by-reference semantic.
436      * </p>
437      *
438      * @param flag true to enable, false to disable
439      * @return this features instance
440      */
441     public JexlFeatures referenceCapture(final boolean flag) {
442         setFeature(REF_CAPTURE, flag);
443         return this;
444     }
445 
446     /**
447      * Sets whether a namespace bound to a class or fully-qualified class name may be
448      * reflectively auto-instantiated into a functor.
449      * <p>
450      * When enabled (the default), a namespace declared as a {@link Class} or class-name
451      * {@link String} is instantiated - through a constructor, optionally receiving the
452      * context - the first time it is used, allowing instance (non-static) namespace methods.
453      * When disabled, no constructor is invoked: only static methods of the bound class are
454      * reachable and explicit host-supplied functors are still honored.
455      * </p>
456      *
457      * @param flag true to enable, false to disable
458      * @return this features instance
459      * @since 3.7.1
460      */
461     public JexlFeatures namespaceInstantiation(final boolean flag) {
462         setFeature(NAMESPACE_INSTANTIATE, flag);
463         return this;
464     }
465 
466     @Override
467     public boolean equals(final Object obj) {
468         if (this == obj) {
469             return true;
470         }
471         if (obj == null) {
472             return false;
473         }
474         if (getClass() != obj.getClass()) {
475             return false;
476         }
477         final JexlFeatures other = (JexlFeatures) obj;
478         if (this.flags != other.flags) {
479             return false;
480         }
481         if (this.nameSpaces != other.nameSpaces) {
482             return false;
483         }
484         if (!this.pragmas.equals(other.pragmas)) {
485             return false;
486         }
487         if (!Objects.equals(this.reservedNames, other.reservedNames)) {
488             return false;
489         }
490         return true;
491     }
492 
493     /**
494      * Sets whether fat-arrow lambda syntax is enabled.
495      * <p>
496      * When disabled, parsing a script/expression using syntactic fat-arrow (=&lt;)
497      * will throw a parsing exception.
498      * </p>
499      *
500      * @param flag true to enable, false to disable
501      * @return this features instance
502      * @since 3.3
503      */
504     public JexlFeatures fatArrow(final boolean flag) {
505         setFeature(FAT_ARROW, flag);
506         return this;
507     }
508 
509     /**
510      * Gets a feature flag value.
511      *
512      * @param feature feature ordinal
513      * @return true if on, false if off
514      */
515     private boolean getFeature(final int feature) {
516         return (flags & 1L << feature) != 0L;
517     }
518 
519     /**
520      * Gets the feature flags
521      *
522      * @return these features&quot;s flags
523      */
524     public long getFlags() {
525         return flags;
526     }
527 
528     /**
529      * Gets the immutable set of reserved names.
530      *
531      * @return The (unmodifiable) set of reserved names.
532      */
533     public Set<String> getReservedNames() {
534         return reservedNames;
535     }
536 
537     @Override
538     public int hashCode() { //CSOFF: MagicNumber
539         int hash = 3;
540         hash = 53 * hash + Long.hashCode(this.flags);
541         hash = 53 * hash + (this.reservedNames != null ? this.reservedNames.hashCode() : 0);
542         hash = 53 * hash + this.pragmas.hashCode();
543         return hash;
544     }
545 
546     /**
547      * Sets whether to ignore template prefixes in template expressions.
548      * <p>Note that this only precludes using a variable whose name is exactly the template prefix
549      * (for example, with the default prefix "$$", a variable named "$$" is ignored, but "$", "$a", or "$$$"
550      * are still valid variable names).</p>
551      * @param flag true to enable, false to disable
552      * @return this features instance
553      * @since 3.6.3
554      */
555     public JexlFeatures ignoreTemplatePrefix(final boolean flag) {
556         setFeature(IGNORE_TEMPLATE_PREFIX, flag);
557         return this;
558     }
559 
560     /**
561      * Gets whether to ignore template prefixes in template expressions.
562      * @return true if enabled, false otherwise
563      * @since 3.6.3
564      */
565     public boolean isIgnoreTemplatePrefix() {
566         return getFeature(IGNORE_TEMPLATE_PREFIX);
567     }
568 
569     /**
570      * Sets whether import pragma constructs are enabled.
571      * <p>
572      * When disabled, parsing a script/expression using syntactic import pragma constructs
573      * (#pragma jexl.import....) will throw a parsing exception.
574      * </p>
575      *
576      * @param flag true to enable, false to disable
577      * @return this features instance
578      * @since 3.3
579      */
580     public JexlFeatures importPragma(final boolean flag) {
581         setFeature(IMPORT_PRAGMA, flag);
582         return this;
583     }
584 
585     /**
586      * Is the lexical scope feature enabled?
587      *
588      * @return whether lexical scope feature is enabled */
589     public boolean isLexical() {
590         return getFeature(LEXICAL);
591     }
592 
593     /**
594      * Is the lexical shade feature enabled?
595      *
596      * @return whether lexical shade feature is enabled */
597     public boolean isLexicalShade() {
598         return getFeature(LEXICAL_SHADE);
599     }
600 
601     /**
602      * Checks whether a name is reserved.
603      *
604      * @param name The name to check
605      * @return true if reserved, false otherwise
606      */
607     public boolean isReservedName(final String name) {
608         return name != null && reservedNames.contains(name);
609     }
610 
611     /**
612      * Sets whether lambda/function constructs are enabled.
613      * <p>
614      * When disabled, parsing a script/expression using syntactic lambda constructs (-&gt;,function)
615      * will throw a parsing exception.
616      * </p>
617      *
618      * @param flag true to enable, false to disable
619      * @return this features instance
620      */
621     public JexlFeatures lambda(final boolean flag) {
622         setFeature(LAMBDA, flag);
623         return this;
624     }
625 
626     /**
627      * Sets whether syntactic lexical mode is enabled.
628      *
629      * @param flag true means syntactic lexical function scope is in effect, false implies non-lexical scoping
630      * @return this features instance
631      */
632     public JexlFeatures lexical(final boolean flag) {
633         setFeature(LEXICAL, flag);
634         if (!flag) {
635             setFeature(LEXICAL_SHADE, false);
636         }
637         return this;
638     }
639 
640     /**
641      * Sets whether syntactic lexical shade is enabled.
642      *
643      * @param flag true means syntactic lexical shade is in effect and implies lexical scope
644      * @return this features instance
645      */
646     public JexlFeatures lexicalShade(final boolean flag) {
647         setFeature(LEXICAL_SHADE, flag);
648         if (flag) {
649             setFeature(LEXICAL, true);
650         }
651         return this;
652     }
653 
654     /**
655      * Sets whether local variables are enabled.
656      * <p>
657      * When disabled, parsing a script/expression using a local variable or parameter syntax
658      * will throw a parsing exception.
659      * </p>
660      *
661      * @param flag true to enable, false to disable
662      * @return this features instance
663      */
664     public JexlFeatures localVar(final boolean flag) {
665         setFeature(LOCAL_VAR, flag);
666         return this;
667     }
668 
669     /**
670      * Sets whether looping constructs are enabled.
671      * <p>
672      * When disabled, parsing a script/expression using syntactic looping constructs (for, while)
673      * will throw a parsing exception.
674      * </p>
675      *
676      * @param flag true to enable, false to disable
677      * @return this features instance
678      */
679     public JexlFeatures loops(final boolean flag) {
680         setFeature(LOOP, flag);
681         return this;
682     }
683 
684     /**
685      * Sets whether method calls expressions are enabled.
686      * <p>
687      * When disabled, parsing a script/expression using 'obj.method()'
688      * will throw a parsing exception;
689      * </p>
690      *
691      * @param flag true to enable, false to disable
692      * @return this features instance
693      */
694     public JexlFeatures methodCall(final boolean flag) {
695         setFeature(METHOD_CALL, flag);
696         return this;
697     }
698 
699     /**
700      * Sets whether namespace pragma constructs are enabled.
701      * <p>
702      * When disabled, parsing a script/expression using syntactic namespace pragma constructs
703      * (#pragma jexl.namespace....) will throw a parsing exception.
704      * </p>
705      *
706      * @param flag true to enable, false to disable
707      * @return this features instance
708      * @since 3.3
709      */
710     public JexlFeatures namespacePragma(final boolean flag) {
711         setFeature(NS_PRAGMA, flag);
712         return this;
713     }
714 
715     /**
716      * Sets whether namespace as identifier syntax is enabled.
717      * <p>
718      * When enabled, a namespace call must be of the form <code>ns:fun(...)</code> with no
719      * spaces between the namespace name and the function.
720      * </p>
721      *
722      * @param flag true to enable, false to disable
723      * @return this features instance
724      * @since 3.5.0
725      */
726     public JexlFeatures namespaceIdentifier(final boolean flag) {
727         setFeature(NS_IDENTIFIER, flag);
728         return this;
729     }
730 
731     /**
732      * Gets the declared namespaces test.
733      *
734      * @return The declared namespaces test.
735      */
736     public Predicate<String> namespaceTest() {
737         return nameSpaces;
738     }
739 
740     /**
741      * Sets a test to determine namespace declaration.
742      *
743      * @param names The name predicate
744      * @return this features instance
745      */
746     public JexlFeatures namespaceTest(final Predicate<String> names) {
747         nameSpaces = names == null ? TEST_STR_FALSE : names;
748         return this;
749     }
750 
751     /**
752      * Sets whether creating new instances is enabled.
753      * <p>
754      * When disabled, parsing a script/expression using 'new(...)' will throw a parsing exception;
755      * using a class as functor will fail at runtime.
756      * </p>
757      *
758      * @param flag true to enable, false to disable
759      * @return this features instance
760      */
761     public JexlFeatures newInstance(final boolean flag) {
762         setFeature(NEW_INSTANCE, flag);
763         return this;
764     }
765 
766     /**
767      * Sets whether pragma constructs are enabled.
768      * <p>
769      * When disabled, parsing a script/expression using syntactic pragma constructs (#pragma)
770      * will throw a parsing exception.
771      * </p>
772      *
773      * @param flag true to enable, false to disable
774      * @return this features instance
775      */
776     public JexlFeatures pragma(final boolean flag) {
777         setFeature(PRAGMA, flag);
778         if (!flag) {
779             setFeature(NS_PRAGMA, false);
780             setFeature(IMPORT_PRAGMA, false);
781         }
782         return this;
783     }
784 
785     /**
786      * Sets whether pragma constructs can appear anywhere in the code.
787      *
788      * @param flag true to enable, false to disable
789      * @return this features instance
790      * @since 3.3
791      */
792     public JexlFeatures pragmaAnywhere(final boolean flag) {
793         setFeature(PRAGMA_ANYWHERE, flag);
794         return this;
795     }
796 
797     /**
798      * Gets the allowed pragma keys test.
799      *
800      * @return The allowed pragma keys test, never null.
801      * @since 3.7.1
802      */
803     public Predicate<String> pragmaTest() {
804         return pragmas;
805     }
806 
807     /**
808      * Sets a test to determine which pragma keys are allowed.
809      * <p>
810      * When set, parsing a script/expression declaring a pragma (#pragma key value) whose key does not satisfy
811      * the predicate will throw a parsing exception. This applies to all pragmas, including
812      * <code>jexl.options</code>, <code>jexl.import</code>, <code>jexl.namespace.*</code> and
813      * <code>jexl.module.*</code>.
814      * </p>
815      * <p>
816      * An engine built with these features applies the test to every script, expression and template it parses.
817      * As with every other feature, scripts created with explicit (non-null) features through
818      * {@link JexlEngine#createScript(JexlFeatures, JexlInfo, String, String...)} use those features' pragma
819      * test, not the engine's.
820      * </p>
821      *
822      * @param keys The pragma key predicate; must be stateless or thread-safe; null allows all pragma keys
823      * @return this features instance
824      * @since 3.7.1
825      */
826     public JexlFeatures pragmaTest(final Predicate<String> keys) {
827         pragmas = keys == null ? TEST_STR_TRUE : keys;
828         return this;
829     }
830 
831     /**
832      * Creates a pragma key test from a collection of patterns, meant for {@link #pragmaTest(Predicate)}.
833      * <p>
834      * A pattern ending with <code>*</code> matches any key starting with what precedes it, any other
835      * pattern matches the key exactly. For instance, <code>dlm.*</code> matches <code>dlm.foo</code> and
836      * <code>dlm.foo.bar</code>; <code>*</code> matches any key. Patterns are trimmed, null or empty
837      * patterns are ignored.
838      * </p>
839      * <p>Tests created from the same patterns are equal, so are the features using them.</p>
840      *
841      * @param patterns The allowed pragma key patterns; null or empty allows no pragma key
842      * @return The pragma key test
843      * @since 3.7.1
844      */
845     public static Predicate<String> pragmaPatterns(final Collection<String> patterns) {
846         final Set<String> exact = new TreeSet<>();
847         final Set<String> prefixes = new TreeSet<>();
848         if (patterns != null) {
849             for (final String pattern : patterns) {
850                 final String trimmed = pattern == null ? "" : pattern.trim();
851                 if ("*".equals(trimmed)) {
852                     return TEST_STR_TRUE;
853                 }
854                 if (trimmed.endsWith("*")) {
855                     prefixes.add(trimmed.substring(0, trimmed.length() - 1));
856                 } else if (!trimmed.isEmpty()) {
857                     exact.add(trimmed);
858                 }
859             }
860         }
861         return exact.isEmpty() && prefixes.isEmpty() ? TEST_STR_FALSE : new PragmaPatterns(exact, prefixes);
862     }
863 
864     /**
865      * A pragma key test made of exact keys and key prefixes.
866      */
867     private static final class PragmaPatterns implements Predicate<String> {
868         /** The exactly matched keys. */
869         private final Set<String> exact;
870         /** The matched key prefixes. */
871         private final Set<String> prefixes;
872 
873         PragmaPatterns(final Set<String> exact, final Set<String> prefixes) {
874             this.exact = exact;
875             this.prefixes = prefixes;
876         }
877 
878         @Override
879         public boolean test(final String key) {
880             if (key == null) {
881                 return false;
882             }
883             if (exact.contains(key)) {
884                 return true;
885             }
886             for (final String prefix : prefixes) {
887                 if (key.startsWith(prefix)) {
888                     return true;
889                 }
890             }
891             return false;
892         }
893 
894         @Override
895         public boolean equals(final Object obj) {
896             if (this == obj) {
897                 return true;
898             }
899             if (!(obj instanceof PragmaPatterns)) {
900                 return false;
901             }
902             final PragmaPatterns other = (PragmaPatterns) obj;
903             return exact.equals(other.exact) && prefixes.equals(other.prefixes);
904         }
905 
906         @Override
907         public int hashCode() {
908             return 31 * exact.hashCode() + prefixes.hashCode(); //CSOFF: MagicNumber
909         }
910 
911         @Override
912         public String toString() {
913             final StringBuilder strb = new StringBuilder();
914             for (final String key : exact) {
915                 strb.append(strb.length() > 0 ? ", " : "").append(key);
916             }
917             for (final String prefix : prefixes) {
918                 strb.append(strb.length() > 0 ? ", " : "").append(prefix).append('*');
919             }
920             return strb.toString();
921         }
922     }
923 
924     /**
925      * Sets whether register are enabled.
926      * <p>
927      * This is mostly used internally during execution of JexlEngine.{g,s}etProperty.
928      * </p>
929      * <p>
930      * When disabled, parsing a script/expression using the register syntax will throw a parsing exception.
931      * </p>
932      *
933      * @param flag true to enable, false to disable
934      * @return this features instance
935      */
936     public JexlFeatures register(final boolean flag) {
937         setFeature(REGISTER, flag);
938         return this;
939     }
940 
941     /**
942      * Sets a collection of reserved r precluding those to be used as local variables or parameter r.
943      *
944      * @param names The r to reserve
945      * @return this features instance
946      */
947     public JexlFeatures reservedNames(final Collection<String> names) {
948         if (names == null || names.isEmpty()) {
949             reservedNames = Collections.emptySet();
950         } else {
951             reservedNames = Collections.unmodifiableSet(new TreeSet<>(names));
952         }
953         return this;
954     }
955 
956     /**
957      * Sets whether scripts constructs are enabled.
958      * <p>
959      * When disabled, parsing a script using syntactic script constructs (statements, ...)
960      * will throw a parsing exception.
961      * </p>
962      *
963      * @param flag true to enable, false to disable
964      * @return this features instance
965      */
966     public JexlFeatures script(final boolean flag) {
967         setFeature(SCRIPT, flag);
968         return this;
969     }
970 
971     /**
972      * Sets a feature flag.
973      *
974      * @param feature The feature ordinal
975      * @param flag    turn-on, turn off
976      */
977     private void setFeature(final int feature, final boolean flag) {
978         if (flag) {
979             flags |= 1L << feature;
980         } else {
981             flags &= ~(1L << feature);
982         }
983     }
984 
985     /**
986      * Sets whether statements can be ambiguous.
987      * <p>
988      * When enabled, the semicolumn is not required between expressions that otherwise are considered
989      * ambiguous. The default will report ambiguity in cases like <code>if (true) { x 5 }</code> considering this
990      * may be missing an operator or that the intent is not clear.
991      * </p>
992      *
993      * @param flag true to enable, false to disable
994      * @return this features instance
995      */
996     public JexlFeatures ambiguousStatement(final boolean flag) {
997         setFeature(AMBIGUOUS_STATEMENT, flag);
998         return this;
999     }
1000 
1001     /**
1002      * Checks whether statements can be ambiguous.
1003      * <p>
1004      * When enabled, the semicolumn is not required between expressions that otherwise are considered
1005      * ambiguous. The default will report ambiguity in cases like <code>if (true) { x 5 }</code> considering this
1006      * may be missing an operator or that the intent is not clear.
1007      * </p>
1008      *
1009      * @return true if statements can be ambiguous, false otherwise
1010      */
1011     public boolean supportsAmbiguousStatement() {
1012         return getFeature(AMBIGUOUS_STATEMENT);
1013     }
1014 
1015     /**
1016      * Sets whether side effect expressions are enabled.
1017      * <p>
1018      * When disabled, parsing a script/expression using syntactical constructs modifying variables
1019      * or members will throw a parsing exception.
1020      * </p>
1021      *
1022      * @param flag true to enable, false to disable
1023      * @return this features instance
1024      */
1025     public JexlFeatures sideEffect(final boolean flag) {
1026         setFeature(SIDE_EFFECT, flag);
1027         return this;
1028     }
1029 
1030     /**
1031      * Sets whether side effect expressions on global variables (aka non-local) are enabled.
1032      * <p>
1033      * When disabled, parsing a script/expression using syntactical constructs modifying variables
1034      * <em>including all potentially ant-ish variables</em> will throw a parsing exception.
1035      * </p>
1036      *
1037      * @param flag true to enable, false to disable
1038      * @return this features instance
1039      */
1040     public JexlFeatures sideEffectGlobal(final boolean flag) {
1041         setFeature(SIDE_EFFECT_GLOBAL, flag);
1042         return this;
1043     }
1044 
1045     /**
1046      * Sets whether array/map/set literal expressions are enabled.
1047      * <p>
1048      * When disabled, parsing a script/expression creating one of these literals
1049      * will throw a parsing exception;
1050      * </p>
1051      *
1052      * @param flag true to enable, false to disable
1053      * @return this features instance
1054      */
1055     public JexlFeatures structuredLiteral(final boolean flag) {
1056         setFeature(STRUCTURED_LITERAL, flag);
1057         return this;
1058     }
1059 
1060     /**
1061      * Does the engine support annotations?
1062      *
1063      * @return true if annotation are enabled, false otherwise
1064      */
1065     public boolean supportsAnnotation() {
1066         return getFeature(ANNOTATION);
1067     }
1068 
1069     /**
1070      * Does the engine support array references which contain method call expressions?
1071      *
1072      * @return true if array references can contain method call expressions, false otherwise
1073      */
1074     public boolean supportsArrayReferenceExpr() {
1075         return getFeature(ARRAY_REF_EXPR);
1076     }
1077 
1078     /**
1079      * Does the engine support legacy comparison operator names syntax?
1080      *
1081      * @return true if legacy comparison operator names syntax is enabled, false otherwise
1082      * @since 3.3
1083      */
1084     public boolean supportsComparatorNames() {
1085         return getFeature(COMPARATOR_NAMES);
1086     }
1087 
1088     /**
1089      * Does the engine support lambda captured-variables as const?
1090      *
1091      * @return true if lambda captured-variables are const, false otherwise
1092      */
1093     public boolean supportsConstCapture() {
1094         return getFeature(CONST_CAPTURE);
1095     }
1096 
1097     /**
1098      * Does the engine support lambda captured-variables as references?
1099      *
1100      * @return true if lambda captured-variables are references, false otherwise
1101      */
1102     public boolean supportsReferenceCapture() {
1103         return getFeature(REF_CAPTURE);
1104     }
1105 
1106     /**
1107      * Does the engine allow reflective auto-instantiation of a namespace functor
1108      * from a class or class-name binding?
1109      *
1110      * @return true if namespace auto-instantiation is allowed, false otherwise
1111      * @since 3.7.1
1112      */
1113     public boolean supportsNamespaceInstantiation() {
1114         return getFeature(NAMESPACE_INSTANTIATE);
1115     }
1116 
1117     /**
1118      * Does the engine support expressions (aka not scripts)
1119      *
1120      * @return true if expressions (aka not scripts) are enabled, false otherwise
1121      */
1122     public boolean supportsExpression() {
1123         return !getFeature(SCRIPT);
1124     }
1125 
1126     /**
1127      * Does the engine support fat-arrow lambda syntax?
1128      *
1129      * @return true if fat-arrow lambda syntax is enabled, false otherwise
1130      * @since 3.3
1131      */
1132     public boolean supportsFatArrow() {
1133         return getFeature(FAT_ARROW);
1134     }
1135 
1136     /**
1137      * Does the engine support import pragma?
1138      *
1139      * @return true if import pragma are enabled, false otherwise
1140      * @since 3.3
1141      */
1142     public boolean supportsImportPragma() {
1143         return getFeature(IMPORT_PRAGMA);
1144     }
1145 
1146     /**
1147      * Does the engine support lambdas?
1148      *
1149      * @return true if lambda are enabled, false otherwise
1150      */
1151     public boolean supportsLambda() {
1152         return getFeature(LAMBDA);
1153     }
1154 
1155     /**
1156      * Is local variables syntax enabled?
1157      *
1158      * @return true if local variables syntax is enabled
1159      */
1160     public boolean supportsLocalVar() {
1161         return getFeature(LOCAL_VAR);
1162     }
1163 
1164     /**
1165      * Are loops enabled?
1166      *
1167      * @return true if loops are enabled, false otherwise
1168      */
1169     public boolean supportsLoops() {
1170         return getFeature(LOOP);
1171     }
1172 
1173     /**
1174      * Can array references contain expressions?
1175      *
1176      * @return true if array references can contain expressions, false otherwise
1177      */
1178     public boolean supportsMethodCall() {
1179         return getFeature(METHOD_CALL);
1180     }
1181 
1182     /**
1183      * Is namespace pragma enabled?
1184      *
1185      * @return true if namespace pragma are enabled, false otherwise
1186      * @since 3.3
1187      */
1188     public boolean supportsNamespacePragma() {
1189         return getFeature(NS_PRAGMA);
1190     }
1191 
1192     /**
1193      * Is namespace identifier syntax enabled?
1194      *
1195      * @return true if namespace identifier syntax is enabled, false otherwise
1196      * @since 3.5.0
1197      */
1198     public boolean supportsNamespaceIdentifier() {
1199         return getFeature(NS_IDENTIFIER);
1200     }
1201 
1202     /**
1203      * Is creating new instances enabled?
1204      *
1205      * @return true if creating new instances is enabled, false otherwise
1206      */
1207     public boolean supportsNewInstance() {
1208         return getFeature(NEW_INSTANCE);
1209     }
1210 
1211     /**
1212      * Is the namespace pragma enabled?
1213      *
1214      * @return true if namespace pragma are enabled, false otherwise
1215      */
1216     public boolean supportsPragma() {
1217         return getFeature(PRAGMA);
1218     }
1219 
1220     /**
1221      * Can pragma constructs appear anywhere in the code?
1222      *
1223      * @return true if pragma constructs can appear anywhere in the code, false otherwise
1224      * @since 3.3
1225      */
1226     public boolean supportsPragmaAnywhere() {
1227         return getFeature(PRAGMA_ANYWHERE);
1228     }
1229 
1230     /**
1231      * Is register syntax enabled?
1232      *
1233      * @return true if register syntax is enabled
1234      */
1235     public boolean supportsRegister() {
1236         return getFeature(REGISTER);
1237     }
1238 
1239     /**
1240      * Are scripts enabled?
1241      *
1242      * @return true if scripts are enabled, false otherwise
1243      */
1244     public boolean supportsScript() {
1245         return getFeature(SCRIPT);
1246     }
1247 
1248     /**
1249      * Are side effects enabled?
1250      *
1251      * @return true if side effects are enabled, false otherwise
1252      */
1253     public boolean supportsSideEffect() {
1254         return getFeature(SIDE_EFFECT);
1255     }
1256 
1257     /**
1258      * Can global variables be assigned?
1259      *
1260      * @return true if global variables can be assigned
1261      */
1262     public boolean supportsSideEffectGlobal() {
1263         return getFeature(SIDE_EFFECT_GLOBAL);
1264     }
1265 
1266     /**
1267      * Are array/map/set literal expressions supported?
1268      *
1269      * @return true if array/map/set literal expressions are supported, false otherwise
1270      */
1271     public boolean supportsStructuredLiteral() {
1272         return getFeature(STRUCTURED_LITERAL);
1273     }
1274 
1275     /**
1276      * Is thin-arrow lambda syntax enabled?
1277      *
1278      * @return true if thin-arrow lambda syntax is enabled, false otherwise
1279      * @since 3.3
1280      */
1281     public boolean supportsThinArrow() {
1282         return getFeature(THIN_ARROW);
1283     }
1284 
1285     /**
1286      * Sets whether thin-arrow lambda syntax is enabled.
1287      * <p>
1288      * When disabled, parsing a script/expression using syntactic thin-arrow (-&lt;)
1289      * will throw a parsing exception.
1290      * </p>
1291      *
1292      * @param flag true to enable, false to disable
1293      * @return this features instance
1294      * @since 3.3
1295      */
1296     public JexlFeatures thinArrow(final boolean flag) {
1297         setFeature(THIN_ARROW, flag);
1298         return this;
1299     }
1300 }