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 *      https://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</li>
039 * <li>Reserved Names: a set of reserved variable names that cannot be used as local variable (or parameter) names</li>
040 * <li>Global Side Effect : assigning/modifying values on global variables (=, += , -=, ...)</li>
041 * <li>Lexical: lexical scope, prevents redefining local variables</li>
042 * <li>Lexical Shade: local variables shade globals, prevents confusing a global variable with a local one</li>
043 * <li>Side Effect : assigning/modifying values on any variables or left-value</li>
044 * <li>Constant Array Reference: ensures array references only use constants;they should be statically solvable.</li>
045 * <li>New Instance: creating an instance using new(...)</li>
046 * <li>Loops: loop constructs (while(true), for(...))</li>
047 * <li>Lambda: function definitions (()-&gt;{...}, function(...) ).</li>
048 * <li>Method calls: calling methods (obj.method(...) or obj['method'](...)); when disabled, leaves function calls
049 * - including namespace prefixes - available</li>
050 * <li>Structured literals: arrays, lists, maps, sets, ranges</li>
051 * <li>Pragma: pragma construct as in {@code #pragma x y}</li>
052 * <li>Annotation: @annotation statement;</li>
053 * <li>Thin-arrow: use the thin-arrow, ie {@code ->} for lambdas as in {@code x -> x + x}</li>
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 *
066 * @since 3.2
067 */
068public final class JexlFeatures {
069
070    /** The false predicate. */
071    public static final Predicate<String> TEST_STR_FALSE = s -> false;
072
073    /**
074     * The true predicate.
075     *
076     * @since 3.7.1
077     */
078    public static final Predicate<String> TEST_STR_TRUE = s -> true;
079
080    /** The feature names (for toString()). */
081    private static final String[] F_NAMES = {
082        "register", "reserved variable", "local variable", "assign/modify",
083        "global assign/modify", "array reference", "create instance", "loop", "function",
084        "method call", "set/map/array literal", "pragma", "annotation", "script", "lexical", "lexicalShade",
085        "thin-arrow", "fat-arrow", "namespace pragma", "namespace identifier", "import pragma", "comparator names", "pragma anywhere",
086        "const capture", "ref capture", "ambiguous statement", "ignore template prefix", "namespace instantiation"
087    };
088
089    /** Registers feature ordinal. */
090    private static final int REGISTER = 0;
091
092    /** Reserved future feature ordinal (unused as of 3.3.1). */
093    public static final int RESERVED = 1;
094
095    /** Locals feature ordinal. */
096    public static final int LOCAL_VAR = 2;
097
098    /** Side effects feature ordinal. */
099    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}