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>Pragma: pragma construct as in <code>#pragma x y</code> 045 * <li>Annotation: @annotation statement; 046 * <li>Thin-arrow: use the thin-arrow, ie <code>-></code> for lambdas as in <code>x -> x + x</code> 047 * <li>Fat-arrow: use the fat-arrow, ie <code>=></code> for lambdas as in <code>x => x + x</code> 048 * <li>Namespace pragma: whether the <code>#pragma jexl.namespace.ns namespace</code> syntax is allowed</li> 049 * <li>Import pragma: whether the <code>#pragma jexl.import fully.qualified.class.name</code> syntax is allowed</li> 050 * <li>Comparator names: whether the comparator operator names can be used (as in <code>gt</code> for >, 051 * <code>lt</code> for <, ...)</li> 052 * <li>Pragma anywhere: whether pragma, that are <em>not</em> statements and handled before execution begins, 053 * can appear anywhere in the source or before any statements - ie at the beginning of a script.</li> 054 * </ul> 055 * @since 3.2 056 */ 057public final class JexlFeatures { 058 /** The feature flags. */ 059 private long flags; 060 /** The set of reserved names, aka global variables that can not be masked by local variables or parameters. */ 061 private Set<String> reservedNames; 062 /** The namespace names. */ 063 private Predicate<String> nameSpaces; 064 /** The false predicate. */ 065 public static final Predicate<String> TEST_STR_FALSE = s->false; 066 /** Te feature names (for toString()). */ 067 private static final String[] F_NAMES = { 068 "register", "reserved variable", "local variable", "assign/modify", 069 "global assign/modify", "array reference", "create instance", "loop", "function", 070 "method call", "set/map/array literal", "pragma", "annotation", "script", "lexical", "lexicalShade", 071 "thin-arrow", "fat-arrow", "namespace pragma", "import pragma", "comparator names", "pragma anywhere" 072 }; 073 /** Registers feature ordinal. */ 074 private static final int REGISTER = 0; 075 /** Reserved name feature ordinal. */ 076 public static final int RESERVED = 1; 077 /** Locals feature ordinal. */ 078 public static final int LOCAL_VAR = 2; 079 /** Side effects feature ordinal. */ 080 public static final int SIDE_EFFECT = 3; 081 /** Global side-effects feature ordinal. */ 082 public static final int SIDE_EFFECT_GLOBAL = 4; 083 /** Array get is allowed on expr. */ 084 public static final int ARRAY_REF_EXPR = 5; 085 /** New-instance feature ordinal. */ 086 public static final int NEW_INSTANCE = 6; 087 /** Loops feature ordinal. */ 088 public static final int LOOP = 7; 089 /** Lambda feature ordinal. */ 090 public static final int LAMBDA = 8; 091 /** Lambda feature ordinal. */ 092 public static final int METHOD_CALL = 9; 093 /** Structured literal feature ordinal. */ 094 public static final int STRUCTURED_LITERAL = 10; 095 /** Pragma feature ordinal. */ 096 public static final int PRAGMA = 11; 097 /** Annotation feature ordinal. */ 098 public static final int ANNOTATION = 12; 099 /** Script feature ordinal. */ 100 public static final int SCRIPT = 13; 101 /** Lexical feature ordinal. */ 102 public static final int LEXICAL = 14; 103 /** Lexical shade feature ordinal. */ 104 public static final int LEXICAL_SHADE = 15; 105 /** Fat-arrow lambda syntax. */ 106 public static final int THIN_ARROW = 16; 107 /** Fat-arrow lambda syntax. */ 108 public static final int FAT_ARROW = 17; 109 /** Namespace pragma feature ordinal. */ 110 public static final int NS_PRAGMA = 18; 111 /** Import pragma feature ordinal. */ 112 public static final int IMPORT_PRAGMA = 19; 113 /** Comparator names (legacy) syntax. */ 114 public static final int COMPARATOR_NAMES = 20; 115 /** The pragma anywhere feature ordinal. */ 116 public static final int PRAGMA_ANYWHERE = 21; 117 /** 118 * The default features flag mask. 119 */ 120 private static final long DEFAULT_FEATURES = 121 (1L << LOCAL_VAR) 122 | (1L << SIDE_EFFECT) 123 | (1L << SIDE_EFFECT_GLOBAL) 124 | (1L << ARRAY_REF_EXPR) 125 | (1L << NEW_INSTANCE) 126 | (1L << LOOP) 127 | (1L << LAMBDA) 128 | (1L << METHOD_CALL) 129 | (1L << STRUCTURED_LITERAL) 130 | (1L << PRAGMA) 131 | (1L << ANNOTATION) 132 | (1L << SCRIPT) 133 | (1L << THIN_ARROW) 134 | (1L << NS_PRAGMA) 135 | (1L << IMPORT_PRAGMA) 136 | (1L << COMPARATOR_NAMES) 137 | (1L << PRAGMA_ANYWHERE); 138 139 /** 140 * Creates an all-features-enabled instance. 141 */ 142 public JexlFeatures() { 143 flags = DEFAULT_FEATURES; 144 reservedNames = Collections.emptySet(); 145 nameSpaces = TEST_STR_FALSE; 146 } 147 148 /** 149 * Copy constructor. 150 * @param features the feature to copy from 151 */ 152 public JexlFeatures(final JexlFeatures features) { 153 this.flags = features.flags; 154 this.reservedNames = features.reservedNames; 155 this.nameSpaces = features.nameSpaces; 156 } 157 158 @Override 159 public int hashCode() { //CSOFF: MagicNumber 160 int hash = 3; 161 hash = 53 * hash + (int) (this.flags ^ (this.flags >>> 32)); 162 hash = 53 * hash + (this.reservedNames != null ? this.reservedNames.hashCode() : 0); 163 return hash; 164 } 165 166 @Override 167 public boolean equals(final Object obj) { 168 if (this == obj) { 169 return true; 170 } 171 if (obj == null) { 172 return false; 173 } 174 if (getClass() != obj.getClass()) { 175 return false; 176 } 177 final JexlFeatures other = (JexlFeatures) obj; 178 if (this.flags != other.flags) { 179 return false; 180 } 181 if (!Objects.equals(this.reservedNames, other.reservedNames)) { 182 return false; 183 } 184 return true; 185 } 186 187 /** 188 * The text corresponding to a feature code. 189 * @param feature the feature number 190 * @return the feature name 191 */ 192 public static String stringify(final int feature) { 193 return feature >= 0 && feature < F_NAMES.length ? F_NAMES[feature] : "unsupported feature"; 194 } 195 196 /** 197 * Sets a collection of reserved names precluding those to be used as local variables or parameter names. 198 * @param names the names to reserve 199 * @return this features instance 200 */ 201 public JexlFeatures reservedNames(final Collection<String> names) { 202 if (names == null || names.isEmpty()) { 203 reservedNames = Collections.emptySet(); 204 } else { 205 reservedNames = Collections.unmodifiableSet(new TreeSet<>(names)); 206 } 207 setFeature(RESERVED, !reservedNames.isEmpty()); 208 return this; 209 } 210 211 /** 212 * @return the (unmodifiable) set of reserved names. 213 */ 214 public Set<String> getReservedNames() { 215 return reservedNames; 216 } 217 218 /** 219 * Checks whether a name is reserved. 220 * @param name the name to check 221 * @return true if reserved, false otherwise 222 */ 223 public boolean isReservedName(final String name) { 224 return name != null && reservedNames.contains(name); 225 } 226 227 /** 228 * Sets a test to determine namespace declaration. 229 * @param names the name predicate 230 * @return this features instance 231 */ 232 public JexlFeatures namespaceTest(final Predicate<String> names) { 233 nameSpaces = names == null? TEST_STR_FALSE : names; 234 return this; 235 } 236 237 /** 238 * @return the declared namespaces test. 239 */ 240 public Predicate<String> namespaceTest() { 241 return nameSpaces; 242 } 243 244 /** 245 * Sets a feature flag. 246 * @param feature the feature ordinal 247 * @param flag turn-on, turn off 248 */ 249 private void setFeature(final int feature, final boolean flag) { 250 if (flag) { 251 flags |= (1L << feature); 252 } else { 253 flags &= ~(1L << feature); 254 } 255 } 256 257 /** 258 * Gets a feature flag value. 259 * @param feature feature ordinal 260 * @return true if on, false if off 261 */ 262 private boolean getFeature(final int feature) { 263 return (flags & (1L << feature)) != 0L; 264 } 265 266 /** 267 * Sets whether register are enabled. 268 * <p> 269 * This is mostly used internally during execution of JexlEngine.{g,s}etProperty. 270 * <p> 271 * When disabled, parsing a script/expression using the register syntax will throw a parsing exception. 272 * @param flag true to enable, false to disable 273 * @return this features instance 274 */ 275 public JexlFeatures register(final boolean flag) { 276 setFeature(REGISTER, flag); 277 return this; 278 } 279 280 /** 281 * @return true if register syntax is enabled 282 */ 283 public boolean supportsRegister() { 284 return getFeature(REGISTER); 285 } 286 287 /** 288 * Sets whether local variables are enabled. 289 * <p> 290 * When disabled, parsing a script/expression using a local variable or parameter syntax 291 * will throw a parsing exception. 292 * @param flag true to enable, false to disable 293 * @return this features instance 294 */ 295 public JexlFeatures localVar(final boolean flag) { 296 setFeature(LOCAL_VAR, flag); 297 return this; 298 } 299 300 /** 301 * @return true if local variables syntax is enabled 302 */ 303 public boolean supportsLocalVar() { 304 return getFeature(LOCAL_VAR); 305 } 306 307 /** 308 * Sets whether side effect expressions on global variables (aka non-local) are enabled. 309 * <p> 310 * When disabled, parsing a script/expression using syntactical constructs modifying variables 311 * <em>including all potentially ant-ish variables</em> will throw a parsing exception. 312 * @param flag true to enable, false to disable 313 * @return this features instance 314 */ 315 public JexlFeatures sideEffectGlobal(final boolean flag) { 316 setFeature(SIDE_EFFECT_GLOBAL, flag); 317 return this; 318 } 319 320 /** 321 * @return true if global variables can be assigned 322 */ 323 public boolean supportsSideEffectGlobal() { 324 return getFeature(SIDE_EFFECT_GLOBAL); 325 } 326 327 /** 328 * Sets whether side effect expressions are enabled. 329 * <p> 330 * When disabled, parsing a script/expression using syntactical constructs modifying variables 331 * or members will throw a parsing exception. 332 * @param flag true to enable, false to disable 333 * @return this features instance 334 */ 335 public JexlFeatures sideEffect(final boolean flag) { 336 setFeature(SIDE_EFFECT, flag); 337 return this; 338 } 339 340 /** 341 * @return true if side effects are enabled, false otherwise 342 */ 343 public boolean supportsSideEffect() { 344 return getFeature(SIDE_EFFECT); 345 } 346 347 /** 348 * Sets whether array references expressions are enabled. 349 * <p> 350 * When disabled, parsing a script/expression using 'obj[ ref ]' where ref is not a string or integer literal 351 * will throw a parsing exception; 352 * @param flag true to enable, false to disable 353 * @return this features instance 354 */ 355 public JexlFeatures arrayReferenceExpr(final boolean flag) { 356 setFeature(ARRAY_REF_EXPR, flag); 357 return this; 358 } 359 360 /** 361 * @return true if array references can contain method call expressions, false otherwise 362 */ 363 public boolean supportsArrayReferenceExpr() { 364 return getFeature(ARRAY_REF_EXPR); 365 } 366 367 /** 368 * Sets whether method calls expressions are enabled. 369 * <p> 370 * When disabled, parsing a script/expression using 'obj.method()' 371 * will throw a parsing exception; 372 * @param flag true to enable, false to disable 373 * @return this features instance 374 */ 375 public JexlFeatures methodCall(final boolean flag) { 376 setFeature(METHOD_CALL, flag); 377 return this; 378 } 379 380 /** 381 * @return true if array references can contain expressions, false otherwise 382 */ 383 public boolean supportsMethodCall() { 384 return getFeature(METHOD_CALL); 385 } 386 387 /** 388 * Sets whether array/map/set literal expressions are enabled. 389 * <p> 390 * When disabled, parsing a script/expression creating one of these literals 391 * will throw a parsing exception; 392 * @param flag true to enable, false to disable 393 * @return this features instance 394 */ 395 public JexlFeatures structuredLiteral(final boolean flag) { 396 setFeature(STRUCTURED_LITERAL, flag); 397 return this; 398 } 399 400 /** 401 * @return true if array/map/set literal expressions are supported, false otherwise 402 */ 403 public boolean supportsStructuredLiteral() { 404 return getFeature(STRUCTURED_LITERAL); 405 } 406 407 /** 408 * Sets whether creating new instances is enabled. 409 * <p> 410 * When disabled, parsing a script/expression using 'new(...)' will throw a parsing exception; 411 * using a class as functor will fail at runtime. 412 * @param flag true to enable, false to disable 413 * @return this features instance 414 */ 415 public JexlFeatures newInstance(final boolean flag) { 416 setFeature(NEW_INSTANCE, flag); 417 return this; 418 } 419 420 /** 421 * @return true if creating new instances is enabled, false otherwise 422 */ 423 public boolean supportsNewInstance() { 424 return getFeature(NEW_INSTANCE); 425 } 426 427 /** 428 * Sets whether looping constructs are enabled. 429 * <p> 430 * When disabled, parsing a script/expression using syntactic looping constructs (for,while) 431 * will throw a parsing exception. 432 * @param flag true to enable, false to disable 433 * @return this features instance 434 */ 435 public JexlFeatures loops(final boolean flag) { 436 setFeature(LOOP, flag); 437 return this; 438 } 439 440 /** 441 * @return true if loops are enabled, false otherwise 442 */ 443 public boolean supportsLoops() { 444 return getFeature(LOOP); 445 } 446 447 /** 448 * Sets whether lambda/function constructs are enabled. 449 * <p> 450 * When disabled, parsing a script/expression using syntactic lambda constructs (->,function) 451 * will throw a parsing exception. 452 * @param flag true to enable, false to disable 453 * @return this features instance 454 */ 455 public JexlFeatures lambda(final boolean flag) { 456 setFeature(LAMBDA, flag); 457 return this; 458 } 459 460 /** 461 * @return true if lambda are enabled, false otherwise 462 */ 463 public boolean supportsLambda() { 464 return getFeature(LAMBDA); 465 } 466 467 /** 468 * Sets whether thin-arrow lambda syntax is enabled. 469 * <p> 470 * When disabled, parsing a script/expression using syntactic thin-arrow (-<) 471 * will throw a parsing exception. 472 * @param flag true to enable, false to disable 473 * @return this features instance 474 * @since 3.3 475 */ 476 public JexlFeatures thinArrow(final boolean flag) { 477 setFeature(THIN_ARROW, flag); 478 return this; 479 } 480 481 /** 482 * @return true if thin-arrow lambda syntax is enabled, false otherwise 483 * @since 3.3 484 */ 485 public boolean supportsThinArrow() { 486 return getFeature(THIN_ARROW); 487 } 488 489 /** 490 * Sets whether fat-arrow lambda syntax is enabled. 491 * <p> 492 * When disabled, parsing a script/expression using syntactic fat-arrow (=<) 493 * will throw a parsing exception. 494 * @param flag true to enable, false to disable 495 * @return this features instance 496 * @since 3.3 497 */ 498 public JexlFeatures fatArrow(final boolean flag) { 499 setFeature(FAT_ARROW, flag); 500 return this; 501 } 502 503 /** 504 * @return true if fat-arrow lambda syntax is enabled, false otherwise 505 * @since 3.3 506 */ 507 public boolean supportsFatArrow() { 508 return getFeature(FAT_ARROW); 509 } 510 511 /** 512 * Sets whether the legacy comparison operator names syntax is enabled. 513 * <p> 514 * When disabled, comparison operators names (eq;ne;le;lt;ge;gt) 515 * will be treated as plain identifiers. 516 * @param flag true to enable, false to disable 517 * @return this features instance 518 * @since 3.3 519 */ 520 public JexlFeatures comparatorNames(final boolean flag) { 521 setFeature(COMPARATOR_NAMES, flag); 522 return this; 523 } 524 525 /** 526 * @return true if legacy comparison operator names syntax is enabled, false otherwise 527 * @since 3.3 528 */ 529 public boolean supportsComparatorNames() { 530 return getFeature(COMPARATOR_NAMES); 531 } 532 533 /** 534 * Sets whether pragma constructs are enabled. 535 * <p> 536 * When disabled, parsing a script/expression using syntactic pragma constructs (#pragma) 537 * will throw a parsing exception. 538 * @param flag true to enable, false to disable 539 * @return this features instance 540 */ 541 public JexlFeatures pragma(final boolean flag) { 542 setFeature(PRAGMA, flag); 543 if (!flag) { 544 setFeature(NS_PRAGMA, false); 545 setFeature(IMPORT_PRAGMA, false); 546 } 547 return this; 548 } 549 550 /** 551 * @return true if namespace pragma are enabled, false otherwise 552 */ 553 public boolean supportsPragma() { 554 return getFeature(PRAGMA); 555 } 556 557 /** 558 * Sets whether pragma constructs can appear anywhere in the code. 559 * <p> 560 * @param flag true to enable, false to disable 561 * @return this features instance 562 * @since 3.3 563 */ 564 public JexlFeatures pragmaAnywhere(final boolean flag) { 565 setFeature(PRAGMA_ANYWHERE, flag); 566 return this; 567 } 568 569 /** 570 * @return true if pragma constructs can appear anywhere in the code, false otherwise 571 * @since 3.3 572 */ 573 public boolean supportsPragmaAnywhere() { 574 return getFeature(PRAGMA_ANYWHERE); 575 } 576 577 /** 578 * Sets whether namespace pragma constructs are enabled. 579 * <p> 580 * When disabled, parsing a script/expression using syntactic namespace pragma constructs 581 * (#pragma jexl.namespace....) will throw a parsing exception. 582 * @param flag true to enable, false to disable 583 * @return this features instance 584 * @since 3.3 585 */ 586 public JexlFeatures namespacePragma(final boolean flag) { 587 setFeature(NS_PRAGMA, flag); 588 return this; 589 } 590 591 /** 592 * @return true if namespace pragma are enabled, false otherwise 593 * @since 3.3 594 */ 595 public boolean supportsNamespacePragma() { 596 return getFeature(NS_PRAGMA); 597 } 598 /** 599 * Sets whether import pragma constructs are enabled. 600 * <p> 601 * When disabled, parsing a script/expression using syntactic import pragma constructs 602 * (#pragma jexl.import....) will throw a parsing exception. 603 * @param flag true to enable, false to disable 604 * @return this features instance 605 * @since 3.3 606 */ 607 public JexlFeatures importPragma(final boolean flag) { 608 setFeature(IMPORT_PRAGMA, flag); 609 return this; 610 } 611 612 /** 613 * @return true if import pragma are enabled, false otherwise 614 * @since 3.3 615 */ 616 public boolean supportsImportPragma() { 617 return getFeature(IMPORT_PRAGMA); 618 } 619 620 621 /** 622 * Sets whether annotation constructs are enabled. 623 * <p> 624 * When disabled, parsing a script/expression using syntactic annotation constructs (@annotation) 625 * will throw a parsing exception. 626 * @param flag true to enable, false to disable 627 * @return this features instance 628 */ 629 public JexlFeatures annotation(final boolean flag) { 630 setFeature(ANNOTATION, flag); 631 return this; 632 } 633 634 /** 635 * @return true if annotation are enabled, false otherwise 636 */ 637 public boolean supportsAnnotation() { 638 return getFeature(ANNOTATION); 639 } 640 641 /** 642 * Sets whether scripts constructs are enabled. 643 * <p> 644 * When disabled, parsing a script using syntactic script constructs (statements, ...) 645 * will throw a parsing exception. 646 * @param flag true to enable, false to disable 647 * @return this features instance 648 */ 649 public JexlFeatures script(final boolean flag) { 650 setFeature(SCRIPT, flag); 651 return this; 652 } 653 654 /** 655 * @return true if scripts are enabled, false otherwise 656 */ 657 public boolean supportsScript() { 658 return getFeature(SCRIPT); 659 } 660 661 /** 662 * 663 * @return true if expressions (aka not scripts) are enabled, false otherwise 664 */ 665 public boolean supportsExpression() { 666 return !getFeature(SCRIPT); 667 } 668 669 /** 670 * Sets whether syntactic lexical mode is enabled. 671 * 672 * @param flag true means syntactic lexical function scope is in effect, false implies non-lexical scoping 673 * @return this features instance 674 */ 675 public JexlFeatures lexical(final boolean flag) { 676 setFeature(LEXICAL, flag); 677 return this; 678 } 679 680 681 /** @return whether lexical scope feature is enabled */ 682 public boolean isLexical() { 683 return getFeature(LEXICAL); 684 } 685 686 /** 687 * Sets whether syntactic lexical shade is enabled. 688 * 689 * @param flag true means syntactic lexical shade is in effect and implies lexical scope 690 * @return this features instance 691 */ 692 public JexlFeatures lexicalShade(final boolean flag) { 693 setFeature(LEXICAL_SHADE, flag); 694 if (flag) { 695 setFeature(LEXICAL, true); 696 } 697 return this; 698 } 699 700 701 /** @return whether lexical shade feature is enabled */ 702 public boolean isLexicalShade() { 703 return getFeature(LEXICAL_SHADE); 704 } 705}