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 * http://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 this instance 334 */ 335 public JexlOptions set(final JexlEngine jexl) { 336 if (jexl instanceof Engine) { 337 ((Engine) jexl).optionsSet(this); 338 } 339 return this; 340 } 341 342 /** 343 * Sets options from options. 344 * @param src the options 345 * @return 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 }