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