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 (()->{...}, 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 >,
59 * {@code lt} for <, ...)</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 (=<)
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"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 (->,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 (-<)
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 }