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