001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * https://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 018package org.apache.commons.jexl3; 019 020import java.math.MathContext; 021import java.util.Collection; 022import java.util.Collections; 023import java.util.Map; 024 025import org.apache.commons.jexl3.internal.Engine; 026 027/** 028 * Flags and properties that can alter the evaluation behavior. 029 * The flags, briefly explained, are the following: 030 * <ul> 031 * <li>silent: whether errors throw exception</li> 032 * <li>safe: whether navigation through null is <em>not</em> an error</li> 033 * <li>cancellable: whether thread interruption is an error</li> 034 * <li>lexical: whether redefining local variables is an error</li> 035 * <li>lexicalShade: whether local variables shade global ones even outside their scope</li> 036 * <li>strict: whether unknown or unsolvable identifiers are errors</li> 037 * <li>strictArithmetic: whether null as operand is an error</li> 038 * <li>sharedInstance: whether these options can be modified at runtime during execution (expert)</li> 039 * <li>constCapture: whether captured variables will throw an error if an attempt is made to change their value</li> 040 * <li>strictInterpolation: whether interpolation strings always return a string or attempt to parse and return integer</li> 041 * <li>booleanLogical: whether logical expressions ("" , ||) coerce their result to boolean</li> 042 * </ul> 043 * The sensible default is cancellable, strict and strictArithmetic. 044 * <p>This interface replaces the now deprecated JexlEngine.Options. 045 * 046 * @since 3.2 047 */ 048public final class JexlOptions { 049 050 /** The namespace auto-instantiation flag. */ 051 private static final int NAMESPACE_INSTANTIATE = 11; 052 053 /** The boolean logical flag. */ 054 private static final int BOOLEAN_LOGICAL = 10; 055 056 /** The interpolation string bit. */ 057 private static final int STRICT_INTERPOLATION= 9; 058 059 /** The const capture bit. */ 060 private static final int CONST_CAPTURE = 8; 061 062 /** The shared instance bit. */ 063 private static final int SHARED = 7; 064 065 /** The local shade bit. */ 066 private static final int SHADE = 6; 067 068 /** The antish var bit. */ 069 private static final int ANTISH = 5; 070 071 /** The lexical scope bit. */ 072 private static final int LEXICAL = 4; 073 074 /** The safe bit. */ 075 private static final int SAFE = 3; 076 077 /** The silent bit. */ 078 private static final int SILENT = 2; 079 080 /** The strict bit. */ 081 private static final int STRICT = 1; 082 083 /** The cancellable bit. */ 084 private static final int CANCELLABLE = 0; 085 086 /** The flag names ordered. */ 087 private static final String[] NAMES = { 088 "cancellable", "strict", "silent", "safe", "lexical", "antish", 089 "lexicalShade", "sharedInstance", "constCapture", "strictInterpolation", 090 "booleanShortCircuit", "namespaceInstantiation" 091 }; 092 093 /** Default mask .*/ 094 private static int DEFAULT = 1 /*<< CANCELLABLE*/ | 1 << STRICT | 1 << ANTISH | 1 << SAFE 095 | 1 << NAMESPACE_INSTANTIATE; 096 097 /** 098 * Checks the value of a flag in the mask. 099 * 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}