Modified: 
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java
URL: 
http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java?rev=1692852&r1=1692851&r2=1692852&view=diff
==============================================================================
--- 
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java
 (original)
+++ 
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java
 Mon Jul 27 10:02:49 2015
@@ -17,7 +17,7 @@
 package org.apache.commons.jexl3.internal;
 
 import org.apache.commons.jexl3.JexlArithmetic;
-import static org.apache.commons.jexl3.JexlArithmetic.Operator;
+import org.apache.commons.jexl3.JexlOperator;
 import org.apache.commons.jexl3.JexlContext;
 import org.apache.commons.jexl3.JexlEngine;
 import org.apache.commons.jexl3.JexlException;
@@ -77,7 +77,15 @@ import org.apache.commons.jexl3.parser.A
 import org.apache.commons.jexl3.parser.ASTReferenceExpression;
 import org.apache.commons.jexl3.parser.ASTReturnStatement;
 import org.apache.commons.jexl3.parser.ASTSWNode;
+import org.apache.commons.jexl3.parser.ASTSetAddNode;
+import org.apache.commons.jexl3.parser.ASTSetAndNode;
+import org.apache.commons.jexl3.parser.ASTSetDivNode;
 import org.apache.commons.jexl3.parser.ASTSetLiteral;
+import org.apache.commons.jexl3.parser.ASTSetModNode;
+import org.apache.commons.jexl3.parser.ASTSetMultNode;
+import org.apache.commons.jexl3.parser.ASTSetOrNode;
+import org.apache.commons.jexl3.parser.ASTSetSubNode;
+import org.apache.commons.jexl3.parser.ASTSetXorNode;
 import org.apache.commons.jexl3.parser.ASTSizeFunction;
 import org.apache.commons.jexl3.parser.ASTSizeMethod;
 import org.apache.commons.jexl3.parser.ASTStringLiteral;
@@ -90,16 +98,10 @@ import org.apache.commons.jexl3.parser.A
 import org.apache.commons.jexl3.parser.JexlNode;
 import org.apache.commons.jexl3.parser.Node;
 import org.apache.commons.jexl3.parser.ParserVisitor;
-
-import org.apache.log4j.Logger;
-
-import java.lang.reflect.Array;
-import java.lang.reflect.InvocationTargetException;
-
-import java.util.Collection;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.Map;
+import org.apache.log4j.Logger;
 
 /**
  * An interpreter of JEXL syntax.
@@ -115,8 +117,8 @@ public class Interpreter extends ParserV
     protected final JexlUberspect uberspect;
     /** The arithmetic handler. */
     protected final JexlArithmetic arithmetic;
-    /** The overloaded arithmetic operators. */
-    protected final JexlArithmetic.Uberspect operators;
+    /** The operators evaluation delegate. */
+    protected final Operators operators;
     /** The map of symboled functions. */
     protected final Map<String, Object> functions;
     /** The map of symboled functions. */
@@ -125,7 +127,7 @@ public class Interpreter extends ParserV
     protected final JexlContext context;
     /** The context to store/retrieve variables. */
     protected final JexlContext.NamespaceResolver ns;
-    /** Strict interpreter flag (may temporarily change during when calling 
size and empty as functions). */
+    /** Strict interpreter flag (may temporarily change when calling size and 
empty as functions). */
     protected boolean strictEngine;
     /** Strict interpreter flag. */
     protected final boolean strictArithmetic;
@@ -170,10 +172,10 @@ public class Interpreter extends ParserV
         }
         this.functions = jexl.functions;
         this.strictArithmetic = this.arithmetic.isStrict();
-        this.operators = uberspect.getArithmetic(arithmetic);
         this.cache = jexl.cache != null;
         this.frame = eFrame;
         this.functors = null;
+        this.operators = new Operators(this);
     }
 
     /**
@@ -222,7 +224,6 @@ public class Interpreter extends ParserV
 
     /** Java7 AutoCloseable interface defined?. */
     private static final Class<?> AUTOCLOSEABLE;
