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