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