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 * https://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.Arrays; 020import java.util.Collection; 021import java.util.Collections; 022import java.util.HashSet; 023import java.util.Objects; 024import java.util.Set; 025import java.util.TreeSet; 026import java.util.function.Predicate; 027 028/** 029 * A set of language feature options. 030 * <p> 031 * These control <em>syntactical</em> constructs that will throw JexlException.Feature exceptions (a 032 * subclass of JexlException.Parsing) when disabled. 033 * </p> 034 * <p>It is recommended to be explicit in choosing the features you need rather than rely on the default 035 * constructor: the 2 convenience methods {@link JexlFeatures#createNone()} and {@link JexlFeatures#createAll()} 036 * are the recommended starting points to selectively enable or disable chosen features.</p> 037 * <ul> 038 * <li>Registers: register syntax (#number), used internally for {g,s}etProperty</li> 039 * <li>Reserved Names: a set of reserved variable names that cannot be used as local variable (or parameter) names</li> 040 * <li>Global Side Effect : assigning/modifying values on global variables (=, += , -=, ...)</li> 041 * <li>Lexical: lexical scope, prevents redefining local variables</li> 042 * <li>Lexical Shade: local variables shade globals, prevents confusing a global variable with a local one</li> 043 * <li>Side Effect : assigning/modifying values on any variables or left-value</li> 044 * <li>Constant Array Reference: ensures array references only use constants;they should be statically solvable.</li> 045 * <li>New Instance: creating an instance using new(...)</li> 046 * <li>Loops: loop constructs (while(true), for(...))</li> 047 * <li>Lambda: function definitions (()->{...}, function(...) ).</li> 048 * <li>Method calls: calling methods (obj.method(...) or obj['method'](...)); when disabled, leaves function calls 049 * - including namespace prefixes - available</li> 050 * <li>Structured literals: arrays, lists, maps, sets, ranges</li> 051 * <li>Pragma: pragma construct as in {@code #pragma x y}</li> 052 * <li>Annotation: @annotation statement;</li> 053 * <li>Thin-arrow: use the thin-arrow, ie {@code ->} for lambdas as in {@code x -> x + x}</li> 054 * <li>Fat-arrow: use the fat-arrow, ie {@code =>} for lambdas as in {@code x => x + x} 055 * <li>Namespace pragma: whether the {@code #pragma jexl.namespace.ns namespace} syntax is allowed</li> 056 * <li>Namespace identifier: whether the {@code ns:fun(...)} parser treats the ns:fun as one identifier, no spaces allowed</li> 057 * <li>Import pragma: whether the {@code #pragma jexl.import fully.qualified.class.name} syntax is allowed</li> 058 * <li>Comparator names: whether the comparator operator names can be used (as in {@code gt} for >, 059 * {@code lt} for <, ...)</li> 060 * <li>Pragma anywhere: whether pragma, that are <em>not</em> statements and handled before execution begins, 061 * can appear anywhere in the source or before any statements - ie at the beginning of a script.</li> 062 * <li>Const Capture: whether variables captured by lambdas are read-only (aka const, same as Java) or read-write.</li> 063 * <li>Reference Capture: whether variables captured by lambdas are pass-by-reference or pass-by-value.</li> 064 * </ul> 065 * 066 * @since 3.2 067 */ 068public final class JexlFeatures { 069 070 /** The false predicate. */ 071 public static final Predicate<String> TEST_STR_FALSE = s -> false; 072 073 /** 074 * The true predicate. 075 * 076 * @since 3.7.1 077 */ 078 public static final Predicate<String> TEST_STR_TRUE = s -> true; 079 080 /** The feature names (for toString()). */ 081 private static final String[] F_NAMES = { 082 "register", "reserved variable", "local variable", "assign/modify", 083 "global assign/modify", "array reference", "create instance", "loop", "function", 084 "method call", "set/map/array literal", "pragma", "annotation", "script", "lexical", "lexicalShade", 085 "thin-arrow", "fat-arrow", "namespace pragma", "namespace identifier", "import pragma", "comparator names", "pragma anywhere", 086 "const capture", "ref capture", "ambiguous statement", "ignore template prefix", "namespace instantiation" 087 }; 088 089 /** Registers feature ordinal. */ 090 private static final int REGISTER = 0; 091 092 /** Reserved future feature ordinal (unused as of 3.3.1). */ 093 public static final int RESERVED = 1; 094 095 /** Locals feature ordinal. */ 096 public static final int LOCAL_VAR = 2; 097 098 /** Side effects feature ordinal. */ 099 public static final int SIDE_EFFECT = 3; 100 101 /** Global side effects feature ordinal. */ 102 public static final int SIDE_EFFECT_GLOBAL = 4; 103 104 /** Expressions allowed in array reference ordinal. */ 105 public static final int ARRAY_REF_EXPR = 5; 106 107 /** New-instance feature ordinal. */ 108 public static final int NEW_INSTANCE = 6; 109 110 /** Loops feature ordinal. */ 111 public static final int LOOP = 7; 112 113 /** Lambda feature ordinal. */ 114 public static final int LAMBDA = 8; 115 116 /** Lambda feature ordinal. */ 117 public static final int METHOD_CALL = 9; 118 119 /** Structured literal feature ordinal. */ 120 public static final int STRUCTURED_LITERAL = 10; 121 122 /** Pragma feature ordinal. */ 123 public static final int PRAGMA = 11; 124 125 /** Annotation feature ordinal. */ 126 public static final int ANNOTATION = 12; 127 128 /** Script feature ordinal. */ 129 public static final int SCRIPT = 13; 130 131 /** Lexical feature ordinal. */ 132 public static final int LEXICAL = 14; 133 134 /** Lexical shade feature ordinal. */ 135 public static final int LEXICAL_SHADE = 15; 136 137 /** Thin-arrow lambda syntax. */ 138 public static final int THIN_ARROW = 16; 139 140 /** Fat-arrow lambda syntax. */ 141 public static final int FAT_ARROW = 17; 142 143 /** Namespace pragma feature ordinal. */ 144 public static final int NS_PRAGMA = 18; 145 146 /** Namespace syntax as an identifier (no space). */ 147 public static final int NS_IDENTIFIER = 19; 148 149 /** Import pragma feature ordinal. */ 150 public static final int IMPORT_PRAGMA = 20; 151 152 /** Comparator names (legacy) syntax. */ 153 public static final int COMPARATOR_NAMES = 21; 154 155 /** The pragma anywhere feature ordinal. */ 156 public static final int PRAGMA_ANYWHERE = 22; 157 158 /** Captured variables are const. */ 159 public static final int CONST_CAPTURE = 23; 160 161 /** Captured variables are reference. */ 162 public static final int REF_CAPTURE = 24; 163 164 /** Ambiguous or strict statement allowed. */ 165 public static final int AMBIGUOUS_STATEMENT = 25; 166 167 /** Ignore template prefix. */ 168 public static final int IGNORE_TEMPLATE_PREFIX = 26; 169 170 /** Bad naming, use AMBIGUOUS_STATEMENT. 171 * @deprecated 3.6 172 */ 173 @Deprecated 174 public static final int STRICT_STATEMENT = 25; 175 176 /** Allow reflective auto-instantiation of a namespace functor from a class/FQCN binding. */ 177 public static final int NAMESPACE_INSTANTIATE = 27; 178 179 /** 180 * All features. 181 * Ensure this is updated if additional features are added. 182 */ 183 private static final long ALL_FEATURES = (1L << NAMESPACE_INSTANTIATE + 1) - 1L; // MUST REMAIN PRIVATE 184 185 /** 186 * The default features flag mask. 187 * <p>Meant for compatibility with scripts written before 3.3.1</p> 188 */ 189 private static final long DEFAULT_FEATURES = // MUST REMAIN PRIVATE 190 1L << LOCAL_VAR 191 | 1L << SIDE_EFFECT 192 | 1L << SIDE_EFFECT_GLOBAL 193 | 1L << ARRAY_REF_EXPR 194 | 1L << NEW_INSTANCE 195 | 1L << LOOP 196 | 1L << LAMBDA 197 | 1L << METHOD_CALL 198 | 1L << STRUCTURED_LITERAL 199 | 1L << PRAGMA 200 | 1L << ANNOTATION 201 | 1L << SCRIPT 202 | 1L << THIN_ARROW 203 | 1L << NS_PRAGMA 204 | 1L << IMPORT_PRAGMA 205 | 1L << COMPARATOR_NAMES 206 | 1L << PRAGMA_ANYWHERE 207 | 1L << NAMESPACE_INSTANTIATE; 208 209 /** 210 * The canonical scripting (since 3.3.1) features flag mask based on the original default. 211 * <p>Adds lexical, lexical-shade and const-capture but removes comparator-names and pragma-anywhere</p> 212 */ 213 private static final long SCRIPT_FEATURES = // MUST REMAIN PRIVATE 214 (DEFAULT_FEATURES 215 | 1L << LEXICAL 216 | 1L << LEXICAL_SHADE 217 | 1L << CONST_CAPTURE) // these parentheses are necessary :-) 218 & ~(1L << COMPARATOR_NAMES) 219 & ~(1L << PRAGMA_ANYWHERE); 220 221 /** 222 * Protected future syntactic elements. 223 * <p><em>class, jexl, $jexl</em></p> 224 * 225 * @since 3.3.1 226 */ 227 private static final Set<String> RESERVED_WORDS = 228 Collections.unmodifiableSet(new HashSet<>(Arrays.asList("class", "jexl", "$jexl"))); 229 230 /* 231 * *WARNING* 232 * Static fields may be inlined by the Java compiler, so their _values_ effectively form part of the external API. 233 * Classes that reference them need to be recompiled to pick up new values. 234 * This means that changes in value are not binary compatible. 235 * Such fields must be private or problems may occur. 236 */ 237 238 /** 239 * Creates an all features enabled set. 240 * 241 * @return A new instance of all features set 242 * @since 3.3.1 243 */ 244 public static JexlFeatures createAll() { 245 return new JexlFeatures(ALL_FEATURES, null, null); 246 } 247 248 /** 249 * Creates a default features set suitable for basic but complete scripting needs. 250 * <p>Maximizes compatibility with older version scripts (before 3.3), new projects should 251 * use {@link JexlFeatures#createScript()} or equivalent features as a base.</p> 252 * <p>The following scripting features are enabled:</p> 253 * <ul> 254 * <li>local variable, {@link JexlFeatures#supportsLocalVar()}</li> 255 * <li>side effect, {@link JexlFeatures#supportsSideEffect()}</li> 256 * <li>global side effect, {@link JexlFeatures#supportsSideEffectGlobal()}</li> 257 * <li>array reference expression, {@link JexlFeatures#supportsStructuredLiteral()}</li> 258 * <li>new instance, {@link JexlFeatures#supportsNewInstance()} </li> 259 * <li>loop, {@link JexlFeatures#supportsLoops()}</li> 260 * <li>lambda, {@link JexlFeatures#supportsLambda()}</li> 261 * <li>method call, {@link JexlFeatures#supportsMethodCall()}</li> 262 * <li>structured literal, {@link JexlFeatures#supportsStructuredLiteral()}</li> 263 * <li>pragma, {@link JexlFeatures#supportsPragma()}</li> 264 * <li>annotation, {@link JexlFeatures#supportsAnnotation()}</li> 265 * <li>script, {@link JexlFeatures#supportsScript()}</li> 266 * <li>comparator names, {@link JexlFeatures#supportsComparatorNames()}</li> 267 * <li>namespace pragma, {@link JexlFeatures#supportsNamespacePragma()}</li> 268 * <li>import pragma, {@link JexlFeatures#supportsImportPragma()}</li> 269 * <li>pragma anywhere, {@link JexlFeatures#supportsPragmaAnywhere()}</li> 270 * </ul> 271 * 272 * @return A new instance of a default scripting features set 273 * @since 3.3.1 274 */ 275 public static JexlFeatures createDefault() { 276 return new JexlFeatures(DEFAULT_FEATURES, null, null); 277 } 278 279 /** 280 * Creates an empty feature set. 281 * <p>This is the strictest base-set since no feature is allowed, suitable as-is only 282 * for the simplest expressions.</p> 283 * 284 * @return A new instance of an empty features set 285 * @since 3.3.1 286 */ 287 public static JexlFeatures createNone() { 288 return new JexlFeatures(0L, null, null); 289 } 290 291 /** 292 * The modern scripting features set. 293 * <p>This is the recommended set for new projects.</p> 294 * <p>All default features with the following differences:</p> 295 * <ul> 296 * <li><em>disable</em> pragma-anywhere, {@link JexlFeatures#supportsPragmaAnywhere()}</li> 297 * <li><em>disable</em> comparator-names, {@link JexlFeatures#supportsComparatorNames()}</li> 298 * <li><em>enable</em> lexical, {@link JexlFeatures#isLexical()}</li> 299 * <li><em>enable</em> lexical-shade, {@link JexlFeatures#isLexicalShade()} </li> 300 * <li><em>enable</em> const-capture, {@link JexlFeatures#supportsConstCapture()}</li> 301 * </ul> 302 * <p>It also adds a set of reserved words to enable future unencumbered syntax evolution: 303 * <em>try, catch, throw, finally, switch, case, default, class, instanceof</em> 304 * </p> 305 * 306 * @return A new instance of a modern scripting features set 307 * @since 3.3.1 308 */ 309 public static JexlFeatures createScript() { 310 return new JexlFeatures(SCRIPT_FEATURES, RESERVED_WORDS, null); 311 } 312 313 /** 314 * The text corresponding to a feature code. 315 * 316 * @param feature The feature number 317 * @return The feature name 318 */ 319 public static String stringify(final int feature) { 320 return feature >= 0 && feature < F_NAMES.length ? F_NAMES[feature] : "unsupported feature"; 321 } 322 323 /** The feature flags. */ 324 private long flags; 325 326 /** The set of reserved names, aka global variables that cannot be masked by local variables or parameters. */ 327 private Set<String> reservedNames; 328 329 /** The namespace names. */ 330 private Predicate<String> nameSpaces; 331 332 /** The allowed pragma keys. */ 333 private Predicate<String> pragmas = TEST_STR_TRUE; 334 335 /** 336 * Creates default instance, equivalent to the result of calling the preferred alternative 337 * {@link JexlFeatures#createDefault()} 338 */ 339 public JexlFeatures() { 340 this(DEFAULT_FEATURES, null, null); 341 } 342 343 /** 344 * Copy constructor. 345 * 346 * @param features The feature to copy from 347 */ 348 public JexlFeatures(final JexlFeatures features) { 349 this(features.flags, features.reservedNames, features.nameSpaces); 350 this.pragmas = features.pragmas; 351 } 352 353 /** 354 * An all member constructor for derivation. 355 * <p>Not respecting immutability or thread-safety constraints for this class constructor arguments will 356 * likely result in unexpected behavior.</p> 357 * 358 * @param f flag 359 * @param r reserved variable names; must be an immutable Set or thread-safe (concurrent or synchronized set) 360 * @param n namespace predicate; must be stateless or thread-safe 361 */ 362 protected JexlFeatures(final long f, final Set<String> r, final Predicate<String> n) { 363 this.flags = f; 364 this.reservedNames = r == null? Collections.emptySet() : r; 365 this.nameSpaces = n == null? TEST_STR_FALSE : n; 366 } 367 368 /** 369 * Sets whether annotation constructs are enabled. 370 * <p> 371 * When disabled, parsing a script/expression using syntactic annotation constructs (@annotation) 372 * will throw a parsing exception. 373 * </p> 374 * 375 * @param flag true to enable, false to disable 376 * @return this features instance 377 */ 378 public JexlFeatures annotation(final boolean flag) { 379 setFeature(ANNOTATION, flag); 380 return this; 381 } 382 383 /** 384 * Sets whether array references expressions are enabled. 385 * <p> 386 * When disabled, parsing a script/expression using 'obj[ ref ]' where ref is not a string or integer literal 387 * will throw a parsing exception; 388 * </p> 389 * 390 * @param flag true to enable, false to disable 391 * @return this features instance 392 */ 393 public JexlFeatures arrayReferenceExpr(final boolean flag) { 394 setFeature(ARRAY_REF_EXPR, flag); 395 return this; 396 } 397 398 /** 399 * Sets whether the legacy comparison operator names syntax is enabled. 400 * <p> 401 * When disabled, comparison operators names (eq;ne;le;lt;ge;gt) 402 * will be treated as plain identifiers. 403 * </p> 404 * 405 * @param flag true to enable, false to disable 406 * @return this features instance 407 * @since 3.3 408 */ 409 public JexlFeatures comparatorNames(final boolean flag) { 410 setFeature(COMPARATOR_NAMES, flag); 411 return this; 412 } 413 414 /** 415 * Sets whether lambda captured-variables are constant or mutable. 416 * <p> 417 * When disabled, lambda-captured variables are implicitly converted to read-write local variable (let), 418 * when enabled, those are implicitly converted to read-only local variables (const). 419 * </p> 420 * 421 * @param flag true to enable, false to disable 422 * @return this features instance 423 */ 424 public JexlFeatures constCapture(final boolean flag) { 425 setFeature(CONST_CAPTURE, flag); 426 return this; 427 } 428 429 /** 430 * Sets whether lambda captured-variables are references or values. 431 * <p>When variables are pass-by-reference, side effects are visible from inner lexical scopes 432 * to outer-scope.</p> 433 * <p> 434 * When disabled, lambda-captured variables use pass-by-value semantic, 435 * when enabled, those use pass-by-reference semantic. 436 * </p> 437 * 438 * @param flag true to enable, false to disable 439 * @return this features instance 440 */ 441 public JexlFeatures referenceCapture(final boolean flag) { 442 setFeature(REF_CAPTURE, flag); 443 return this; 444 } 445 446 /** 447 * Sets whether a namespace bound to a class or fully-qualified class name may be 448 * reflectively auto-instantiated into a functor. 449 * <p> 450 * When enabled (the default), a namespace declared as a {@link Class} or class-name 451 * {@link String} is instantiated - through a constructor, optionally receiving the 452 * context - the first time it is used, allowing instance (non-static) namespace methods. 453 * When disabled, no constructor is invoked: only static methods of the bound class are 454 * reachable and explicit host-supplied functors are still honored. 455 * </p> 456 * 457 * @param flag true to enable, false to disable 458 * @return this features instance 459 * @since 3.7.1 460 */ 461 public JexlFeatures namespaceInstantiation(final boolean flag) { 462 setFeature(NAMESPACE_INSTANTIATE, flag); 463 return this; 464 } 465 466 @Override 467 public boolean equals(final Object obj) { 468 if (this == obj) { 469 return true; 470 } 471 if (obj == null) { 472 return false; 473 } 474 if (getClass() != obj.getClass()) { 475 return false; 476 } 477 final JexlFeatures other = (JexlFeatures) obj; 478 if (this.flags != other.flags) { 479 return false; 480 } 481 if (this.nameSpaces != other.nameSpaces) { 482 return false; 483 } 484 if (!this.pragmas.equals(other.pragmas)) { 485 return false; 486 } 487 if (!Objects.equals(this.reservedNames, other.reservedNames)) { 488 return false; 489 } 490 return true; 491 } 492 493 /** 494 * Sets whether fat-arrow lambda syntax is enabled. 495 * <p> 496 * When disabled, parsing a script/expression using syntactic fat-arrow (=<) 497 * will throw a parsing exception. 498 * </p> 499 * 500 * @param flag true to enable, false to disable 501 * @return this features instance 502 * @since 3.3 503 */ 504 public JexlFeatures fatArrow(final boolean flag) { 505 setFeature(FAT_ARROW, flag); 506 return this; 507 } 508 509 /** 510 * Gets a feature flag value. 511 * 512 * @param feature feature ordinal 513 * @return true if on, false if off 514 */ 515 private boolean getFeature(final int feature) { 516 return (flags & 1L << feature) != 0L; 517 } 518 519 /** 520 * Gets the feature flags 521 * 522 * @return these features"s flags 523 */ 524 public long getFlags() { 525 return flags; 526 } 527 528 /** 529 * Gets the immutable set of reserved names. 530 * 531 * @return The (unmodifiable) set of reserved names. 532 */ 533 public Set<String> getReservedNames() { 534 return reservedNames; 535 } 536 537 @Override 538 public int hashCode() { //CSOFF: MagicNumber 539 int hash = 3; 540 hash = 53 * hash + Long.hashCode(this.flags); 541 hash = 53 * hash + (this.reservedNames != null ? this.reservedNames.hashCode() : 0); 542 hash = 53 * hash + this.pragmas.hashCode(); 543 return hash; 544 } 545 546 /** 547 * Sets whether to ignore template prefixes in template expressions. 548 * <p>Note that this only precludes using a variable whose name is exactly the template prefix 549 * (for example, with the default prefix "$$", a variable named "$$" is ignored, but "$", "$a", or "$$$" 550 * are still valid variable names).</p> 551 * @param flag true to enable, false to disable 552 * @return this features instance 553 * @since 3.6.3 554 */ 555 public JexlFeatures ignoreTemplatePrefix(final boolean flag) { 556 setFeature(IGNORE_TEMPLATE_PREFIX, flag); 557 return this; 558 } 559 560 /** 561 * Gets whether to ignore template prefixes in template expressions. 562 * @return true if enabled, false otherwise 563 * @since 3.6.3 564 */ 565 public boolean isIgnoreTemplatePrefix() { 566 return getFeature(IGNORE_TEMPLATE_PREFIX); 567 } 568 569 /** 570 * Sets whether import pragma constructs are enabled. 571 * <p> 572 * When disabled, parsing a script/expression using syntactic import pragma constructs 573 * (#pragma jexl.import....) will throw a parsing exception. 574 * </p> 575 * 576 * @param flag true to enable, false to disable 577 * @return this features instance 578 * @since 3.3 579 */ 580 public JexlFeatures importPragma(final boolean flag) { 581 setFeature(IMPORT_PRAGMA, flag); 582 return this; 583 } 584 585 /** 586 * Is the lexical scope feature enabled? 587 * 588 * @return whether lexical scope feature is enabled */ 589 public boolean isLexical() { 590 return getFeature(LEXICAL); 591 } 592 593 /** 594 * Is the lexical shade feature enabled? 595 * 596 * @return whether lexical shade feature is enabled */ 597 public boolean isLexicalShade() { 598 return getFeature(LEXICAL_SHADE); 599 } 600 601 /** 602 * Checks whether a name is reserved. 603 * 604 * @param name The name to check 605 * @return true if reserved, false otherwise 606 */ 607 public boolean isReservedName(final String name) { 608 return name != null && reservedNames.contains(name); 609 } 610 611 /** 612 * Sets whether lambda/function constructs are enabled. 613 * <p> 614 * When disabled, parsing a script/expression using syntactic lambda constructs (->,function) 615 * will throw a parsing exception. 616 * </p> 617 * 618 * @param flag true to enable, false to disable 619 * @return this features instance 620 */ 621 public JexlFeatures lambda(final boolean flag) { 622 setFeature(LAMBDA, flag); 623 return this; 624 } 625 626 /** 627 * Sets whether syntactic lexical mode is enabled. 628 * 629 * @param flag true means syntactic lexical function scope is in effect, false implies non-lexical scoping 630 * @return this features instance 631 */ 632 public JexlFeatures lexical(final boolean flag) { 633 setFeature(LEXICAL, flag); 634 if (!flag) { 635 setFeature(LEXICAL_SHADE, false); 636 } 637 return this; 638 } 639 640 /** 641 * Sets whether syntactic lexical shade is enabled. 642 * 643 * @param flag true means syntactic lexical shade is in effect and implies lexical scope 644 * @return this features instance 645 */ 646 public JexlFeatures lexicalShade(final boolean flag) { 647 setFeature(LEXICAL_SHADE, flag); 648 if (flag) { 649 setFeature(LEXICAL, true); 650 } 651 return this; 652 } 653 654 /** 655 * Sets whether local variables are enabled. 656 * <p> 657 * When disabled, parsing a script/expression using a local variable or parameter syntax 658 * will throw a parsing exception. 659 * </p> 660 * 661 * @param flag true to enable, false to disable 662 * @return this features instance 663 */ 664 public JexlFeatures localVar(final boolean flag) { 665 setFeature(LOCAL_VAR, flag); 666 return this; 667 } 668 669 /** 670 * Sets whether looping constructs are enabled. 671 * <p> 672 * When disabled, parsing a script/expression using syntactic looping constructs (for, while) 673 * will throw a parsing exception. 674 * </p> 675 * 676 * @param flag true to enable, false to disable 677 * @return this features instance 678 */ 679 public JexlFeatures loops(final boolean flag) { 680 setFeature(LOOP, flag); 681 return this; 682 } 683 684 /** 685 * Sets whether method calls expressions are enabled. 686 * <p> 687 * When disabled, parsing a script/expression using 'obj.method()' 688 * will throw a parsing exception; 689 * </p> 690 * 691 * @param flag true to enable, false to disable 692 * @return this features instance 693 */ 694 public JexlFeatures methodCall(final boolean flag) { 695 setFeature(METHOD_CALL, flag); 696 return this; 697 } 698 699 /** 700 * Sets whether namespace pragma constructs are enabled. 701 * <p> 702 * When disabled, parsing a script/expression using syntactic namespace pragma constructs 703 * (#pragma jexl.namespace....) will throw a parsing exception. 704 * </p> 705 * 706 * @param flag true to enable, false to disable 707 * @return this features instance 708 * @since 3.3 709 */ 710 public JexlFeatures namespacePragma(final boolean flag) { 711 setFeature(NS_PRAGMA, flag); 712 return this; 713 } 714 715 /** 716 * Sets whether namespace as identifier syntax is enabled. 717 * <p> 718 * When enabled, a namespace call must be of the form <code>ns:fun(...)</code> with no 719 * spaces between the namespace name and the function. 720 * </p> 721 * 722 * @param flag true to enable, false to disable 723 * @return this features instance 724 * @since 3.5.0 725 */ 726 public JexlFeatures namespaceIdentifier(final boolean flag) { 727 setFeature(NS_IDENTIFIER, flag); 728 return this; 729 } 730 731 /** 732 * Gets the declared namespaces test. 733 * 734 * @return The declared namespaces test. 735 */ 736 public Predicate<String> namespaceTest() { 737 return nameSpaces; 738 } 739 740 /** 741 * Sets a test to determine namespace declaration. 742 * 743 * @param names The name predicate 744 * @return this features instance 745 */ 746 public JexlFeatures namespaceTest(final Predicate<String> names) { 747 nameSpaces = names == null ? TEST_STR_FALSE : names; 748 return this; 749 } 750 751 /** 752 * Sets whether creating new instances is enabled. 753 * <p> 754 * When disabled, parsing a script/expression using 'new(...)' will throw a parsing exception; 755 * using a class as functor will fail at runtime. 756 * </p> 757 * 758 * @param flag true to enable, false to disable 759 * @return this features instance 760 */ 761 public JexlFeatures newInstance(final boolean flag) { 762 setFeature(NEW_INSTANCE, flag); 763 return this; 764 } 765 766 /** 767 * Sets whether pragma constructs are enabled. 768 * <p> 769 * When disabled, parsing a script/expression using syntactic pragma constructs (#pragma) 770 * will throw a parsing exception. 771 * </p> 772 * 773 * @param flag true to enable, false to disable 774 * @return this features instance 775 */ 776 public JexlFeatures pragma(final boolean flag) { 777 setFeature(PRAGMA, flag); 778 if (!flag) { 779 setFeature(NS_PRAGMA, false); 780 setFeature(IMPORT_PRAGMA, false); 781 } 782 return this; 783 } 784 785 /** 786 * Sets whether pragma constructs can appear anywhere in the code. 787 * 788 * @param flag true to enable, false to disable 789 * @return this features instance 790 * @since 3.3 791 */ 792 public JexlFeatures pragmaAnywhere(final boolean flag) { 793 setFeature(PRAGMA_ANYWHERE, flag); 794 return this; 795 } 796 797 /** 798 * Gets the allowed pragma keys test. 799 * 800 * @return The allowed pragma keys test, never null. 801 * @since 3.7.1 802 */ 803 public Predicate<String> pragmaTest() { 804 return pragmas; 805 } 806 807 /** 808 * Sets a test to determine which pragma keys are allowed. 809 * <p> 810 * When set, parsing a script/expression declaring a pragma (#pragma key value) whose key does not satisfy 811 * the predicate will throw a parsing exception. This applies to all pragmas, including 812 * <code>jexl.options</code>, <code>jexl.import</code>, <code>jexl.namespace.*</code> and 813 * <code>jexl.module.*</code>. 814 * </p> 815 * <p> 816 * An engine built with these features applies the test to every script, expression and template it parses. 817 * As with every other feature, scripts created with explicit (non-null) features through 818 * {@link JexlEngine#createScript(JexlFeatures, JexlInfo, String, String...)} use those features' pragma 819 * test, not the engine's. 820 * </p> 821 * 822 * @param keys The pragma key predicate; must be stateless or thread-safe; null allows all pragma keys 823 * @return this features instance 824 * @since 3.7.1 825 */ 826 public JexlFeatures pragmaTest(final Predicate<String> keys) { 827 pragmas = keys == null ? TEST_STR_TRUE : keys; 828 return this; 829 } 830 831 /** 832 * Creates a pragma key test from a collection of patterns, meant for {@link #pragmaTest(Predicate)}. 833 * <p> 834 * A pattern ending with <code>*</code> matches any key starting with what precedes it, any other 835 * pattern matches the key exactly. For instance, <code>dlm.*</code> matches <code>dlm.foo</code> and 836 * <code>dlm.foo.bar</code>; <code>*</code> matches any key. Patterns are trimmed, null or empty 837 * patterns are ignored. 838 * </p> 839 * <p>Tests created from the same patterns are equal, so are the features using them.</p> 840 * 841 * @param patterns The allowed pragma key patterns; null or empty allows no pragma key 842 * @return The pragma key test 843 * @since 3.7.1 844 */ 845 public static Predicate<String> pragmaPatterns(final Collection<String> patterns) { 846 final Set<String> exact = new TreeSet<>(); 847 final Set<String> prefixes = new TreeSet<>(); 848 if (patterns != null) { 849 for (final String pattern : patterns) { 850 final String trimmed = pattern == null ? "" : pattern.trim(); 851 if ("*".equals(trimmed)) { 852 return TEST_STR_TRUE; 853 } 854 if (trimmed.endsWith("*")) { 855 prefixes.add(trimmed.substring(0, trimmed.length() - 1)); 856 } else if (!trimmed.isEmpty()) { 857 exact.add(trimmed); 858 } 859 } 860 } 861 return exact.isEmpty() && prefixes.isEmpty() ? TEST_STR_FALSE : new PragmaPatterns(exact, prefixes); 862 } 863 864 /** 865 * A pragma key test made of exact keys and key prefixes. 866 */ 867 private static final class PragmaPatterns implements Predicate<String> { 868 /** The exactly matched keys. */ 869 private final Set<String> exact; 870 /** The matched key prefixes. */ 871 private final Set<String> prefixes; 872 873 PragmaPatterns(final Set<String> exact, final Set<String> prefixes) { 874 this.exact = exact; 875 this.prefixes = prefixes; 876 } 877 878 @Override 879 public boolean test(final String key) { 880 if (key == null) { 881 return false; 882 } 883 if (exact.contains(key)) { 884 return true; 885 } 886 for (final String prefix : prefixes) { 887 if (key.startsWith(prefix)) { 888 return true; 889 } 890 } 891 return false; 892 } 893 894 @Override 895 public boolean equals(final Object obj) { 896 if (this == obj) { 897 return true; 898 } 899 if (!(obj instanceof PragmaPatterns)) { 900 return false; 901 } 902 final PragmaPatterns other = (PragmaPatterns) obj; 903 return exact.equals(other.exact) && prefixes.equals(other.prefixes); 904 } 905 906 @Override 907 public int hashCode() { 908 return 31 * exact.hashCode() + prefixes.hashCode(); //CSOFF: MagicNumber 909 } 910 911 @Override 912 public String toString() { 913 final StringBuilder strb = new StringBuilder(); 914 for (final String key : exact) { 915 strb.append(strb.length() > 0 ? ", " : "").append(key); 916 } 917 for (final String prefix : prefixes) { 918 strb.append(strb.length() > 0 ? ", " : "").append(prefix).append('*'); 919 } 920 return strb.toString(); 921 } 922 } 923 924 /** 925 * Sets whether register are enabled. 926 * <p> 927 * This is mostly used internally during execution of JexlEngine.{g,s}etProperty. 928 * </p> 929 * <p> 930 * When disabled, parsing a script/expression using the register syntax will throw a parsing exception. 931 * </p> 932 * 933 * @param flag true to enable, false to disable 934 * @return this features instance 935 */ 936 public JexlFeatures register(final boolean flag) { 937 setFeature(REGISTER, flag); 938 return this; 939 } 940 941 /** 942 * Sets a collection of reserved r precluding those to be used as local variables or parameter r. 943 * 944 * @param names The r to reserve 945 * @return this features instance 946 */ 947 public JexlFeatures reservedNames(final Collection<String> names) { 948 if (names == null || names.isEmpty()) { 949 reservedNames = Collections.emptySet(); 950 } else { 951 reservedNames = Collections.unmodifiableSet(new TreeSet<>(names)); 952 } 953 return this; 954 } 955 956 /** 957 * Sets whether scripts constructs are enabled. 958 * <p> 959 * When disabled, parsing a script using syntactic script constructs (statements, ...) 960 * will throw a parsing exception. 961 * </p> 962 * 963 * @param flag true to enable, false to disable 964 * @return this features instance 965 */ 966 public JexlFeatures script(final boolean flag) { 967 setFeature(SCRIPT, flag); 968 return this; 969 } 970 971 /** 972 * Sets a feature flag. 973 * 974 * @param feature The feature ordinal 975 * @param flag turn-on, turn off 976 */ 977 private void setFeature(final int feature, final boolean flag) { 978 if (flag) { 979 flags |= 1L << feature; 980 } else { 981 flags &= ~(1L << feature); 982 } 983 } 984 985 /** 986 * Sets whether statements can be ambiguous. 987 * <p> 988 * When enabled, the semicolumn is not required between expressions that otherwise are considered 989 * ambiguous. The default will report ambiguity in cases like <code>if (true) { x 5 }</code> considering this 990 * may be missing an operator or that the intent is not clear. 991 * </p> 992 * 993 * @param flag true to enable, false to disable 994 * @return this features instance 995 */ 996 public JexlFeatures ambiguousStatement(final boolean flag) { 997 setFeature(AMBIGUOUS_STATEMENT, flag); 998 return this; 999 } 1000 1001 /** 1002 * Checks whether statements can be ambiguous. 1003 * <p> 1004 * When enabled, the semicolumn is not required between expressions that otherwise are considered 1005 * ambiguous. The default will report ambiguity in cases like <code>if (true) { x 5 }</code> considering this 1006 * may be missing an operator or that the intent is not clear. 1007 * </p> 1008 * 1009 * @return true if statements can be ambiguous, false otherwise 1010 */ 1011 public boolean supportsAmbiguousStatement() { 1012 return getFeature(AMBIGUOUS_STATEMENT); 1013 } 1014 1015 /** 1016 * Sets whether side effect expressions are enabled. 1017 * <p> 1018 * When disabled, parsing a script/expression using syntactical constructs modifying variables 1019 * or members will throw a parsing exception. 1020 * </p> 1021 * 1022 * @param flag true to enable, false to disable 1023 * @return this features instance 1024 */ 1025 public JexlFeatures sideEffect(final boolean flag) { 1026 setFeature(SIDE_EFFECT, flag); 1027 return this; 1028 } 1029 1030 /** 1031 * Sets whether side effect expressions on global variables (aka non-local) are enabled. 1032 * <p> 1033 * When disabled, parsing a script/expression using syntactical constructs modifying variables 1034 * <em>including all potentially ant-ish variables</em> will throw a parsing exception. 1035 * </p> 1036 * 1037 * @param flag true to enable, false to disable 1038 * @return this features instance 1039 */ 1040 public JexlFeatures sideEffectGlobal(final boolean flag) { 1041 setFeature(SIDE_EFFECT_GLOBAL, flag); 1042 return this; 1043 } 1044 1045 /** 1046 * Sets whether array/map/set literal expressions are enabled. 1047 * <p> 1048 * When disabled, parsing a script/expression creating one of these literals 1049 * will throw a parsing exception; 1050 * </p> 1051 * 1052 * @param flag true to enable, false to disable 1053 * @return this features instance 1054 */ 1055 public JexlFeatures structuredLiteral(final boolean flag) { 1056 setFeature(STRUCTURED_LITERAL, flag); 1057 return this; 1058 } 1059 1060 /** 1061 * Does the engine support annotations? 1062 * 1063 * @return true if annotation are enabled, false otherwise 1064 */ 1065 public boolean supportsAnnotation() { 1066 return getFeature(ANNOTATION); 1067 } 1068 1069 /** 1070 * Does the engine support array references which contain method call expressions? 1071 * 1072 * @return true if array references can contain method call expressions, false otherwise 1073 */ 1074 public boolean supportsArrayReferenceExpr() { 1075 return getFeature(ARRAY_REF_EXPR); 1076 } 1077 1078 /** 1079 * Does the engine support legacy comparison operator names syntax? 1080 * 1081 * @return true if legacy comparison operator names syntax is enabled, false otherwise 1082 * @since 3.3 1083 */ 1084 public boolean supportsComparatorNames() { 1085 return getFeature(COMPARATOR_NAMES); 1086 } 1087 1088 /** 1089 * Does the engine support lambda captured-variables as const? 1090 * 1091 * @return true if lambda captured-variables are const, false otherwise 1092 */ 1093 public boolean supportsConstCapture() { 1094 return getFeature(CONST_CAPTURE); 1095 } 1096 1097 /** 1098 * Does the engine support lambda captured-variables as references? 1099 * 1100 * @return true if lambda captured-variables are references, false otherwise 1101 */ 1102 public boolean supportsReferenceCapture() { 1103 return getFeature(REF_CAPTURE); 1104 } 1105 1106 /** 1107 * Does the engine allow reflective auto-instantiation of a namespace functor 1108 * from a class or class-name binding? 1109 * 1110 * @return true if namespace auto-instantiation is allowed, false otherwise 1111 * @since 3.7.1 1112 */ 1113 public boolean supportsNamespaceInstantiation() { 1114 return getFeature(NAMESPACE_INSTANTIATE); 1115 } 1116 1117 /** 1118 * Does the engine support expressions (aka not scripts) 1119 * 1120 * @return true if expressions (aka not scripts) are enabled, false otherwise 1121 */ 1122 public boolean supportsExpression() { 1123 return !getFeature(SCRIPT); 1124 } 1125 1126 /** 1127 * Does the engine support fat-arrow lambda syntax? 1128 * 1129 * @return true if fat-arrow lambda syntax is enabled, false otherwise 1130 * @since 3.3 1131 */ 1132 public boolean supportsFatArrow() { 1133 return getFeature(FAT_ARROW); 1134 } 1135 1136 /** 1137 * Does the engine support import pragma? 1138 * 1139 * @return true if import pragma are enabled, false otherwise 1140 * @since 3.3 1141 */ 1142 public boolean supportsImportPragma() { 1143 return getFeature(IMPORT_PRAGMA); 1144 } 1145 1146 /** 1147 * Does the engine support lambdas? 1148 * 1149 * @return true if lambda are enabled, false otherwise 1150 */ 1151 public boolean supportsLambda() { 1152 return getFeature(LAMBDA); 1153 } 1154 1155 /** 1156 * Is local variables syntax enabled? 1157 * 1158 * @return true if local variables syntax is enabled 1159 */ 1160 public boolean supportsLocalVar() { 1161 return getFeature(LOCAL_VAR); 1162 } 1163 1164 /** 1165 * Are loops enabled? 1166 * 1167 * @return true if loops are enabled, false otherwise 1168 */ 1169 public boolean supportsLoops() { 1170 return getFeature(LOOP); 1171 } 1172 1173 /** 1174 * Can array references contain expressions? 1175 * 1176 * @return true if array references can contain expressions, false otherwise 1177 */ 1178 public boolean supportsMethodCall() { 1179 return getFeature(METHOD_CALL); 1180 } 1181 1182 /** 1183 * Is namespace pragma enabled? 1184 * 1185 * @return true if namespace pragma are enabled, false otherwise 1186 * @since 3.3 1187 */ 1188 public boolean supportsNamespacePragma() { 1189 return getFeature(NS_PRAGMA); 1190 } 1191 1192 /** 1193 * Is namespace identifier syntax enabled? 1194 * 1195 * @return true if namespace identifier syntax is enabled, false otherwise 1196 * @since 3.5.0 1197 */ 1198 public boolean supportsNamespaceIdentifier() { 1199 return getFeature(NS_IDENTIFIER); 1200 } 1201 1202 /** 1203 * Is creating new instances enabled? 1204 * 1205 * @return true if creating new instances is enabled, false otherwise 1206 */ 1207 public boolean supportsNewInstance() { 1208 return getFeature(NEW_INSTANCE); 1209 } 1210 1211 /** 1212 * Is the namespace pragma enabled? 1213 * 1214 * @return true if namespace pragma are enabled, false otherwise 1215 */ 1216 public boolean supportsPragma() { 1217 return getFeature(PRAGMA); 1218 } 1219 1220 /** 1221 * Can pragma constructs appear anywhere in the code? 1222 * 1223 * @return true if pragma constructs can appear anywhere in the code, false otherwise 1224 * @since 3.3 1225 */ 1226 public boolean supportsPragmaAnywhere() { 1227 return getFeature(PRAGMA_ANYWHERE); 1228 } 1229 1230 /** 1231 * Is register syntax enabled? 1232 * 1233 * @return true if register syntax is enabled 1234 */ 1235 public boolean supportsRegister() { 1236 return getFeature(REGISTER); 1237 } 1238 1239 /** 1240 * Are scripts enabled? 1241 * 1242 * @return true if scripts are enabled, false otherwise 1243 */ 1244 public boolean supportsScript() { 1245 return getFeature(SCRIPT); 1246 } 1247 1248 /** 1249 * Are side effects enabled? 1250 * 1251 * @return true if side effects are enabled, false otherwise 1252 */ 1253 public boolean supportsSideEffect() { 1254 return getFeature(SIDE_EFFECT); 1255 } 1256 1257 /** 1258 * Can global variables be assigned? 1259 * 1260 * @return true if global variables can be assigned 1261 */ 1262 public boolean supportsSideEffectGlobal() { 1263 return getFeature(SIDE_EFFECT_GLOBAL); 1264 } 1265 1266 /** 1267 * Are array/map/set literal expressions supported? 1268 * 1269 * @return true if array/map/set literal expressions are supported, false otherwise 1270 */ 1271 public boolean supportsStructuredLiteral() { 1272 return getFeature(STRUCTURED_LITERAL); 1273 } 1274 1275 /** 1276 * Is thin-arrow lambda syntax enabled? 1277 * 1278 * @return true if thin-arrow lambda syntax is enabled, false otherwise 1279 * @since 3.3 1280 */ 1281 public boolean supportsThinArrow() { 1282 return getFeature(THIN_ARROW); 1283 } 1284 1285 /** 1286 * Sets whether thin-arrow lambda syntax is enabled. 1287 * <p> 1288 * When disabled, parsing a script/expression using syntactic thin-arrow (-<) 1289 * will throw a parsing exception. 1290 * </p> 1291 * 1292 * @param flag true to enable, false to disable 1293 * @return this features instance 1294 * @since 3.3 1295 */ 1296 public JexlFeatures thinArrow(final boolean flag) { 1297 setFeature(THIN_ARROW, flag); 1298 return this; 1299 } 1300}