You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by he...@apache.org on 2015/07/27 12:02:50 UTC

svn commit: r1692852 [3/3] - in /commons/proper/jexl/trunk/src: main/java/org/apache/commons/jexl3/ main/java/org/apache/commons/jexl3/internal/ main/java/org/apache/commons/jexl3/internal/introspection/ main/java/org/apache/commons/jexl3/introspection...

Added: commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/Operators.java
URL: http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/Operators.java?rev=1692852&view=auto
==============================================================================
--- commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/Operators.java (added)
+++ commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/Operators.java Mon Jul 27 10:02:49 2015
@@ -0,0 +1,406 @@
+/*
+ * 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.jexl3.internal;
+
+import org.apache.commons.jexl3.JexlArithmetic;
+import org.apache.commons.jexl3.JexlEngine;
+import org.apache.commons.jexl3.JexlException;
+import org.apache.commons.jexl3.JexlOperator;
+import org.apache.commons.jexl3.introspection.JexlMethod;
+import org.apache.commons.jexl3.introspection.JexlUberspect;
+import org.apache.commons.jexl3.parser.JexlNode;
+
+/**
+ * Helper class to deal with operator overloading and specifics.
+ * @since 3.0
+ */
+public class Operators {
+    /** The owner. */
+    protected final Interpreter interpreter;
+    /** The overloaded arithmetic operators. */
+    protected final JexlArithmetic.Uberspect operators;
+
+    /**
+     * Constructor.
+     * @param owner the owning interpreter
+     */
+    protected Operators(Interpreter owner) {
+        final JexlArithmetic arithmetic = owner.arithmetic;
+        final JexlUberspect uberspect = owner.uberspect;
+        this.interpreter = owner;
+        this.operators = uberspect.getArithmetic(arithmetic);
+    }
+
+    /**
+     * Checks whether a method returns a boolean or a Boolean
+     * @param vm the JexlMethod (may be null)
+     * @return true of false
+     */
+    private boolean returnsBoolean(JexlMethod vm) {
+        if (vm !=null) {
+            Class<?> rc = vm.getReturnType();
+            return Boolean.TYPE.equals(rc) || Boolean.class.equals(rc);
+        }
+        return false;
+    }
+
+    /**
+     * 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 tryOverload(JexlNode node, JexlOperator operator, Object arg) {
+        if (operators != null && operators.overloads(operator)) {
+            final JexlArithmetic arithmetic = interpreter.arithmetic;
+            final boolean cache = interpreter.cache;
+            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) {
+                interpreter.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 tryOverload(JexlNode node, JexlOperator operator, Object lhs, Object rhs) {
+        if (operators != null && operators.overloads(operator)) {
+            final JexlArithmetic arithmetic = interpreter.arithmetic;
+            final boolean cache = interpreter.cache;
+            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) {
+                interpreter.operatorError(node, operator, xany);
+            }
+        }
+        return JexlEngine.TRY_FAILED;
+    }
+
+    /**
+     * Evaluates an assign operator.
+     * <p>
+     * This takes care of finding and caching the operator method when appropriate.
+     * If an overloads returns Operator.ASSIGN, it means the side-effect is complete.
+     * Otherwise, a += b &lt;=&gt; a = a + b
+     * </p>
+     * @param node     the syntactic node
+     * @param operator the operator
+     * @param lhs      the left hand side, target of the side-effect
+     * @param rhs      the right hand side, argument of the (base) operator
+     * @return the result of the operator evaluation
+     */
+    protected Object tryAssignOverload(JexlNode node, JexlOperator operator, Object lhs, Object rhs) {
+        final JexlArithmetic arithmetic = interpreter.arithmetic;
+        // try to call overload on side effect
+        Object result = tryOverload(node, operator, lhs, rhs);
+        if (result != JexlEngine.TRY_FAILED) {
+            return result;
+        }
+        // call base operator
+        JexlOperator base = operator.getBaseOperator();
+        if (base == null) {
+            throw new IllegalArgumentException("must be called with a side-effect operator");
+        }
+        if (operators != null && operators.overloads(base)) {
+            // in case there is an overload
+            try {
+                JexlMethod emptym = operators.getOperator(base, lhs, rhs);
+                if (emptym != null) {
+                    result = emptym.invoke(arithmetic, lhs, rhs);
+                    if (result != JexlEngine.TRY_FAILED) {
+                        return result;
+                    }
+                }
+            } catch (Exception xany) {
+                interpreter.operatorError(node, base, xany);
+            }
+        }
+        // base eval
+        switch (operator) {
+            case SELF_ADD:
+                return arithmetic.add(lhs, rhs);
+            case SELF_SUBTRACT:
+                return arithmetic.subtract(lhs, rhs);
+            case SELF_MULTIPLY:
+                return arithmetic.multiply(lhs, rhs);
+            case SELF_DIVIDE:
+                return arithmetic.divide(lhs, rhs);
+            case SELF_MOD:
+                return arithmetic.mod(lhs, rhs);
+            case SELF_AND:
+                return arithmetic.and(lhs, rhs);
+            case SELF_OR:
+                return arithmetic.or(lhs, rhs);
+            case SELF_XOR:
+                return arithmetic.xor(lhs, rhs);
+            default:
+                throw new JexlException.Operator(node, operator.getOperatorSymbol(), null);
+        }
+    }
+
+    /**
+     * 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) {
+        final JexlArithmetic arithmetic = interpreter.arithmetic;
+        final JexlUberspect uberspect = interpreter.uberspect;
+        try {
+            // try operator overload
+            Object result = tryOverload(node, JexlOperator.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 (returnsBoolean(vm)) {
+                    return (Boolean) vm.invoke(left, argv);
+                } else if (arithmetic.narrowArguments(argv)) {
+                    vm = uberspect.getMethod(left, "startsWith", argv);
+                    if (returnsBoolean(vm)) {
+                        return (Boolean) vm.invoke(left, argv);
+                    }
+                }
+            } 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);
+        }
+    }
+
+    /**
+     * 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) {
+        final JexlArithmetic arithmetic = interpreter.arithmetic;
+        final JexlUberspect uberspect = interpreter.uberspect;
+        try {
+            // try operator overload
+            Object result = tryOverload(node, JexlOperator.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 (returnsBoolean(vm)) {
+                    return (Boolean) vm.invoke(left, argv);
+                } else if (arithmetic.narrowArguments(argv)) {
+                    vm = uberspect.getMethod(left, "endsWith", argv);
+                    if (returnsBoolean(vm)) {
+                        return (Boolean) vm.invoke(left, argv);
+                    }
+                }
+            } 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);
+        }
+    }
+
+    /**
+     * The 'match'/'in' operator implementation.
+     * <p>
+     * Note that 'x in y' or 'x matches y' means 'y contains x' ;
+     * the JEXL operator arguments order syntax is the reverse of this method call.
+     * </p>
+     * @param node  the node
+     * @param op    the calling operator, =~ or !=
+     * @param right the left operand
+     * @param left  the right operand
+     * @return true if left matches right, false otherwise
+     */
+    protected boolean contains(JexlNode node, String op, Object left, Object right) {
+        final JexlArithmetic arithmetic = interpreter.arithmetic;
+        final JexlUberspect uberspect = interpreter.uberspect;
+        try {
+            // try operator overload
+            Object result = tryOverload(node, JexlOperator.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 = {right};
+                JexlMethod vm = uberspect.getMethod(left, "contains", argv);
+                if (returnsBoolean(vm)) {
+                    return (Boolean) vm.invoke(left, argv);
+                } else if (arithmetic.narrowArguments(argv)) {
+                    vm = uberspect.getMethod(left, "contains", argv);
+                    if (returnsBoolean(vm)) {
+                        return (Boolean) vm.invoke(left, argv);
+                    }
+                }
+            } catch (Exception e) {
+                throw new JexlException(node, op + " error", e);
+            }
+            // defaults to equal
+            return arithmetic.equals(left, right);
+        } catch (ArithmeticException xrt) {
+            throw new JexlException(node, op + " error", xrt);
+        }
+    }
+
+    /**
+     * 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
+     */
+    protected Boolean empty(JexlNode node, Object object) {
+        final JexlArithmetic arithmetic = interpreter.arithmetic;
+        final JexlUberspect uberspect = interpreter.uberspect;
+        if (object == null) {
+            return Boolean.TRUE;
+        }
+        Object opcall = Operators.this.tryOverload(node, JexlOperator.EMPTY, object);
+        if (opcall instanceof Boolean) {
+            return (Boolean) opcall;
+        }
+        Boolean result = arithmetic.isEmpty(object);
+        if (result == null) {
+            result = 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", Interpreter.EMPTY_PARAMS);
+            if (returnsBoolean(vm)) {
+                try {
+                    result = (Boolean) vm.invoke(object, Interpreter.EMPTY_PARAMS);
+                } catch (Exception xany) {
+                    interpreter.operatorError(node, JexlOperator.EMPTY, xany);
+                }
+            }
+        }
+        return result;
+    }
+
+    /**
+     * 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
+     */
+    protected Integer size(JexlNode node, Object object) {
+        final JexlArithmetic arithmetic = interpreter.arithmetic;
+        final JexlUberspect uberspect = interpreter.uberspect;
+        if (object == null) {
+            return 0;
+        }
+        Object opcall = Operators.this.tryOverload(node, JexlOperator.SIZE, object);
+        if (opcall instanceof Integer) {
+            return (Integer) opcall;
+        }
+        Integer result = arithmetic.size(object);
+        if (result == null) {
+            // 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", Interpreter.EMPTY_PARAMS);
+            if (vm != null && (Integer.TYPE.equals(vm.getReturnType()) || Integer.class.equals(vm.getReturnType()))) {
+                try {
+                    result = (Integer) vm.invoke(object, Interpreter.EMPTY_PARAMS);
+                } catch (Exception xany) {
+                    interpreter.operatorError(node, JexlOperator.SIZE, xany);
+                }
+            }
+        }
+        return result;
+    }
+}

