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 * http://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 */ 017package org.apache.commons.jexl3; 018 019import java.util.Collection; 020import java.util.Collections; 021import java.util.Set; 022import java.util.TreeSet; 023import java.util.Objects; 024import java.util.function.Predicate; 025 026/** 027 * A set of language feature options. 028 * These control <em>syntactical</em> constructs that will throw JexlException.Feature exceptions (a 029 * subclass of JexlException.Parsing) when disabled. 030 * <ul> 031 * <li>Registers: register syntax (#number), used internally for {g,s}etProperty 032 * <li>Reserved Names: a set of reserved variable names that can not be used as local variable (or parameter) names 033 * <li>Global Side Effect : assigning/modifying values on global variables (=, += , -=, ...) 034 * <li>Lexical: lexical scope, prevents redefining local variables 035 * <li>Lexical Shade: local variables shade globals, prevents confusing a global variable with a local one 036 * <li>Side Effect : assigning/modifying values on any variables or left-value 037 * <li>Constant Array Reference: ensures array references only use constants;they should be statically solvable. 038 * <li>New Instance: creating an instance using new(...) 039 * <li>Loops: loop constructs (while(true), for(...)) 040 * <li>Lambda: function definitions (()->{...}, function(...) ). 041 * <li>Method calls: calling methods (obj.method(...) or obj['method'](...)); when disabled, leaves function calls 042 * - including namespace prefixes - available 043 * <li>Structured literals: arrays, lists, maps, sets, ranges 044 * <li>Pragmas: #pragma x y 045 * <li>Annotation: @annotation statement; 046 * </ul> 047 * @since 3.2 048 */ 049public final class JexlFeatures { 050 /** The feature flags. */ 051 private long flags; 052 /** The set of reserved names, aka global variables that can not be masked by local variables or parameters. */ 053 private Set<String> reservedNames; 054 /** The namespace names. */ 055 private Predicate<String> nameSpaces; 056 /** The false predicate. */ 057 public static final Predicate<String> TEST_STR_FALSE = (s)->false; 058 /** Te feature names (for toString()). */ 059 private static final String[] F_NAMES = { 060 "register", "reserved variable", "local variable", "assign/modify", 061 "global assign/modify", "array reference", "create instance", "loop", "function", 062 "method call", "set/map/array literal", "pragma", "annotation", "script", "lexical", "lexicalShade" 063 }; 064 /** Registers feature ordinal. */ 065 private static final int REGISTER = 0; 066 /** Reserved name feature ordinal. */ 067 public static final int RESERVED = 1; 068 /** Locals feature ordinal. */ 069 public static final int LOCAL_VAR = 2; 070 /** Side-effects feature ordinal. */ 071 public static final int SIDE_EFFECT = 3; 072 /** Global side-effects feature ordinal. */ 073 public static final int SIDE_EFFECT_GLOBAL = 4; 074 /** Array get is allowed on expr. */ 075 public static final int ARRAY_REF_EXPR = 5; 076 /** New-instance feature ordinal. */ 077 public static final int NEW_INSTANCE = 6; 078 /** Loops feature ordinal. */ 079 public static final int LOOP = 7; 080 /** Lambda feature ordinal. */ 081 public static final int LAMBDA = 8; 082 /** Lambda feature ordinal. */ 083 public static final int METHOD_CALL = 9; 084 /** Structured literal feature ordinal. */ 085 public static final int STRUCTURED_LITERAL = 10; 086 /** Pragma feature ordinal. */ 087 public static final int PRAGMA = 11; 088 /** Annotation feature ordinal. */ 089 public static final int ANNOTATION = 12; 090 /** Script feature ordinal. */ 091 public static final int SCRIPT = 13; 092 /** Lexical feature ordinal. */ 093 public static final int LEXICAL = 14; 094 /** Lexical shade feature ordinal. */ 095 public static final int LEXICAL_SHADE = 15; 096 097 /** 098 * Creates an all-features-enabled instance. 099 */ 100 public JexlFeatures() { 101 flags = (1L << LOCAL_VAR) 102 | (1L << SIDE_EFFECT) 103 | (1L << SIDE_EFFECT_GLOBAL) 104 | (1L << ARRAY_REF_EXPR) 105 | (1L << NEW_INSTANCE) 106 | (1L << LOOP) 107 | (1L << LAMBDA) 108 | (1L << METHOD_CALL) 109 | (1L << STRUCTURED_LITERAL) 110 | (1L << PRAGMA) 111 | (1L << ANNOTATION) 112 | (1L << SCRIPT); 113 reservedNames = Collections.emptySet(); 114 nameSpaces = TEST_STR_FALSE; 115 } 116 117 /** 118 * Copy constructor. 119 * @param features the feature to copy from 120 */ 121 public JexlFeatures(final JexlFeatures features) { 122 this.flags = features.flags; 123 this.reservedNames = features.reservedNames; 124 this.nameSpaces = features.nameSpaces; 125 } 126 127 @Override 128 public int hashCode() { //CSOFF: MagicNumber 129 int hash = 3; 130 hash = 53 * hash + (int) (this.flags ^ (this.flags >>> 32)); 131 hash = 53 * hash + (this.reservedNames != null ? this.reservedNames.hashCode() : 0); 132 return hash; 133 } 134 135 @Override 136 public boolean equals(final Object obj) { 137 if (this == obj) { 138 return true; 139 } 140 if (obj == null) { 141 return false; 142 } 143 if (getClass() != obj.getClass()) { 144 return false; 145 } 146 final JexlFeatures other = (JexlFeatures) obj; 147 if (this.flags != other.flags) { 148 return false; 149 } 150 if (!Objects.equals(this.reservedNames, other.reservedNames)) { 151 return false; 152 } 153 return true; 154 } 155 156 /** 157 * The text corresponding to a feature code. 158 * @param feature the feature number 159 * @return the feature name 160 */ 161 public static String stringify(final int feature) { 162 return feature >= 0 && feature < F_NAMES.length ? F_NAMES[feature] : "unsupported feature"; 163 } 164 165 /** 166 * Sets a collection of reserved names precluding those to be used as local variables or parameter names. 167 * @param names the names to reserve 168 * @return this features instance 169 */ 170 public JexlFeatures reservedNames(final Collection<String> names) { 171 if (names == null || names.isEmpty()) { 172 reservedNames = Collections.emptySet(); 173 } else { 174 reservedNames = Collections.unmodifiableSet(new TreeSet<String>(names)); 175 } 176 setFeature(RESERVED, !reservedNames.isEmpty()); 177 return this; 178 } 179 180 /** 181 * @return the (unmodifiable) set of reserved names. 182 */ 183 public Set<String> getReservedNames() { 184 return reservedNames; 185 } 186 187 /** 188 * Checks whether a name is reserved. 189 * @param name the name to check 190 * @return true if reserved, false otherwise 191 */ 192 public boolean isReservedName(final String name) { 193 return name != null && reservedNames.contains(name); 194 } 195 196 /** 197 * Sets a test to determine namespace declaration. 198 * @param names the name predicate 199 * @return this features instance 200 */ 201 public JexlFeatures namespaceTest(final Predicate<String> names) { 202 nameSpaces = names == null? TEST_STR_FALSE : names; 203 return this; 204 } 205 206 /** 207 * @return the declared namespaces test. 208 */ 209 public Predicate<String> namespaceTest() { 210 return nameSpaces; 211 } 212 213 /** 214 * Sets a feature flag. 215 * @param feature the feature ordinal 216 * @param flag turn-on, turn off 217 */ 218 private void setFeature(final int feature, final boolean flag) { 219 if (flag) { 220 flags |= (1 << feature); 221 } else { 222 flags &= ~(1L << feature); 223 } 224 } 225 226 /** 227 * Gets a feature flag value. 228 * @param feature feature ordinal 229 * @return true if on, false if off 230 */ 231 private boolean getFeature(final int feature) { 232 return (flags & (1L << feature)) != 0L; 233 } 234 235 /** 236 * Sets whether register are enabled. 237 * <p> 238 * This is mostly used internally during execution of JexlEngine.{g,s}etProperty. 239 * <p> 240 * When disabled, parsing a script/expression using the register syntax will throw a parsing exception. 241 * @param flag true to enable, false to disable 242 * @return this features instance 243 */ 244 public JexlFeatures register(final boolean flag) { 245 setFeature(REGISTER, flag); 246 return this; 247 } 248 249 /** 250 * @return true if register syntax is enabled 251 */ 252 public boolean supportsRegister() { 253 return getFeature(REGISTER); 254 } 255 256 /** 257 * Sets whether local variables are enabled. 258 * <p> 259 * When disabled, parsing a script/expression using a local variable or parameter syntax 260 * will throw a parsing exception. 261 * @param flag true to enable, false to disable 262 * @return this features instance 263 */ 264 public JexlFeatures localVar(final boolean flag) { 265 setFeature(LOCAL_VAR, flag); 266 return this; 267 } 268 269 /** 270 * @return true if local variables syntax is enabled 271 */ 272 public boolean supportsLocalVar() { 273 return getFeature(LOCAL_VAR); 274 } 275 276 /** 277 * Sets whether side effect expressions on global variables (aka non local) are enabled. 278 * <p> 279 * When disabled, parsing a script/expression using syntactical constructs modifying variables 280 * <em>including all potentially ant-ish variables</em> will throw a parsing exception. 281 * @param flag true to enable, false to disable 282 * @return this features instance 283 */ 284 public JexlFeatures sideEffectGlobal(final boolean flag) { 285 setFeature(SIDE_EFFECT_GLOBAL, flag); 286 return this; 287 } 288 289 /** 290 * @return true if global variables can be assigned 291 */ 292 public boolean supportsSideEffectGlobal() { 293 return getFeature(SIDE_EFFECT_GLOBAL); 294 } 295 296 /** 297 * Sets whether side effect expressions are enabled. 298 * <p> 299 * When disabled, parsing a script/expression using syntactical constructs modifying variables 300 * or members will throw a parsing exception. 301 * @param flag true to enable, false to disable 302 * @return this features instance 303 */ 304 public JexlFeatures sideEffect(final boolean flag) { 305 setFeature(SIDE_EFFECT, flag); 306 return this; 307 } 308 309 /** 310 * @return true if side effects are enabled, false otherwise 311 */ 312 public boolean supportsSideEffect() { 313 return getFeature(SIDE_EFFECT); 314 } 315 316 /** 317 * Sets whether array references expressions are enabled. 318 * <p> 319 * When disabled, parsing a script/expression using 'obj[ ref ]' where ref is not a string or integer literal 320 * will throw a parsing exception; 321 * @param flag true to enable, false to disable 322 * @return this features instance 323 */ 324 public JexlFeatures arrayReferenceExpr(final boolean flag) { 325 setFeature(ARRAY_REF_EXPR, flag); 326 return this; 327 } 328 329 /** 330 * @return true if array references can contain method call expressions, false otherwise 331 */ 332 public boolean supportsArrayReferenceExpr() { 333 return getFeature(ARRAY_REF_EXPR); 334 } 335 336 /** 337 * Sets whether method calls expressions are enabled. 338 * <p> 339 * When disabled, parsing a script/expression using 'obj.method()' 340 * will throw a parsing exception; 341 * @param flag true to enable, false to disable 342 * @return this features instance 343 */ 344 public JexlFeatures methodCall(final boolean flag) { 345 setFeature(METHOD_CALL, flag); 346 return this; 347 } 348 349 /** 350 * @return true if array references can contain expressions, false otherwise 351 */ 352 public boolean supportsMethodCall() { 353 return getFeature(METHOD_CALL); 354 } 355 356 /** 357 * Sets whether array/map/set literal expressions are enabled. 358 * <p> 359 * When disabled, parsing a script/expression creating one of these literals 360 * will throw a parsing exception; 361 * @param flag true to enable, false to disable 362 * @return this features instance 363 */ 364 public JexlFeatures structuredLiteral(final boolean flag) { 365 setFeature(STRUCTURED_LITERAL, flag); 366 return this; 367 } 368 369 /** 370 * @return true if array/map/set literal expressions are supported, false otherwise 371 */ 372 public boolean supportsStructuredLiteral() { 373 return getFeature(STRUCTURED_LITERAL); 374 } 375 376 /** 377 * Sets whether creating new instances is enabled. 378 * <p> 379 * When disabled, parsing a script/expression using 'new(...)' will throw a parsing exception; 380 * using a class as functor will fail at runtime. 381 * @param flag true to enable, false to disable 382 * @return this features instance 383 */ 384 public JexlFeatures newInstance(final boolean flag) { 385 setFeature(NEW_INSTANCE, flag); 386 return this; 387 } 388 389 /** 390 * @return true if creating new instances is enabled, false otherwise 391 */ 392 public boolean supportsNewInstance() { 393 return getFeature(NEW_INSTANCE); 394 } 395 396 /** 397 * Sets whether looping constructs are enabled. 398 * <p> 399 * When disabled, parsing a script/expression using syntactic looping constructs (for,while) 400 * will throw a parsing exception. 401 * @param flag true to enable, false to disable 402 * @return this features instance 403 */ 404 public JexlFeatures loops(final boolean flag) { 405 setFeature(LOOP, flag); 406 return this; 407 } 408 409 /** 410 * @return true if loops are enabled, false otherwise 411 */ 412 public boolean supportsLoops() { 413 return getFeature(LOOP); 414 } 415 416 /** 417 * Sets whether lambda/function constructs are enabled. 418 * <p> 419 * When disabled, parsing a script/expression using syntactic lambda constructs (->,function) 420 * will throw a parsing exception. 421 * @param flag true to enable, false to disable 422 * @return this features instance 423 */ 424 public JexlFeatures lambda(final boolean flag) { 425 setFeature(LAMBDA, flag); 426 return this; 427 } 428 429 /** 430 * @return true if lambda are enabled, false otherwise 431 */ 432 public boolean supportsLambda() { 433 return getFeature(LAMBDA); 434 } 435 436 /** 437 * Sets whether pragma constructs are enabled. 438 * <p> 439 * When disabled, parsing a script/expression using syntactic pragma constructs (#pragma) 440 * will throw a parsing exception. 441 * @param flag true to enable, false to disable 442 * @return this features instance 443 */ 444 public JexlFeatures pragma(final boolean flag) { 445 setFeature(PRAGMA, flag); 446 return this; 447 } 448 449 /** 450 * @return true if pragma are enabled, false otherwise 451 */ 452 public boolean supportsPragma() { 453 return getFeature(PRAGMA); 454 } 455 456 /** 457 * Sets whether annotation constructs are enabled. 458 * <p> 459 * When disabled, parsing a script/expression using syntactic annotation constructs (@annotation) 460 * will throw a parsing exception. 461 * @param flag true to enable, false to disable 462 * @return this features instance 463 */ 464 public JexlFeatures annotation(final boolean flag) { 465 setFeature(ANNOTATION, flag); 466 return this; 467 } 468 469 /** 470 * @return true if annotation are enabled, false otherwise 471 */ 472 public boolean supportsAnnotation() { 473 return getFeature(ANNOTATION); 474 } 475 476 /** 477 * Sets whether scripts constructs are enabled. 478 * <p> 479 * When disabled, parsing a script using syntactic script constructs (statements, ...) 480 * will throw a parsing exception. 481 * @param flag true to enable, false to disable 482 * @return this features instance 483 */ 484 public JexlFeatures script(final boolean flag) { 485 setFeature(SCRIPT, flag); 486 return this; 487 } 488 489 /** 490 * @return true if scripts are enabled, false otherwise 491 */ 492 public boolean supportsScript() { 493 return getFeature(SCRIPT); 494 } 495 496 /** 497 * 498 * @return true if expressions (aka not scripts) are enabled, false otherwise 499 */ 500 public boolean supportsExpression() { 501 return !getFeature(SCRIPT); 502 } 503 504 /** 505 * Sets whether syntactic lexical mode is enabled. 506 * 507 * @param flag true means syntactic lexical function scope is in effect, false implies non-lexical scoping 508 * @return this features instance 509 */ 510 public JexlFeatures lexical(final boolean flag) { 511 setFeature(LEXICAL, flag); 512 return this; 513 } 514 515 516 /** @return whether lexical scope feature is enabled */ 517 public boolean isLexical() { 518 return getFeature(LEXICAL); 519 } 520 521 /** 522 * Sets whether syntactic lexical shade is enabled. 523 * 524 * @param flag true means syntactic lexical shade is in effect and implies lexical scope 525 * @return this features instance 526 */ 527 public JexlFeatures lexicalShade(final boolean flag) { 528 setFeature(LEXICAL_SHADE, flag); 529 if (flag) { 530 setFeature(LEXICAL, true); 531 } 532 return this; 533 } 534 535 536 /** @return whether lexical shade feature is enabled */ 537 public boolean isLexicalShade() { 538 return getFeature(LEXICAL_SHADE); 539 } 540}