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