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 boolean logical flag. */ 051 private static final int BOOLEAN_LOGICAL = 10; 052 053 /** The interpolation string bit. */ 054 private static final int STRICT_INTERPOLATION= 9; 055 056 /** The const capture bit. */ 057 private static final int CONST_CAPTURE = 8; 058 059 /** The shared instance bit. */ 060 private static final int SHARED = 7; 061 062 /** The local shade bit. */ 063 private static final int SHADE = 6; 064 065 /** The antish var bit. */ 066 private static final int ANTISH = 5; 067 068 /** The lexical scope bit. */ 069 private static final int LEXICAL = 4; 070 071 /** The safe bit. */ 072 private static final int SAFE = 3; 073 074 /** The silent bit. */ 075 private static final int SILENT = 2; 076 077 /** The strict bit. */ 078 private static final int STRICT = 1; 079 080 /** The cancellable bit. */ 081 private static final int CANCELLABLE = 0; 082 083 /** The flag names ordered. */ 084 private static final String[] NAMES = { 085 "cancellable", "strict", "silent", "safe", "lexical", "antish", 086 "lexicalShade", "sharedInstance", "constCapture", "strictInterpolation", 087 "booleanShortCircuit" 088 }; 089 090 /** Default mask .*/ 091 private static int DEFAULT = 1 /*<< CANCELLABLE*/ | 1 << STRICT | 1 << ANTISH | 1 << SAFE; 092 093 /** 094 * Checks the value of a flag in the mask. 095 * 096 * @param ordinal the flag ordinal 097 * @param mask the flags mask 098 * @return the mask value with this flag or-ed in 099 */ 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}