Propchange: commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/Operators.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/introspection/Uberspect.java
URL: http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/introspection/Uberspect.java?rev=1692852&r1=1692851&r2=1692852&view=diff
==============================================================================
--- commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/introspection/Uberspect.java (original)
+++ commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/introspection/Uberspect.java Mon Jul 27 10:02:49 2015
@@ -18,8 +18,8 @@ package org.apache.commons.jexl3.interna
 
 
 import org.apache.commons.jexl3.JexlArithmetic;
-import org.apache.commons.jexl3.JexlArithmetic.Operator;
 import org.apache.commons.jexl3.JexlEngine;
+import org.apache.commons.jexl3.JexlOperator;
 import org.apache.commons.jexl3.introspection.JexlMethod;
 import org.apache.commons.jexl3.introspection.JexlPropertyGet;
 import org.apache.commons.jexl3.introspection.JexlPropertySet;
@@ -66,7 +66,7 @@ public class Uberspect implements JexlUb
      * <p>This keeps track of which operator methods are overloaded per JexlArithemtic classes
      * allowing a fail fast test during interpretation by avoiding seeking a method when there is none.
      */
-    private final Map<Class<? extends JexlArithmetic>, Set<Operator>> operatorMap;
+    private final Map<Class<? extends JexlArithmetic>, Set<JexlOperator>> operatorMap;
 
     /**
      * Creates a new Uberspect.
@@ -76,7 +76,7 @@ public class Uberspect implements JexlUb
         rlog = runtimeLogger;
         ref = new SoftReference<Introspector>(null);
         loader = new SoftReference<ClassLoader>(getClass().getClassLoader());
-        operatorMap = new ConcurrentHashMap<Class<? extends JexlArithmetic>, Set<Operator>>();
+        operatorMap = new ConcurrentHashMap<Class<? extends JexlArithmetic>, Set<JexlOperator>>();
         version = new AtomicInteger(0);
     }
 
@@ -370,14 +370,14 @@ public class Uberspect implements JexlUb
         /** The arithmetic instance being analyzed. */
         private final JexlArithmetic arithmetic;
         /** The set of overloaded operators. */
-        private final EnumSet<Operator> overloads;
+        private final EnumSet<JexlOperator> overloads;
 
         /**
          * Creates an instance.
          * @param theArithmetic the arithmetic instance
          * @param theOverloads  the overloaded operators
          */
