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); }