001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.commons.jexl3;
018
019import java.util.Collection;
020import java.util.Collections;
021import java.util.Set;
022import java.util.TreeSet;
023import java.util.Objects;
024import java.util.function.Predicate;
025
026/**
027 * A set of language feature options.
028 * These control <em>syntactical</em> constructs that will throw JexlException.Feature exceptions (a
029 * subclass of JexlException.Parsing) when disabled.
030 * <ul>
031 * <li>Registers: register syntax (#number), used internally for {g,s}etProperty
032 * <li>Reserved Names: a set of reserved variable names that can not be used as local variable (or parameter) names
033 * <li>Global Side Effect : assigning/modifying values on global variables (=, += , -=, ...)
034 * <li>Lexical: lexical scope, prevents redefining local variables
035 * <li>Lexical Shade: local variables shade globals, prevents confusing a global variable with a local one
036 * <li>Side Effect : assigning/modifying values on any variables or left-value
037 * <li>Constant Array Reference: ensures array references only use constants;they should be statically solvable.
038 * <li>New Instance: creating an instance using new(...)
039 * <li>Loops: loop constructs (while(true), for(...))
040 * <li>Lambda: function definitions (()-&gt;{...}, function(...) ).
041 * <li>Method calls: calling methods (obj.method(...) or obj['method'](...)); when disabled, leaves function calls
042 * - including namespace prefixes - available
043 * <li>Structured literals: arrays, lists, maps, sets, ranges
044 * <li>Pragma: pragma construct as in <code>#pragma x y</code>
045 * <li>Annotation: @annotation statement;
046 * <li>Thin-arrow: use the thin-arrow, ie <code>-&gt;</code> for lambdas as in <code>x -&gt; x + x</code>
047 * <li>Fat-arrow: use the  fat-arrow, ie <code>=&gt;</code> for lambdas as in <code>x =&gt; x + x</code>
048 * <li>Namespace pragma: whether the <code>#pragma jexl.namespace.ns namespace</code> syntax is allowed</li>
049 * <li>Import pragma: whether the <code>#pragma jexl.import fully.qualified.class.name</code> syntax is allowed</li>
050 * <li>Comparator names: whether the comparator operator names can be used (as in <code>gt</code> for &gt;,
051 * <code>lt</code> for &lt;, ...)</li>
052 * <li>Pragma anywhere: whether pragma, that are <em>not</em> statements and handled before execution begins,
053 * can appear anywhere in the source or before any statements - ie at the beginning of a script.</li>
054 * </ul>
055 * @since 3.2
056 */
057public final class JexlFeatures {
058    /** The feature flags. */
059    private long flags;
060    /** The set of reserved names, aka global variables that can not be masked by local variables or parameters. */
061    private Set<String> reservedNames;
062    /** The namespace names. */
063    private Predicate<String> nameSpaces;
064    /** The false predicate. */
065    public static final Predicate<String> TEST_STR_FALSE = s->false;
066    /** Te feature names (for toString()). */
067    private static final String[] F_NAMES = {
068        "register", "reserved variable", "local variable", "assign/modify",
069        "global assign/modify", "array reference", "create instance", "loop", "function",
070        "method call", "set/map/array literal", "pragma", "annotation", "script", "lexical", "lexicalShade",
071        "thin-arrow", "fat-arrow", "namespace pragma", "import pragma", "comparator names", "pragma anywhere"
072    };
073    /** Registers feature ordinal. */
074    private static final int REGISTER = 0;
075    /** Reserved name feature ordinal. */
076    public static final int RESERVED = 1;
077    /** Locals feature ordinal. */
078    public static final int LOCAL_VAR = 2;
079    /** Side effects feature ordinal. */
080    public static final int SIDE_EFFECT = 3;
081    /** Global side-effects feature ordinal. */
082    public static final int SIDE_EFFECT_GLOBAL = 4;
083    /** Array get is allowed on expr. */
084    public static final int ARRAY_REF_EXPR = 5;
085    /** New-instance feature ordinal. */
086    public static final int NEW_INSTANCE = 6;
087    /** Loops feature ordinal. */
088    public static final int LOOP = 7;
089    /** Lambda feature ordinal. */
090    public static final int LAMBDA = 8;
091    /** Lambda feature ordinal. */
092    public static final int METHOD_CALL = 9;
093    /** Structured literal feature ordinal. */
094    public static final int STRUCTURED_LITERAL = 10;
095    /** Pragma feature ordinal. */
096    public static final int PRAGMA = 11;
097    /** Annotation feature ordinal. */
098    public static final int ANNOTATION = 12;
099    /** 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}