You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by he...@apache.org on 2019/10/29 15:57:55 UTC
[commons-jexl] 02/07: JEXL-307: added lexical and lexical shade
option;
JEXL-307: added lexical feature, controlling var redefinition at parsing
time;
JEXL-307: refactored option (JexlOption) and added feature to deal with
lexical scope; JEXL-307: refactored test code to use new option classes;
JEXL-307: added lexical scope and frame handling interpreting scripts,
lambdas, for-loops;
JEXL-314: changed JexlArithmetic/Interpreter to handle NullOperand exception
when needed Task #JEXL-307 - Variable redeclaration option
This is an automated email from the ASF dual-hosted git repository.
henrib pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-jexl.git
commit d15901734478efeac605d3dc55c4b25a479c28d4
Author: henrib <he...@apache.org>
AuthorDate: Sun Oct 27 13:59:37 2019 +0100
JEXL-307: added lexical and lexical shade option;
JEXL-307: added lexical feature, controlling var redefinition at parsing time;
JEXL-307: refactored option (JexlOption) and added feature to deal with lexical scope;
JEXL-307: refactored test code to use new option classes;
JEXL-307: added lexical scope and frame handling interpreting scripts, lambdas, for-loops;
JEXL-314: changed JexlArithmetic/Interpreter to handle NullOperand exception when needed
Task #JEXL-307 - Variable redeclaration option
---
RELEASE-NOTES.txt | 8 +-
pom.xml | 173 +++++++-------
.../org/apache/commons/jexl3/JexlArithmetic.java | 87 +++++--
.../java/org/apache/commons/jexl3/JexlBuilder.java | 56 ++++-
.../java/org/apache/commons/jexl3/JexlContext.java | 19 +-
.../java/org/apache/commons/jexl3/JexlEngine.java | 10 +-
.../org/apache/commons/jexl3/JexlException.java | 74 ++++--
.../org/apache/commons/jexl3/JexlFeatures.java | 21 +-
.../java/org/apache/commons/jexl3/JexlOptions.java | 192 +++++++++++++++
.../org/apache/commons/jexl3/internal/Closure.java | 24 +-
.../org/apache/commons/jexl3/internal/Engine.java | 85 +++++--
.../org/apache/commons/jexl3/internal/Frame.java | 126 ++++++++++
.../apache/commons/jexl3/internal/Interpreter.java | 259 +++++++++++++--------
.../commons/jexl3/internal/InterpreterBase.java | 151 ++++++++----
.../commons/jexl3/internal/LexicalScope.java | 118 ++++++++++
.../org/apache/commons/jexl3/internal/Options.java | 229 ++++++++++++++++++
.../org/apache/commons/jexl3/internal/Scope.java | 119 +---------
.../org/apache/commons/jexl3/internal/Script.java | 8 +-
.../jexl3/internal/TemplateInterpreter.java | 4 +-
.../commons/jexl3/internal/TemplateScript.java | 4 +-
.../jexl3/internal/introspection/ClassMap.java | 2 +-
.../internal/introspection/ConstructorMethod.java | 3 +-
.../jexl3/internal/introspection/Introspector.java | 18 +-
.../internal/introspection/MethodExecutor.java | 2 +-
.../jexl3/internal/introspection/Uberspect.java | 12 +-
.../org/apache/commons/jexl3/parser/ASTBlock.java | 62 +++++
.../commons/jexl3/parser/ASTForeachStatement.java | 62 +++++
.../apache/commons/jexl3/parser/ASTJexlScript.java | 50 +++-
.../org/apache/commons/jexl3/parser/JexlNode.java | 10 +-
.../apache/commons/jexl3/parser/JexlParser.java | 167 +++++++++++--
.../org/apache/commons/jexl3/parser/Parser.jjt | 25 +-
src/site/xdoc/changes.xml | 9 +
src/site/xdoc/reference/syntax.xml | 3 +
.../org/apache/commons/jexl3/AnnotationTest.java | 29 +--
.../org/apache/commons/jexl3/ArithmeticTest.java | 43 ++--
.../apache/commons/jexl3/BitwiseOperatorTest.java | 2 +-
.../org/apache/commons/jexl3/ClassCreatorTest.java | 5 +-
.../org/apache/commons/jexl3/ExceptionTest.java | 35 +--
.../org/apache/commons/jexl3/FeaturesTest.java | 2 +-
src/test/java/org/apache/commons/jexl3/IfTest.java | 59 ++---
.../org/apache/commons/jexl3/Issues100Test.java | 11 +-
.../org/apache/commons/jexl3/Issues200Test.java | 8 +-
.../org/apache/commons/jexl3/Issues300Test.java | 83 +++++++
.../java/org/apache/commons/jexl3/IssuesTest.java | 39 ++--
.../java/org/apache/commons/jexl3/JXLTTest.java | 34 ++-
.../org/apache/commons/jexl3/JexlEvalContext.java | 158 ++-----------
.../java/org/apache/commons/jexl3/JexlTest.java | 21 +-
.../java/org/apache/commons/jexl3/LexicalTest.java | 228 ++++++++++++++++++
.../java/org/apache/commons/jexl3/PragmaTest.java | 41 ++++
.../org/apache/commons/jexl3/ReadonlyContext.java | 47 +---
.../java/org/apache/commons/jexl3/VarTest.java | 7 +-
.../org/apache/commons/jexl3/junit/Asserter.java | 11 +-
52 files changed, 2274 insertions(+), 781 deletions(-)
diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt
index 3373d7c..6a6ee8d 100644
--- a/RELEASE-NOTES.txt
+++ b/RELEASE-NOTES.txt
@@ -36,7 +36,8 @@ It is nevertheless possible and we are sorry if it causes problems to any of you
What's new in 3.2:
==================
-
+* Lexical scoping for local variables and parameters allowing at parse and/or runtime to reduce scripting errors and bugs and align with
+ known development languages best practice (C, Java, ...).
* Interpolated strings in identifiers, as in x.`${prefix}_${suffix}` that in many cases would be equivalent to
x[prefix + '_' + suffix] or x[`${prefix}_${suffix}`].
* Safe-navigation mode and operator '?.' allowing lenient handling of non-existent or null properties so that an expression
@@ -47,7 +48,7 @@ What's new in 3.2:
New Features in 3.2:
====================
-
+* JEXL-307: Variable redeclaration option
* JEXL-295: Add unary plus operator
* JEXL-292: Allow specifying custom Permissions class for Uberspect to be used later by Introspector
* JEXL-288: Annotation can not be specified for a standalone statement
@@ -72,7 +73,8 @@ New Features in 3.2:
Bugs Fixed in 3.2:
==================
-
+* JEXL-315: JxltEngine literal string strings ending in \ $ or # throw JxltEngine$Exception
+* JEXL-314: Comparison NULL values of variables NAME1.NAME2
* JEXL-312: @NoJexl fails to disallow method call
* JEXL-311: Jxlt template scripts fail using verbatim expressions embedded in lambdas
* JEXL-309: Line numbers are not correct when template report errors
diff --git a/pom.xml b/pom.xml
index 23a9bac..b52a569 100644
--- a/pom.xml
+++ b/pom.xml
@@ -16,31 +16,55 @@
limitations under the License.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.apache.commons</groupId>
<artifactId>commons-parent</artifactId>
- <version>48</version>
+ <version>49</version>
</parent>
- <modelVersion>4.0.0</modelVersion>
+
<groupId>org.apache.commons</groupId>
<artifactId>commons-jexl3</artifactId>
<version>3.2-SNAPSHOT</version>
<name>Apache Commons JEXL</name>
- <inceptionYear>2001</inceptionYear>
<description>The Apache Commons JEXL library is an implementation of the JSTL Expression Language with extensions.</description>
<url>https://commons.apache.org/proper/commons-jexl/</url>
+ <inceptionYear>2001</inceptionYear>
- <issueManagement>
- <system>jira</system>
- <url>https://issues.apache.org/jira/browse/JEXL</url>
- </issueManagement>
-
+ <properties>
+ <maven.compiler.source>1.6</maven.compiler.source>
+ <maven.compiler.target>1.6</maven.compiler.target>
+ <commons.componentid>jexl3</commons.componentid>
+ <commons.module.name>org.apache.commons.jexl3</commons.module.name>
+ <commons.release.version>3.1</commons.release.version>
+ <commons.site.path>jexl</commons.site.path>
+ <commons.scmPubUrl>https://svn.apache.org/repos/infra/websites/production/commons/content/proper/commons-jexl</commons.scmPubUrl>
+ <commons.scmPubCheckoutDirectory>site-content</commons.scmPubCheckoutDirectory>
+ <!-- The RC version used in the staging repository URL. -->
+ <commons.rc.version>RC1</commons.rc.version>
+ <commons.release.2.version>2.1.1</commons.release.2.version>
+ <!-- Override the default, which is ${project.artifactId}-${commons.release.[23].version} -->
+ <commons.release.name>commons-jexl-${commons.release.version}</commons.release.name>
+ <commons.release.2.name>commons-jexl-${commons.release.2.version}</commons.release.2.name>
+ <commons.release.3.name>commons-jexl-${commons.release.3.version}</commons.release.3.name>
+ <commons.release.3.desc>Legacy</commons.release.3.desc>
+ <commons.release.3.version>1.1</commons.release.3.version>
+ <commons.release.3.binary.suffix />
+ <commons.jira.id>JEXL</commons.jira.id>
+ <commons.jira.pid>12310479</commons.jira.pid>
+ <checkstyle.plugin.version>2.17</checkstyle.plugin.version>
+ <checksyle.version>6.13</checksyle.version>
+ </properties>
+
<scm>
<connection>scm:git:https://gitbox.apache.org/repos/asf/commons-jexl.git</connection>
<developerConnection>scm:git:https://gitbox.apache.org/repos/asf/commons-jexl.git</developerConnection>
<url>https://gitbox.apache.org/repos/asf/commons-jexl.git</url>
- </scm>
-
+ </scm>
+ <issueManagement>
+ <system>jira</system>
+ <url>https://issues.apache.org/jira/browse/JEXL</url>
+ </issueManagement>
<distributionManagement>
<site>
<id>apache.website</id>
@@ -49,55 +73,6 @@
</site>
</distributionManagement>
- <developers>
- <developer>
- <name>dIon Gillard</name>
- <id>dion</id>
- <email>dion AT apache DOT org</email>
- <organization>The Apache Software Foundation</organization>
- </developer>
- <developer>
- <name>Geir Magnusson Jr.</name>
- <id>geirm</id>
- <email>geirm AT apache DOT org</email>
- <organization>independent</organization>
- </developer>
- <developer>
- <name>Tim O'Brien</name>
- <id>tobrien</id>
- <email>tobrien AT apache DOT org</email>
- <organization>independent</organization>
- </developer>
- <developer>
- <name>Peter Royal</name>
- <id>proyal</id>
- <email>proyal AT apache DOT org</email>
- <organization>The Apache Software Foundation</organization>
- </developer>
- <developer>
- <name>James Strachan</name>
- <id>jstrachan</id>
- <email>jstrachan AT apache DOT org</email>
- <organization>SpiritSoft, Inc.</organization>
- </developer>
- <developer>
- <name>Rahul Akolkar</name>
- <id>rahul</id>
- <email>rahul AT apache DOT org</email>
- <organization>The Apache Software Foundation</organization>
- </developer>
- <developer>
- <name>Sebastian Bazley</name>
- <id>sebb</id>
- <email>sebb AT apache DOT org</email>
- </developer>
- <developer>
- <name>Henri Biestro</name>
- <id>henrib</id>
- <email>henrib AT apache DOT org</email>
- </developer>
- </developers>
-
<dependencies>
<dependency>
<groupId>commons-logging</groupId>
@@ -112,31 +87,6 @@
</dependency>
</dependencies>
- <properties>
- <maven.compiler.source>1.6</maven.compiler.source>
- <maven.compiler.target>1.6</maven.compiler.target>
- <commons.componentid>jexl3</commons.componentid>
- <commons.module.name>org.apache.commons.jexl3</commons.module.name>
- <commons.release.version>3.1</commons.release.version>
- <commons.site.path>jexl</commons.site.path>
- <commons.scmPubUrl>https://svn.apache.org/repos/infra/websites/production/commons/content/proper/commons-jexl</commons.scmPubUrl>
- <commons.scmPubCheckoutDirectory>site-content</commons.scmPubCheckoutDirectory>
- <!-- The RC version used in the staging repository URL. -->
- <commons.rc.version>RC1</commons.rc.version>
- <commons.release.2.version>2.1.1</commons.release.2.version>
- <!-- Override the default, which is ${project.artifactId}-${commons.release.[23].version} -->
- <commons.release.name>commons-jexl-${commons.release.version}</commons.release.name>
- <commons.release.2.name>commons-jexl-${commons.release.2.version}</commons.release.2.name>
- <commons.release.3.name>commons-jexl-${commons.release.3.version}</commons.release.3.name>
- <commons.release.3.desc>Legacy</commons.release.3.desc>
- <commons.release.3.version>1.1</commons.release.3.version>
- <commons.release.3.binary.suffix />
- <commons.jira.id>JEXL</commons.jira.id>
- <commons.jira.pid>12310479</commons.jira.pid>
- <checkstyle.plugin.version>2.17</checkstyle.plugin.version>
- <checksyle.version>6.13</checksyle.version>
- </properties>
-
<build>
<plugins>
<plugin>
@@ -369,4 +319,59 @@
</plugin>
</plugins>
</reporting>
+
+ <developers>
+ <developer>
+ <name>dIon Gillard</name>
+ <id>dion</id>
+ <email>dion AT apache DOT org</email>
+ <organization>The Apache Software Foundation</organization>
+ </developer>
+ <developer>
+ <name>Geir Magnusson Jr.</name>
+ <id>geirm</id>
+ <email>geirm AT apache DOT org</email>
+ <organization>independent</organization>
+ </developer>
+ <developer>
+ <name>Tim O'Brien</name>
+ <id>tobrien</id>
+ <email>tobrien AT apache DOT org</email>
+ <organization>independent</organization>
+ </developer>
+ <developer>
+ <name>Peter Royal</name>
+ <id>proyal</id>
+ <email>proyal AT apache DOT org</email>
+ <organization>The Apache Software Foundation</organization>
+ </developer>
+ <developer>
+ <name>James Strachan</name>
+ <id>jstrachan</id>
+ <email>jstrachan AT apache DOT org</email>
+ <organization>SpiritSoft, Inc.</organization>
+ </developer>
+ <developer>
+ <name>Rahul Akolkar</name>
+ <id>rahul</id>
+ <email>rahul AT apache DOT org</email>
+ <organization>The Apache Software Foundation</organization>
+ </developer>
+ <developer>
+ <name>Sebastian Bazley</name>
+ <id>sebb</id>
+ <email>sebb AT apache DOT org</email>
+ </developer>
+ <developer>
+ <name>Henri Biestro</name>
+ <id>henrib</id>
+ <email>henrib AT apache DOT org</email>
+ </developer>
+ </developers>
+
+ <contributors>
+ <contributor>
+ <name>Dmitri Blinov</name>
+ </contributor>
+ </contributors>
</project>
diff --git a/src/main/java/org/apache/commons/jexl3/JexlArithmetic.java b/src/main/java/org/apache/commons/jexl3/JexlArithmetic.java
index ca25585..d9778fc 100644
--- a/src/main/java/org/apache/commons/jexl3/JexlArithmetic.java
+++ b/src/main/java/org/apache/commons/jexl3/JexlArithmetic.java
@@ -103,7 +103,7 @@ public class JexlArithmetic {
this.mathContext = bigdContext == null ? MathContext.DECIMAL128 : bigdContext;
this.mathScale = bigdScale == Integer.MIN_VALUE ? BIGD_SCALE : bigdScale;
}
-
+
/**
* Apply options to this arithmetic which eventually may create another instance.
* @see #createWithOptions(boolean, java.math.MathContext, int)
@@ -111,39 +111,73 @@ public class JexlArithmetic {
* @param options the {@link JexlEngine.Options} to use
* @return an arithmetic with those options set
*/
- public JexlArithmetic options(JexlEngine.Options options) {
- Boolean ostrict = options.isStrictArithmetic();
- if (ostrict == null) {
- ostrict = isStrict();
- }
- MathContext bigdContext = options.getArithmeticMathContext();
- if (bigdContext == null) {
- bigdContext = getMathContext();
- }
- int bigdScale = options.getArithmeticMathScale();
- if (bigdScale == Integer.MIN_VALUE) {
- bigdScale = getMathScale();
+ public JexlArithmetic options(JexlOptions options) {
+ if (options != null) {
+ boolean ostrict = options.isStrictArithmetic();
+ MathContext bigdContext = options.getMathContext();
+ if (bigdContext == null) {
+ bigdContext = getMathContext();
+ }
+ int bigdScale = options.getMathScale();
+ if (bigdScale == Integer.MIN_VALUE) {
+ bigdScale = getMathScale();
+ }
+ if (ostrict != isStrict()
+ || bigdScale != getMathScale()
+ || bigdContext != getMathContext()) {
+ return createWithOptions(ostrict, bigdContext, bigdScale);
+ }
}
- if (ostrict != isStrict()
- || bigdScale != getMathScale()
- || bigdContext != getMathContext()) {
- return createWithOptions(ostrict, bigdContext, bigdScale);
+ return this;
+ }
+
+ /**
+ * Apply options to this arithmetic which eventually may create another instance.
+ * @see #createWithOptions(boolean, java.math.MathContext, int)
+ *
+ * @param options the {@link JexlEngine.Options} to use
+ * @return an arithmetic with those options set
+ * @deprecated
+ */
+ public JexlArithmetic options(JexlEngine.Options options) {
+ if (options != null) {
+ Boolean ostrict = options.isStrictArithmetic();
+ if (ostrict == null) {
+ ostrict = isStrict();
+ }
+ MathContext bigdContext = options.getArithmeticMathContext();
+ if (bigdContext == null) {
+ bigdContext = getMathContext();
+ }
+ int bigdScale = options.getArithmeticMathScale();
+ if (bigdScale == Integer.MIN_VALUE) {
+ bigdScale = getMathScale();
+ }
+ if (ostrict != isStrict()
+ || bigdScale != getMathScale()
+ || bigdContext != getMathContext()) {
+ return createWithOptions(ostrict, bigdContext, bigdScale);
+ }
}
return this;
}
-
+
/**
* Apply options to this arithmetic which eventually may create another instance.
* @see #createWithOptions(boolean, java.math.MathContext, int)
*
- * @param context the context that may extend {@link JexlEngine.Options} to use
+ * @param context the context that may extend {@link JexlContext.OptionsHandle} to use
* @return a new arithmetic instance or this
* @since 3.1
*/
public JexlArithmetic options(JexlContext context) {
- return context instanceof JexlEngine.Options
- ? options((JexlEngine.Options) context)
- : this;
+ if (context instanceof JexlContext.OptionsHandle) {
+ return options(((JexlContext.OptionsHandle) context).getEngineOptions());
+ }
+ if (context instanceof JexlEngine.Options) {
+ return options((JexlEngine.Options) context);
+ }
+ return this;
}
/**
@@ -802,7 +836,10 @@ public class JexlArithmetic {
* @return the negated value
*/
public Object negate(Object val) {
- if (val instanceof Integer) {
+ if (val == null) {
+ controlNullOperand();
+ return null;
+ } else if (val instanceof Integer) {
return -((Integer) val);
} else if (val instanceof Double) {
return - ((Double) val);
@@ -847,6 +884,10 @@ public class JexlArithmetic {
* @return the positive value
*/
public Object positivize(Object val) {
+ if (val == null) {
+ controlNullOperand();
+ return null;
+ }
if (val instanceof Short) {
return ((Short) val).intValue();
}
diff --git a/src/main/java/org/apache/commons/jexl3/JexlBuilder.java b/src/main/java/org/apache/commons/jexl3/JexlBuilder.java
index ad69d67..a02bfb2 100644
--- a/src/main/java/org/apache/commons/jexl3/JexlBuilder.java
+++ b/src/main/java/org/apache/commons/jexl3/JexlBuilder.java
@@ -18,6 +18,7 @@
package org.apache.commons.jexl3;
import org.apache.commons.jexl3.internal.Engine;
+import org.apache.commons.jexl3.internal.Options;
import org.apache.commons.jexl3.introspection.JexlSandbox;
import org.apache.commons.jexl3.introspection.JexlUberspect;
import org.apache.commons.logging.Log;
@@ -90,6 +91,9 @@ public class JexlBuilder {
/** Whether interrupt throws JexlException.Cancel. */
private Boolean cancellable = null;
+
+ /** The options. */
+ private final Options options = new Options();
/** Whether getVariables considers all potential equivalent syntactic forms. */
private Boolean collectAll = null;
@@ -151,6 +155,11 @@ public class JexlBuilder {
return this.strategy;
}
+ /** @return the current set of options */
+ public JexlOptions options() {
+ return options;
+ }
+
/**
* Sets the JexlArithmetic instance the engine will use.
*
@@ -159,6 +168,9 @@ public class JexlBuilder {
*/
public JexlBuilder arithmetic(JexlArithmetic a) {
this.arithmetic = a;
+ options.setStrictArithmetic(a.isStrict());
+ options.setMathContext(a.getMathContext());
+ options.setMathScale(a.getMathScale());
return this;
}
@@ -203,11 +215,11 @@ public class JexlBuilder {
/**
* Sets the o.a.c.Log instance to use.
*
- * @param l the logger
+ * @param log the logger
* @return this builder
*/
- public JexlBuilder logger(Log l) {
- this.logger = l;
+ public JexlBuilder logger(Log log) {
+ this.logger = log;
return this;
}
@@ -260,7 +272,39 @@ public class JexlBuilder {
public Charset charset() {
return charset;
}
-
+
+ /**
+ * Sets whether the engine will resolve antish variable names.
+ *
+ * @param flag true means antish resolution is enabled, false disables it
+ * @return this builder
+ */
+ public JexlBuilder antish(boolean flag) {
+ options.setAntish(flag);
+ return this;
+ }
+
+ /** @return whether antish resolution is enabled */
+ public boolean antish() {
+ return options.isAntish();
+ }
+
+ /**
+ * Sets whether the engine is in lexical mode.
+ *
+ * @param flag true means lexical function scope is in effect, false implies non-lexical scoping
+ * @return this builder
+ */
+ public JexlBuilder lexical(boolean flag) {
+ options.setLexical(true);
+ return this;
+ }
+
+ /** @return whether lexical scope is enabled */
+ public boolean lexical() {
+ return options.isLexical();
+ }
+
/**
* Sets whether the engine will throw JexlException during evaluation when an error is triggered.
*
@@ -269,6 +313,7 @@ public class JexlBuilder {
*/
public JexlBuilder silent(boolean flag) {
this.silent = flag;
+ options.setSilent(flag);
return this;
}
@@ -286,6 +331,7 @@ public class JexlBuilder {
*/
public JexlBuilder strict(boolean flag) {
this.strict = flag;
+ options.setStrict(flag);
return this;
}
@@ -305,6 +351,7 @@ public class JexlBuilder {
*/
public JexlBuilder safe(boolean flag) {
this.safe = flag;
+ options.setSafe(flag);
return this;
}
@@ -339,6 +386,7 @@ public class JexlBuilder {
*/
public JexlBuilder cancellable(boolean flag) {
this.cancellable = flag;
+ options.setCancellable(flag);
return this;
}
diff --git a/src/main/java/org/apache/commons/jexl3/JexlContext.java b/src/main/java/org/apache/commons/jexl3/JexlContext.java
index 0ab9194..fdc85a3 100644
--- a/src/main/java/org/apache/commons/jexl3/JexlContext.java
+++ b/src/main/java/org/apache/commons/jexl3/JexlContext.java
@@ -121,7 +121,7 @@ public interface JexlContext {
}
/**
- * A marker interface of the JexlContext that allows to process annotations.
+ * A marker interface of the JexlContext that processes annotations.
* It is used by the interpreter during evaluation to execute annotation evaluations.
* <p>If the JexlContext is not an instance of an AnnotationProcessor, encountering an annotation will generate
* an error or a warning depending on the engine strictness.
@@ -143,5 +143,20 @@ public interface JexlContext {
*/
Object processAnnotation(String name, Object[] args, Callable<Object> statement) throws Exception;
}
-
+
+ /**
+ * A marker interface of the JexlContext that exposes runtime evaluation options.
+ */
+ interface OptionsHandle {
+ /**
+ * Retrieves the current set of options though the context.
+ * <p>
+ * This method will be called once at beginning of evaluation and the
+ * JexlOptions instance kept as a property of the evaluator;
+ * the JexlOptions instance is free to alter its boolean flags during
+ * execution.
+ * @return the engine options
+ */
+ JexlOptions getEngineOptions();
+ }
}
diff --git a/src/main/java/org/apache/commons/jexl3/JexlEngine.java b/src/main/java/org/apache/commons/jexl3/JexlEngine.java
index 2b371b8..bc41a82 100644
--- a/src/main/java/org/apache/commons/jexl3/JexlEngine.java
+++ b/src/main/java/org/apache/commons/jexl3/JexlEngine.java
@@ -107,6 +107,7 @@ public abstract class JexlEngine {
/**
* Script evaluation options.
* <p>The JexlContext used for evaluation can implement this interface to alter behavior.</p>
+ * @deprecated 3.2
*/
public interface Options {
@@ -159,7 +160,7 @@ public abstract class JexlEngine {
*/
int getArithmeticMathScale();
}
-
+
/** Default features. */
public static final JexlFeatures DEFAULT_FEATURES = new JexlFeatures();
@@ -257,6 +258,13 @@ public abstract class JexlEngine {
* @return true if strict, false otherwise
*/
public abstract boolean isStrict();
+
+ /**
+ * Checks whether this engine uses safe navigation.
+ *
+ * @return true if safe, false otherwise
+ */
+ public abstract boolean isSafe();
/**
* Checks whether this engine will throw JexlException.Cancel (true) or return null (false) when interrupted
diff --git a/src/main/java/org/apache/commons/jexl3/JexlException.java b/src/main/java/org/apache/commons/jexl3/JexlException.java
index cccccbe..815adba 100644
--- a/src/main/java/org/apache/commons/jexl3/JexlException.java
+++ b/src/main/java/org/apache/commons/jexl3/JexlException.java
@@ -467,6 +467,32 @@ public class JexlException extends RuntimeException {
return parserError(JexlFeatures.stringify(code), getDetail());
}
}
+
+ /**
+ * The various type of variable issues.
+ */
+ public static enum VariableIssue {
+ /** The variable is undefined. */
+ UNDEFINED,
+ /** The variable is already declared. */
+ REDEFINED,
+ /** The variable has a null value. */
+ NULLVALUE;
+
+ /**
+ * Stringifies the variable issue.
+ * @param var the variable name
+ * @return the issue message
+ */
+ public String message(String var) {
+ switch(this) {
+ case NULLVALUE : return "variable '" + var + "' is null";
+ case REDEFINED : return "variable '" + var + "' is already defined";
+ case UNDEFINED :
+ default: return "variable '" + var + "' is undefined";
+ }
+ }
+ }
/**
* Thrown when a variable is unknown.
@@ -477,7 +503,20 @@ public class JexlException extends RuntimeException {
/**
* Undefined variable flag.
*/
- private final boolean undefined;
+ private final VariableIssue issue;
+
+ /**
+ * Creates a new Variable exception instance.
+ *
+ * @param node the offending ASTnode
+ * @param var the unknown variable
+ * @param vi the variable issue
+ */
+ public Variable(JexlNode node, String var, VariableIssue vi) {
+ super(node, var, null);
+ issue = vi;
+ }
+
/**
* Creates a new Variable exception instance.
*
@@ -486,8 +525,7 @@ public class JexlException extends RuntimeException {
* @param undef whether the variable is undefined or evaluated as null
*/
public Variable(JexlNode node, String var, boolean undef) {
- super(node, var, null);
- undefined = undef;
+ this(node, var, undef ? VariableIssue.UNDEFINED : VariableIssue.NULLVALUE);
}
/**
@@ -496,7 +534,7 @@ public class JexlException extends RuntimeException {
* @return true if undefined, false otherwise
*/
public boolean isUndefined() {
- return undefined;
+ return issue == VariableIssue.UNDEFINED;
}
/**
@@ -508,7 +546,7 @@ public class JexlException extends RuntimeException {
@Override
protected String detailedMessage() {
- return (undefined? "undefined" : "null value") + " variable " + getVariable();
+ return issue.message(getVariable());
}
}
@@ -520,15 +558,22 @@ public class JexlException extends RuntimeException {
* @param undef whether the variable is null or undefined
* @return the error message
*/
+ @Deprecated
public static String variableError(JexlNode node, String variable, boolean undef) {
+ return variableError(node, variable, undef? VariableIssue.UNDEFINED : VariableIssue.NULLVALUE);
+ }
+
+ /**
+ * Generates a message for a variable error.
+ *
+ * @param node the node where the error occurred
+ * @param variable the variable
+ * @param issue the variable kind of issue
+ * @return the error message
+ */
+ public static String variableError(JexlNode node, String variable, VariableIssue issue) {
StringBuilder msg = errorAt(node);
- if (undef) {
- msg.append("undefined");
- } else {
- msg.append("null value");
- }
- msg.append(" variable ");
- msg.append(variable);
+ msg.append(issue.message(variable));
return msg.toString();
}
@@ -598,7 +643,7 @@ public class JexlException extends RuntimeException {
@Override
protected String detailedMessage() {
- return (undefined? "undefined" : "null value") + " property " + getProperty();
+ return (undefined? "undefined" : "null value") + " property '" + getProperty() + "'";
}
}
@@ -617,8 +662,9 @@ public class JexlException extends RuntimeException {
} else {
msg.append("null value");
}
- msg.append(" property ");
+ msg.append(" property '");
msg.append(pty);
+ msg.append('\'');
return msg.toString();
}
diff --git a/src/main/java/org/apache/commons/jexl3/JexlFeatures.java b/src/main/java/org/apache/commons/jexl3/JexlFeatures.java
index 8e1a6cf..12d1e6e 100644
--- a/src/main/java/org/apache/commons/jexl3/JexlFeatures.java
+++ b/src/main/java/org/apache/commons/jexl3/JexlFeatures.java
@@ -29,6 +29,7 @@ import java.util.TreeSet;
* <li>Registers: register syntax (#number), used internally for {g,s}etProperty
* <li>Reserved Names: a set of reserved variable names that can not be used as local variable (or parameter) names
* <li>Global Side Effect : assigning/modifying values on global variables (=, += , -=, ...)
+ * <li>Lexical: lexical scope, prevents redefining local variables
* <li>Side Effect : assigning/modifying values on any variables or left-value
* <li>Constant Array Reference: ensures array references only use constants;they should be statically solvable.
* <li>New Instance: creating an instance using new(...)
@@ -51,7 +52,7 @@ public final class JexlFeatures {
private static final String[] F_NAMES = {
"register", "reserved variable", "local variable", "assign/modify",
"global assign/modify", "array reference", "create instance", "loop", "function",
- "method call", "set/map/array literal", "pragma", "annotation", "script"
+ "method call", "set/map/array literal", "pragma", "annotation", "script", "lexical"
};
/** Registers feature ordinal. */
private static final int REGISTER = 0;
@@ -81,6 +82,8 @@ public final class JexlFeatures {
public static final int ANNOTATION = 12;
/** Script feature ordinal. */
public static final int SCRIPT = 13;
+ /** Script feature ordinal. */
+ public static final int LEXICAL = 14;
/**
* Creates an all-features-enabled instance.
@@ -471,4 +474,20 @@ public final class JexlFeatures {
return !getFeature(SCRIPT);
}
+ /**
+ * Sets whether syntactic lexical mode is enabled.
+ *
+ * @param flag true means syntactic lexical function scope is in effect, false implies non-lexical scoping
+ * @return this features instance
+ */
+ public JexlFeatures lexical(boolean flag) {
+ setFeature(LEXICAL, flag);
+ return this;
+ }
+
+
+ /** @return whether lexical scope feature is enabled */
+ public boolean isLexical() {
+ return getFeature(LEXICAL);
+ }
}
diff --git a/src/main/java/org/apache/commons/jexl3/JexlOptions.java b/src/main/java/org/apache/commons/jexl3/JexlOptions.java
new file mode 100644
index 0000000..4d1ba6e
--- /dev/null
+++ b/src/main/java/org/apache/commons/jexl3/JexlOptions.java
@@ -0,0 +1,192 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.jexl3;
+
+import java.math.MathContext;
+
+/**
+ * Flags and properties that can alter the evaluation behavior.
+ * The flags, briefly explained, are the following:
+ * <ul>
+ * <li>silent: whether errors throw exception</li>
+ * <li>safe: whether navigation through null is an error</li>
+ * <li>cancellable: whether thread interruption is an error</li>
+ * <li>lexical: whether redefining local variables is an error</li>
+ * <li>lexicalShade: whether local variables shade global ones even outside their scope</li>
+ * <li>strict: whether unknown or unsolvable identifiers are errors</li>
+ * <li>strictArithmetic: whether null as operand is an error</li>
+ * </ul>
+ * The sensible default is cancellable, strict and strictArithmetic.
+ * <p>This interface replaces the now deprecated JexlEngine.Options.
+ * @since 3.2
+ */
+public interface JexlOptions {
+ /**
+ * The MathContext instance used for +,-,/,*,% operations on big decimals.
+ * @return the math context
+ */
+ MathContext getMathContext();
+
+ /**
+ * The BigDecimal scale used for comparison and coercion operations.
+ * @return the scale
+ */
+ int getMathScale();
+
+ /**
+ * Checks whether evaluation will attempt resolving antish variable names.
+ * @return true if antish variables are solved, false otherwise
+ */
+ boolean isAntish();
+
+ /**
+ * Checks whether evaluation will throw JexlException.Cancel (true) or
+ * return null (false) if interrupted.
+ * @return true when cancellable, false otherwise
+ */
+ boolean isCancellable();
+
+ /**
+ * Checks whether runtime variable scope is lexical.
+ * <p>If true, lexical scope applies to local variables and parameters.
+ * Redefining a variable in the same lexical unit will generate errors.
+ * @return true if scope is lexical, false otherwise
+ */
+ boolean isLexical();
+
+ /**
+ * Checks whether local variables shade global ones.
+ * <p>After a symbol is defined as local, dereferencing it outside its
+ * scope will trigger an error instead of seeking a global variable of the
+ * same name. To further reduce potential naming ambiguity errors,
+ * global variables (ie non local) must be declared to be assigned (@link JexlContext#has(String) )
+ * when this flag is on; attempting to set an undeclared global variables will
+ * raise an error.
+ * @return true if lexical shading is applied, false otherwise
+ */
+ boolean isLexicalShade();
+
+ /**
+ * Checks whether the engine considers null in navigation expression as
+ * errors during evaluation..
+ * @return true if safe, false otherwise
+ */
+ boolean isSafe();
+
+ /**
+ * Checks whether the engine will throw a {@link JexlException} when an
+ * error is encountered during evaluation.
+ * @return true if silent, false otherwise
+ */
+ boolean isSilent();
+
+ /**
+ * Checks whether the engine considers unknown variables, methods and
+ * constructors as errors during evaluation.
+ * @return true if strict, false otherwise
+ */
+ boolean isStrict();
+
+ /**
+ * Checks whether the arithmetic triggers errors during evaluation when null
+ * is used as an operand.
+ * @return true if strict, false otherwise
+ */
+ boolean isStrictArithmetic();
+
+ /**
+ * Sets whether the engine will attempt solving antish variable names from
+ * context.
+ * @param flag true if antish variables are solved, false otherwise
+ */
+ void setAntish(boolean flag);
+
+ /**
+ * Sets whether the engine will throw JexlException.Cancel (true) or return
+ * null (false) when interrupted during evaluation.
+ * @param flag true when cancellable, false otherwise
+ */
+ void setCancellable(boolean flag);
+
+ /**
+ * Sets whether the engine uses a strict block lexical scope during
+ * evaluation.
+ * @param flag true if lexical scope is used, false otherwise
+ */
+ void setLexical(boolean flag);
+
+ /**
+ * Sets whether the engine strictly shades global variables.
+ * Local symbols shade globals after definition and creating global
+ * variables is prohibited evaluation.
+ * @param flag true if creation is allowed, false otherwise
+ */
+ void setLexicalShade(boolean flag);
+
+ /**
+ * Sets the arithmetic math context.
+ * @param mcontext the context
+ */
+ void setMathContext(MathContext mcontext);
+
+ /**
+ * Sets the arithmetic math scale.
+ * @param mscale the scale
+ */
+ void setMathScale(int mscale);
+
+ /**
+ * Set options from engine.
+ * @param jexl the engine
+ */
+ void setOptions(JexlEngine jexl);
+
+ /**
+ * Sets whether the engine considers null in navigation expression as errors
+ * during evaluation.
+ * @param flag true if safe, false otherwise
+ */
+ void setSafe(boolean flag);
+
+ /**
+ * Sets whether the engine will throw a {@link JexlException} when an error
+ * is encountered during evaluation.
+ * @param flag true if silent, false otherwise
+ */
+ void setSilent(boolean flag);
+
+ /**
+ * Sets whether the engine considers unknown variables, methods and
+ * constructors as errors during evaluation.
+ * @param flag true if strict, false otherwise
+ */
+ void setStrict(boolean flag);
+
+ /**
+ * Sets the strict arithmetic flag.
+ * @param stricta true or false
+ */
+ void setStrictArithmetic(boolean stricta);
+
+ /**
+ * Creates a copy of this instance.
+ * @return a copy
+ */
+ JexlOptions copy();
+
+}
\ No newline at end of file
diff --git a/src/main/java/org/apache/commons/jexl3/internal/Closure.java b/src/main/java/org/apache/commons/jexl3/internal/Closure.java
index a346fa7..839b1a0 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/Closure.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/Closure.java
@@ -18,15 +18,13 @@ package org.apache.commons.jexl3.internal;
import org.apache.commons.jexl3.JexlContext;
import org.apache.commons.jexl3.parser.ASTJexlLambda;
-import org.apache.commons.jexl3.parser.JexlNode;
-
/**
* A Script closure.
*/
public class Closure extends Script {
/** The frame. */
- protected final Scope.Frame frame;
+ protected final Frame frame;
/**
* Creates a closure.
@@ -45,7 +43,7 @@ public class Closure extends Script {
*/
protected Closure(Script base, Object[] args) {
super(base.jexl, base.source, base.script);
- Scope.Frame sf = (base instanceof Closure) ? ((Closure) base).frame : null;
+ Frame sf = (base instanceof Closure) ? ((Closure) base).frame : null;
frame = sf == null
? script.createFrame(args)
: sf.assign(args);
@@ -120,26 +118,18 @@ public class Closure extends Script {
@Override
public Object execute(JexlContext context, Object... args) {
- Scope.Frame callFrame = null;
- if (frame != null) {
- callFrame = frame.assign(args);
- }
- Interpreter interpreter = createInterpreter(context, callFrame);
- JexlNode block = script.jjtGetChild(script.jjtGetNumChildren() - 1);
- return interpreter.interpret(block);
+ Frame local = frame != null? frame.assign(args) : null;
+ Interpreter interpreter = createInterpreter(context, local);
+ return interpreter.runClosure(this, null);
}
@Override
public Callable callable(JexlContext context, Object... args) {
- Scope.Frame local = null;
- if (frame != null) {
- local = frame.assign(args);
- }
+ Frame local = frame != null? frame.assign(args) : null;
return new Callable(createInterpreter(context, local)) {
@Override
public Object interpret() {
- JexlNode block = script.jjtGetChild(script.jjtGetNumChildren() - 1);
- return interpreter.interpret(block);
+ return interpreter.runClosure(Closure.this, null);
}
};
}
diff --git a/src/main/java/org/apache/commons/jexl3/internal/Engine.java b/src/main/java/org/apache/commons/jexl3/internal/Engine.java
index b8c77a8..3205cef 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/Engine.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/Engine.java
@@ -23,6 +23,7 @@ import org.apache.commons.jexl3.JexlEngine;
import org.apache.commons.jexl3.JexlException;
import org.apache.commons.jexl3.JexlFeatures;
import org.apache.commons.jexl3.JexlInfo;
+import org.apache.commons.jexl3.JexlOptions;
import org.apache.commons.jexl3.JexlScript;
import org.apache.commons.jexl3.internal.introspection.SandboxUberspect;
import org.apache.commons.jexl3.internal.introspection.Uberspect;
@@ -48,7 +49,6 @@ import java.util.Set;
import java.nio.charset.Charset;
import java.util.concurrent.atomic.AtomicBoolean;
-
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@@ -117,14 +117,6 @@ public class Engine extends JexlEngine {
*/
protected final boolean debug;
/**
- * The atomic parsing flag; true whilst parsing.
- */
- protected final AtomicBoolean parsing = new AtomicBoolean(false);
- /**
- * The default charset.
- */
- protected final Charset charset;
- /**
* The set of default script parsing features.
*/
protected final JexlFeatures scriptFeatures;
@@ -133,6 +125,14 @@ public class Engine extends JexlEngine {
*/
protected final JexlFeatures expressionFeatures;
/**
+ * The default charset.
+ */
+ protected final Charset charset;
+ /**
+ * The atomic parsing flag; true whilst parsing.
+ */
+ protected final AtomicBoolean parsing = new AtomicBoolean(false);
+ /**
* The {@link Parser}; when parsing expressions, this engine uses the parser if it
* is not already in use otherwise it will create a new temporary one.
*/
@@ -153,6 +153,10 @@ public class Engine extends JexlEngine {
* Collect all or only dot references.
*/
protected final boolean collectAll;
+ /**
+ * A cached version of the options.
+ */
+ protected final JexlOptions options = new org.apache.commons.jexl3.internal.Options();
/**
* Creates an engine with default arguments.
@@ -200,17 +204,9 @@ public class Engine extends JexlEngine {
if (uberspect == null) {
throw new IllegalArgumentException("uberspect can not be null");
}
+ options.setOptions(this);
}
- /**
- * Solves an optional option.
- * @param conf the option as configured, may be null
- * @param def the default value if null
- * @return true or false
- */
- private boolean option(Boolean conf, boolean def) {
- return conf == null? def : conf;
- }
/**
* Gets the default instance of Uberspect.
@@ -252,10 +248,15 @@ public class Engine extends JexlEngine {
@Override
public boolean isStrict() {
- return strict;
+ return this.strict;
}
@Override
+ public boolean isSafe() {
+ return this.safe;
+ }
+
+ @Override
public boolean isCancellable() {
return this.cancellable;
}
@@ -290,7 +291,47 @@ public class Engine extends JexlEngine {
public Charset getCharset() {
return charset;
}
+
+ /**
+ * Extracts the engine evaluation options from context.
+ * @param context the context
+ * @return the options if any
+ */
+ JexlOptions options(JexlContext context) {
+ JexlOptions jexlo = null;
+ if (context instanceof JexlContext.OptionsHandle) {
+ jexlo = ((JexlContext.OptionsHandle) context).getEngineOptions();
+ }
+ if (jexlo == null) {
+ jexlo = options;
+ /** The following block for compatibility between 3.1 and 3.2*/
+ if (context instanceof JexlEngine.Options) {
+ jexlo = jexlo.copy();
+ JexlEngine jexl = this;
+ JexlEngine.Options opts = (JexlEngine.Options) context;
+ jexlo.setCancellable(option(opts.isCancellable(), jexl.isCancellable()));
+ jexlo.setSilent(option(opts.isSilent(), jexl.isSilent()));
+ jexlo.setStrict(option(opts.isStrict(), jexl.isStrict()));
+ JexlArithmetic jexla = jexl.getArithmetic();
+ jexlo.setStrictArithmetic(option(opts.isStrictArithmetic(), jexla.isStrict()));
+ jexlo.setMathContext(opts.getArithmeticMathContext());
+ jexlo.setMathScale(opts.getArithmeticMathScale());
+ }
+ }
+ return jexlo;
+ }
+ /**
+ * Solves an optional option.
+ * @param conf the option as configured, may be null
+ * @param def the default value if null, shall not be null
+ * @param <T> the option type
+ * @return conf or def
+ */
+ private static <T> T option(T conf, T def) {
+ return conf == null? def : conf;
+ }
+
@Override
public TemplateEngine createJxltEngine(boolean noScript, int cacheSize, char immediate, char deferred) {
return new TemplateEngine(this, noScript, cacheSize, immediate, deferred);
@@ -309,7 +350,7 @@ public class Engine extends JexlEngine {
* @param frame the interpreter frame
* @return an Interpreter
*/
- protected Interpreter createInterpreter(JexlContext context, Scope.Frame frame) {
+ protected Interpreter createInterpreter(JexlContext context, Frame frame) {
return new Interpreter(this, context, frame);
}
@@ -359,7 +400,7 @@ public class Engine extends JexlEngine {
final Scope scope = new Scope(null, "#0");
final ASTJexlScript script = parse(null, PROPERTY_FEATURES, src, scope);
final JexlNode node = script.jjtGetChild(0);
- final Scope.Frame frame = script.createFrame(bean);
+ final Frame frame = script.createFrame(bean);
final Interpreter interpreter = createInterpreter(context, frame);
return node.jjtAccept(interpreter, null);
} catch (JexlException xjexl) {
@@ -388,7 +429,7 @@ public class Engine extends JexlEngine {
final Scope scope = new Scope(null, "#0", "#1");
final ASTJexlScript script = parse(null, PROPERTY_FEATURES, src, scope);
final JexlNode node = script.jjtGetChild(0);
- final Scope.Frame frame = script.createFrame(bean, value);
+ final Frame frame = script.createFrame(bean, value);
final Interpreter interpreter = createInterpreter(context, frame);
node.jjtAccept(interpreter, null);
} catch (JexlException xjexl) {
diff --git a/src/main/java/org/apache/commons/jexl3/internal/Frame.java b/src/main/java/org/apache/commons/jexl3/internal/Frame.java
new file mode 100644
index 0000000..8de92e6
--- /dev/null
+++ b/src/main/java/org/apache/commons/jexl3/internal/Frame.java
@@ -0,0 +1,126 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.jexl3.internal;
+
+import java.util.Arrays;
+
+/**
+ * A call frame, created from a scope, stores the arguments and local variables in a "stack frame" (sic).
+ * @since 3.0
+ */
+public final class Frame {
+ /** The scope. */
+ private final Scope scope;
+ /** The actual stack frame. */
+ private final Object[] stack;
+ /** Number of curried parameters. */
+ private int curried = 0;
+
+ /**
+ * Creates a new frame.
+ * @param s the scope
+ * @param r the stack frame
+ * @param c the number of curried parameters
+ */
+ public Frame(Scope s, Object[] r, int c) {
+ scope = s;
+ stack = r;
+ curried = c;
+ }
+
+ /**
+ * Gets this script unbound parameters, i.e. parameters not bound through curry().
+ * @return the parameter names
+ */
+ public String[] getUnboundParameters() {
+ return scope.getParameters(curried);
+ }
+
+ /**
+ * Gets the scope.
+ * @return this frame scope
+ */
+ public Scope getScope() {
+ return scope;
+ }
+
+ @Override
+ public int hashCode() {
+ return Arrays.deepHashCode(this.stack);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ final Frame other = (Frame) obj;
+ return Arrays.deepEquals(this.stack, other.stack);
+ }
+
+ /**
+ * Gets a value.
+ * @param s the offset in this frame
+ * @return the stacked value
+ */
+ Object get(int s) {
+ return stack[s];
+ }
+
+ /**
+ * Whether this frame defines a symbol, ie declared it and assigned it a value.
+ * @param s the offset in this frame
+ * @return true if this symbol has been assigned a value, false otherwise
+ */
+ boolean has(int s) {
+ return s >= 0 && s < stack.length && stack[s] != Scope.UNDEFINED;
+ }
+
+ /**
+ * Sets a value.
+ * @param r the offset in this frame
+ * @param value the value to set in this frame
+ */
+ void set(int r, Object value) {
+ stack[r] = value;
+ }
+
+ /**
+ * Assign values to this frame.
+ * @param values the values
+ * @return this frame
+ */
+ Frame assign(Object... values) {
+ if (stack != null) {
+ int nparm = scope.getArgCount();
+ Object[] copy = stack.clone();
+ int ncopy = 0;
+ if (values != null && values.length > 0) {
+ ncopy = Math.min(nparm - curried, Math.min(nparm, values.length));
+ System.arraycopy(values, 0, copy, curried, ncopy);
+ }
+ // unbound parameters are defined as null
+ Arrays.fill(copy, curried + ncopy, nparm, null);
+ return new Frame(scope, copy, curried + ncopy);
+ }
+ return this;
+ }
+
+}
diff --git a/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java b/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java
index 9ef7b4a..86eeb75 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java
@@ -117,8 +117,10 @@ public class Interpreter extends InterpreterBase {
/** Frame height. */
protected int fp = 0;
/** Symbol values. */
- protected final Scope.Frame frame;
-
+ protected final Frame frame;
+ /** Block micro-frames. */
+ protected LexicalScope block = null;
+
/**
* The thread local interpreter.
*/
@@ -131,7 +133,7 @@ public class Interpreter extends InterpreterBase {
* @param aContext the context to evaluate expression
* @param eFrame the interpreter evaluation frame
*/
- protected Interpreter(Engine engine, JexlContext aContext, Scope.Frame eFrame) {
+ protected Interpreter(Engine engine, JexlContext aContext, Frame eFrame) {
super(engine, aContext);
this.frame = eFrame;
}
@@ -156,7 +158,7 @@ public class Interpreter extends InterpreterBase {
INTER.set(inter);
return pinter;
}
-
+
/**
* Interpret the given script/expression.
* <p>
@@ -601,7 +603,44 @@ public class Interpreter extends InterpreterBase {
}
@Override
+ protected Object visit(ASTVar node, Object data) {
+ int symbol = node.getSymbol();
+ // if we have a var, we have a scope thus a frame
+ Object value;
+ if (frame.has(symbol)) {
+ value = frame.get(symbol);
+ } else {
+ frame.set(symbol, null);
+ value = null;
+ }
+ if (options.isLexical() && !block.declareSymbol(symbol)) {
+ return redefinedVariable(node, node.getName());
+ }
+ return value;
+ }
+
+ @Override
protected Object visit(ASTBlock node, Object data) {
+ int cnt = node.getSymbolCount();
+ if (!options.isLexical() || cnt <= 0) {
+ return visitBlock(node, data);
+ }
+ LexicalScope lexical = block;
+ try {
+ block = new LexicalScope(lexical);
+ return visitBlock(node, data);
+ } finally {
+ block = lexical;
+ }
+ }
+
+ /**
+ * Base visitation for blocks.
+ * @param node the block
+ * @param data the usual data
+ * @return the result of the last expression evaluation
+ */
+ private Object visitBlock(ASTBlock node, Object data) {
int numChildren = node.jjtGetNumChildren();
Object result = null;
for (int i = 0; i < numChildren; i++) {
@@ -634,27 +673,36 @@ public class Interpreter extends InterpreterBase {
/* first objectNode is the loop variable */
ASTReference loopReference = (ASTReference) node.jjtGetChild(0);
ASTIdentifier loopVariable = (ASTIdentifier) loopReference.jjtGetChild(0);
- int symbol = loopVariable.getSymbol();
- /* second objectNode is the variable to iterate */
- Object iterableValue = node.jjtGetChild(1).jjtAccept(this, data);
- // make sure there is a value to iterate upon
- if (iterableValue != null) {
- /* third objectNode is the statement to execute */
- JexlNode statement = node.jjtGetNumChildren() >= 3? node.jjtGetChild(2) : null;
- // get an iterator for the collection/array etc via the introspector.
- Object forEach = null;
- try {
+ final int symbol = loopVariable.getSymbol();
+ final LexicalScope lexical = block;
+ if (options.isLexical()) {
+ // the iteration variable can not be declared in parent block
+ if (symbol >= 0 && block.hasSymbol(symbol)) {
+ return redefinedVariable(node, loopVariable.getName());
+ }
+ // create lexical frame
+ block = new LexicalScope(lexical);
+ }
+ Object forEach = null;
+ try {
+ /* second objectNode is the variable to iterate */
+ Object iterableValue = node.jjtGetChild(1).jjtAccept(this, data);
+ // make sure there is a value to iterate upon
+ if (iterableValue != null) {
+ /* third objectNode is the statement to execute */
+ JexlNode statement = node.jjtGetNumChildren() >= 3 ? node.jjtGetChild(2) : null;
+ // get an iterator for the collection/array etc via the introspector.
forEach = operators.tryOverload(node, JexlOperator.FOR_EACH, iterableValue);
Iterator<?> itemsIterator = forEach instanceof Iterator
- ? (Iterator<?>) forEach
- : uberspect.getIterator(iterableValue);
+ ? (Iterator<?>) forEach
+ : uberspect.getIterator(iterableValue);
if (itemsIterator != null) {
while (itemsIterator.hasNext()) {
cancelCheck(node);
// set loopVariable to value of iterator
Object value = itemsIterator.next();
if (symbol < 0) {
- context.set(loopVariable.getName(), value);
+ setContextVariable(node, loopVariable.getName(), value);
} else {
frame.set(symbol, value);
}
@@ -670,9 +718,13 @@ public class Interpreter extends InterpreterBase {
}
}
}
- } finally {
- // closeable iterator handling
- closeIfSupported(forEach);
+ }
+ } finally {
+ // closeable iterator handling
+ closeIfSupported(forEach);
+ // restore lexical frame
+ if (lexical != null && block != null) {
+ block = lexical;
}
}
return result;
@@ -864,7 +916,15 @@ public class Interpreter extends InterpreterBase {
@Override
protected Object visit(ASTTernaryNode node, Object data) {
- Object condition = node.jjtGetChild(0).jjtAccept(this, data);
+ Object condition;
+ try {
+ condition = node.jjtGetChild(0).jjtAccept(this, data);
+ } catch(JexlException xany) {
+ if (!(xany.getCause() instanceof JexlArithmetic.NullOperand)) {
+ throw xany;
+ }
+ condition = null;
+ }
// ternary as in "x ? y : z"
if (node.jjtGetNumChildren() == 3) {
if (condition != null && arithmetic.toBoolean(condition)) {
@@ -883,7 +943,15 @@ public class Interpreter extends InterpreterBase {
@Override
protected Object visit(ASTNullpNode node, Object data) {
- Object lhs = node.jjtGetChild(0).jjtAccept(this, data);
+ Object lhs;
+ try {
+ lhs = node.jjtGetChild(0).jjtAccept(this, data);
+ } catch(JexlException xany) {
+ if (!(xany.getCause() instanceof JexlArithmetic.NullOperand)) {
+ throw xany;
+ }
+ lhs = null;
+ }
// null elision as in "x ?? z"
return lhs != null? lhs : node.jjtGetChild(1).jjtAccept(this, data);
}
@@ -908,19 +976,43 @@ public class Interpreter extends InterpreterBase {
}
}
+ /**
+ * Runs a closure.
+ * @param closure the closure
+ * @param data the usual data
+ * @return the closure return value
+ */
+ protected Object runClosure(Closure closure, Object data) {
+ ASTJexlScript script = closure.getScript();
+ final LexicalScope lexical = block;
+ block = new LexicalScope(frame, null);
+ try {
+ JexlNode body = script.jjtGetChild(script.jjtGetNumChildren() - 1);
+ return interpret(body);
+ } finally {
+ block = lexical;
+ }
+ }
+
@Override
- protected Object visit(ASTJexlScript node, Object data) {
- if (node instanceof ASTJexlLambda && !((ASTJexlLambda) node).isTopLevel()) {
- return new Closure(this, (ASTJexlLambda) node);
+ protected Object visit(ASTJexlScript script, Object data) {
+ if (script instanceof ASTJexlLambda && !((ASTJexlLambda) script).isTopLevel()) {
+ return new Closure(this, (ASTJexlLambda) script);
} else {
- final int numChildren = node.jjtGetNumChildren();
- Object result = null;
- for (int i = 0; i < numChildren; i++) {
- JexlNode child = node.jjtGetChild(i);
- result = child.jjtAccept(this, data);
- cancelCheck(child);
+ final LexicalScope lexical = block;
+ block = new LexicalScope(frame, null);
+ try {
+ final int numChildren = script.jjtGetNumChildren();
+ Object result = null;
+ for (int i = 0; i < numChildren; i++) {
+ JexlNode child = script.jjtGetChild(i);
+ result = child.jjtAccept(this, data);
+ cancelCheck(child);
+ }
+ return result;
+ } finally {
+ block = lexical;
}
- return result;
}
}
@@ -930,39 +1022,11 @@ public class Interpreter extends InterpreterBase {
}
@Override
- protected Object visit(ASTVar node, Object data) {
- int symbol = node.getSymbol();
- // if we have a var, we have a scope thus a frame
- if (frame.has(symbol)) {
- return frame.get(symbol);
- } else {
- frame.set(symbol, null);
- return null;
- }
- }
-
- @Override
- protected Object visit(ASTIdentifier node, Object data) {
- cancelCheck(node);
- String name = node.getName();
- if (data == null) {
- int symbol = node.getSymbol();
- // if we have a symbol, we have a scope thus a frame
- if (symbol >= 0 && frame.has(symbol)) {
- return frame.get(symbol);
- }
- Object value = context.get(name);
- if (value == null
- && !(node.jjtGetParent() instanceof ASTReference)
- && !(context.has(name))) {
- return jexl.safe
- ? null
- : unsolvableVariable(node, name, !(node.getSymbol() >= 0 || context.has(name)));
- }
- return value;
- } else {
- return getAttribute(data, name, node);
- }
+ protected Object visit(ASTIdentifier identifier, Object data) {
+ cancelCheck(identifier);
+ return data != null
+ ? getAttribute(data, identifier.getName(), identifier)
+ : getVariable(frame, block, identifier);
}
@Override
@@ -1036,7 +1100,7 @@ public class Interpreter extends InterpreterBase {
JexlNode objectNode = null;
JexlNode ptyNode = null;
StringBuilder ant = null;
- boolean antish = !(parent instanceof ASTReference);
+ boolean antish = !(parent instanceof ASTReference) && options.isAntish();
int v = 1;
main:
for (int c = 0; c < numChildren; c++) {
@@ -1123,7 +1187,7 @@ public class Interpreter extends InterpreterBase {
// am I the left-hand side of a safe op ?
if (object == null) {
if (ptyNode != null) {
- if (ptyNode.isSafeLhs(jexl.safe)) {
+ if (ptyNode.isSafeLhs(isSafe())) {
return null;
}
if (ant != null) {
@@ -1134,7 +1198,7 @@ public class Interpreter extends InterpreterBase {
return unsolvableProperty(node, stringifyProperty(ptyNode), ptyNode == objectNode, null);
}
if (antish) {
- if (node.isSafeLhs(jexl.safe)) {
+ if (node.isSafeLhs(isSafe())) {
return null;
}
String aname = ant != null ? ant.toString() : "?";
@@ -1203,21 +1267,36 @@ public class Interpreter extends InterpreterBase {
cancelCheck(node);
// left contains the reference to assign to
final JexlNode left = node.jjtGetChild(0);
- // right is the value expression to assign
- Object right = node.jjtGetChild(1).jjtAccept(this, data);
+ ASTIdentifier var = null;
Object object = null;
int symbol = -1;
- boolean antish = true;
- // 0: determine initial object & property:
- final int last = left.jjtGetNumChildren() - 1;
+ // check var decl with assign is ok
if (left instanceof ASTIdentifier) {
- ASTIdentifier var = (ASTIdentifier) left;
+ var = (ASTIdentifier) left;
symbol = var.getSymbol();
+ if (symbol >= 0 && options.isLexical()) {
+ if (var instanceof ASTVar) {
+ if (!block.declareSymbol(symbol)) {
+ return redefinedVariable(var, var.getName());
+ }
+ // if not in lexical block, undefined if (in its symbol) shade
+ } else if (!block.hasSymbol(symbol) && options.isLexicalShade()) {
+ return undefinedVariable(var, var.getName());
+ }
+ }
+ }
+ boolean antish = options.isAntish();
+ // 0: determine initial object & property:
+ final int last = left.jjtGetNumChildren() - 1;
+ // right is the value expression to assign
+ Object right = node.jjtGetChild(1).jjtAccept(this, data);
+ // a (var?) v = ... expression
+ if (var != null) {
if (symbol >= 0) {
// check we are not assigning a symbol itself
if (last < 0) {
if (assignop != null) {
- Object self = getVariable(frame, var);
+ Object self = getVariable(frame, block, var);
right = operators.tryAssignOverload(node, assignop, self, right);
if (right == JexlOperator.ASSIGN) {
return self;
@@ -1230,7 +1309,7 @@ public class Interpreter extends InterpreterBase {
}
return right; // 1
}
- object = getVariable(frame, var);
+ object = getVariable(frame, block, var);
// top level is a symbol, can not be an antish var
antish = false;
} else {
@@ -1243,11 +1322,7 @@ public class Interpreter extends InterpreterBase {
return self;
}
}
- try {
- context.set(var.getName(), right);
- } catch (UnsupportedOperationException xsupport) {
- throw new JexlException(node, "context is readonly", xsupport);
- }
+ setContextVariable(node, var.getName(), right);
return right; // 2
}
object = context.get(var.getName());
@@ -1326,11 +1401,7 @@ public class Interpreter extends InterpreterBase {
return self;
}
}
- try {
- context.set(ant.toString(), right);
- } catch (UnsupportedOperationException xsupport) {
- throw new JexlException(node, "context is readonly", xsupport);
- }
+ setContextVariable(propertyNode, ant.toString(), right);
return right; // 3
}
// property of an object ?
@@ -1401,7 +1472,7 @@ public class Interpreter extends InterpreterBase {
object = data;
if (object == null) {
// no object, we fail
- return node.isSafeLhs(jexl.safe)
+ return node.isSafeLhs(isSafe())
? null
: unsolvableMethod(methodNode, "<null>.<?>(...)");
}
@@ -1416,7 +1487,7 @@ public class Interpreter extends InterpreterBase {
for (int a = 1; a < node.jjtGetNumChildren(); ++a) {
if (result == null) {
// no method, we fail// variable unknown in context and not a local
- return node.isSafeLhs(jexl.safe)
+ return node.isSafeLhs(isSafe())
? null
: unsolvableMethod(methodNode, "<?>.<null>(...)");
}
@@ -1470,7 +1541,7 @@ public class Interpreter extends InterpreterBase {
functor = null;
// is it a global or local variable ?
if (target == context) {
- if (symbol >= 0 && frame.has(symbol)) {
+ if (frame != null && frame.has(symbol)) {
functor = frame.get(symbol);
isavar = functor != null;
} else if (context.has(methodName)) {
@@ -1491,7 +1562,7 @@ public class Interpreter extends InterpreterBase {
symbol = -1 - 1; // -2;
methodName = null;
cacheable = false;
- } else if (!node.isSafeLhs(jexl.safe)) {
+ } else if (!node.isSafeLhs(isSafe())) {
return unsolvableMethod(node, "?(...)");
} else {
// safe lhs
@@ -1596,7 +1667,7 @@ public class Interpreter extends InterpreterBase {
}
}
// we have either evaluated and returned or no method was found
- return node.isSafeLhs(jexl.safe)
+ return node.isSafeLhs(isSafe())
? null
: unsolvableMethod(node, methodName, argv);
} catch (JexlException.TryFailed xany) {
@@ -1719,7 +1790,7 @@ public class Interpreter extends InterpreterBase {
// are we evaluating the block ?
final int last = stmt.jjtGetNumChildren() - 1;
if (index == last) {
- JexlNode block = stmt.jjtGetChild(last);
+ JexlNode cblock = stmt.jjtGetChild(last);
// if the context has changed, might need a new interpreter
final JexlArithmetic jexla = arithmetic.options(context);
if (jexla != arithmetic) {
@@ -1729,13 +1800,13 @@ public class Interpreter extends InterpreterBase {
);
}
Interpreter ii = new Interpreter(Interpreter.this, jexla);
- Object r = block.jjtAccept(ii, data);
+ Object r = cblock.jjtAccept(ii, data);
if (ii.isCancelled()) {
Interpreter.this.cancel();
}
return r;
} else {
- return block.jjtAccept(Interpreter.this, data);
+ return cblock.jjtAccept(Interpreter.this, data);
}
}
// tracking whether we processed the annotation
diff --git a/src/main/java/org/apache/commons/jexl3/internal/InterpreterBase.java b/src/main/java/org/apache/commons/jexl3/internal/InterpreterBase.java
index 399ee3a..832bd10 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/InterpreterBase.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/InterpreterBase.java
@@ -24,7 +24,9 @@ import org.apache.commons.jexl3.JexlArithmetic;
import org.apache.commons.jexl3.JexlContext;
import org.apache.commons.jexl3.JexlEngine;
import org.apache.commons.jexl3.JexlException;
+import org.apache.commons.jexl3.JexlException.VariableIssue;
import org.apache.commons.jexl3.JexlOperator;
+import org.apache.commons.jexl3.JexlOptions;
import org.apache.commons.jexl3.introspection.JexlMethod;
import org.apache.commons.jexl3.introspection.JexlPropertyGet;
import org.apache.commons.jexl3.introspection.JexlPropertySet;
@@ -56,6 +58,8 @@ public abstract class InterpreterBase extends ParserVisitor {
protected final JexlArithmetic arithmetic;
/** The context to store/retrieve variables. */
protected final JexlContext context;
+ /** The options. */
+ protected final JexlOptions options;
/** Cache executors. */
protected final boolean cache;
/** Cancellation support. */
@@ -70,7 +74,7 @@ public abstract class InterpreterBase extends ParserVisitor {
protected Map<String, Object> functors;
/** The operators evaluation delegate. */
protected final Operators operators;
-
+
/**
* Creates an interpreter base.
* @param engine the engine creating this interpreter
@@ -83,10 +87,11 @@ public abstract class InterpreterBase extends ParserVisitor {
this.context = aContext != null ? aContext : Engine.EMPTY_CONTEXT;
this.cache = engine.cache != null;
JexlArithmetic jexla = jexl.arithmetic;
- this.arithmetic = jexla.options(context);
+ this.options = jexl.options(context);
+ this.arithmetic = jexla.options(options);
if (arithmetic != jexla && !arithmetic.getClass().equals(jexla.getClass())) {
logger.warn("expected arithmetic to be " + jexla.getClass().getSimpleName()
- + ", got " + arithmetic.getClass().getSimpleName()
+ + ", got " + arithmetic.getClass().getSimpleName()
);
}
if (this.context instanceof JexlContext.NamespaceResolver) {
@@ -98,7 +103,7 @@ public abstract class InterpreterBase extends ParserVisitor {
this.functors = null;
this.operators = new Operators(this);
}
-
+
/**
* Copy constructor.
* @param ii the base to copy
@@ -115,6 +120,7 @@ public abstract class InterpreterBase extends ParserVisitor {
functions = ii.functions;
functors = ii.functors;
operators = ii.operators;
+ options = new org.apache.commons.jexl3.internal.Options(ii.options);
}
@@ -188,9 +194,13 @@ public abstract class InterpreterBase extends ParserVisitor {
} else if (namespace instanceof Class<?> || namespace instanceof String) {
// attempt to reuse last ctor cached in volatile JexlNode.value
if (cached instanceof JexlMethod) {
- Object eval = ((JexlMethod) cached).tryInvoke(null, context);
- if (JexlEngine.TRY_FAILED != eval) {
- functor = eval;
+ try {
+ Object eval = ((JexlMethod) cached).tryInvoke(null, context);
+ if (JexlEngine.TRY_FAILED != eval) {
+ functor = eval;
+ }
+ } catch (JexlException.TryFailed xtry) {
+ throw new JexlException(node, "unable to instantiate namespace " + prefix, xtry.getCause());
}
}
if (functor == null) {
@@ -227,15 +237,53 @@ public abstract class InterpreterBase extends ParserVisitor {
/**
* Gets a value of a defined local variable or from the context.
* @param frame the local frame
- * @param node the variable node
+ * @param block the lexical block if any
+ * @param identifier the variable node
* @return the value
*/
- protected Object getVariable(Scope.Frame frame, ASTIdentifier node) {
- int symbol = node.getSymbol();
- if (frame.has(symbol)) {
- return frame.get(symbol);
+ protected Object getVariable(Frame frame, LexicalScope block, ASTIdentifier identifier) {
+ int symbol = identifier.getSymbol();
+ // if we have a symbol, we have a scope thus a frame
+ if (symbol >= 0) {
+ if (frame.has(symbol)) {
+ if (options.isLexical()) {
+ // if not in lexical block, undefined if (in its symbol) shade
+ if (!block.hasSymbol(symbol) && options.isLexicalShade()) {
+ return undefinedVariable(identifier, identifier.getName());
+ }
+ }
+ return frame.get(symbol);
+ }
+ }
+ String name = identifier.getName();
+ Object value = context.get(name);
+ if (value == null
+ && !(identifier.jjtGetParent() instanceof ASTReference)
+ && !(context.has(name))) {
+ return isSafe()
+ ? null
+ : unsolvableVariable(identifier, name, true); // undefined
+ }
+ return value;
+ }
+
+ /**
+ * Sets a variable in the global context.
+ * <p>If interpretation applies lexical shade, the variable must exist (ie
+ * the context has(...) method returns true) otherwise an error occurs.
+ * @param node the node
+ * @param name the variable name
+ * @param value the variable value
+ */
+ protected void setContextVariable(JexlNode node, String name, Object value) {
+ if (options.isLexicalShade() && !context.has(name)) {
+ throw new JexlException.Variable(node, name, true);
+ }
+ try {
+ context.set(name, value);
+ } catch (UnsupportedOperationException xsupport) {
+ throw new JexlException(node, "context is readonly", xsupport);
}
- return context.get(node.getName());
}
/**
@@ -243,14 +291,15 @@ public abstract class InterpreterBase extends ParserVisitor {
* @return true if strict engine, false otherwise
*/
protected boolean isStrictEngine() {
- if (this.context instanceof JexlEngine.Options) {
- JexlEngine.Options opts = (JexlEngine.Options) context;
- Boolean strict = opts.isStrict();
- if (strict != null) {
- return strict.booleanValue();
- }
- }
- return jexl.isStrict();
+ return options.isStrict();
+ }
+
+ /**
+ * Whether this interpreter ignores null in navigation expression as errors.
+ * @return true if safe, false otherwise
+ */
+ protected boolean isSafe() {
+ return options.isSafe();
}
/**
@@ -258,26 +307,14 @@ public abstract class InterpreterBase extends ParserVisitor {
* @return true if silent, false otherwise
*/
protected boolean isSilent() {
- if (this.context instanceof JexlEngine.Options) {
- JexlEngine.Options opts = (JexlEngine.Options) context;
- Boolean silent = opts.isSilent();
- if (silent != null) {
- return silent.booleanValue();
- }
- }
- return jexl.isSilent();
+ return options.isSilent();
}
-
- /** @return true if interrupt throws a JexlException.Cancel. */
+
+ /**
+ * @return true if interrupt throws a JexlException.Cancel.
+ */
protected boolean isCancellable() {
- if (this.context instanceof JexlEngine.Options) {
- JexlEngine.Options opts = (JexlEngine.Options) context;
- Boolean ocancellable = opts.isCancellable();
- if (ocancellable != null) {
- return ocancellable.booleanValue();
- }
- }
- return jexl.isCancellable();
+ return options.isCancellable();
}
/**
@@ -308,14 +345,44 @@ public abstract class InterpreterBase extends ParserVisitor {
* @return throws JexlException if strict and not silent, null otherwise
*/
protected Object unsolvableVariable(JexlNode node, String var, boolean undef) {
+ return variableError(node, var, undef? VariableIssue.UNDEFINED : VariableIssue.NULLVALUE);
+ }
+
+ /**
+ * Triggered when a variable is lexically known as undefined.
+ * @param node the node where the error originated from
+ * @param var the variable name
+ * @return throws JexlException if strict and not silent, null otherwise
+ */
+ protected Object undefinedVariable(JexlNode node, String var) {
+ return variableError(node, var, VariableIssue.UNDEFINED);
+ }
+
+ /**
+ * Triggered when a variable is lexically known as being redefined.
+ * @param node the node where the error originated from
+ * @param var the variable name
+ * @return throws JexlException if strict and not silent, null otherwise
+ */
+ protected Object redefinedVariable(JexlNode node, String var) {
+ return variableError(node, var, VariableIssue.REDEFINED);
+ }
+
+ /**
+ * Triggered when a variable generates an issue.
+ * @param node the node where the error originated from
+ * @param var the variable name
+ * @param issue the issue type
+ * @return throws JexlException if strict and not silent, null otherwise
+ */
+ protected Object variableError(JexlNode node, String var, VariableIssue issue) {
if (isStrictEngine() && !node.isTernaryProtected()) {
- throw new JexlException.Variable(node, var, undef);
+ throw new JexlException.Variable(node, var, issue);
} else if (logger.isDebugEnabled()) {
- logger.debug(JexlException.variableError(node, var, undef));
+ logger.debug(JexlException.variableError(node, var, issue));
}
return null;
}
-
/**
* Triggered when a method can not be resolved.
* @param node the node where the error originated from
diff --git a/src/main/java/org/apache/commons/jexl3/internal/LexicalScope.java b/src/main/java/org/apache/commons/jexl3/internal/LexicalScope.java
new file mode 100644
index 0000000..b9fc4bb
--- /dev/null
+++ b/src/main/java/org/apache/commons/jexl3/internal/LexicalScope.java
@@ -0,0 +1,118 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.jexl3.internal;
+
+import java.util.BitSet;
+
+/**
+ * The set of symbols declared in a lexical scope.
+ * <p>The symbol identifiers are determined by the functional scope.
+ */
+public final class LexicalScope {
+ /** Number of bits in a long. */
+ protected static final int LONGBITS = 64;
+ /** The mask of symbols in the frame. */
+ protected long symbols = 0L;
+ /** Symbols after 64. */
+ protected BitSet moreSymbols = null;
+ /** Previous block. */
+ protected final LexicalScope previous;
+
+ /**
+ * Default ctor.
+ * @param scope the previous scope
+ */
+ public LexicalScope(LexicalScope scope) {
+ this(null, scope);
+ }
+
+ /**
+ * Create a scope.
+ * @param frame the current execution frame
+ * @param scope the previous scope
+ */
+ public LexicalScope(Frame frame, LexicalScope scope) {
+ if (frame != null) {
+ int argc = frame.getScope().getArgCount();
+ for(int a = 0; a < argc; ++a) {
+ declareSymbol(a);
+ }
+ }
+ previous = scope;
+ }
+
+ /**
+ * Ensure more symbpls can be stored.
+ * @return the set of more symbols
+ */
+ final BitSet moreSymbols() {
+ if (moreSymbols == null) {
+ moreSymbols = new BitSet();
+ }
+ return moreSymbols;
+ }
+
+ /**
+ * Checks whether a symbol has already been declared.
+ * @param symbol the symbol
+ * @return true if declared, false otherwise
+ */
+ public boolean hasSymbol(int symbol) {
+ if (symbol < LONGBITS) {
+ return (symbols & (1L << symbol)) != 0L;
+ } else {
+ return moreSymbols == null ? false : moreSymbols.get(symbol - LONGBITS);
+ }
+ }
+
+ /**
+ * Declares a local symbol.
+ *
+ * @param symbol the symbol index
+ * @return true if was not already declared, false if lexical clash (error)
+ */
+ public boolean declareSymbol(int symbol) {
+ LexicalScope walk = previous;
+ while (walk != null) {
+ if (walk.hasSymbol(symbol)) {
+ return false;
+ }
+ walk = walk.previous;
+ }
+ if (symbol < LONGBITS) {
+ if ((symbols & (1L << symbol)) != 0L) {
+ return false;
+ }
+ symbols |= (1L << symbol);
+ } else {
+ int s = symbol - LONGBITS;
+ BitSet ms = moreSymbols();
+ if (ms.get(s)) {
+ return false;
+ }
+ ms.set(s, true);
+ }
+ return true;
+ }
+
+ /**
+ * @return the number of symbols defined in this scope.
+ */
+ public int getSymbolCount() {
+ return Long.bitCount(symbols) + (moreSymbols == null? 0 : moreSymbols.cardinality());
+ }
+}
diff --git a/src/main/java/org/apache/commons/jexl3/internal/Options.java b/src/main/java/org/apache/commons/jexl3/internal/Options.java
new file mode 100644
index 0000000..f33ea5e
--- /dev/null
+++ b/src/main/java/org/apache/commons/jexl3/internal/Options.java
@@ -0,0 +1,229 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.jexl3.internal;
+
+import org.apache.commons.jexl3.JexlOptions;
+import org.apache.commons.jexl3.JexlEngine;
+
+import java.math.MathContext;
+
+/**
+ * A basic implementation of JexlOptions.
+ * <p>Thread safety is only guaranteed if no modifications (call set* method)
+ * occurs in different threads; note however that using the 'callable()' method to
+ * allow a script to run as such will use a copy.
+ */
+public class Options implements JexlOptions {
+ /** The local shade bit. */
+ protected static final int SHADE = 6;
+ /** The antish var bit. */
+ protected static final int ANTISH_VAR = 5;
+ /** The lexical scope bit. */
+ protected static final int LEXICAL = 4;
+ /** The safe bit. */
+ protected static final int SAFE = 3;
+ /** The silent bit. */
+ protected static final int SILENT = 2;
+ /** The strict bit. */
+ protected static final int STRICT = 1;
+ /** The cancellable bit. */
+ protected static final int CANCELLABLE = 0;
+ /** Default mask .*/
+ protected static final int DEFAULT = 1 /*<< CANCELLABLE*/ | 1 << STRICT | 1 << ANTISH_VAR;
+ /** The arithmetic math context. */
+ private MathContext mathContext = null;
+ /** The arithmetic math scale. */
+ private int mathScale = Integer.MIN_VALUE;
+ /** The arithmetic strict math flag. */
+ private boolean strictArithmetic = true;
+ /** The default flags, all but safe. */
+ private int flags = DEFAULT;
+
+ /**
+ * Sets the value of a flag in a mask.
+ * @param ordinal the flag ordinal
+ * @param mask the flags mask
+ * @param value true or false
+ * @return the new flags mask value
+ */
+ protected static int set(int ordinal, int mask, boolean value) {
+ return value? mask | (1 << ordinal) : mask & ~(1 << ordinal);
+ }
+
+ /**
+ * Checks the value of a flag in the mask.
+ * @param ordinal the flag ordinal
+ * @param mask the flags mask
+ * @return the mask value with this flag or-ed in
+ */
+ protected static boolean isSet(int ordinal, int mask) {
+ return (mask & 1 << ordinal) != 0;
+ }
+
+ /**
+ * Default ctor.
+ */
+ public Options() {}
+
+ /**
+ * Set options from engine.
+ * @param jexl the engine
+ */
+ @Override
+ public void setOptions(JexlEngine jexl) {
+ mathContext = jexl.getArithmetic().getMathContext();
+ mathScale = jexl.getArithmetic().getMathScale();
+ strictArithmetic = jexl.getArithmetic().isStrict();
+ int mask = DEFAULT;
+ mask = set(STRICT, mask, jexl.isStrict());
+ mask = set(SILENT, mask, jexl.isSilent());
+ mask = set(SAFE, mask, jexl.isSafe());
+ mask = set(CANCELLABLE, mask, jexl.isCancellable());
+ flags = mask;
+ }
+
+ @Override
+ public JexlOptions copy() {
+ return new Options(this);
+ }
+
+ /**
+ * Create a copy from another set of options.
+ * @param opts the source options to copy
+ */
+ public Options(JexlOptions opts) {
+ if (opts instanceof Options) {
+ Options src = (Options) opts;
+ mathContext = src.mathContext;
+ mathScale = src.mathScale;
+ strictArithmetic = src.strictArithmetic;
+ flags = src.flags;
+ } else {
+ mathContext = opts.getMathContext();
+ mathScale = opts.getMathScale();
+ strictArithmetic = opts.isStrict();
+ int mask = DEFAULT;
+ mask = set(STRICT, mask, opts.isStrict());
+ mask = set(SILENT, mask, opts.isSilent());
+ mask = set(SAFE, mask, opts.isSafe());
+ mask = set(CANCELLABLE, mask, opts.isCancellable());
+ flags = mask;
+ }
+ }
+
+ @Override
+ public void setAntish(boolean flag) {
+ flags = set(ANTISH_VAR, flags, flag);
+ }
+
+ @Override
+ public void setMathContext(MathContext mcontext) {
+ this.mathContext = mcontext;
+ }
+
+ @Override
+ public void setMathScale(int mscale) {
+ this.mathScale = mscale;
+ }
+
+ @Override
+ public void setStrictArithmetic(boolean stricta) {
+ this.strictArithmetic = stricta;
+ }
+
+ @Override
+ public void setStrict(boolean flag) {
+ flags = set(STRICT, flags, flag);
+ }
+
+ @Override
+ public void setSafe(boolean flag) {
+ flags = set(SAFE, flags, flag);
+ }
+
+ @Override
+ public void setSilent(boolean flag) {
+ flags = set(SILENT, flags, flag);
+ }
+
+ @Override
+ public void setCancellable(boolean flag) {
+ flags = set(CANCELLABLE, flags, flag);
+ }
+
+ @Override
+ public void setLexical(boolean flag) {
+ flags = set(LEXICAL, flags, flag);
+ }
+
+ @Override
+ public void setLexicalShade(boolean flag) {
+ flags = set(SHADE, flags, flag);
+ }
+
+ @Override
+ public boolean isAntish() {
+ return isSet(ANTISH_VAR, flags);
+ }
+
+ @Override
+ public boolean isSilent() {
+ return isSet(SILENT, flags);
+ }
+
+ @Override
+ public boolean isStrict() {
+ return isSet(STRICT, flags);
+ }
+
+ @Override
+ public boolean isSafe() {
+ return isSet(SAFE, flags);
+ }
+
+ @Override
+ public boolean isCancellable() {
+ return isSet(CANCELLABLE, flags);
+ }
+
+ @Override
+ public boolean isLexical() {
+ return isSet(LEXICAL, flags);
+ }
+
+ @Override
+ public boolean isLexicalShade() {
+ return isSet(SHADE, flags);
+ }
+
+ @Override
+ public boolean isStrictArithmetic() {
+ return strictArithmetic;
+ }
+
+ @Override
+ public MathContext getMathContext() {
+ return mathContext;
+ }
+
+ @Override
+ public int getMathScale() {
+ return mathScale;
+ }
+
+}
diff --git a/src/main/java/org/apache/commons/jexl3/internal/Scope.java b/src/main/java/org/apache/commons/jexl3/internal/Scope.java
index 15c0385..51cf842 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/Scope.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/Scope.java
@@ -22,13 +22,14 @@ import java.util.Map;
/**
* A script scope, stores the declaration of parameters and local variables as symbols.
+ * <p>This also acts as the functional scope and variable definition store.
* @since 3.0
*/
public final class Scope {
/**
* The value of a declared but undefined variable, for instance: var x;.
*/
- private static final Object UNDEFINED = new Object() {
+ static final Object UNDEFINED = new Object() {
@Override public String toString() {
return "?";
}
@@ -152,8 +153,9 @@ public final class Scope {
* This method creates an new entry in the symbol map.
* </p>
* @param name the parameter name
+ * @return the register index storing this variable
*/
- public void declareParameter(String name) {
+ public int declareParameter(String name) {
if (namedVariables == null) {
namedVariables = new LinkedHashMap<String, Integer>();
} else if (vars > 0) {
@@ -165,6 +167,7 @@ public final class Scope {
namedVariables.put(name, register);
parms += 1;
}
+ return register;
}
/**
@@ -175,7 +178,7 @@ public final class Scope {
* @param name the variable name
* @return the register index storing this variable
*/
- public Integer declareVariable(String name) {
+ public int declareVariable(String name) {
if (namedVariables == null) {
namedVariables = new LinkedHashMap<String, Integer>();
}
@@ -202,9 +205,10 @@ public final class Scope {
* Creates a frame by copying values up to the number of parameters.
* <p>This captures the hoisted variables values.</p>
* @param frame the caller frame
+ * @param args the arguments
* @return the arguments array
*/
- public Frame createFrame(Frame frame) {
+ public Frame createFrame(Frame frame, Object...args) {
if (namedVariables != null) {
Object[] arguments = new Object[namedVariables.size()];
Arrays.fill(arguments, UNDEFINED);
@@ -216,7 +220,7 @@ public final class Scope {
arguments[target] = arg;
}
}
- return new Frame(this, arguments, 0);
+ return new Frame(this, arguments, 0).assign(args);
} else {
return null;
}
@@ -305,109 +309,4 @@ public final class Scope {
}
}
- /**
- * A call frame, created from a scope, stores the arguments and local variables in a "stack frame" (sic).
- * @since 3.0
- */
- public static final class Frame {
- /** The scope. */
- private final Scope scope;
- /** The actual stack frame. */
- private final Object[] stack;
- /** Number of curried parameters. */
- private int curried = 0;
-
- /**
- * Creates a new frame.
- * @param s the scope
- * @param r the stack frame
- * @param c the number of curried parameters
- */
- public Frame(Scope s, Object[] r, int c) {
- scope = s;
- stack = r;
- curried = c;
- }
-
- /**
- * Gets this script unbound parameters, i.e. parameters not bound through curry().
- * @return the parameter names
- */
- public String[] getUnboundParameters() {
- return scope.getParameters(curried);
- }
-
- /**
- * Gets the scope.
- * @return this frame scope
- */
- public Scope getScope() {
- return scope;
- }
-
- @Override
- public int hashCode() {
- return Arrays.deepHashCode(this.stack);
- }
-
- @Override
- public boolean equals(Object obj) {
- if (obj == null) {
- return false;
- }
- if (getClass() != obj.getClass()) {
- return false;
- }
- final Frame other = (Frame) obj;
- return Arrays.deepEquals(this.stack, other.stack);
- }
-
- /**
- * Gets a value.
- * @param s the offset in this frame
- * @return the stacked value
- */
- public Object get(int s) {
- return stack[s];
- }
-
- /**
- * Whether this frame defines a symbol, ie declared it and assigned it a value.
- * @param s the offset in this frame
- * @return true if this symbol has been assigned a value, false otherwise
- */
- public boolean has(int s) {
- return s >= 0 && s < stack.length && stack[s] != UNDEFINED;
- }
-
- /**
- * Sets a value.
- * @param r the offset in this frame
- * @param value the value to set in this frame
- */
- public void set(int r, Object value) {
- stack[r] = value;
- }
-
- /**
- * Assign values to this frame.
- * @param values the values
- * @return this frame
- */
- public Frame assign(Object... values) {
- if (stack != null) {
- int nparm = scope.getArgCount();
- Object[] copy = stack.clone();
- int ncopy = 0;
- if (values != null && values.length > 0) {
- ncopy = Math.min(nparm - curried, Math.min(nparm, values.length));
- System.arraycopy(values, 0, copy, curried, ncopy);
- }
- // unbound parameters are defined as null
- Arrays.fill(copy, curried + ncopy, nparm, null);
- return new Frame(scope, copy, curried + ncopy);
- }
- return this;
- }
- }
}
diff --git a/src/main/java/org/apache/commons/jexl3/internal/Script.java b/src/main/java/org/apache/commons/jexl3/internal/Script.java
index 2cd6642..62ecaca 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/Script.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/Script.java
@@ -93,7 +93,7 @@ public class Script implements JexlScript, JexlExpression {
* @param args the arguments to bind to parameters
* @return the frame (may be null)
*/
- protected Scope.Frame createFrame(Object[] args) {
+ protected Frame createFrame(Object[] args) {
return script.createFrame(args);
}
@@ -103,7 +103,7 @@ public class Script implements JexlScript, JexlExpression {
* @param frame the calling frame
* @return the interpreter
*/
- protected Interpreter createInterpreter(JexlContext context, Scope.Frame frame) {
+ protected Interpreter createInterpreter(JexlContext context, Frame frame) {
return jexl.createInterpreter(context, frame);
}
@@ -179,7 +179,7 @@ public class Script implements JexlScript, JexlExpression {
@Override
public Object execute(JexlContext context) {
checkCacheVersion();
- Scope.Frame frame = createFrame(null);
+ Frame frame = createFrame(null);
Interpreter interpreter = createInterpreter(context, frame);
return interpreter.interpret(script);
}
@@ -187,7 +187,7 @@ public class Script implements JexlScript, JexlExpression {
@Override
public Object execute(JexlContext context, Object... args) {
checkCacheVersion();
- Scope.Frame frame = createFrame(args != null && args.length > 0 ? args : null);
+ Frame frame = createFrame(args != null && args.length > 0 ? args : null);
Interpreter interpreter = createInterpreter(context, frame);
return interpreter.interpret(script);
}
diff --git a/src/main/java/org/apache/commons/jexl3/internal/TemplateInterpreter.java b/src/main/java/org/apache/commons/jexl3/internal/TemplateInterpreter.java
index ffca1ef..f1ba7c0 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/TemplateInterpreter.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/TemplateInterpreter.java
@@ -47,7 +47,7 @@ public class TemplateInterpreter extends Interpreter {
* @param out the output writer
*/
TemplateInterpreter(Engine jexl,
- JexlContext jcontext, Scope.Frame jframe, TemplateExpression[] expressions, Writer out) {
+ JexlContext jcontext, Frame jframe, TemplateExpression[] expressions, Writer out) {
super(jexl, jcontext, jframe);
exprs = expressions;
writer = out;
@@ -148,7 +148,7 @@ public class TemplateInterpreter extends Interpreter {
if (node instanceof ASTJexlLambda && !((ASTJexlLambda) node).isTopLevel()) {
return new Closure(this, (ASTJexlLambda) node) {
@Override
- protected Interpreter createInterpreter(JexlContext context, Scope.Frame local) {
+ protected Interpreter createInterpreter(JexlContext context, Frame local) {
return new TemplateInterpreter(jexl, context, local, exprs, writer);
}
};
diff --git a/src/main/java/org/apache/commons/jexl3/internal/TemplateScript.java b/src/main/java/org/apache/commons/jexl3/internal/TemplateScript.java
index 63829f2..760f876 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/TemplateScript.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/TemplateScript.java
@@ -233,7 +233,7 @@ public final class TemplateScript implements JxltEngine.Template {
@Override
public TemplateScript prepare(JexlContext context) {
- Scope.Frame frame = script.createFrame((Object[]) null);
+ Frame frame = script.createFrame((Object[]) null);
TemplateExpression[] immediates = new TemplateExpression[exprs.length];
for (int e = 0; e < exprs.length; ++e) {
immediates[e] = exprs[e].prepare(frame, context);
@@ -248,7 +248,7 @@ public final class TemplateScript implements JxltEngine.Template {
@Override
public void evaluate(JexlContext context, Writer writer, Object... args) {
- Scope.Frame frame = script.createFrame(args);
+ Frame frame = script.createFrame(args);
Interpreter interpreter = new TemplateInterpreter(jxlt.getEngine(), context, frame, exprs, writer);
interpreter.interpret(script);
}
diff --git a/src/main/java/org/apache/commons/jexl3/internal/introspection/ClassMap.java b/src/main/java/org/apache/commons/jexl3/internal/introspection/ClassMap.java
index f3c9340..05e6726 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/introspection/ClassMap.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/introspection/ClassMap.java
@@ -284,7 +284,7 @@ final class ClassMap {
// add method to byKey cache; do not override
MethodKey key = new MethodKey(mi);
Method pmi = cache.byKey.putIfAbsent(key, permissions.allow(mi) ? mi : CACHE_MISS);
- if (pmi != null && log.isDebugEnabled() && !key.equals(new MethodKey(pmi))) {
+ if (pmi != null && pmi != CACHE_MISS && log.isDebugEnabled() && !key.equals(new MethodKey(pmi))) {
// foo(int) and foo(Integer) have the same signature for JEXL
log.debug("Method " + pmi + " is already registered, key: " + key.debugString());
}
diff --git a/src/main/java/org/apache/commons/jexl3/internal/introspection/ConstructorMethod.java b/src/main/java/org/apache/commons/jexl3/internal/introspection/ConstructorMethod.java
index e787b4b..e82a111 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/introspection/ConstructorMethod.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/introspection/ConstructorMethod.java
@@ -19,7 +19,6 @@ package org.apache.commons.jexl3.internal.introspection;
import java.beans.IntrospectionException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
-import org.apache.commons.jexl3.JexlException;
import org.apache.commons.jexl3.introspection.JexlMethod;
/**
@@ -102,7 +101,7 @@ public final class ConstructorMethod implements JexlMethod {
} catch (IllegalArgumentException xargument) {
return Uberspect.TRY_FAILED;
} catch (InvocationTargetException xinvoke) {
- throw JexlException.tryFailed(xinvoke); // throw
+ return Uberspect.TRY_FAILED;
}
return Uberspect.TRY_FAILED;
}
diff --git a/src/main/java/org/apache/commons/jexl3/internal/introspection/Introspector.java b/src/main/java/org/apache/commons/jexl3/internal/introspection/Introspector.java
index 4566638..b442aa3 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/introspection/Introspector.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/introspection/Introspector.java
@@ -61,7 +61,7 @@ public final class Introspector {
/**
* the logger.
*/
- protected final Log rlog;
+ protected final Log logger;
/**
* The class loader used to solve constructors if needed.
*/
@@ -103,7 +103,7 @@ public final class Introspector {
* @param perms the permissions
*/
public Introspector(Log log, ClassLoader cloader, Permissions perms) {
- this.rlog = log;
+ this.logger = log;
this.loader = cloader;
this.permissions = perms != null? perms : Permissions.DEFAULT;
}
@@ -146,8 +146,8 @@ public final class Introspector {
return getMap(c).getMethod(key);
} catch (MethodKey.AmbiguousException xambiguous) {
// whoops. Ambiguous and not benign. Make a nice log message and return null...
- if (rlog != null && xambiguous.isSevere() && rlog.isInfoEnabled()) {
- rlog.info("ambiguous method invocation: "
+ if (logger != null && xambiguous.isSevere() && logger.isInfoEnabled()) {
+ logger.info("ambiguous method invocation: "
+ c.getName() + "."
+ key.debugString(), xambiguous);
}
@@ -273,15 +273,15 @@ public final class Introspector {
constructorsMap.put(key, CTOR_MISS);
}
} catch (ClassNotFoundException xnotfound) {
- if (rlog != null && rlog.isDebugEnabled()) {
- rlog.debug("unable to find class: "
+ if (logger != null && logger.isDebugEnabled()) {
+ logger.debug("unable to find class: "
+ cname + "."
+ key.debugString(), xnotfound);
}
ctor = null;
} catch (MethodKey.AmbiguousException xambiguous) {
- if (rlog != null && xambiguous.isSevere() && rlog.isInfoEnabled()) {
- rlog.info("ambiguous constructor invocation: "
+ if (logger != null && xambiguous.isSevere() && logger.isInfoEnabled()) {
+ logger.info("ambiguous constructor invocation: "
+ cname + "."
+ key.debugString(), xambiguous);
}
@@ -312,7 +312,7 @@ public final class Introspector {
// try again
classMap = classMethodMaps.get(c);
if (classMap == null) {
- classMap = new ClassMap(c, permissions, rlog);
+ classMap = new ClassMap(c, permissions, logger);
classMethodMaps.put(c, classMap);
}
} finally {
diff --git a/src/main/java/org/apache/commons/jexl3/internal/introspection/MethodExecutor.java b/src/main/java/org/apache/commons/jexl3/internal/introspection/MethodExecutor.java
index d596c9d..6e0a118 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/introspection/MethodExecutor.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/introspection/MethodExecutor.java
@@ -83,7 +83,7 @@ public final class MethodExecutor extends AbstractExecutor.Method {
@Override
public Object invoke(Object o, Object... args) throws IllegalAccessException, InvocationTargetException {
- if (vaClass != null) {
+ if (vaClass != null && args != null) {
args = handleVarArg(args);
}
if (method.getDeclaringClass() == ArrayListWrapper.class && o.getClass().isArray()) {
diff --git a/src/main/java/org/apache/commons/jexl3/internal/introspection/Uberspect.java b/src/main/java/org/apache/commons/jexl3/internal/introspection/Uberspect.java
index 8301800..abc3e13 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/introspection/Uberspect.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/introspection/Uberspect.java
@@ -53,7 +53,7 @@ public class Uberspect implements JexlUberspect {
/** Publicly exposed special failure object returned by tryInvoke. */
public static final Object TRY_FAILED = JexlEngine.TRY_FAILED;
/** The logger to use for all warnings and errors. */
- protected final Log rlog;
+ protected final Log logger;
/** The resolver strategy. */
private final JexlUberspect.ResolverStrategy strategy;
/** The permissions. */
@@ -88,7 +88,7 @@ public class Uberspect implements JexlUberspect {
* @param perms the introspector permissions
*/
public Uberspect(Log runtimeLogger, JexlUberspect.ResolverStrategy sty, Permissions perms) {
- rlog = runtimeLogger;
+ logger = runtimeLogger;
strategy = sty == null? JexlUberspect.JEXL_STRATEGY : sty;
permissions = perms;
ref = new SoftReference<Introspector>(null);
@@ -111,7 +111,7 @@ public class Uberspect implements JexlUberspect {
synchronized (this) {
intro = ref.get();
if (intro == null) {
- intro = new Introspector(rlog, loader.get(), permissions);
+ intro = new Introspector(logger, loader.get(), permissions);
ref = new SoftReference<Introspector>(intro);
loader = new SoftReference<ClassLoader>(intro.getLoader());
version.incrementAndGet();
@@ -129,7 +129,7 @@ public class Uberspect implements JexlUberspect {
if (intro != null) {
intro.setLoader(nloader);
} else {
- intro = new Introspector(rlog, nloader, permissions);
+ intro = new Introspector(logger, nloader, permissions);
ref = new SoftReference<Introspector>(intro);
}
loader = new SoftReference<ClassLoader>(intro.getLoader());
@@ -394,8 +394,8 @@ public class Uberspect implements JexlUberspect {
return (Iterator<Object>) it.invoke(obj, (Object[]) null);
}
} catch (Exception xany) {
- if (rlog != null && rlog.isDebugEnabled()) {
- rlog.info("unable to solve iterator()", xany);
+ if (logger != null && logger.isDebugEnabled()) {
+ logger.info("unable to solve iterator()", xany);
}
}
return null;
diff --git a/src/main/java/org/apache/commons/jexl3/parser/ASTBlock.java b/src/main/java/org/apache/commons/jexl3/parser/ASTBlock.java
new file mode 100644
index 0000000..03a00d9
--- /dev/null
+++ b/src/main/java/org/apache/commons/jexl3/parser/ASTBlock.java
@@ -0,0 +1,62 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.jexl3.parser;
+
+import org.apache.commons.jexl3.internal.LexicalScope;
+
+/**
+ * Declares a local variable.
+ */
+public class ASTBlock extends JexlNode implements JexlParser.LexicalUnit {
+ private LexicalScope locals = null;
+
+ public ASTBlock(int id) {
+ super(id);
+ }
+
+ public ASTBlock(Parser p, int id) {
+ super(p, id);
+ }
+
+ @Override
+ public Object jjtAccept(ParserVisitor visitor, Object data) {
+ return visitor.visit(this, data);
+ }
+
+ @Override
+ public boolean declareSymbol(int symbol) {
+ if (locals == null) {
+ locals = new LexicalScope(null);
+ }
+ return locals.declareSymbol(symbol);
+ }
+
+ @Override
+ public int getSymbolCount() {
+ return locals == null? 0 : locals.getSymbolCount();
+ }
+
+ @Override
+ public boolean hasSymbol(int symbol) {
+ return locals == null? false : locals.hasSymbol(symbol);
+ }
+
+ @Override
+ public void clearUnit() {
+ locals = null;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/org/apache/commons/jexl3/parser/ASTForeachStatement.java b/src/main/java/org/apache/commons/jexl3/parser/ASTForeachStatement.java
new file mode 100644
index 0000000..1ad61c0
--- /dev/null
+++ b/src/main/java/org/apache/commons/jexl3/parser/ASTForeachStatement.java
@@ -0,0 +1,62 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.jexl3.parser;
+
+import org.apache.commons.jexl3.internal.LexicalScope;
+
+/**
+ * Declares a local variable.
+ */
+public class ASTForeachStatement extends JexlNode implements JexlParser.LexicalUnit {
+ private LexicalScope locals = null;
+
+ public ASTForeachStatement(int id) {
+ super(id);
+ }
+
+ public ASTForeachStatement(Parser p, int id) {
+ super(p, id);
+ }
+
+ @Override
+ public Object jjtAccept(ParserVisitor visitor, Object data) {
+ return visitor.visit(this, data);
+ }
+
+ @Override
+ public boolean declareSymbol(int symbol) {
+ if (locals == null) {
+ locals = new LexicalScope(null);
+ }
+ return locals.declareSymbol(symbol);
+ }
+
+ @Override
+ public int getSymbolCount() {
+ return locals == null? 0 : locals.getSymbolCount();
+ }
+
+ @Override
+ public boolean hasSymbol(int symbol) {
+ return locals == null? false : locals.hasSymbol(symbol);
+ }
+
+ @Override
+ public void clearUnit() {
+ locals = null;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/org/apache/commons/jexl3/parser/ASTJexlScript.java b/src/main/java/org/apache/commons/jexl3/parser/ASTJexlScript.java
index 74c7bbc..51c5d3b 100644
--- a/src/main/java/org/apache/commons/jexl3/parser/ASTJexlScript.java
+++ b/src/main/java/org/apache/commons/jexl3/parser/ASTJexlScript.java
@@ -19,17 +19,21 @@ package org.apache.commons.jexl3.parser;
import org.apache.commons.jexl3.JexlFeatures;
import org.apache.commons.jexl3.internal.Scope;
import java.util.Map;
+import org.apache.commons.jexl3.internal.Frame;
+import org.apache.commons.jexl3.internal.LexicalScope;
/**
* Enhanced script to allow parameters declaration.
*/
-public class ASTJexlScript extends JexlNode {
- /** The script scope. */
- private Scope scope = null;
+public class ASTJexlScript extends JexlNode implements JexlParser.LexicalUnit {
/** The pragmas. */
private Map<String, Object> pragmas = null;
/** Features. */
private JexlFeatures features = null;
+ /** The script scope. */
+ private Scope scope = null;
+ /** The local symbol set. */
+ private LexicalScope locals = null;
public ASTJexlScript(int id) {
super(id);
@@ -38,7 +42,30 @@ public class ASTJexlScript extends JexlNode {
public ASTJexlScript(Parser p, int id) {
super(p, id);
}
+
+ @Override
+ public boolean declareSymbol(int symbol) {
+ if (locals == null) {
+ locals = new LexicalScope(null);
+ }
+ return locals.declareSymbol(symbol);
+ }
+
+ @Override
+ public int getSymbolCount() {
+ return locals == null? 0 : locals.getSymbolCount();
+ }
+ @Override
+ public boolean hasSymbol(int symbol) {
+ return locals == null? false : locals.hasSymbol(symbol);
+ }
+
+ @Override
+ public void clearUnit() {
+ locals = null;
+ }
+
/**
* Consider script with no parameters that return lambda as parametric-scripts.
* @return the script
@@ -93,6 +120,11 @@ public class ASTJexlScript extends JexlNode {
*/
public void setScope(Scope theScope) {
this.scope = theScope;
+ if (theScope != null) {
+ for(int a = 0; a < theScope.getArgCount(); ++a) {
+ this.declareSymbol(a);
+ }
+ }
}
/**
@@ -108,14 +140,8 @@ public class ASTJexlScript extends JexlNode {
* @param values the argument values
* @return the arguments array
*/
- public Scope.Frame createFrame(Scope.Frame caller, Object... values) {
- if (scope != null) {
- Scope.Frame frame = scope.createFrame(caller);
- if (frame != null) {
- return frame.assign(values);
- }
- }
- return null;
+ public Frame createFrame(Frame caller, Object... values) {
+ return scope != null? scope.createFrame(caller, values) : null;
}
/**
@@ -123,7 +149,7 @@ public class ASTJexlScript extends JexlNode {
* @param values the argument values
* @return the arguments array
*/
- public Scope.Frame createFrame(Object... values) {
+ public Frame createFrame(Object... values) {
return createFrame(null, values);
}
diff --git a/src/main/java/org/apache/commons/jexl3/parser/JexlNode.java b/src/main/java/org/apache/commons/jexl3/parser/JexlNode.java
index 64efd98..93b2bc3 100644
--- a/src/main/java/org/apache/commons/jexl3/parser/JexlNode.java
+++ b/src/main/java/org/apache/commons/jexl3/parser/JexlNode.java
@@ -162,10 +162,8 @@ public abstract class JexlNode extends SimpleNode {
int nc = walk.jjtGetNumChildren() - 1;
if (nc >= 0) {
walk = walk.jjtGetChild(nc);
- } else if (walk.jjtGetParent() instanceof ASTReference) {
- return true;
} else {
- return false;
+ return walk.jjtGetParent() instanceof ASTReference;
}
} while (walk != null);
return false;
@@ -212,8 +210,8 @@ public abstract class JexlNode extends SimpleNode {
}
if (this instanceof ASTMethodNode) {
if (this.jjtGetNumChildren() > 1
- && this.jjtGetChild(0) instanceof ASTIdentifierAccess
- && (((ASTIdentifierAccess) this.jjtGetChild(0)).isSafe() || safe)) {
+ && this.jjtGetChild(0) instanceof ASTIdentifierAccess
+ && (((ASTIdentifierAccess) this.jjtGetChild(0)).isSafe() || safe)) {
return true;
}
}
@@ -262,7 +260,7 @@ public abstract class JexlNode extends SimpleNode {
JexlNode node = this;
for (JexlNode walk = node.jjtGetParent(); walk != null; walk = walk.jjtGetParent()) {
// protect only the condition part of the ternary
- if (walk instanceof ASTTernaryNode || walk instanceof ASTNullpNode) {
+ if (walk instanceof ASTTernaryNode || walk instanceof ASTNullpNode || walk instanceof ASTEQNode || walk instanceof ASTNENode) {
return node == walk.jjtGetChild(0);
}
if (!(walk instanceof ASTReference || walk instanceof ASTArrayAccess)) {
diff --git a/src/main/java/org/apache/commons/jexl3/parser/JexlParser.java b/src/main/java/org/apache/commons/jexl3/parser/JexlParser.java
index 17e1937..f0cf9b0 100644
--- a/src/main/java/org/apache/commons/jexl3/parser/JexlParser.java
+++ b/src/main/java/org/apache/commons/jexl3/parser/JexlParser.java
@@ -74,8 +74,49 @@ public abstract class JexlParser extends StringParser {
* Stack of parsing loop counts.
*/
protected Deque<Integer> loopCounts = new ArrayDeque<Integer>();
+ /**
+ * Lexical unit merge, next block push is swallowed.
+ */
+ protected boolean mergeBlock = false;
+ /**
+ * The current lexical block.
+ */
+ protected LexicalUnit block = null;
+ /**
+ * Stack of lexical blocks.
+ */
+ protected Deque<LexicalUnit> blocks = new ArrayDeque<LexicalUnit>();
-
+ /**
+ * A lexical unit is the container defining local symbols and their
+ * visibility boundaries.
+ */
+ public interface LexicalUnit {
+ /**
+ * Declares a local symbol.
+ * @param symbol the symbol index in the scope
+ * @return true if declaration was successful, false if symbol was already declared
+ */
+ boolean declareSymbol(int symbol);
+
+ /**
+ * Checks whether a symbol is declared in this lexical unit.
+ * @param symbol the symbol
+ * @return true if declared, false otherwise
+ */
+ boolean hasSymbol(int symbol);
+
+ /**
+ * @return the number of local variables declared in this unit
+ */
+ int getSymbolCount();
+
+ /**
+ * Clears this unit.
+ */
+ void clearUnit();
+ }
+
/**
* Cleanup.
* @param features the feature set to restore if any
@@ -88,6 +129,7 @@ public abstract class JexlParser extends StringParser {
pragmas = null;
loopCounts.clear();
loopCount = 0;
+ blocks.clear();
}
/**
* Utility function to create '.' separated string from a list of string.
@@ -153,15 +195,6 @@ public abstract class JexlParser extends StringParser {
}
/**
- * Sets the frame to use by this parser.
- * <p> This is used to allow parameters to be declared before parsing. </p>
- * @param theFrame the register map
- */
- protected void setFrame(Scope theFrame) {
- frame = theFrame;
- }
-
- /**
* Gets the frame used by this parser.
* <p> Since local variables create new symbols, it is important to
* regain access after parsing to known which / how-many registers are needed. </p>
@@ -198,21 +231,82 @@ public abstract class JexlParser extends StringParser {
}
/**
+ * Gets the lexical unit currently used by this parser.
+ * @return the named register map
+ */
+ protected LexicalUnit getUnit() {
+ return block;
+ }
+
+ /**
+ * Pushes a new lexical unit.
+ * <p>The merge flag allows the for(...) and lamba(...) constructs to
+ * merge in the next block since their loop-variable/parameter spill in the
+ * same lexical unit as their first block.
+ * @param unit the new lexical unit
+ * @param merge whether the next unit merges in this one
+ */
+ protected void pushUnit(LexicalUnit unit, boolean merge) {
+ if (merge) {
+ mergeBlock = true;
+ } else if (mergeBlock) {
+ mergeBlock = false;
+ return;
+ }
+ if (block != null) {
+ blocks.push(block);
+ }
+ block = unit;
+ }
+
+ /**
+ * Pushes a block as new lexical unit.
+ * @param unit the lexical unit
+ */
+ protected void pushUnit(LexicalUnit unit) {
+ pushUnit(unit, false);
+ }
+
+ /**
+ * Restores the previous lexical unit.
+ * @param unit restores the previous lexical scope
+ */
+ protected void popUnit(LexicalUnit unit) {
+ if (block == unit){
+ if (!blocks.isEmpty()) {
+ block = blocks.pop();
+ } else {
+ block = null;
+ }
+ //unit.clearUnit();
+ }
+ }
+
+ /**
* Checks whether an identifier is a local variable or argument, ie a symbol, stored in a register.
* @param identifier the identifier
- * @param image the identifier image
+ * @param name the identifier name
* @return the image
*/
- protected String checkVariable(ASTIdentifier identifier, String image) {
+ protected String checkVariable(ASTIdentifier identifier, String name) {
if (frame != null) {
- Integer register = frame.getSymbol(image);
- if (register != null) {
- identifier.setSymbol(register.intValue(), image);
+ Integer symbol = frame.getSymbol(name);
+ if (symbol != null) {
+ // can not reuse a local as a global
+ if (!block.hasSymbol(symbol) && getFeatures().isLexical()) {
+ throw new JexlException(identifier, name + ": variable is not defined");
+ }
+ identifier.setSymbol(symbol, name);
}
}
- return image;
+ return name;
}
+ /**
+ * Whether a given variable name is allowed.
+ * @param image the name
+ * @return true if allowed, false if reserved
+ */
protected boolean allowVariable(String image) {
JexlFeatures features = getFeatures();
if (!features.supportsLocalVar()) {
@@ -223,6 +317,27 @@ public abstract class JexlParser extends StringParser {
}
return true;
}
+
+ /**
+ * Declares a symbol.
+ * @param symbol the symbol index
+ * @return true if symbol can be declared in lexical scope, false (error)
+ * if it is already declared
+ */
+ private boolean declareSymbol(int symbol) {
+ if (blocks != null) {
+ for(LexicalUnit lu : blocks) {
+ if (lu.hasSymbol(symbol)) {
+ return false;
+ }
+ // stop at first new scope reset, aka lambda
+ if (lu instanceof ASTJexlLambda) {
+ break;
+ }
+ }
+ }
+ return block.declareSymbol(symbol);
+ }
/**
* Declares a local variable.
@@ -231,15 +346,19 @@ public abstract class JexlParser extends StringParser {
* @param token the variable name toekn
*/
protected void declareVariable(ASTVar var, Token token) {
- String identifier = token.image;
- if (!allowVariable(identifier)) {
+ String name = token.image;
+ if (!allowVariable(name)) {
throwFeatureException(JexlFeatures.LOCAL_VAR, token);
}
if (frame == null) {
frame = new Scope(null, (String[]) null);
}
- Integer register = frame.declareVariable(identifier);
- var.setSymbol(register.intValue(), identifier);
+ int symbol = frame.declareVariable(name);
+ var.setSymbol(symbol, name);
+ // lexical feature error
+ if (!declareSymbol(symbol) && getFeatures().isLexical()) {
+ throw new JexlException(var, name + ": variable is already declared");
+ }
}
/**
@@ -270,7 +389,13 @@ public abstract class JexlParser extends StringParser {
if (frame == null) {
frame = new Scope(null, (String[]) null);
}
- frame.declareParameter(identifier);
+ int symbol = frame.declareParameter(identifier);
+ // not sure how declaring a parameter could fail...
+ // lexical feature error
+ if (!declareSymbol(symbol) && getFeatures().isLexical()) {
+ JexlInfo xinfo = info.at(token.beginLine, token.beginColumn);
+ throw new JexlException(xinfo, identifier + ": variable is already declared", null);
+ }
}
/**
diff --git a/src/main/java/org/apache/commons/jexl3/parser/Parser.jjt b/src/main/java/org/apache/commons/jexl3/parser/Parser.jjt
index 4cc2426..cdb19ca 100644
--- a/src/main/java/org/apache/commons/jexl3/parser/Parser.jjt
+++ b/src/main/java/org/apache/commons/jexl3/parser/Parser.jjt
@@ -295,8 +295,12 @@ ASTJexlScript JexlScript(Scope frame) : {
jjtThis.setScope(frame);
}
{
- ( ( Statement() )*) <EOF>
{
+ pushUnit(jjtThis, frame != null && frame.getArgCount() > 0);
+ }
+ ( ( Statement() )*) <EOF>
+ {
+ popUnit(jjtThis);
return jjtThis.script();
}
}
@@ -304,9 +308,13 @@ ASTJexlScript JexlScript(Scope frame) : {
ASTJexlScript JexlExpression(Scope frame) #JexlScript : {
jjtThis.setScope(frame);
}
-{
+{
+ {
+ pushUnit(jjtThis, true);
+ }
( Expression() )? <EOF>
{
+ popUnit(jjtThis);
return jjtThis.script();
}
}
@@ -343,7 +351,7 @@ void Statement() #void : {}
void Block() #Block : {}
{
- <LCURLY> ( Statement() )* <RCURLY>
+ <LCURLY> { pushUnit(jjtThis); } ( Statement() )* { popUnit(jjtThis); } <RCURLY>
}
@@ -391,7 +399,7 @@ void Break() #Break : {
void ForeachStatement() : {}
{
- <FOR> <LPAREN> ForEachVar() <COLON> Expression() <RPAREN> { loopCount += 1; } (LOOKAHEAD(1) Block() | Statement()) { loopCount -= 1; }
+ { pushUnit(jjtThis, true); } <FOR> <LPAREN> ForEachVar() <COLON> Expression() <RPAREN> { loopCount += 1; } (LOOKAHEAD(1) Block() | Statement()) { loopCount -= 1; popUnit(jjtThis); }
}
void ForEachVar() #Reference : {}
@@ -610,7 +618,6 @@ void UnaryExpression() #void : {}
ValueExpression()
}
-
/***************************************
* Identifier & Literals
***************************************/
@@ -809,7 +816,7 @@ void Parameter() #void :
void Parameters() #void : {}
{
- <LPAREN> [Parameter() (<COMMA> Parameter())* ] <RPAREN>
+ <LPAREN> [(<VAR>)? Parameter() (<COMMA> (<VAR>)? Parameter())* ] <RPAREN>
}
@@ -827,11 +834,11 @@ void Lambda() #JexlLambda() :
pushFrame();
}
{
- <FUNCTION> Parameters() Block()
+ { pushUnit(jjtThis, true); } <FUNCTION> Parameters() Block() { popUnit(jjtThis); }
|
- Parameters() <LAMBDA> Block()
+ { pushUnit(jjtThis, true); } Parameters() <LAMBDA> Block() { popUnit(jjtThis); }
|
- Parameter() <LAMBDA> Block()
+ { pushUnit(jjtThis, true); } Parameter() <LAMBDA> Block() { popUnit(jjtThis); }
}
diff --git a/src/site/xdoc/changes.xml b/src/site/xdoc/changes.xml
index 30ec007..24e4b2a 100644
--- a/src/site/xdoc/changes.xml
+++ b/src/site/xdoc/changes.xml
@@ -26,6 +26,12 @@
</properties>
<body>
<release version="3.2" date="unreleased">
+ <action dev="henrib" type="fix" issue="JEXL-315" due-to="Mike Bartlett">
+ JxltEngine literal string strings ending in \ $ or # throw JxltEngine$Exception
+ </action>
+ <action dev="henrib" type="fix" issue="JEXL-314" due-to="Osy">
+ Comparison NULL values of variables NAME1.NAME2
+ </action>
<action dev="henrib" type="fix" issue="JEXL-312">
@NoJexl fails to disallow method call
</action>
@@ -35,6 +41,9 @@
<action dev="henrib" type="fix" issue="JEXL-309">
Line numbers are not correct when template report errors
</action>
+ <action dev="henrib" type="fix" issue="JEXL-307" due-to="Dmitri Blinov">
+ Variable redeclaration option
+ </action>
<action dev="henrib" type="fix" issue="JEXL-306" due-to="Dmitri Blinov">
Ternary operator ? protects also its branches from resolution errors
</action>
diff --git a/src/site/xdoc/reference/syntax.xml b/src/site/xdoc/reference/syntax.xml
index 400d9b2..e447787 100644
--- a/src/site/xdoc/reference/syntax.xml
+++ b/src/site/xdoc/reference/syntax.xml
@@ -132,6 +132,9 @@
a value of <code>42</code>.
<p>Pragma keys can be identifiers or antish names, pragma values can be literals (boolean, integer,
real, string, null, NaN) and antish names</p>
+ <p>Although pragmas are statements, they are <em>not</em> evaluated at runtime; they are constants
+ associated to the script after parsing. It is expected that user code accesses the pragma map from
+ scripts to alter some functions behavior.</p>
</td>
</tr>
<tr>
diff --git a/src/test/java/org/apache/commons/jexl3/AnnotationTest.java b/src/test/java/org/apache/commons/jexl3/AnnotationTest.java
index b36cd24..c466153 100644
--- a/src/test/java/org/apache/commons/jexl3/AnnotationTest.java
+++ b/src/test/java/org/apache/commons/jexl3/AnnotationTest.java
@@ -77,38 +77,40 @@ public class AnnotationTest extends JexlTestCase {
public class OptAnnotationContext extends JexlEvalContext implements JexlContext.AnnotationProcessor {
@Override
public Object processAnnotation(String name, Object[] args, Callable<Object> statement) throws Exception {
+ JexlOptions options = this.getEngineOptions();
// transient side effect for strict
if ("strict".equals(name)) {
boolean s = (Boolean) args[0];
- boolean b = this.isStrict();
- setStrict(s);
+ boolean b = options.isStrict();
+ options.setStrict(s);
Object r = statement.call();
- setStrict(b);
+ options.setStrict(b);
return r;
}
// transient side effect for silent
if ("silent".equals(name)) {
if (args == null || args.length == 0) {
- boolean b = this.isSilent();
+ boolean b = options.isSilent();
try {
return statement.call();
} catch(JexlException xjexl) {
return null;
} finally {
- setSilent(b);
+ options.setSilent(b);
}
} else {
boolean s = (Boolean) args[0];
- boolean b = this.isSilent();
- setSilent(s);
+ boolean b = options.isSilent();
+ options.setSilent(s);
+ Assert.assertEquals(s, options.isSilent());
Object r = statement.call();
- setSilent(b);
+ options.setSilent(b);
return r;
}
}
// durable side effect for scale
if ("scale".equals(name)) {
- this.setMathScale((Integer) args[0]);
+ options.setMathScale((Integer) args[0]);
return statement.call();
}
return statement.call();
@@ -118,8 +120,9 @@ public class AnnotationTest extends JexlTestCase {
@Test
public void testVarStmt() throws Exception {
OptAnnotationContext jc = new OptAnnotationContext();
+ JexlOptions options = jc.getEngineOptions();
JexlEngine jexl = new JexlBuilder().strict(true).silent(false).create();
- jc.setOptions(jexl);
+ jc.getEngineOptions().setOptions(jexl);
JexlScript e;
Object r;
e = jexl.createScript("(s, v)->{ @strict(s) @silent(v) var x = y ; 42; }");
@@ -159,12 +162,12 @@ public class AnnotationTest extends JexlTestCase {
Assert.fail("should not have thrown");
}
//Assert.assertEquals(42, r);
- Assert.assertTrue(jc.isStrict());
+ Assert.assertTrue(options.isStrict());
e = jexl.createScript("@scale(5) 42;");
r = e.execute(jc);
Assert.assertEquals(42, r);
- Assert.assertTrue(jc.isStrict());
- Assert.assertEquals(5, jc.getArithmeticMathScale());
+ Assert.assertTrue(options.isStrict());
+ Assert.assertEquals(5, options.getMathScale());
}
@Test
diff --git a/src/test/java/org/apache/commons/jexl3/ArithmeticTest.java b/src/test/java/org/apache/commons/jexl3/ArithmeticTest.java
index 9ec7f75..1635bb8 100644
--- a/src/test/java/org/apache/commons/jexl3/ArithmeticTest.java
+++ b/src/test/java/org/apache/commons/jexl3/ArithmeticTest.java
@@ -54,7 +54,7 @@ public class ArithmeticTest extends JexlTestCase {
@Test
public void testUndefinedVar() throws Exception {
- asserter.failExpression("objects[1].status", ".* undefined variable objects.*");
+ asserter.failExpression("objects[1].status", ".*variable 'objects' is undefined.*");
}
@Test
@@ -268,7 +268,8 @@ public class ArithmeticTest extends JexlTestCase {
@Test
public void testLongLiterals() throws Exception {
JexlEvalContext ctxt = new JexlEvalContext();
- ctxt.setStrictArithmetic(true);
+ JexlOptions options = ctxt.getEngineOptions();
+ options.setStrictArithmetic(true);
String stmt = "{a = 10L; b = 10l; c = 42.0D; d = 42.0d; e=56.3F; f=56.3f; g=63.5; h=0x10; i=010; j=0x10L; k=010l}";
JexlScript expr = JEXL.createScript(stmt);
/* Object value = */ expr.execute(ctxt);
@@ -288,7 +289,8 @@ public class ArithmeticTest extends JexlTestCase {
@Test
public void testBigLiteralValue() throws Exception {
JexlEvalContext ctxt = new JexlEvalContext();
- ctxt.setStrictArithmetic(true);
+ JexlOptions options = ctxt.getEngineOptions();
+ options.setStrictArithmetic(true);
JexlExpression e = JEXL.createExpression("9223372036854775806.5B");
String res = String.valueOf(e.evaluate(ctxt));
Assert.assertEquals("9223372036854775806.5", res);
@@ -311,7 +313,8 @@ public class ArithmeticTest extends JexlTestCase {
@Test
public void testBigLiterals() throws Exception {
JexlEvalContext ctxt = new JexlEvalContext();
- ctxt.setStrictArithmetic(true);
+ JexlOptions options = ctxt.getEngineOptions();
+ options.setStrictArithmetic(true);
String stmt = "{a = 10H; b = 10h; c = 42.0B; d = 42.0b;}";
JexlScript expr = JEXL.createScript(stmt);
/* Object value = */ expr.execute(ctxt);
@@ -325,7 +328,8 @@ public class ArithmeticTest extends JexlTestCase {
@Test
public void testBigExponentLiterals() throws Exception {
JexlEvalContext ctxt = new JexlEvalContext();
- ctxt.setStrictArithmetic(true);
+ JexlOptions options = ctxt.getEngineOptions();
+ options.setStrictArithmetic(true);
String stmt = "{a = 42.0e1B; b = 42.0E+2B; c = 42.0e-1B; d = 42.0E-2b; e=4242.4242e1b}";
JexlScript expr = JEXL.createScript(stmt);
/* Object value = */ expr.execute(ctxt);
@@ -340,7 +344,8 @@ public class ArithmeticTest extends JexlTestCase {
@Test
public void test2DoubleLiterals() throws Exception {
JexlEvalContext ctxt = new JexlEvalContext();
- ctxt.setStrictArithmetic(true);
+ JexlOptions options = ctxt.getEngineOptions();
+ options.setStrictArithmetic(true);
String stmt = "{a = 42.0e1D; b = 42.0E+2D; c = 42.0e-1d; d = 42.0E-2d; e=10e10; f= +1.e1; g=1e1; }";
JexlScript expr = JEXL.createScript(stmt);
/* Object value = */ expr.execute(ctxt);
@@ -363,6 +368,8 @@ public class ArithmeticTest extends JexlTestCase {
public void testDivideByZero() throws Exception {
Map<String, Object> vars = new HashMap<String, Object>();
JexlEvalContext context = new JexlEvalContext(vars);
+ JexlOptions options = context.getEngineOptions();
+ options.setStrictArithmetic(true);
vars.put("aByte", new Byte((byte) 1));
vars.put("aShort", new Short((short) 2));
vars.put("aInteger", new Integer(3));
@@ -393,7 +400,8 @@ public class ArithmeticTest extends JexlTestCase {
// for non-silent, silent...
for (int s = 0; s < 2; ++s) {
boolean strict = Boolean.valueOf(s != 0);
- context.setStrict(true, strict);
+ options.setStrict(true);
+ options.setStrictArithmetic(strict);
int zthrow = 0;
int zeval = 0;
// for vars of all types...
@@ -581,14 +589,16 @@ public class ArithmeticTest extends JexlTestCase {
public void testOption() throws Exception {
Map<String, Object> vars = new HashMap<String, Object>();
JexlEvalContext context = new JexlEvalContext(vars);
+ JexlOptions options = context.getEngineOptions();
+ options.setStrictArithmetic(true);
JexlScript script = JEXL.createScript("0 + '1.2' ");
Object result;
- context.setStrictArithmetic(true);
+ options.setStrictArithmetic(true);
result = script.execute(context);
Assert.assertEquals("01.2", result);
- context.setStrictArithmetic(false);
+ options.setStrictArithmetic(false);
result = script.execute(context);
Assert.assertEquals(1.2d, (Double) result, EPSILON);
}
@@ -1284,7 +1294,8 @@ public class ArithmeticTest extends JexlTestCase {
public void testCoerceInteger() throws Exception {
JexlArithmetic ja = JEXL.getArithmetic();
JexlEvalContext ctxt = new JexlEvalContext();
- ctxt.setStrictArithmetic(true);
+ JexlOptions options = ctxt.getEngineOptions();
+ options.setStrictArithmetic(true);
String stmt = "a = 34L; b = 45.0D; c=56.0F; d=67B; e=78H;";
JexlScript expr = JEXL.createScript(stmt);
/* Object value = */ expr.execute(ctxt);
@@ -1302,7 +1313,8 @@ public class ArithmeticTest extends JexlTestCase {
public void testCoerceLong() throws Exception {
JexlArithmetic ja = JEXL.getArithmetic();
JexlEvalContext ctxt = new JexlEvalContext();
- ctxt.setStrictArithmetic(true);
+ JexlOptions options = ctxt.getEngineOptions();
+ options.setStrictArithmetic(true);
String stmt = "a = 34L; b = 45.0D; c=56.0F; d=67B; e=78H;";
JexlScript expr = JEXL.createScript(stmt);
/* Object value = */ expr.execute(ctxt);
@@ -1320,7 +1332,8 @@ public class ArithmeticTest extends JexlTestCase {
public void testCoerceDouble() throws Exception {
JexlArithmetic ja = JEXL.getArithmetic();
JexlEvalContext ctxt = new JexlEvalContext();
- ctxt.setStrictArithmetic(true);
+ JexlOptions options = ctxt.getEngineOptions();
+ options.setStrictArithmetic(true);
String stmt = "{a = 34L; b = 45.0D; c=56.0F; d=67B; e=78H; }";
JexlScript expr = JEXL.createScript(stmt);
/* Object value = */ expr.execute(ctxt);
@@ -1338,7 +1351,8 @@ public class ArithmeticTest extends JexlTestCase {
public void testCoerceBigInteger() throws Exception {
JexlArithmetic ja = JEXL.getArithmetic();
JexlEvalContext ctxt = new JexlEvalContext();
- ctxt.setStrictArithmetic(true);
+ JexlOptions options = ctxt.getEngineOptions();
+ options.setStrictArithmetic(true);
String stmt = "{a = 34L; b = 45.0D; c=56.0F; d=67B; e=78H; }";
JexlScript expr = JEXL.createScript(stmt);
/* Object value = */ expr.execute(ctxt);
@@ -1356,7 +1370,8 @@ public class ArithmeticTest extends JexlTestCase {
public void testCoerceBigDecimal() throws Exception {
JexlArithmetic ja = JEXL.getArithmetic();
JexlEvalContext ctxt = new JexlEvalContext();
- ctxt.setStrictArithmetic(true);
+ JexlOptions options = ctxt.getEngineOptions();
+ options.setStrictArithmetic(true);
String stmt = "{a = 34L; b = 45.0D; c=56.0F; d=67B; e=78H; }";
JexlScript expr = JEXL.createScript(stmt);
/* Object value = */ expr.execute(ctxt);
diff --git a/src/test/java/org/apache/commons/jexl3/BitwiseOperatorTest.java b/src/test/java/org/apache/commons/jexl3/BitwiseOperatorTest.java
index 94ba69a..ad80adb 100644
--- a/src/test/java/org/apache/commons/jexl3/BitwiseOperatorTest.java
+++ b/src/test/java/org/apache/commons/jexl3/BitwiseOperatorTest.java
@@ -32,7 +32,7 @@ public class BitwiseOperatorTest extends JexlTestCase {
@Before
public void setUp() {
asserter = new Asserter(JEXL);
- asserter.setStrict(false);
+ asserter.setStrict(false, false);
}
/**
diff --git a/src/test/java/org/apache/commons/jexl3/ClassCreatorTest.java b/src/test/java/org/apache/commons/jexl3/ClassCreatorTest.java
index 7a5e768..c241547 100644
--- a/src/test/java/org/apache/commons/jexl3/ClassCreatorTest.java
+++ b/src/test/java/org/apache/commons/jexl3/ClassCreatorTest.java
@@ -279,8 +279,9 @@ public class ClassCreatorTest extends JexlTestCase {
JexlExpression expr = jexl.createExpression("foo.value");
JexlExpression newx = jexl.createExpression("foo = new(clazz)");
JexlEvalContext context = new JexlEvalContext();
- context.setStrict(false);
- context.setSilent(true);
+ JexlOptions options = context.getEngineOptions();
+ options.setStrict(false);
+ options.setSilent(true);
ClassCreator cctor = new ClassCreator(jexl, base);
for (int i = 0; i < LOOPS && gced < 0; ++i) {
diff --git a/src/test/java/org/apache/commons/jexl3/ExceptionTest.java b/src/test/java/org/apache/commons/jexl3/ExceptionTest.java
index 2f744a2..49cabae 100644
--- a/src/test/java/org/apache/commons/jexl3/ExceptionTest.java
+++ b/src/test/java/org/apache/commons/jexl3/ExceptionTest.java
@@ -125,32 +125,33 @@ public class ExceptionTest extends JexlTestCase {
JexlEngine jexl = createEngine(false);
JexlExpression e = jexl.createExpression("c.e * 6");
JexlEvalContext ctxt = new JexlEvalContext();
+ JexlOptions options = ctxt.getEngineOptions();
// ensure errors will throw
- ctxt.setSilent(false);
+ options.setSilent(false);
// make unknown vars throw
- ctxt.setStrict(true);
+ options.setStrict(true);
// empty cotext
try {
/* Object o = */ e.evaluate(ctxt);
Assert.fail("c not defined as variable should throw");
} catch (JexlException.Variable xjexl) {
String msg = xjexl.getMessage();
- Assert.assertTrue(msg.indexOf("variable c") > 0);
+ Assert.assertTrue(msg.indexOf("variable 'c.e'") > 0);
}
// disallow null operands
- ctxt.setStrictArithmetic(true);
+ options.setStrictArithmetic(true);
ctxt.set("c.e", null);
try {
/* Object o = */ e.evaluate(ctxt);
Assert.fail("c.e as null operand should throw");
} catch (JexlException.Variable xjexl) {
String msg = xjexl.getMessage();
- Assert.assertTrue(msg.indexOf("variable c.e") > 0);
+ Assert.assertTrue(msg.indexOf("variable 'c.e'") > 0);
}
// allow null operands
- ctxt.setStrictArithmetic(false);
+ options.setStrictArithmetic(false);
try {
/* Object o = */ e.evaluate(ctxt);
@@ -166,7 +167,7 @@ public class ExceptionTest extends JexlTestCase {
Assert.fail("c.e not accessible as property should throw");
} catch (JexlException.Property xjexl) {
String msg = xjexl.getMessage();
- Assert.assertTrue(msg.indexOf("property e") > 0);
+ Assert.assertTrue(msg.indexOf("property 'e") > 0);
}
}
@@ -176,11 +177,12 @@ public class ExceptionTest extends JexlTestCase {
JexlEngine jexl = createEngine(false);
JexlScript e = jexl.createScript("(x)->{ x * 6 }");
JexlEvalContext ctxt = new JexlEvalContext();
+ JexlOptions options = ctxt.getEngineOptions();
// ensure errors will throw
- ctxt.setSilent(false);
+ options.setSilent(false);
// make unknown vars throw
- ctxt.setStrict(true);
- ctxt.setStrictArithmetic(true);
+ options.setStrict(true);
+ options.setStrictArithmetic(true);
// empty cotext
try {
/* Object o = */ e.execute(ctxt);
@@ -191,7 +193,7 @@ public class ExceptionTest extends JexlTestCase {
}
// allow null operands
- ctxt.setStrictArithmetic(false);
+ options.setStrictArithmetic(false);
try {
Object o = e.execute(ctxt, (Object) null);
} catch (JexlException.Variable xjexl) {
@@ -205,28 +207,29 @@ public class ExceptionTest extends JexlTestCase {
JexlEngine jexl = createEngine(false);
JexlExpression e = jexl.createExpression("c.e.foo()");
JexlEvalContext ctxt = new JexlEvalContext();
+ JexlOptions options = ctxt.getEngineOptions();
// ensure errors will throw
- ctxt.setSilent(false);
+ options.setSilent(false);
// make unknown vars throw
- ctxt.setStrict(true);
+ options.setStrict(true);
// empty cotext
try {
/* Object o = */ e.evaluate(ctxt);
Assert.fail("c not declared as variable should throw");
} catch (JexlException.Variable xjexl) {
String msg = xjexl.getMessage();
- Assert.assertTrue(msg.indexOf("variable c") > 0);
+ Assert.assertTrue(msg.indexOf("variable 'c.e'") > 0);
}
// disallow null operands
- ctxt.setStrictArithmetic(true);
+ options.setStrictArithmetic(true);
ctxt.set("c.e", null);
try {
/* Object o = */ e.evaluate(ctxt);
Assert.fail("c.e as null operand should throw");
} catch (JexlException xjexl) {
String msg = xjexl.getMessage();
- Assert.assertTrue(msg.indexOf("variable c.e") > 0);
+ Assert.assertTrue(msg.indexOf("variable 'c.e'") > 0);
}
}
diff --git a/src/test/java/org/apache/commons/jexl3/FeaturesTest.java b/src/test/java/org/apache/commons/jexl3/FeaturesTest.java
index 3a64362..02256ad 100644
--- a/src/test/java/org/apache/commons/jexl3/FeaturesTest.java
+++ b/src/test/java/org/apache/commons/jexl3/FeaturesTest.java
@@ -261,7 +261,7 @@ public class FeaturesTest extends JexlTestCase {
JexlFeatures f = new JexlFeatures().pragma(false);
String[] scripts = new String[]{
"#pragma foo 42",
- "@two var x = 3; #pragma foo 'bar'"
+ "#pragma foo 'bar'\n@two var x = 3;"
};
checkFeature(f, scripts);
}
diff --git a/src/test/java/org/apache/commons/jexl3/IfTest.java b/src/test/java/org/apache/commons/jexl3/IfTest.java
index 377cf82..2309705 100644
--- a/src/test/java/org/apache/commons/jexl3/IfTest.java
+++ b/src/test/java/org/apache/commons/jexl3/IfTest.java
@@ -212,13 +212,14 @@ public class IfTest extends JexlTestCase {
JexlEngine jexl = JEXL;
JexlEvalContext jc = new JexlEvalContext();
+ JexlOptions options = jc.getEngineOptions();
JexlExpression e = jexl.createExpression("x.y.z = foo ?'bar':'quux'");
Object o;
// undefined foo
for (int l = 0; l < 4; ++l) {
- jc.setStrict((l & 1) == 0);
- jc.setSilent((l & 2) != 0);
+ options.setStrict((l & 1) == 0);
+ options.setSilent((l & 2) != 0);
o = e.evaluate(jc);
Assert.assertEquals("Should be quux", "quux", o);
o = jc.get("x.y.z");
@@ -228,8 +229,8 @@ public class IfTest extends JexlTestCase {
jc.set("foo", null);
for (int l = 0; l < 4; ++l) {
- jc.setStrict((l & 1) == 0);
- jc.setSilent((l & 2) != 0);
+ options.setStrict((l & 1) == 0);
+ options.setSilent((l & 2) != 0);
o = e.evaluate(jc);
Assert.assertEquals("Should be quux", "quux", o);
o = jc.get("x.y.z");
@@ -239,8 +240,8 @@ public class IfTest extends JexlTestCase {
jc.set("foo", Boolean.FALSE);
for (int l = 0; l < 4; ++l) {
- jc.setStrict((l & 1) == 0);
- jc.setSilent((l & 2) != 0);
+ options.setStrict((l & 1) == 0);
+ options.setSilent((l & 2) != 0);
o = e.evaluate(jc);
Assert.assertEquals("Should be quux", "quux", o);
o = jc.get("x.y.z");
@@ -250,8 +251,8 @@ public class IfTest extends JexlTestCase {
jc.set("foo", Boolean.TRUE);
for (int l = 0; l < 4; ++l) {
- jc.setStrict((l & 1) == 0);
- jc.setSilent((l & 2) != 0);
+ options.setStrict((l & 1) == 0);
+ options.setSilent((l & 2) != 0);
o = e.evaluate(jc);
Assert.assertEquals("Should be bar", "bar", o);
o = jc.get("x.y.z");
@@ -269,14 +270,15 @@ public class IfTest extends JexlTestCase {
@Test
public void testTernaryShorthand() throws Exception {
JexlEvalContext jc = new JexlEvalContext();
+ JexlOptions options = jc.getEngineOptions();
JexlExpression e = JEXL.createExpression("x.y.z = foo?:'quux'");
JexlExpression f = JEXL.createExpression("foo??'quux'");
Object o;
// undefined foo
for (int l = 0; l < 4; ++l) {
- jc.setStrict((l & 1) == 0);
- jc.setSilent((l & 2) != 0);
+ options.setStrict((l & 1) == 0);
+ options.setSilent((l & 2) != 0);
o = e.evaluate(jc);
Assert.assertEquals("Should be quux", "quux", o);
o = jc.get("x.y.z");
@@ -288,8 +290,8 @@ public class IfTest extends JexlTestCase {
jc.set("foo", null);
for (int l = 0; l < 4; ++l) {
- jc.setStrict((l & 1) == 0);
- jc.setSilent((l & 2) != 0);
+ options.setStrict((l & 1) == 0);
+ options.setSilent((l & 2) != 0);
o = e.evaluate(jc);
Assert.assertEquals("Should be quux", "quux", o);
o = jc.get("x.y.z");
@@ -301,8 +303,8 @@ public class IfTest extends JexlTestCase {
jc.set("foo", Boolean.FALSE);
for (int l = 0; l < 4; ++l) {
- jc.setStrict((l & 1) == 0);
- jc.setSilent((l & 2) != 0);
+ options.setStrict((l & 1) == 0);
+ options.setSilent((l & 2) != 0);
o = e.evaluate(jc);
Assert.assertEquals("Should be quux", "quux", o);
o = jc.get("x.y.z");
@@ -314,8 +316,8 @@ public class IfTest extends JexlTestCase {
jc.set("foo", Double.NaN);
for (int l = 0; l < 4; ++l) {
- jc.setStrict((l & 1) == 0);
- jc.setSilent((l & 2) != 0);
+ options.setStrict((l & 1) == 0);
+ options.setSilent((l & 2) != 0);
o = e.evaluate(jc);
Assert.assertEquals("Should be quux", "quux", o);
o = jc.get("x.y.z");
@@ -327,8 +329,8 @@ public class IfTest extends JexlTestCase {
jc.set("foo", "");
for (int l = 0; l < 4; ++l) {
- jc.setStrict((l & 1) == 0);
- jc.setSilent((l & 2) != 0);
+ options.setStrict((l & 1) == 0);
+ options.setSilent((l & 2) != 0);
o = e.evaluate(jc);
Assert.assertEquals("Should be quux", "quux", o);
o = jc.get("x.y.z");
@@ -340,8 +342,8 @@ public class IfTest extends JexlTestCase {
jc.set("foo", "false");
for (int l = 0; l < 4; ++l) {
- jc.setStrict((l & 1) == 0);
- jc.setSilent((l & 2) != 0);
+ options.setStrict((l & 1) == 0);
+ options.setSilent((l & 2) != 0);
o = e.evaluate(jc);
Assert.assertEquals("Should be quux", "quux", o);
o = jc.get("x.y.z");
@@ -353,8 +355,8 @@ public class IfTest extends JexlTestCase {
jc.set("foo", 0d);
for (int l = 0; l < 4; ++l) {
- jc.setStrict((l & 1) == 0);
- jc.setSilent((l & 2) != 0);
+ options.setStrict((l & 1) == 0);
+ options.setSilent((l & 2) != 0);
o = e.evaluate(jc);
Assert.assertEquals("Should be quux", "quux", o);
o = jc.get("x.y.z");
@@ -366,8 +368,8 @@ public class IfTest extends JexlTestCase {
jc.set("foo", 0);
for (int l = 0; l < 4; ++l) {
- jc.setStrict((l & 1) == 0);
- jc.setSilent((l & 2) != 0);
+ options.setStrict((l & 1) == 0);
+ options.setSilent((l & 2) != 0);
o = e.evaluate(jc);
Assert.assertEquals("Should be quux", "quux", o);
o = jc.get("x.y.z");
@@ -379,8 +381,8 @@ public class IfTest extends JexlTestCase {
jc.set("foo", "bar");
for (int l = 0; l < 4; ++l) {
- jc.setStrict((l & 1) == 0);
- jc.setSilent((l & 2) != 0);
+ options.setStrict((l & 1) == 0);
+ options.setSilent((l & 2) != 0);
o = e.evaluate(jc);
Assert.assertEquals("Should be bar", "bar", o);
o = jc.get("x.y.z");
@@ -432,10 +434,11 @@ public class IfTest extends JexlTestCase {
@Test
public void testTernaryFail() throws Exception {
JexlEvalContext jc = new JexlEvalContext();
+ JexlOptions options = jc.getEngineOptions();
JexlExpression e = JEXL.createExpression("false ? bar : quux");
Object o;
- jc.setStrict(true);
- jc.setSilent(false);
+ options.setStrict(true);
+ options.setSilent(false);
try {
o = e.evaluate(jc);
Assert.fail("Should have failed");
diff --git a/src/test/java/org/apache/commons/jexl3/Issues100Test.java b/src/test/java/org/apache/commons/jexl3/Issues100Test.java
index 1ec0d73..fa7c576 100644
--- a/src/test/java/org/apache/commons/jexl3/Issues100Test.java
+++ b/src/test/java/org/apache/commons/jexl3/Issues100Test.java
@@ -114,7 +114,9 @@ public class Issues100Test extends JexlTestCase {
@Test
public void test106() throws Exception {
JexlEvalContext context = new JexlEvalContext();
- context.setStrict(true, true);
+ JexlOptions options = context.getEngineOptions();
+ options.setStrict(true);
+ options.setStrictArithmetic(true);
context.set("a", new BigDecimal(1));
context.set("b", new BigDecimal(3));
JexlEngine jexl = new Engine();
@@ -124,8 +126,8 @@ public class Issues100Test extends JexlTestCase {
} catch (JexlException xjexl) {
Assert.fail("should not occur");
}
- context.setMathContext(MathContext.UNLIMITED);
- context.setMathScale(2);
+ options.setMathContext(MathContext.UNLIMITED);
+ options.setMathScale(2);
try {
jexl.createExpression("a / b").evaluate(context);
Assert.fail("should fail");
@@ -289,6 +291,7 @@ public class Issues100Test extends JexlTestCase {
String expStr1 = "result == salary/month * work.percent/100.00";
JexlExpression exp1 = jexlX.createExpression(expStr1);
JexlEvalContext ctx = new JexlEvalContext();
+ JexlOptions options = ctx.getEngineOptions();
ctx.set("result", new BigDecimal("9958.33"));
ctx.set("salary", new BigDecimal("119500.00"));
ctx.set("month", new BigDecimal("12.00"));
@@ -298,7 +301,7 @@ public class Issues100Test extends JexlTestCase {
Assert.assertFalse((Boolean) exp1.evaluate(ctx));
// will succeed with scale = 2
- ctx.setMathScale(2);
+ options.setMathScale(2);
Assert.assertTrue((Boolean) exp1.evaluate(ctx));
}
diff --git a/src/test/java/org/apache/commons/jexl3/Issues200Test.java b/src/test/java/org/apache/commons/jexl3/Issues200Test.java
index cc28fc3..71c0324 100644
--- a/src/test/java/org/apache/commons/jexl3/Issues200Test.java
+++ b/src/test/java/org/apache/commons/jexl3/Issues200Test.java
@@ -133,6 +133,7 @@ public class Issues200Test extends JexlTestCase {
@Test
public void test217() throws Exception {
JexlEvalContext jc = new JexlEvalContext();
+ JexlOptions options = jc.getEngineOptions();
jc.set("foo", new int[]{0, 1, 2, 42});
JexlEngine jexl;
JexlScript e;
@@ -144,7 +145,8 @@ public class Issues200Test extends JexlTestCase {
// cache and fail?
jc.set("foo", new int[]{0, 1});
- jc.setStrict(true);
+ options.setStrict(true);
+ Assert.assertTrue(options.isStrict());
try {
r = e.execute(jc);
Assert.fail("should have thrown an exception");
@@ -153,7 +155,7 @@ public class Issues200Test extends JexlTestCase {
Assert.assertTrue(ArrayIndexOutOfBoundsException.class.equals(th.getClass()));
}
//
- jc.setStrict(false);
+ options.setStrict(false);
r = e.execute(jc);
Assert.assertNull("oob adverted", r);
}
@@ -815,7 +817,7 @@ public class Issues200Test extends JexlTestCase {
JexlEngine jexl = new JexlBuilder().strict(true).create();
String src;
JexlScript script;
- Object result;
+ Object result = null;
// declared, not defined
src = "x = 1; if (false) var x = 2; x";
script = jexl.createScript(src);
diff --git a/src/test/java/org/apache/commons/jexl3/Issues300Test.java b/src/test/java/org/apache/commons/jexl3/Issues300Test.java
index 527d124..d661a4e 100644
--- a/src/test/java/org/apache/commons/jexl3/Issues300Test.java
+++ b/src/test/java/org/apache/commons/jexl3/Issues300Test.java
@@ -16,7 +16,11 @@
*/
package org.apache.commons.jexl3;
+import java.util.Arrays;
import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
import org.junit.Assert;
import static org.junit.Assert.assertEquals;
import org.junit.Test;
@@ -234,4 +238,83 @@ public class Issues300Test {
Assert.assertEquals(4, xerror.getInfo().getLine());
}
}
+
+ public static class VaContext extends MapContext {
+ VaContext(Map<String, Object> vars) {
+ super(vars);
+ }
+ public int cell(String... ms) {
+ return ms.length == 0 ? 0 : ms.length;
+ }
+
+ public int cell(List<?> l, String...ms) {
+ return 42 + cell(ms);
+ }
+ }
+
+ @Test
+ public void test314() throws Exception {
+ JexlEngine jexl = new JexlBuilder().strict(true).create();
+ Map<String,Object> vars = new HashMap<String, Object>();
+ JexlContext ctxt = new VaContext(vars);
+ JexlScript script;
+ Object result;
+ script = jexl.createScript("cell()");
+ result = script.execute(ctxt);
+ Assert.assertEquals(0, result);
+ script = jexl.createScript("x.cell()", "x");
+ result = script.execute(ctxt, Arrays.asList(10, 20));
+ Assert.assertEquals(42, result);
+
+ vars.put("TVALOGAR", null);
+ String jexlExp = "TVALOGAR==null?'SIMON':'SIMONAZO'";
+ script = jexl.createScript(jexlExp);
+ result = script.execute(ctxt);
+ Assert.assertEquals("SIMON", result);
+
+ jexlExp = "TVALOGAR.PEPITO==null?'SIMON':'SIMONAZO'";
+ script = jexl.createScript(jexlExp);
+
+ Map<String, Object> tva = new LinkedHashMap<String, Object>();
+ tva.put("PEPITO", null);
+ vars.put("TVALOGAR", tva);
+ result = script.execute(ctxt);
+ Assert.assertEquals("SIMON", result);
+
+ vars.remove("TVALOGAR");
+ ctxt.set("TVALOGAR.PEPITO", null);
+ result = script.execute(ctxt);
+ Assert.assertEquals("SIMON", result);
+ }
+
+ @Test
+ public void test315() throws Exception {
+ JexlEngine jexl = new JexlBuilder().strict(true).create();
+ Map<String,Object> vars = new HashMap<String, Object>();
+ JexlContext ctxt = new VaContext(vars);
+ JexlScript script;
+ Object result;
+ script = jexl.createScript("a?? 42 + 10", "a");
+ result = script.execute(ctxt, 32);
+ Assert.assertEquals(32, result);
+ result = script.execute(ctxt, (Object) null);
+ Assert.assertEquals(52, result);
+ script = jexl.createScript("- a??42 + +10", "a");
+ result = script.execute(ctxt, 32);
+ Assert.assertEquals(-32, result);
+ result = script.execute(ctxt, (Object) null);
+ Assert.assertEquals(52, result);
+ // long version of ternary
+ script = jexl.createScript("a? a : +42 + 10", "a");
+ result = script.execute(ctxt, 32);
+ Assert.assertEquals(32, result);
+ result = script.execute(ctxt, (Object) null);
+ Assert.assertEquals(52, result);
+ // short one, elvis, equivalent
+ script = jexl.createScript("a ?: +42 + 10", "a");
+ result = script.execute(ctxt, 32);
+ Assert.assertEquals(32, result);
+ result = script.execute(ctxt, (Object) null);
+ Assert.assertEquals(52, result);
+ }
}
diff --git a/src/test/java/org/apache/commons/jexl3/IssuesTest.java b/src/test/java/org/apache/commons/jexl3/IssuesTest.java
index 7b8ebf8..bda7b35 100644
--- a/src/test/java/org/apache/commons/jexl3/IssuesTest.java
+++ b/src/test/java/org/apache/commons/jexl3/IssuesTest.java
@@ -84,9 +84,10 @@ public class IssuesTest extends JexlTestCase {
public void test48() throws Exception {
JexlEngine jexl = new Engine();
JexlEvalContext jc = new JexlEvalContext();
+ JexlOptions options = jc.getEngineOptions();
// ensure errors will throw
- jc.setStrict(true);
- jc.setSilent(false);
+ options.setStrict(true);
+ options.setSilent(false);
try {
String jexlExp = "(foo.getInner().foo() eq true) and (foo.getInner().goo() = (foo.getInner().goo()+1-1))";
JexlExpression e = jexl.createExpression(jexlExp);
@@ -106,8 +107,9 @@ public class IssuesTest extends JexlTestCase {
public void test47() throws Exception {
JexlEngine jexl = new Engine();
JexlEvalContext ctxt = new JexlEvalContext();
+ JexlOptions options = ctxt.getEngineOptions();
// ensure errors will throw
- ctxt.setSilent(false);
+ options.setSilent(false);
JexlExpression expr = jexl.createExpression("true//false\n");
Object value = expr.evaluate(ctxt);
@@ -131,7 +133,10 @@ public class IssuesTest extends JexlTestCase {
// ensure errors will throw
//jexl.setSilent(false);
JexlEvalContext ctxt = new JexlEvalContext();
- ctxt.setStrict(false);
+ JexlOptions options = ctxt.getEngineOptions();
+ options.setOptions(jexl);
+ options.setStrict(false);
+ options.setStrictArithmetic(false);
ctxt.set("ax", "ok");
JxltEngine.Expression expr = uel.createExpression("${ax+(bx)}");
@@ -156,8 +161,10 @@ public class IssuesTest extends JexlTestCase {
public void test40() throws Exception {
JexlEngine jexl = new Engine();
JexlEvalContext ctxt = new JexlEvalContext();
+ JexlOptions options = ctxt.getEngineOptions();
+ options.setOptions(jexl);
// ensure errors will throw
- ctxt.setSilent(false);
+ options.setSilent(false);
ctxt.set("derived", new Derived());
@@ -198,9 +205,10 @@ public class IssuesTest extends JexlTestCase {
public void test11() throws Exception {
JexlEngine jexl = createEngine(false);
JexlEvalContext ctxt = new JexlEvalContext();
+ JexlOptions options = ctxt.getEngineOptions();
// ensure errors will throw
- ctxt.setSilent(false);
- ctxt.setStrict(true);
+ options.setSilent(false);
+ options.setStrict(true);
ctxt.set("a", null);
@@ -228,8 +236,9 @@ public class IssuesTest extends JexlTestCase {
JexlEngine jexl = createEngine(false);
MapContext vars = new MapContext();
JexlEvalContext ctxt = new JexlEvalContext(vars);
- ctxt.setStrict(true);
- ctxt.setSilent(true);// to avoid throwing JexlException on null method call
+ JexlOptions options = ctxt.getEngineOptions();
+ options.setStrict(true);
+ options.setSilent(true);// to avoid throwing JexlException on null method call
JexlScript jscript;
@@ -259,8 +268,9 @@ public class IssuesTest extends JexlTestCase {
public void test87() throws Exception {
JexlEngine jexl = createEngine(false);
JexlEvalContext ctxt = new JexlEvalContext();
+ JexlOptions options = ctxt.getEngineOptions();
// ensure errors will throw
- ctxt.setSilent(false);
+ options.setSilent(false);
JexlExpression divide = jexl.createExpression("l / r");
JexlExpression modulo = jexl.createExpression("l % r");
@@ -280,8 +290,9 @@ public class IssuesTest extends JexlTestCase {
public void test90() throws Exception {
JexlEngine jexl = createEngine(false);
JexlEvalContext ctxt = new JexlEvalContext();
+ JexlOptions options = ctxt.getEngineOptions();
// ensure errors will throw
- ctxt.setSilent(false);
+ options.setSilent(false);
// ';' is necessary between expressions
String[] fexprs = {
"a=3 b=4",
@@ -320,8 +331,9 @@ public class IssuesTest extends JexlTestCase {
public void test44() throws Exception {
JexlEngine jexl = createEngine(false);
JexlEvalContext ctxt = new JexlEvalContext();
+ JexlOptions options = ctxt.getEngineOptions();
// ensure errors will throw
- ctxt.setSilent(false);
+ options.setSilent(false);
JexlScript script;
script = jexl.createScript("'hello world!'//commented");
Assert.assertEquals("hello world!", script.execute(ctxt));
@@ -337,8 +349,9 @@ public class IssuesTest extends JexlTestCase {
public void test97() throws Exception {
JexlEngine jexl = createEngine(false);
JexlEvalContext ctxt = new JexlEvalContext();
+ JexlOptions options = ctxt.getEngineOptions();
// ensure errors will throw
- ctxt.setSilent(false);
+ options.setSilent(false);
for (char v = 'a'; v <= 'z'; ++v) {
ctxt.set(Character.toString(v), 10);
}
diff --git a/src/test/java/org/apache/commons/jexl3/JXLTTest.java b/src/test/java/org/apache/commons/jexl3/JXLTTest.java
index cc2b8d5..ca085fc 100644
--- a/src/test/java/org/apache/commons/jexl3/JXLTTest.java
+++ b/src/test/java/org/apache/commons/jexl3/JXLTTest.java
@@ -389,19 +389,20 @@ public class JXLTTest extends JexlTestCase {
@Test
public void testCharAtBug() throws Exception {
context.set("foo", "abcdef");
+ JexlOptions options = context.getEngineOptions();
JxltEngine.Expression expr = JXLT.createExpression("${foo.substring(2,4)/*comment*/}");
Object o = expr.evaluate(context);
Assert.assertEquals("cd", o);
context.set("bar", "foo");
try {
- context.setSilent(true);
+ options.setSilent(true);
expr = JXLT.createExpression("#{${bar}+'.charAt(-2)'}");
expr = expr.prepare(context);
o = expr.evaluate(context);
Assert.assertEquals(null, o);
} finally {
- context.setSilent(false);
+ options.setSilent(false);
}
}
@@ -861,4 +862,33 @@ public class JXLTTest extends JexlTestCase {
String output = strw.toString();
Assert.assertEquals("<p>Universe 42</p>\n", output);
}
+
+ @Test
+ public void test315() throws Exception {
+ String s315;
+ StringWriter strw;
+ JxltEngine.Template t315;
+ String output;
+
+ s315 = "<report/>$";
+ t315 = JXLT.createTemplate("$$", new StringReader(s315));
+ strw = new StringWriter();
+ t315.evaluate(context, strw);
+ output = strw.toString();
+ Assert.assertEquals(s315, output);
+
+ s315 = "<foo/>#";
+ t315 = JXLT.createTemplate("$$", new StringReader(s315));
+ strw = new StringWriter();
+ t315.evaluate(context, strw);
+ output = strw.toString();
+ Assert.assertEquals(s315, output);
+
+ s315 = "<bar/>\\";
+ t315 = JXLT.createTemplate("$$", new StringReader(s315));
+ strw = new StringWriter();
+ t315.evaluate(context, strw);
+ output = strw.toString();
+ Assert.assertEquals(s315, output);
+ }
}
diff --git a/src/test/java/org/apache/commons/jexl3/JexlEvalContext.java b/src/test/java/org/apache/commons/jexl3/JexlEvalContext.java
index 4a64f07..55fdcad 100644
--- a/src/test/java/org/apache/commons/jexl3/JexlEvalContext.java
+++ b/src/test/java/org/apache/commons/jexl3/JexlEvalContext.java
@@ -16,10 +16,10 @@
*/
package org.apache.commons.jexl3;
-import java.math.MathContext;
-import java.nio.charset.Charset;
import java.util.Collections;
import java.util.Map;
+import org.apache.commons.jexl3.annotations.NoJexl;
+import org.apache.commons.jexl3.internal.Options;
/**
* A JEXL evaluation environment wrapping variables, namespace and options.
@@ -27,44 +27,33 @@ import java.util.Map;
public class JexlEvalContext implements
JexlContext,
JexlContext.NamespaceResolver,
- JexlEngine.Options {
+ JexlContext.OptionsHandle {
/** The marker for the empty vars. */
private static final Map<String,Object> EMPTY_MAP = Collections.<String,Object>emptyMap();
/** The variables.*/
private final JexlContext vars;
/** The namespace. */
- private final JexlContext.NamespaceResolver ns;
- /** Whether the engine should be silent. */
- private Boolean silent = null;
- /** Whether the engine should be strict. */
- private Boolean strict = null;
- /** Whether the engine should be cancellable. */
- private Boolean cancellable = null;
- /** Whether the arithmetic should be strict. */
- private Boolean mathStrict = null;
- /** The math scale the arithmetic should use. */
- private int mathScale = Integer.MIN_VALUE;
- /** The math context the arithmetic should use. */
- private MathContext mathContext = null;
+ private final JexlContext.NamespaceResolver ns;
+ /** The options. */
+ private final JexlOptions options = new Options();
+
+
/**
* Default constructor.
*/
+ @NoJexl
public JexlEvalContext() {
this(EMPTY_MAP);
}
-
- @Override
- public Charset getCharset() {
- return Charset.defaultCharset();
- }
-
+
/**
* Creates an evaluation environment wrapping an existing user provided vars.
* <p>The supplied vars should be null only in derived classes that override the get/set/has methods.
* For a default vars context with a code supplied vars, use the default no-parameter contructor.</p>
* @param map the variables map
*/
+ @NoJexl
public JexlEvalContext(Map<String, Object> map) {
this.vars = map == EMPTY_MAP ? new MapContext() : new MapContext(map);
this.ns = null;
@@ -74,6 +63,7 @@ public class JexlEvalContext implements
* Creates an evaluation environment from a context.
* @param context the context (may be null, implies readonly)
*/
+ @NoJexl
public JexlEvalContext(JexlContext context) {
this(context, context instanceof JexlContext.NamespaceResolver? (JexlContext.NamespaceResolver) context : null);
}
@@ -83,148 +73,40 @@ public class JexlEvalContext implements
* @param context the context (may be null, implies readonly)
* @param namespace the namespace (may be null, implies empty namespace)
*/
+ @NoJexl
public JexlEvalContext(JexlContext context, JexlContext.NamespaceResolver namespace) {
this.vars = context != null? context : JexlEngine.EMPTY_CONTEXT;
this.ns = namespace != null? namespace : JexlEngine.EMPTY_NS;
}
@Override
+ @NoJexl
public boolean has(String name) {
return vars.has(name);
}
@Override
+ @NoJexl
public Object get(String name) {
return vars.get(name);
}
@Override
+ @NoJexl
public void set(String name, Object value) {
vars.set(name, value);
}
@Override
+ @NoJexl
public Object resolveNamespace(String name) {
return ns != null? ns.resolveNamespace(name) : null;
}
- /**
- * Clear all options.
- */
- public void clearOptions() {
- silent = null;
- strict = null;
- cancellable = null;
- mathScale = -1;
- mathContext = null;
- }
-
- /**
- * Set options from engine.
- * @param jexl the engine
- */
- public void setOptions(JexlEngine jexl) {
- silent = jexl.isSilent();
- strict = jexl.isStrict();
- mathScale = jexl.getArithmetic().getMathScale();
- mathContext = jexl.getArithmetic().getMathContext();
- }
-
- /**
- * Sets whether the engine will throw JexlException during evaluation when an error is triggered.
- * @param s true means no JexlException will occur, false allows them
- */
- public void setSilent(boolean s) {
- this.silent = s ? Boolean.TRUE : Boolean.FALSE;
- }
-
- @Override
- public Boolean isSilent() {
- return this.silent;
- }
-
- /**
- * Sets whether the engine will throw JexlException.Cancel during evaluation when interrupted.
- * @param s true means JexlException.Cancel will be thrown, false implies null will be returned
- */
- public void setCancellable(boolean c) {
- this.cancellable = c ? Boolean.TRUE : Boolean.FALSE;
- }
-
@Override
- public Boolean isCancellable() {
- return this.cancellable;
- }
-
- /**
- * Sets the engine and arithmetic strict flags in one call.
- * @param se the engine strict flag
- * @param sa the arithmetic strict flag
- */
- public void setStrict(Boolean se, Boolean sa) {
- this.strict = se == null? null : se ? Boolean.TRUE : Boolean.FALSE;
- this.mathStrict = sa == null? null : sa ? Boolean.TRUE : Boolean.FALSE;
- }
-
- /**
- * Sets whether the engine will consider unknown variables, methods and constructors as errors or evaluates them
- * as null.
- * @param se true means strict error reporting, false allows mentioned conditions to be evaluated as null
- */
- public void setStrict(boolean se) {
- setStrict(se, se);
- }
-
- @Override
- public Boolean isStrict() {
- if (strict == null) {
- return null;
- } else {
- return strict.booleanValue() ? Boolean.TRUE : Boolean.FALSE;
- }
- }
-
- /**
- * Sets whether the arithmetic will consider null arguments as errors during evaluation.
- * @param s true means strict error reporting, false allows mentioned conditions to be evaluated as 0
- */
- public void setStrictArithmetic(boolean s) {
- this.mathStrict = s ? Boolean.TRUE : Boolean.FALSE;
- }
-
- @Override
- public Boolean isStrictArithmetic() {
- if (mathStrict == null) {
- return null;
- } else {
- return mathStrict.booleanValue() ? Boolean.TRUE : Boolean.FALSE;
- }
- }
-
- @Override
- public MathContext getArithmeticMathContext() {
- return mathContext;
- }
-
- /**
- * Sets the {@link MathContext} to use by the {@link JexlArithmetic} during evaluation.
- * @param mc the math context
- */
- public void setMathContext(MathContext mc) {
- mathContext = mc;
- }
-
- @Override
- public int getArithmeticMathScale() {
- return mathScale;
- }
-
- /**
- * Sets the math scale to use to use by the {@link JexlArithmetic} during evaluation.
- * @param scale the math scale
- */
- public void setMathScale(int scale) {
- mathScale = scale;
+ @NoJexl
+ public JexlOptions getEngineOptions() {
+ return options;
}
}
diff --git a/src/test/java/org/apache/commons/jexl3/JexlTest.java b/src/test/java/org/apache/commons/jexl3/JexlTest.java
index 8db318b..e8bd57a 100644
--- a/src/test/java/org/apache/commons/jexl3/JexlTest.java
+++ b/src/test/java/org/apache/commons/jexl3/JexlTest.java
@@ -155,7 +155,8 @@ public class JexlTest extends JexlTestCase {
@Test
public void testEmpty() throws Exception {
JexlEvalContext jc = new JexlEvalContext();
- jc.setStrict(false);
+ JexlOptions options = jc.getEngineOptions();
+ options.setStrict(false);
jc.set("string", "");
jc.set("array", new Object[0]);
jc.set("map", new HashMap<Object, Object>());
@@ -179,7 +180,8 @@ public class JexlTest extends JexlTestCase {
@Test
public void testSize() throws Exception {
JexlEvalContext jc = new JexlEvalContext();
- jc.setStrict(false);
+ JexlOptions options = jc.getEngineOptions();
+ options.setStrict(false);
jc.set("s", "five!");
jc.set("array", new Object[5]);
@@ -275,7 +277,9 @@ public class JexlTest extends JexlTestCase {
@Test
public void testCalculations() throws Exception {
JexlEvalContext jc = new JexlEvalContext();
- jc.setStrict(false);
+ JexlOptions options = jc.getEngineOptions();
+ options.setStrict(false);
+ options.setStrictArithmetic(false);
/*
* test to ensure new string cat works
@@ -301,6 +305,7 @@ public class JexlTest extends JexlTestCase {
@Test
public void testConditions() throws Exception {
JexlEvalContext jc = new JexlEvalContext();
+ JexlOptions options = jc.getEngineOptions();
jc.set("foo", new Integer(2));
jc.set("aFloat", new Float(1));
jc.set("aDouble", new Double(2));
@@ -331,11 +336,11 @@ public class JexlTest extends JexlTestCase {
assertExpression(jc, "aBool == 'false'", Boolean.FALSE);
assertExpression(jc, "aBool != 'false'", Boolean.TRUE);
// test null and boolean
- jc.setStrict(false);
+ options.setStrict(false);
assertExpression(jc, "aBool == notThere", Boolean.FALSE);
assertExpression(jc, "aBool != notThere", Boolean.TRUE);
// anything and string as a string comparison
- jc.setStrict(true);
+ options.setStrict(true);
assertExpression(jc, "aBuffer == 'abc'", Boolean.TRUE);
assertExpression(jc, "aBuffer != 'abc'", Boolean.FALSE);
// arbitrary equals
@@ -409,7 +414,8 @@ public class JexlTest extends JexlTestCase {
@Test
public void testNull() throws Exception {
JexlEvalContext jc = new JexlEvalContext();
- jc.setStrict(false);
+ JexlOptions options = jc.getEngineOptions();
+ options.setStrict(false);
jc.set("bar", new Integer(2));
assertExpression(jc, "empty foo", Boolean.TRUE);
@@ -541,7 +547,8 @@ public class JexlTest extends JexlTestCase {
@Test
public void testCharAtBug() throws Exception {
JexlEvalContext jc = new JexlEvalContext();
- jc.setSilent(true);
+ JexlOptions options = jc.getEngineOptions();
+ options.setSilent(true);
jc.set("foo", "abcdef");
diff --git a/src/test/java/org/apache/commons/jexl3/LexicalTest.java b/src/test/java/org/apache/commons/jexl3/LexicalTest.java
new file mode 100644
index 0000000..3520c16
--- /dev/null
+++ b/src/test/java/org/apache/commons/jexl3/LexicalTest.java
@@ -0,0 +1,228 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.jexl3;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+/**
+ * Test cases for lexical option and feature.
+ */
+public class LexicalTest {
+
+ @Test
+ public void testLexical0a() throws Exception {
+ runLexical0(false);
+ }
+
+ @Test
+ public void testLexical0b() throws Exception {
+ runLexical0(true);
+ }
+
+ void runLexical0(boolean feature) throws Exception {
+ JexlFeatures f = new JexlFeatures();
+ f.lexical(feature);
+ JexlEngine jexl = new JexlBuilder().strict(true).features(f).create();
+ JexlEvalContext ctxt = new JexlEvalContext();
+ JexlOptions options = ctxt.getEngineOptions();
+ // ensure errors will throw
+ options.setLexical(true);
+ JexlScript script;
+ try {
+ script = jexl.createScript("var x = 0; var x = 1;");
+ if (!feature) {
+ script.execute(ctxt);
+ }
+ Assert.fail();
+ } catch (JexlException xany) {
+ String ww = xany.toString();
+ }
+ try {
+ script = jexl.createScript("var x = 0; for(var y : null) { var y = 1;");
+ if (!feature) {
+ script.execute(ctxt);
+ }
+ Assert.fail();
+ } catch (JexlException xany) {
+ String ww = xany.toString();
+ }
+ try {
+ script = jexl.createScript("var x = 0; for(var x : null) {};");
+ if (!feature) {
+ script.execute(ctxt);
+ }
+ Assert.fail();
+ } catch (JexlException xany) {
+ String ww = xany.toString();
+ }
+ try {
+ script = jexl.createScript("(x)->{ var x = 0; x; }");
+ if (!feature) {
+ script.execute(ctxt);
+ }
+ Assert.fail();
+ } catch (JexlException xany) {
+ String ww = xany.toString();
+ }
+ try {
+ script = jexl.createScript("var x; if (true) { if (true) { var x = 0; x; } }");
+ if (!feature) {
+ script.execute(ctxt);
+ }
+ Assert.fail();
+ } catch (JexlException xany) {
+ String ww = xany.toString();
+ }
+ try {
+ script = jexl.createScript("if (a) { var y = (x)->{ var x = 0; x; }; y(2) }", "a");
+ if (!feature) {
+ script.execute(ctxt);
+ }
+ Assert.fail();
+ } catch (JexlException xany) {
+ String ww = xany.toString();
+ }
+ try {
+ script = jexl.createScript("(x)->{ for(var x : null) { x; } }");
+ if (!feature) {
+ script.execute(ctxt, 42);
+ }
+ Assert.fail();
+ } catch (JexlException xany) {
+ String ww = xany.toString();
+ }
+ // no fail
+ script = jexl.createScript("var x = 32; (()->{ for(var x : null) { x; }})();");
+ if (!feature) {
+ script.execute(ctxt, 42);
+ }
+ }
+
+ @Test
+ public void testLexical1a() throws Exception {
+ runLexical1(false);
+ }
+
+ @Test
+ public void testLexical1b() throws Exception {
+ runLexical1(true);
+ }
+
+ void runLexical1(boolean shade) throws Exception {
+ JexlEngine jexl = new JexlBuilder().strict(true).create();
+ JexlEvalContext ctxt = new JexlEvalContext();
+ Object result;
+ ctxt.set("x", 4242);
+ JexlOptions options = ctxt.getEngineOptions();
+ // ensure errors will throw
+ options.setLexical(true);
+ options.setLexicalShade(shade);
+ JexlScript script;
+ try {
+ // if local shade, x is undefined
+ script = jexl.createScript("{ var x = 0; } x");
+ script.execute(ctxt);
+ if (shade) {
+ Assert.fail("local shade means 'x' should be undefined");
+ }
+ } catch (JexlException xany) {
+ if (!shade) {
+ throw xany;
+ }
+ }
+ try {
+ // if local shade, x = 42 is undefined
+ script = jexl.createScript("{ var x = 0; } x = 42");
+ script.execute(ctxt);
+ if (shade) {
+ Assert.fail("local shade means 'x = 42' should be undefined");
+ }
+ } catch (JexlException xany) {
+ if (!shade) {
+ throw xany;
+ }
+ }
+ try {
+ // if local shade, x = 42 is undefined
+ script = jexl.createScript("{ var x = 0; } y = 42");
+ script.execute(ctxt);
+ if (shade) {
+ Assert.fail("local shade means 'y = 42' should be undefined (y is undefined)");
+ }
+ } catch (JexlException xany) {
+ if (!shade) {
+ throw xany;
+ }
+ }
+ // no fail
+ script = jexl.createScript("var x = 32; (()->{ for(var x : null) { x; }})();");
+ //if (!feature) {
+ script.execute(ctxt, 42);
+ //}
+ // y being defined as global
+ ctxt.set("y", 4242);
+ try {
+ // if no shade and global y being defined,
+ script = jexl.createScript("{ var y = 0; } y = 42");
+ result = script.execute(ctxt);
+ if (!shade) {
+ Assert.assertEquals(42, result);
+ } else {
+ Assert.fail("local shade means 'y = 42' should be undefined");
+ }
+ } catch (JexlException xany) {
+ if (!shade) {
+ throw xany;
+ }
+ }
+ }
+
+ @Test
+ public void testLexical1() throws Exception {
+ JexlEngine jexl = new JexlBuilder().strict(true).create();
+ JexlEvalContext ctxt = new JexlEvalContext();
+ JexlOptions options = ctxt.getEngineOptions();
+ // ensure errors will throw
+ options.setLexical(true);
+ JexlScript script;
+ Object result;
+
+ script = jexl.createScript("var x = 0; for(var y : [1]) { var x = 42; return x; };");
+ try {
+ result = script.execute(ctxt);
+ //Assert.assertEquals(42, result);
+ Assert.fail();
+ } catch (JexlException xany) {
+ String ww = xany.toString();
+ }
+
+ try {
+ script = jexl.createScript("(x)->{ if (x) { var x = 7 * (x + x); x; } }");
+ result = script.execute(ctxt, 3);
+ Assert.fail();
+ } catch (JexlException xany) {
+ String ww = xany.toString();
+ }
+
+ script = jexl.createScript("{ var x = 0; } var x = 42; x");
+ result = script.execute(ctxt, 21);
+ Assert.assertEquals(42, result);
+
+ }
+
+}
diff --git a/src/test/java/org/apache/commons/jexl3/PragmaTest.java b/src/test/java/org/apache/commons/jexl3/PragmaTest.java
index 7dbecbb..2e549c8 100644
--- a/src/test/java/org/apache/commons/jexl3/PragmaTest.java
+++ b/src/test/java/org/apache/commons/jexl3/PragmaTest.java
@@ -16,6 +16,8 @@
*/
package org.apache.commons.jexl3;
+import java.math.MathContext;
+import java.nio.charset.Charset;
import java.util.Map;
import org.junit.Assert;
import org.junit.Test;
@@ -58,5 +60,44 @@ public class PragmaTest extends JexlTestCase {
Assert.assertEquals(1, pragmas.get("one"));
Assert.assertEquals("truth", pragmas.get("the.very.hard"));
}
+
+ public static class SafeContext extends JexlEvalContext {
+ // @Override
+ public void processPragmas(Map<String, Object> pragmas) {
+ if (pragmas != null && !pragmas.isEmpty()) {
+ JexlOptions options = getEngineOptions();
+ for (Map.Entry<String, Object> pragma : pragmas.entrySet()) {
+ String key = pragma.getKey();
+ Object value = pragma.getValue();
+ if ("jexl.safe".equals(key) && value instanceof Boolean) {
+ options.setSafe(((Boolean) value).booleanValue());
+ } else if ("jexl.strict".equals(key) && value instanceof Boolean) {
+ options.setStrict(((Boolean) value).booleanValue());
+ } else if ("jexl.silent".equals(key) && value instanceof Boolean) {
+ options.setSilent(((Boolean) value).booleanValue());
+ }
+ }
+ }
+ }
+ }
+ @Test
+ @SuppressWarnings("AssertEqualsBetweenInconvertibleTypes")
+ public void testSafePragma() throws Exception {
+ SafeContext jc = new SafeContext();
+ jc.set("foo", null);
+ JexlScript script = JEXL.createScript("#pragma jexl.safe true\nfoo.bar;");
+ Assert.assertTrue(script != null);
+ jc.processPragmas(script.getPragmas());
+ Object result = script.execute(jc);
+ Assert.assertNull(result);
+ jc = new SafeContext();
+ jc.set("foo", null);
+ try {
+ result = script.execute(jc);
+ Assert.fail("should have thrown");
+ } catch (JexlException xvar) {
+ // ok, expected
+ }
+ }
}
diff --git a/src/test/java/org/apache/commons/jexl3/ReadonlyContext.java b/src/test/java/org/apache/commons/jexl3/ReadonlyContext.java
index 97fe8c1..7b094e4 100644
--- a/src/test/java/org/apache/commons/jexl3/ReadonlyContext.java
+++ b/src/test/java/org/apache/commons/jexl3/ReadonlyContext.java
@@ -16,30 +16,30 @@
*/
package org.apache.commons.jexl3;
-import java.math.MathContext;
-import java.nio.charset.Charset;
+import org.apache.commons.jexl3.annotations.NoJexl;
/**
* A readonly context wrapper.
* @since 3.0
*/
-public final class ReadonlyContext implements JexlContext, JexlEngine.Options {
+public final class ReadonlyContext implements JexlContext, JexlContext.OptionsHandle {
/** The wrapped context. */
private final JexlContext wrapped;
/** The wrapped engine options. */
- private final JexlEngine.Options options;
+ private final JexlOptions options;
/**
* Creates a new readonly context.
* @param context the wrapped context
* @param eopts the engine evaluation options
*/
- public ReadonlyContext(JexlContext context, JexlEngine.Options eopts) {
+ public ReadonlyContext(JexlContext context, JexlOptions eopts) {
wrapped = context;
options = eopts;
}
@Override
+ @NoJexl
public Object get(String name) {
return wrapped.get(name);
}
@@ -50,47 +50,20 @@ public final class ReadonlyContext implements JexlContext, JexlEngine.Options {
* @param value the unused variable value
*/
@Override
+ @NoJexl
public void set(String name, Object value) {
throw new UnsupportedOperationException("Not supported.");
}
@Override
+ @NoJexl
public boolean has(String name) {
return wrapped.has(name);
}
@Override
- public Boolean isSilent() {
- return options == null? null : options.isSilent();
- }
-
- @Override
- public Boolean isStrict() {
- return options == null? null : options.isStrict();
- }
-
- @Override
- public Boolean isCancellable() {
- return options == null? null : options.isCancellable();
- }
-
- @Override
- public Boolean isStrictArithmetic() {
- return options == null? null : options.isStrict();
- }
-
- @Override
- public MathContext getArithmeticMathContext() {
- return options == null? null : options.getArithmeticMathContext();
- }
-
- @Override
- public int getArithmeticMathScale() {
- return options == null? -1 : options.getArithmeticMathScale();
- }
-
- @Override
- public Charset getCharset() {
- return options == null? Charset.defaultCharset() : options.getCharset();
+ @NoJexl
+ public JexlOptions getEngineOptions() {
+ return options;
}
}
diff --git a/src/test/java/org/apache/commons/jexl3/VarTest.java b/src/test/java/org/apache/commons/jexl3/VarTest.java
index f0f3126..23982b8 100644
--- a/src/test/java/org/apache/commons/jexl3/VarTest.java
+++ b/src/test/java/org/apache/commons/jexl3/VarTest.java
@@ -41,9 +41,10 @@ public class VarTest extends JexlTestCase {
@Test
public void testStrict() throws Exception {
JexlEvalContext env = new JexlEvalContext();
- JexlContext ctxt = new ReadonlyContext(env, env);
- env.setStrict(true);
- env.setSilent(false);
+ JexlOptions options = env.getEngineOptions();
+ JexlContext ctxt = new ReadonlyContext(env, options);
+ options.setStrict(true);
+ options.setSilent(false);
JexlScript e;
e = JEXL.createScript("x");
diff --git a/src/test/java/org/apache/commons/jexl3/junit/Asserter.java b/src/test/java/org/apache/commons/jexl3/junit/Asserter.java
index 93f2c70..a68d64b 100644
--- a/src/test/java/org/apache/commons/jexl3/junit/Asserter.java
+++ b/src/test/java/org/apache/commons/jexl3/junit/Asserter.java
@@ -72,19 +72,16 @@ public class Asserter extends Assert {
}
public void setStrict(boolean s) {
- context.setStrict(s, s);
+ context.getEngineOptions().setStrict(s);
}
public void setStrict(boolean es, boolean as) {
- context.setStrict(es, as);
+ context.getEngineOptions().setStrict(es);
+ context.getEngineOptions().setStrictArithmetic(as);
}
public void setSilent(boolean silent) {
- context.setSilent(silent);
- }
-
- public void clearOptions() {
- context.clearOptions();
+ context.getEngineOptions().setSilent(silent);
}
/**