You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by ra...@apache.org on 2009/07/15 05:43:50 UTC

svn commit: r794133 [1/2] - in /commons/proper/jexl/branches/2.0/src: java/org/apache/commons/jexl/ java/org/apache/commons/jexl/parser/ java/org/apache/commons/jexl/util/introspection/ test/org/apache/commons/jexl/

Author: rahul
Date: Wed Jul 15 03:43:49 2009
New Revision: 794133

URL: http://svn.apache.org/viewvc?rev=794133&view=rev
Log:
Add an evaluator supporting syntax similar to the unified EL evaluator used in JSP/JSF, but based on JEXL (see UnifiedJEXL.java).
Add some tests for existing issues (see IssuesTest.java).
Some other miscellaneous improvements (catching up with JEXL-55 changes).
Thanks to Henri Biestro <hbiestro at gmail dot com>.
JEXL-58

Added:
    commons/proper/jexl/branches/2.0/src/java/org/apache/commons/jexl/UnifiedJEXL.java   (with props)
    commons/proper/jexl/branches/2.0/src/test/org/apache/commons/jexl/IssuesTest.java   (with props)
    commons/proper/jexl/branches/2.0/src/test/org/apache/commons/jexl/UnifiedJEXLTest.java   (with props)
Modified:
    commons/proper/jexl/branches/2.0/src/java/org/apache/commons/jexl/Debugger.java
    commons/proper/jexl/branches/2.0/src/java/org/apache/commons/jexl/Expression.java
    commons/proper/jexl/branches/2.0/src/java/org/apache/commons/jexl/ExpressionImpl.java
    commons/proper/jexl/branches/2.0/src/java/org/apache/commons/jexl/Interpreter.java
    commons/proper/jexl/branches/2.0/src/java/org/apache/commons/jexl/JexlArithmetic.java
    commons/proper/jexl/branches/2.0/src/java/org/apache/commons/jexl/JexlEngine.java
    commons/proper/jexl/branches/2.0/src/java/org/apache/commons/jexl/JexlException.java
    commons/proper/jexl/branches/2.0/src/java/org/apache/commons/jexl/ScriptImpl.java
    commons/proper/jexl/branches/2.0/src/java/org/apache/commons/jexl/package.html
    commons/proper/jexl/branches/2.0/src/java/org/apache/commons/jexl/parser/Parser.jjt
    commons/proper/jexl/branches/2.0/src/java/org/apache/commons/jexl/util/introspection/ClassMap.java
    commons/proper/jexl/branches/2.0/src/java/org/apache/commons/jexl/util/introspection/UberspectImpl.java
    commons/proper/jexl/branches/2.0/src/test/org/apache/commons/jexl/ArithmeticTest.java
    commons/proper/jexl/branches/2.0/src/test/org/apache/commons/jexl/AssignTest.java
    commons/proper/jexl/branches/2.0/src/test/org/apache/commons/jexl/Jexl.java

Modified: commons/proper/jexl/branches/2.0/src/java/org/apache/commons/jexl/Debugger.java
URL: http://svn.apache.org/viewvc/commons/proper/jexl/branches/2.0/src/java/org/apache/commons/jexl/Debugger.java?rev=794133&r1=794132&r2=794133&view=diff
==============================================================================
--- commons/proper/jexl/branches/2.0/src/java/org/apache/commons/jexl/Debugger.java (original)
+++ commons/proper/jexl/branches/2.0/src/java/org/apache/commons/jexl/Debugger.java Wed Jul 15 03:43:49 2009
@@ -91,17 +91,19 @@
      * @return true if the cause was located, false otherwise
      */
     public boolean debug(Node cause) {
-        builder = new StringBuilder();
-        this.cause = cause;
-        // make arg cause become the root cause
-        Node root = cause;
-        while (root.jjtGetParent() != null) {
-            root = root.jjtGetParent();
-        }
         start = 0;
         end = 0;
-        root.jjtAccept(this, null);
-        return start > 0;
+        if (cause != null) {
+            builder = new StringBuilder();
+            this.cause = cause;
+            // make arg cause become the root cause
+            Node root = cause;
+            while (root.jjtGetParent() != null) {
+                root = root.jjtGetParent();
+            }
+            root.jjtAccept(this, null);
+        }
+        return end > 0;
     }
 
     /**

Modified: commons/proper/jexl/branches/2.0/src/java/org/apache/commons/jexl/Expression.java
URL: http://svn.apache.org/viewvc/commons/proper/jexl/branches/2.0/src/java/org/apache/commons/jexl/Expression.java?rev=794133&r1=794132&r2=794133&view=diff
==============================================================================
--- commons/proper/jexl/branches/2.0/src/java/org/apache/commons/jexl/Expression.java (original)
+++ commons/proper/jexl/branches/2.0/src/java/org/apache/commons/jexl/Expression.java Wed Jul 15 03:43:49 2009
@@ -41,9 +41,9 @@
      *
      * @param context A JexlContext containing variables.
      * @return The result of this evaluation
-     * @throws Exception on any error
+     * @throws JexlException on any error
      */
