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