View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      https://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.commons.jexl3;
19  
20  import java.math.MathContext;
21  import java.util.Collection;
22  import java.util.Collections;
23  import java.util.Map;
24  
25  import org.apache.commons.jexl3.internal.Engine;
26  
27  /**
28   * Flags and properties that can alter the evaluation behavior.
29   * The flags, briefly explained, are the following:
30   * <ul>
31   * <li>silent: whether errors throw exception</li>
32   * <li>safe: whether navigation through null is <em>not</em> an error</li>
33   * <li>cancellable: whether thread interruption is an error</li>
34   * <li>lexical: whether redefining local variables is an error</li>
35   * <li>lexicalShade: whether local variables shade global ones even outside their scope</li>
36   * <li>strict: whether unknown or unsolvable identifiers are errors</li>
37   * <li>strictArithmetic: whether null as operand is an error</li>
38   * <li>sharedInstance: whether these options can be modified at runtime during execution (expert)</li>
39   * <li>constCapture: whether captured variables will throw an error if an attempt is made to change their value</li>
40   * <li>strictInterpolation: whether interpolation strings always return a string or attempt to parse and return integer</li>
41   * <li>booleanLogical: whether logical expressions (&quot;&quot; , ||) coerce their result to boolean</li>
42   * </ul>
43   * The sensible default is cancellable, strict and strictArithmetic.
44   * <p>This interface replaces the now deprecated JexlEngine.Options.
45   *
46   * @since 3.2
47   */
48  public final class JexlOptions {
49  
50      /** The namespace auto-instantiation flag. */
51      private static final int NAMESPACE_INSTANTIATE = 11;
52  
53      /** The boolean logical flag. */
54      private static final int BOOLEAN_LOGICAL = 10;
55  
56      /** The interpolation string bit. */
57      private static final int STRICT_INTERPOLATION= 9;
58  
59      /** The const capture bit. */
60      private static final int CONST_CAPTURE = 8;
61  
62      /** The shared instance bit. */
63      private static final int SHARED = 7;
64  
65      /** The local shade bit. */
66      private static final int SHADE = 6;
67  
68      /** The antish var bit. */
69      private static final int ANTISH = 5;
70  
71      /** The lexical scope bit. */
72      private static final int LEXICAL = 4;
73  
74      /** The safe bit. */
75      private static final int SAFE = 3;
76  
77      /** The silent bit. */
78      private static final int SILENT = 2;
79  
80      /** The strict bit. */
81      private static final int STRICT = 1;
82  
83      /** The cancellable bit. */
84      private static final int CANCELLABLE = 0;
85  
86      /** The flag names ordered. */
87      private static final String[] NAMES = {
88          "cancellable", "strict", "silent", "safe", "lexical", "antish",
89          "lexicalShade", "sharedInstance", "constCapture", "strictInterpolation",
90          "booleanShortCircuit", "namespaceInstantiation"
91      };
92  
93      /** Default mask .*/
94      private static int DEFAULT = 1 /*<< CANCELLABLE*/ | 1 << STRICT | 1 << ANTISH | 1 << SAFE
95          | 1 << NAMESPACE_INSTANTIATE;
96  
97      /**
98       * Checks the value of a flag in the mask.
99       *
100      * @param ordinal The flag ordinal
101      * @param mask The flags mask
102      * @return The mask value with this flag or-ed in
103      */
104     private static boolean isSet(final int ordinal, final int mask) {
105         return (mask & 1 << ordinal) != 0;
106     }
107 
108     /**
109      * Parses flags by name.
110      * <p>A '+flag' or 'flag' will set flag as true, '-flag' set as false.
111      * The possible flag names are:
112      * cancellable, strict, silent, safe, lexical, antish, lexicalShade
113      *
114      * @param initial The initial mask state
115      * @param flags The flags to set
116      * @return The flag mask updated
117      */
118     public static int parseFlags(final int initial, final String... flags) {
119         int mask = initial;
120         for (final String flag : flags) {
121             boolean b = true;
122             final String name;
123             if (flag.charAt(0) == '+') {
124                 name = flag.substring(1);
125             } else if (flag.charAt(0) == '-') {
126                 name = flag.substring(1);
127                 b = false;
128             } else {
129                 name = flag;
130             }
131             for (int f = 0; f < NAMES.length; ++f) {
132                 if (NAMES[f].equals(name)) {
133                     if (b) {
134                         mask |= 1 << f;
135                     } else {
136                         mask &= ~(1 << f);
137                     }
138                     break;
139                 }
140             }
141         }
142         return mask;
143     }
144 
145     /**
146      * Sets the value of a flag in a mask.
147      *
148      * @param ordinal The flag ordinal
149      * @param mask The flags mask
150      * @param value true or false
151      * @return The new flags mask value
152      */
153     private static int set(final int ordinal, final int mask, final boolean value) {
154         return value? mask | 1 << ordinal : mask & ~(1 << ordinal);
155     }
156 
157     /**
158      * Sets the default (static, shared) option flags.
159      * <p>
160      * Whenever possible, we recommend using JexlBuilder methods to unambiguously instantiate a JEXL
161      * engine; this method should only be used for testing / validation.
162      * <p>A '+flag' or 'flag' will set the option named 'flag' as true, '-flag' set as false.
163      * The possible flag names are:
164      * cancellable, strict, silent, safe, lexical, antish, lexicalShade
165      * <p>Calling JexlBuilder.setDefaultOptions("+safe") once before JEXL engine creation
166      * may ease validating JEXL3.2 in your environment.
167      *
168      * @param flags The flags to set
169      */
170     public static void setDefaultFlags(final String...flags) {
171         DEFAULT = parseFlags(DEFAULT, flags);
172     }
173 
174     /** The arithmetic math context. */
175     private MathContext mathContext;
176 
177     /** The arithmetic math scale. */
178     private int mathScale = Integer.MIN_VALUE;
179 
180     /** The arithmetic strict math flag. */
181     private boolean strictArithmetic = true;
182 
183     /** The default flags, all but safe. */
184     private int flags = DEFAULT;
185 
186     /** The namespaces .*/
187     private Map<String, Object> namespaces = Collections.emptyMap();
188 
189     /** The imports. */
190     private Collection<String> imports = Collections.emptySet();
191 
192     /**
193      * Default ctor.
194      */
195     public JexlOptions() {
196         // all inits in members declarations
197     }
198 
199     /**
200      * Creates a copy of this instance.
201      *
202      * @return A copy
203      */
204     public JexlOptions copy() {
205         return new JexlOptions().set(this);
206     }
207 
208     /**
209      * Gets the optional set of imported packages.
210      *
211      * @return The set of imports, may be empty, not null
212      */
213     public Collection<String> getImports() {
214         return imports;
215     }
216 
217     /**
218      * The MathContext instance used for +,-,/,*,% operations on big decimals.
219      *
220      * @return The math context
221      */
222     public MathContext getMathContext() {
223         return mathContext;
224     }
225 
226     /**
227      * The BigDecimal scale used for comparison and coercion operations.
228      *
229      * @return The scale
230      */
231     public int getMathScale() {
232         return mathScale;
233     }
234 
235     /**
236      * Gets the optional map of namespaces.
237      *
238      * @return The map of namespaces, may be empty, not null
239      */
240     public Map<String, Object> getNamespaces() {
241         return namespaces;
242     }
243 
244     /**
245      * Checks whether evaluation will attempt resolving antish variable names.
246      *
247      * @return true if antish variables are solved, false otherwise
248      */
249     public boolean isAntish() {
250         return isSet(ANTISH, flags);
251     }
252 
253     /**
254      * Gets whether logical expressions (&quot;&quot; , ||) coerce their result to boolean; if set,
255      * an expression like (3 &quot;&quot; 4 &quot;&quot; 5) will evaluate to true. If not, it will evaluate to 5.
256      * To preserve strict compatibility with 3.4, set the flag to true.
257      *
258      * @return true if short-circuit logicals coerce their result to boolean, false otherwise
259      * @since 3.5.0
260      */
261     public boolean isBooleanLogical() {
262         return isSet(BOOLEAN_LOGICAL, flags);
263     }
264 
265     /**
266      * Checks whether evaluation will throw JexlException.Cancel (true) or
267      * return null (false) if interrupted.
268      *
269      * @return true when cancellable, false otherwise
270      */
271     public boolean isCancellable() {
272         return isSet(CANCELLABLE, flags);
273     }
274 
275     /**
276      * Are lambda captured-variables const?
277      *
278      * @return true if lambda captured-variables are const, false otherwise
279      */
280     public boolean isConstCapture() {
281         return isSet(CONST_CAPTURE, flags);
282     }
283 
284     /**
285      * Is reflective auto-instantiation of a namespace functor from a class/class-name
286      * binding allowed?
287      *
288      * @return true if namespace auto-instantiation is allowed, false otherwise
289      * @since 3.7.1
290      */
291     public boolean isNamespaceInstantiation() {
292         return isSet(NAMESPACE_INSTANTIATE, flags);
293     }
294 
295     /**
296      * Checks whether runtime variable scope is lexical.
297      * <p>If true, lexical scope applies to local variables and parameters.
298      * Redefining a variable in the same lexical unit will generate errors.
299      *
300      * @return true if scope is lexical, false otherwise
301      */
302     public boolean isLexical() {
303         return isSet(LEXICAL, flags);
304     }
305 
306     /**
307      * Checks whether local variables shade global ones.
308      * <p>After a symbol is defined as local, dereferencing it outside its
309      * scope will trigger an error instead of seeking a global variable of the
310      * same name. To further reduce potential naming ambiguity errors,
311      * global variables (ie non-local) must be declared to be assigned {@link JexlContext#has(String)}
312      * when this flag is on; attempting to set an undeclared global variables will
313      * raise an error.
314      *
315      * @return true if lexical shading is applied, false otherwise
316      */
317     public boolean isLexicalShade() {
318         return isSet(SHADE, flags);
319     }
320 
321     /**
322      * Checks whether the engine considers null in navigation expression as
323      * errors during evaluation.
324      *
325      * @return true if safe, false otherwise
326      */
327     public boolean isSafe() {
328         return isSet(SAFE, flags);
329     }
330 
331     /**
332      * Gets sharing state.
333      *
334      * @return false if a copy of these options is used during execution,
335      * true if those can potentially be modified
336      */
337     public boolean isSharedInstance() {
338         return isSet(SHARED, flags);
339     }
340 
341     /**
342      * Checks whether the engine will throw a {@link JexlException} when an
343      * error is encountered during evaluation.
344      *
345      * @return true if silent, false otherwise
346      */
347     public boolean isSilent() {
348         return isSet(SILENT, flags);
349     }
350 
351     /**
352      * Checks whether the engine considers unknown variables, methods and
353      * constructors as errors during evaluation.
354      *
355      * @return true if strict, false otherwise
356      */
357     public boolean isStrict() {
358         return isSet(STRICT, flags);
359     }
360 
361     /**
362      * Checks whether the arithmetic triggers errors during evaluation when null
363      * is used as an operand.
364      *
365      * @return true if strict, false otherwise
366      */
367     public boolean isStrictArithmetic() {
368         return strictArithmetic;
369     }
370 
371     /**
372      * Gets the strict-interpolation flag of this options instance.
373      *
374      * @return true if interpolation strings always return string, false otherwise
375      */
376     public boolean isStrictInterpolation() {
377         return isSet(STRICT_INTERPOLATION, flags);
378     }
379 
380     /**
381      * Sets options from engine.
382      *
383      * @param jexl The engine
384      * @return {@code this} instance
385      */
386     public JexlOptions set(final JexlEngine jexl) {
387         if (jexl instanceof Engine) {
388             return ((Engine) jexl).optionsSet(this);
389         }
390         throw new UnsupportedOperationException("JexlEngine is not an Engine instance: " + jexl.getClass().getName());
391     }
392 
393     /**
394      * Sets options from options.
395      *
396      * @param src The options
397      * @return {@code this} instance
398      */
399     public JexlOptions set(final JexlOptions src) {
400         mathContext = src.mathContext;
401         mathScale = src.mathScale;
402         strictArithmetic = src.strictArithmetic;
403         flags = src.flags;
404         namespaces = src.namespaces;
405         imports = src.imports;
406         return this;
407     }
408 
409     /**
410      * Sets whether the engine will attempt solving antish variable names from
411      * context.
412      *
413      * @param flag true if antish variables are solved, false otherwise
414      */
415     public void setAntish(final boolean flag) {
416         flags = set(ANTISH, flags, flag);
417     }
418 
419     /**
420      * Sets whether logical expressions (&quot;&quot; , ||) coerce their result to boolean.
421      *
422      * @param flag true or false
423      */
424     public void setBooleanLogical(final boolean flag) {
425         flags = set(BOOLEAN_LOGICAL, flags, flag);
426     }
427 
428     /**
429      * Sets whether the engine will throw JexlException.Cancel (true) or return
430      * null (false) when interrupted during evaluation.
431      *
432      * @param flag true when cancellable, false otherwise
433      */
434     public void setCancellable(final boolean flag) {
435         flags = set(CANCELLABLE, flags, flag);
436     }
437 
438     /**
439      * Sets whether lambda captured-variables are const or not.
440      * <p>
441      * When disabled, lambda-captured variables are implicitly converted to read-write local variable (let),
442      * when enabled, those are implicitly converted to read-only local variables (const).
443      * </p>
444      *
445      * @param flag true to enable, false to disable
446      */
447     public void setConstCapture(final boolean flag) {
448         flags = set(CONST_CAPTURE, flags, flag);
449     }
450 
451     /**
452      * Sets whether a namespace bound to a class or class-name may be reflectively
453      * auto-instantiated into a functor.
454      *
455      * @param flag true to enable, false to disable
456      * @since 3.7.1
457      */
458     public void setNamespaceInstantiation(final boolean flag) {
459         flags = set(NAMESPACE_INSTANTIATE, flags, flag);
460     }
461 
462     /**
463      * Sets this option flags using the +/- syntax.
464      *
465      * @param opts The option flags
466      */
467     public void setFlags(final String... opts) {
468         flags = parseFlags(flags, opts);
469     }
470 
471     /**
472      * Sets the optional set of imports.
473      *
474      * @param imports The imported packages
475      */
476     public void setImports(final Collection<String> imports) {
477         this.imports = imports == null || imports.isEmpty()? Collections.emptySet() : imports;
478     }
479 
480     /**
481      * Sets whether the engine uses a strict block lexical scope during
482      * evaluation.
483      *
484      * @param flag true if lexical scope is used, false otherwise
485      */
486     public void setLexical(final boolean flag) {
487         flags = set(LEXICAL, flags, flag);
488     }
489 
490     /**
491      * Sets whether the engine strictly shades global variables.
492      * Local symbols shade globals after definition and creating global
493      * variables is prohibited during evaluation.
494      * If setting to lexical shade, lexical scope is also set.
495      *
496      * @param flag true if creation is allowed, false otherwise
497      */
498     public void setLexicalShade(final boolean flag) {
499         flags = set(SHADE, flags, flag);
500         if (flag) {
501             flags = set(LEXICAL, flags, true);
502         }
503     }
504 
505     /**
506      * Sets the arithmetic math context.
507      *
508      * @param mcontext The context
509      */
510     public void setMathContext(final MathContext mcontext) {
511         this.mathContext = mcontext;
512     }
513 
514     /**
515      * Sets the arithmetic math scale.
516      *
517      * @param mscale The scale
518      */
519     public void setMathScale(final int mscale) {
520         this.mathScale = mscale;
521     }
522 
523     /**
524      * Sets the optional map of namespaces.
525      *
526      * @param ns A namespaces map
527      */
528     public void setNamespaces(final Map<String, Object> ns) {
529         this.namespaces = ns == null || ns.isEmpty()? Collections.emptyMap() : ns;
530     }
531 
532     /**
533      * Sets whether the engine considers null in navigation expression as null or as errors
534      * during evaluation.
535      * <p>If safe, encountering null during a navigation expression - dereferencing a method or a field through a null
536      * object or property - will <em>not</em> be considered an error but evaluated as <em>null</em>. It is recommended
537      * to use <em>setSafe(false)</em> as an explicit default.</p>
538      *
539      * @param flag true if safe, false otherwise
540      */
541     public void setSafe(final boolean flag) {
542         flags = set(SAFE, flags, flag);
543     }
544 
545     /**
546      * Sets wether these options are immutable at runtime.
547      * <p>Expert mode; allows instance handled through context to be shared
548      * instead of copied.
549      *
550      * @param flag true if shared, false if not
551      */
552     public void setSharedInstance(final boolean flag) {
553         flags = set(SHARED, flags, flag);
554     }
555 
556     /**
557      * Sets whether the engine will throw a {@link JexlException} when an error
558      * is encountered during evaluation.
559      *
560      * @param flag true if silent, false otherwise
561      */
562     public void setSilent(final boolean flag) {
563         flags = set(SILENT, flags, flag);
564     }
565 
566     /**
567      * Sets whether the engine considers unknown variables, methods and
568      * constructors as errors during evaluation.
569      *
570      * @param flag true if strict, false otherwise
571      */
572     public void setStrict(final boolean flag) {
573         flags = set(STRICT, flags, flag);
574     }
575 
576     /**
577      * Sets the strict arithmetic flag.
578      *
579      * @param stricta true or false
580      */
581     public void setStrictArithmetic(final boolean stricta) {
582         this.strictArithmetic = stricta;
583     }
584 
585     /**
586      * Sets the strict interpolation flag.
587      * <p>When strict, interpolation strings composed only of an expression (ie `${...}`) are evaluated
588      * as strings; when not strict, integer results are left untouched.</p>
589      * This can affect the results of expressions like <code>map.`${key}`</code> when key is
590      * an integer (or a string); it is almost always possible to use <code>map[key]</code> to ensure
591      * that the key type is not altered.
592      *
593      * @param strict true or false
594      */
595     public void setStrictInterpolation(final boolean strict) {
596         flags = set(STRICT_INTERPOLATION, flags, strict);
597     }
598 
599     @Override public String toString() {
600         final StringBuilder strb = new StringBuilder();
601         for(int i = 0; i < NAMES.length; ++i) {
602             if (i > 0) {
603                 strb.append(' ');
604             }
605             strb.append((flags & 1 << i) != 0? '+':'-');
606             strb.append(NAMES[i]);
607         }
608         return strb.toString();
609     }
610 
611 }