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  
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   * @since 3.2
46   */
47  public final class JexlOptions {
48      /** The boolean logical flag. */
49      private static final int BOOLEAN_LOGICAL = 10;
50      /** The interpolation string bit. */
51      private static final int STRICT_INTERPOLATION= 9;
52      /** The const capture bit. */
53      private static final int CONST_CAPTURE = 8;
54      /** The shared instance bit. */
55      private static final int SHARED = 7;
56      /** The local shade bit. */
57      private static final int SHADE = 6;
58      /** The antish var bit. */
59      private static final int ANTISH = 5;
60      /** The lexical scope bit. */
61      private static final int LEXICAL = 4;
62      /** The safe bit. */
63      private static final int SAFE = 3;
64      /** The silent bit. */
65      private static final int SILENT = 2;
66      /** The strict bit. */
67      private static final int STRICT = 1;
68      /** The cancellable bit. */
69      private static final int CANCELLABLE = 0;
70      /** The flag names ordered. */
71      private static final String[] NAMES = {
72          "cancellable", "strict", "silent", "safe", "lexical", "antish",
73          "lexicalShade", "sharedInstance", "constCapture", "strictInterpolation",
74          "booleanShortCircuit"
75      };
76      /** Default mask .*/
77      private static int DEFAULT = 1 /*<< CANCELLABLE*/ | 1 << STRICT | 1 << ANTISH | 1 << SAFE;
78  
79      /**
80       * Checks the value of a flag in the mask.
81       * @param ordinal the flag ordinal
82       * @param mask the flags mask
83       * @return the mask value with this flag or-ed in
84       */
85      private static boolean isSet(final int ordinal, final int mask) {
86          return (mask & 1 << ordinal) != 0;
87      }
88  
89      /**
90       * Parses flags by name.
91       * <p>A '+flag' or 'flag' will set flag as true, '-flag' set as false.
92       * The possible flag names are:
93       * cancellable, strict, silent, safe, lexical, antish, lexicalShade
94       * @param initial the initial mask state
95       * @param flags the flags to set
96       * @return the flag mask updated
97       */
98      public static int parseFlags(final int initial, final String... flags) {
99          int mask = initial;
100         for (final String flag : flags) {
101             boolean b = true;
102             final String name;
103             if (flag.charAt(0) == '+') {
104                 name = flag.substring(1);
105             } else if (flag.charAt(0) == '-') {
106                 name = flag.substring(1);
107                 b = false;
108             } else {
109                 name = flag;
110             }
111             for (int f = 0; f < NAMES.length; ++f) {
112                 if (NAMES[f].equals(name)) {
113                     if (b) {
114                         mask |= 1 << f;
115                     } else {
116                         mask &= ~(1 << f);
117                     }
118                     break;
119                 }
120             }
121         }
122         return mask;
123     }
124 
125     /**
126      * Sets the value of a flag in a mask.
127      * @param ordinal the flag ordinal
128      * @param mask the flags mask
129      * @param value true or false
130      * @return the new flags mask value
131      */
132     private static int set(final int ordinal, final int mask, final boolean value) {
133         return value? mask | 1 << ordinal : mask & ~(1 << ordinal);
134     }
135 
136     /**
137      * Sets the default (static, shared) option flags.
138      * <p>
139      * Whenever possible, we recommend using JexlBuilder methods to unambiguously instantiate a JEXL
140      * engine; this method should only be used for testing / validation.
141      * <p>A '+flag' or 'flag' will set the option named 'flag' as true, '-flag' set as false.
142      * The possible flag names are:
143      * cancellable, strict, silent, safe, lexical, antish, lexicalShade
144      * <p>Calling JexlBuilder.setDefaultOptions("+safe") once before JEXL engine creation
145      * may ease validating JEXL3.2 in your environment.
146      * @param flags the flags to set
147      */
148     public static void setDefaultFlags(final String...flags) {
149         DEFAULT = parseFlags(DEFAULT, flags);
150     }
151 
152     /** The arithmetic math context. */
153     private MathContext mathContext;
154 
155     /** The arithmetic math scale. */
156     private int mathScale = Integer.MIN_VALUE;
157 
158     /** The arithmetic strict math flag. */
159     private boolean strictArithmetic = true;
160 
161     /** The default flags, all but safe. */
162     private int flags = DEFAULT;
163 
164     /** The namespaces .*/
165     private Map<String, Object> namespaces = Collections.emptyMap();
166 
167     /** The imports. */
168     private Collection<String> imports = Collections.emptySet();
169 
170     /**
171      * Default ctor.
172      */
173     public JexlOptions() {
174         // all inits in members declarations
175     }
176 
177     /**
178      * Creates a copy of this instance.
179      * @return a copy
180      */
181     public JexlOptions copy() {
182         return new JexlOptions().set(this);
183     }
184 
185     /**
186      * Gets the optional set of imported packages.
187      * @return the set of imports, may be empty, not null
188      */
189     public Collection<String> getImports() {
190         return imports;
191     }
192 
193     /**
194      * The MathContext instance used for +,-,/,*,% operations on big decimals.
195      * @return the math context
196      */
197     public MathContext getMathContext() {
198         return mathContext;
199     }
200 
201     /**
202      * The BigDecimal scale used for comparison and coercion operations.
203      * @return the scale
204      */
205     public int getMathScale() {
206         return mathScale;
207     }
208 
209     /**
210      * Gets the optional map of namespaces.
211      * @return the map of namespaces, may be empty, not null
212      */
213     public Map<String, Object> getNamespaces() {
214         return namespaces;
215     }
216 
217     /**
218      * Checks whether evaluation will attempt resolving antish variable names.
219      * @return true if antish variables are solved, false otherwise
220      */
221     public boolean isAntish() {
222         return isSet(ANTISH, flags);
223     }
224 
225     /**
226      * Gets whether logical expressions (&quot;&quot; , ||) coerce their result to boolean; if set,
227      * an expression like (3 &quot;&quot; 4 &quot;&quot; 5) will evaluate to true. If not, it will evaluate to 5.
228      * To preserve strict compatibility with 3.4, set the flag to true.
229      * @return true if short-circuit logicals coerce their result to boolean, false otherwise
230      * @since 3.5.0
231      */
232     public boolean isBooleanLogical() {
233         return isSet(BOOLEAN_LOGICAL, flags);
234     }
235 
236     /**
237      * Checks whether evaluation will throw JexlException.Cancel (true) or
238      * return null (false) if interrupted.
239      * @return true when cancellable, false otherwise
240      */
241     public boolean isCancellable() {
242         return isSet(CANCELLABLE, flags);
243     }
244 
245     /**
246      * Are lambda captured-variables const?
247      * @return true if lambda captured-variables are const, false otherwise
248      */
249     public boolean isConstCapture() {
250         return isSet(CONST_CAPTURE, flags);
251     }
252 
253     /**
254      * Checks whether runtime variable scope is lexical.
255      * <p>If true, lexical scope applies to local variables and parameters.
256      * Redefining a variable in the same lexical unit will generate errors.
257      * @return true if scope is lexical, false otherwise
258      */
259     public boolean isLexical() {
260         return isSet(LEXICAL, flags);
261     }
262 
263     /**
264      * Checks whether local variables shade global ones.
265      * <p>After a symbol is defined as local, dereferencing it outside its
266      * scope will trigger an error instead of seeking a global variable of the
267      * same name. To further reduce potential naming ambiguity errors,
268      * global variables (ie non-local) must be declared to be assigned (@link JexlContext#has(String) )
269      * when this flag is on; attempting to set an undeclared global variables will
270      * raise an error.
271      * @return true if lexical shading is applied, false otherwise
272      */
273     public boolean isLexicalShade() {
274         return isSet(SHADE, flags);
275     }
276 
277     /**
278      * Checks whether the engine considers null in navigation expression as
279      * errors during evaluation.
280      * @return true if safe, false otherwise
281      */
282     public boolean isSafe() {
283         return isSet(SAFE, flags);
284     }
285 
286     /**
287      * Gets sharing state.
288      * @return false if a copy of these options is used during execution,
289      * true if those can potentially be modified
290      */
291     public boolean isSharedInstance() {
292         return isSet(SHARED, flags);
293     }
294 
295     /**
296      * Checks whether the engine will throw a {@link JexlException} when an
297      * error is encountered during evaluation.
298      * @return true if silent, false otherwise
299      */
300     public boolean isSilent() {
301         return isSet(SILENT, flags);
302     }
303 
304     /**
305      * Checks whether the engine considers unknown variables, methods and
306      * constructors as errors during evaluation.
307      * @return true if strict, false otherwise
308      */
309     public boolean isStrict() {
310         return isSet(STRICT, flags);
311     }
312 
313     /**
314      * Checks whether the arithmetic triggers errors during evaluation when null
315      * is used as an operand.
316      * @return true if strict, false otherwise
317      */
318     public boolean isStrictArithmetic() {
319         return strictArithmetic;
320     }
321 
322     /**
323      * Gets the strict-interpolation flag of this options instance.
324      * @return true if interpolation strings always return string, false otherwise
325      */
326     public boolean isStrictInterpolation() {
327         return isSet(STRICT_INTERPOLATION, flags);
328     }
329 
330     /**
331      * Sets options from engine.
332      * @param jexl the engine
333      * @return this instance
334      */
335     public JexlOptions set(final JexlEngine jexl) {
336         if (jexl instanceof Engine) {
337             ((Engine) jexl).optionsSet(this);
338         }
339         return this;
340     }
341 
342     /**
343      * Sets options from options.
344      * @param src the options
345      * @return this instance
346      */
347     public JexlOptions set(final JexlOptions src) {
348         mathContext = src.mathContext;
349         mathScale = src.mathScale;
350         strictArithmetic = src.strictArithmetic;
351         flags = src.flags;
352         namespaces = src.namespaces;
353         imports = src.imports;
354         return this;
355     }
356 
357     /**
358      * Sets whether the engine will attempt solving antish variable names from
359      * context.
360      * @param flag true if antish variables are solved, false otherwise
361      */
362     public void setAntish(final boolean flag) {
363         flags = set(ANTISH, flags, flag);
364     }
365 
366     /**
367      * Sets whether logical expressions (&quot;&quot; , ||) coerce their result to boolean.
368      * @param flag true or false
369      */
370     public void setBooleanLogical(final boolean flag) {
371         flags = set(BOOLEAN_LOGICAL, flags, flag);
372     }
373 
374     /**
375      * Sets whether the engine will throw JexlException.Cancel (true) or return
376      * null (false) when interrupted during evaluation.
377      * @param flag true when cancellable, false otherwise
378      */
379     public void setCancellable(final boolean flag) {
380         flags = set(CANCELLABLE, flags, flag);
381     }
382 
383     /**
384      * Sets whether lambda captured-variables are const or not.
385      * <p>
386      * When disabled, lambda-captured variables are implicitly converted to read-write local variable (let),
387      * when enabled, those are implicitly converted to read-only local variables (const).
388      * </p>
389      * @param flag true to enable, false to disable
390      */
391     public void setConstCapture(final boolean flag) {
392         flags = set(CONST_CAPTURE, flags, flag);
393     }
394 
395     /**
396      * Sets this option flags using the +/- syntax.
397      * @param opts the option flags
398      */
399     public void setFlags(final String... opts) {
400         flags = parseFlags(flags, opts);
401     }
402 
403     /**
404      * Sets the optional set of imports.
405      * @param imports the imported packages
406      */
407     public void setImports(final Collection<String> imports) {
408         this.imports = imports == null || imports.isEmpty()? Collections.emptySet() : imports;
409     }
410 
411     /**
412      * Sets whether the engine uses a strict block lexical scope during
413      * evaluation.
414      * @param flag true if lexical scope is used, false otherwise
415      */
416     public void setLexical(final boolean flag) {
417         flags = set(LEXICAL, flags, flag);
418     }
419 
420     /**
421      * Sets whether the engine strictly shades global variables.
422      * Local symbols shade globals after definition and creating global
423      * variables is prohibited during evaluation.
424      * If setting to lexical shade, lexical scope is also set.
425      * @param flag true if creation is allowed, false otherwise
426      */
427     public void setLexicalShade(final boolean flag) {
428         flags = set(SHADE, flags, flag);
429         if (flag) {
430             flags = set(LEXICAL, flags, true);
431         }
432     }
433 
434     /**
435      * Sets the arithmetic math context.
436      * @param mcontext the context
437      */
438     public void setMathContext(final MathContext mcontext) {
439         this.mathContext = mcontext;
440     }
441 
442     /**
443      * Sets the arithmetic math scale.
444      * @param mscale the scale
445      */
446     public void setMathScale(final int mscale) {
447         this.mathScale = mscale;
448     }
449 
450     /**
451      * Sets the optional map of namespaces.
452      * @param ns a namespaces map
453      */
454     public void setNamespaces(final Map<String, Object> ns) {
455         this.namespaces = ns == null || ns.isEmpty()? Collections.emptyMap() : ns;
456     }
457 
458     /**
459      * Sets whether the engine considers null in navigation expression as null or as errors
460      * during evaluation.
461      * <p>If safe, encountering null during a navigation expression - dereferencing a method or a field through a null
462      * object or property - will <em>not</em> be considered an error but evaluated as <em>null</em>. It is recommended
463      * to use <em>setSafe(false)</em> as an explicit default.</p>
464      * @param flag true if safe, false otherwise
465      */
466     public void setSafe(final boolean flag) {
467         flags = set(SAFE, flags, flag);
468     }
469 
470     /**
471      * Sets wether these options are immutable at runtime.
472      * <p>Expert mode; allows instance handled through context to be shared
473      * instead of copied.
474      * @param flag true if shared, false if not
475      */
476     public void setSharedInstance(final boolean flag) {
477         flags = set(SHARED, flags, flag);
478     }
479 
480     /**
481      * Sets whether the engine will throw a {@link JexlException} when an error
482      * is encountered during evaluation.
483      * @param flag true if silent, false otherwise
484      */
485     public void setSilent(final boolean flag) {
486         flags = set(SILENT, flags, flag);
487     }
488 
489     /**
490      * Sets whether the engine considers unknown variables, methods and
491      * constructors as errors during evaluation.
492      * @param flag true if strict, false otherwise
493      */
494     public void setStrict(final boolean flag) {
495         flags = set(STRICT, flags, flag);
496     }
497 
498     /**
499      * Sets the strict arithmetic flag.
500      * @param stricta true or false
501      */
502     public void setStrictArithmetic(final boolean stricta) {
503         this.strictArithmetic = stricta;
504     }
505 
506     /**
507      * Sets the strict interpolation flag.
508      * <p>When strict, interpolation strings composed only of an expression (ie `${...}`) are evaluated
509      * as strings; when not strict, integer results are left untouched.</p>
510      * This can affect the results of expressions like <code>map.`${key}`</code> when key is
511      * an integer (or a string); it is almost always possible to use <code>map[key]</code> to ensure
512      * that the key type is not altered.
513      * @param strict true or false
514      */
515     public void setStrictInterpolation(final boolean strict) {
516         flags = set(STRICT_INTERPOLATION, flags, strict);
517     }
518 
519     @Override public String toString() {
520         final StringBuilder strb = new StringBuilder();
521         for(int i = 0; i < NAMES.length; ++i) {
522             if (i > 0) {
523                 strb.append(' ');
524             }
525             strb.append((flags & 1 << i) != 0? '+':'-');
526             strb.append(NAMES[i]);
527         }
528         return strb.toString();
529     }
530 
531 }