-
     static {
         Class<?> c;
         try {
@@ -310,7 +311,7 @@ public class Interpreter extends ParserV
      * @param cause    the cause of error (if any)
      * @throws JexlException if isStrict
      */
-    protected void operatorError(JexlNode node, JexlArithmetic.Operator 
operator, Throwable cause) {
+    protected void operatorError(JexlNode node, JexlOperator operator, 
Throwable cause) {
         if (cause != null) {
             if (strictEngine) {
                 throw new JexlException.Operator(node, 
operator.getOperatorSymbol(), cause);
@@ -400,136 +401,12 @@ public class Interpreter extends ParserV
         }
     }
 
-    /**
-     * Attempts to call a monadic operator.
-     * <p>
-     * This takes care of finding and caching the operator method when 
appropriate
-     * @param node     the syntactic node
-     * @param operator the operator
-     * @param arg      the argument
-     * @return the result of the operator evaluation or TRY_FAILED
-     */
-    protected Object callOperator(JexlNode node, Operator operator, Object 
arg) {
-        if (operators != null && operators.overloads(operator)) {
-            if (cache) {
-                Object cached = node.jjtGetValue();
-                if (cached instanceof JexlMethod) {
-                    JexlMethod me = (JexlMethod) cached;
-                    Object eval = me.tryInvoke(operator.getMethodName(), 
arithmetic, arg);
-                    if (!me.tryFailed(eval)) {
-                        return eval;
-                    }
-                }
-            }
-            try {
-                JexlMethod emptym = operators.getOperator(operator, arg);
-                if (emptym != null) {
-                    Object result = emptym.invoke(arithmetic, arg);
-                    if (cache) {
-                        node.jjtSetValue(emptym);
-                    }
-                    return result;
-                }
-            } catch (Exception xany) {
-                operatorError(node, operator, xany);
-            }
-        }
-        return JexlEngine.TRY_FAILED;
-    }
-
-    /**
-     * Attempts to call a diadic operator.
-     * <p>
-     * This takes care of finding and caching the operator method when 
appropriate
-     * @param node     the syntactic node
-     * @param operator the operator
-     * @param lhs      the left hand side argument
-     * @param rhs      the right hand side argument
-     * @return the result of the operator evaluation or TRY_FAILED
-     */
-    protected Object callOperator(JexlNode node, Operator operator, Object 
lhs, Object rhs) {
-        if (operators != null && operators.overloads(operator)) {
-            if (cache) {
-                Object cached = node.jjtGetValue();
-                if (cached instanceof JexlMethod) {
-                    JexlMethod me = (JexlMethod) cached;
-                    Object eval = me.tryInvoke(operator.getMethodName(), 
arithmetic, lhs, rhs);
-                    if (!me.tryFailed(eval)) {
-                        return eval;
-                    }
-                }
-            }
-            try {
-                JexlMethod emptym = operators.getOperator(operator, lhs, rhs);
-                if (emptym != null) {
-                    Object result = emptym.invoke(arithmetic, lhs, rhs);
-                    if (cache) {
-                        node.jjtSetValue(emptym);
-                    }
-                    return result;
-                }
-            } catch (Exception xany) {
-                operatorError(node, operator, xany);
-            }
-        }
-        return JexlEngine.TRY_FAILED;
-    }
-
-    @Override
-    protected Object visit(ASTAndNode 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);
-        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 (ArithmeticException xrt) {
-            throw new JexlException(node.jjtGetChild(1), "boolean coercion 
error", xrt);
-        }
-        return Boolean.TRUE;
-    }
-
-    @Override
-    protected Object visit(ASTArrayLiteral node, Object data) {
-        int childCount = node.jjtGetNumChildren();
-        JexlArithmetic.ArrayBuilder ab = arithmetic.arrayBuilder(childCount);
-        if (ab != null) {
-            boolean extended = false;
-            for (int i = 0; i < childCount; i++) {
-                JexlNode child = node.jjtGetChild(i);
-                if (child instanceof ASTExtendedLiteral) {
-                    extended = true;
-                } else {
-                    Object entry = node.jjtGetChild(i).jjtAccept(this, data);
-                    ab.add(entry);
-                }
-            }
-            return ab.create(extended);
-        } else {
-            return null;
-        }
-    }
-
     @Override
     protected Object visit(ASTAddNode node, Object data) {
         Object left = node.jjtGetChild(0).jjtAccept(this, data);
         Object right = node.jjtGetChild(1).jjtAccept(this, data);
         try {
-            Object result = callOperator(node, Operator.ADD, left, right);
+            Object result = operators.tryOverload(node, JexlOperator.ADD, 
left, right);
             return result != JexlEngine.TRY_FAILED ? result : 
arithmetic.add(left, right);
         } catch (ArithmeticException xrt) {
             throw new JexlException(node, "+ error", xrt);
@@ -541,7 +418,7 @@ public class Interpreter extends ParserV
         Object left = node.jjtGetChild(0).jjtAccept(this, data);
         Object right = node.jjtGetChild(1).jjtAccept(this, data);
         try {
-            Object result = callOperator(node, Operator.SUBTRACT, left, right);
+            Object result = operators.tryOverload(node, JexlOperator.SUBTRACT, 
left, right);
             return result != JexlEngine.TRY_FAILED ? result : 
arithmetic.subtract(left, right);
         } catch (ArithmeticException xrt) {
             throw new JexlException(node, "- error", xrt);
@@ -549,75 +426,83 @@ public class Interpreter extends ParserV
     }
 
     @Override
-    protected Object visit(ASTBitwiseAndNode node, Object data) {
+    protected Object visit(ASTMulNode node, Object data) {
         Object left = node.jjtGetChild(0).jjtAccept(this, data);
         Object right = node.jjtGetChild(1).jjtAccept(this, data);
         try {
-            Object result = callOperator(node, Operator.AND, left, right);
-            return result != JexlEngine.TRY_FAILED ? result : 
arithmetic.bitwiseAnd(left, right);
+            Object result = operators.tryOverload(node, JexlOperator.MULTIPLY, 
left, right);
+            return result != JexlEngine.TRY_FAILED ? result : 
arithmetic.multiply(left, right);
         } catch (ArithmeticException xrt) {
-            throw new JexlException(node, "& error", xrt);
+            JexlNode xnode = findNullOperand(xrt, node, left, right);
+            throw new JexlException(xnode, "* error", xrt);
         }
     }
 
     @Override
-    protected Object visit(ASTBitwiseComplNode node, Object data) {
-        Object arg = node.jjtGetChild(0).jjtAccept(this, data);
+    protected Object visit(ASTDivNode node, Object data) {
+        Object left = node.jjtGetChild(0).jjtAccept(this, data);
+        Object right = node.jjtGetChild(1).jjtAccept(this, data);
         try {
-            Object result = callOperator(node, Operator.COMPLEMENT, arg);
-            return result != JexlEngine.TRY_FAILED ? result : 
arithmetic.bitwiseComplement(arg);
+            Object result = operators.tryOverload(node, JexlOperator.DIVIDE, 
left, right);
+            return result != JexlEngine.TRY_FAILED ? result : 
arithmetic.divide(left, right);
         } catch (ArithmeticException xrt) {
-            throw new JexlException(node, "~ error", xrt);
+            if (!strictArithmetic) {
+                return 0.0d;
+            }
+            JexlNode xnode = findNullOperand(xrt, node, left, right);
+            throw new JexlException(xnode, "/ error", xrt);
         }
     }
 
     @Override
-    protected Object visit(ASTBitwiseOrNode node, Object data) {
+    protected Object visit(ASTModNode node, Object data) {
         Object left = node.jjtGetChild(0).jjtAccept(this, data);
         Object right = node.jjtGetChild(1).jjtAccept(this, data);
         try {
-            Object result = callOperator(node, Operator.OR, left, right);
-            return result != JexlEngine.TRY_FAILED ? result : 
arithmetic.bitwiseOr(left, right);
+            Object result = operators.tryOverload(node, JexlOperator.MOD, 
left, right);
+            return result != JexlEngine.TRY_FAILED ? result : 
arithmetic.mod(left, right);
         } catch (ArithmeticException xrt) {
-            throw new JexlException(node, "| error", xrt);
+            if (!strictArithmetic) {
+                return 0.0d;
+            }
+            JexlNode xnode = findNullOperand(xrt, node, left, right);
+            throw new JexlException(xnode, "% error", xrt);
         }
     }
 
     @Override
-    protected Object visit(ASTBitwiseXorNode node, Object data) {
+    protected Object visit(ASTBitwiseAndNode node, Object data) {
         Object left = node.jjtGetChild(0).jjtAccept(this, data);
         Object right = node.jjtGetChild(1).jjtAccept(this, data);
         try {
-            Object result = callOperator(node, Operator.XOR, left, right);
-            return result != JexlEngine.TRY_FAILED ? result : 
arithmetic.bitwiseXor(left, right);
+            Object result = operators.tryOverload(node, JexlOperator.AND, 
left, right);
+            return result != JexlEngine.TRY_FAILED ? result : 
arithmetic.and(left, right);
         } catch (ArithmeticException xrt) {
-            throw new JexlException(node, "^ error", xrt);
+            throw new JexlException(node, "& error", xrt);
         }
     }
 
     @Override
-    protected 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);
+    protected Object visit(ASTBitwiseOrNode node, Object data) {
+        Object left = node.jjtGetChild(0).jjtAccept(this, data);
+        Object right = node.jjtGetChild(1).jjtAccept(this, data);
+        try {
+            Object result = operators.tryOverload(node, JexlOperator.OR, left, 
right);
+            return result != JexlEngine.TRY_FAILED ? result : 
arithmetic.or(left, right);
+        } catch (ArithmeticException xrt) {
+            throw new JexlException(node, "| error", xrt);
         }
-        return result;
     }
 
     @Override
-    protected Object visit(ASTDivNode node, Object data) {
+    protected Object visit(ASTBitwiseXorNode node, Object data) {
         Object left = node.jjtGetChild(0).jjtAccept(this, data);
         Object right = node.jjtGetChild(1).jjtAccept(this, data);
         try {
-            Object result = callOperator(node, Operator.DIVIDE, left, right);
-            return result != JexlEngine.TRY_FAILED ? result : 
arithmetic.divide(left, right);
+            Object result = operators.tryOverload(node, JexlOperator.XOR, 
left, right);
+            return result != JexlEngine.TRY_FAILED ? result : 
arithmetic.xor(left, right);
         } catch (ArithmeticException xrt) {
-            if (!strictArithmetic) {
-                return 0.0d;
-            }
-            JexlNode xnode = findNullOperand(xrt, node, left, right);
-            throw new JexlException(xnode, "divide error", xrt);
+            throw new JexlException(node, "^ error", xrt);
         }
     }
 
@@ -626,7 +511,7 @@ public class Interpreter extends ParserV
         Object left = node.jjtGetChild(0).jjtAccept(this, data);
         Object right = node.jjtGetChild(1).jjtAccept(this, data);
         try {
-            Object result = callOperator(node, Operator.EQ, left, right);
+            Object result = operators.tryOverload(node, JexlOperator.EQ, left, 
right);
             return result != JexlEngine.TRY_FAILED
                    ? result
                    : arithmetic.equals(left, right) ? Boolean.TRUE : 
Boolean.FALSE;
@@ -636,60 +521,18 @@ public class Interpreter extends ParserV
     }
 
     @Override
-    protected Object visit(ASTFalseNode node, Object data) {
-        return Boolean.FALSE;
-    }
-
-    @Override
-    protected Object visit(ASTContinue node, Object data) {
-        throw new JexlException.Continue(node);
-    }
-
-    @Override
-    protected Object visit(ASTBreak node, Object data) {
-        throw new JexlException.Break(node);
-    }
-
-    @Override
-    protected 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);
-        int symbol = loopVariable.getSymbol();
-        /* 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 */
-            JexlNode statement = node.jjtGetChild(2);
-            // get an iterator for the collection/array etc via the
-            // introspector.
-            Iterator<?> itemsIterator = uberspect.getIterator(iterableValue);
-            if (itemsIterator != null) {
-                while (itemsIterator.hasNext()) {
-                    if (isCancelled()) {
-                        throw new JexlException.Cancel(node);
-                    }
-                    // set loopVariable to value of iterator
-                    Object value = itemsIterator.next();
-                    if (symbol < 0) {
-                        context.set(loopVariable.getName(), value);
-                    } else {
-                        frame.set(symbol, value);
-                    }
-                    try {
-                        // execute statement
-                        result = statement.jjtAccept(this, data);
-                    } catch (JexlException.Break stmtBreak) {
-                        break;
-                    } catch (JexlException.Continue stmtContinue) {
-                        //continue;
-                    }
-                }
-            }
+    protected Object visit(ASTNENode node, Object data) {
+        Object left = node.jjtGetChild(0).jjtAccept(this, data);
+        Object right = node.jjtGetChild(1).jjtAccept(this, data);
+        try {
+            Object result = operators.tryOverload(node, JexlOperator.EQ, left, 
right);
+            return result != JexlEngine.TRY_FAILED
+                   ? arithmetic.toBoolean(result) ? Boolean.FALSE : 
Boolean.TRUE
+                   : arithmetic.equals(left, right) ? Boolean.FALSE : 
Boolean.TRUE;
+        } catch (ArithmeticException xrt) {
+            JexlNode xnode = findNullOperand(xrt, node, left, right);
+            throw new JexlException(xnode, "!= error", xrt);
         }
-        return result;
     }
 
     @Override
@@ -697,7 +540,7 @@ public class Interpreter extends ParserV
         Object left = node.jjtGetChild(0).jjtAccept(this, data);
         Object right = node.jjtGetChild(1).jjtAccept(this, data);
         try {
-            Object result = callOperator(node, Operator.GTE, left, right);
+            Object result = operators.tryOverload(node, JexlOperator.GTE, 
left, right);
             return result != JexlEngine.TRY_FAILED
                    ? result
                    : arithmetic.greaterThanOrEqual(left, right) ? Boolean.TRUE 
: Boolean.FALSE;
@@ -711,7 +554,7 @@ public class Interpreter extends ParserV
         Object left = node.jjtGetChild(0).jjtAccept(this, data);
         Object right = node.jjtGetChild(1).jjtAccept(this, data);
         try {
-            Object result = callOperator(node, Operator.GT, left, right);
+            Object result = operators.tryOverload(node, JexlOperator.GT, left, 
right);
             return result != JexlEngine.TRY_FAILED
                    ? result
                    : arithmetic.greaterThan(left, right) ? Boolean.TRUE : 
Boolean.FALSE;
@@ -720,368 +563,272 @@ public class Interpreter extends ParserV
         }
     }
 
-    /**
-     * The 'startsWith' operator implementation.
-     * @param node     the node
-     * @param operator the calling operator, $= or $!
-     * @param left     the left operand
-     * @param right    the right operand
-     * @return true if left starts with right, false otherwise
-     */
-    protected boolean startsWith(JexlNode node, String operator, Object left, 
Object right) {
+    @Override
+    protected Object visit(ASTLENode node, Object data) {
+        Object left = node.jjtGetChild(0).jjtAccept(this, data);
+        Object right = node.jjtGetChild(1).jjtAccept(this, data);
         try {
-            // try operator overload
-            Object result = callOperator(node, Operator.STARTSWITH, left, 
right);
-            if (result instanceof Boolean) {
-                return (Boolean) result;
-            }
-            // use arithmetic / pattern matching ?
-            Boolean matched = arithmetic.startsWith(left, right);
-            if (matched != null) {
-                return matched;
-            }
-            // try a startsWith method (duck type)
-            try {
-                Object[] argv = {right};
-                JexlMethod vm = uberspect.getMethod(left, "startsWith", argv);
-                if (vm != null && vm.getReturnType() == Boolean.TYPE) {
-                    return (Boolean) vm.invoke(left, argv);
-                }
-                if (arithmetic.narrowArguments(argv)) {
-                    vm = uberspect.getMethod(left, "startsWith", argv);
-                    if (vm != null && vm.getReturnType() == Boolean.TYPE) {
-                        return (Boolean) vm.invoke(left, argv);
-                    }
-                }
-            } catch (InvocationTargetException e) {
-                throw new JexlException(node, operator + " invocation error", 
e.getCause());
-            } catch (Exception e) {
-                throw new JexlException(node, operator + " error", e);
-            }
-            // defaults to equal
-            return arithmetic.equals(left, right) ? Boolean.TRUE : 
Boolean.FALSE;
+            Object result = operators.tryOverload(node, JexlOperator.LTE, 
left, right);
+            return result != JexlEngine.TRY_FAILED
+                   ? result
+                   : arithmetic.lessThanOrEqual(left, right) ? Boolean.TRUE : 
Boolean.FALSE;
         } catch (ArithmeticException xrt) {
-            throw new JexlException(node, operator + " error", xrt);
+            throw new JexlException(node, "<= error", xrt);
         }
     }
 
     @Override
-    protected Object visit(ASTSWNode node, Object data) {
+    protected Object visit(ASTLTNode node, Object data) {
         Object left = node.jjtGetChild(0).jjtAccept(this, data);
         Object right = node.jjtGetChild(1).jjtAccept(this, data);
-        return startsWith(node, "^=", left, right) ? Boolean.TRUE : 
Boolean.FALSE;
+        try {
+            Object result = operators.tryOverload(node, JexlOperator.LT, left, 
right);
+            return result != JexlEngine.TRY_FAILED
+                   ? result
+                   : arithmetic.lessThan(left, right) ? Boolean.TRUE : 
Boolean.FALSE;
+        } catch (ArithmeticException xrt) {
+            throw new JexlException(node, "< error", xrt);
+        }
     }
 
     @Override
-    protected Object visit(ASTNSWNode node, Object data) {
+    protected Object visit(ASTSWNode node, Object data) {
         Object left = node.jjtGetChild(0).jjtAccept(this, data);
         Object right = node.jjtGetChild(1).jjtAccept(this, data);
-        return startsWith(node, "^!", left, right) ? Boolean.FALSE : 
Boolean.TRUE;
+        return operators.startsWith(node, "^=", left, right) ? Boolean.TRUE : 
Boolean.FALSE;
     }
 
-    /**
-     * The 'endsWith' operator implementation.
-     * @param node     the node
-     * @param operator the calling operator, ^= or ^!
-     * @param left     the left operand
-     * @param right    the right operand
-     * @return true if left ends with right, false otherwise
-     */
-    protected boolean endsWith(JexlNode node, String operator, Object left, 
Object right) {
-        try {
-            // try operator overload
-            Object result = callOperator(node, Operator.ENDSWITH, left, right);
-            if (result instanceof Boolean) {
-                return (Boolean) result;
-            }
-            // use arithmetic / pattern matching ?
-            Boolean matched = arithmetic.endsWith(left, right);
-            if (matched != null) {
-                return matched;
-            }
-            // try a endsWith method (duck type)
-            try {
-                Object[] argv = {right};
-                JexlMethod vm = uberspect.getMethod(left, "endsWith", argv);
-                if (vm != null && vm.getReturnType() == Boolean.TYPE) {
-                    return (Boolean) vm.invoke(left, argv);
-                }
-                if (arithmetic.narrowArguments(argv)) {
-                    vm = uberspect.getMethod(left, "endsWith", argv);
-                    if (vm != null && vm.getReturnType() == Boolean.TYPE) {
-                        return (Boolean) vm.invoke(left, argv);
-                    }
-                }
-            } catch (InvocationTargetException e) {
-                throw new JexlException(node, operator + " invocation error", 
e.getCause());
-            } catch (Exception e) {
-                throw new JexlException(node, operator + " error", e);
-            }
-            // defaults to equal
-            return arithmetic.equals(left, right) ? Boolean.TRUE : 
Boolean.FALSE;
-        } catch (ArithmeticException xrt) {
-            throw new JexlException(node, operator + " error", xrt);
-        }
+    @Override
+    protected Object visit(ASTNSWNode node, Object data) {
+        Object left = node.jjtGetChild(0).jjtAccept(this, data);
+        Object right = node.jjtGetChild(1).jjtAccept(this, data);
+        return operators.startsWith(node, "^!", left, right) ? Boolean.FALSE : 
Boolean.TRUE;
     }
 
     @Override
     protected Object visit(ASTEWNode node, Object data) {
         Object left = node.jjtGetChild(0).jjtAccept(this, data);
         Object right = node.jjtGetChild(1).jjtAccept(this, data);
-        return endsWith(node, "$=", left, right) ? Boolean.TRUE : 
Boolean.FALSE;
+        return operators.endsWith(node, "$=", left, right) ? Boolean.TRUE : 
Boolean.FALSE;
     }
 
     @Override
     protected Object visit(ASTNEWNode node, Object data) {
         Object left = node.jjtGetChild(0).jjtAccept(this, data);
         Object right = node.jjtGetChild(1).jjtAccept(this, data);
-        return endsWith(node, "$!", left, right) ? Boolean.FALSE : 
Boolean.TRUE;
-    }
-
-    /**
-     * The 'match'/'in' operator implementation.
-     * @param node  the node
-     * @param op    the calling operator, =~ or !=
-     * @param left  the left operand
-     * @param right the right operand
-     * @return true if left matches right, false otherwise
-     */
-    protected boolean contains(JexlNode node, String op, Object left, Object 
right) {
-        try {
-            // try operator overload
-            Object result = callOperator(node, Operator.CONTAINS, left, right);
-            if (result instanceof Boolean) {
-                return (Boolean) result;
-            }
-            // use arithmetic / pattern matching ?
-            Boolean matched = arithmetic.contains(left, right);
-            if (matched != null) {
-                return matched;
-            }
-            // try a contains method (duck type set)
-            try {
-                Object[] argv = {left};
-                JexlMethod vm = uberspect.getMethod(right, "contains", argv);
-                if (vm != null && vm.getReturnType() == Boolean.TYPE) {
-                    return (Boolean) vm.invoke(right, argv);
-                } else if (arithmetic.narrowArguments(argv)) {
-                    vm = uberspect.getMethod(right, "contains", argv);
-                    if (vm != null && vm.getReturnType() == Boolean.TYPE) {
-                        return (Boolean) vm.invoke(right, argv);
-                    }
-                }
-            } catch (InvocationTargetException e) {
-                throw new JexlException(node, op + " invocation error", 
e.getCause());
-            } catch (Exception e) {
-                throw new JexlException(node, op + " error", e);
-            }
-            // try iterative comparison
-            Iterator<?> it = uberspect.getIterator(right);
-            if (it != null) {
-                while (it.hasNext()) {
-                    Object next = it.next();
-                    if (next == left || (next != null && next.equals(left))) {
-                        return true;
-                    }
-                }
-                return false;
-            }
-            // defaults to equal
-            return arithmetic.equals(left, right);
-        } catch (ArithmeticException xrt) {
-            throw new JexlException(node, op + " error", xrt);
-        }
+        return operators.endsWith(node, "$!", left, right) ? Boolean.FALSE : 
Boolean.TRUE;
     }
 
     @Override
     protected Object visit(ASTERNode node, Object data) {
         Object left = node.jjtGetChild(0).jjtAccept(this, data);
         Object right = node.jjtGetChild(1).jjtAccept(this, data);
-        return contains(node, "=~", left, right) ? Boolean.TRUE : 
Boolean.FALSE;
+        return operators.contains(node, "=~", right, left) ? Boolean.TRUE : 
Boolean.FALSE;
     }
 
     @Override
     protected Object visit(ASTNRNode node, Object data) {
         Object left = node.jjtGetChild(0).jjtAccept(this, data);
         Object right = node.jjtGetChild(1).jjtAccept(this, data);
-        return contains(node, "!~", left, right) ? Boolean.FALSE : 
Boolean.TRUE;
+        return operators.contains(node, "!~", right, left) ? Boolean.FALSE : 
Boolean.TRUE;
     }
 
     @Override
-    protected Object visit(ASTIfStatement node, Object data) {
-        int n = 0;
+    protected Object visit(ASTRangeNode node, Object data) {
+        Object left = node.jjtGetChild(0).jjtAccept(this, data);
+        Object right = node.jjtGetChild(1).jjtAccept(this, data);
         try {
-            Object result = null;
-            // first objectNode is the condition
-            Object expression = node.jjtGetChild(0).jjtAccept(this, null);
-            if (arithmetic.toBoolean(expression)) {
-                // first objectNode is true statement
-                n = 1;
-                result = node.jjtGetChild(n).jjtAccept(this, null);
-            } else {
-                // if there is a false, execute it. false statement is the 
second
-                // objectNode
-                if (node.jjtGetNumChildren() == 3) {
-                    n = 2;
-                    result = node.jjtGetChild(n).jjtAccept(this, null);
-                }
-            }
-            return result;
+            return arithmetic.createRange(left, right);
         } catch (ArithmeticException xrt) {
-            throw new JexlException(node.jjtGetChild(n), "if error", xrt);
+            JexlNode xnode = findNullOperand(xrt, node, left, right);
+            throw new JexlException(xnode, ".. error", xrt);
         }
     }
 
     @Override
-    protected Object visit(ASTNumberLiteral node, Object data) {
-        if (data != null && node.isInteger()) {
-            return getAttribute(data, node.getLiteral(), node);
+    protected Object visit(ASTUnaryMinusNode node, Object data) {
+        JexlNode valNode = node.jjtGetChild(0);
+        Object val = valNode.jjtAccept(this, data);
+        try {
+            Object result = operators.tryOverload(node, JexlOperator.NEGATE, 
val);
+            if (result != JexlEngine.TRY_FAILED) {
+                return result;
+            }
+            Object number = arithmetic.negate(val);
+            // attempt to recoerce to literal class
+            if (valNode instanceof ASTNumberLiteral && number instanceof 
Number) {
+                number = arithmetic.narrowNumber((Number) number, 
((ASTNumberLiteral) valNode).getLiteralClass());
+            }
+            return number;
+        } catch (ArithmeticException xrt) {
+            throw new JexlException(valNode, "- error", xrt);
         }
-        return node.getLiteral();
     }
 
     @Override
-    protected Object visit(ASTLENode node, Object data) {
-        Object left = node.jjtGetChild(0).jjtAccept(this, data);
-        Object right = node.jjtGetChild(1).jjtAccept(this, data);
+    protected Object visit(ASTBitwiseComplNode node, Object data) {
+        Object arg = node.jjtGetChild(0).jjtAccept(this, data);
         try {
-            Object result = callOperator(node, Operator.LTE, left, right);
-            return result != JexlEngine.TRY_FAILED
-                   ? result
-                   : arithmetic.lessThanOrEqual(left, right) ? Boolean.TRUE : 
Boolean.FALSE;
+            Object result = operators.tryOverload(node, 
JexlOperator.COMPLEMENT, arg);
+            return result != JexlEngine.TRY_FAILED ? result : 
arithmetic.complement(arg);
         } catch (ArithmeticException xrt) {
-            throw new JexlException(node, "<= error", xrt);
+            throw new JexlException(node, "~ error", xrt);
         }
     }
 
     @Override
-    protected Object visit(ASTLTNode node, Object data) {
-        Object left = node.jjtGetChild(0).jjtAccept(this, data);
-        Object right = node.jjtGetChild(1).jjtAccept(this, data);
+    protected Object visit(ASTNotNode node, Object data) {
+        Object val = node.jjtGetChild(0).jjtAccept(this, data);
         try {
-            Object result = callOperator(node, Operator.LT, left, right);
-            return result != JexlEngine.TRY_FAILED
-                   ? result
-                   : arithmetic.lessThan(left, right) ? Boolean.TRUE : 
Boolean.FALSE;
+            Object result = operators.tryOverload(node, JexlOperator.NOT, val);
+            return result != JexlEngine.TRY_FAILED ? result : 
arithmetic.not(val);
         } catch (ArithmeticException xrt) {
-            throw new JexlException(node, "< error", xrt);
+            throw new JexlException(node, "! error", xrt);
         }
     }
 
     @Override
-    protected 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};
+    protected Object visit(ASTIfStatement node, Object data) {
+        int n = 0;
+        try {
+            Object result = null;
+            // first objectNode is the condition
+            Object expression = node.jjtGetChild(0).jjtAccept(this, null);
+            if (arithmetic.toBoolean(expression)) {
+                // first objectNode is true statement
+                n = 1;
+                result = node.jjtGetChild(n).jjtAccept(this, null);
+            } else {
+                // if there is a false, execute it. false statement is the 
second
+                // objectNode
+                if (node.jjtGetNumChildren() == 3) {
+                    n = 2;
+                    result = node.jjtGetChild(n).jjtAccept(this, null);
+                }
+            }
+            return result;
+        } catch (ArithmeticException xrt) {
+            throw new JexlException(node.jjtGetChild(n), "if error", xrt);
+        }
     }
 
     @Override
-    protected Object visit(ASTExtendedLiteral node, Object data) {
-        return node;
+    protected 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;
     }
 
     @Override
-    protected Object visit(ASTSetLiteral node, Object data) {
-        int childCount = node.jjtGetNumChildren();
-        JexlArithmetic.SetBuilder mb = arithmetic.setBuilder(childCount);
-        if (mb != null) {
-            for (int i = 0; i < childCount; i++) {
-                Object entry = node.jjtGetChild(i).jjtAccept(this, data);
-                mb.add(entry);
-            }
-            return mb.create();
-        } else {
-            return null;
-        }
+    protected Object visit(ASTReturnStatement node, Object data) {
+        Object val = node.jjtGetChild(0).jjtAccept(this, data);
+        throw new JexlException.Return(node, null, val);
     }
 
     @Override
-    protected Object visit(ASTMapLiteral node, Object data) {
-        int childCount = node.jjtGetNumChildren();
-        JexlArithmetic.MapBuilder mb = arithmetic.mapBuilder(childCount);
-        if (mb != null) {
-            for (int i = 0; i < childCount; i++) {
-                Object[] entry = (Object[]) 
(node.jjtGetChild(i)).jjtAccept(this, data);
-                mb.put(entry[0], entry[1]);
-            }
-            return mb.create();
-        } else {
-            return null;
-        }
+    protected Object visit(ASTContinue node, Object data) {
+        throw new JexlException.Continue(node);
     }
 
     @Override
-    protected Object visit(ASTRangeNode node, Object data) {
-        Object left = node.jjtGetChild(0).jjtAccept(this, data);
-        Object right = node.jjtGetChild(1).jjtAccept(this, data);
-        try {
-            return arithmetic.createRange(left, right);
-        } catch (ArithmeticException xrt) {
-            JexlNode xnode = findNullOperand(xrt, node, left, right);
-            throw new JexlException(xnode, ".. error", xrt);
-        }
+    protected Object visit(ASTBreak node, Object data) {
+        throw new JexlException.Break(node);
     }
 
     @Override
-    protected Object visit(ASTModNode node, Object data) {
-        Object left = node.jjtGetChild(0).jjtAccept(this, data);
-        Object right = node.jjtGetChild(1).jjtAccept(this, data);
-        try {
-            Object result = callOperator(node, Operator.MOD, left, right);
-            return result != JexlEngine.TRY_FAILED ? result : 
arithmetic.mod(left, right);
-        } catch (ArithmeticException xrt) {
-            if (!strictArithmetic) {
-                return 0.0d;
+    protected 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);
+        int symbol = loopVariable.getSymbol();
+        /* 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 */
+            JexlNode statement = node.jjtGetChild(2);
+            // get an iterator for the collection/array etc via the
+            // introspector.
+            Iterator<?> itemsIterator = uberspect.getIterator(iterableValue);
+            if (itemsIterator != null) {
+                while (itemsIterator.hasNext()) {
+                    if (isCancelled()) {
+                        throw new JexlException.Cancel(node);
+                    }
+                    // set loopVariable to value of iterator
+                    Object value = itemsIterator.next();
+                    if (symbol < 0) {
+                        context.set(loopVariable.getName(), value);
+                    } else {
+                        frame.set(symbol, value);
+                    }
+                    try {
+                        // execute statement
+                        result = statement.jjtAccept(this, data);
+                    } catch (JexlException.Break stmtBreak) {
+                        break;
+                    } catch (JexlException.Continue stmtContinue) {
+                        //continue;
+                    }
+                }
             }
-            JexlNode xnode = findNullOperand(xrt, node, left, right);
-            throw new JexlException(xnode, "% error", xrt);
         }
+        return result;
     }
 
     @Override
-    protected Object visit(ASTMulNode node, Object data) {
-        Object left = node.jjtGetChild(0).jjtAccept(this, data);
-        Object right = node.jjtGetChild(1).jjtAccept(this, data);
-        try {
-            Object result = callOperator(node, Operator.MULTIPLY, left, right);
-            return result != JexlEngine.TRY_FAILED ? result : 
arithmetic.multiply(left, right);
-        } catch (ArithmeticException xrt) {
-            JexlNode xnode = findNullOperand(xrt, node, left, right);
-            throw new JexlException(xnode, "* error", xrt);
+    protected Object visit(ASTWhileStatement node, Object data) {
+        Object result = null;
+        /* first objectNode is the expression */
+        Node expressionNode = node.jjtGetChild(0);
+        while (arithmetic.toBoolean(expressionNode.jjtAccept(this, data))) {
+            if (isCancelled()) {
+                throw new JexlException.Cancel(node);
+            }
+            if (node.jjtGetNumChildren() > 1) {
+                try {
+                    // execute statement
+                    result = node.jjtGetChild(1).jjtAccept(this, data);
+                } catch (JexlException.Break stmtBreak) {
+                    break;
+                } catch (JexlException.Continue stmtContinue) {
+                    //continue;
+                }
+            }
         }
+        return result;
     }
 
     @Override
-    protected Object visit(ASTNENode node, Object data) {
+    protected Object visit(ASTAndNode 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 {
-            Object result = callOperator(node, Operator.EQ, left, right);
-            return result != JexlEngine.TRY_FAILED
-                   ? arithmetic.toBoolean(result) ? Boolean.FALSE : 
Boolean.TRUE
-                   : arithmetic.equals(left, right) ? Boolean.FALSE : 
Boolean.TRUE;
-        } catch (ArithmeticException xrt) {
-            JexlNode xnode = findNullOperand(xrt, node, left, right);
-            throw new JexlException(xnode, "!= error", xrt);
+            boolean leftValue = arithmetic.toBoolean(left);
+            if (!leftValue) {
+                return Boolean.FALSE;
+            }
+        } catch (RuntimeException xrt) {
+            throw new JexlException(node.jjtGetChild(0), "boolean coercion 
error", xrt);
         }
-    }
-
-    @Override
-    protected Object visit(ASTNotNode node, Object data) {
-        Object val = node.jjtGetChild(0).jjtAccept(this, data);
+        Object right = node.jjtGetChild(1).jjtAccept(this, data);
         try {
-            Object result = callOperator(node, Operator.NOT, val);
-            return result != JexlEngine.TRY_FAILED
-                   ? result
-                   : arithmetic.toBoolean(val) ? Boolean.FALSE : Boolean.TRUE;
+            boolean rightValue = arithmetic.toBoolean(right);
+            if (!rightValue) {
+                return Boolean.FALSE;
+            }
         } catch (ArithmeticException xrt) {
-            throw new JexlException(node, "arithmetic error", xrt);
+            throw new JexlException(node.jjtGetChild(1), "boolean coercion 
error", xrt);
         }
-    }
-
-    @Override
-    protected Object visit(ASTNullLiteral node, Object data) {
-        return null;
+        return Boolean.TRUE;
     }
 
     @Override
@@ -1108,14 +855,26 @@ public class Interpreter extends ParserV
     }
 
     @Override
-    protected Object visit(ASTReferenceExpression node, Object data) {
-        return node.jjtGetChild(0).jjtAccept(this, data);
+    protected Object visit(ASTNullLiteral node, Object data) {
+        return null;
     }
 
     @Override
-    protected Object visit(ASTReturnStatement node, Object data) {
-        Object val = node.jjtGetChild(0).jjtAccept(this, data);
-        throw new JexlException.Return(node, null, val);
+    protected Object visit(ASTTrueNode node, Object data) {
+        return Boolean.TRUE;
+    }
+
+    @Override
+    protected Object visit(ASTFalseNode node, Object data) {
+        return Boolean.FALSE;
+    }
+
+    @Override
+    protected Object visit(ASTNumberLiteral node, Object data) {
+        if (data != null && node.isInteger()) {
+            return getAttribute(data, node.getLiteral(), node);
+        }
+        return node.getLiteral();
     }
 
     @Override
@@ -1127,68 +886,83 @@ public class Interpreter extends ParserV
     }
 
     @Override
-    protected Object visit(ASTTernaryNode node, Object data) {
-        Object condition = node.jjtGetChild(0).jjtAccept(this, data);
-        if (node.jjtGetNumChildren() == 3) {
-            if (condition != null && arithmetic.toBoolean(condition)) {
-                return node.jjtGetChild(1).jjtAccept(this, data);
-            } else {
-                return node.jjtGetChild(2).jjtAccept(this, data);
+    protected Object visit(ASTArrayLiteral node, Object data) {
+        int childCount = node.jjtGetNumChildren();
+        JexlArithmetic.ArrayBuilder ab = arithmetic.arrayBuilder(childCount);
+        if (ab != null) {
+            boolean extended = false;
+            for (int i = 0; i < childCount; i++) {
+                JexlNode child = node.jjtGetChild(i);
+                if (child instanceof ASTExtendedLiteral) {
+                    extended = true;
+                } else {
+                    Object entry = node.jjtGetChild(i).jjtAccept(this, data);
+                    ab.add(entry);
+                }
             }
-        }
-        if (condition != null && arithmetic.toBoolean(condition)) {
-            return condition;
+            return ab.create(extended);
         } else {
-            return node.jjtGetChild(1).jjtAccept(this, data);
+            return null;
         }
     }
 
     @Override
-    protected Object visit(ASTTrueNode node, Object data) {
-        return Boolean.TRUE;
+    protected Object visit(ASTExtendedLiteral node, Object data) {
+        return node;
     }
 
     @Override
-    protected Object visit(ASTUnaryMinusNode node, Object data) {
-        JexlNode valNode = node.jjtGetChild(0);
-        Object val = valNode.jjtAccept(this, data);
-        try {
-            Object result = callOperator(node, Operator.NEGATE, val);
-            if (result != JexlEngine.TRY_FAILED) {
-                return result;
-            }
-            Object number = result != JexlEngine.TRY_FAILED ? result : 
arithmetic.negate(val);
-            // attempt to recoerce to literal class
-            if (valNode instanceof ASTNumberLiteral && number instanceof 
Number) {
-                number = arithmetic.narrowNumber((Number) number, 
((ASTNumberLiteral) valNode).getLiteralClass());
+    protected Object visit(ASTSetLiteral node, Object data) {
+        int childCount = node.jjtGetNumChildren();
+        JexlArithmetic.SetBuilder mb = arithmetic.setBuilder(childCount);
+        if (mb != null) {
+            for (int i = 0; i < childCount; i++) {
+                Object entry = node.jjtGetChild(i).jjtAccept(this, data);
+                mb.add(entry);
             }
-            return number;
-        } catch (ArithmeticException xrt) {
-            throw new JexlException(valNode, "arithmetic error", xrt);
+            return mb.create();
+        } else {
+            return null;
         }
     }
 
     @Override
-    protected Object visit(ASTWhileStatement node, Object data) {
-        Object result = null;
-        /* first objectNode is the expression */
-        Node expressionNode = node.jjtGetChild(0);
-        while (arithmetic.toBoolean(expressionNode.jjtAccept(this, data))) {
-            if (isCancelled()) {
-                throw new JexlException.Cancel(node);
+    protected Object visit(ASTMapLiteral node, Object data) {
+        int childCount = node.jjtGetNumChildren();
+        JexlArithmetic.MapBuilder mb = arithmetic.mapBuilder(childCount);
+        if (mb != null) {
+            for (int i = 0; i < childCount; i++) {
+                Object[] entry = (Object[]) 
(node.jjtGetChild(i)).jjtAccept(this, data);
+                mb.put(entry[0], entry[1]);
             }
-            if (node.jjtGetNumChildren() > 1) {
-                try {
-                    // execute statement
-                    result = node.jjtGetChild(1).jjtAccept(this, data);
-                } catch (JexlException.Break stmtBreak) {
-                    break;
-                } catch (JexlException.Continue stmtContinue) {
-                    //continue;
-                }
+            return mb.create();
+        } else {
+            return null;
+        }
+    }
+
+    @Override
+    protected 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};
+    }
+
+    @Override
+    protected Object visit(ASTTernaryNode node, Object data) {
+        Object condition = node.jjtGetChild(0).jjtAccept(this, data);
+        if (node.jjtGetNumChildren() == 3) {
+            if (condition != null && arithmetic.toBoolean(condition)) {
+                return node.jjtGetChild(1).jjtAccept(this, data);
+            } else {
+                return node.jjtGetChild(2).jjtAccept(this, data);
             }
         }
-        return result;
+        if (condition != null && arithmetic.toBoolean(condition)) {
+            return condition;
+        } else {
+            return node.jjtGetChild(1).jjtAccept(this, data);
+        }
     }
 
     @Override
@@ -1197,7 +971,7 @@ public class Interpreter extends ParserV
         try {
             strictEngine = false;
             Object val = node.jjtGetChild(0).jjtAccept(this, data);
-            return sizeOf(node, val);
+            return operators.size(node, val);
         } finally {
             strictEngine = isStrict;
         }
@@ -1206,7 +980,7 @@ public class Interpreter extends ParserV
     @Override
     protected Object visit(ASTSizeMethod node, Object data) {
         Object val = node.jjtGetChild(0).jjtAccept(this, data);
-        return sizeOf(node, val);
+        return operators.size(node, val);
     }
 
     @Override
@@ -1215,7 +989,7 @@ public class Interpreter extends ParserV
         try {
             strictEngine = false;
             Object value = node.jjtGetChild(0).jjtAccept(this, data);
-            return callEmpty(node, value);
+            return operators.empty(node, value);
         } finally {
             strictEngine = isStrict;
         }
@@ -1224,94 +998,7 @@ public class Interpreter extends ParserV
     @Override
     protected Object visit(ASTEmptyMethod node, Object data) {
         Object val = node.jjtGetChild(0).jjtAccept(this, data);
-        return callEmpty(node, val);
-    }
-
-    /**
-     * Check for emptyness of various types: Collection, Array, Map, String, 
and anything that has a boolean isEmpty()
-     * method.
-     *
-     * @param node   the node holding the object
-     * @param object the object to check the emptyness of.
-     * @return the boolean
-     */
-    private Object callEmpty(JexlNode node, Object object) {
-        if (object == null) {
-            return Boolean.TRUE;
-        }
-        Object opcall = callOperator(node, Operator.EMPTY, object);
-        if (opcall != JexlEngine.TRY_FAILED) {
-            return opcall;
-        }
-        if (object instanceof Number) {
-            double d = ((Number) object).doubleValue();
-            return Double.isNaN(d) || d == 0.d ? Boolean.TRUE : Boolean.FALSE;
-        }
-        if (object instanceof String) {
-            return "".equals(object) ? Boolean.TRUE : Boolean.FALSE;
-        }
-        if (object.getClass().isArray()) {
-            return Array.getLength(object) == 0 ? Boolean.TRUE : Boolean.FALSE;
-        }
-        if (object instanceof Collection<?>) {
-            return ((Collection<?>) object).isEmpty() ? Boolean.TRUE : 
Boolean.FALSE;
-        }
-        // Map isn't a collection
-        if (object instanceof Map<?, ?>) {
-            return ((Map<?, ?>) object).isEmpty() ? Boolean.TRUE : 
Boolean.FALSE;
-        }
-        // check if there is an isEmpty method on the object that returns a
-        // boolean and if so, just use it
-        JexlMethod vm = uberspect.getMethod(object, "isEmpty", EMPTY_PARAMS);
-        if (vm != null && vm.getReturnType() == Boolean.TYPE) {
-            try {
-                return (Boolean) vm.invoke(object, EMPTY_PARAMS);
-            } catch (Exception xany) {
-                operatorError(node, Operator.EMPTY, xany);
-            }
-        }
-        return Boolean.FALSE;
-    }
-
-    /**
-     * Calculate the <code>size</code> of various types:
-     * Collection, Array, Map, String, and anything that has a int size() 
method.
-     *
-     * @param node   the node that gave the value to size
-     * @param object the object to get the size of.
-     * @return the size of val
-     */
-    private Object sizeOf(JexlNode node, Object object) {
-        if (object == null) {
-            return 0;
-        }
-        Object opcall = callOperator(node, Operator.SIZE, object);
-        if (opcall != JexlEngine.TRY_FAILED) {
-            return opcall;
-        }
-        if (object instanceof String) {
-            return ((String) object).length();
-        }
-        if (object.getClass().isArray()) {
-            return Array.getLength(object);
-        }
-        if (object instanceof Collection<?>) {
-            return ((Collection<?>) object).size();
-        }
-        if (object instanceof Map<?, ?>) {
-            return ((Map<?, ?>) object).size();
-        }
-        // check if there is a size method on the object that returns an
-        // integer and if so, just use it
-        JexlMethod vm = uberspect.getMethod(object, "size", EMPTY_PARAMS);
-        if (vm != null && vm.getReturnType() == Integer.TYPE) {
-            try {
-                return (Integer) vm.invoke(object, EMPTY_PARAMS);
-            } catch (Exception xany) {
-                operatorError(node, Operator.SIZE, xany);
-            }
-        }
-        return 0;
+        return operators.empty(node, val);
     }
 
     @Override
@@ -1330,6 +1017,16 @@ public class Interpreter extends ParserV
     }
 
     @Override
+    protected Object visit(ASTVar node, Object data) {
+        return visit((ASTIdentifier) node, data);
+    }
+
+    @Override
+    protected Object visit(ASTReferenceExpression node, Object data) {
+        return node.jjtGetChild(0).jjtAccept(this, data);
+    }
+
+    @Override
     protected Object visit(ASTIdentifier node, Object data) {
         if (isCancelled()) {
             throw new JexlException.Cancel(node);
@@ -1354,11 +1051,6 @@ public class Interpreter extends ParserV
     }
 
     @Override
-    protected Object visit(ASTVar node, Object data) {
-        return visit((ASTIdentifier) node, data);
-    }
-
-    @Override
     protected Object visit(ASTArrayAccess node, Object data) {
         // first objectNode is the identifier
         Object object = data;
@@ -1375,12 +1067,6 @@ public class Interpreter extends ParserV
         return object;
     }
 
-    @Override
-    protected Object visit(ASTIdentifierAccess node, Object data) {
-        // child 0 is the identifier, data is the object
-        return data != null ? getAttribute(data, node.getIdentifier(), node) : 
null;
-    }
-
     /**
      * Check if a null evaluated expression is protected by a ternary 
expression.
      * <p>
@@ -1391,7 +1077,7 @@ public class Interpreter extends ParserV
      * @param node the expression node
      * @return true if nullable variable, false otherwise
      */
-    private boolean isTernaryProtected(JexlNode node) {
+    protected boolean isTernaryProtected(JexlNode node) {
         for (JexlNode walk = node.jjtGetParent(); walk != null; walk = 
walk.jjtGetParent()) {
             if (walk instanceof ASTTernaryNode) {
                 return true;
@@ -1408,82 +1094,135 @@ public class Interpreter extends ParserV
      * @param which the child we are checking
      * @return true if child is local variable, false otherwise
      */
-    private boolean isLocalVariable(ASTReference node, int which) {
+    protected boolean isLocalVariable(ASTReference node, int which) {
         return (node.jjtGetNumChildren() > which
                 && node.jjtGetChild(which) instanceof ASTIdentifier
                 && ((ASTIdentifier) node.jjtGetChild(which)).getSymbol() >= 0);
     }
 
     @Override
+    protected Object visit(ASTIdentifierAccess node, Object data) {
+        return data != null ? getAttribute(data, node.getIdentifier(), node) : 
null;
+    }
+
+    @Override
     protected Object visit(ASTReference node, Object data) {
+        if (isCancelled()) {
+            throw new JexlException.Cancel(node);
+        }
         final int numChildren = node.jjtGetNumChildren();
         JexlNode parent = node.jjtGetParent();
         // pass first piece of data in and loop through children
         Object object = null;
         JexlNode objectNode;
-        StringBuilder variableName = null;
+        StringBuilder ant = null;
         boolean antish = !(parent instanceof ASTReference);
-        int v = 0;
+        int v = 1;
         main:
         for (int c = 0; c < numChildren; c++) {
-            if (isCancelled()) {
-                throw new JexlException.Cancel(node);
-            }
             objectNode = node.jjtGetChild(c);
             if (objectNode instanceof ASTMethodNode && object == null) {
                 break;
             }
-            // attempt to evaluate the property within the object
+            // attempt to evaluate the property within the object 
(visit(ASTIdentifierAccess node))
             object = objectNode.jjtAccept(this, object);
-            if (object == null && antish) {
-                // if we still have a null object and we are evaluating 'x.y', 
check for an antish variable
-                if (v == 0) {
-                    // if the first node is a local variable or parameter, the 
object can not be null
+            if (object != null) {
+                // disallow mixing antish variable & bean with same root; 
avoid ambiguity
+                antish = false;
+            } else if (antish) {  // if we still have a null object, check for 
an antish variable
+                if (ant == null) {
                     JexlNode first = node.jjtGetChild(0);
-                    if (first instanceof ASTIdentifier && ((ASTIdentifier) 
first).getSymbol() >= 0) {
-                        antish = false;
-                        break main;
-                    }
-                    // first node must be an Identifier
-                    if (objectNode instanceof ASTIdentifier) {
-                        variableName = new StringBuilder(((ASTIdentifier) 
objectNode).getName());
-                        v = 1;
+                    if (first instanceof ASTIdentifier && ((ASTIdentifier) 
first).getSymbol() < 0) {
+                        ant = new StringBuilder(((ASTIdentifier) 
first).getName());
                     } else {
-                        break main;
+                        break;
                     }
                 }
                 for (; v <= c; ++v) {
-                    // subsequent nodes must be identifier access
-                    objectNode = node.jjtGetChild(v);
-                    if (objectNode instanceof ASTIdentifierAccess) {
-                        // variableName can *not* be null; it has been 
necessarily set by the (v == 0) condition
-                        variableName.append('.');
-                        variableName.append(((ASTIdentifierAccess) 
objectNode).getName());
+                    JexlNode child = node.jjtGetChild(v);
+                    if (child instanceof ASTIdentifierAccess) {
+                        ant.append('.');
+                        ant.append(((ASTIdentifierAccess) 
objectNode).getName());
                     } else {
-                        break main;
+                        break;
                     }
                 }
-                // variableName can *not* be null; the code before this line 
made sure of that
-                object = context.get(variableName.toString());
+                object = context.get(ant.toString());
+            } else {
+                break;
             }
-            antish &= object == null;
         }
-        if (object == null && antish && variableName != null && 
!isTernaryProtected(node)) {
-            boolean undefined = !(context.has(variableName.toString()) || 
isLocalVariable(node, 0));
+        if (object == null && antish && ant != null && 
!isTernaryProtected(node)) {
+            boolean undefined = !(context.has(ant.toString()) || 
isLocalVariable(node, 0));
             // variable unknown in context and not a local
-            return unsolvableVariable(node, variableName.toString(), 
undefined);
+            return unsolvableVariable(node, ant.toString(), undefined);
         }
         return object;
     }
 
     @Override
     protected Object visit(ASTAssignment node, Object data) {
+        return executeAssign(node, null, data);
+    }
+
+    @Override
+    protected Object visit(ASTSetAddNode node, Object data) {
+        return executeAssign(node, JexlOperator.SELF_ADD, data);
+    }
+
+    @Override
+    protected Object visit(ASTSetSubNode node, Object data) {
+        return executeAssign(node, JexlOperator.SELF_SUBTRACT, data);
+    }
+
+    @Override
+    protected Object visit(ASTSetMultNode node, Object data) {
+        return executeAssign(node, JexlOperator.SELF_MULTIPLY, data);
+    }
+
+    @Override
+    protected Object visit(ASTSetDivNode node, Object data) {
+        return executeAssign(node, JexlOperator.SELF_DIVIDE, data);
+    }
+
+    @Override
+    protected Object visit(ASTSetModNode node, Object data) {
+        return executeAssign(node, JexlOperator.SELF_MOD, data);
+    }
+
+    @Override
+    protected Object visit(ASTSetAndNode node, Object data) {
+        return executeAssign(node, JexlOperator.SELF_AND, data);
+    }
+
+    @Override
+    protected Object visit(ASTSetOrNode node, Object data) {
+        return executeAssign(node, JexlOperator.SELF_OR, data);
+    }
+
+    @Override
+    protected Object visit(ASTSetXorNode node, Object data) {
+        return executeAssign(node, JexlOperator.SELF_XOR, data);
+    }
+
+    /**
+     * Executes an assignment with an optional side-effect operator.
+     * @param node     the node
+     * @param assignop the assignment operator or null if simply assignment
+     * @param data     the data
+     * @return the left hand side
+     */
+    protected Object executeAssign(JexlNode node, JexlOperator assignop, 
Object data) {
+        if (isCancelled()) {
+            throw new JexlException.Cancel(node);
+        }
         // left contains the reference to assign to
         final JexlNode left = node.jjtGetChild(0);
         // right is the value expression to assign
-        final Object right = node.jjtGetChild(1).jjtAccept(this, data);
+        Object right = node.jjtGetChild(1).jjtAccept(this, data);
         Object object = null;
         int symbol = -1;
+        boolean antish = true;
         // 0: determine initial object & property:
         final int last = left.jjtGetNumChildren() - 1;
         if (left instanceof ASTIdentifier) {
@@ -1492,6 +1231,13 @@ public class Interpreter extends ParserV
             if (symbol >= 0) {
                 // check we are not assigning a symbol itself
                 if (last < 0) {
+                    if (assignop != null) {
+                        Object self = frame.get(symbol);
+                        right = operators.tryAssignOverload(node, assignop, 
self, right);
+                        if (right == JexlOperator.ASSIGN) {
+                            return self;
+                        }
+                    }
                     frame.set(symbol, right);
                     // make the closure accessible to itself, ie hoist the 
currently set variable after frame creation
                     if (right instanceof Closure) {
@@ -1500,9 +1246,18 @@ public class Interpreter extends ParserV
                     return right; // 1
                 }
                 object = frame.get(symbol);
+                // top level is a symbol, can not be an antish var
+                antish = false;
             } else {
                 // check we are not assigning direct global
                 if (last < 0) {
+                    if (assignop != null) {
+                        Object self = context.get(var.getName());
+                        right = operators.tryAssignOverload(node, assignop, 
self, right);
+                        if (right == JexlOperator.ASSIGN) {
+                            return self;
+                        }
+                    }
                     try {
                         context.set(var.getName(), right);
                     } catch (UnsupportedOperationException xsupport) {
@@ -1511,57 +1266,45 @@ public class Interpreter extends ParserV
                     return right; // 2
                 }
                 object = context.get(var.getName());
+                // top level accesses object, can not be an antish var
+                if (object != null) {
+                    antish = false;
+                }
             }
         } else if (!(left instanceof ASTReference)) {
             throw new JexlException(left, "illegal assignment form 0");
         }
         // 1: follow children till penultimate, resolve dot/array
         JexlNode objectNode = null;
-        boolean antish = true;
-        int v = 0;
-        StringBuilder variableName = null;
+        StringBuilder ant = null;
+        int v = 1;
         // start at 1 if symbol
         for (int c = symbol >= 0 ? 1 : 0; c < last; ++c) {
-            if (isCancelled()) {
-                throw new JexlException.Cancel(left);
-            }
             objectNode = left.jjtGetChild(c);
             object = objectNode.jjtAccept(this, object);
             if (object != null) {
                 // disallow mixing antish variable & bean with same root; 
avoid ambiguity
                 antish = false;
-                continue;
             }
-            // if we still have a null object, check for an antish variable
-            if (antish) {
-                if (v == 0) {
-                    // if the first node is a local variable or parameter, the 
object can not be null
+            else if (antish) {
+                if (ant == null) {
                     JexlNode first = left.jjtGetChild(0);
-                    if (first instanceof ASTIdentifier && ((ASTIdentifier) 
first).getSymbol() >= 0) {
-                        antish = false;
-                        break;
-                    }
-                    if (objectNode instanceof ASTIdentifier) {
-                        variableName = new StringBuilder(((ASTIdentifier) 
objectNode).getName());
-                        v = 1;
+                    if (first instanceof ASTIdentifier && ((ASTIdentifier) 
first).getSymbol() < 0) {
+                        ant = new StringBuilder(((ASTIdentifier) 
first).getName());
                     } else {
-                        antish = false;
+                        break;
                     }
                 }
-                for (; antish && v <= c; ++v) {
+                for (; v <= c; ++v) {
                     JexlNode child = left.jjtGetChild(v);
                     if (child instanceof ASTIdentifierAccess) {
-                        variableName.append('.');
-                        variableName.append(((ASTIdentifierAccess) 
objectNode).getName());
+                        ant.append('.');
+                        ant.append(((ASTIdentifierAccess) 
objectNode).getName());
                     } else {
-                        antish = false;
+                        break;
                     }
                 }
-                if (antish) {
-                    object = context.get(variableName.toString());
-                } else {
-                    break;
-                }
+                object = context.get(ant.toString());
             } else {
                 throw new JexlException(objectNode, "illegal assignment form");
             }
@@ -1572,13 +1315,20 @@ public class Interpreter extends ParserV
         if (propertyNode instanceof ASTIdentifierAccess) {
             property = ((ASTIdentifierAccess) propertyNode).getIdentifier();
             // deal with antish variable
-            if (variableName != null && object == null) {
+            if (ant != null && object == null) {
                 if (last > 0) {
-                    variableName.append('.');
+                    ant.append('.');
+                }
+                ant.append(String.valueOf(property));
+                if (assignop != null) {
+                    Object self = context.get(ant.toString());
+                    right = operators.tryAssignOverload(node, assignop, self, 
right);
+                    if (right == JexlOperator.ASSIGN) {
+                        return self;
+                    }
                 }
-                variableName.append(String.valueOf(property));
                 try {
-                    context.set(variableName.toString(), right);
+                    context.set(ant.toString(), right);
                 } catch (UnsupportedOperationException xsupport) {
                     throw new JexlException(node, "context is readonly", 
xsupport);
                 }
@@ -1606,6 +1356,13 @@ public class Interpreter extends ParserV
             throw new JexlException(objectNode, "bean is null");
         }
         // 3: one before last, assign
+        if (assignop != null) {
+            Object self = getAttribute(object, property, propertyNode);
+            right = operators.tryAssignOverload(node, assignop, self, right);
+            if (right == JexlOperator.ASSIGN) {
+                return self;
+            }
+        }
         setAttribute(object, property, right, propertyNode);
         return right; // 4
     }
@@ -1666,7 +1423,7 @@ public class Interpreter extends ParserV
      * @param argNode the node carrying the arguments
      * @return the result of the method invocation
      */
-    private Object call(JexlNode node, Object bean, Object functor, 
ASTArguments argNode) {
+    protected Object call(JexlNode node, Object bean, Object functor, 
ASTArguments argNode) {
         if (isCancelled()) {
             throw new JexlException.Cancel(node);
         }

Modified: 
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/LongRange.java
URL: 
http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/LongRange.java?rev=1692852&r1=1692851&r2=1692852&view=diff
==============================================================================
--- 
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/LongRange.java
 (original)
+++ 
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/LongRange.java
 Mon Jul 27 10:02:49 2015
@@ -52,13 +52,8 @@ public abstract class LongRange implemen
      * @param to   the higher inclusive boundary
      */
     protected LongRange(long from, long to) {
-        if (from > to) {
-            max = from;
-            min = to;
-        } else {
-            min = from;
-            max = to;
-        }
+        min = from;
+        max = to;
     }
 
     /**
@@ -203,6 +198,11 @@ public abstract class LongRange implemen
      * Ascending long range.
      */
     public static class Ascending extends LongRange {
+        /**
+         * Constructor.
+         * @param from lower boundary
+         * @param to upper boundary
+         */
         protected Ascending(long from, long to) {
             super(from, to);
         }
@@ -217,6 +217,11 @@ public abstract class LongRange implemen
      * Descending long range.
      */
     public static class Descending extends LongRange {
+        /**
+         * Constructor.
+         * @param from upper boundary
+         * @param to lower boundary
+         */
         protected Descending(long from, long to) {
             super(from, to);
         }


Reply via email to