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


Reply via email to