-        private ArithmeticUberspect(JexlArithmetic theArithmetic, Set<Operator> theOverloads) {
+        private ArithmeticUberspect(JexlArithmetic theArithmetic, Set<JexlOperator> theOverloads) {
             this.arithmetic = theArithmetic;
             this.overloads = EnumSet.copyOf(theOverloads);
             // register this arithmetic class in the operator map
@@ -385,21 +385,21 @@ public class Uberspect implements JexlUb
         }
 
         @Override
-        public JexlMethod getOperator(JexlArithmetic.Operator operator, Object arg) {
+        public JexlMethod getOperator(JexlOperator operator, Object arg) {
             return overloads.contains(operator) && arg != null
                    ? getMethod(arithmetic, operator.getMethodName(), arg)
                    : null;
         }
 
         @Override
-        public JexlMethod getOperator(JexlArithmetic.Operator operator, Object lhs, Object rhs) {
+        public JexlMethod getOperator(JexlOperator operator, Object lhs, Object rhs) {
             return overloads.contains(operator) && lhs != null && rhs != null
                    ? getMethod(arithmetic, operator.getMethodName(), lhs, rhs)
                    : null;
         }
 
         @Override
-        public boolean overloads(Operator operator) {
+        public boolean overloads(JexlOperator operator) {
             return overloads.contains(operator);
         }
     }
@@ -408,10 +408,10 @@ public class Uberspect implements JexlUb
     public JexlArithmetic.Uberspect getArithmetic(JexlArithmetic arithmetic) {
         JexlArithmetic.Uberspect jau = null;
         if (arithmetic != null) {
-            Set<Operator> ops = operatorMap.get(arithmetic.getClass());
+            Set<JexlOperator> ops = operatorMap.get(arithmetic.getClass());
             if (ops == null) {
-                ops = EnumSet.noneOf(Operator.class);
-                for (JexlArithmetic.Operator op : JexlArithmetic.Operator.values()) {
+                ops = EnumSet.noneOf(JexlOperator.class);
+                for (JexlOperator op : JexlOperator.values()) {
                     Method[] methods = getMethods(arithmetic.getClass(), op.getMethodName());
                     if (methods != null) {
                         for (Method method : methods) {

Modified: commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/introspection/JexlUberspect.java
URL: http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/introspection/JexlUberspect.java?rev=1692852&r1=1692851&r2=1692852&view=diff
==============================================================================
--- commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/introspection/JexlUberspect.java (original)
+++ commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/introspection/JexlUberspect.java Mon Jul 27 10:02:49 2015
@@ -88,6 +88,7 @@ public interface JexlUberspect {
      * Gets an arithmetic operator resolver for a given arithmetic instance.
      * @param arithmetic the arithmetic instance
      * @return the arithmetic uberspect or null if no operator method were override
+     * @since 3.0
      */
     JexlArithmetic.Uberspect getArithmetic(JexlArithmetic arithmetic);
 

Modified: commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/parser/Parser.jjt
URL: http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/parser/Parser.jjt?rev=1692852&r1=1692851&r2=1692852&view=diff
==============================================================================
--- commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/parser/Parser.jjt (original)
+++ commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/parser/Parser.jjt Mon Jul 27 10:02:49 2015
@@ -157,17 +157,27 @@ PARSER_END(Parser)
 }
 
 <*> TOKEN : { /* OPERATORS */
-      < assign : "=" >
-    | < mod : "%" | "mod" >
-    | < div : "/" | "div" >
-    | < not : "!" | "not" >
+      < plus_assign : "+=" >
+    | < minus_assign : "-=" >
+    | < mult_assign : "*=" >
+    | < div_assign : "/=" >
+    | < mod_assign : "%=" >
+    | < and_assign : "&=" >
+    | < or_assign : "|=" >
+    | < xor_assign : "^=" >
+
+    | < assign : "=" >
     | < plus : "+" >
     | < minus : "-" >
     | < mult : "*" >
-    | < tilda : "~" >
+    | < div : "/" | "div" >
+    | < mod : "%" | "mod" >
+    | < not : "!" | "not" >
     | < and : "&" >
     | < or : "|" >
     | < xor : "^" >
+
+    | < tilda : "~" >
     | < range : ".." >
 }
 
@@ -368,7 +378,26 @@ void Expression() #void : {}
 
 void AssignmentExpression() #void : {}
 {
-    ConditionalExpression() [ LOOKAHEAD( <assign> ) <assign> Expression() #Assignment(2) ]
+  ConditionalExpression()
+  ( LOOKAHEAD(2) (
+    <plus_assign>  Expression() #SetAddNode(2)
+  |
+    <mult_assign>  Expression() #SetMultNode(2)
+  |
+    <div_assign>  Expression() #SetDivNode(2)
+  |
+    <mod_assign>  Expression() #SetModNode(2)
+  |
+    <and_assign>  Expression() #SetAndNode(2)
+  |
+    <or_assign>  Expression() #SetOrNode(2)
+  |
+    <xor_assign> Expression() #SetXorNode(2)
+  |
+    <minus_assign>  Expression() #SetSubNode(2)
+  |
+    <assign> Expression() #Assignment(2)
+  ) )*
 }
 
 /***************************************
@@ -481,17 +510,17 @@ void MultiplicativeExpression() #void :
 
 void UnaryExpression() #void : {}
 {
-  <minus> UnaryExpression() #UnaryMinusNode(1)
-|
-  <tilda> UnaryExpression() #BitwiseComplNode(1)
-|
-  <not> UnaryExpression() #NotNode(1)
-|
-  <EMPTY> UnaryExpression() #EmptyFunction(1)
-|
-  <SIZE> UnaryExpression() #SizeFunction(1)
-|
-  ValueExpression()
+    <minus> UnaryExpression() #UnaryMinusNode(1)
+  |
+    <tilda> UnaryExpression() #BitwiseComplNode(1)
+  |
+    <not> UnaryExpression() #NotNode(1)
+  |
+    <EMPTY> UnaryExpression() #EmptyFunction(1)
+  |
+    <SIZE> UnaryExpression() #SizeFunction(1)
+  |
+    ValueExpression()
 }
 
 

Modified: commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/parser/ParserVisitor.java
URL: http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/parser/ParserVisitor.java?rev=1692852&r1=1692851&r2=1692852&view=diff
==============================================================================
--- commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/parser/ParserVisitor.java (original)
+++ commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/parser/ParserVisitor.java Mon Jul 27 10:02:49 2015
@@ -159,4 +159,20 @@ public abstract class ParserVisitor {
     protected abstract Object visit(ASTArguments node, Object data);
 
     protected abstract Object visit(ASTReferenceExpression node, Object data);
+
+    protected abstract Object visit(ASTSetAddNode node, Object data);
+
+    protected abstract Object visit(ASTSetSubNode node, Object data);
+
+    protected abstract Object visit(ASTSetMultNode node, Object data);
+
+    protected abstract Object visit(ASTSetDivNode node, Object data);
+
+    protected abstract Object visit(ASTSetModNode node, Object data);
+
+    protected abstract Object visit(ASTSetAndNode node, Object data);
+
+    protected abstract Object visit(ASTSetOrNode node, Object data);
+
+    protected abstract Object visit(ASTSetXorNode node, Object data);
 }

Modified: commons/proper/jexl/trunk/src/site/xdoc/changes.xml
URL: http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/site/xdoc/changes.xml?rev=1692852&r1=1692851&r2=1692852&view=diff
==============================================================================
--- commons/proper/jexl/trunk/src/site/xdoc/changes.xml (original)
+++ commons/proper/jexl/trunk/src/site/xdoc/changes.xml Mon Jul 27 10:02:49 2015
@@ -26,6 +26,9 @@
     </properties>
     <body>
         <release version="3.0" date="unreleased">
+            <action dev="henrib" type="add" issue="JEXL-170">
+                Implement assignment operators
+            </action>
             <action dev="henrib" type="fix" issue="JEXL-169" due-to="Robert Neßelrath">
                 A string is wrongly identified as FloatingPointNumber
             </action>

Modified: commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/ArithmeticOperatorTest.java
URL: http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/ArithmeticOperatorTest.java?rev=1692852&r1=1692851&r2=1692852&view=diff
==============================================================================
--- commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/ArithmeticOperatorTest.java (original)
+++ commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/ArithmeticOperatorTest.java Mon Jul 27 10:02:49 2015
@@ -26,6 +26,7 @@ import java.util.Set;
 import java.util.SortedSet;
 import java.util.TreeSet;
 import org.apache.commons.jexl3.junit.Asserter;
+import java.util.Arrays;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
@@ -127,6 +128,14 @@ public class ArithmeticOperatorTest exte
             return values.iterator();
         }
 
+        public boolean contains(int i) {
+            return values.contains(i);
+        }
+
+        public boolean contains(int[] i) {
+            return values.containsAll(Arrays.asList(i));
+        }
+
         public boolean startsWith(int i) {
             return values.first().equals(i);
         }

Modified: commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/ArithmeticTest.java
URL: http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/ArithmeticTest.java?rev=1692852&r1=1692851&r2=1692852&view=diff
==============================================================================
--- commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/ArithmeticTest.java (original)
+++ commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/ArithmeticTest.java Mon Jul 27 10:02:49 2015
@@ -16,6 +16,7 @@
  */
 package org.apache.commons.jexl3;
 
+import static org.apache.commons.jexl3.JexlArithmetic.FLOAT_PATTERN;
 import org.apache.commons.jexl3.junit.Asserter;
 
 import java.io.ByteArrayInputStream;
@@ -27,7 +28,6 @@ import java.math.BigDecimal;
 import java.math.BigInteger;
 import javax.xml.parsers.DocumentBuilder;
 import javax.xml.parsers.DocumentBuilderFactory;
-import static org.apache.commons.jexl3.JexlArithmetic.FLOAT_PATTERN;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
@@ -412,7 +412,6 @@ public class ArithmeticTest extends Jexl
         Assert.assertTrue((Boolean) result);
     }
 
-
     @Test
     public void testAddWithStringsLenient() throws Exception {
         JexlEngine jexl = new JexlBuilder().arithmetic(new JexlArithmetic(false)).create();
@@ -568,7 +567,7 @@ public class ArithmeticTest extends Jexl
     }
 
     public static class Var {
-        final int value;
+        int value;
 
         Var(int v) {
             value = v;
@@ -630,15 +629,15 @@ public class ArithmeticTest extends Jexl
             return new Var(-arg.value);
         }
 
-        public Var bitwiseAnd(Var lhs, Var rhs) {
+        public Var and(Var lhs, Var rhs) {
             return new Var(lhs.value & rhs.value);
         }
 
-        public Var bitwiseOr(Var lhs, Var rhs) {
+        public Var or(Var lhs, Var rhs) {
             return new Var(lhs.value | rhs.value);
         }
 
-        public Var bitwiseXor(Var lhs, Var rhs) {
+        public Var xor(Var lhs, Var rhs) {
             return new Var(lhs.value ^ rhs.value);
         }
 
@@ -654,7 +653,7 @@ public class ArithmeticTest extends Jexl
             return lhs.toString().endsWith(rhs.toString());
         }
 
-        public Var bitwiseComplement(Var arg) {
+        public Var complement(Var arg) {
             return new Var(~arg.value);
         }
 
@@ -683,6 +682,7 @@ public class ArithmeticTest extends Jexl
         JexlEngine jexl = new JexlBuilder().cache(64).arithmetic(new ArithmeticPlus(false)).create();
         JexlContext jc = new EmptyTestContext();
         runOverload(jexl, jc);
+        runOverload(jexl, jc);
     }
 
     @Test
@@ -854,15 +854,21 @@ public class ArithmeticTest extends Jexl
         result = script.execute(jc, 3155, 15);
         Assert.assertFalse((Boolean) result);
         result = script.execute(jc, new Var(3155), new Var(15));
+        Assert.assertFalse((Boolean) result);
+        result = script.execute(jc, new Var(15), new Var(3155));
         Assert.assertTrue((Boolean) result);
 
         script = jexl.createScript("(x, y)->{ x !~ y }");
         result = script.execute(jc, 3115, 15);
         Assert.assertTrue((Boolean) result);
         result = script.execute(jc, new Var(3155), new Var(15));
+        Assert.assertTrue((Boolean) result);
+        result = script.execute(jc, new Var(15), new Var(3155));
         Assert.assertFalse((Boolean) result);
+
     }
 
+
     public static class Arithmetic132 extends JexlArithmetic {
         public Arithmetic132() {
             super(false);

Modified: commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/ArrayAccessTest.java
URL: http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/ArrayAccessTest.java?rev=1692852&r1=1692851&r2=1692852&view=diff
==============================================================================
--- commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/ArrayAccessTest.java (original)
+++ commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/ArrayAccessTest.java Mon Jul 27 10:02:49 2015
@@ -22,7 +22,9 @@ import java.util.List;
 import java.util.Map;
 
 import org.apache.commons.jexl3.junit.Asserter;
+import org.junit.Assert;
 import org.junit.Before;
+import org.junit.Test;
 
 
 /**

Modified: commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/IssuesTest.java
URL: http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/IssuesTest.java?rev=1692852&r1=1692851&r2=1692852&view=diff
==============================================================================
--- commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/IssuesTest.java (original)
+++ commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/IssuesTest.java Mon Jul 27 10:02:49 2015
@@ -22,14 +22,26 @@ import java.math.MathContext;
 import java.util.HashMap;
 import java.util.Map;
 import org.apache.commons.jexl3.internal.introspection.Uberspect;
+import java.io.ByteArrayInputStream;
 import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
 import java.net.URL;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
+import org.w3c.dom.Attr;
+import org.w3c.dom.Document;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+import org.xml.sax.SAXException;
+//import org.apache.commons.beanutils.LazyDynaMap;
 
 /**
  * Test cases for reported issue .
@@ -1051,16 +1063,19 @@ public class IssuesTest extends JexlTest
 
     public static class Question42 extends MapContext {
         public String functionA(String arg) {
-            return "a".equals(arg)? "A" : "";
+            return "a".equals(arg) ? "A" : "";
         }
+
         public String functionB(String arg) {
-            return "b".equals(arg)? "B" : "";
+            return "b".equals(arg) ? "B" : "";
         }
+
         public String functionC(String arg) {
-            return "c".equals(arg)? "C" : "";
+            return "c".equals(arg) ? "C" : "";
         }
+
         public String functionD(String arg) {
-            return "d".equals(arg)? "D" : "";
+            return "d".equals(arg) ? "D" : "";
         }
     }
 
@@ -1069,15 +1084,23 @@ public class IssuesTest extends JexlTest
             super(false);
         }
 
-        public Object bitwiseAnd(String lhs, String rhs) {
-            if (rhs.isEmpty()) { return "";  }
-            if (lhs.isEmpty()) { return ""; }
+        public Object and(String lhs, String rhs) {
+            if (rhs.isEmpty()) {
+                return "";
+            }
+            if (lhs.isEmpty()) {
+                return "";
+            }
             return lhs + rhs;
         }
 
-        public Object bitwiseOr(String lhs, String rhs) {
-            if (rhs.isEmpty()) { return lhs; }
-            if (lhs.isEmpty()) { return rhs; }
+        public Object or(String lhs, String rhs) {
+            if (rhs.isEmpty()) {
+                return lhs;
+            }
+            if (lhs.isEmpty()) {
+                return rhs;
+            }
             return lhs + rhs;
         }
     }
@@ -1109,4 +1132,117 @@ public class IssuesTest extends JexlTest
         Object r1 = jexl.createExpression("463.0B * 0.1B").evaluate(jc);
         Assert.assertEquals(java.math.BigDecimal.class, r1.getClass());
     }
+
+//
+//
+//	@Test
+//	public void testUnderscoreInName() {
+//        JexlEngine jexl = new Engine();
+//        String jexlExp = "(x.length_mm * x.width)";
+//        JexlExpression e = jexl.createExpression( jexlExp );
+//        JexlContext jc = new MapContext();
+//
+//        LazyDynaMap object = new LazyDynaMap();
+//        object.set("length_mm", "10.0");
+//        object.set("width", "5.0");
+//
+//        jc.set("x", object );
+//
+//	    Assert.assertEquals(null, ((Double)e.evaluate(jc)).doubleValue(), 50d, 0d);
+//   }
+//
+//	@Test
+//	public void testFullStopInName() {
+//        JexlEngine jexl = new Engine();
+//        String jexlExp = "(x.length.mm * x.width)";
+//        JexlExpression e = jexl.createExpression( jexlExp );
+//        JexlContext jc = new MapContext();
+//
+//        LazyDynaMap object = new LazyDynaMap();
+//        object.set("length.mm", "10.0");
+//        object.set("width", "5.0");
+//
+//        Assert.assertEquals(null, object.get("length.mm"), "10.0");
+//
+//        jc.set("x", object );
+//
+//	    Assert.assertEquals(null, ((Double)e.evaluate(jc)).doubleValue(), 50d, 0d);
+//	}
+//
+//	@Test
+//	public void testFullStopInNameMakingSubObject() {
+//        JexlEngine jexl = new Engine();
+//        String jexlExp = "(x.length.mm * x.width)";
+//        JexlExpression e = jexl.createExpression( jexlExp );
+//        JexlContext jc = new MapContext();
+//
+//        LazyDynaMap object = new LazyDynaMap();
+//        LazyDynaMap subObject = new LazyDynaMap();
+//        object.set("length", subObject);
+//        subObject.set("mm", "10.0");
+//        object.set("width", "5.0");
+//
+//        jc.set("x", object );
+//
+//	    Assert.assertEquals(null, ((Double)e.evaluate(jc)).doubleValue(), 50d, 0d);
+//	}
+
+
+    @Test
+    public void test161() throws Exception {
+        final JexlEngine jexl = new Engine();
+        final JexlContext jc = new MapContext();
+
+        Document xml = getDocument("<node info='123'/>");
+        NamedNodeMap nnm = xml.getLastChild().getAttributes();
+        Attr info = (Attr) nnm.getNamedItem("info");
+        Assert.assertEquals("123", info.getValue());
+
+        jc.set("x", xml.getLastChild());
+        final String y = "456";
+        jc.set("y", y);
+        JexlScript s = jexl.createScript("x.attribute.info = y");
+        Object r = s.execute(jc);
+        nnm = xml.getLastChild().getAttributes();
+        info = (Attr) nnm.getNamedItem("info");
+        Assert.assertEquals(y, r);
+        Assert.assertEquals(y, info.getValue());
+    }
+
+    public static class XmlArithmetic extends JexlArithmetic {
+        public XmlArithmetic(boolean lenient) {
+            super(lenient);
+        }
+        public boolean empty(org.w3c.dom.Element elt) {
+            return !elt.hasAttributes() && !elt.hasChildNodes();
+        }
+    }
+
+    @Test
+    public void test162() throws Exception {
+        JexlEngine jexl = //new JexlBuilder().arithmetic(new JexlArithmetic(false)).create();
+new JexlBuilder().arithmetic(new XmlArithmetic(false)).create();
+        JexlScript s0 = jexl.createScript("x.empty()", "x");
+        Document xml;
+        Node x;
+        Boolean r;
+        xml = getDocument("<node info='123'/>");
+        x = xml.getLastChild();
+        r = (Boolean) s0.execute(null, x);
+        Assert.assertFalse(r);
+        xml = getDocument("<node>some content</node>");
+        x = xml.getLastChild();
+        r = (Boolean) s0.execute(null, x);
+        Assert.assertFalse(r);
+        xml = getDocument("<node/>");
+        x = xml.getLastChild();
+        r = (Boolean) s0.execute(null, x);
+        Assert.assertTrue(r);
+    }
+
+    private static Document getDocument(String xml) throws IOException, SAXException, ParserConfigurationException {
+        DocumentBuilder xmlBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
+        InputStream stringInputStream = new ByteArrayInputStream(xml.getBytes("UTF-8"));
+        return xmlBuilder.parse(stringInputStream);
+    }
 }

Added: commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/SideEffectTest.java
URL: http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/SideEffectTest.java?rev=1692852&view=auto
==============================================================================
--- commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/SideEffectTest.java (added)
+++ commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/SideEffectTest.java Mon Jul 27 10:02:49 2015
@@ -0,0 +1,404 @@
+/*
+ * 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.jexl3;
+
+import java.util.Map;
+
+import org.apache.commons.jexl3.junit.Asserter;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+
+/**
+ * Tests for array access operator []
+ *
+ * @since 2.0
+ */
+@SuppressWarnings({"UnnecessaryBoxing", "AssertEqualsBetweenInconvertibleTypes"})
+public class SideEffectTest extends JexlTestCase {
+
+    private Asserter asserter;
+
+    public SideEffectTest() {
+        super("SideEffectTest");
+    }
+
+    @Override
+    @Before
+    public void setUp() {
+        asserter = new Asserter(JEXL);
+    }
+
+    @Test
+    public void testSideEffectVar() throws Exception {
+        Map<String,Object> context = asserter.getVariables();
+        Integer i41 = Integer.valueOf(4141);
+        Object foo = i41;
+
+        context.put("foo", foo);
+        asserter.assertExpression("foo += 2", i41 + 2);
+        Assert.assertEquals(context.get("foo"), i41 + 2);
+
+        context.put("foo", foo);
+        asserter.assertExpression("foo -= 2", i41 - 2);
+        Assert.assertEquals(context.get("foo"), i41 - 2);
+
+        context.put("foo", foo);
+        asserter.assertExpression("foo *= 2", i41 * 2);
+        Assert.assertEquals(context.get("foo"), i41 * 2);
+
+        context.put("foo", foo);
+        asserter.assertExpression("foo /= 2", i41 / 2);
+        Assert.assertEquals(context.get("foo"), i41 / 2);
+
+        context.put("foo", foo);
+        asserter.assertExpression("foo %= 2", i41 % 2);
+        Assert.assertEquals(context.get("foo"), i41 % 2);
+
+        context.put("foo", foo);
+        asserter.assertExpression("foo &= 3", (long) (i41 & 3));
+        Assert.assertEquals(context.get("foo"), (long)(i41 & 3));
+
+        context.put("foo", foo);
+        asserter.assertExpression("foo |= 2", (long)(i41 | 2));
+        Assert.assertEquals(context.get("foo"), (long)(i41 | 2));
+
+        context.put("foo", foo);
+        asserter.assertExpression("foo ^= 2", (long)(i41 ^ 2));
+        Assert.assertEquals(context.get("foo"), (long)(i41 ^ 2));
+    }
+
+    @Test
+    public void testSideEffectArray() throws Exception {
+        Integer i41 = Integer.valueOf(4141);
+        Integer i42 = Integer.valueOf(42);
+        Integer i43 = Integer.valueOf(43);
+        String s42 = "fourty-two";
+        String s43 = "fourty-three";
+        Object[] foo = new Object[3];
+        foo[1] = i42;
+        foo[2] = i43;
+        asserter.setVariable("foo", foo);
+        foo[0] = i41;
+        asserter.assertExpression("foo[0] += 2", i41 + 2);
+        Assert.assertEquals(foo[0], i41 + 2);
+        foo[0] = i41;
+        asserter.assertExpression("foo[0] -= 2", i41 - 2);
+        Assert.assertEquals(foo[0], i41 - 2);
+        foo[0] = i41;
+        asserter.assertExpression("foo[0] *= 2", i41 * 2);
+        Assert.assertEquals(foo[0], i41 * 2);
+        foo[0] = i41;
+        asserter.assertExpression("foo[0] /= 2", i41 / 2);
+        Assert.assertEquals(foo[0], i41 / 2);
+        foo[0] = i41;
+        asserter.assertExpression("foo[0] %= 2", i41 % 2);
+        Assert.assertEquals(foo[0], i41 % 2);
+        foo[0] = i41;
+        asserter.assertExpression("foo[0] &= 3", (long) (i41 & 3));
+        Assert.assertEquals(foo[0], (long)(i41 & 3));
+        foo[0] = i41;
+        asserter.assertExpression("foo[0] |= 2", (long)(i41 | 2));
+        Assert.assertEquals(foo[0], (long)(i41 | 2));
+        foo[0] = i41;
+        asserter.assertExpression("foo[0] ^= 2", (long)(i41 ^ 2));
+        Assert.assertEquals(foo[0], (long)(i41 ^ 2));
+    }
+
+    @Test
+    public void testSideEffectAntishArray() throws Exception {
+        Integer i41 = Integer.valueOf(4141);
+        Integer i42 = Integer.valueOf(42);
+        Integer i43 = Integer.valueOf(43);
+        Object[] foo = new Object[3];
+        foo[1] = i42;
+        foo[2] = i43;
+        asserter.setVariable("foo.bar", foo);
+        foo[0] = i41;
+        asserter.assertExpression("foo.bar[0] += 2", i41 + 2);
+        Assert.assertEquals(foo[0], i41 + 2);
+        foo[0] = i41;
+        asserter.assertExpression("foo.bar[0] -= 2", i41 - 2);
+        Assert.assertEquals(foo[0], i41 - 2);
+        foo[0] = i41;
+        asserter.assertExpression("foo.bar[0] *= 2", i41 * 2);
+        Assert.assertEquals(foo[0], i41 * 2);
+        foo[0] = i41;
+        asserter.assertExpression("foo.bar[0] /= 2", i41 / 2);
+        Assert.assertEquals(foo[0], i41 / 2);
+        foo[0] = i41;
+        asserter.assertExpression("foo.bar[0] %= 2", i41 % 2);
+        Assert.assertEquals(foo[0], i41 % 2);
+        foo[0] = i41;
+        asserter.assertExpression("foo.bar[0] &= 3", (long) (i41 & 3));
+        Assert.assertEquals(foo[0], (long)(i41 & 3));
+        foo[0] = i41;
+        asserter.assertExpression("foo.bar[0] |= 2", (long)(i41 | 2));
+        Assert.assertEquals(foo[0], (long)(i41 | 2));
+        foo[0] = i41;
+        asserter.assertExpression("foo.bar[0] ^= 2", (long)(i41 ^ 2));
+        Assert.assertEquals(foo[0], (long)(i41 ^ 2));
+    }
+
+    public static class Foo {
+        int value;
+        Foo(int v) {
+            value = v;
+        }
+
+        @Override
+        public String toString() {
+            return Integer.toString(value);
+        }
+
+        public void setValue(long v) {
+            value = (int) v;
+        }
+        public int getValue() {
+            return value;
+        }
+
+        public void setBar(int x, long v) {
+            value = (int) v + x;
+        }
+
+        public int getBar(int x) {
+            return value + x;
+        }
+    }
+
+    @Test
+    public void testSideEffectBean() throws Exception {
+        Integer i41 = Integer.valueOf(4141);
+        Foo foo = new Foo(0);
+        asserter.setVariable("foo", foo);
+        foo.value = i41;
+        asserter.assertExpression("foo.value += 2", i41 + 2);
+        Assert.assertEquals(foo.value, i41 + 2);
+        foo.value = i41;
+        asserter.assertExpression("foo.value -= 2", i41 - 2);
+        Assert.assertEquals(foo.value, i41 - 2);
+        foo.value = i41;
+        asserter.assertExpression("foo.value *= 2", i41 * 2);
+        Assert.assertEquals(foo.value, i41 * 2);
+        foo.value = i41;
+        asserter.assertExpression("foo.value /= 2", i41 / 2);
+        Assert.assertEquals(foo.value, i41 / 2);
+        foo.value = i41;
+        asserter.assertExpression("foo.value %= 2", i41 % 2);
+        Assert.assertEquals(foo.value, i41 % 2);
+        foo.value = i41;
+        asserter.assertExpression("foo.value &= 3", (long) (i41 & 3));
+        Assert.assertEquals(foo.value, (long)(i41 & 3));
+        foo.value = i41;
+        asserter.assertExpression("foo.value |= 2", (long)(i41 | 2));
+        Assert.assertEquals(foo.value, (long)(i41 | 2));
+        foo.value = i41;
+        asserter.assertExpression("foo.value ^= 2", (long)(i41 ^ 2));
+        Assert.assertEquals(foo.value, (long)(i41 ^ 2));
+    }
+
+    @Test
+    public void testSideEffectBeanContainer() throws Exception {
+        Integer i41 = Integer.valueOf(4141);
+        Foo foo = new Foo(0);
+        asserter.setVariable("foo", foo);
+        foo.value = i41;
+        asserter.assertExpression("foo.bar[0] += 2", i41 + 2);
+        Assert.assertEquals(foo.value, i41 + 2);
+        foo.value = i41;
+        asserter.assertExpression("foo.bar[1] += 2", i41 + 3);
+        Assert.assertEquals(foo.value, i41 + 4);
+        foo.value = i41;
+        asserter.assertExpression("foo.bar[0] -= 2", i41 - 2);
+        Assert.assertEquals(foo.value, i41 - 2);
+        foo.value = i41;
+        asserter.assertExpression("foo.bar[0] *= 2", i41 * 2);
+        Assert.assertEquals(foo.value, i41 * 2);
+        foo.value = i41;
+        asserter.assertExpression("foo.bar[0] /= 2", i41 / 2);
+        Assert.assertEquals(foo.value, i41 / 2);
+        foo.value = i41;
+        asserter.assertExpression("foo.bar[0] %= 2", i41 % 2);
+        Assert.assertEquals(foo.value, i41 % 2);
+        foo.value = i41;
+        asserter.assertExpression("foo.bar[0] &= 3", (long) (i41 & 3));
+        Assert.assertEquals(foo.value, (long)(i41 & 3));
+        foo.value = i41;
+        asserter.assertExpression("foo.bar[0] |= 2", (long)(i41 | 2));
+        Assert.assertEquals(foo.value, (long)(i41 | 2));
+        foo.value = i41;
+        asserter.assertExpression("foo.bar[0] ^= 2", (long)(i41 ^ 2));
+        Assert.assertEquals(foo.value, (long)(i41 ^ 2));
+    }
+
+    @Test
+    public void testArithmeticSelf() throws Exception {
+        JexlEngine jexl = new JexlBuilder().cache(64).arithmetic(new SelfArithmetic(false)).create();
+        JexlContext jc = null;
+        runSelfOverload(jexl, jc);
+        runSelfOverload(jexl, jc);
+    }
+
+    @Test
+    public void testArithmeticSelfNoCache() throws Exception {
+        JexlEngine jexl = new JexlBuilder().cache(0).arithmetic(new SelfArithmetic(false)).create();
+        JexlContext jc = null;
+        runSelfOverload(jexl, jc);
+    }
+
+    protected void runSelfOverload(JexlEngine jexl, JexlContext jc) {
+        JexlScript script;
+        Object result;
+        script = jexl.createScript("(x, y)->{ x += y }");
+        result = script.execute(jc, 3115, 15);
+        Assert.assertEquals(3115 + 15,  result);
+        Var v0 = new Var(3115);
+        result = script.execute(jc, v0, new Var(15));
+        Assert.assertEquals(result, v0);
+        Assert.assertEquals(3115 + 15,  v0.value);
+
+        script = jexl.createScript("(x, y)->{ x -= y}");
+        result = script.execute(jc, 3115, 15);
+        Assert.assertEquals(3115 - 15,  result);
+        Var v1 = new Var(3115);
+        result = script.execute(jc, v1, new Var(15));
+        Assert.assertNotEquals(result, v1); // not a real side effect
+        Assert.assertEquals(3115 - 15,  ((Var) result).value);
+
+        script = jexl.createScript("(x, y)->{ x *= y }");
+        result = script.execute(jc, 3115, 15);
+        Assert.assertEquals(3115 * 15,  result);
+        Var v2 = new Var(3115);
+        result = script.execute(jc, v2, new Var(15));
+        Assert.assertEquals(result, v2);
+        Assert.assertEquals(3115 * 15,  v2.value);
+
+        script = jexl.createScript("(x, y)->{ x /= y }");
+        result = script.execute(jc, 3115, 15);
+        Assert.assertEquals(3115 / 15,  result);
+        Var v3 = new Var(3115);
+        result = script.execute(jc, v3, new Var(15));
+        Assert.assertEquals(result, v3);
+        Assert.assertEquals(3115 / 15,  v3.value);
+
+        script = jexl.createScript("(x, y)->{ x %= y }");
+        result = script.execute(jc, 3115, 15);
+        Assert.assertEquals(3115 % 15,  result);
+        Var v4 = new Var(3115);
+        result = script.execute(jc, v4, new Var(15));
+        Assert.assertEquals(result, v4);
+        Assert.assertEquals(3115 % 15,  v4.value);
+
+        script = jexl.createScript("(x, y)->{ x &= y }");
+        result = script.execute(jc, 3115, 15);
+        Assert.assertEquals(3115L & 15,  result);
+        Var v5 = new Var(3115);
+        result = script.execute(jc, v5, new Var(15));
+        Assert.assertEquals(result, v5);
+        Assert.assertEquals(3115 & 15,  v5.value);
+
+        script = jexl.createScript("(x, y)->{ x |= y }");
+        result = script.execute(jc, 3115, 15);
+        Assert.assertEquals(3115L | 15,  result);
+        Var v6 = new Var(3115);
+        result = script.execute(jc, v6, new Var(15));
+        Assert.assertEquals(result, v6);
+        Assert.assertEquals(3115L | 15,  v6.value);
+
+        script = jexl.createScript("(x, y)->{ x ^= y }");
+        result = script.execute(jc, 3115, 15);
+        Assert.assertEquals(3115L ^ 15,  result);
+        Var v7 = new Var(3115);
+        result = script.execute(jc, v7, new Var(15));
+        Assert.assertEquals(result, v7);
+        Assert.assertEquals(3115L ^ 15,  v7.value);
+    }
+
+    public static class Var {
+        int value;
+
+        Var(int v) {
+            value = v;
+        }
+
+        @Override
+        public String toString() {
+            return Integer.toString(value);
+        }
+    }
+
+    // an arithmetic that performs side effects
+    public static class SelfArithmetic extends JexlArithmetic {
+        public SelfArithmetic(boolean strict) {
+            super(strict);
+        }
+
+        public JexlOperator selfAdd(Var lhs, Var rhs) {
+            lhs.value += rhs.value;
+            return JexlOperator.ASSIGN;
+        }
+
+        // for kicks, this one does not side effect but overloads nevertheless
+        public Var selfSubtract(Var lhs, Var rhs) {
+            return new Var(lhs.value - rhs.value);
+        }
+
+        public JexlOperator selfDivide(Var lhs, Var rhs) {
+            lhs.value /= rhs.value;
+            return JexlOperator.ASSIGN;
+        }
+
+        public JexlOperator selfMultiply(Var lhs, Var rhs) {
+            lhs.value *= rhs.value;
+            return JexlOperator.ASSIGN;
+        }
+
+        public JexlOperator selfMod(Var lhs, Var rhs) {
+            lhs.value %= rhs.value;
+            return JexlOperator.ASSIGN;
+        }
+
+        public Var and(Var lhs, Var rhs) {
+            return new Var(lhs.value & rhs.value);
+        }
+
+        public JexlOperator selfAnd(Var lhs, Var rhs) {
+            lhs.value &= rhs.value;
+            return JexlOperator.ASSIGN;
+        }
+
+        public Var or(Var lhs, Var rhs) {
+            return new Var(lhs.value | rhs.value);
+        }
+
+        public JexlOperator selfOr(Var lhs, Var rhs) {
+            lhs.value |= rhs.value;
+            return JexlOperator.ASSIGN;
+        }
+
+        public Var xor(Var lhs, Var rhs) {
+            return new Var(lhs.value ^ rhs.value);
+        }
+
+        public JexlOperator selfXor(Var lhs, Var rhs) {
+            lhs.value ^= rhs.value;
+            return JexlOperator.ASSIGN;
+        }
+    }
+}

Propchange: commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/SideEffectTest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/junit/Asserter.java
URL: http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/junit/Asserter.java?rev=1692852&r1=1692851&r2=1692852&view=diff
==============================================================================
--- commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/junit/Asserter.java (original)
+++ commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/junit/Asserter.java Mon Jul 27 10:02:49 2015
@@ -151,4 +151,21 @@ public class Asserter extends Assert {
     public Object removeVariable(String name) {
         return variables.remove(name);
     }
+
+    /**
+     * Gets a variable of a certain name.
+     *
+     * @param name variable name
+     * @return value variable value
+     */
+    public Object getVariable(String name) {
+        return variables.get(name);
+    }
+
+    /**
+     * @return the variables map
+     */
+    public Map<String, Object> getVariables() {
+        return variables;
+    }
 }