You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by gr...@apache.org on 2009/06/13 19:45:24 UTC

svn commit: r784442 [2/4] - in /commons/proper/jexl/branches/2.0: ./ src/java/org/apache/commons/jexl/ src/java/org/apache/commons/jexl/logging/ src/java/org/apache/commons/jexl/parser/ src/java/org/apache/commons/jexl/util/ src/java/org/apache/commons...

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=784442&r1=784441&r2=784442&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 Sat Jun 13 17:45:23 2009
@@ -1,938 +1,1021 @@
-/*
- * 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.lang.reflect.Array;
-import java.lang.reflect.InvocationTargetException;
-import java.math.BigDecimal;
-import java.math.BigInteger;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-
-import org.apache.commons.jexl.parser.ASTAddNode;
-import org.apache.commons.jexl.parser.ASTAndNode;
-import org.apache.commons.jexl.parser.ASTArrayAccess;
-import org.apache.commons.jexl.parser.ASTAssignment;
-import org.apache.commons.jexl.parser.ASTBitwiseAndNode;
-import org.apache.commons.jexl.parser.ASTBitwiseComplNode;
-import org.apache.commons.jexl.parser.ASTBitwiseOrNode;
-import org.apache.commons.jexl.parser.ASTBitwiseXorNode;
-import org.apache.commons.jexl.parser.ASTBlock;
-import org.apache.commons.jexl.parser.ASTDivNode;
-import org.apache.commons.jexl.parser.ASTEQNode;
-import org.apache.commons.jexl.parser.ASTEmptyFunction;
-import org.apache.commons.jexl.parser.ASTExpression;
-import org.apache.commons.jexl.parser.ASTExpressionExpression;
-import org.apache.commons.jexl.parser.ASTFalseNode;
-import org.apache.commons.jexl.parser.ASTFloatLiteral;
-import org.apache.commons.jexl.parser.ASTForeachStatement;
-import org.apache.commons.jexl.parser.ASTGENode;
-import org.apache.commons.jexl.parser.ASTGTNode;
-import org.apache.commons.jexl.parser.ASTIdentifier;
-import org.apache.commons.jexl.parser.ASTIfStatement;
-import org.apache.commons.jexl.parser.ASTIntegerLiteral;
-import org.apache.commons.jexl.parser.ASTJexlScript;
-import org.apache.commons.jexl.parser.ASTLENode;
-import org.apache.commons.jexl.parser.ASTLTNode;
-import org.apache.commons.jexl.parser.ASTMapEntry;
-import org.apache.commons.jexl.parser.ASTMapLiteral;
-import org.apache.commons.jexl.parser.ASTMethod;
-import org.apache.commons.jexl.parser.ASTModNode;
-import org.apache.commons.jexl.parser.ASTMulNode;
-import org.apache.commons.jexl.parser.ASTNENode;
-import org.apache.commons.jexl.parser.ASTNotNode;
-import org.apache.commons.jexl.parser.ASTNullLiteral;
-import org.apache.commons.jexl.parser.ASTOrNode;
-import org.apache.commons.jexl.parser.ASTReference;
-import org.apache.commons.jexl.parser.ASTReferenceExpression;
-import org.apache.commons.jexl.parser.ASTSizeFunction;
-import org.apache.commons.jexl.parser.ASTSizeMethod;
-import org.apache.commons.jexl.parser.ASTStatementExpression;
-import org.apache.commons.jexl.parser.ASTStringLiteral;
-import org.apache.commons.jexl.parser.ASTSubtractNode;
-import org.apache.commons.jexl.parser.ASTTrueNode;
-import org.apache.commons.jexl.parser.ASTUnaryMinusNode;
-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.VisitorAdapter;
-import org.apache.commons.jexl.util.Coercion;
-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;
-
-/**
- * An interpreter of JEXL syntax.
- *
- * @since 2.0
- */
-class Interpreter extends VisitorAdapter {
-
-    /** The uberspect. */
-    private Uberspect uberspect;
-    /** The context to store/retrieve variables. */
-    private JexlContext context;
-    /** the arithmetic handler. */
-    private Arithmetic arithmetic;
-
-    /** dummy velocity info. */
-    private static final Info DUMMY = new Info("", 1, 1);
-
-    /**
-     * Create the interpreter.
-     * @param ctx the context to retrieve variables from.
-     * @param uber the helper to perform introspection,
-     * @param arith the arithmetic handler
-     */
-    public Interpreter(JexlContext ctx, Uberspect uber, Arithmetic arith) {
-        uberspect = uber;
-        context = ctx;
-        arithmetic = arith;
-    }
-
-    /**
-     * Interpret the given script/expression.
-     *
-     * @param node the script or expression to interpret.
-     * @param aContext the context to interpret against.
-     * @return the result of the interpretation.
-     */
-    public Object interpret(SimpleNode node, JexlContext aContext) {
-        context = aContext;
-        return node.jjtAccept(this, null);
-    }
-
-    /**
-     * Gets the uberspect.
-     *
-     * @return an {@link Uberspect}
-     */
-    protected Uberspect getUberspect() {
-        return uberspect;
-    }
-
-    /**
-     * Sets the context that contain variables.
-     *
-     * @param aContext a {link JexlContext}
-     */
-    public void setContext(JexlContext aContext) {
-        context = aContext;
-    }
-
-    // up to here
-    /** {@inheritDoc} */
-    public Object visit(ASTAddNode node, Object data) {
-        Object left = node.jjtGetChild(0).jjtAccept(this, data);
-        Object right = node.jjtGetChild(1).jjtAccept(this, data);
-        return arithmetic.add(left, right);
-    }
-
-    /** {@inheritDoc} */
-    public Object visit(ASTAndNode node, Object data) {
-
-        Object left = node.jjtGetChild(0).jjtAccept(this, data);
-        boolean leftValue = Coercion.coerceboolean(left);
-
-        // coercion rules
-        return (leftValue && Coercion.coerceboolean(node.jjtGetChild(1).jjtAccept(this, data))) ? Boolean.TRUE
-            : Boolean.FALSE;
-    }
-
-    /** {@inheritDoc} */
-    public Object visit(ASTArrayAccess node, Object data) {
-        // first child is the identifier
-        Object object = node.jjtGetChild(0).jjtAccept(this, data);
-        // can have multiple nodes - either an expression, integer literal or
-        // reference
-        int numChildren = node.jjtGetNumChildren();
-        for (int i = 1; i < numChildren; i++) {
-            Object index = node.jjtGetChild(i).jjtAccept(this, null);
-            object = getAttribute(object, index);
-        }
-
-        return object;
-    }
-
-    /** {@inheritDoc} */
-    public Object visit(ASTAssignment node, Object data) {
-        // child 0 should be the variable (reference) to assign to
-        Node left = node.jjtGetChild(0);
-        Object result = null;
-        if (left instanceof ASTReference) {
-            ASTReference reference = (ASTReference) left;
-            left = reference.jjtGetChild(0);
-            // TODO: this only works for a Reference that has a single
-            // identifier as it's child
-            if (left instanceof ASTIdentifier) {
-                String identifier = ((ASTIdentifier) left).image;
-                result = node.jjtGetChild(1).jjtAccept(this, data);
-                context.getVars().put(identifier, result);
-            }
-        } else {
-            throw new RuntimeException("Trying to assign to something other than a reference: " + left);
-        }
-
-        return result;
-    }
-
-    /** {@inheritDoc} */
-    public Object visit(ASTBitwiseAndNode node, Object data) {
-        Object left = node.jjtGetChild(0).jjtAccept(this, data);
-        Object right = node.jjtGetChild(1).jjtAccept(this, data);
-        // coerce these two values longs and add.
-        long l = Coercion.coercelong(left);
-        long r = Coercion.coercelong(right);
-        return new Long(l & r);
-    }
-
-    /** {@inheritDoc} */
-    public Object visit(ASTBitwiseComplNode node, Object data) {
-        Object left = node.jjtGetChild(0).jjtAccept(this, data);
-        long l = Coercion.coercelong(left);
-        return new Long(~l);
-    }
-
-    /** {@inheritDoc} */
-    public Object visit(ASTBitwiseOrNode node, Object data) {
-        Object left = node.jjtGetChild(0).jjtAccept(this, data);
-        Object right = node.jjtGetChild(1).jjtAccept(this, data);
-        long l = Coercion.coercelong(left);
-        long r = Coercion.coercelong(right);
-        return new Long(l | r);
-    }
-
-    /** {@inheritDoc} */
-    public Object visit(ASTBitwiseXorNode node, Object data) {
-        Object left = node.jjtGetChild(0).jjtAccept(this, data);
-        Object right = node.jjtGetChild(1).jjtAccept(this, data);
-        long l = Coercion.coercelong(left);
-        long r = Coercion.coercelong(right);
-        return new Long(l ^ r);
-    }
-
-    /** {@inheritDoc} */
-    public Object visit(ASTBlock node, Object data) {
-        int numChildren = node.jjtGetNumChildren();
-        Object result = null;
-        for (int i = 0; i < numChildren; i++) {
-            result = node.jjtGetChild(i).jjtAccept(this, data);
-        }
-        return result;
-    }
-
-    /** {@inheritDoc} */
-    public Object visit(ASTDivNode node, Object data) {
-
-        Object left = node.jjtGetChild(0).jjtAccept(this, data);
-        Object right = node.jjtGetChild(1).jjtAccept(this, data);
-        
-        return arithmetic.divide(left, right);
-    }
-
-    /** {@inheritDoc} */
-    public Object visit(ASTEmptyFunction node, Object data) {
-
-        Object o = node.jjtGetChild(0).jjtAccept(this, data);
-
-        if (o == null) {
-            return Boolean.TRUE;
-        }
-
-        if (o instanceof String && "".equals(o)) {
-            return Boolean.TRUE;
-        }
-
-        if (o.getClass().isArray() && ((Object[]) o).length == 0) {
-            return Boolean.TRUE;
-        }
-
-        if (o instanceof Collection && ((Collection) o).isEmpty()) {
-            return Boolean.TRUE;
-        }
-
-        // Map isn't a collection
-        if (o instanceof Map && ((Map) o).isEmpty()) {
-            return Boolean.TRUE;
-        }
-
-        return Boolean.FALSE;
-    }
-
-    /** {@inheritDoc} */
-    public Object visit(ASTEQNode node, Object data) {
-
-        Object left = node.jjtGetChild(0).jjtAccept(this, data);
-        Object right = node.jjtGetChild(1).jjtAccept(this, data);
-
-        return equals(left, right) ? Boolean.TRUE : Boolean.FALSE;
-    }
-
-    /** {@inheritDoc} */
-    public Object visit(ASTExpression node, Object data) {
-        return node.jjtGetChild(0).jjtAccept(this, data);
-    }
-
-    /** {@inheritDoc} */
-    public Object visit(ASTExpressionExpression node, Object data) {
-        return node.jjtGetChild(0).jjtAccept(this, data);
-    }
-
-    /** {@inheritDoc} */
-    public Object visit(ASTFalseNode node, Object data) {
-
-        return Boolean.FALSE;
-    }
-
-    /** {@inheritDoc} */
-    public Object visit(ASTFloatLiteral node, Object data) {
-
-        return Float.valueOf(node.image);
-    }
-
-    /** {@inheritDoc} */
-    public Object visit(ASTForeachStatement node, Object data) {
-
-        Object result = null;
-        /* first child is the loop variable */
-        ASTReference loopReference = (ASTReference) node.jjtGetChild(0);
-        ASTIdentifier loopVariable = (ASTIdentifier) loopReference.jjtGetChild(0);
-        /* second child is the variable to iterate */
-        Object iterableValue = node.jjtGetChild(1).jjtAccept(this, data);
-        // make sure there is a value to iterate on and a statement to execute
-        if (iterableValue != null && node.jjtGetNumChildren() >= 3) {
-            /* third child is the statement to execute */
-            SimpleNode statement = (SimpleNode) node.jjtGetChild(2);
-            // get an iterator for the collection/array etc via the
-            // introspector.
-            Iterator itemsIterator = getUberspect().getIterator(iterableValue, DUMMY);
-            while (itemsIterator.hasNext()) {
-                // set loopVariable to value of iterator
-                Object value = itemsIterator.next();
-                context.getVars().put(loopVariable.image, value);
-                // execute statement
-                result = statement.jjtAccept(this, data);
-            }
-        }
-        return result;
-    }
-
-    /** {@inheritDoc} */
-    public Object visit(ASTGENode node, Object data) {
-
-        Object left = node.jjtGetChild(0).jjtAccept(this, data);
-        Object right = node.jjtGetChild(1).jjtAccept(this, data);
-
-        return greaterThanOrEqual(left, right) ? Boolean.TRUE : Boolean.FALSE;
-    }
-
-    /** {@inheritDoc} */
-    public Object visit(ASTGTNode node, Object data) {
-
-        Object left = node.jjtGetChild(0).jjtAccept(this, data);
-        Object right = node.jjtGetChild(1).jjtAccept(this, data);
-
-        return greaterThan(left, right) ? Boolean.TRUE : Boolean.FALSE;
-    }
-
-    /** {@inheritDoc} */
-    public Object visit(ASTIdentifier node, Object data) {
-
-        String name = node.image;
-        if (data == null) {
-            return context.getVars().get(name);
-        } else {
-            return getAttribute(data, name);
-        }
-    }
-
-    /** {@inheritDoc} */
-    public Object visit(ASTIfStatement node, Object data) {
-
-        Object result = null;
-        /* first child is the expression */
-        Object expression = node.jjtGetChild(0).jjtAccept(this, data);
-        if (Coercion.coerceboolean(expression)) {
-            // first child is true statement
-            result = node.jjtGetChild(1).jjtAccept(this, data);
-        } else {
-            // if there is a false, execute it. false statement is the second
-            // child
-            if (node.jjtGetNumChildren() == 3) {
-                result = node.jjtGetChild(2).jjtAccept(this, data);
-            }
-        }
-
-        return result;
-    }
-
-    /** {@inheritDoc} */
-    public Object visit(ASTIntegerLiteral node, Object data) {
-        Integer value = Integer.valueOf(node.image);
-        if (data == null) {
-            return value;
-        } else {
-            return getAttribute(data, value);
-        }
-    }
-
-    /** {@inheritDoc} */
-    public Object visit(ASTJexlScript node, Object data) {
-
-        int numChildren = node.jjtGetNumChildren();
-        Object result = null;
-        for (int i = 0; i < numChildren; i++) {
-            Node child = node.jjtGetChild(i);
-            result = child.jjtAccept(this, data);
-        }
-        return result;
-    }
-
-    /** {@inheritDoc} */
-    public Object visit(ASTLENode node, Object data) {
-
-        Object left = node.jjtGetChild(0).jjtAccept(this, data);
-        Object right = node.jjtGetChild(1).jjtAccept(this, data);
-
-        return lessThanOrEqual(left, right) ? Boolean.TRUE : Boolean.FALSE;
-    }
-
-    /** {@inheritDoc} */
-    public Object visit(ASTLTNode node, Object data) {
-
-        Object left = node.jjtGetChild(0).jjtAccept(this, data);
-        Object right = node.jjtGetChild(1).jjtAccept(this, data);
-
-        return lessThan(left, right) ? Boolean.TRUE : Boolean.FALSE;
-    }
-
-    /** {@inheritDoc} */
-    public Object visit(ASTMapEntry node, Object data) {
-        Object key = node.jjtGetChild(0).jjtAccept(this, data);
-        Object value = node.jjtGetChild(1).jjtAccept(this, data);
-        return new Object[] {key, value};
-    }
-
-    /** {@inheritDoc} */
-    public Object visit(ASTMapLiteral node, Object data) {
-
-        int childCount = node.jjtGetNumChildren();
-        Map map = new HashMap();
-
-        for (int i = 0; i < childCount; i++) {
-            Object[] entry = (Object[]) (node.jjtGetChild(i)).jjtAccept(this, data);
-            map.put(entry[0], entry[1]);
-        }
-
-        return map;
-    }
-
-    /** {@inheritDoc} */
-    public Object visit(ASTMethod node, Object data) {
-
-        // child 0 is the identifier (method name), the others are parameters.
-        // the object to invoke the method on should be in the data argument
-        String methodName = ((ASTIdentifier) node.jjtGetChild(0)).image;
-
-        int paramCount = node.jjtGetNumChildren() - 1;
-
-        // get our params
-        Object[] params = new Object[paramCount];
-
-        try {
-            for (int i = 0; i < paramCount; i++) {
-                params[i] = node.jjtGetChild(i + 1).jjtAccept(this, null);
-            }
-
-            VelMethod vm = getUberspect().getMethod(data, methodName, params, DUMMY);
-            // DG: If we can't find an exact match, narrow the parameters and
-            // try again!
-            if (vm == null) {
-
-                // replace all numbers with the smallest type that will fit
-                for (int i = 0; i < params.length; i++) {
-                    Object param = params[i];
-                    if (param instanceof Number) {
-                        params[i] = narrow((Number) param);
-                    }
-                }
-                vm = getUberspect().getMethod(data, methodName, params, DUMMY);
-                if (vm == null) {
-                    return null;
-                }
-            }
-
-            return vm.invoke(data, params);
-        } catch (InvocationTargetException e) {
-            Throwable t = e.getTargetException();
-
-            if (t instanceof Exception) {
-                throw new RuntimeException(t);
-            }
-
-            throw new RuntimeException(e);
-        } catch (Exception e) {
-            throw new RuntimeException(e);
-        }
-    }
-
-    /** {@inheritDoc} */
-    public Object visit(ASTModNode node, Object data) {
-
-        Object left = node.jjtGetChild(0).jjtAccept(this, data);
-        Object right = node.jjtGetChild(1).jjtAccept(this, data);
-
-        return arithmetic.mod(left, right);
-    }
-
-    /** {@inheritDoc} */
-    public Object visit(ASTMulNode node, Object data) {
-        Object left = node.jjtGetChild(0).jjtAccept(this, data);
-        Object right = node.jjtGetChild(1).jjtAccept(this, data);
-        
-        return arithmetic.multiply(left, right);
-    }
-
-    /** {@inheritDoc} */
-    public Object visit(ASTNENode node, Object data) {
-
-        Object left = node.jjtGetChild(0).jjtAccept(this, data);
-        Object right = node.jjtGetChild(1).jjtAccept(this, data);
-
-        return equals(left, right) ? Boolean.FALSE : Boolean.TRUE;
-    }
-
-    /** {@inheritDoc} */
-    public Object visit(ASTNotNode node, Object data) {
-
-        Object val = node.jjtGetChild(0).jjtAccept(this, data);
-
-        // coercion rules
-        if (val != null) {
-            return Coercion.coerceboolean(val) ? Boolean.FALSE : Boolean.TRUE;
-        }
-
-        throw new IllegalArgumentException("not expression: not boolean valued " + val);
-    }
-
-    /** {@inheritDoc} */
-    public Object visit(ASTNullLiteral node, Object data) {
-        return null;
-    }
-
-    /** {@inheritDoc} */
-    public Object visit(ASTOrNode node, Object data) {
-
-        Object left = node.jjtGetChild(0).jjtAccept(this, data);
-        boolean leftValue = Coercion.coerceboolean(left);
-
-        // coercion rules
-        return (leftValue || Coercion.coerceboolean(node.jjtGetChild(1).jjtAccept(this, data))) ? Boolean.TRUE
-            : Boolean.FALSE;
-    }
-
-    /** {@inheritDoc} */
-    public Object visit(ASTReference node, Object data) {
-        // could be array access, identifier or map literal
-        // followed by zero or more ("." and array access, method, size,
-        // identifier or integer literal)
-
-        int numChildren = node.jjtGetNumChildren();
-
-        // pass first piece of data in and loop through children
-        Object result = null;
-        StringBuffer variableName = new StringBuffer();
-        boolean isVariable = true;
-        for (int i = 0; i < numChildren; i++) {
-            Node theNode = node.jjtGetChild(i);
-            isVariable = isVariable && (theNode instanceof ASTIdentifier);
-            result = theNode.jjtAccept(this, result);
-            // if we get null back a result, check for an ant variable
-            if (result == null && isVariable) {
-                if (i != 0) {
-                    variableName.append('.');
-                }
-                String name = ((ASTIdentifier) theNode).image;
-                variableName.append(name);
-                result = context.getVars().get(variableName.toString());
-            }
-        }
-
-        return result;
-    }
-
-    /** {@inheritDoc} */
-    public Object visit(ASTReferenceExpression node, Object data) {
-
-        return node.jjtGetChild(0).jjtAccept(this, data);
-    }
-
-    /** {@inheritDoc} */
-    public Object visit(ASTSizeFunction node, Object data) {
-        Object val = node.jjtGetChild(0).jjtAccept(this, data);
-
-        if (val == null) {
-            throw new IllegalArgumentException("size() : null arg");
-        }
-
-        return new Integer(sizeOf(val));
-    }
-
-    /** {@inheritDoc} */
-    public Object visit(ASTSizeMethod node, Object data) {
-        return new Integer(sizeOf(data));
-    }
-
-    /** {@inheritDoc} */
-    public Object visit(ASTStatementExpression node, Object data) {
-        return node.jjtGetChild(0).jjtAccept(this, data);
-    }
-
-    /** {@inheritDoc} */
-    public Object visit(ASTStringLiteral node, Object data) {
-        return node.image;
-    }
-
-    /** {@inheritDoc} */
-    public Object visit(ASTSubtractNode node, Object data) {
-        Object left = node.jjtGetChild(0).jjtAccept(this, data);
-        Object right = node.jjtGetChild(1).jjtAccept(this, data);
-        
-        return arithmetic.subtract(left, right);
-    }
-
-    /** {@inheritDoc} */
-    public Object visit(ASTTrueNode node, Object data) {
-        return Boolean.TRUE;
-    }
-
-    /** {@inheritDoc} */
-    public Object visit(ASTUnaryMinusNode node, Object data) {
-
-        Object val = node.jjtGetChild(0).jjtAccept(this, data);
-
-        if (val instanceof Byte) {
-            byte valueAsByte = ((Byte) val).byteValue();
-            return new Byte((byte) -valueAsByte);
-        } else if (val instanceof Short) {
-            short valueAsShort = ((Short) val).shortValue();
-            return new Short((short) -valueAsShort);
-        } else if (val instanceof Integer) {
-            int valueAsInt = ((Integer) val).intValue();
-            return new Integer(-valueAsInt);
-        } else if (val instanceof Long) {
-            long valueAsLong = ((Long) val).longValue();
-            return new Long(-valueAsLong);
-        } else if (val instanceof Float) {
-            float valueAsFloat = ((Float) val).floatValue();
-            return new Float(-valueAsFloat);
-        } else if (val instanceof Double) {
-            double valueAsDouble = ((Double) val).doubleValue();
-            return new Double(-valueAsDouble);
-        } else if (val instanceof BigDecimal) {
-            BigDecimal valueAsBigD = (BigDecimal) val;
-            return valueAsBigD.negate();
-        } else if (val instanceof BigInteger) {
-            BigInteger valueAsBigI = (BigInteger) val;
-            return valueAsBigI.negate();
-        }
-        throw new NumberFormatException("expression not a number: " + val);
-    }
-
-    /** {@inheritDoc} */
-    public Object visit(ASTWhileStatement node, Object data) {
-        Object result = null;
-        /* first child is the expression */
-        Node expressionNode = (Node) node.jjtGetChild(0);
-        while (Coercion.coerceboolean(expressionNode.jjtAccept(this, data))) {
-            // execute statement
-            result = node.jjtGetChild(1).jjtAccept(this, data);
-        }
-
-        return result;
-    }
-
-    // other stuff
-
-    /**
-     * Given a Number, return back the value using the smallest type the result
-     * will fit into. This works hand in hand with parameter 'widening' in java
-     * method calls, e.g. a call to substring(int,int) with an int and a long
-     * will fail, but a call to substring(int,int) with an int and a short will
-     * succeed.
-     *
-     * @param original the original number.
-     * @return a value of the smallest type the original number will fit into.
-     * @since 1.1
-     */
-    private Number narrow(Number original) {
-        if (original == null) {
-            return original;
-        }
-        Number result = original;
-        if (original instanceof BigDecimal) {
-            BigDecimal bigd = (BigDecimal) original;
-            // if it's bigger than a double it can't be narrowed
-            if (bigd.compareTo(new BigDecimal(Double.MAX_VALUE)) > 0) {
-                return original;
-            }
-        }
-        if (original instanceof Double || original instanceof Float || original instanceof BigDecimal) {
-            double value = original.doubleValue();
-            if (value <= Float.MAX_VALUE && value >= Float.MIN_VALUE) {
-                result = new Float(result.floatValue());
-            }
-            // else it fits in a double only
-        } else {
-            if (original instanceof BigInteger) {
-                BigInteger bigi = (BigInteger) original;
-                // if it's bigger than a Long it can't be narrowed
-                if (bigi.compareTo(new BigInteger(String.valueOf(Long.MAX_VALUE))) > 0) {
-                    return original;
-                }
-            }
-            long value = original.longValue();
-            if (value <= Byte.MAX_VALUE && value >= Byte.MIN_VALUE) {
-                // it will fit in a byte
-                result = new Byte((byte) value);
-            } else if (value <= Short.MAX_VALUE && value >= Short.MIN_VALUE) {
-                result = new Short((short) value);
-            } else if (value <= Integer.MAX_VALUE && value >= Integer.MIN_VALUE) {
-                result = new Integer((int) value);
-            }
-            // else it fits in a long
-        }
-        return result;
-    }
-
-    /**
-     * Calculate the <code>size</code> of various types: Collection, Array,
-     * Map, String, and anything that has a int size() method.
-     *
-     * @param val the object to get the size of.
-     * @return the size of val
-     */
-    private int sizeOf(Object val) {
-        if (val instanceof Collection) {
-            return ((Collection) val).size();
-        } else if (val.getClass().isArray()) {
-            return Array.getLength(val);
-        } else if (val instanceof Map) {
-            return ((Map) val).size();
-        } else if (val instanceof String) {
-            return ((String) val).length();
-        } else {
-            // check if there is a size method on the object that returns an
-            // integer
-            // and if so, just use it
-            Object[] params = new Object[0];
-            // Info velInfo = new Info("", 1, 1);
-            VelMethod vm = uberspect.getMethod(val, "size", params, DUMMY);
-            if (vm != null && vm.getReturnType() == Integer.TYPE) {
-                Integer result;
-                try {
-                    result = (Integer) vm.invoke(val, params);
-                } catch (Exception e) {
-                    throw new RuntimeException("size() : error executing", e);
-                }
-                return result.intValue();
-            }
-            throw new IllegalArgumentException("size() : unknown type : " + val.getClass());
-        }
-    }
-
-    /**
-     * Get an attribute of an object.
-     *
-     * @param object to retrieve value from
-     * @param attribute the attribute of the object, e.g. an index (1, 0, 2) or
-     *            key for a map
-     * @return the attribute.
-     */
-    private Object getAttribute(Object object, Object attribute) {
-        if (object == null) {
-            return null;
-        }
-        if (attribute == null) {
-            return null;
-        }
-        if (object instanceof Map) {
-            return ((Map) object).get(attribute);
-        } else if (object instanceof List) {
-            int idx = Coercion.coerceinteger(attribute);
-
-            try {
-                return ((List) object).get(idx);
-            } catch (IndexOutOfBoundsException iobe) {
-                return null;
-            }
-        } else if (object.getClass().isArray()) {
-            int idx = Coercion.coerceinteger(attribute);
-
-            try {
-                return Array.get(object, idx);
-            } catch (ArrayIndexOutOfBoundsException aiobe) {
-                return null;
-            }
-        } else {
-            // look up bean property of data and return
-            VelPropertyGet vg = getUberspect().getPropertyGet(object, attribute.toString(), DUMMY);
-
-            if (vg != null) {
-                try {
-                    return vg.invoke(object);
-                } catch (Exception e) {
-                    throw new RuntimeException(e);
-                }
-            } else {
-                return null;
-            }
-
-        }
-    }
-
-    /**
-     * Test if left and right are equal.
-     *
-     * @param left first value
-     * @param right second value
-     * @return test result.
-     */
-    private boolean equals(Object left, Object right) {
-        if (left == null && right == null) {
-            /*
-             * if both are null L == R
-             */
-            return true;
-        } else if (left == null || right == null) {
-            /*
-             * we know both aren't null, therefore L != R
-             */
-            return false;
-        } else if (left.getClass().equals(right.getClass())) {
-            return left.equals(right);
-        } else if (left instanceof BigDecimal || right instanceof BigDecimal) {
-            return Coercion.coerceBigDecimal(left).compareTo(Coercion.coerceBigDecimal(right)) == 0;
-        } else if (isFloatingPointType(left, right)) {
-            Double l = Coercion.coerceDouble(left);
-            Double r = Coercion.coerceDouble(right);
-
-            return l.equals(r);
-        } else if (left instanceof Number || right instanceof Number || left instanceof Character
-            || right instanceof Character) {
-            return Coercion.coerceLong(left).equals(Coercion.coerceLong(right));
-        } else if (left instanceof Boolean || right instanceof Boolean) {
-            return Coercion.coerceBoolean(left).equals(Coercion.coerceBoolean(right));
-        } else if (left instanceof java.lang.String || right instanceof String) {
-            return left.toString().equals(right.toString());
-        }
-
-        return left.equals(right);
-    }
-
-    /**
-     * Test if either left or right are either a Float or Double.
-     * @param left one object to test
-     * @param right the other
-     * @return the result of the test.
-     */
-    private boolean isFloatingPointType(Object left, Object right) {
-        return left instanceof Float || left instanceof Double || right instanceof Float || right instanceof Double;
-    }
-
-    /**
-     * Test if left < right.
-     *
-     * @param left first value
-     * @param right second value
-     * @return test result.
-     */
-    private boolean lessThan(Object left, Object right) {
-        if ((left == right) || (left == null) || (right == null)) {
-            return false;
-        } else if (Coercion.isFloatingPoint(left) || Coercion.isFloatingPoint(right)) {
-            double leftDouble = Coercion.coerceDouble(left).doubleValue();
-            double rightDouble = Coercion.coerceDouble(right).doubleValue();
-
-            return leftDouble < rightDouble;
-            } else if (left instanceof BigDecimal || right instanceof BigDecimal) {
-                BigDecimal l  = Coercion.coerceBigDecimal(left);
-                BigDecimal r  = Coercion.coerceBigDecimal(right);
-                return l.compareTo(r) < 0;
-        } else if (Coercion.isNumberable(left) || Coercion.isNumberable(right)) {
-            long leftLong = Coercion.coerceLong(left).longValue();
-            long rightLong = Coercion.coerceLong(right).longValue();
-
-            return leftLong < rightLong;
-        } else if (left instanceof String || right instanceof String) {
-            String leftString = left.toString();
-            String rightString = right.toString();
-
-            return leftString.compareTo(rightString) < 0;
-        } else if (left instanceof Comparable) {
-            return ((Comparable) left).compareTo(right) < 0;
-        } else if (right instanceof Comparable) {
-            return ((Comparable) right).compareTo(left) > 0;
-        }
-
-        throw new IllegalArgumentException("Invalid comparison : comparing cardinality for left: " + left
-            + " and right: " + right);
-
-    }
-
-    /**
-     * Test if left > right.
-     *
-     * @param left first value
-     * @param right second value
-     * @return test result.
-     */
-    private boolean greaterThan(Object left, Object right) {
-        if (left == null || right == null) {
-            return false;
-        }
-        return !equals(left, right) && !lessThan(left, right);
-    }
-
-    /**
-     * Test if left <= right.
-     *
-     * @param left first value
-     * @param right second value
-     * @return test result.
-     */
-    private boolean lessThanOrEqual(Object left, Object right) {
-        return equals(left, right) || lessThan(left, right);
-    }
-
-    /**
-     * Test if left >= right.
-     *
-     * @param left first value
-     * @param right second value
-     * @return test result.
-     */
-    private boolean greaterThanOrEqual(Object left, Object right) {
-        return equals(left, right) || greaterThan(left, right);
-    }
-
+/*
+ * 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.lang.reflect.Array;
+import java.lang.reflect.InvocationTargetException;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.jexl.parser.ASTAddNode;
+import org.apache.commons.jexl.parser.ASTAndNode;
+import org.apache.commons.jexl.parser.ASTArrayAccess;
+import org.apache.commons.jexl.parser.ASTAssignment;
+import org.apache.commons.jexl.parser.ASTBitwiseAndNode;
+import org.apache.commons.jexl.parser.ASTBitwiseComplNode;
+import org.apache.commons.jexl.parser.ASTBitwiseOrNode;
+import org.apache.commons.jexl.parser.ASTBitwiseXorNode;
+import org.apache.commons.jexl.parser.ASTBlock;
+import org.apache.commons.jexl.parser.ASTDivNode;
+import org.apache.commons.jexl.parser.ASTEQNode;
+import org.apache.commons.jexl.parser.ASTEmptyFunction;
+import org.apache.commons.jexl.parser.ASTExpression;
+import org.apache.commons.jexl.parser.ASTExpressionExpression;
+import org.apache.commons.jexl.parser.ASTFalseNode;
+import org.apache.commons.jexl.parser.ASTFloatLiteral;
+import org.apache.commons.jexl.parser.ASTForeachStatement;
+import org.apache.commons.jexl.parser.ASTGENode;
+import org.apache.commons.jexl.parser.ASTGTNode;
+import org.apache.commons.jexl.parser.ASTIdentifier;
+import org.apache.commons.jexl.parser.ASTIfStatement;
+import org.apache.commons.jexl.parser.ASTIntegerLiteral;
+import org.apache.commons.jexl.parser.ASTJexlScript;
+import org.apache.commons.jexl.parser.ASTLENode;
+import org.apache.commons.jexl.parser.ASTLTNode;
+import org.apache.commons.jexl.parser.ASTMapEntry;
+import org.apache.commons.jexl.parser.ASTMapLiteral;
+import org.apache.commons.jexl.parser.ASTMethod;
+import org.apache.commons.jexl.parser.ASTModNode;
+import org.apache.commons.jexl.parser.ASTMulNode;
+import org.apache.commons.jexl.parser.ASTNENode;
+import org.apache.commons.jexl.parser.ASTNotNode;
+import org.apache.commons.jexl.parser.ASTNullLiteral;
+import org.apache.commons.jexl.parser.ASTOrNode;
+import org.apache.commons.jexl.parser.ASTReference;
+import org.apache.commons.jexl.parser.ASTReferenceExpression;
+import org.apache.commons.jexl.parser.ASTSizeFunction;
+import org.apache.commons.jexl.parser.ASTSizeMethod;
+import org.apache.commons.jexl.parser.ASTStatementExpression;
+import org.apache.commons.jexl.parser.ASTStringLiteral;
+import org.apache.commons.jexl.parser.ASTSubtractNode;
+import org.apache.commons.jexl.parser.ASTTernaryNode;
+import org.apache.commons.jexl.parser.ASTTrueNode;
+import org.apache.commons.jexl.parser.ASTUnaryMinusNode;
+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.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 uberspect. */
+    private final Uberspect uberspect;
+    /** the arithmetic handler. */
+    private final Arithmetic arithmetic;
+    /** The context to store/retrieve variables. */
+    private final JexlContext context;
+
+    /** dummy velocity info. */
+    private static final Info DUMMY = new Info("", 1, 1);
+    /** empty params for method matching. */
+    static private 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
+     */
+    public Interpreter(Uberspect uber, Arithmetic arith, JexlContext context) {
+        this.uberspect = uber;
+        this.arithmetic = arith;
+        this.context = context;
+    }
+
+    /**
+     * Interpret the given script/expression.
+     *
+     * @param node the script or expression to interpret.
+     * @param aContext the context to interpret against.
+     * @return the result of the interpretation.
+     */
+    public Object interpret(SimpleNode node, boolean silent) {
+        try {
+        return node.jjtAccept(this, null);
+    }
+        catch(JexlException error) {
+            if (silent)
+                return null;
+            throw error;
+        }
+    }
+
+    public String debug(SimpleNode node) {
+        return debug(node, null);
+    }
+    
+    public String debug(SimpleNode node, int[] offsets) {
+        Debugger debug = new Debugger();
+        debug.debug(node);
+        if (offsets != null) {
+            offsets[0] = debug.start();
+            offsets[1] = debug.end();
+        }
+        return debug.data();
+    }
+
+    /**
+     * Gets the uberspect.
+     *
+     * @return an {@link Uberspect}
+     */
+    protected Uberspect getUberspect() {
+        return uberspect;
+    }
+
+
+    public Object visit(SimpleNode node, Object data) {
+        throw new UnsupportedOperationException("unexpected node " + node);
+    }
+
+    /** {@inheritDoc} */
+    public Object visit(ASTAddNode node, Object data) {
+        /**
+         * The pattern for exception mgmt is to let the child*.jjtAccept
+         * out of the try/catch loop so that if one fails, the ex will
+         * traverse up to the interpreter.
+         * In cases where this is not convenient/possible, JexlException must
+         * be caught explicitly and rethrown.
+         */
+        Object left = node.jjtGetChild(0).jjtAccept(this, data);
+        Object right = node.jjtGetChild(1).jjtAccept(this, data);
+        try {
+        return arithmetic.add(left, right);
+    }
+        catch(RuntimeException xrt) {
+            throw new JexlException(node, "add error", xrt);
+        }
+    }
+
+    /** {@inheritDoc} */
+    public Object visit(ASTAndNode node, Object data) {
+        Object left = node.jjtGetChild(0).jjtAccept(this, data);
+        try {
+            boolean leftValue = arithmetic.toBoolean(left);
+            if (!leftValue)
+                return Boolean.FALSE;
+        }
+        catch(RuntimeException xrt) {
+            throw new JexlException(node.jjtGetChild(0), "boolean coercion error", xrt);
+        }
+        Object right = node.jjtGetChild(1).jjtAccept(this, data);
+        try {
+            boolean rightValue = arithmetic.toBoolean(right);
+            if (!rightValue)
+                return Boolean.FALSE;
+        }
+        catch(RuntimeException xrt) {
+            throw new JexlException(node.jjtGetChild(1), "boolean coercion error", xrt);
+        }
+        return Boolean.TRUE;
+    }
+
+    /** {@inheritDoc} */
+    public Object visit(ASTArrayAccess node, Object data) {
+        // first objectNode is the identifier
+        Object object = node.jjtGetChild(0).jjtAccept(this, data);
+        // can have multiple nodes - either an expression, integer literal or
+        // reference
+        int numChildren = node.jjtGetNumChildren();
+        for (int i = 1; i < numChildren; i++) {
+            Node nindex = node.jjtGetChild(i);
+            Object index = nindex.jjtAccept(this, null);
+            object = getAttribute(object, index, nindex);
+        }
+
+        return object;
+    }
+
+    /** {@inheritDoc} */
+    public Object visit(ASTAssignment node, Object data) {
+        // left contains the reference to assign to
+        Node left = node.jjtGetChild(0);
+        if (!(left instanceof ASTReference))
+                throw new JexlException(left, "illegal assignment form");
+        // right is the value expression to assign
+        Object right = node.jjtGetChild(1).jjtAccept(this, data);
+
+        // determine initial object & property:
+        Node objectNode = null;
+        Object object = null;
+        Node propertyNode = null;
+        Object property = null;
+        boolean isVariable = true;
+        StringBuilder variableName = null;
+        // 1: follow children till penultimate
+        int last = left.jjtGetNumChildren() - 1;
+        for (int c = 0; c < last; ++c) {
+            objectNode = left.jjtGetChild(c);
+            // evaluate the property within the object
+            object = objectNode.jjtAccept(this, object);
+            if (object != null)
+                continue;
+            isVariable &= objectNode instanceof ASTIdentifier;
+            // if we get null back as a result, check for an ant variable
+            if (isVariable) {
+                String name = ((ASTIdentifier) objectNode).image;
+                if (c == 0)
+                    variableName = new StringBuilder(name);
+                else {
+                    variableName.append('.');
+                    variableName.append(name);
+                }
+                object = context.getVars().get(variableName.toString());
+                // disallow mixing ant & bean with same root; avoid ambiguity
+                if (object != null)
+                    isVariable = false;
+            }
+            else
+                throw new JexlException(objectNode, "illegal assignment form");
+        }
+        // 2: last objectNode will perform assignement in all cases
+        propertyNode = left.jjtGetChild(last);
+        if (propertyNode instanceof ASTIdentifier) {
+            property = ((ASTIdentifier) propertyNode).image;
+            // deal with ant variable
+            if (isVariable && object == null) {
+                if (variableName != null) {
+                    if (last > 0) variableName.append('.');
+                    variableName.append(property);
+                    property = variableName.toString();
+                }
+                context.getVars().put(property, right);
+                return right;
+            }
+        } else if (propertyNode instanceof ASTArrayAccess) {
+            // first objectNode is the identifier
+            objectNode = propertyNode;
+            ASTArrayAccess narray = (ASTArrayAccess) objectNode;
+            Object nobject = narray.jjtGetChild(0).jjtAccept(this, object);
+            if (nobject == null)
+                throw new JexlException(objectNode, "array element is null");
+            else
+                object = nobject;
+            // can have multiple nodes - either an expression, integer literal or
+            // reference
+            last = narray.jjtGetNumChildren() - 1;
+            for (int i = 1; i < last; i++) {
+                objectNode = narray.jjtGetChild(i);
+                Object index = objectNode.jjtAccept(this, null);
+                object = getAttribute(object, index, objectNode);
+            }
+            property = narray.jjtGetChild(last).jjtAccept(this, null);
+        } else {
+            throw new JexlException(objectNode, "illegal assignment form");
+        }
+        if (property == null) {
+            // no property, we fail
+            throw new JexlException(propertyNode, "property is null");
+        }
+        if (object == null) {
+            // no object, we fail
+            throw new JexlException(objectNode, "bean is null");
+        }
+        // one before last, assign
+        setAttribute(object, property, right, propertyNode);
+        return right;
+    }
+
+    /** {@inheritDoc} */
+    public Object visit(ASTBitwiseAndNode node, Object data) {
+        Object left = node.jjtGetChild(0).jjtAccept(this, data);
+        Object right = node.jjtGetChild(1).jjtAccept(this, data);
+        int n = 0;
+        // coerce these two values longs and 'and'.
+        try {
+            long l = arithmetic.toLong(left);
+            n = 1;
+            long r = arithmetic.toLong(right);
+        return new Long(l & r);
+    }
+        catch(RuntimeException xrt) {
+            throw new JexlException(node.jjtGetChild(n), "long coercion error", xrt);
+        }
+    }
+
+    /** {@inheritDoc} */
+    public Object visit(ASTBitwiseComplNode node, Object data) {
+        Object left = node.jjtGetChild(0).jjtAccept(this, data);
+        try {
+            long l = arithmetic.toLong(left);
+        return new Long(~l);
+    }
+        catch(RuntimeException xrt) {
+            throw new JexlException(node.jjtGetChild(0), "long coercion error", xrt);
+        }
+    }
+
+    /** {@inheritDoc} */
+    public Object visit(ASTBitwiseOrNode node, Object data) {
+        Object left = node.jjtGetChild(0).jjtAccept(this, data);
+        Object right = node.jjtGetChild(1).jjtAccept(this, data);
+        int n = 0;
+        // coerce these two values longs and 'or'.
+        try {
+            long l = arithmetic.toLong(left);
+            n = 1;
+            long r = arithmetic.toLong(right);
+        return new Long(l | r);
+    }
+        catch(RuntimeException xrt) {
+            throw new JexlException(node.jjtGetChild(n), "long coercion error", xrt);
+        }
+    }
+
+    /** {@inheritDoc} */
+    public Object visit(ASTBitwiseXorNode node, Object data) {
+        Object left = node.jjtGetChild(0).jjtAccept(this, data);
+        Object right = node.jjtGetChild(1).jjtAccept(this, data);
+        int n = 0;
+        // coerce these two values longs and 'xor'.
+        try {
+            long l = arithmetic.toLong(left);
+            n = 1;
+            long r = arithmetic.toLong(right);
+        return new Long(l ^ r);
+    }
+        catch(RuntimeException xrt) {
+            throw new JexlException(node.jjtGetChild(n), "long coercion error", xrt);
+        }
+    }
+
+    /** {@inheritDoc} */
+    public Object visit(ASTBlock node, Object data) {
+        int numChildren = node.jjtGetNumChildren();
+        Object result = null;
+        for (int i = 0; i < numChildren; i++) {
+            result = node.jjtGetChild(i).jjtAccept(this, data);
+        }
+        return result;
+    }
+
+    /** {@inheritDoc} */
+    public Object visit(ASTDivNode node, Object data) {
+        Object left = node.jjtGetChild(0).jjtAccept(this, data);
+        Object right = node.jjtGetChild(1).jjtAccept(this, data);
+        try {
+        return arithmetic.divide(left, right);
+    }
+        catch(RuntimeException xrt) {
+            throw new JexlException(node, "divide error", xrt);
+        }
+    }
+
+    /** {@inheritDoc} */
+    public Object visit(ASTEmptyFunction node, Object data) {
+
+        Object o = node.jjtGetChild(0).jjtAccept(this, data);
+
+        if (o == null) {
+            return Boolean.TRUE;
+        }
+
+        if (o instanceof String && "".equals(o)) {
+            return Boolean.TRUE;
+        }
+
+        if (o.getClass().isArray() && ((Object[]) o).length == 0) {
+            return Boolean.TRUE;
+        }
+
+        if (o instanceof Collection && ((Collection) o).isEmpty()) {
+            return Boolean.TRUE;
+        }
+
+        // Map isn't a collection
+        if (o instanceof Map && ((Map) o).isEmpty()) {
+            return Boolean.TRUE;
+        }
+
+        return Boolean.FALSE;
+    }
+
+    /** {@inheritDoc} */
+    public Object visit(ASTEQNode node, Object data) {
+        Object left = node.jjtGetChild(0).jjtAccept(this, data);
+        Object right = node.jjtGetChild(1).jjtAccept(this, data);
+        try {
+            return arithmetic.equals(left, right) ? Boolean.TRUE : Boolean.FALSE;
+        }
+        catch(RuntimeException xrt) {
+            throw new JexlException(node, "== error", xrt);
+        }
+    }
+
+    /** {@inheritDoc} */
+    public Object visit(ASTExpression node, Object data) {
+        return node.jjtGetChild(0).jjtAccept(this, data);
+    }
+
+    /** {@inheritDoc} */
+    public Object visit(ASTExpressionExpression node, Object data) {
+        return node.jjtGetChild(0).jjtAccept(this, data);
+    }
+
+    /** {@inheritDoc} */
+    public Object visit(ASTFalseNode node, Object data) {
+
+        return Boolean.FALSE;
+    }
+
+    /** {@inheritDoc} */
+    public Object visit(ASTFloatLiteral node, Object data) {
+
+        return Float.valueOf(node.image);
+    }
+
+    /** {@inheritDoc} */
+    public Object visit(ASTForeachStatement node, Object data) {
+        Object result = null;
+        /* first objectNode is the loop variable */
+        ASTReference loopReference = (ASTReference) node.jjtGetChild(0);
+        ASTIdentifier loopVariable = (ASTIdentifier) loopReference.jjtGetChild(0);
+        /* second objectNode is the variable to iterate */
+        Object iterableValue = node.jjtGetChild(1).jjtAccept(this, data);
+        // make sure there is a value to iterate on and a statement to execute
+        if (iterableValue != null && node.jjtGetNumChildren() >= 3) {
+            /* third objectNode is the statement to execute */
+            SimpleNode statement = (SimpleNode) node.jjtGetChild(2);
+            // get an iterator for the collection/array etc via the
+            // introspector.
+            Iterator itemsIterator = getUberspect().getIterator(iterableValue, DUMMY);
+            while (itemsIterator.hasNext()) {
+                // set loopVariable to value of iterator
+                Object value = itemsIterator.next();
+                context.getVars().put(loopVariable.image, value);
+                // execute statement
+                result = statement.jjtAccept(this, data);
+            }
+        }
+        return result;
+    }
+
+    /** {@inheritDoc} */
+    public Object visit(ASTGENode node, Object data) {
+        Object left = node.jjtGetChild(0).jjtAccept(this, data);
+        Object right = node.jjtGetChild(1).jjtAccept(this, data);
+        try {
+            return arithmetic.greaterThanOrEqual(left, right) ? Boolean.TRUE : Boolean.FALSE;
+        }
+        catch(RuntimeException xrt) {
+            throw new JexlException(node, ">= error", xrt);
+        }
+    }
+
+    /** {@inheritDoc} */
+    public Object visit(ASTGTNode node, Object data) {
+        Object left = node.jjtGetChild(0).jjtAccept(this, data);
+        Object right = node.jjtGetChild(1).jjtAccept(this, data);
+        try {
+            return arithmetic.greaterThan(left, right) ? Boolean.TRUE : Boolean.FALSE;
+        }
+        catch(RuntimeException xrt) {
+            throw new JexlException(node, "> error", xrt);
+        }
+    }
+
+    /** {@inheritDoc} */
+    public Object visit(ASTIdentifier node, Object data) {
+
+        String name = node.image;
+        if (data == null) {
+            return context.getVars().get(name);
+        } else {
+            return getAttribute(data, name, node);
+        }
+    }
+
+    /** {@inheritDoc} */
+    public Object visit(ASTIfStatement node, Object data) {
+        int n = 0;
+        try {
+        Object result = null;
+            /* first objectNode is the expression */
+        Object expression = node.jjtGetChild(0).jjtAccept(this, data);
+            if (arithmetic.toBoolean(expression)) {
+                // first objectNode is true statement
+                n = 1;
+            result = node.jjtGetChild(1).jjtAccept(this, data);
+        } else {
+            // if there is a false, execute it. false statement is the second
+                // objectNode
+            if (node.jjtGetNumChildren() == 3) {
+                    n = 2;
+                result = node.jjtGetChild(2).jjtAccept(this, data);
+            }
+        }
+        return result;
+    }
+        catch(JexlException error) {
+            throw error;
+        }
+        catch(RuntimeException xrt) {
+            throw new JexlException(node.jjtGetChild(n), "if error", xrt);
+        }
+    }
+
+    /** {@inheritDoc} */
+    public Object visit(ASTIntegerLiteral node, Object data) {
+        Integer value = Integer.valueOf(node.image);
+        if (data == null) {
+            return value;
+        } else {
+            return getAttribute(data, value);
+        }
+    }
+
+    /** {@inheritDoc} */
+    public Object visit(ASTJexlScript node, Object data) {
+
+        int numChildren = node.jjtGetNumChildren();
+        Object result = null;
+        for (int i = 0; i < numChildren; i++) {
+            Node child = node.jjtGetChild(i);
+            result = child.jjtAccept(this, data);
+        }
+        return result;
+    }
+
+    /** {@inheritDoc} */
+    public Object visit(ASTLENode node, Object data) {
+        Object left = node.jjtGetChild(0).jjtAccept(this, data);
+        Object right = node.jjtGetChild(1).jjtAccept(this, data);
+        try {
+            return arithmetic.lessThanOrEqual(left, right) ? Boolean.TRUE : Boolean.FALSE;
+        }
+        catch(RuntimeException xrt) {
+            throw new JexlException(node, "<= error", xrt);
+        }
+    }
+
+    /** {@inheritDoc} */
+    public Object visit(ASTLTNode node, Object data) {
+        Object left = node.jjtGetChild(0).jjtAccept(this, data);
+        Object right = node.jjtGetChild(1).jjtAccept(this, data);
+        try {
+            return arithmetic.lessThan(left, right) ? Boolean.TRUE : Boolean.FALSE;
+        }
+        catch(RuntimeException xrt) {
+            throw new JexlException(node, "< error", xrt);
+        }
+    }
+
+    /** {@inheritDoc} */
+    public Object visit(ASTMapEntry node, Object data) {
+        Object key = node.jjtGetChild(0).jjtAccept(this, data);
+        Object value = node.jjtGetChild(1).jjtAccept(this, data);
+        return new Object[] {key, value};
+    }
+
+    /** {@inheritDoc} */
+    public Object visit(ASTMapLiteral node, Object data) {
+
+        int childCount = node.jjtGetNumChildren();
+        Map map = new HashMap();
+
+        for (int i = 0; i < childCount; i++) {
+            Object[] entry = (Object[]) (node.jjtGetChild(i)).jjtAccept(this, data);
+            map.put(entry[0], entry[1]);
+        }
+
+        return map;
+    }
+
+    /** {@inheritDoc} */
+    public Object visit(ASTMethod node, Object data) {
+        // objectNode 0 is the identifier (method name), the others are parameters.
+        // the object to invoke the method on should be in the data argument
+        String methodName = ((ASTIdentifier) node.jjtGetChild(0)).image;
+
+        // get our params
+        int paramCount = node.jjtGetNumChildren() - 1;
+        Object[] params = new Object[paramCount];
+            for (int i = 0; i < paramCount; i++) {
+                params[i] = node.jjtGetChild(i + 1).jjtAccept(this, null);
+            }
+
+        try {
+            VelMethod vm = getUberspect().getMethod(data, methodName, params, DUMMY);
+            // DG: If we can't find an exact match, narrow the parameters and
+            // try again!
+            if (vm == null) {
+
+                // replace all numbers with the smallest type that will fit
+                for (int i = 0; i < params.length; i++) {
+                    Object param = params[i];
+                    if (param instanceof Number) {
+                        params[i] = arithmetic.narrow((Number) param);
+                    }
+                }
+                vm = getUberspect().getMethod(data, methodName, params, DUMMY);
+                if (vm == null) {
+                    return null;
+                }
+            }
+
+            return vm.invoke(data, params);
+        } catch (InvocationTargetException e) {
+            Throwable t = e.getTargetException();
+            if (!(t instanceof Exception)) {
+                t = e;
+            }
+            throw new JexlException(node, "method invocation error", t); 
+        } catch (Exception e) {
+            throw new JexlException(node, "method error", e); 
+        }
+    }
+
+    /** {@inheritDoc} */
+    public Object visit(ASTModNode node, Object data) {
+        Object left = node.jjtGetChild(0).jjtAccept(this, data);
+        Object right = node.jjtGetChild(1).jjtAccept(this, data);
+        try {
+        return arithmetic.mod(left, right);
+    }
+        catch(RuntimeException xrt) {
+            throw new JexlException(node, "% error", xrt);
+        }
+    }
+
+    /** {@inheritDoc} */
+    public Object visit(ASTMulNode node, Object data) {
+        Object left = node.jjtGetChild(0).jjtAccept(this, data);
+        Object right = node.jjtGetChild(1).jjtAccept(this, data);
+        try {
+        return arithmetic.multiply(left, right);
+    }
+        catch(RuntimeException xrt) {
+            throw new JexlException(node, "* error", xrt);
+        }
+    }
+
+    /** {@inheritDoc} */
+    public Object visit(ASTNENode node, Object data) {
+        Object left = node.jjtGetChild(0).jjtAccept(this, data);
+        Object right = node.jjtGetChild(1).jjtAccept(this, data);
+        try {
+            return arithmetic.equals(left, right) ? Boolean.FALSE : Boolean.TRUE;
+        }
+        catch(RuntimeException xrt) {
+            throw new JexlException(node, "!= error", xrt);
+        }
+    }
+
+    /** {@inheritDoc} */
+    public Object visit(ASTNotNode node, Object data) {
+        Object val = node.jjtGetChild(0).jjtAccept(this, data);
+        return arithmetic.toBoolean(val) ? Boolean.FALSE : Boolean.TRUE;
+    }
+
+    /** {@inheritDoc} */
+    public Object visit(ASTNullLiteral node, Object data) {
+        return null;
+    }
+
+    /** {@inheritDoc} */
+    public Object visit(ASTOrNode node, Object data) {
+        Object left = node.jjtGetChild(0).jjtAccept(this, data);
+        try {
+            boolean leftValue = arithmetic.toBoolean(left);
+            if (leftValue)
+                return Boolean.TRUE;
+        }
+        catch(RuntimeException xrt) {
+            throw new JexlException(node.jjtGetChild(0), "boolean coercion error", xrt);
+        }
+        Object right = node.jjtGetChild(1).jjtAccept(this, data);
+        try {
+            boolean rightValue = arithmetic.toBoolean(right);
+            if (rightValue)
+                return Boolean.TRUE;
+        }
+        catch(RuntimeException xrt) {
+            throw new JexlException(node.jjtGetChild(1), "boolean coercion error", xrt);
+        }
+        return Boolean.FALSE;
+    }
+
+    /** {@inheritDoc} */
+    public Object visit(ASTReference node, Object data) {
+        // could be array access, identifier or map literal
+        // followed by zero or more ("." and array access, method, size,
+        // identifier or integer literal)
+
+        int numChildren = node.jjtGetNumChildren();
+
+        // pass first piece of data in and loop through children
+        Object result = null;
+        StringBuilder variableName = null;
+        boolean isVariable = true;
+        for (int i = 0; i < numChildren; i++) {
+            Node theNode = node.jjtGetChild(i);
+            isVariable &= (theNode instanceof ASTIdentifier);
+            result = theNode.jjtAccept(this, result);
+            // if we get null back a result, check for an ant variable
+            if (result == null && isVariable) {
+                String name = ((ASTIdentifier) theNode).image;
+                if (i == 0)
+                    variableName = new StringBuilder(name);
+                else {
+                    variableName.append('.');
+                variableName.append(name);
+                }
+                result = context.getVars().get(variableName.toString());
+            }
+        }
+
+        return result;
+    }
+
+    /** {@inheritDoc} */
+    public Object visit(ASTReferenceExpression node, Object data) {
+
+        return node.jjtGetChild(0).jjtAccept(this, data);
+    }
+
+    /** {@inheritDoc} */
+    public Object visit(ASTSizeFunction node, Object data) {
+        Object val = node.jjtGetChild(0).jjtAccept(this, data);
+
+        if (val == null) {
+            throw new JexlException(node, "size() : argument is null", null);
+        }
+
+        return new Integer(sizeOf(node, val));
+    }
+
+    /** {@inheritDoc} */
+    public Object visit(ASTSizeMethod node, Object data) {
+        return new Integer(sizeOf(node, data));
+    }
+
+    /** {@inheritDoc} */
+    public Object visit(ASTStatementExpression node, Object data) {
+        return node.jjtGetChild(0).jjtAccept(this, data);
+    }
+
+    /** {@inheritDoc} */
+    public Object visit(ASTStringLiteral node, Object data) {
+        return node.image;
+    }
+
+    /** {@inheritDoc} */
+    public Object visit(ASTSubtractNode node, Object data) {
+        Object left = node.jjtGetChild(0).jjtAccept(this, data);
+        Object right = node.jjtGetChild(1).jjtAccept(this, data);
+        try {
+        return arithmetic.subtract(left, right);
+    }
+        catch(RuntimeException xrt) {
+            throw new JexlException(node, "- error", xrt);
+        }
+    }
+    
+    /** {@inheritDoc} */
+    public Object visit(ASTTernaryNode node, Object data) {
+        Object condition = node.jjtGetChild(0).jjtAccept(this, data);
+        if (node.jjtGetNumChildren() == 3)
+            return arithmetic.toBoolean(condition)?
+                node.jjtGetChild(1).jjtAccept(this, data) :
+                node.jjtGetChild(2).jjtAccept(this, data);
+        return condition != null && !Boolean.FALSE.equals(condition)? condition : node.jjtGetChild(1).jjtAccept(this, data);
+    }
+
+    /** {@inheritDoc} */
+    public Object visit(ASTTrueNode node, Object data) {
+        return Boolean.TRUE;
+    }
+
+    /** {@inheritDoc} */
+    public Object visit(ASTUnaryMinusNode node, Object data) {
+        Node valNode = node.jjtGetChild(0);
+        Object val = valNode.jjtAccept(this, data);
+
+        if (val instanceof Byte) {
+            byte valueAsByte = ((Byte) val).byteValue();
+            return new Byte((byte) -valueAsByte);
+        } else if (val instanceof Short) {
+            short valueAsShort = ((Short) val).shortValue();
+            return new Short((short) -valueAsShort);
+        } else if (val instanceof Integer) {
+            int valueAsInt = ((Integer) val).intValue();
+            return new Integer(-valueAsInt);
+        } else if (val instanceof Long) {
+            long valueAsLong = ((Long) val).longValue();
+            return new Long(-valueAsLong);
+        } else if (val instanceof Float) {
+            float valueAsFloat = ((Float) val).floatValue();
+            return new Float(-valueAsFloat);
+        } else if (val instanceof Double) {
+            double valueAsDouble = ((Double) val).doubleValue();
+            return new Double(-valueAsDouble);
+        } else if (val instanceof BigDecimal) {
+            BigDecimal valueAsBigD = (BigDecimal) val;
+            return valueAsBigD.negate();
+        } else if (val instanceof BigInteger) {
+            BigInteger valueAsBigI = (BigInteger) val;
+            return valueAsBigI.negate();
+        }
+        throw new JexlException(valNode, "not a number");
+    }
+
+    /** {@inheritDoc} */
+    public Object visit(ASTWhileStatement node, Object data) {
+        Object result = null;
+        /* first objectNode is the expression */
+        Node expressionNode = (Node) node.jjtGetChild(0);
+        while (arithmetic.toBoolean(expressionNode.jjtAccept(this, data))) {
+            // execute statement
+            result = node.jjtGetChild(1).jjtAccept(this, data);
+        }
+
+        return result;
+    }
+
+    /**
+     * Calculate the <code>size</code> of various types: Collection, Array,
+     * Map, String, and anything that has a int size() method.
+     *
+     * @param val the object to get the size of.
+     * @return the size of val
+     */
+    private int sizeOf(Node node, Object val) {
+        if (val instanceof Collection) {
+            return ((Collection) val).size();
+        } else if (val.getClass().isArray()) {
+            return Array.getLength(val);
+        } else if (val instanceof Map) {
+            return ((Map) val).size();
+        } else if (val instanceof String) {
+            return ((String) val).length();
+        } else {
+            // check if there is a size method on the object that returns an
+            // integer and if so, just use it
+            Object[] params = new Object[0];
+            VelMethod vm = uberspect.getMethod(val, "size", EMPTY_PARAMS, DUMMY);
+            if (vm != null && vm.getReturnType() == Integer.TYPE) {
+                Integer result;
+                try {
+                    result = (Integer) vm.invoke(val, params);
+                } catch (Exception e) {
+                    throw new JexlException(node, "size() : error executing", e);
+                }
+                return result.intValue();
+            }
+            throw new JexlException(node, "size() : unsupported type : " + val.getClass(), null);
+        }
+    }
+
+    /**
+     * Get an attribute of an object.
+     *
+     * @param object to retrieve value from
+     * @param attribute the attribute of the object, e.g. an index (1, 0, 2) or
+     *            key for a map
+     * @return the attribute.
+     */
+    public Object getAttribute(Object object, Object attribute) {
+        return getAttribute(object, attribute, null);
+    }
+
+    protected Object getAttribute(Object object, Object attribute, Node node) {
+        if (object == null) {
+            return null;
+        }
+        // maps do accept null keys; check attribute null status after trying
+        if (object instanceof Map) {
+            try {
+                return ((Map) object).get(attribute);
+            } catch (RuntimeException xrt) {
+                throw node == null ? xrt : new JexlException(node, "get map element error", xrt);
+            }
+        }
+        if (attribute == null) {
+            return null;
+        }
+        if (object instanceof List) {
+            try {
+                int idx = arithmetic.toInteger(attribute);
+                return ((List) object).get(idx);
+            } catch (RuntimeException xrt) {
+                throw node == null ? xrt : new JexlException(node, "get list element error", xrt);
+            }
+        }
+        if (object.getClass().isArray()) {
+            try {
+                int idx = arithmetic.toInteger(attribute);
+                return Array.get(object, idx);
+            } catch (RuntimeException xrt) {
+                throw node == null ? xrt : new JexlException(node, "get array element error", xrt);
+            }
+            }
+            // look up bean property of data and return
+            VelPropertyGet vg = getUberspect().getPropertyGet(object, attribute.toString(), DUMMY);
+            if (vg != null) {
+                try {
+                    return vg.invoke(object);
+            } catch (Exception xany) {
+                throw node == null ? new RuntimeException(xany) : new JexlException(node, "get object property error", xany);
+                }
+            }
+
+        return null;
+    }
+
+    /**
+     * Sets an attribute of an object.
+     *
+     * @param object to set the value to
+     * @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);
+        }
+
+    protected void setAttribute(Object object, Object attribute, Object value, Node node) {
+        if (object instanceof JexlContext) {
+            ((JexlContext) object).getVars().put(attribute, value);
+            return;
+    }
+
+        if (object instanceof Map) {
+            try {
+                ((Map) object).put(attribute, value);
+                return;
+            } catch (RuntimeException xrt) {
+                throw node == null ? xrt : new JexlException(node, "set map element error", xrt);
+    }
+        }
+        if (object instanceof List) {
+            try {
+                int idx = arithmetic.toInteger(attribute);
+                ((List) object).set(idx, value);
+                return;
+            } catch (RuntimeException xrt) {
+                throw node == null ? xrt : new JexlException(node, "set list element error", xrt);
+            }
+    }
+
+        if (object.getClass().isArray()) {
+            try {
+                int idx = arithmetic.toInteger(attribute);
+                Array.set(object, idx, value);
+                return;
+            } catch (RuntimeException xrt) {
+                throw node == null ? xrt : new JexlException(node, "set array element error", xrt);
+        }
+    }
+
+        // "Otherwise (a JavaBean object)..." huh? :)
+        String s = attribute.toString();
+        VelPropertySet vs = getUberspect().getPropertySet(object, s, value, DUMMY);
+        if (vs != null) {
+            try {
+                vs.invoke(object, value);
+            } catch (RuntimeException xrt) {
+                throw node == null ? xrt : new JexlException(node, "set object property error", xrt);
+            } catch (Exception xany) {
+                throw node == null ? new RuntimeException(xany) : new JexlException(node, "set object property error", xany);
+            }
+            return;
+        }
+        if (node == null)
+            new UnsupportedOperationException("unable to set object property, object:" + object + ", property: " + attribute);
+        throw new JexlException(node, "unable to set bean property", null);
+    }
+
+
 }
\ No newline at end of file