-    Object evaluate(JexlContext context) throws Exception;
+    Object evaluate(JexlContext context);
 
     /**
      * Returns the JEXL expression this Expression was created with.

Modified: commons/proper/jexl/branches/2.0/src/java/org/apache/commons/jexl/ExpressionImpl.java
URL: http://svn.apache.org/viewvc/commons/proper/jexl/branches/2.0/src/java/org/apache/commons/jexl/ExpressionImpl.java?rev=794133&r1=794132&r2=794133&view=diff
==============================================================================
--- commons/proper/jexl/branches/2.0/src/java/org/apache/commons/jexl/ExpressionImpl.java (original)
+++ commons/proper/jexl/branches/2.0/src/java/org/apache/commons/jexl/ExpressionImpl.java Wed Jul 15 03:43:49 2009
@@ -17,12 +17,10 @@
 
 package org.apache.commons.jexl;
 
-import java.util.List;
-
 import org.apache.commons.jexl.parser.SimpleNode;
 
 /**
- * Instances of ExpressionImpl are created by the {@link ExpressionFactory},
+ * Instances of ExpressionImpl are created by the {@link JexlEngine},
  * and this is the default implementation of the {@link Expression} interface.
  *
  * @since 1.0
@@ -30,30 +28,19 @@
  * @version $Id$
  */
 class ExpressionImpl implements Expression {
-
-    // TODO: move resolving to interpreter
-    /** resolvers called before expression evaluation. */
-    protected List preResolvers;
-
-    /** resolvers called after expression evaluation. */
-    protected List postResolvers;
-
+    /** The engine for this expression. */
+    protected final JexlEngine jexl;
     /**
      * Original expression. This is just a 'snippet', not a valid statement
      * (i.e. foo.bar() vs foo.bar();
      */
-    protected String expression;
+    protected final String expression;
 
     /**
-     * The resulting AST we can call value() on.
+     * The resulting AST we can interpret.
      */
-    protected SimpleNode node;
-
-	/** The engine for this expression. */
-     protected final JexlEngine jexl;
+    protected final SimpleNode node;
 
-    /** The interpreter of the expression. */
-    protected Interpreter interpreter;
 
     /**
      * do not let this be generally instantiated with a 'new'.
@@ -71,11 +58,14 @@
     /**
      * {@inheritDoc}
      */
-    public Object evaluate(JexlContext context) throws Exception {
+    public Object evaluate(JexlContext context) {
         Interpreter interpreter = jexl.createInterpreter(context);
-        return interpreter.interpret(node, jexl.isSilent());
+        return interpreter.interpret(node);
     }
-    
+
+    /**
+     * {@inheritDoc}
+     */
     public String dump() {
         Debugger debug = new Debugger();
         return debug.debug(node)? debug.toString() : "/*?*/";
@@ -93,6 +83,7 @@
      *
      * @return the expression or blank if it's null.
      */
+    @Override
     public String toString() {
         String expr = getExpression();
         return expr == null ? "" : expr;

Modified: commons/proper/jexl/branches/2.0/src/java/org/apache/commons/jexl/Interpreter.java
URL: http://svn.apache.org/viewvc/commons/proper/jexl/branches/2.0/src/java/org/apache/commons/jexl/Interpreter.java?rev=794133&r1=794132&r2=794133&view=diff
==============================================================================
--- commons/proper/jexl/branches/2.0/src/java/org/apache/commons/jexl/Interpreter.java (original)
+++ commons/proper/jexl/branches/2.0/src/java/org/apache/commons/jexl/Interpreter.java Wed Jul 15 03:43:49 2009
@@ -26,6 +26,8 @@
 import java.util.List;
 import java.util.Map;
 
+import org.apache.commons.logging.*;
+
 import org.apache.commons.jexl.parser.ASTAddNode;
 import org.apache.commons.jexl.parser.ASTAndNode;
 import org.apache.commons.jexl.parser.ASTArrayAccess;
@@ -74,61 +76,87 @@
 import org.apache.commons.jexl.parser.ASTWhileStatement;
 import org.apache.commons.jexl.parser.Node;
 import org.apache.commons.jexl.parser.SimpleNode;
+import org.apache.commons.jexl.parser.ParserVisitor;
+
 import org.apache.commons.jexl.util.introspection.Info;
 import org.apache.commons.jexl.util.introspection.Uberspect;
 import org.apache.commons.jexl.util.introspection.VelMethod;
 import org.apache.commons.jexl.util.introspection.VelPropertyGet;
 import org.apache.commons.jexl.util.introspection.VelPropertySet;
 
-import org.apache.commons.jexl.parser.ParserVisitor;
-
 /**
  * An interpreter of JEXL syntax.
  *
  * @since 2.0
  */
 public class Interpreter implements ParserVisitor {
+    /** The logger. */
+    protected final Log LOG;
     /** The uberspect. */
-    private final Uberspect uberspect;
+    protected final Uberspect uberspect;
     /** the arithmetic handler. */
-    private final Arithmetic arithmetic;
+    protected final Arithmetic arithmetic;
     /** The map of registered functions. */
-    private final Map<String,Object> functions;
+    protected final Map<String,Object> functions;
     /** The context to store/retrieve variables. */
-    private final JexlContext context;
+    protected final JexlContext context;
+    /** silent */
+    protected boolean silent;
+    /** registers; 2 * {name, value} */
+    protected Object[] registers = null;
     /** dummy velocity info. */
-    private static final Info DUMMY = new Info("", 1, 1);
+    protected static final Info DUMMY = new Info("", 1, 1);
     /** empty params for method matching. */
-    static private final Object[] EMPTY_PARAMS = new Object[0];
-
+    protected static final Object[] EMPTY_PARAMS = new Object[0];
     /**
      * Create the interpreter.
-     * @param ctx the context to retrieve variables from.
-     * @param uber the helper to perform introspection,
-     * @param arith the arithmetic handler
+     * @param jexl the engine creating this interpreter
+     * @param context the context to evaluate expression
      */
-    public Interpreter(Uberspect uber, Arithmetic arith, Map<String,Object> functions, JexlContext context) {
-        this.uberspect = uber;
-        this.arithmetic = arith;
-        this.functions = functions;
+    public Interpreter(JexlEngine jexl, JexlContext context) {
+        this.LOG = jexl.LOG;
+        this.uberspect = jexl.uberspect;
+        this.arithmetic = jexl.arithmetic;
+        this.functions = jexl.functions;
+        this.silent = jexl.silent;
         this.context = context;
     }
 
     /**
+     * Sets whether this interpreter throws JexlException during evaluation.
+     * @param silent true means no JexlException will be thrown but will be logged
+     *        as info through the Jexl engine logger, false allows them to be thrown.
+     */
+    public void setSilent(boolean silent) {
+        this.silent = silent;
+    }
+
+    /**
+     * Checks whether this interpreter throws JexlException during evaluation.
+     */
+    public boolean isSilent() {
+        return this.silent;
+    }
+
+    /**
      * Interpret the given script/expression.
-     *
+     * <p>
+     * If the underlying JEXL engine is silent, errors will be logged through its logger as info.
+     * </p>
      * @param node the script or expression to interpret.
-     * @param aContext the context to interpret against.
      * @return the result of the interpretation.
+     * @throws JexlException if any error occurs during interpretation.
      */
-    public Object interpret(SimpleNode node, boolean silent) {
+    public Object interpret(SimpleNode node) {
         try {
             return node.jjtAccept(this, null);
         }
-        catch (JexlException error) {
-            if (silent)
+        catch(JexlException xjexl) {
+            if (silent) {
+                LOG.warn(xjexl.getMessage(), xjexl.getCause());
                 return null;
-            throw error;
+            }
+            throw xjexl;
         }
     }
 
@@ -148,15 +176,28 @@
 
     /**
      * Gets the uberspect.
-     *
      * @return an {@link Uberspect}
      */
     protected Uberspect getUberspect() {
         return uberspect;
     }
 
+    /**
+     * Sets this interpreter registers for bean access/assign expressions.
+     * @param registers the array of registers
+     */
+    protected void setRegisters(Object[] registers) {
+        this.registers = registers;
+    }
+
+    
     public Object visit(SimpleNode node, Object data) {
-        throw new UnsupportedOperationException("unexpected node " + node);
+        int numChildren = node.jjtGetNumChildren();
+        Object result = null;
+        for (int i = 0; i < numChildren; i++) {
+            result = node.jjtGetChild(i).jjtAccept(this, data);
+        }
+        return result;
     }
 
     /** {@inheritDoc} */
@@ -216,7 +257,7 @@
 
         return object;
     }
-
+  
     /** {@inheritDoc} */
     public Object visit(ASTAssignment node, Object data) {
         // left contains the reference to assign to
@@ -388,6 +429,8 @@
             return arithmetic.divide(left, right);
         }
         catch (RuntimeException xrt) {
+            if (silent && xrt instanceof ArithmeticException)
+                return 0.0;
             throw new JexlException(node, "divide error", xrt);
         }
     }
@@ -509,6 +552,10 @@
     public Object visit(ASTIdentifier node, Object data) {
         String name = node.image;
         if (data == null) {
+            if (registers != null) {
+                if (registers[0].equals(name)) return registers[1];
+                if (registers[2].equals(name)) return registers[3];
+            }
             return context.getVars().get(name);
         } else {
             return getAttribute(data, name, node);
@@ -728,6 +775,8 @@
             return arithmetic.mod(left, right);
         }
         catch (RuntimeException xrt) {
+            if (silent && xrt instanceof ArithmeticException)
+                return 0.0;
             throw new JexlException(node, "% error", xrt);
         }
     }
@@ -824,7 +873,6 @@
 
     /** {@inheritDoc} */
     public Object visit(ASTReferenceExpression node, Object data) {
-
         return node.jjtGetChild(0).jjtAccept(this, data);
     }
 
@@ -1027,7 +1075,6 @@
      * @param attribute the attribute of the object, e.g. an index (1, 0, 2) or
      *            key for a map
      * @param value the value to assign to the object's attribute
-     * @return the attribute.
      */
     public void setAttribute(Object object, Object attribute, Object value) {
         setAttribute(object, attribute, null);

Modified: commons/proper/jexl/branches/2.0/src/java/org/apache/commons/jexl/JexlArithmetic.java
URL: http://svn.apache.org/viewvc/commons/proper/jexl/branches/2.0/src/java/org/apache/commons/jexl/JexlArithmetic.java?rev=794133&r1=794132&r2=794133&view=diff
==============================================================================
--- commons/proper/jexl/branches/2.0/src/java/org/apache/commons/jexl/JexlArithmetic.java (original)
+++ commons/proper/jexl/branches/2.0/src/java/org/apache/commons/jexl/JexlArithmetic.java Wed Jul 15 03:43:49 2009
@@ -24,7 +24,13 @@
  * @since 2.0
  */
 class JexlArithmetic implements Arithmetic {
-
+    /**
+     * The result of +,/,-,* when both operands are null.
+     * @return Long(0)
+     */
+    protected Object NullNull() {
+        return 0l;
+    }
     /**
      * Add two values together.
      * Rules are:<ol>
@@ -41,7 +47,7 @@
      */
     public Object add(Object left, Object right) {
         if (left == null && right == null) {
-            return new Long(0);
+            return NullNull();
         }
         
         try {
@@ -76,6 +82,8 @@
             return result;
         } catch (java.lang.NumberFormatException nfe) {
             // Well, use strings!
+            if (left == null) left = "";
+            else if (right == null) right = "";
             return left.toString().concat(right.toString());
         }
     }
@@ -92,16 +100,13 @@
      */
     public Object divide(Object left, Object right) {
         if (left == null && right == null) {
-            return new Long(0);
+            return NullNull();
         }
 
         // if both are bigintegers use that type
         if (left instanceof BigInteger && right instanceof BigInteger) {
             BigInteger l = toBigInteger(left);
             BigInteger r = toBigInteger(right);
-            if (r.compareTo(BigInteger.valueOf(0)) == 0) {
-                return r;
-            }
             return l.divide(r);
         }
         
@@ -109,18 +114,16 @@
         if (left instanceof BigDecimal || right instanceof BigDecimal) {
             BigDecimal l = toBigDecimal(left);
             BigDecimal r = toBigDecimal(right);
-            if (r.compareTo(BigDecimal.valueOf(0)) == 0) {
-                return r;
-            }
             return l.divide(r, BigDecimal.ROUND_HALF_UP);
         }
 
         double l = toDouble(left);
         double r = toDouble(right);
-        if (r == 0) {
-            return new Double(r);
+        double d = l / r;
+        if (Double.isNaN(d) || Double.isInfinite(d)) {
+            throw new ArithmeticException("/");
         }
-        return new Double(l / r);
+        return d;
 
     }
     
@@ -136,22 +139,23 @@
      */
     public Object mod(Object left, Object right) {
         if (left == null && right == null) {
-            return new Long(0);
+            return NullNull();
         }
 
         if (isFloatingPointNumber(left) || isFloatingPointNumber(right)) {
             double l = toDouble(left);
             double r = toDouble(right);
-            return new Double(l % r);
+            double d = l % r;
+            if (Double.isNaN(d) || Double.isInfinite(d)) {
+                throw new ArithmeticException("%");
+            }
+            return d;
         }
 
         // if both are bigintegers use that type
         if (left instanceof BigInteger && right instanceof BigInteger) {
             BigInteger l = toBigInteger(left);
             BigInteger r = toBigInteger(right);
-            if (r.compareTo(BigInteger.valueOf(0)) == 0) {
-                return r;
-            }
             return l.mod(r);
         }
 
@@ -159,9 +163,6 @@
         if (left instanceof BigDecimal || right instanceof BigDecimal) {
             BigDecimal l = toBigDecimal(left);
             BigDecimal r = toBigDecimal(right);
-            if (r.compareTo(BigDecimal.valueOf(0)) == 0) {
-                return r;
-            }
             BigInteger intDiv = l.divide(r, BigDecimal.ROUND_HALF_UP).toBigInteger();
             BigInteger intValue = (r.multiply(new BigDecimal(intDiv))).toBigInteger();
             BigDecimal remainder = new BigDecimal(l.subtract(new BigDecimal(intValue)).toBigInteger());
@@ -195,7 +196,7 @@
      */
     public Object multiply(Object left, Object right) {
         if (left == null && right == null) {
-            return new Long(0);
+            return NullNull();
         }
         
         if (isFloatingPointNumber(left) || isFloatingPointNumber(right)) {
@@ -245,7 +246,7 @@
      */
     public Object subtract(Object left, Object right) {
         if (left == null && right == null) {
-            return new Long(0);
+            return NullNull();
         }
         
         if (isFloatingPointNumber(left) || isFloatingPointNumber(right)) {

Modified: commons/proper/jexl/branches/2.0/src/java/org/apache/commons/jexl/JexlEngine.java
URL: http://svn.apache.org/viewvc/commons/proper/jexl/branches/2.0/src/java/org/apache/commons/jexl/JexlEngine.java?rev=794133&r1=794132&r2=794133&view=diff
==============================================================================
--- commons/proper/jexl/branches/2.0/src/java/org/apache/commons/jexl/JexlEngine.java (original)
+++ commons/proper/jexl/branches/2.0/src/java/org/apache/commons/jexl/JexlEngine.java Wed Jul 15 03:43:49 2009
@@ -22,9 +22,9 @@
 import java.io.IOException;
 import java.io.InputStreamReader;
 import java.io.StringReader;
+import java.io.Reader;
 import java.util.Map;
 import java.util.Collections;
-import java.util.HashMap;
 import java.net.URL;
 import java.net.URLConnection;
 import org.apache.commons.logging.*;
@@ -68,17 +68,35 @@
      * Whether expressions evaluated by this engine will throw exceptions or 
      * return null
      */
-    boolean silent = true;
+    protected boolean silent = true;
 
     /**
      *  The map of 'prefix:function' to object implementing the function.
      */
     protected Map<String,Object> functions = Collections.EMPTY_MAP;
+
+    /**
+     * The expression cache.
+     */
+    protected Map<String,SimpleNode> cache = null;
+
     /**
      * ExpressionFactory & ScriptFactory need a singleton and this is the package
      * instance fulfilling that pattern.
      */
-    static final JexlEngine DEFAULT = new JexlEngine();
+    protected static final JexlEngine DEFAULT = new JexlEngine();
+
+    /**
+     * An empty/static/non-mutable JexlContext used instead of null context
+     */
+    protected static final JexlContext EMPTY_CONTEXT = new JexlContext() {
+        public void setVars(Map vars) {
+            throw new UnsupportedOperationException("Immutable JexlContext");
+        }
+        public Map getVars() {
+            return java.util.Collections.EMPTY_MAP;
+        }
+    };
 
     /**
      * Creates a default engine
@@ -97,12 +115,15 @@
     public JexlEngine(Uberspect uberspect, Arithmetic arithmetic, Map<String,Object> funcs, Log log) {
         this.uberspect = uberspect == null? Introspector.getUberspect() : uberspect;
         this.arithmetic = arithmetic == null? new JexlArithmetic() : arithmetic;
-        if (funcs != null)
+        if (funcs != null) {
             this.functions = funcs;
-        if (log == null)
+        }
+        if (log == null) {
             log = LogFactory.getLog(JexlEngine.class);
-        if (log == null)
+        }
+        if (log == null) {
             throw new NullPointerException("logger can not be null");
+        }
         this.LOG = log;
     }
     
@@ -120,11 +141,27 @@
     public boolean isSilent() {
         return this.silent;
     }
-    
+
+    /**
+     * Sets a cache of the defined size for expressions.
+     * @param size if not strictly positive, no cache is used.
+     */
+    public void setCache(int size) {
+        // since the cache is only used during parse, use same sync object
+        synchronized(parser) {
+            if (size <= 0) {
+                cache = null;
+            }
+            else if (cache == null || cache.size() != size) {
+                cache = createCache(size);
+            }
+        }
+    }
+
     /**
      * Sets the map of function namespaces.
      * <p>
-     * It should be defined once not modified afterwards since it might be shared
+     * It should be defined once and not modified afterwards since it might be shared
      * between multiple engines evaluating expressions concurrently.
      * </p>
      * <p>
@@ -159,7 +196,7 @@
     public Map<String, Object> getFunctions() {
         return functions;
     }
-    
+
     /**
      * Creates an Expression from a String containing valid
      * JEXL syntax.  This method parses the expression which
@@ -172,33 +209,14 @@
      */
     public Expression createExpression(String expression)
         throws ParseException {
-        String expr = cleanExpression(expression);
-
-        // Parse the Expression
-        SimpleNode tree;
-        synchronized (parser) {
-            LOG.debug("Parsing expression: " + expr);
-            try {
-                tree = parser.parse(new StringReader(expr));
-            } catch (TokenMgrError tme) {
-                throw new ParseException(tme.getMessage());
-            } catch (ParseException e) {
-                throw e;
-            } catch (Exception e) {
-                throw new RuntimeException(e);
-            }
-        }
-
+        // Parse the expression
+        SimpleNode tree = parse(expression);
         if (tree.jjtGetNumChildren() > 1) {
             LOG.warn("The JEXL Expression created will be a reference"
                 + " to the first expression from the supplied script: \""
                 + expression + "\" ");
         }
-
-        // Must be a simple reference, expression, statement or if, otherwise
-        // throw an exception.
         SimpleNode node = (SimpleNode) tree.jjtGetChild(0);
-
         return new ExpressionImpl(this, expression, node);
     }
 
@@ -213,22 +231,14 @@
      *      problem parsing the script.
      */
     public Script createScript(String scriptText) throws Exception {
-        String cleanText = cleanExpression(scriptText);
-        SimpleNode script;
-        // Parse the Expression
-        synchronized (parser) {
-            LOG.debug("Parsing script: " + cleanText);
-            try {
-                script = parser.parse(new StringReader(cleanText));
-            } catch (TokenMgrError tme) {
-                throw new ParseException(tme.getMessage());
-            }
+        if (scriptText == null) {
+            throw new NullPointerException("scriptText is null");
         }
+        SimpleNode script = parse(scriptText);
         if (script instanceof ASTJexlScript) {
-            return new ScriptImpl(this, cleanText, (ASTJexlScript) script);
+            return new ScriptImpl(this, scriptText, (ASTJexlScript) script);
         } else {
-            throw new IllegalStateException("Parsed script is not "
-                + "an ASTJexlScript");
+            throw new IllegalStateException("Parsed script is not an ASTJexlScript");
         }
     }
 
@@ -277,24 +287,178 @@
             new InputStreamReader(connection.getInputStream()));
         return createScript(readerToString(reader));
     }
+
+    /**
+     * Access properties of a bean using an expression.
+     * <p>
+     * jexl.get(myobject, "foo.bar"); should equate to
+     * myobject.getFoo().getBar(); (or myobject.getFoo().get("bar"))
+     * </p>
+     * <p>
+     * If the JEXL engine is silent, errors will be logged through its logger as info.
+     * </p>
+     * @param bean the bean to get properties from
+     * @param expr the property expression
+     * @return the value of the property
+     */
+    public Object getProperty(Object bean, String expr) {
+        return getProperty(EMPTY_CONTEXT, bean, expr);
+    }
+    public Object getProperty(JexlContext context, Object bean, String expr) {
+        Map<String, Object> vars = context.getVars();
+        // lets build 1 unique & unused identifiers wrt context
+        String r0 = "$0";
+        for(int s = 0; vars.containsKey(r0); ++s) r0 = r0 + s;
+        expr = r0 + (expr.charAt(0) =='['? "" : "." )+ expr +";";
+        try {
+            SimpleNode tree = parse(expr);
+            SimpleNode node = (SimpleNode) tree.jjtGetChild(0).jjtGetChild(0);
+            Interpreter interpreter = createInterpreter(context);
+            // ensure 4 objects in register array
+            Object[] r = {r0, bean, r0, bean};
+            interpreter.setRegisters(r);
+            return node.jjtAccept(interpreter, null);
+        }
+        catch(JexlException xjexl) {
+            if (silent) {
+                LOG.warn(xjexl.getMessage(), xjexl.getCause());
+                return null;
+            }
+            throw xjexl;
+        }
+        catch(ParseException xparse) {
+            if (silent) {
+                LOG.warn(xparse.getMessage(), xparse.getCause());
+                return null;
+            }
+            throw new RuntimeException(xparse);
+        }
+    }
+
+    /**
+     * Assign properties of a bean using an expression.
+     * <p>
+     * jexl.set(myobject, "foo.bar", 10); should equate to
+     * myobject.getFoo().setBar(10); (or myobject.getFoo().put("bar", 10) )
+     * </p>
+     * <p>
+     * If the JEXL engine is silent, errors will be logged through its logger as info.
+     * </p>
+     * @param bean the bean to set properties in
+     * @param expr the property expression
+     * @param value the value of the property
+     */
+    public void setProperty(Object bean, String expr, Object value) {
+       setProperty(EMPTY_CONTEXT, bean, expr, value);
+    }
+    public void setProperty(JexlContext context, Object bean, String expr, Object value) {
+        Map<String, Object> vars = context.getVars();
+        // lets build 2 unique & unused identifiers wrt context
+        String r0 = "$0", r1 = "$1";
+        for(int s = 0; vars.containsKey(r0); ++s) r0 = r0 + s;
+        for(int s = 0; vars.containsKey(r1); ++s) r1 = r1 + s;
+        // synthetize expr
+        expr = r0 + (expr.charAt(0) =='['? "" : "." )+ expr +"=" + r1 +";";
+        try {
+            SimpleNode tree = parse(expr);
+            SimpleNode node = (SimpleNode) tree.jjtGetChild(0).jjtGetChild(0);
+            Interpreter interpreter = createInterpreter(context);
+            // set the registers
+            Object[] r = {r0, bean, r1, value};
+            interpreter.setRegisters(r);
+            node.jjtAccept(interpreter, null);
+        }
+        catch(JexlException xjexl) {
+            if (silent) {
+                LOG.warn(xjexl.getMessage(), xjexl.getCause());
+                return;
+            }
+            throw xjexl;
+        }
+        catch(ParseException xparse) {
+            if (silent) {
+                LOG.warn(xparse.getMessage(), xparse.getCause());
+                return;
+            }
+            throw new RuntimeException(xparse);
+        }
+    }
     
     /**
-     * Creates an interpreter
+     * Creates an interpreter.
+     * @param context a JexlContext; if null, the EMPTY_CONTEXT is used instead.
+     * @return an Interpreter
      */
     protected Interpreter createInterpreter(JexlContext context) {
-        return new Interpreter(uberspect, arithmetic, functions, context);
+        if (context == null) context = EMPTY_CONTEXT;
+        return new Interpreter(this, context);
+    }
+    
+    /**
+     * Creates a SimpleNode cache.
+     * @param cacheSize the cache size, must be > 0
+     * @return a Map usable as a cache bounded to the given size
+     */
+    protected Map<String,SimpleNode> createCache(final int cacheSize) {
+        return new java.util.LinkedHashMap<String,SimpleNode>(cacheSize, 0.75f, true) {
+            @Override
+            protected boolean removeEldestEntry(Map.Entry eldest) {
+                return size() > cacheSize;
+            }
+        };
     }
+
     /**
-     * Trims the expression and adds a semi-colon if missing.
-     * @param expression to clean
+     * Parses an expression.
+     * @param expression
+     * @return the parsed tree
+     * @throws ParseException if any error occured during parsing
+     */
+    protected SimpleNode parse(CharSequence expression) throws ParseException {
+        String expr = cleanExpression(expression);
+        SimpleNode tree = null;
+        synchronized (parser) {
+            LOG.debug("Parsing expression: " + expression);
+            if (cache != null) {
+                tree = cache.get(expr);
+                if (tree != null) {
+                    return tree;
+                }
+            }
+            try {
+                Reader reader = expr.endsWith(";")?
+                            new StringReader(expr) :
+                            new StringReader(expr + ";");
+                tree = parser.parse(reader);
+                if (cache != null) {
+                    cache.put(expr, tree);
+                }
+            } catch (TokenMgrError tme) {
+                throw new ParseException(tme.getMessage());
+            } catch (ParseException e) {
+                throw e;
+            }
+        }
+        return tree;
+    }
+
+    /**
+     * Trims the expression from front & ending spaces.
+     * @param str expression to clean
      * @return trimmed expression ending in a semi-colon
      */
-    protected String cleanExpression(String expression) {
-        String expr = expression.trim();
-        if (!expr.endsWith(";")) {
-            expr += ";";
+    protected String cleanExpression(CharSequence str) {
+        if (str != null) {
+            int start = 0;
+            int end = str.length();
+            if (end > 0) {
+                for(start = 0; start < end && str.charAt(start) == ' '; ++start); // trim front spaces
+                for(;end > 0 && str.charAt(end - 1) == ' '; --end); // trim ending spaces
+                return str.subSequence(start, end).toString();
+            }
+            return "";
         }
-        return expr;
+        return null;
     }
     
     /**
@@ -306,7 +470,7 @@
      */
     protected static String readerToString(BufferedReader reader)
         throws IOException {
-        StringBuffer buffer = new StringBuffer();
+        StringBuilder buffer = new StringBuilder();
         try {
             String line;
             while ((line = reader.readLine()) != null) {

Modified: commons/proper/jexl/branches/2.0/src/java/org/apache/commons/jexl/JexlException.java
URL: http://svn.apache.org/viewvc/commons/proper/jexl/branches/2.0/src/java/org/apache/commons/jexl/JexlException.java?rev=794133&r1=794132&r2=794133&view=diff
==============================================================================
--- commons/proper/jexl/branches/2.0/src/java/org/apache/commons/jexl/JexlException.java (original)
+++ commons/proper/jexl/branches/2.0/src/java/org/apache/commons/jexl/JexlException.java Wed Jul 15 03:43:49 2009
@@ -26,6 +26,7 @@
 
     public JexlException(Node node, String msg) {
         super(msg);
+        mark = node;
     }
 
     public JexlException(Node node, String msg, Throwable cause) {
@@ -60,7 +61,8 @@
      * - msg is the actual explanation message for this error
      * @return this error as a string
      */
-    public String toString() {
+    @Override
+    public String getMessage() {
         Debugger dbg = new Debugger();
         StringBuilder msg = new StringBuilder();
         if (dbg.debug(mark)) {
@@ -72,7 +74,7 @@
             msg.append(dbg.data());
             msg.append("\n");
         }
-        msg.append(super.toString());
+        msg.append(super.getMessage());
         return msg.toString();
     }
 }

Modified: commons/proper/jexl/branches/2.0/src/java/org/apache/commons/jexl/ScriptImpl.java
URL: http://svn.apache.org/viewvc/commons/proper/jexl/branches/2.0/src/java/org/apache/commons/jexl/ScriptImpl.java?rev=794133&r1=794132&r2=794133&view=diff
==============================================================================
--- commons/proper/jexl/branches/2.0/src/java/org/apache/commons/jexl/ScriptImpl.java (original)
+++ commons/proper/jexl/branches/2.0/src/java/org/apache/commons/jexl/ScriptImpl.java Wed Jul 15 03:43:49 2009
@@ -43,17 +43,11 @@
         jexl = engine;
     }
 
-    /**
-     * {@inheritDoc}
-     */
     public Object execute(JexlContext context) throws Exception {
         Interpreter interpreter = jexl.createInterpreter(context);
-        return interpreter.interpret(parsedScript, jexl.isSilent());
+        return interpreter.interpret(parsedScript);
     }
 
-    /**
-     * {@inheritDoc}
-     */
     public String getText() {
         return text;
     }

Added: commons/proper/jexl/branches/2.0/src/java/org/apache/commons/jexl/UnifiedJEXL.java
URL: http://svn.apache.org/viewvc/commons/proper/jexl/branches/2.0/src/java/org/apache/commons/jexl/UnifiedJEXL.java?rev=794133&view=auto
==============================================================================
--- commons/proper/jexl/branches/2.0/src/java/org/apache/commons/jexl/UnifiedJEXL.java (added)
+++ commons/proper/jexl/branches/2.0/src/java/org/apache/commons/jexl/UnifiedJEXL.java Wed Jul 15 03:43:49 2009
@@ -0,0 +1,948 @@
+/*
+ * 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.jexl;
+
+import java.util.Map;
+import java.util.LinkedHashMap;
+import java.util.ArrayList;
+import org.apache.commons.jexl.parser.SimpleNode;
+import org.apache.commons.jexl.parser.ParseException;
+
+/**
+ * An evaluator similar to the unified EL evaluator used in JSP/JSF based on JEXL.
+ * It is intended to be used in configuration modules, XML based frameworks or JSP taglibs
+ * and facilitate the implementation of expression evaluation.
+ * <p>
+ * An expression can mix immediate, deferred and nested sub-expressions as well as string constants;<ol>
+ * <li>The "immediate" syntax is of the form "...${jexl-expr}..."</li>
+ * <li>The "deferred" syntax is of the form "...#{jexl-expr}..."</li>
+ * <li>The "nested" syntax is of the form "...#{...${jexl-expr0}...}..."</li>
+ * <li>The "composite" syntax is of the form "...${jexl-expr0}... #{jexl-expr1}..."</li>
+ * </ol>
+ * </p>
+ * <p>
+ * Deferred & immediate expression carry different intentions:
+ * <ol>
+ * <li>An immediate expression indicate that evaluation is intended to be performed close to
+ * the definition/parsing point.</li>
+ * <li>A deferred expression indicate that evaluation is intended to occur at a later stage.</li>
+ * </ol>
+ * </p>
+ * <p>
+ * For instance: "Hello ${name}, now is #{time}" is a composite "deferred" expression since one
+ * of its subexpressions is deferred. Furthermore, this (composite) expression intent is
+ * to perform two evaluations; one close to its definition and another one in a later
+ * phase.
+ * </p>
+ * <p>
+ * The API reflects this feature in 2 methods, prepare and evaluate. The prepare method
+ * will evaluate the immediate subexpression and return an expression that contains only
+ * the deferred subexpressions (& constants), a prepared expression. Such a prepared expression
+ * is suitable for a later phase evaluation that may occur with a different JexlContext.
+ * Note that it is valid to call evaluate without prepare in which case the same JexlContext
+ * is used for the 2 evaluation phases.
+ * </p>
+ * <p>
+ * In the most common use-case where deferred expressions are to be kept around as properties of objects,
+ * one should parse & prepare an expression before storing it and evaluate it each time
+ * the property storing it is accessed.
+ * </p>
+ * <p>
+ * Note that nested expression use the JEXL syntax as in:
+ * <code>"#{${bar}+'.charAt(2)'}"</code>
+ * The most common mistake leading to an invalid expression being the following:
+ * <code>"#{${bar}charAt(2)}"</code>
+ * </p>
+ */
+public class UnifiedJEXL {
+    /** The engine for this expression. */
+    private final JexlEngine jexl;
+    /** The expression cache. */
+    private final Map<String, Expression> cache;
+
+    /**
+     * Creates a new instance of UnifiedJEXL with a default size cache.
+     * @param jexl the JexlEngine to use.
+     */
+    public UnifiedJEXL(JexlEngine jexl) {
+        this(jexl, 512);
+    }
+
+    /**
+     * Creates a new instance of UnifiedJEXL creating a local cache.
+     * @param jexl the JexlEngine to use.
+     * @param cacheSize the number of expressions in this cache
+     */
+    public UnifiedJEXL(JexlEngine jexl, int cacheSize) {
+        this.jexl = jexl;
+        this.cache = cacheSize > 0 ? createCache(cacheSize) : null;
+    }
+
+    /**
+     * Creates an expression cache.
+     * @param size the cache size, must be > 0
+     * @return a LinkedHashMap
+     */
+    static private Map<String, Expression> createCache(final int cacheSize) {
+        return new LinkedHashMap<String, Expression>(cacheSize, 0.75f, true) {
+            @Override
+            protected boolean removeEldestEntry(Map.Entry eldest) {
+                return size() > cacheSize;
+            }
+        };
+    }
+
+    /**
+     * Types of expressions.
+     * Each instance carries a counter index per (composite sub-) expression type.
+     * @see ExpressionBuilder
+     */
+    private static enum ExpressionType {
+        CONSTANT(0), // constant count at index 0
+        IMMEDIATE(1), // immediate count at index 1
+        DEFERRED(2), // deferred count at index 2
+        NESTED(2), // nested are counted as deferred thus count at index 2
+        COMPOSITE(-1); // composite are not counted
+        /** the index in arrays of expression counters for composite expressions. */
+        private final int index;
+        ExpressionType(int index) {
+            this.index = index;
+        }
+    }
+
+    /**
+     * A helper class to build expressions.
+     * Keeps count of sub-expressions by type.
+     */
+    private static class ExpressionBuilder {
+        final int[] counts;
+        final ArrayList<Expression> expressions;
+
+        ExpressionBuilder(int size) {
+           counts = new int[]{0, 0, 0};
+           expressions = new ArrayList<Expression>(size <= 0? 3 : size);
+        }
+
+        /**
+         * Adds an expression to the list of expressions, maintain per-type counts.
+         * @param expr the expression to add
+         */
+        void add(Expression expr) {
+            counts[expr.getType().index] += 1;
+            expressions.add(expr);
+        }
+
+        /**
+         * Builds an expression from a source, performs checks
+         * @param el the unified el instance
+         * @param source the source expression
+         * @return an expression
+         */
+        Expression build(UnifiedJEXL el, Expression source) {
+            int sum = 0;
+            for(int i : counts) sum += i;
+            if (expressions.size() != sum) {
+                String error = "parsing algorithm error, exprs: " + expressions.size() +
+                   ", constant:" + counts[ExpressionType.CONSTANT.index] +
+                   ", immediate:" + counts[ExpressionType.IMMEDIATE.index] +
+                   ", deferred:" + counts[ExpressionType.DEFERRED.index];
+                throw new IllegalStateException(error);
+            }
+            // if only one sub-expr, no need to create a composite
+            return (expressions.size() == 1)?
+                    expressions.get(0) :
+                    el.new Composite(counts, expressions, source);
+        }
+    }
+
+
+    /**
+     * Gets the JexlEngine underlying the UnifiedJEXL.
+     * @return the JexlEngine
+     */
+    public JexlEngine getEngine() {
+        return jexl;
+    }
+
+    /**
+     * The sole type of (runtime) exception the UnifiedJEXL can throw.
+     */
+    public class Exception extends RuntimeException {
+        public Exception(String msg, Throwable cause) {
+            super(msg, cause);
+        }
+    }
+
+    /**
+     * The abstract base class for all expressions, immediate '${...}' and deferred '#{...}'.
+     */
+    public abstract class Expression {
+        final Expression source;
+
+        Expression(Expression source) {
+            this.source = source != null ? source : this;
+        }
+
+        /**
+         * Generate the string corresponding to this expression.
+         * @return the string representation
+         */
+        public String asString() {
+            return toString();
+        }
+
+        /**
+         * When the expression is dependant upon immediate and deferred sub-expressions,
+         * evaluates the immediate sub-expressions with the context passed as parameter
+         * and returns this expression deferred form.
+         * <p>
+         * In effect, this binds the result of the immediate sub-expressions evaluation in the
+         * context, allowing to differ evaluation of the remaining (deferred) expression within another context.
+         * This only has an effect to nested & composite expressions that contain differed & immediate sub-expressions.
+         * </p>
+         * <p>
+         * If the underlying JEXL engine is silent, errors will be logged through its logger as info.
+         * </p>
+         * @param context the context to use for immediate expression evaluations
+         * @return  an expression or null if an error occurs and the {@link JexlEngine} is silent
+         * @throws {@link Exception} if any error occurs and the {@link JexlEngine} is not silent
+         */
+        public abstract Expression prepare(JexlContext context);
+
+        /**
+         * Evaluates this expression.
+         * <p>
+         * If the underlying JEXL engine is silent, errors will be logged through its logger as info.
+         * </p>
+         * @param context the variable context
+         * @return the result of this expression evaluation or null if an error occurs and the {@link JexlEngine} is silent
+         * @throws [@link 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();
+        }
+
+        /**
+         * Retrieves this expression's source expression.
+         * If this expression was prepared, this allows to retrieve the
+         * original expression that lead to it.
+         * Other expressions return themselves.
+         * @return the source expression
+         */
+        public final Expression getSource() {
+            return source;
+        }
+
+        /**
+         * 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 ParseException (only for nested & composite)
+         */
+        abstract Expression prepare(Interpreter interpreter) throws ParseException;
+
+        /**
+         * Intreprets a sub-expression.
+         * @param interpreter a JEXL interpreter
+         * @return the result of interpretation
+         * @throws ParseException (only for nested & composite)
+         */
+        abstract Object evaluate(Interpreter interpreter) throws ParseException;
+    }
+
+
+    /** A constant expression. */
+    private class Constant extends Expression {
+        private final Object value;
+
+        Constant(Object value, Expression source) {
+            super(source);
+            if (value == null) {
+                throw new NullPointerException("constant can not be null");
+            }
+            this.value = value;
+        }
+
+        ExpressionType getType() {
+            return ExpressionType.CONSTANT;
+        }
+
+        @Override
+        public String toString() {
+            String str = value.toString();
+            if (value instanceof String || value instanceof CharSequence) {
+                StringBuilder strb = new StringBuilder(str.length() + 2);
+                if (source != this) {
+                    strb.append(source.toString());
+                    strb.append(" /*= ");
+                }
+                strb.append('"');
+                for (int i = 0, size = str.length(); i < size; ++i) {
+                    char c = str.charAt(i);
+                    if (c == '"')
+                        strb.append('\\');
+                    strb.append(c);
+                }
+                strb.append('"');
+                if (source != this) {
+                    strb.append(" */");
+                }
+                return strb.toString();
+            }
+            return str;
+        }
+
+        @Override
+        public String asString() {
+            return value.toString();
+        }
+
+        @Override
+        public Expression prepare(JexlContext context) {
+            return this;
+        }
+
+        @Override
+        Expression prepare(Interpreter interpreter) throws ParseException {
+            return this;
+        }
+
+        @Override
+        public Object evaluate(JexlContext context) {
+            return value;
+        }
+
+        @Override
+        Object evaluate(Interpreter interpreter) throws ParseException {
+            return value;
+        }
+
+    }
+
+
+    /** An immediate expression: ${jexl}. */
+    private class Immediate extends Expression {
+        private final CharSequence expr;
+        private final SimpleNode node;
+
+        Immediate(CharSequence expr, SimpleNode node, Expression source) {
+            super(source);
+            this.expr = expr;
+            this.node = node;
+        }
+
+        @Override
+        ExpressionType getType() {
+            return ExpressionType.IMMEDIATE;
+        }
+
+        @Override
+        public String toString() {
+            StringBuilder strb = new StringBuilder(expr.length() + 3);
+            if (source != this) {
+                strb.append(source.toString());
+                strb.append(" /*= ");
+            }
+            strb.append("${");
+            strb.append(expr);
+            strb.append("}");
+            if (source != this) {
+                strb.append(" */");
+            }
+            return strb.toString();
+        }
+
+        @Override
+        public String asString() {
+            return expr.toString();
+        }
+
+        @Override
+        public Expression prepare(JexlContext context) {
+            return this;
+        }
+
+        @Override
+        Expression prepare(Interpreter interpreter) throws ParseException {
+            return this;
+        }
+
+        @Override
+        public Object evaluate(JexlContext context) {
+            return UnifiedJEXL.this.evaluate(context, this);
+        }
+
+        @Override
+        Object evaluate(Interpreter interpreter) throws ParseException {
+            return interpreter.interpret(node);
+        }
+
+    }
+
+
+    /** A deferred expression: #{jexl}. */
+    private class Deferred extends Expression {
+        protected final CharSequence expr;
+        protected final SimpleNode node;
+
+        Deferred(CharSequence expr, SimpleNode node, Expression source) {
+            super(source);
+            this.expr = expr.toString();
+            this.node = node;
+        }
+
+        @Override
+        public boolean isImmediate() {
+            return false;
+        }
+
+        ExpressionType getType() {
+            return ExpressionType.DEFERRED;
+        }
+
+        @Override
+        public String toString() {
+            StringBuilder strb = new StringBuilder(expr.length() + 3);
+            if (source != this) {
+                strb.append(source.toString());
+                strb.append(" /*= ");
+            }
+            strb.append("#{");
+            strb.append(expr);
+            strb.append("}");
+            if (source != this) {
+                strb.append(" */");
+            }
+            return strb.toString();
+        }
+
+        @Override
+        public String asString() {
+            return expr.toString();
+        }
+
+        @Override
+        public Expression prepare(JexlContext context) {
+            return this;
+        }
+
+        @Override
+        Expression prepare(Interpreter interpreter) throws ParseException {
+            return this;
+        }
+
+        @Override
+        public Object evaluate(JexlContext context) {
+            return UnifiedJEXL.this.evaluate(context, this);
+        }
+
+        @Override
+        Object evaluate(Interpreter interpreter) throws ParseException {
+            return interpreter.interpret(node);
+        }
+    }
+
+
+    /**
+     * A deferred expression that nests an immediate expression.
+     * #{...${jexl}...}
+     * Note that the deferred syntax is JEXL's, not UnifiedJEXL.
+     */
+    private class Nested extends Deferred {
+        Nested(CharSequence expr, SimpleNode node, Expression source) {
+            super(expr, node, source);
+            if (this.source != this) {
+                throw new IllegalArgumentException("Nested expression can not have a source");
+            }
+        }
+        
+        @Override
+        ExpressionType getType() {
+            return ExpressionType.NESTED;
+        }
+
+        @Override
+        public String toString() {
+            return expr.toString();
+        }
+
+        @Override
+        public Expression prepare(JexlContext context) {
+            return UnifiedJEXL.this.prepare(context, this);
+        }
+
+        @Override
+        public Expression prepare(Interpreter interpreter) throws ParseException {
+            String value = interpreter.interpret(node).toString();
+            SimpleNode dnode = toNode(value);
+            return new Deferred(value, dnode, this);
+        }
+
+        @Override
+        public Object evaluate(Interpreter interpreter) throws ParseException {
+            return prepare(interpreter).evaluate(interpreter);
+        }
+    }
+
+
+    /** A composite expression: "... ${...} ... #{...} ...". */
+    private class Composite extends Expression {
+        // bit encoded (deferred count > 0) bit 1, (immediate count > 0) bit 0
+        final int meta;
+        // the list of expression resulting from parsing
+        final Expression[] exprs;
+
+        Composite(int[] counts, ArrayList<Expression> exprs, Expression source) {
+            super(source);
+            this.exprs = exprs.toArray(new Expression[exprs.size()]);
+            this.meta = (counts[ExpressionType.DEFERRED.index] > 0? 2 : 0) |
+                        (counts[ExpressionType.IMMEDIATE.index] > 0? 1 : 0);
+        }
+
+        int size() {
+            return exprs.length;
+        }
+
+        ExpressionType getType() {
+            return ExpressionType.COMPOSITE;
+        }
+
+        @Override
+        public boolean isImmediate() {
+            // immediate if no deferred
+            return (meta & 2) != 0;
+        }
+        
+        @Override
+        public String toString() {
+            StringBuilder strb = new StringBuilder();
+            if (source != this) {
+                strb.append(source.toString());
+                strb.append(" /*= ");
+            }
+            for (Expression e : exprs) {
+                strb.append(e.toString());
+            }
+            if (source != this) {
+                strb.append(" */ ");
+            }
+            return strb.toString();
+        }
+
+        @Override
+        public String asString() {
+            StringBuilder strb = new StringBuilder();
+            for (Expression e : exprs) {
+                strb.append(e.asString());
+            }
+            return strb.toString();
+        }
+
+        @Override
+        public Expression prepare(JexlContext context) {
+            return UnifiedJEXL.this.prepare(context, this);
+        }
+
+        @Override
+        Expression prepare(Interpreter interpreter) throws ParseException {
+            // 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;
+            final int size = size();
+            final ExpressionBuilder builder = new ExpressionBuilder(size);
+            // tracking whether prepare will return a different expression
+            boolean eq = true;
+            for(int e = 0; e < size; ++e) {
+                Expression expr = exprs[e];
+                Expression prepared = expr.prepare(interpreter);
+                if (evalImmediate && prepared instanceof Immediate) {
+                    // evaluate immediate as constant
+                    Object value = prepared.evaluate(interpreter);
+                    prepared = value == null? null : new Constant(value, prepared);
+                }
+                // add it if not null
+                if (prepared != null) {
+                    builder.add(prepared);
+                }
+                // keep track of expression equivalence
+                eq &= expr == prepared;
+            }
+            Expression ready = eq? this : builder.build(UnifiedJEXL.this, this);
+            return ready;
+        }
+
+
+        @Override
+        public Object evaluate(JexlContext context) {
+            return UnifiedJEXL.this.evaluate(context, this);
+        }
+
+        @Override
+        Object evaluate(Interpreter interpreter) throws ParseException {
+            final int size = size();
+            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) {
+                    strb.append(value.toString());
+                }
+            }
+            value = strb.toString();
+            return value;
+        }
+    }
+
+    /** Creates a a {@link UnifiedJEXL.Expression} from an expression string.
+     *  Uses & fills up the expression cache if any.
+     * <p>
+     * If the underlying JEXL engine is silent, errors will be logged through its logger as info.
+     * </p>
+     * @param expression the UnifiedJEXL string expression
+     * @return the UnifiedJEXL object expression, null if silent and an error occured
+     * @throws [@link Exception} if an error occurs and the {@link JexlEngine} is not silent
+     */
+    public Expression parse(String expression) {
+        try {
+            if (cache == null) {
+                return parseExpression(expression);
+            }
+            else synchronized (cache) {
+                Expression stmt = cache.get(expression);
+                if (stmt == null) {
+                    stmt = parseExpression(expression);
+                    cache.put(expression, stmt);
+                }
+                return stmt;
+            }
+        }
+        catch(JexlException xjexl) {
+            Exception xuel = new Exception("failed to parse '" + expression + "'", xjexl);
+            if (jexl.isSilent()) {
+                jexl.LOG.warn(xuel.getMessage(), xuel.getCause());
+                return null;
+            }
+            throw xuel;
+        }
+        catch(ParseException xparse) {
+            Exception xuel = new Exception("failed to parse '" + expression + "'", xparse);
+            if (jexl.isSilent()) {
+                jexl.LOG.warn(xuel.getMessage(), xuel.getCause());
+                return null;
+            }
+            throw xuel;
+        }
+    }
+
+    /**
+     * Prepares an expression (nested & composites), handles exception reporting.
+     *
+     * @param context the JEXL context to use
+     * @param expr the expression to prepare
+     * @return a prepared expression
+     */
+    Expression prepare(JexlContext context, Expression expr) {
+        try {
+            Interpreter interpreter = jexl.createInterpreter(context);
+            interpreter.setSilent(false);
+            return expr.prepare(interpreter);
+        }
+        catch (JexlException xjexl) {
+            Exception xuel = createException("prepare" , expr, xjexl);
+            if (jexl.isSilent()) {
+                jexl.LOG.warn(xuel.getMessage(), xuel.getCause());
+                return null;
+            }
+            throw xuel;
+        }
+        catch (ParseException xparse) {
+            Exception xuel = createException("prepare" , expr, xparse);
+            if (jexl.isSilent()) {
+                jexl.LOG.warn(xuel.getMessage(), xuel.getCause());
+                return null;
+            }
+            throw xuel;
+        }
+    }
+
+    /**
+     * Evaluates an expression (nested & composites), handles exception reporting.
+     *
+     * @param context the JEXL context to use
+     * @param expr the expression to prepare
+     * @return the result of the evaluation
+     */
+    Object evaluate(JexlContext context, Expression expr) {
+        try {
+            Interpreter interpreter = jexl.createInterpreter(context);
+            interpreter.setSilent(false);
+            return expr.evaluate(interpreter);
+        }
+        catch (JexlException xjexl) {
+            Exception xuel = createException("evaluate" , expr, xjexl);
+            if (jexl.isSilent()) {
+                jexl.LOG.warn(xuel.getMessage(), xuel.getCause());
+                return null;
+            }
+            throw xuel;
+        }
+        catch (ParseException xparse) {
+            Exception xuel = createException("evaluate" , expr, xparse);
+            if (jexl.isSilent()) {
+                jexl.LOG.warn(xuel.getMessage(), xuel.getCause());
+                return null;
+            }
+            throw xuel;
+        }
+    }
+
+    /**
+     * Use the JEXL parser to create the AST for an expression.
+     * @param expression the expression to parse
+     * @return the AST
+     */
+    private SimpleNode toNode(CharSequence expression) throws ParseException {
+        return (SimpleNode) jexl.parse(expression).jjtGetChild(0);
+    }
+
+    /**
+     * Creates a UnifiedJEXL.Exception from a JexlException.
+     * @param action parse, prepare, evaluate
+     * @param expr the expression
+     * @param xjexl the exception
+     * @return a "meaningfull" error message
+     */
+    private Exception createException(String action, Expression expr, java.lang.Exception xany) {
+        StringBuilder strb = new StringBuilder("failed to ");
+        strb.append(action);
+        strb.append(" '");
+        strb.append(expr.toString());
+        strb.append("'");
+        Throwable cause = xany.getCause();
+        if (cause != null) {
+            String causeMsg = cause.getMessage();
+            if (causeMsg != null) {
+                strb.append(", ");
+                strb.append(causeMsg);
+             }
+        }
+        return new Exception(strb.toString(), xany);
+    }
+
+    /** The different parsing states. */
+    private static enum ParseState {
+        CONST, // parsing a constant string
+        IMMEDIATE0, // seen $
+        DEFERRED0, // seen #
+        IMMEDIATE1, // seen ${
+        DEFERRED1, // seen #{
+        ESCAPE // seen \
+    }
+
+    /**
+     * Parses a unified expression
+     * @param expr the expression
+     * @param counts the expression type counters
+     * @return the list of expressions
+     * @throws Exception
+     */
+    private Expression parseExpression(String expr) throws ParseException {
+        final int size = expr.length();
+        ExpressionBuilder builder = new ExpressionBuilder(0);
+        StringBuilder strb = new StringBuilder(size);
+        ParseState state = ParseState.CONST;
+        int inner = 0;
+        boolean nested = false;
+        int inested = -1;
+        for (int i = 0; i < size; ++i) {
+            char c = expr.charAt(i);
+            switch (state) {
+                case CONST: {
+                    if (c == '$') {
+                        state = ParseState.IMMEDIATE0;
+                    } else if (c == '#') {
+                        inested = i;
+                        state = ParseState.DEFERRED0;
+                    } else if (c == '\\') {
+                        state = ParseState.ESCAPE;
+                    } else {
+                        // do buildup expr
+                        strb.append(c);
+                    }
+                    break;
+                }
+                case IMMEDIATE0: { // $
+                    if (c == '{') {
+                        state = ParseState.IMMEDIATE1;
+                        // if chars in buffer, create constant
+                        if (strb.length() > 0) {
+                            Expression cexpr = new Constant(strb.toString(), null);
+                            builder.add(cexpr);
+                            strb.delete(0, Integer.MAX_VALUE);
+                        }
+                    } else {
+                        // revert to CONST
+                        strb.append('$');
+                        strb.append(c);
+                        state = ParseState.CONST;
+                    }
+                    break;
+                }
+                case DEFERRED0: { // #
+                    if (c == '{') {
+                        state = ParseState.DEFERRED1;
+                        // if chars in buffer, create constant
+                        if (strb.length() > 0) {
+                            Expression cexpr = new Constant(strb.toString(), null);
+                            builder.add(cexpr);
+                            strb.delete(0, Integer.MAX_VALUE);
+                        }
+                    } else {
+                        // revert to CONST
+                        strb.append('#');
+                        strb.append(c);
+                        state = ParseState.CONST;
+                    }
+                    break;
+                }
+                case IMMEDIATE1: { // ${...
+                    if (c == '}') {
+                        // materialize the immediate expr
+                        //Expression iexpr = createExpression(ExpressionType.IMMEDIATE, strb, null);
+                        Expression iexpr = new Immediate(strb.toString(), toNode(strb), null);
+                        builder.add(iexpr);
+                        strb.delete(0, Integer.MAX_VALUE);
+                        state = ParseState.CONST;
+                    } else {
+                        // do buildup expr
+                        strb.append(c);
+                    }
+                    break;
+                }
+                case DEFERRED1: { // #{...
+                    // skip inner strings (for '}')
+                    if (c == '"' || c == '\'') {
+                        strb.append(c);
+                        i = readDeferredString(strb, expr, i + 1, c);
+                        continue;
+                    }
+                    // nested immediate in deferred; need to balance count of '{' & '}'
+                    if (c == '{') {
+                        if (expr.charAt(i - 1) == '$') {
+                            inner += 1;
+                            strb.deleteCharAt(strb.length() - 1);
+                            nested = true;
+                        }
+                        continue;
+                    }
+                    // closing '}'
+                    if (c == '}') {
+                        // balance nested immediate
+                        if (inner > 0) {
+                            inner -= 1;
+                        } else {
+                            // materialize the nested/deferred expr
+                            Expression dexpr = nested?
+                                               new Nested(expr.substring(inested, i+1), toNode(strb), null) :
+                                               new Deferred(strb.toString(), toNode(strb), null);
+                            builder.add(dexpr);
+                            strb.delete(0, Integer.MAX_VALUE);
+                            nested = false;
+                            state = ParseState.CONST;
+                        }
+                    } else {
+                        // do buildup expr
+                        strb.append(c);
+                    }
+                    break;
+                }
+                case ESCAPE: {
+                    if (c == '#') {
+                        strb.append('#');
+                    } else {
+                        strb.append('\\');
+                        strb.append(c);
+                    }
+                    state = ParseState.CONST;
+                }
+
+            }
+        }
+        // we should be in that state
+        if (state != ParseState.CONST)
+            throw new IllegalStateException("malformed expression: " + expr);
+        // if any chars were buffered, add them as a constant
+        if (strb.length() > 0) {
+            Expression cexpr = new Constant(strb.toString(), null);
+            builder.add(cexpr);
+        }
+        return builder.build(this, null);
+    }
+
+    /**
+     * Read the remainder of a string till a given separator,
+     * handles escaping through '\' syntax.
+     * @param strb the destination buffer to copy characters into
+     * @param str the origin
+     * @param index the offset into the origin
+     * @param sep the separator, single or double quote, marking end of string
+     * @return the offset in origin
+     */
+    static private int readDeferredString(StringBuilder strb, String str, int index, char sep) {
+        boolean escape = false;
+        for(;index < str.length();++index) {
+           char c = str.charAt(index);
+           if (escape) {
+               strb.append(c);
+               escape = false;
+               continue;
+           }
+           if (c == '\\') {
+               escape = true;
+               continue;
+           }
+           strb.append(c);
+           if (c == sep) {
+              break;
+           }
+        }
+        return index;
+    }
+}

Propchange: commons/proper/jexl/branches/2.0/src/java/org/apache/commons/jexl/UnifiedJEXL.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: commons/proper/jexl/branches/2.0/src/java/org/apache/commons/jexl/UnifiedJEXL.java
------------------------------------------------------------------------------
    svn:keywords = Date Author Id Revision HeadURL

Modified: commons/proper/jexl/branches/2.0/src/java/org/apache/commons/jexl/package.html
URL: http://svn.apache.org/viewvc/commons/proper/jexl/branches/2.0/src/java/org/apache/commons/jexl/package.html?rev=794133&r1=794132&r2=794133&view=diff
==============================================================================
--- commons/proper/jexl/branches/2.0/src/java/org/apache/commons/jexl/package.html (original)
+++ commons/proper/jexl/branches/2.0/src/java/org/apache/commons/jexl/package.html Wed Jul 15 03:43:49 2009
@@ -57,7 +57,7 @@
     jc.getVars().put("foo", new Foo() );
 
     // Now evaluate the expression, getting the result
-    Object o = e.evaluate(jc);</pre>
+    Object o = e.evaluate(jc);
   </pre>
   
 </body>

Modified: commons/proper/jexl/branches/2.0/src/java/org/apache/commons/jexl/parser/Parser.jjt
URL: http://svn.apache.org/viewvc/commons/proper/jexl/branches/2.0/src/java/org/apache/commons/jexl/parser/Parser.jjt?rev=794133&r1=794132&r2=794133&view=diff
==============================================================================
--- commons/proper/jexl/branches/2.0/src/java/org/apache/commons/jexl/parser/Parser.jjt (original)
+++ commons/proper/jexl/branches/2.0/src/java/org/apache/commons/jexl/parser/Parser.jjt Wed Jul 15 03:43:49 2009
@@ -47,7 +47,7 @@
 {
 
     public SimpleNode parse(Reader reader)
-        throws Exception
+        throws ParseException
     {
         ReInit(reader);
 
@@ -66,9 +66,10 @@
 
 SKIP : /* WHITE SPACE */
 {
-    <COMMENT: ( "##" ( ~["\"","\n","\r"] )* ("\n" | "\r") ) >
-|
-  " "
+  <COMMENT: ( "##" ( ~["\"","\n","\r"] )* ("\n" | "\r") ) >
+| <"/*" (~["*"])* "*" ("*" | ~["*","/"] (~["*"])* "*")* "/">
+| <"//" (~["\n","\r"])* ("\n" | "\r" | "\r\n")>
+| " "
 | "\t"
 | "\n"
 | "\r"

Modified: commons/proper/jexl/branches/2.0/src/java/org/apache/commons/jexl/util/introspection/ClassMap.java
URL: http://svn.apache.org/viewvc/commons/proper/jexl/branches/2.0/src/java/org/apache/commons/jexl/util/introspection/ClassMap.java?rev=794133&r1=794132&r2=794133&view=diff
==============================================================================
--- commons/proper/jexl/branches/2.0/src/java/org/apache/commons/jexl/util/introspection/ClassMap.java (original)
+++ commons/proper/jexl/branches/2.0/src/java/org/apache/commons/jexl/util/introspection/ClassMap.java Wed Jul 15 03:43:49 2009
@@ -29,9 +29,9 @@
 /**
  * Taken from the Velocity tree so we can be self-sufficient
  *
- * A cache of introspection information for a specific class instance. Keys
- * {@link MethodKey} objects by an agregation of the method name and
- * the names of classes that make up the parameters.
+ * A cache of introspection information for a specific class instance.
+ * Keys objects by an agregation of the method name and the names of classes
+ * that make up the parameters.
  *
  * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
  * @author <a href="mailto:bob@werken.com">Bob McWhirter</a>

Modified: commons/proper/jexl/branches/2.0/src/java/org/apache/commons/jexl/util/introspection/UberspectImpl.java
URL: http://svn.apache.org/viewvc/commons/proper/jexl/branches/2.0/src/java/org/apache/commons/jexl/util/introspection/UberspectImpl.java?rev=794133&r1=794132&r2=794133&view=diff
==============================================================================
--- commons/proper/jexl/branches/2.0/src/java/org/apache/commons/jexl/util/introspection/UberspectImpl.java (original)
+++ commons/proper/jexl/branches/2.0/src/java/org/apache/commons/jexl/util/introspection/UberspectImpl.java Wed Jul 15 03:43:49 2009
@@ -409,9 +409,7 @@
         }
     } // CSON: VisibilityModifier
 
-    /**
-     * {@inheritDoc}
-     */
+    
     public static class VelGetterImpl implements VelPropertyGet {
         /**
          * executor for performing the get.
@@ -457,9 +455,7 @@
         }
     }
 
-    /**
-     * {@inheritDoc}
-     */
+    
     public static class VelSetterImpl implements VelPropertySet {
         /**
          * the method to call.

Modified: commons/proper/jexl/branches/2.0/src/test/org/apache/commons/jexl/ArithmeticTest.java
URL: http://svn.apache.org/viewvc/commons/proper/jexl/branches/2.0/src/test/org/apache/commons/jexl/ArithmeticTest.java?rev=794133&r1=794132&r2=794133&view=diff
==============================================================================
--- commons/proper/jexl/branches/2.0/src/test/org/apache/commons/jexl/ArithmeticTest.java (original)
+++ commons/proper/jexl/branches/2.0/src/test/org/apache/commons/jexl/ArithmeticTest.java Wed Jul 15 03:43:49 2009
@@ -16,6 +16,7 @@
  */
 package org.apache.commons.jexl;
 
+import java.util.Map;
 import java.math.BigDecimal;
 import java.math.BigInteger;
 
@@ -112,4 +113,78 @@
         asserter.assertExpression("imanull + 2", new Long(2));
         asserter.assertExpression("imanull + imanull", new Long(0));
     }
+
+    /**
+     *
+     * if silent, all arith exception return 0.0
+     * if not silent, all arith exception throw
+     * @throws Exception
+     */
+    public void testDivideByZero() throws Exception {
+        JexlContext context = JexlHelper.createContext();
+        Map vars = context.getVars();
+        vars.put("aByte", new Byte((byte) 1));
+        vars.put("aShort", new Short((short) 2));
+        vars.put("anInteger", new Integer(3));
+        vars.put("aLong", new Long(4));
+        vars.put("aFloat", new Float(5.5));
+        vars.put("aDouble", new Double(6.6));
+        vars.put("aBigInteger", new BigInteger("7"));
+        vars.put("aBigDecimal", new BigDecimal("8.8"));
+
+
+        vars.put("zByte", new Byte((byte) 0));
+        vars.put("zShort", new Short((short) 0));
+        vars.put("zInteger", new Integer(0));
+        vars.put("zLong", new Long(0));
+        vars.put("zFloat", new Float(0));
+        vars.put("zDouble", new Double(0));
+        vars.put("zBigInteger", new BigInteger("0"));
+        vars.put("zBigDecimal", new BigDecimal("0"));
+
+        String[] tnames = {
+            "Byte", "Short", "Integer", "Long",
+            "Float", "Double",
+            "BigInteger", "BigDecimal"
+        };
+        // number of permutations this will generate
+        final int PERMS = tnames.length * tnames.length;
+
+        JexlEngine jexl = new JexlEngine();
+        // for non-silent, silent...
+        for (int s = 0; s < 2; ++s) {
+            jexl.setSilent(s != 0);
+            int zthrow = 0;
+            int zeval = 0;
+            // for vars of all types...
+            for (String vname : tnames) {
+                // for zeros of all types...
+                for (String zname : tnames) {
+                    // divide var by zero
+                    String expr = "a" + vname + " / " + "z" + zname;
+                    try {
+                        Expression zexpr = jexl.createExpression(expr);
+                        Object nan = zexpr.evaluate(context);
+                        // check we have a zero & incremement zero count
+                        if (nan instanceof Number) {
+                            double zero = ((Number) nan).doubleValue();
+                            if (zero == 0.0)
+                                zeval += 1;
+                        }
+                    }
+                    catch (Exception any) {
+                        // increment the exception count
+                        zthrow += 1;
+                    }
+                }
+            }
+            if (!jexl.isSilent())
+                assertTrue("All expressions should have thrown " + zthrow,
+                        zthrow == PERMS);
+            else
+                assertTrue("All expressions should have zeroed" + zeval,
+                        zeval == PERMS);
+        }
+    }
+
 }

Modified: commons/proper/jexl/branches/2.0/src/test/org/apache/commons/jexl/AssignTest.java
URL: http://svn.apache.org/viewvc/commons/proper/jexl/branches/2.0/src/test/org/apache/commons/jexl/AssignTest.java?rev=794133&r1=794132&r2=794133&view=diff
==============================================================================
--- commons/proper/jexl/branches/2.0/src/test/org/apache/commons/jexl/AssignTest.java (original)
+++ commons/proper/jexl/branches/2.0/src/test/org/apache/commons/jexl/AssignTest.java Wed Jul 15 03:43:49 2009
@@ -120,7 +120,6 @@
     public void testArray() throws Exception {
         Expression assign = JEXL.createExpression("froboz[\"value\"] = 10");
         Expression check = JEXL.createExpression("froboz[\"value\"]");
-        System.out.print(assign.dump());
         JexlContext jc = JexlHelper.createContext();
         Froboz froboz = new Froboz(0);
         jc.getVars().put("froboz", froboz);
@@ -134,12 +133,24 @@
         Expression assign = JEXL.createExpression("quux.froboz.value = 10");
         Expression check = JEXL.createExpression("quux[\"froboz\"].value");
         JexlContext jc = JexlHelper.createContext();
-        jc.getVars().put("quux", new Quux("xuuq", 100));
+        Quux quux = new Quux("xuuq", 100);
+        jc.getVars().put("quux", quux);
         Object o = assign.evaluate(jc);
         assertEquals("Result is not 10", new Integer(10), o);
         o = check.evaluate(jc);
         assertEquals("Result is not 10", new Integer(10), o);
     }
+
+    public void testUtil() throws Exception {
+        JexlContext jc = JexlHelper.createContext();
+        Quux quux = new Quux("xuuq", 100);
+        JEXL.setProperty(quux, "froboz.value", 100);
+        Object o = JEXL.getProperty(quux, "froboz.value");
+        assertEquals("Result is not 100", new Integer(100), o);
+        JEXL.setProperty(quux, "['froboz'].value", 1000);
+        o = JEXL.getProperty(quux, "['froboz']['value']");
+        assertEquals("Result is not 1000", new Integer(1000), o);
+    }
     
     
     public void testTernary() throws Exception {
@@ -169,7 +180,7 @@
     }
     
     public static void main(String[] args) throws Exception {
-        new AssignTest("debug").testAmbiguous();
+        new AssignTest("debug").testUtil();
         //new AssignTest("debug").testArray();
     }
 }
\ No newline at end of file

Added: commons/proper/jexl/branches/2.0/src/test/org/apache/commons/jexl/IssuesTest.java
URL: http://svn.apache.org/viewvc/commons/proper/jexl/branches/2.0/src/test/org/apache/commons/jexl/IssuesTest.java?rev=794133&view=auto
==============================================================================
--- commons/proper/jexl/branches/2.0/src/test/org/apache/commons/jexl/IssuesTest.java (added)
+++ commons/proper/jexl/branches/2.0/src/test/org/apache/commons/jexl/IssuesTest.java Wed Jul 15 03:43:49 2009
@@ -0,0 +1,127 @@
+/*
+ * 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.jexl;
+import java.util.Map;
+import junit.framework.TestCase;
+
+/**
+ * Test cases for reported issues
+ */
+public class IssuesTest  extends TestCase {
+
+    // JEXL-52: can be implemented by deriving Interpreter.{g,s}etAttribute
+    // JEXL-50: can be implemented through a namespace:function or through JexlArithmetic derivation
+
+    // JEXL-49: blocks not parsed (fixed)
+    public void test49() throws Exception {
+        JexlContext ctxt = JexlHelper.createContext();
+        Map vars = ctxt.getVars();
+
+        String stmt = "{a = 'b'; c = 'd';}";
+        Script expr = ScriptFactory.createScript(stmt);
+        Object value = expr.execute(ctxt);
+        assertTrue("JEXL-49 is not fixed", vars.get("a").equals("b") && vars.get("c").equals("d"));
+    }
+
+
+    // JEXL-48: bad assignment detection
+     public static class Another {
+        private Boolean foo = true;
+
+        public Boolean foo() {
+            return foo;
+        }
+
+        public int goo() {
+            return 100;
+        }
+    }
+
+     public static class Foo {
+        private Another inner;
+
+        Foo() {
+            inner = new Another();
+        }
+
+        public Another getInner() {
+            return inner;
+        }
+    }
+
+    public void test48() throws Exception {
+        JexlEngine jexl = new JexlEngine();
+        // ensure errors will throw
+        jexl.setSilent(false);
+        String jexlExp = "(foo.getInner().foo() eq true) and (foo.getInner().goo() = (foo.getInner().goo()+1-1))";
+        Expression e = jexl.createExpression( jexlExp );
+        JexlContext jc = JexlHelper.createContext();
+        jc.getVars().put("foo", new Foo() );
+
+        try {
+            Object o = e.evaluate(jc);
+            fail("Should have failed due to invalid assignment");
+        }
+        catch(JexlException xjexl) {
+            // expected
+        }
+    }
+
+    // JEXL-47: C style comments (single & multi line) (fixed)
+    // JEXL-44: comments dont allow double quotes (fixed)
+    public void test47() throws Exception {
+        JexlEngine jexl = new JexlEngine();
+        // ensure errors will throw
+        jexl.setSilent(false);
+        JexlContext ctxt = JexlHelper.createContext();
+
+        Expression expr = jexl.createExpression( "true//false\n" );
+        Object value = expr.evaluate(ctxt);
+        assertTrue("should be true", (Boolean) value);
+
+        expr = jexl.createExpression( "/*true*/false" );
+        value = expr.evaluate(ctxt);
+        assertFalse("should be false", (Boolean) value);
+
+        expr = jexl.createExpression( "/*\"true\"*/false" );
+        value = expr.evaluate(ctxt);
+        assertFalse("should be false", (Boolean) value);
+    }
+
+    // JEXL-46: regexp syntax; should we really add more syntactic elements?
+    // JEXL-45: unhandled division by zero (fixed see ArithmeticTest)
+
+    // JEXL-42: NullPointerException evaluating an expression (fixed)
+    public void test42() throws Exception {
+        JexlEngine jexl = new JexlEngine();
+        UnifiedJEXL uel = new UnifiedJEXL(jexl);
+        // ensure errors will throw
+        //jexl.setSilent(false);
+        JexlContext ctxt = JexlHelper.createContext();
+        ctxt.getVars().put("ax", "ok" );
+
+        UnifiedJEXL.Expression expr = uel.parse( "${ax+(bx)}" );
+        Object value = expr.evaluate(ctxt);
+        assertTrue("should be ok", "ok".equals(value));
+    }
+
+    public static void main(String[] args) throws Exception {
+        new IssuesTest().test42();
+    }
+
+}

Propchange: commons/proper/jexl/branches/2.0/src/test/org/apache/commons/jexl/IssuesTest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: commons/proper/jexl/branches/2.0/src/test/org/apache/commons/jexl/IssuesTest.java
------------------------------------------------------------------------------
    svn:keywords = Date Author Id Revision HeadURL

Modified: commons/proper/jexl/branches/2.0/src/test/org/apache/commons/jexl/Jexl.java
URL: http://svn.apache.org/viewvc/commons/proper/jexl/branches/2.0/src/test/org/apache/commons/jexl/Jexl.java?rev=794133&r1=794132&r2=794133&view=diff
==============================================================================
--- commons/proper/jexl/branches/2.0/src/test/org/apache/commons/jexl/Jexl.java (original)
+++ commons/proper/jexl/branches/2.0/src/test/org/apache/commons/jexl/Jexl.java Wed Jul 15 03:43:49 2009
@@ -31,15 +31,6 @@
         JexlContext context = new JexlContext() {
             public Map getVars() { return System.getProperties(); }
             public void setVars(Map map) { }
-
-            public void setFunctions(Map<String, Object> prefixes) {
-                throw new UnsupportedOperationException("Not supported yet.");
-            }
-
-            public Map<String, Object> getFunctions() {
-                throw new UnsupportedOperationException("Not supported yet.");
-            }
-
         };
         try {
             for (int i = 0; i < args.length; i++) {