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    *      http://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.Collection;
20  import java.util.Collections;
21  import java.util.Set;
22  import java.util.TreeSet;
23  import java.util.Objects;
24  import java.util.function.Predicate;
25  
26  /**
27   * A set of language feature options.
28   * These control <em>syntactical</em> constructs that will throw JexlException.Feature exceptions (a
29   * subclass of JexlException.Parsing) when disabled.
30   * <ul>
31   * <li>Registers: register syntax (#number), used internally for {g,s}etProperty
32   * <li>Reserved Names: a set of reserved variable names that can not be used as local variable (or parameter) names
33   * <li>Global Side Effect : assigning/modifying values on global variables (=, += , -=, ...)
34   * <li>Lexical: lexical scope, prevents redefining local variables
35   * <li>Lexical Shade: local variables shade globals, prevents confusing a global variable with a local one
36   * <li>Side Effect : assigning/modifying values on any variables or left-value
37   * <li>Constant Array Reference: ensures array references only use constants;they should be statically solvable.
38   * <li>New Instance: creating an instance using new(...)
39   * <li>Loops: loop constructs (while(true), for(...))
40   * <li>Lambda: function definitions (()-&gt;{...}, function(...) ).
41   * <li>Method calls: calling methods (obj.method(...) or obj['method'](...)); when disabled, leaves function calls
42   * - including namespace prefixes - available
43   * <li>Structured literals: arrays, lists, maps, sets, ranges
44   * <li>Pragma: pragma construct as in <code>#pragma x y</code>
45   * <li>Annotation: @annotation statement;
46   * <li>Thin-arrow: use the thin-arrow, ie <code>-&gt;</code> for lambdas as in <code>x -&gt; x + x</code>
47   * <li>Fat-arrow: use the  fat-arrow, ie <code>=&gt;</code> for lambdas as in <code>x =&gt; x + x</code>
48   * <li>Namespace pragma: whether the <code>#pragma jexl.namespace.ns namespace</code> syntax is allowed</li>
49   * <li>Import pragma: whether the <code>#pragma jexl.import fully.qualified.class.name</code> syntax is allowed</li>
50   * <li>Comparator names: whether the comparator operator names can be used (as in <code>gt</code> for &gt;,
51   * <code>lt</code> for &lt;, ...)</li>
52   * <li>Pragma anywhere: whether pragma, that are <em>not</em> statements and handled before execution begins,
53   * can appear anywhere in the source or before any statements - ie at the beginning of a script.</li>
54   * </ul>
55   * @since 3.2
56   */
57  public final class JexlFeatures {
58      /** The feature flags. */
59      private long flags;
60      /** The set of reserved names, aka global variables that can not be masked by local variables or parameters. */
61      private Set<String> reservedNames;
62      /** The namespace names. */
63      private Predicate<String> nameSpaces;
64      /** The false predicate. */
65      public static final Predicate<String> TEST_STR_FALSE = s->false;
66      /** Te feature names (for toString()). */
67      private static final String[] F_NAMES = {
68          "register", "reserved variable", "local variable", "assign/modify",
69          "global assign/modify", "array reference", "create instance", "loop", "function",
70          "method call", "set/map/array literal", "pragma", "annotation", "script", "lexical", "lexicalShade",
71          "thin-arrow", "fat-arrow", "namespace pragma", "import pragma", "comparator names", "pragma anywhere"
72      };
73      /** Registers feature ordinal. */
74      private static final int REGISTER = 0;
75      /** Reserved name feature ordinal. */
76      public static final int RESERVED = 1;
77      /** Locals feature ordinal. */
78      public static final int LOCAL_VAR = 2;
79      /** Side effects feature ordinal. */
80      public static final int SIDE_EFFECT = 3;
81      /** Global side-effects feature ordinal. */
82      public static final int SIDE_EFFECT_GLOBAL = 4;
83      /** Array get is allowed on expr. */
84      public static final int ARRAY_REF_EXPR = 5;
85      /** New-instance feature ordinal. */
86      public static final int NEW_INSTANCE = 6;
87      /** Loops feature ordinal. */
88      public static final int LOOP = 7;
89      /** Lambda feature ordinal. */
90      public static final int LAMBDA = 8;
91      /** Lambda feature ordinal. */
92      public static final int METHOD_CALL = 9;
93      /** Structured literal feature ordinal. */
94      public static final int STRUCTURED_LITERAL = 10;
95      /** Pragma feature ordinal. */
96      public static final int PRAGMA = 11;
97      /** Annotation feature ordinal. */
98      public static final int ANNOTATION = 12;
99      /** Script feature ordinal. */
100     public static final int SCRIPT = 13;
101     /** Lexical feature ordinal. */
102     public static final int LEXICAL = 14;
103     /** Lexical shade feature ordinal. */
104     public static final int LEXICAL_SHADE = 15;
105     /** Fat-arrow lambda syntax. */
106     public static final int THIN_ARROW = 16;
107     /** Fat-arrow lambda syntax. */
108     public static final int FAT_ARROW = 17;
109     /** Namespace pragma feature ordinal. */
110     public static final int NS_PRAGMA = 18;
111     /** Import pragma feature ordinal. */
112     public static final int IMPORT_PRAGMA = 19;
113     /** Comparator names (legacy) syntax. */
114     public static final int COMPARATOR_NAMES = 20;
115     /** The pragma anywhere feature ordinal. */
116     public static final int PRAGMA_ANYWHERE = 21;
117     /**
118      * The default features flag mask.
119      */
120     private static final long DEFAULT_FEATURES =
121             (1L << LOCAL_VAR)
122             | (1L << SIDE_EFFECT)
123             | (1L << SIDE_EFFECT_GLOBAL)
124             | (1L << ARRAY_REF_EXPR)
125             | (1L << NEW_INSTANCE)
126             | (1L << LOOP)
127             | (1L << LAMBDA)
128             | (1L << METHOD_CALL)
129             | (1L << STRUCTURED_LITERAL)
130             | (1L << PRAGMA)
131             | (1L << ANNOTATION)
132             | (1L << SCRIPT)
133             | (1L << THIN_ARROW)
134             | (1L << NS_PRAGMA)
135             | (1L << IMPORT_PRAGMA)
136             | (1L << COMPARATOR_NAMES)
137             | (1L << PRAGMA_ANYWHERE);
138 
139     /**
140      * Creates an all-features-enabled instance.
141      */
142     public JexlFeatures() {
143         flags = DEFAULT_FEATURES;
144         reservedNames = Collections.emptySet();
145         nameSpaces = TEST_STR_FALSE;
146     }
147 
148     /**
149      * Copy constructor.
150      * @param features the feature to copy from
151      */
152     public JexlFeatures(final JexlFeatures features) {
153         this.flags = features.flags;
154         this.reservedNames = features.reservedNames;
155         this.nameSpaces = features.nameSpaces;
156     }
157 
158     @Override
159     public int hashCode() { //CSOFF: MagicNumber
160         int hash = 3;
161         hash = 53 * hash + (int) (this.flags ^ (this.flags >>> 32));
162         hash = 53 * hash + (this.reservedNames != null ? this.reservedNames.hashCode() : 0);
163         return hash;
164     }
165 
166     @Override
167     public boolean equals(final Object obj) {
168         if (this == obj) {
169             return true;
170         }
171         if (obj == null) {
172             return false;
173         }
174         if (getClass() != obj.getClass()) {
175             return false;
176         }
177         final JexlFeatures other = (JexlFeatures) obj;
178         if (this.flags != other.flags) {
179             return false;
180         }
181         if (!Objects.equals(this.reservedNames, other.reservedNames)) {
182             return false;
183         }
184         return true;
185     }
186 
187     /**
188      * The text corresponding to a feature code.
189      * @param feature the feature number
190      * @return the feature name
191      */
192     public static String stringify(final int feature) {
193         return feature >= 0 && feature < F_NAMES.length ? F_NAMES[feature] : "unsupported feature";
194     }
195 
196     /**
197      * Sets a collection of reserved names precluding those to be used as local variables or parameter names.
198      * @param names the names to reserve
199      * @return this features instance
200      */
201     public JexlFeatures reservedNames(final Collection<String> names) {
202         if (names == null || names.isEmpty()) {
203             reservedNames = Collections.emptySet();
204         } else {
205             reservedNames = Collections.unmodifiableSet(new TreeSet<>(names));
206         }
207         setFeature(RESERVED, !reservedNames.isEmpty());
208         return this;
209     }
210 
211     /**
212      * @return the (unmodifiable) set of reserved names.
213      */
214     public Set<String> getReservedNames() {
215         return reservedNames;
216     }
217 
218     /**
219      * Checks whether a name is reserved.
220      * @param name the name to check
221      * @return true if reserved, false otherwise
222      */
223     public boolean isReservedName(final String name) {
224         return name != null && reservedNames.contains(name);
225     }
226 
227     /**
228      * Sets a test to determine namespace declaration.
229      * @param names the name predicate
230      * @return this features instance
231      */
232     public JexlFeatures namespaceTest(final Predicate<String> names) {
233         nameSpaces = names == null? TEST_STR_FALSE : names;
234         return this;
235     }
236 
237     /**
238      * @return the declared namespaces test.
239      */
240     public Predicate<String> namespaceTest() {
241         return nameSpaces;
242     }
243 
244     /**
245      * Sets a feature flag.
246      * @param feature the feature ordinal
247      * @param flag    turn-on, turn off
248      */
249     private void setFeature(final int feature, final boolean flag) {
250         if (flag) {
251             flags |= (1L << feature);
252         } else {
253             flags &= ~(1L << feature);
254         }
255     }
256 
257     /**
258      * Gets a feature flag value.
259      * @param feature feature ordinal
260      * @return true if on, false if off
261      */
262     private boolean getFeature(final int feature) {
263         return (flags & (1L << feature)) != 0L;
264     }
265 
266     /**
267      * Sets whether register are enabled.
268      * <p>
269      * This is mostly used internally during execution of JexlEngine.{g,s}etProperty.
270      * <p>
271      * When disabled, parsing a script/expression using the register syntax will throw a parsing exception.
272      * @param flag true to enable, false to disable
273      * @return this features instance
274      */
275     public JexlFeatures register(final boolean flag) {
276         setFeature(REGISTER, flag);
277         return this;
278     }
279 
280     /**
281      * @return true if register syntax is enabled
282      */
283     public boolean supportsRegister() {
284         return getFeature(REGISTER);
285     }
286 
287     /**
288      * Sets whether local variables are enabled.
289      * <p>
290      * When disabled, parsing a script/expression using a local variable or parameter syntax
291      * will throw a parsing exception.
292      * @param flag true to enable, false to disable
293      * @return this features instance
294      */
295     public JexlFeatures localVar(final boolean flag) {
296         setFeature(LOCAL_VAR, flag);
297         return this;
298     }
299 
300     /**
301      * @return true if local variables syntax is enabled
302      */
303     public boolean supportsLocalVar() {
304         return getFeature(LOCAL_VAR);
305     }
306 
307     /**
308      * Sets whether side effect expressions on global variables (aka non-local) are enabled.
309      * <p>
310      * When disabled, parsing a script/expression using syntactical constructs modifying variables
311      * <em>including all potentially ant-ish variables</em> will throw a parsing exception.
312      * @param flag true to enable, false to disable
313      * @return this features instance
314      */
315     public JexlFeatures sideEffectGlobal(final boolean flag) {
316         setFeature(SIDE_EFFECT_GLOBAL, flag);
317         return this;
318     }
319 
320     /**
321      * @return true if global variables can be assigned
322      */
323     public boolean supportsSideEffectGlobal() {
324         return getFeature(SIDE_EFFECT_GLOBAL);
325     }
326 
327     /**
328      * Sets whether side effect expressions are enabled.
329      * <p>
330      * When disabled, parsing a script/expression using syntactical constructs modifying variables
331      * or members will throw a parsing exception.
332      * @param flag true to enable, false to disable
333      * @return this features instance
334      */
335     public JexlFeatures sideEffect(final boolean flag) {
336         setFeature(SIDE_EFFECT, flag);
337         return this;
338     }
339 
340     /**
341      * @return true if side effects are enabled, false otherwise
342      */
343     public boolean supportsSideEffect() {
344         return getFeature(SIDE_EFFECT);
345     }
346 
347     /**
348      * Sets whether array references expressions are enabled.
349      * <p>
350      * When disabled, parsing a script/expression using 'obj[ ref ]' where ref is not a string or integer literal
351      * will throw a parsing exception;
352      * @param flag true to enable, false to disable
353      * @return this features instance
354      */
355     public JexlFeatures arrayReferenceExpr(final boolean flag) {
356         setFeature(ARRAY_REF_EXPR, flag);
357         return this;
358     }
359 
360     /**
361      * @return true if array references can contain method call expressions, false otherwise
362      */
363     public boolean supportsArrayReferenceExpr() {
364         return getFeature(ARRAY_REF_EXPR);
365     }
366 
367     /**
368      * Sets whether method calls expressions are enabled.
369      * <p>
370      * When disabled, parsing a script/expression using 'obj.method()'
371      * will throw a parsing exception;
372      * @param flag true to enable, false to disable
373      * @return this features instance
374      */
375     public JexlFeatures methodCall(final boolean flag) {
376         setFeature(METHOD_CALL, flag);
377         return this;
378     }
379 
380     /**
381      * @return true if array references can contain expressions, false otherwise
382      */
383     public boolean supportsMethodCall() {
384         return getFeature(METHOD_CALL);
385     }
386 
387     /**
388      * Sets whether array/map/set literal expressions are enabled.
389      * <p>
390      * When disabled, parsing a script/expression creating one of these literals
391      * will throw a parsing exception;
392      * @param flag true to enable, false to disable
393      * @return this features instance
394      */
395     public JexlFeatures structuredLiteral(final boolean flag) {
396         setFeature(STRUCTURED_LITERAL, flag);
397         return this;
398     }
399 
400     /**
401      * @return true if array/map/set literal expressions are supported, false otherwise
402      */
403     public boolean supportsStructuredLiteral() {
404         return getFeature(STRUCTURED_LITERAL);
405     }
406 
407     /**
408      * Sets whether creating new instances is enabled.
409      * <p>
410      * When disabled, parsing a script/expression using 'new(...)' will throw a parsing exception;
411      * using a class as functor will fail at runtime.
412      * @param flag true to enable, false to disable
413      * @return this features instance
414      */
415     public JexlFeatures newInstance(final boolean flag) {
416         setFeature(NEW_INSTANCE, flag);
417         return this;
418     }
419 
420     /**
421      * @return true if creating new instances is enabled, false otherwise
422      */
423     public boolean supportsNewInstance() {
424         return getFeature(NEW_INSTANCE);
425     }
426 
427     /**
428      * Sets whether looping constructs are enabled.
429      * <p>
430      * When disabled, parsing a script/expression using syntactic looping constructs (for,while)
431      * will throw a parsing exception.
432      * @param flag true to enable, false to disable
433      * @return this features instance
434      */
435     public JexlFeatures loops(final boolean flag) {
436         setFeature(LOOP, flag);
437         return this;
438     }
439 
440     /**
441      * @return true if loops are enabled, false otherwise
442      */
443     public boolean supportsLoops() {
444         return getFeature(LOOP);
445     }
446 
447     /**
448      * Sets whether lambda/function constructs are enabled.
449      * <p>
450      * When disabled, parsing a script/expression using syntactic lambda constructs (-&gt;,function)
451      * will throw a parsing exception.
452      * @param flag true to enable, false to disable
453      * @return this features instance
454      */
455     public JexlFeatures lambda(final boolean flag) {
456         setFeature(LAMBDA, flag);
457         return this;
458     }
459 
460     /**
461      * @return true if lambda are enabled, false otherwise
462      */
463     public boolean supportsLambda() {
464         return getFeature(LAMBDA);
465     }
466 
467     /**
468      * Sets whether thin-arrow lambda syntax is enabled.
469      * <p>
470      * When disabled, parsing a script/expression using syntactic thin-arrow (-&lt;)
471      * will throw a parsing exception.
472      * @param flag true to enable, false to disable
473      * @return this features instance
474      * @since 3.3
475      */
476     public JexlFeatures thinArrow(final boolean flag) {
477         setFeature(THIN_ARROW, flag);
478         return this;
479     }
480 
481     /**
482      * @return true if thin-arrow lambda syntax is enabled, false otherwise
483      * @since 3.3
484      */
485     public boolean supportsThinArrow() {
486         return getFeature(THIN_ARROW);
487     }
488 
489     /**
490      * Sets whether fat-arrow lambda syntax is enabled.
491      * <p>
492      * When disabled, parsing a script/expression using syntactic fat-arrow (=&lt;)
493      * will throw a parsing exception.
494      * @param flag true to enable, false to disable
495      * @return this features instance
496      * @since 3.3
497      */
498     public JexlFeatures fatArrow(final boolean flag) {
499         setFeature(FAT_ARROW, flag);
500         return this;
501     }
502 
503     /**
504      * @return true if fat-arrow lambda syntax is enabled, false otherwise
505      * @since 3.3
506      */
507     public boolean supportsFatArrow() {
508         return getFeature(FAT_ARROW);
509     }
510 
511     /**
512      * Sets whether the legacy comparison operator names syntax is enabled.
513      * <p>
514      * When disabled, comparison operators names (eq;ne;le;lt;ge;gt)
515      * will be treated as plain identifiers.
516      * @param flag true to enable, false to disable
517      * @return this features instance
518      * @since 3.3
519      */
520     public JexlFeatures comparatorNames(final boolean flag) {
521         setFeature(COMPARATOR_NAMES, flag);
522         return this;
523     }
524 
525     /**
526      * @return true if legacy comparison operator names syntax is enabled, false otherwise
527      * @since 3.3
528      */
529     public boolean supportsComparatorNames() {
530         return getFeature(COMPARATOR_NAMES);
531     }
532 
533     /**
534      * Sets whether pragma constructs are enabled.
535      * <p>
536      * When disabled, parsing a script/expression using syntactic pragma constructs (#pragma)
537      * will throw a parsing exception.
538      * @param flag true to enable, false to disable
539      * @return this features instance
540      */
541     public JexlFeatures pragma(final boolean flag) {
542         setFeature(PRAGMA, flag);
543         if (!flag) {
544             setFeature(NS_PRAGMA, false);
545             setFeature(IMPORT_PRAGMA, false);
546         }
547         return this;
548     }
549 
550     /**
551      * @return true if namespace pragma are enabled, false otherwise
552      */
553     public boolean supportsPragma() {
554         return getFeature(PRAGMA);
555     }
556 
557     /**
558      * Sets whether pragma constructs can appear anywhere in the code.
559      * <p>
560      * @param flag true to enable, false to disable
561      * @return this features instance
562      * @since 3.3
563      */
564     public JexlFeatures pragmaAnywhere(final boolean flag) {
565         setFeature(PRAGMA_ANYWHERE, flag);
566         return this;
567     }
568 
569     /**
570      * @return true if pragma constructs can appear anywhere in the code, false otherwise
571      * @since 3.3
572      */
573     public boolean supportsPragmaAnywhere() {
574         return getFeature(PRAGMA_ANYWHERE);
575     }
576 
577     /**
578      * Sets whether namespace pragma constructs are enabled.
579      * <p>
580      * When disabled, parsing a script/expression using syntactic namespace pragma constructs
581      * (#pragma jexl.namespace....) will throw a parsing exception.
582      * @param flag true to enable, false to disable
583      * @return this features instance
584      * @since 3.3
585      */
586     public JexlFeatures namespacePragma(final boolean flag) {
587         setFeature(NS_PRAGMA, flag);
588         return this;
589     }
590 
591     /**
592      * @return true if namespace pragma are enabled, false otherwise
593      * @since 3.3
594      */
595     public boolean supportsNamespacePragma() {
596         return getFeature(NS_PRAGMA);
597     }
598     /**
599      * Sets whether import pragma constructs are enabled.
600      * <p>
601      * When disabled, parsing a script/expression using syntactic import pragma constructs
602      * (#pragma jexl.import....) will throw a parsing exception.
603      * @param flag true to enable, false to disable
604      * @return this features instance
605      * @since 3.3
606      */
607     public JexlFeatures importPragma(final boolean flag) {
608         setFeature(IMPORT_PRAGMA, flag);
609         return this;
610     }
611 
612     /**
613      * @return true if import pragma are enabled, false otherwise
614      * @since 3.3
615      */
616     public boolean supportsImportPragma() {
617         return getFeature(IMPORT_PRAGMA);
618     }
619 
620 
621     /**
622      * Sets whether annotation constructs are enabled.
623      * <p>
624      * When disabled, parsing a script/expression using syntactic annotation constructs (@annotation)
625      * will throw a parsing exception.
626      * @param flag true to enable, false to disable
627      * @return this features instance
628      */
629     public JexlFeatures annotation(final boolean flag) {
630         setFeature(ANNOTATION, flag);
631         return this;
632     }
633 
634     /**
635      * @return true if annotation are enabled, false otherwise
636      */
637     public boolean supportsAnnotation() {
638         return getFeature(ANNOTATION);
639     }
640 
641     /**
642      * Sets whether scripts constructs are enabled.
643      * <p>
644      * When disabled, parsing a script using syntactic script constructs (statements, ...)
645      * will throw a parsing exception.
646      * @param flag true to enable, false to disable
647      * @return this features instance
648      */
649     public JexlFeatures script(final boolean flag) {
650         setFeature(SCRIPT, flag);
651         return this;
652     }
653 
654     /**
655      * @return true if scripts are enabled, false otherwise
656      */
657     public boolean supportsScript() {
658         return getFeature(SCRIPT);
659     }
660 
661     /**
662      *
663      * @return true if expressions (aka not scripts) are enabled, false otherwise
664      */
665     public boolean supportsExpression() {
666         return !getFeature(SCRIPT);
667     }
668 
669     /**
670      * Sets whether syntactic lexical mode is enabled.
671      *
672      * @param flag true means syntactic lexical function scope is in effect, false implies non-lexical scoping
673      * @return this features instance
674      */
675     public JexlFeatures lexical(final boolean flag) {
676         setFeature(LEXICAL, flag);
677         return this;
678     }
679 
680 
681     /** @return whether lexical scope feature is enabled */
682     public boolean isLexical() {
683         return getFeature(LEXICAL);
684     }
685 
686     /**
687      * Sets whether syntactic lexical shade is enabled.
688      *
689      * @param flag true means syntactic lexical shade is in effect and implies lexical scope
690      * @return this features instance
691      */
692     public JexlFeatures lexicalShade(final boolean flag) {
693         setFeature(LEXICAL_SHADE, flag);
694         if (flag) {
695             setFeature(LEXICAL, true);
696         }
697         return this;
698     }
699 
700 
701     /** @return whether lexical shade feature is enabled */
702     public boolean isLexicalShade() {
703         return getFeature(LEXICAL_SHADE);
704     }
705 }