Author: henrib
Date: Wed Jul 11 12:24:24 2012
New Revision: 1360142
URL: http://svn.apache.org/viewvc?rev=1360142&view=rev
Log:
JEXL-137:
Modified JexlEngine.getVariable() logic and test;
Added Dumper class to ease characterizing AST related issues;
Added:
commons/proper/jexl/branches/2.0/src/test/java/org/apache/commons/jexl2/Dumper.java
(with props)
Modified:
commons/proper/jexl/branches/2.0/src/main/java/org/apache/commons/jexl2/JexlEngine.java
commons/proper/jexl/branches/2.0/src/test/java/org/apache/commons/jexl2/VarTest.java
Modified:
commons/proper/jexl/branches/2.0/src/main/java/org/apache/commons/jexl2/JexlEngine.java
URL:
http://svn.apache.org/viewvc/commons/proper/jexl/branches/2.0/src/main/java/org/apache/commons/jexl2/JexlEngine.java?rev=1360142&r1=1360141&r2=1360142&view=diff
==============================================================================
---
commons/proper/jexl/branches/2.0/src/main/java/org/apache/commons/jexl2/JexlEngine.java
(original)
+++
commons/proper/jexl/branches/2.0/src/main/java/org/apache/commons/jexl2/JexlEngine.java
Wed Jul 11 12:24:24 2012
@@ -55,10 +55,10 @@ import org.apache.commons.jexl2.parser.A
* Creates and evaluates Expression and Script objects.
* Determines the behavior of Expressions & Scripts during their evaluation
with respect to:
* <ul>
- * <li>Introspection, see {@link Uberspect}</li>
- * <li>Arithmetic & comparison, see {@link JexlArithmetic}</li>
- * <li>Error reporting</li>
- * <li>Logging</li>
+ * <li>Introspection, see {@link Uberspect}</li>
+ * <li>Arithmetic & comparison, see {@link JexlArithmetic}</li>
+ * <li>Error reporting</li>
+ * <li>Logging</li>
* </ul>
* </p>
* <p>The <code>setSilent</code> and <code>setLenient</code> methods allow to
fine-tune an engine instance behavior
@@ -117,7 +117,7 @@ public class JexlEngine {
};
/**
- * Gets the default instance of Uberspect.
+ * Gets the default instance of Uberspect.
* <p>This is lazily initialized to avoid building a default instance if
there
* is no use for it. The main reason for not using the default Uberspect
instance is to
* be able to use a (low level) introspector created with a given logger
@@ -166,7 +166,7 @@ public class JexlEngine {
*/
private volatile boolean strict = false;
/**
- * The map of 'prefix:function' to object implementing the functions.
+ * The map of 'prefix:function' to object implementing the functions.
*/
// TODO this could probably be private; is it threadsafe?
protected Map<String, Object> functions = Collections.emptyMap();
@@ -190,10 +190,10 @@ public class JexlEngine {
/**
* Creates a JEXL engine using the provided {@link Uberspect}, (@link
JexlArithmetic),
* a function map and logger.
- * @param anUberspect to allow different introspection behaviour
+ * @param anUberspect to allow different introspection behaviour
* @param anArithmetic to allow different arithmetic behaviour
* @param theFunctions an optional map of functions (@link setFunctions)
- * @param log the logger for various messages
+ * @param log the logger for various messages
*/
public JexlEngine(Uberspect anUberspect, JexlArithmetic anArithmetic,
Map<String, Object> theFunctions, Log log) {
this.uberspect = anUberspect == null ? getUberspect(log) : anUberspect;
@@ -209,7 +209,7 @@ public class JexlEngine {
}
/**
- * Gets the default instance of Uberspect.
+ * Gets the default instance of Uberspect.
* <p>This is lazily initialized to avoid building a default instance if
there
* is no use for it. The main reason for not using the default Uberspect
instance is to
* be able to use a (low level) introspector created with a given logger
@@ -385,7 +385,7 @@ public class JexlEngine {
* <p>Note that the JexlContext is also used to try to solve top-level
functions. This allows ObjectContext
* derived instances to call methods on the wrapped object.</p>
* @param funcs the map of functions that should not mutate after the
call; if null
- * is passed, the empty collection is used.
+ * is passed, the empty collection is used.
*/
public void setFunctions(Map<String, Object> funcs) {
functions = funcs != null ? funcs : Collections.<String,
Object>emptyMap();
@@ -395,7 +395,7 @@ public class JexlEngine {
* Retrieves the map of function namespaces.
*
* @return the map passed in setFunctions or the empty map if the
- * original was null.
+ * original was null.
*/
public Map<String, Object> getFunctions() {
return functions;
@@ -413,13 +413,13 @@ public class JexlEngine {
/**
* Creates an Expression from a String containing valid
- * JEXL syntax. This method parses the expression which
+ * JEXL syntax. This method parses the expression which
* must contain either a reference or an expression.
* @param expression A String containing valid JEXL syntax
* @return An Expression object which can be evaluated with a JexlContext
* @throws JexlException An exception can be thrown if there is a problem
- * parsing this expression, or if the expression is neither an
- * expression nor a reference.
+ * parsing this expression, or if the expression is
neither an
+ * expression nor a reference.
*/
public Expression createExpression(String expression) {
return createExpression(expression, null);
@@ -427,14 +427,14 @@ public class JexlEngine {
/**
* Creates an Expression from a String containing valid
- * JEXL syntax. This method parses the expression which
+ * JEXL syntax. This method parses the expression which
* must contain either a reference or an expression.
* @param expression A String containing valid JEXL syntax
* @return An Expression object which can be evaluated with a JexlContext
- * @param info An info structure to carry debugging information if needed
+ * @param info An info structure to carry debugging information if
needed
* @throws JexlException An exception can be thrown if there is a problem
- * parsing this expression, or if the expression is neither an
- * expression or a reference.
+ * parsing this expression, or if the expression is
neither an
+ * expression or a reference.
*/
public Expression createExpression(String expression, JexlInfo info) {
// Parse the expression
@@ -463,7 +463,7 @@ public class JexlEngine {
* This method parses the script which validates the syntax.
*
* @param scriptText A String containing valid JEXL syntax
- * @param info An info structure to carry debugging information if needed
+ * @param info An info structure to carry debugging information if
needed
* @return A {@link Script} which can be executed using a {@link
JexlContext}.
* @throws JexlException if there is a problem parsing the script.
* @deprecated Use {@link #createScript(String, JexlInfo, String[])}
@@ -483,7 +483,7 @@ public class JexlEngine {
* This method parses the script which validates the syntax.
*
* @param scriptText A String containing valid JEXL syntax
- * @param names the script parameter names
+ * @param names the script parameter names
* @return A {@link Script} which can be executed using a {@link
JexlContext}.
* @throws JexlException if there is a problem parsing the script.
*/
@@ -498,8 +498,8 @@ public class JexlEngine {
* a corresponding array of arguments containing values should be used
during evaluation.
*
* @param scriptText A String containing valid JEXL syntax
- * @param info An info structure to carry debugging information if needed
- * @param names the script parameter names
+ * @param info An info structure to carry debugging information if
needed
+ * @param names the script parameter names
* @return A {@link Script} which can be executed using a {@link
JexlContext}.
* @throws JexlException if there is a problem parsing the script.
* @since 2.1
@@ -528,10 +528,10 @@ public class JexlEngine {
* This method parses the script and validates the syntax.
*
* @param scriptFile A {@link File} containing valid JEXL syntax.
- * Must not be null. Must be a readable file.
+ * Must not be null. Must be a readable file.
* @return A {@link Script} which can be executed with a
- * {@link JexlContext}.
- * @throws IOException if there is a problem reading the script.
+ * {@link JexlContext}.
+ * @throws IOException if there is a problem reading the script.
* @throws JexlException if there is a problem parsing the script.
*/
public Script createScript(File scriptFile) throws IOException {
@@ -554,10 +554,10 @@ public class JexlEngine {
* This method parses the script and validates the syntax.
*
* @param scriptUrl A {@link URL} containing valid JEXL syntax.
- * Must not be null. Must be a readable file.
+ * Must not be null. Must be a readable file.
* @return A {@link Script} which can be executed with a
- * {@link JexlContext}.
- * @throws IOException if there is a problem reading the script.
+ * {@link JexlContext}.
+ * @throws IOException if there is a problem reading the script.
* @throws JexlException if there is a problem parsing the script.
*/
public Script createScript(URL scriptUrl) throws IOException {
@@ -599,8 +599,8 @@ public class JexlEngine {
* If the JEXL engine is silent, errors will be logged through its logger
as warning.
* </p>
* @param context the evaluation context
- * @param bean the bean to get properties from
- * @param expr the property expression
+ * @param bean the bean to get properties from
+ * @param expr the property expression
* @return the value of the property
* @throws JexlException if there is an error parsing the expression or
during evaluation
*/
@@ -639,8 +639,8 @@ public class JexlEngine {
* <p>
* If the JEXL engine is silent, errors will be logged through its logger
as warning.
* </p>
- * @param bean the bean to set properties in
- * @param expr the property expression
+ * @param bean the bean to set properties in
+ * @param expr the property expression
* @param value the value of the property
* @throws JexlException if there is an error parsing the expression or
during evaluation
*/
@@ -654,9 +654,9 @@ public class JexlEngine {
* If the JEXL engine is silent, errors will be logged through its logger
as warning.
* </p>
* @param context the evaluation context
- * @param bean the bean to set properties in
- * @param expr the property expression
- * @param value the value of the property
+ * @param bean the bean to set properties in
+ * @param expr the property expression
+ * @param value the value of the property
* @throws JexlException if there is an error parsing the expression or
during evaluation
*/
public void setProperty(JexlContext context, Object bean, String expr,
Object value) {
@@ -687,7 +687,7 @@ public class JexlEngine {
/**
* Invokes an object's method by name and arguments.
- * @param obj the method's invoker object
+ * @param obj the method's invoker object
* @param meth the method's name
* @param args the method's arguments
* @return the method returned value or null if it failed and engine is
silent
@@ -724,9 +724,9 @@ public class JexlEngine {
/**
* Creates a new instance of an object using the most appropriate
constructor
* based on the arguments.
- * @param <T> the type of object
+ * @param <T> the type of object
* @param clazz the class to instantiate
- * @param args the constructor arguments
+ * @param args the constructor arguments
* @return the created object instance or null on failure when silent
*/
public <T> T newInstance(Class<? extends T> clazz, Object... args) {
@@ -737,7 +737,7 @@ public class JexlEngine {
* Creates a new instance of an object using the most appropriate
constructor
* based on the arguments.
* @param clazz the name of the class to instantiate resolved through this
engine's class loader
- * @param args the constructor arguments
+ * @param args the constructor arguments
* @return the created object instance or null on failure when silent
*/
public Object newInstance(String clazz, Object... args) {
@@ -748,7 +748,7 @@ public class JexlEngine {
* Creates a new instance of an object using the most appropriate
constructor
* based on the arguments.
* @param clazz the class to instantiate
- * @param args the constructor arguments
+ * @param args the constructor arguments
* @return the created object instance or null on failure when silent
*/
protected Object doCreateInstance(Object clazz, Object... args) {
@@ -790,7 +790,7 @@ public class JexlEngine {
/**
* Creates an interpreter.
- * @param context a JexlContext; if null, the EMPTY_CONTEXT is used
instead.
+ * @param context a JexlContext; if null, the EMPTY_CONTEXT is used
instead.
* @param strictFlag whether the interpreter runs in strict mode
* @param silentFlag whether the interpreter runs in silent mode
* @return an Interpreter
@@ -861,7 +861,7 @@ public class JexlEngine {
/**
* Puts a value in cache.
- * @param key the cache entry key
+ * @param key the cache entry key
* @param script the cache entry value
*/
void put(K key, V script) {
@@ -876,8 +876,8 @@ public class JexlEngine {
/**
* Creates a cache.
- * @param <K> the key type
- * @param <V> the value type
+ * @param <K> the key type
+ * @param <V> the value type
* @param cacheSize the cache size, must be > 0
* @return a Map usable as a cache bounded to the given size
*/
@@ -926,7 +926,7 @@ public class JexlEngine {
* Fills up the list of variables accessed by a node.
* @param node the node
* @param refs the set of variable being filled
- * @param ref the current variable being filled
+ * @param ref the current variable being filled
* @since 2.1
*/
protected void getVariables(JexlNode node, Set<List<String>> refs,
List<String> ref) {
@@ -939,25 +939,42 @@ public class JexlEngine {
for (int i = 0; i < num; ++i) {
JexlNode child = node.jjtGetChild(i);
if (array) {
- if (child instanceof ASTReference &&
child.jjtGetNumChildren() == 1) {
- JexlNode desc = child.jjtGetChild(0);
- if (varf && desc.isConstant()) {
- String image = desc.image;
- if (image == null) {
- var.add(new Debugger().data(desc));
+ if (child instanceof ASTReference) {
+ if (child.jjtGetNumChildren() == 1) {
+ JexlNode desc = child.jjtGetChild(0);
+ if (varf && desc.isConstant()) {
+ String image = desc.image;
+ if (image == null) {
+ var.add(new Debugger().data(desc));
+ } else {
+ var.add(image);
+ }
+ } else if (desc instanceof ASTIdentifier) {
+ if (((ASTIdentifier) desc).getRegister() < 0) {
+ List<String> di = new ArrayList<String>(1);
+ di.add(desc.image);
+ refs.add(di);
+ }
+ var = new ArrayList<String>();
+ varf = false;
} else {
- var.add(image);
- }
- } else if (desc instanceof ASTIdentifier) {
- if (((ASTIdentifier) desc).getRegister() < 0) {
- List<String> di = new ArrayList<String>(1);
- di.add(desc.image);
- refs.add(di);
+ varf = false;
+ if (!var.isEmpty()) {
+ refs.add(var);
+ var = new ArrayList<String>();
+ }
+ getVariables(child, refs, null);
}
- var = new ArrayList<String>();
+ continue;
+ } else {
varf = false;
+ if (!var.isEmpty()) {
+ refs.add(var);
+ var = new ArrayList<String>();
+ }
+ getVariables(child, refs, null);
+ continue;
}
- continue;
} else if (child instanceof ASTIdentifier) {
if (i == 0 && (((ASTIdentifier) child).getRegister() <
0)) {
var.add(child.image);
@@ -1210,7 +1227,7 @@ public class JexlEngine {
/**
* Parses an expression.
* @param expression the expression to parse
- * @param info debug information structure
+ * @param info debug information structure
* @return the parsed tree
* @throws JexlException if any error occured during parsing
* @deprecated Use {@link #parse(CharSequence, JexlInfo, Scope)} instead
@@ -1223,8 +1240,8 @@ public class JexlEngine {
/**
* Parses an expression.
* @param expression the expression to parse
- * @param info debug information structure
- * @param frame the script frame to use
+ * @param info debug information structure
+ * @param frame the script frame to use
* @return the parsed tree
* @throws JexlException if any error occured during parsing
*/
@@ -1274,8 +1291,8 @@ public class JexlEngine {
/**
* Creates a JexlInfo instance.
* @param fn url/file name
- * @param l line number
- * @param c column number
+ * @param l line number
+ * @param c column number
* @return a JexlInfo instance
*/
protected JexlInfo createInfo(String fn, int l, int c) {
Added:
commons/proper/jexl/branches/2.0/src/test/java/org/apache/commons/jexl2/Dumper.java
URL:
http://svn.apache.org/viewvc/commons/proper/jexl/branches/2.0/src/test/java/org/apache/commons/jexl2/Dumper.java?rev=1360142&view=auto
==============================================================================
---
commons/proper/jexl/branches/2.0/src/test/java/org/apache/commons/jexl2/Dumper.java
(added)
+++
commons/proper/jexl/branches/2.0/src/test/java/org/apache/commons/jexl2/Dumper.java
Wed Jul 11 12:24:24 2012
@@ -0,0 +1,73 @@
+/*
+ * 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.jexl2;
+
+import org.apache.commons.jexl2.parser.ASTIdentifier;
+import org.apache.commons.jexl2.parser.JexlNode;
+
+/**
+ * Utility to dump AST, useful in debug sessions.
+ */
+public class Dumper {
+ private StringBuilder strb = new StringBuilder();
+ private int indent = 0;
+
+ private void indent() {
+ for (int i = 0; i < indent; ++i) {
+ strb.append(" ");
+ }
+ }
+
+ private void dump(JexlNode node, Object data) {
+ final int num = node.jjtGetNumChildren();
+ indent();
+ strb.append(node.getClass().getSimpleName());
+ if (node instanceof ASTIdentifier) {
+ strb.append("@");
+ strb.append(node.toString());
+ }
+ strb.append('(');
+ indent += 1;
+ for (int c = 0; c < num; ++c) {
+ JexlNode child = node.jjtGetChild(c);
+ if (c > 0) {
+ strb.append(',');
+ }
+ strb.append('\n');
+ dump(child, data);
+ }
+ indent -= 1;
+ if (num > 0) {
+ strb.append('\n');
+ indent();
+ }
+ strb.append(')');
+ }
+
+ private Dumper(Script script) {
+ dump(((ExpressionImpl) script).script, null);
+ }
+
+ @Override
+ public String toString() {
+ return strb.toString();
+ }
+
+ public static String toString(Script script) {
+ return new Dumper(script).toString();
+ }
+}
Propchange:
commons/proper/jexl/branches/2.0/src/test/java/org/apache/commons/jexl2/Dumper.java
------------------------------------------------------------------------------
svn:eol-style = native
Modified:
commons/proper/jexl/branches/2.0/src/test/java/org/apache/commons/jexl2/VarTest.java
URL:
http://svn.apache.org/viewvc/commons/proper/jexl/branches/2.0/src/test/java/org/apache/commons/jexl2/VarTest.java?rev=1360142&r1=1360141&r2=1360142&view=diff
==============================================================================
---
commons/proper/jexl/branches/2.0/src/test/java/org/apache/commons/jexl2/VarTest.java
(original)
+++
commons/proper/jexl/branches/2.0/src/test/java/org/apache/commons/jexl2/VarTest.java
Wed Jul 11 12:24:24 2012
@@ -23,6 +23,7 @@ import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.logging.Logger;
+import org.junit.Test;
/**
* Tests local variables.
@@ -33,28 +34,28 @@ public class VarTest extends JexlTestCas
public VarTest(String testName) {
super(testName);
}
-
+
public void testStrict() throws Exception {
JexlContext map = new MapContext();
JexlContext ctxt = new ReadonlyContext(new MapContext());
JEXL.setStrict(true);
Script e;
-
+
e = JEXL.createScript("x");
try {
Object o = e.execute(ctxt);
fail("should have thrown an unknown var exception");
} catch(JexlException xjexl) {
// ok since we are strict and x does not exist
- }
+ }
e = JEXL.createScript("x = 42");
try {
Object o = e.execute(ctxt);
fail("should have thrown a readonly context exception");
} catch(JexlException xjexl) {
// ok since we are strict and context is readonly
- }
-
+ }
+
map.set("x", "fourty-two");
e = JEXL.createScript("x.theAnswerToEverything()");
try {
@@ -62,7 +63,7 @@ public class VarTest extends JexlTestCas
fail("should have thrown an unknown method exception");
} catch(JexlException xjexl) {
// ok since we are strict and method does not exist
- }
+ }
}
public void testLocalBasic() throws Exception {
@@ -105,7 +106,7 @@ public class VarTest extends JexlTestCas
Script e = JEXL.createScript("var y = 42; for(var x : numbers()) { if
(x > 10) return x } y;");
Object o = e.execute(jc);
assertEquals("Result is not 17", new Integer(17), o);
-
+
assertTrue(toString(e.getVariables()), e.getVariables().isEmpty());
}
@@ -135,7 +136,7 @@ public class VarTest extends JexlTestCas
strb.append("}");
return strb.toString();
}
-
+
/**
* Creates a variable reference set from an array of array of strings.
* @param refs the variable reference set
@@ -148,7 +149,7 @@ public class VarTest extends JexlTestCas
}
return set;
}
-
+
/**
* Checks that two sets of variable references are equal
* @param lhs the left set
@@ -170,7 +171,7 @@ public class VarTest extends JexlTestCas
}
return true;
}
-
+
List<String> stringify(Set<List<String>> sls) {
List<String> ls = new ArrayList<String>();
for(List<String> l : sls) {
@@ -190,12 +191,22 @@ public class VarTest extends JexlTestCas
Set<List<String>> vars;
Set<List<String>> expect;
+ e = JEXL.createScript("D[E[F]]");
+ vars = e.getVariables();
+ expect = mkref(new String[][]{{"D"}, {"E"}, {"F"}});
+ assertTrue(eq(expect, vars));
+
+ e = JEXL.createScript("D[E[F[G[H]]]]");
+ vars = e.getVariables();
+ expect = mkref(new String[][]{{"D"}, {"E"}, {"F"}, {"G"}, {"H"}});
+ assertTrue(eq(expect, vars));
+
e = JEXL.createScript("e[f]");
vars = e.getVariables();
expect = mkref(new String[][]{{"e"},{"f"}});
assertTrue(eq(expect, vars));
-
-
+
+
e = JEXL.createScript("e[f][g]");
vars = e.getVariables();
expect = mkref(new String[][]{{"e"},{"f"},{"g"}});
@@ -215,7 +226,7 @@ public class VarTest extends JexlTestCas
vars = e.getVariables();
expect = mkref(new String[][]{{"e"},{"f"}});
assertTrue(eq(expect, vars));
-
+
e = JEXL.createScript("e['f']['g']");
vars = e.getVariables();
expect = mkref(new String[][]{{"e","f","g"}});
@@ -231,8 +242,18 @@ public class VarTest extends JexlTestCas
vars = e.getVariables();
expect = mkref(new String[][]{{"a"}, {"b", "c"}, {"b", "c", "d"},
{"e", "f"}});
assertTrue(eq(expect, vars));
+
+ e = JEXL.createScript(" A + B[C] + D[E[F]] + x[y[z]] ");
+ vars = e.getVariables();
+ expect = mkref(new String[][]{{"A"}, {"B"}, {"C"}, {"D"}, {"E"},
{"F"}, {"x"} , {"y"}, {"z"}});
+ assertTrue(eq(expect, vars));
+
+ e = JEXL.createScript(" A + B[C] + D.E['F'] + x[y.z] ");
+ vars = e.getVariables();
+ expect = mkref(new String[][]{{"A"}, {"B"}, {"C"}, {"D", "E", "F"},
{"x"} , {"y", "z"}});
+ assertTrue(eq(expect, vars));
}
-
+
public void testMix() throws Exception {
Script e;
// x is a parameter, y a context variable, z a local variable
@@ -240,20 +261,20 @@ public class VarTest extends JexlTestCas
Set<List<String>> vars = e.getVariables();
String[] parms = e.getParameters();
String[] locals = e.getLocalVariables();
-
+
assertTrue(eq(mkref(new String[][]{{"y"}}), vars));
assertEquals(1, parms.length);
assertEquals("x", parms[0]);
assertEquals(1, locals.length);
assertEquals("z", locals[0]);
}
-
+
public void testLiteral() throws Exception {
Script e = JEXL.createScript("x.y[['z', 't']]");
Set<List<String>> vars = e.getVariables();
assertEquals(1, vars.size());
assertTrue(eq(mkref(new String[][]{{"x", "y", "[ 'z', 't' ]"}}),
vars));
-
+
e = JEXL.createScript("x.y[{'z': 't'}]");
vars = e.getVariables();
assertEquals(1, vars.size());