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 ("" , ||) 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 ("" , ||) coerce their result to boolean; if set,
227 * an expression like (3 "" 4 "" 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 {@code this} instance
334 */
335 public JexlOptions set(final JexlEngine jexl) {
336 if (jexl instanceof Engine) {
337 return ((Engine) jexl).optionsSet(this);
338 }
339 throw new UnsupportedOperationException("JexlEngine is not an Engine instance: " + jexl.getClass().getName());
340 }
341
342 /**
343 * Sets options from options.
344 * @param src the options
345 * @return {@code 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 ("" , ||) 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 }