Author: henrib
Date: Fri Oct 21 16:40:17 2011
New Revision: 1187458
URL: http://svn.apache.org/viewvc?rev=1187458&view=rev
Log:
Added getVariables method (similar to JexlEngine) to extract all references
variables from an UJEXL expression;
Fixed issue where preparing a deferred expression would not always return an
immediate expression;
Updated test accordingly
Modified:
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/UnifiedJEXL.java
commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl2/UnifiedJEXLTest.java
Modified:
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/UnifiedJEXL.java
URL:
http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/UnifiedJEXL.java?rev=1187458&r1=1187457&r2=1187458&view=diff
==============================================================================
---
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/UnifiedJEXL.java
(original)
+++
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/UnifiedJEXL.java
Fri Oct 21 16:40:17 2011
@@ -17,6 +17,10 @@
package org.apache.commons.jexl2;
import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
import org.apache.commons.jexl2.parser.JexlNode;
import org.apache.commons.jexl2.parser.StringParser;
@@ -235,6 +239,28 @@ public final class UnifiedJEXL {
}
/**
+ * Checks whether this expression is immediate.
+ * @return true if immediate, false otherwise
+ */
+ public boolean isImmediate() {
+ return true;
+ }
+
+ /**
+ * Checks whether this expression is deferred.
+ * @return true if deferred, false otherwise
+ */
+ public final boolean isDeferred() {
+ return !isImmediate();
+ }
+
+ /**
+ * Gets this expression type.
+ * @return its type
+ */
+ abstract ExpressionType getType();
+
+ /**
* Formats this expression, adding its source string representation in
* comments if available: 'expression /*= source *\/'' .
* @return the formatted expression string
@@ -267,7 +293,26 @@ public final class UnifiedJEXL {
* Adds this expression's string representation to a StringBuilder.
* @param strb the builder to fill
*/
- abstract void asString(StringBuilder strb);
+ public abstract StringBuilder asString(StringBuilder strb);
+
+ /**
+ * Gets the list of variables accessed by this expression.
+ * <p>This method will visit all nodes of the sub-expressions and
extract all variables whether they
+ * are written in 'dot' or 'bracketed' notation. (a.b is equivalent to
a['b']).</p>
+ * @return the set of variables, each as a list of strings (ant-ish
variables use more than 1 string)
+ * or the empty set if no variables are used
+ */
+ public Set<List<String>> getVariables() {
+ return Collections.emptySet();
+ }
+
+ /**
+ * Fills up the list of variables accessed by this expression.
+ * @param refs the set of variable being filled
+ */
+ protected void getVariables(Set<List<String>> refs) {
+ // nothing to do
+ }
/**
* When the expression is dependant upon immediate and deferred
sub-expressions,
@@ -285,7 +330,9 @@ public final class UnifiedJEXL {
* @return an expression or null if an error occurs and the {@link
JexlEngine} is silent
* @throws UnifiedJEXL.Exception if an error occurs and the {@link
JexlEngine} is not silent
*/
- public abstract Expression prepare(JexlContext context);
+ public final Expression prepare(JexlContext context) {
+ return UnifiedJEXL.this.prepare(context, this);
+ }
/**
* Evaluates this expression.
@@ -297,22 +344,8 @@ public final class UnifiedJEXL {
* silent
* @throws UnifiedJEXL.Exception if an error occurs and the {@link
JexlEngine} is not silent
*/
- public abstract Object evaluate(JexlContext context);
-
- /**
- * Checks whether this expression is immediate.
- * @return true if immediate, false otherwise
- */
- public boolean isImmediate() {
- return true;
- }
-
- /**
- * Checks whether this expression is deferred.
- * @return true if deferred, false otherwise
- */
- public final boolean isDeferred() {
- return !isImmediate();
+ public final Object evaluate(JexlContext context) {
+ return UnifiedJEXL.this.evaluate(context, this);
}
/**
@@ -327,18 +360,14 @@ public final class UnifiedJEXL {
}
/**
- * Gets this expression type.
- * @return its type
- */
- abstract ExpressionType getType();
-
- /**
* Prepares a sub-expression for interpretation.
* @param interpreter a JEXL interpreter
* @return a prepared expression
* @throws JexlException (only for nested & composite)
*/
- abstract Expression prepare(Interpreter interpreter);
+ protected Expression prepare(Interpreter interpreter) {
+ return this;
+ }
/**
* Intreprets a sub-expression.
@@ -346,7 +375,7 @@ public final class UnifiedJEXL {
* @return the result of interpretation
* @throws JexlException (only for nested & composite)
*/
- abstract Object evaluate(Interpreter interpreter);
+ protected abstract Object evaluate(Interpreter interpreter);
}
/** A constant expression. */
@@ -376,25 +405,22 @@ public final class UnifiedJEXL {
/** {@inheritDoc} */
@Override
- public String asString() {
- StringBuilder strb = new StringBuilder();
- strb.append('"');
- asString(strb);
- strb.append('"');
- return strb.toString();
+ ExpressionType getType() {
+ return ExpressionType.CONSTANT;
}
/** {@inheritDoc} */
@Override
- ExpressionType getType() {
- return ExpressionType.CONSTANT;
+ public String toString() {
+ return value.toString();
}
/** {@inheritDoc} */
@Override
- void asString(StringBuilder strb) {
+ public StringBuilder asString(StringBuilder strb) {
String str = value.toString();
if (value instanceof String || value instanceof CharSequence) {
+ strb.append('"');
for (int i = 0, size = str.length(); i < size; ++i) {
char c = str.charAt(i);
if (c == '"' || c == '\\') {
@@ -402,32 +428,16 @@ public final class UnifiedJEXL {
}
strb.append(c);
}
+ strb.append('"');
} else {
strb.append(str);
}
+ return strb;
}
/** {@inheritDoc} */
@Override
- public Expression prepare(JexlContext context) {
- return this;
- }
-
- /** {@inheritDoc} */
- @Override
- Expression prepare(Interpreter interpreter) {
- return this;
- }
-
- /** {@inheritDoc} */
- @Override
- public Object evaluate(JexlContext context) {
- return value;
- }
-
- /** {@inheritDoc} */
- @Override
- Object evaluate(Interpreter interpreter) {
+ protected Object evaluate(Interpreter interpreter) {
return value;
}
}
@@ -454,52 +464,31 @@ public final class UnifiedJEXL {
/** {@inheritDoc} */
@Override
public String toString() {
- StringBuilder strb = new StringBuilder(expr.length() + 3);
- if (source != this) {
- strb.append(source.toString());
- strb.append(" /*= ");
- }
- strb.append(isImmediate() ? '$' : '#');
- strb.append("{");
- strb.append(expr);
- strb.append("}");
- if (source != this) {
- strb.append(" */");
- }
- return strb.toString();
+ return asString();
}
/** {@inheritDoc} */
@Override
- public void asString(StringBuilder strb) {
+ public StringBuilder asString(StringBuilder strb) {
strb.append(isImmediate() ? '$' : '#');
strb.append("{");
strb.append(expr);
strb.append("}");
+ return strb;
}
/** {@inheritDoc} */
@Override
- public Expression prepare(JexlContext context) {
- return this;
- }
-
- /** {@inheritDoc} */
- @Override
- Expression prepare(Interpreter interpreter) {
- return this;
- }
-
- /** {@inheritDoc} */
- @Override
- public Object evaluate(JexlContext context) {
- return UnifiedJEXL.this.evaluate(context, this);
+ protected Object evaluate(Interpreter interpreter) {
+ return interpreter.interpret(node);
}
/** {@inheritDoc} */
@Override
- Object evaluate(Interpreter interpreter) {
- return interpreter.interpret(node);
+ public Set<List<String>> getVariables() {
+ Set<List<String>> refs = new LinkedHashSet<List<String>>();
+ getVariables(refs);
+ return refs;
}
}
@@ -523,12 +512,21 @@ public final class UnifiedJEXL {
/** {@inheritDoc} */
@Override
- public boolean isImmediate() {
- return true;
+ protected void getVariables(Set<List<String>> refs) {
+ jexl.getVariables(node, refs, null);
+ }
+
+ /** {@inheritDoc} */
+ /** {@inheritDoc} */
+ @Override
+ protected Expression prepare(Interpreter interpreter) {
+ // evaluate immediate as constant
+ Object value = evaluate(interpreter);
+ return value == null ? null : new ConstantExpression(value, this);
}
}
- /** An immediate expression: ${jexl}. */
+ /** A deferred expression: #{jexl}. */
private class DeferredExpression extends JexlBasedExpression {
/**
* Creates a deferred expression.
@@ -542,14 +540,20 @@ public final class UnifiedJEXL {
/** {@inheritDoc} */
@Override
+ public boolean isImmediate() {
+ return true;
+ }
+
+ /** {@inheritDoc} */
+ @Override
ExpressionType getType() {
return ExpressionType.DEFERRED;
}
/** {@inheritDoc} */
@Override
- public boolean isImmediate() {
- return false;
+ protected Expression prepare(Interpreter interpreter) {
+ return new ImmediateExpression(expr, node, source);
}
}
@@ -586,21 +590,15 @@ public final class UnifiedJEXL {
/** {@inheritDoc} */
@Override
- public Expression prepare(JexlContext context) {
- return UnifiedJEXL.this.prepare(context, this);
- }
-
- /** {@inheritDoc} */
- @Override
- public Expression prepare(Interpreter interpreter) {
+ protected Expression prepare(Interpreter interpreter) {
String value = interpreter.interpret(node).toString();
JexlNode dnode = toNode(value, jexl.isDebug() ? node.debugInfo() :
null);
- return new DeferredExpression(value, dnode, this);
+ return new ImmediateExpression(value, dnode, this);
}
/** {@inheritDoc} */
@Override
- public Object evaluate(Interpreter interpreter) {
+ protected Object evaluate(Interpreter interpreter) {
return prepare(interpreter).evaluate(interpreter);
}
}
@@ -627,41 +625,54 @@ public final class UnifiedJEXL {
/** {@inheritDoc} */
@Override
+ public boolean isImmediate() {
+ // immediate if no deferred
+ return (meta & 2) == 0;
+ }
+
+ /** {@inheritDoc} */
+ @Override
ExpressionType getType() {
return ExpressionType.COMPOSITE;
}
/** {@inheritDoc} */
@Override
- public boolean isImmediate() {
- // immediate if no deferred
- return (meta & 2) == 0;
+ public String toString() {
+ StringBuilder strb = new StringBuilder();
+ for (Expression e : exprs) {
+ strb.append(e.toString());
+ }
+ return strb.toString();
}
/** {@inheritDoc} */
@Override
- void asString(StringBuilder strb) {
+ public StringBuilder asString(StringBuilder strb) {
for (Expression e : exprs) {
e.asString(strb);
}
+ return strb;
}
/** {@inheritDoc} */
@Override
- public Expression prepare(JexlContext context) {
- return UnifiedJEXL.this.prepare(context, this);
+ public Set<List<String>> getVariables() {
+ Set<List<String>> refs = new LinkedHashSet<List<String>>();
+ for (Expression expr : exprs) {
+ expr.getVariables(refs);
+ }
+ return refs;
}
/** {@inheritDoc} */
@Override
- Expression prepare(Interpreter interpreter) {
+ protected Expression prepare(Interpreter interpreter) {
// if this composite is not its own source, it is already prepared
if (source != this) {
return this;
}
- // we need to eval immediate expressions if there are some
deferred/nested
- // ie both immediate & deferred counts > 0, bits 1 & 0 set, (1 <<
1) & 1 == 3
- final boolean evalImmediate = meta == 3;
+ // we need to prepare all sub-expressions
final int size = exprs.length;
final ExpressionBuilder builder = new ExpressionBuilder(size);
// tracking whether prepare will return a different expression
@@ -669,11 +680,6 @@ public final class UnifiedJEXL {
for (int e = 0; e < size; ++e) {
Expression expr = exprs[e];
Expression prepared = expr.prepare(interpreter);
- if (evalImmediate && prepared instanceof ImmediateExpression) {
- // evaluate immediate as constant
- Object value = prepared.evaluate(interpreter);
- prepared = value == null ? null : new
ConstantExpression(value, prepared);
- }
// add it if not null
if (prepared != null) {
builder.add(prepared);
@@ -687,20 +693,16 @@ public final class UnifiedJEXL {
/** {@inheritDoc} */
@Override
- public Object evaluate(JexlContext context) {
- return UnifiedJEXL.this.evaluate(context, this);
- }
-
- /** {@inheritDoc} */
- @Override
- Object evaluate(Interpreter interpreter) {
+ protected Object evaluate(Interpreter interpreter) {
final int size = exprs.length;
Object value = null;
// common case: evaluate all expressions & concatenate them as a
string
StringBuilder strb = new StringBuilder();
for (int e = 0; e < size; ++e) {
value = exprs[e].evaluate(interpreter);
- if (value != null) {
+ if (value instanceof Expression) {
+ ((Expression) value).asString(strb);
+ } else if (value != null) {
strb.append(value.toString());
}
}
Modified:
commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl2/UnifiedJEXLTest.java
URL:
http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl2/UnifiedJEXLTest.java?rev=1187458&r1=1187457&r2=1187458&view=diff
==============================================================================
---
commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl2/UnifiedJEXLTest.java
(original)
+++
commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl2/UnifiedJEXLTest.java
Fri Oct 21 16:40:17 2011
@@ -15,8 +15,11 @@
* limitations under the License.
*/
package org.apache.commons.jexl2;
+import java.util.Arrays;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
+import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
@@ -74,6 +77,8 @@ public class UnifiedJEXLTest extends Jex
UnifiedJEXL.Expression check = EL.parse("${froboz.value = 32;
froboz.plus10(); froboz.value}");
Object o = check.evaluate(context);
assertEquals("Result is not 42", new Integer(42), o);
+ Set<List<String>> evars = check.getVariables();
+ assertEquals(2, evars.size());
}
public void testAssign() throws Exception {
@@ -101,10 +106,17 @@ public class UnifiedJEXLTest extends Jex
public void testPrepareEvaluate() throws Exception {
UnifiedJEXL.Expression expr = EL.parse("Dear #{p} ${name};");
assertTrue("expression should be deferred", expr.isDeferred());
+
+ Set<List<String>> evars = expr.getVariables();
+ assertEquals(1, evars.size());
+ assertTrue(evars.contains(Arrays.asList("name")));
vars.put("name", "Doe");
UnifiedJEXL.Expression phase1 = expr.prepare(context);
- String as = phase1.asString();
- assertEquals("Dear #{p} Doe;", as);
+ String as = phase1.toString();
+ assertEquals("Dear ${p} Doe;", as);
+ Set<List<String>> evars1 = phase1.getVariables();
+ assertEquals(1, evars1.size());
+ assertTrue(evars1.contains(Arrays.asList("p")));
vars.put("p", "Mr");
vars.put("name", "Should not be used in 2nd phase");
Object o = phase1.evaluate(context);
@@ -116,15 +128,16 @@ public class UnifiedJEXLTest extends Jex
vars.put("hi", "hello");
vars.put("hello.world", "Hello World!");
Object o = expr.evaluate(context);
- assertTrue("source should not be expression", expr.getSource() !=
expr.prepare(context));
- assertTrue("expression should be deferred", expr.isDeferred());
+ assertTrue("source should not same expression", expr.getSource() !=
expr.prepare(context));
+ assertTrue("expression should be immediate", expr.isImmediate());
assertEquals("Hello World!", o);
}
public void testImmediate() throws Exception {
JexlContext none = null;
UnifiedJEXL.Expression expr = EL.parse("${'Hello ' + 'World!'}");
- assertTrue("prepare should return same expression", expr.prepare(none)
== expr);
+ UnifiedJEXL.Expression prepared = expr.prepare(none);
+ assertEquals("prepare should return same expression","Hello
World!",prepared.toString());
Object o = expr.evaluate(none);
assertTrue("expression should be immediate", expr.isImmediate());
assertEquals("Hello World!", o);
@@ -142,9 +155,10 @@ public class UnifiedJEXLTest extends Jex
public void testDeferred() throws Exception {
JexlContext none = null;
UnifiedJEXL.Expression expr = EL.parse("#{'world'}");
- assertTrue("prepare should return same expression", expr.prepare(none)
== expr);
+ String as = expr.prepare(none).asString();
+ assertEquals("prepare should return immediate version", "${'world'}",
as);
Object o = expr.evaluate(none);
- assertTrue("expression should be deferred", expr.isDeferred());
+ assertTrue("expression should be immediate", expr.isImmediate());
assertEquals("world", o);
}