Author: henrib
Date: Wed Oct  1 08:57:12 2014
New Revision: 1628650

URL: http://svn.apache.org/r1628650
Log:
Fixing JEXL-146;
Better error reporting and improved arithmetic overloading;
Clean up javadoc/checkstyle;

Modified:
    
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/JexlArithmetic.java
    
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/JexlContext.java
    
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/JexlException.java
    
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/JexlInfo.java
    
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/JxltEngine.java
    
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/ArrayBuilder.java
    
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/Closure.java
    
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/Debugger.java
    
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java
    
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/Scope.java
    
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/TemplateEngine.java
    
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/introspection/MethodExecutor.java
    
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/introspection/MethodKey.java
    
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/introspection/PropertySetExecutor.java
    
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/introspection/Uberspect.java
    
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/parser/ASTArrayLiteral.java
    
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/parser/ASTJexlLambda.java
    
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/parser/ASTJexlScript.java
    commons/proper/jexl/trunk/src/site/xdoc/changes.xml
    
commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/ArithmeticTest.java
    
commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/ClassCreator.java
    
commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/ClassCreatorTest.java
    
commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/ExceptionTest.java
    
commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/LambdaTest.java
    
commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/introspection/SandboxTest.java
    
commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/scripting/JexlScriptEngineTest.java

Modified: 
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/JexlArithmetic.java
URL: 
http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/JexlArithmetic.java?rev=1628650&r1=1628649&r2=1628650&view=diff
==============================================================================
--- 
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/JexlArithmetic.java
 (original)
+++ 
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/JexlArithmetic.java
 Wed Oct  1 08:57:12 2014
@@ -44,28 +44,47 @@ import java.math.MathContext;
  */
 public class JexlArithmetic {
     /**
-     * The overridable operators.
+     * The overload-able operators.
+     * Note that logical and (ie &&) and logical or (ie ||) are not in this 
list to avoid breaking
+     * their shortcut semantics.
      * @since 3.0
      */
     public enum Operator {
+        /** add(x, y). */
         ADD("+", "add", 2),
+        /** subtract(x, y). */
         SUBTRACT("-", "subtract", 2),
+        /** multiply(x, y). */
         MULTIPLY("*", "multiply", 2),
+        /** divide(x, y). */
         DIVIDE("/", "divide", 2),
+        /** mod(x, y). */
         MOD("%", "mod", 2),
+        /** bitwiseAnd(x, y). */
         AND("&", "bitwiseAnd", 2),
+        /** bitwiseOr(x, y). */
         OR("|", "bitwiseOr", 2),
+        /** bitwiseXor(x, y). */
         XOR("^", "bitwiseXor", 2),
+        /** logicalNot(x). */
         NOT("!", "logicalNot", 1),
+        /** bitiwiseComplement(x). */
         COMPLEMENT("-", "bitwiseComplement", 1),
+        /** equals(x, y). */
         EQ("==", "equals", 2),
+        /** lessThan(x, y). */
         LT("<", "lessThan", 2),
+        /** lessThanOrEqual(x, y). */
         LTE("<=", "lessThanOrEqual", 2),
+        /** greaterThan(x, y). */
         GT(">", "greaterThan", 2),
+        /** greaterThanOrEqual(x, y). */
         GTE(">=", "greaterThanOrEqual", 2),
-        ABS("+", "abs", 1),
+        /** negate(x). */
         NEGATE("-", "negate", 1),
+        /** size(x). */
         SIZE("size", "size", 1),
+        /** empty(x). */
         EMPTY("empty", "empty", 1);
 
         /**
@@ -85,12 +104,12 @@ public class JexlArithmetic {
          * Creates an operator.
          * @param o the operator name
          * @param m the method name associated to this operator in a 
JexlArithmetic
-         * @param arity the number of parameters for the method
+         * @param argc the number of parameters for the method
          */
-        Operator(String o, String m, int arity) {
+        Operator(String o, String m, int argc) {
             this.operator = o;
             this.methodName = m;
-            this.arity = arity;
+            this.arity = argc;
         }
 
         /**
@@ -124,13 +143,19 @@ public class JexlArithmetic {
      */
     public interface Uberspect {
         /**
+         * Checks whether this uberspect has overloads for a given operator.
+         * @param operator the operator to check
+         * @return true if an overload exists, false otherwise
+         */
+        boolean overloads(JexlArithmetic.Operator operator);
+
+        /**
          * Gets the most specific method for a monadic operator.
          * @param operator the operator
          * @param arg the argument
          * @return the most specific method or null if no specific override 
could be found
          */
         JexlMethod getOperator(JexlArithmetic.Operator operator, Object arg);
-        Object tryInvokeOperator(JexlArithmetic.Operator operator, Object arg);
 
         /**
          * Gets the most specific method for a diadic operator.
@@ -140,10 +165,9 @@ public class JexlArithmetic {
          * @return the most specific method or null if no specific override 
could be found
          */
         JexlMethod getOperator(JexlArithmetic.Operator operator, Object lhs, 
Object rhs);
-        Object tryInvokeOperator(JexlArithmetic.Operator operator, Object lhs, 
Object rhs);
     }
 
-    /** Maker class for null operand exceptions. */
+    /** Marker class for null operand exceptions. */
     public static class NullOperand extends ArithmeticException {}
     /** Double.MAX_VALUE as BigDecimal. */
     protected static final BigDecimal BIGD_DOUBLE_MAX_VALUE = 
BigDecimal.valueOf(Double.MAX_VALUE);
@@ -190,7 +214,7 @@ public class JexlArithmetic {
     public JexlArithmetic options(JexlEngine.Options options) {
         boolean ostrict = options.isStrictArithmetic() == null
                           ? this.strict
-                          : options.isStrictArithmetic().booleanValue();
+                          : options.isStrictArithmetic();
         MathContext bigdContext = options.getArithmeticMathContext();
         if (bigdContext == null) {
             bigdContext = mathContext;

Modified: 
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/JexlContext.java
URL: 
http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/JexlContext.java?rev=1628650&r1=1628649&r2=1628650&view=diff
==============================================================================
--- 
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/JexlContext.java
 (original)
+++ 
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/JexlContext.java
 Wed Oct  1 08:57:12 2014
@@ -100,10 +100,10 @@ public interface JexlContext {
      * A marker interface that indicates the interpreter to put this context 
in the JexlEngine thread local context
      * instance during evaluation.
      * This allows user functions or methods to access the context during a 
call.
-     * Note that the usual caveats wrt using thread local apply 
(caching/leaking references, etc.); in particular, keeping
-     * a reference to such a context is to be considered with great care and 
caution.
-     * It should also be noted that sharing such a context between threads 
should implicate synchronizing variable access
-     * in the implementation class.
+     * Note that the usual caveats wrt using thread local apply 
(caching/leaking references, etc.); in particular,
+     * keeping a reference to such a context is to be considered with great 
care and caution.
+     * It should also be noted that sharing such a context between threads 
should implicate synchronizing variable
+     * accessing the implementation class.
      * @see JexlEngine#setThreadContext()
      * @see JexlEngine#getThreadContext()
      */

Modified: 
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/JexlException.java
URL: 
http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/JexlException.java?rev=1628650&r1=1628649&r2=1628650&view=diff
==============================================================================
--- 
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/JexlException.java
 (original)
+++ 
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/JexlException.java
 Wed Oct  1 08:57:12 2014
@@ -83,9 +83,36 @@ public class JexlException extends Runti
      * @return the information
      */
     public JexlInfo getInfo() {
-        if (info != null && mark != null) {
+        return getInfo(mark, info);
+    }
+
+    /**
+     * Creates a string builder pre-filled with common error information (if 
possible).
+     * @param node the node
+     * @return a string builder
+     */
+    private static StringBuilder errorAt(JexlNode node) {
+        JexlInfo info = node != null? getInfo(node, node.jexlInfo()) : null;
+        StringBuilder msg = new StringBuilder();
+        if (info != null) {
+            msg.append(info.toString());
+        } else {
+            msg.append("?:");
+        }
+        msg.append(' ');
+        return msg;
+    }
+
+    /**
+     * Gets the most specific information attached to a node.
+     * @param node the node
+     * @param info the information
+     * @return the information or null
+     */
+    public static JexlInfo getInfo(JexlNode node, JexlInfo info) {
+        if (info != null && node != null) {
             final Debugger dbg = new Debugger();
-            if (dbg.debug(mark)) {
+            if (dbg.debug(node)) {
                 return new JexlInfo(info) {
                     @Override
                     public JexlInfo.Detail getDetail() {
@@ -300,12 +327,26 @@ public class JexlException extends Runti
      */
     public static class Variable extends JexlException {
         /**
+         * Undefined variable flag.
+         */
+        private final boolean undefined;
+        /**
          * Creates a new Variable exception instance.
          * @param node the offending ASTnode
          * @param var  the unknown variable
+         * @param undef whether the variable is undefined or evaluated as null
          */
-        public Variable(JexlNode node, String var) {
+        public Variable(JexlNode node, String var, boolean undef) {
             super(node, var, null);
+            undefined = undef;
+        }
+
+        /**
+         * Whether the variable causing an error is undefined or evaluated as 
null.
+         * @return true if undefined, false otherwise
+         */
+        public boolean isUndefined() {
+            return undefined;
         }
 
         /**
@@ -317,8 +358,27 @@ public class JexlException extends Runti
 
         @Override
         protected String detailedMessage() {
-            return "undefined variable " + getVariable();
+            return (undefined? "undefined" : "null value") + " variable " + 
getVariable();
+        }
+    }
+
+    /**
+     * Generates a message for a variable error.
+     * @param node the node where the error occurred
+     * @param variable the variable
+     * @param undef whether the variable is null or undefined
+     * @return the error message
+     */
+    public static String variableError(JexlNode node, String variable, boolean 
undef) {
+        StringBuilder msg = errorAt(node);
+        if (undef) {
+            msg.append("undefined");
+        } else {
+            msg.append("null value");
         }
+        msg.append(" variable ");
+        msg.append(variable);
+        return msg.toString();
     }
 
     /**
@@ -359,6 +419,20 @@ public class JexlException extends Runti
     }
 
     /**
+     * Generates a message for an unsolvable property error.
+     * @param node the node where the error occurred
+     * @param var the variable
+     * @return the error message
+     */
+    public static String propertyError(JexlNode node, String var) {
+        StringBuilder msg = errorAt(node);
+        msg.append("unsolvable property '");
+        msg.append(var);
+        msg.append('\'');
+        return msg.toString();
+    }
+
+    /**
      * Thrown when a method or ctor is unknown, ambiguous or inaccessible.
      * @since 3.0
      */
@@ -366,11 +440,10 @@ public class JexlException extends Runti
         /**
          * Creates a new Method exception instance.
          * @param node  the offending ASTnode
-         * @param name  the unknown method
-         * @param cause the exception causing the error
+         * @param name  the method name
          */
-        public Method(JexlNode node, String name, Throwable cause) {
-            super(node, name, cause);
+        public Method(JexlNode node, String name) {
+            super(node, name);
         }
 
         /**
@@ -397,6 +470,20 @@ public class JexlException extends Runti
     }
 
     /**
+     * Generates a message for a unsolvable method error.
+     * @param node the node where the error occurred
+     * @param method the method name
+     * @return the error message
+     */
+    public static String methodError(JexlNode node, String method) {
+        StringBuilder msg = errorAt(node);
+        msg.append("unsolvable function/method '");
+        msg.append(method);
+        msg.append('\'');
+        return msg.toString();
+    }
+
+    /**
      * Thrown to return a value.
      * @since 3.0
      */

Modified: 
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/JexlInfo.java
URL: 
http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/JexlInfo.java?rev=1628650&r1=1628649&r2=1628650&view=diff
==============================================================================
--- 
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/JexlInfo.java 
(original)
+++ 
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/JexlInfo.java 
Wed Oct  1 08:57:12 2014
@@ -37,14 +37,20 @@ public class JexlInfo {
     }
 
     /**
-     * Describes errors more precicely.
+     * Describes errors more precisely.
      */
-    public static interface Detail {
-        /** The start column on the line that triggered the error. */
+    public interface Detail {
+        /**
+         * @return he start column on the line that triggered the error
+         */
         int start();
-        /** The end column on the line that triggered the error. */
+        /**
+         * @return the end column on the line that triggered the error
+         */
         int end();
-        /** The actual part of code that triggered the error. */
+        /**
+         * @return the actual part of code that triggered the error
+         */
         @Override
         String toString();
     }
@@ -61,10 +67,20 @@ public class JexlInfo {
         column = c;
     }
 
+    /**
+     * Creates info reusing the name.
+     * @param l the line
+     * @param c the column
+     * @return a new info instance
+     */
     public JexlInfo at(int l, int c) {
         return new JexlInfo(name, l, c);
     }
 
+    /**
+     * The copy constructor.
+     * @param copy the instance to copy
+     */
     protected JexlInfo(JexlInfo copy) {
         name = copy.getName();
         line = copy.getLine();

Modified: 
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/JxltEngine.java
URL: 
http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/JxltEngine.java?rev=1628650&r1=1628649&r2=1628650&view=diff
==============================================================================
--- 
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/JxltEngine.java
 (original)
+++ 
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/JxltEngine.java
 Wed Oct  1 08:57:12 2014
@@ -16,7 +16,6 @@
  */
 package org.apache.commons.jexl3;
 
-import org.apache.commons.jexl3.internal.TemplateEngine;
 import java.io.Reader;
 import java.io.StringReader;
 import java.io.Writer;
@@ -49,6 +48,7 @@ public abstract class JxltEngine {
 
         /**
          * Creates an Exception.
+         * @param info the contextual information
          * @param msg the exception message
          * @param cause the exception cause
          */

Modified: 
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/ArrayBuilder.java
URL: 
http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/ArrayBuilder.java?rev=1628650&r1=1628649&r2=1628650&view=diff
==============================================================================
--- 
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/ArrayBuilder.java
 (original)
+++ 
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/ArrayBuilder.java
 Wed Oct  1 08:57:12 2014
@@ -26,8 +26,9 @@ import java.util.Map;
  * Helper class to create typed arrays.
  */
 public class ArrayBuilder implements JexlArithmetic.ArrayBuilder {
-    /** The boxing types to primitive conversion map. */
+    /** The number of primitive types. */
     private static final int PRIMITIVE_SIZE = 8;
+    /** The boxing types to primitive conversion map. */
     private static final Map<Class<?>, Class<?>> BOXING_CLASSES;
     static {
         BOXING_CLASSES = new IdentityHashMap<Class<?>, 
Class<?>>(PRIMITIVE_SIZE);
@@ -41,6 +42,11 @@ public class ArrayBuilder implements Jex
         BOXING_CLASSES.put(Short.class, Short.TYPE);
     }
 
+    /**
+     * Gets the primitive type of a given class (when it exists).
+     * @param parm a class
+     * @return the primitive type or null it the argument is not unboxable
+     */
     private static Class<?> unboxingClass(Class<?> parm) {
         Class<?> prim = BOXING_CLASSES.get(parm);
         return prim == null ? parm : prim;

Modified: 
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/Closure.java
URL: 
http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/Closure.java?rev=1628650&r1=1628649&r2=1628650&view=diff
==============================================================================
--- 
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/Closure.java
 (original)
+++ 
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/Closure.java
 Wed Oct  1 08:57:12 2014
@@ -44,12 +44,38 @@ public class Closure extends Script {
     }
 
     @Override
+    public String toString() {
+        return getParsedText();
+    }
+
+    @Override
     public String getParsedText() {
         Debugger debug = new Debugger();
-        debug.debug(script);
+        debug.debug(script, false);
         return debug.toString();
     }
 
+    /**
+     * Sets the hoisted index of a given symbol, ie the target index of a 
parent hoisted symbol in this closure's frame.
+     * <p>This is meant to allow a locally defined function to "see" and call 
itself as a local (hoisted) variable;
+     * in other words, this allows recursive call of a function.
+     * @param symbol the symbol index (in the caller of this closure)
+     * @param value the value to set in the local frame
+     */
+    public void setHoisted(int symbol, Object value) {
+        if (script instanceof ASTJexlLambda) {
+            ASTJexlLambda lambda = (ASTJexlLambda) script;
+            Scope scope = lambda.getScope();
+            if (scope != null) {
+                Integer reg = scope.getHoisted(symbol);
+                if (reg != null) {
+                    frame.set(reg, value);
+                    return;
+                }
+            }
+        }
+    }
+
     @Override
     public Object evaluate(JexlContext context) {
         return execute(context, (Object[])null);
@@ -74,10 +100,11 @@ public class Closure extends Script {
 
     @Override
     public Callable<Object> callable(JexlContext context, Object... args) {
+        Scope.Frame local = null;
         if (frame != null) {
-            frame.assign(args);
+            local = frame.assign(args);
         }
-        final Interpreter interpreter = jexl.createInterpreter(context, frame);
+        final Interpreter interpreter = jexl.createInterpreter(context, local);
         interpreter.functors = functors;
         return new Callable<Object>() {
             /** Use interpreter as marker for not having run. */
@@ -87,7 +114,7 @@ public class Closure extends Script {
             public Object call() throws Exception {
                 if (result == interpreter) {
                     JexlNode block = 
script.jjtGetChild(script.jjtGetNumChildren() - 1);
-                    return interpreter.interpret(block);
+                    result = interpreter.interpret(block);
                 }
                 return result;
             }

Modified: 
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/Debugger.java
URL: 
http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/Debugger.java?rev=1628650&r1=1628649&r2=1628650&view=diff
==============================================================================
--- 
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/Debugger.java
 (original)
+++ 
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/Debugger.java
 Wed Oct  1 08:57:12 2014
@@ -144,6 +144,9 @@ public final class Debugger extends Pars
      * @return true if the cause was located, false otherwise
      */
     public boolean debug(JexlNode node) {
+        return debug(node, true);
+    }
+    public boolean debug(JexlNode node, boolean r) {
         start = 0;
         end = 0;
         indentLevel = 0;
@@ -153,9 +156,11 @@ public final class Debugger extends Pars
             cause = node;
             // make arg cause become the root cause
             JexlNode root = node;
+            if (r) {
             while (root.jjtGetParent() != null) {
                 root = root.jjtGetParent();
             }
+            }
             root.jjtAccept(this, null);
         }
         return end > 0;

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=1628650&r1=1628649&r2=1628650&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
 Wed Oct  1 08:57:12 2014
@@ -123,8 +123,8 @@ public class Interpreter extends ParserV
     protected final JexlContext context;
     /** The context to store/retrieve variables. */
     protected final JexlContext.NamespaceResolver ns;
-    /** Strict interpreter flag. */
-    protected final boolean strictEngine;
+    /** Strict interpreter flag (may temporarily change during when calling 
size & empty as functions). */
+    protected boolean strictEngine;
     /** Strict interpreter flag. */
     protected final boolean strictArithmetic;
     /** Silent intepreter flag. */
@@ -153,8 +153,8 @@ public class Interpreter extends ParserV
             JexlEngine.Options opts = (JexlEngine.Options) context;
             Boolean ostrict = opts.isStrict();
             Boolean osilent = opts.isSilent();
-            this.strictEngine = ostrict == null ? jexl.isStrict() : 
ostrict.booleanValue();
-            this.silent = osilent == null ? jexl.isSilent() : 
osilent.booleanValue();
+            this.strictEngine = ostrict == null ? jexl.isStrict() : ostrict;
+            this.silent = osilent == null ? jexl.isSilent() : osilent;
             this.arithmetic = jexl.arithmetic.options(opts);
         } else {
             this.strictEngine = jexl.isStrict();
@@ -199,7 +199,7 @@ public class Interpreter extends ParserV
             }
             throw xjexl.clean();
         } finally {
-            if (functors != null && AUTOCLOSEABLE != null ) {
+            if (functors != null && AUTOCLOSEABLE != null) {
                 for(Object functor : functors.values()) {
                    if (functor != null && 
AUTOCLOSEABLE.isAssignableFrom(functor.getClass())) {
                        try {
@@ -250,20 +250,56 @@ public class Interpreter extends ParserV
     }
 
     /**
-     * Triggered when variable can not be resolved.
-     * @param xjexl the JexlException ("undefined variable " + variable)
+     * Triggered when a variable can not be resolved.
+     * @param node the node where the error originated from
+     * @param var the variable name
+     * @param undef whether the variable is undefined or null
      * @return throws JexlException if isStrict, null otherwise
      */
-    protected Object unknownVariable(JexlException xjexl) {
+    protected Object unsolvableVariable(JexlNode node, String var, boolean 
undef) {
+        if (strictEngine && (undef || arithmetic.isStrict())) {
+            throw new JexlException.Variable(node, var, undef);
+        }
+        if (!silent) {
+            logger.warn(JexlException.variableError(node, var, undef));
+        }
+        return null;
+    }
+
+    /**
+     * Triggered when a method can not be resolved.
+     * @param node the node where the error originated from
+     * @param method the method name
+     * @return throws JexlException if isStrict, null otherwise
+     */
+    protected Object unsolvableMethod(JexlNode node, String method) {
         if (strictEngine) {
-            throw xjexl;
+            throw new JexlException.Method(node, method);
         }
         if (!silent) {
-            logger.warn(xjexl.getMessage());
+            logger.warn(JexlException.methodError(node, method));
+        }
+        return null;
+    }
+
+    /**
+     * Triggered when a property can not be resolved.
+     * @param node the node where the error originated from
+     * @param var the property name
+     * @param cause the cause if any
+     * @return throws JexlException if isStrict, null otherwise
+     */
+    protected Object unsolvableProperty(JexlNode node, String var, Throwable 
cause) {
+        if (strictEngine) {
+            throw new JexlException.Property(node, var, cause);
+        }
+        if (!silent) {
+            logger.warn(JexlException.propertyError(node, var));
         }
         return null;
     }
 
+
     /**
      * Triggered when method, function or constructor invocation fails.
      * @param xjexl the JexlException wrapping the original error
@@ -341,6 +377,79 @@ 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) {
+                return invocationFailed(new JexlException(node, 
operator.getMethodName(), 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) {
+                return invocationFailed(new JexlException(node, 
operator.getMethodName(), xany));
+            }
+        }
+        return JexlEngine.TRY_FAILED;
+    }
+
     @Override
     protected Object visit(ASTAndNode node, Object data) {
         /**
@@ -389,13 +498,8 @@ public class Interpreter extends ParserV
         Object left = node.jjtGetChild(0).jjtAccept(this, data);
         Object right = node.jjtGetChild(1).jjtAccept(this, data);
         try {
-            if (operators != null) {
-                Object result = operators.tryInvokeOperator(Operator.ADD, 
left, right);
-                if (result != JexlEngine.TRY_FAILED) {
-                    return result;
-                }
-            }
-            return arithmetic.add(left, right);
+            Object result = callOperator(node, Operator.ADD, left, right);
+            return result != JexlEngine.TRY_FAILED? result : 
arithmetic.add(left, right);
         } catch (ArithmeticException xrt) {
             throw new JexlException(node, "+ error", xrt);
         }
@@ -406,13 +510,8 @@ public class Interpreter extends ParserV
         Object left = node.jjtGetChild(0).jjtAccept(this, data);
         Object right = node.jjtGetChild(1).jjtAccept(this, data);
         try {
-            if (operators != null) {
-                Object result = operators.tryInvokeOperator(Operator.SUBTRACT, 
left, right);
-                if (result != JexlEngine.TRY_FAILED) {
-                    return result;
-                }
-            }
-            return arithmetic.subtract(left, right);
+            Object result = callOperator(node, Operator.SUBTRACT, left, right);
+            return result != JexlEngine.TRY_FAILED? result : 
arithmetic.subtract(left, right);
         } catch (ArithmeticException xrt) {
             throw new JexlException(node, "- error", xrt);
         }
@@ -423,13 +522,8 @@ public class Interpreter extends ParserV
         Object left = node.jjtGetChild(0).jjtAccept(this, data);
         Object right = node.jjtGetChild(1).jjtAccept(this, data);
         try {
-            if (operators != null) {
-                Object result = operators.tryInvokeOperator(Operator.AND, 
left, right);
-                if (result != JexlEngine.TRY_FAILED) {
-                    return result;
-                }
-            }
-            return arithmetic.bitwiseAnd(left, right);
+            Object result = callOperator(node, Operator.AND, left, right);
+            return result != JexlEngine.TRY_FAILED? result : 
arithmetic.bitwiseAnd(left, right);
         } catch (ArithmeticException xrt) {
             throw new JexlException(node, "& error", xrt);
         }
@@ -437,15 +531,10 @@ public class Interpreter extends ParserV
 
     @Override
     protected Object visit(ASTBitwiseComplNode node, Object data) {
-        Object left = node.jjtGetChild(0).jjtAccept(this, data);
+        Object arg = node.jjtGetChild(0).jjtAccept(this, data);
         try {
-            if (operators != null) {
-                Object result = 
operators.tryInvokeOperator(Operator.COMPLEMENT, left);
-                if (result != JexlEngine.TRY_FAILED) {
-                    return result;
-                }
-            }
-            return arithmetic.bitwiseComplement(left);
+            Object result = callOperator(node, Operator.COMPLEMENT, arg);
+            return result != JexlEngine.TRY_FAILED? result : 
arithmetic.bitwiseComplement(arg);
         } catch (ArithmeticException xrt) {
             throw new JexlException(node, "~ error", xrt);
         }
@@ -456,13 +545,8 @@ public class Interpreter extends ParserV
         Object left = node.jjtGetChild(0).jjtAccept(this, data);
         Object right = node.jjtGetChild(1).jjtAccept(this, data);
         try {
-            if (operators != null) {
-                Object result = operators.tryInvokeOperator(Operator.OR, left, 
right);
-                if (result != JexlEngine.TRY_FAILED) {
-                    return result;
-                }
-            }
-            return arithmetic.bitwiseOr(left, right);
+            Object result = callOperator(node, Operator.OR, left, right);
+            return result != JexlEngine.TRY_FAILED? result : 
arithmetic.bitwiseOr(left, right);
         } catch (ArithmeticException xrt) {
             throw new JexlException(node, "| error", xrt);
         }
@@ -473,13 +557,8 @@ public class Interpreter extends ParserV
         Object left = node.jjtGetChild(0).jjtAccept(this, data);
         Object right = node.jjtGetChild(1).jjtAccept(this, data);
         try {
-            if (operators != null) {
-                Object result = operators.tryInvokeOperator(Operator.XOR, 
left, right);
-                if (result != JexlEngine.TRY_FAILED) {
-                    return result;
-                }
-            }
-            return arithmetic.bitwiseXor(left, right);
+            Object result = callOperator(node, Operator.XOR, left, right);
+            return result != JexlEngine.TRY_FAILED? result : 
arithmetic.bitwiseXor(left, right);
         } catch (ArithmeticException xrt) {
             throw new JexlException(node, "^ error", xrt);
         }
@@ -500,16 +579,11 @@ public class Interpreter extends ParserV
         Object left = node.jjtGetChild(0).jjtAccept(this, data);
         Object right = node.jjtGetChild(1).jjtAccept(this, data);
         try {
-            if (operators != null) {
-                Object result = operators.tryInvokeOperator(Operator.DIVIDE, 
left, right);
-                if (result != JexlEngine.TRY_FAILED) {
-                    return result;
-                }
-            }
-            return arithmetic.divide(left, right);
+            Object result = callOperator(node, Operator.DIVIDE, left, right);
+            return result != JexlEngine.TRY_FAILED? result : 
arithmetic.divide(left, right);
         } catch (ArithmeticException xrt) {
             if (!strictArithmetic) {
-                return new Double(0.0);
+                return 0.0d;
             }
             JexlNode xnode = findNullOperand(xrt, node, left, right);
             throw new JexlException(xnode, "divide error", xrt);
@@ -521,13 +595,10 @@ public class Interpreter extends ParserV
         Object left = node.jjtGetChild(0).jjtAccept(this, data);
         Object right = node.jjtGetChild(1).jjtAccept(this, data);
         try {
-            if (operators != null) {
-                Object result = operators.tryInvokeOperator(Operator.EQ, left, 
right);
-                if (result != JexlEngine.TRY_FAILED) {
-                    return result;
-                }
-            }
-            return arithmetic.equals(left, right) ? Boolean.TRUE : 
Boolean.FALSE;
+            Object result = callOperator(node, Operator.EQ, left, right);
+            return result != JexlEngine.TRY_FAILED
+                   ? result
+                   : arithmetic.equals(left, right) ? Boolean.TRUE : 
Boolean.FALSE;
         } catch (ArithmeticException xrt) {
             throw new JexlException(node, "== error", xrt);
         }
@@ -595,13 +666,10 @@ public class Interpreter extends ParserV
         Object left = node.jjtGetChild(0).jjtAccept(this, data);
         Object right = node.jjtGetChild(1).jjtAccept(this, data);
         try {
-            if (operators != null) {
-                Object result = operators.tryInvokeOperator(Operator.GTE, 
left, right);
-                if (result != JexlEngine.TRY_FAILED) {
-                    return result;
-                }
-            }
-            return arithmetic.greaterThanOrEqual(left, right) ? Boolean.TRUE : 
Boolean.FALSE;
+            Object result = callOperator(node, Operator.GTE, left, right);
+            return result != JexlEngine.TRY_FAILED
+                   ? result
+                   : arithmetic.greaterThanOrEqual(left, right) ? Boolean.TRUE 
: Boolean.FALSE;
         } catch (ArithmeticException xrt) {
             throw new JexlException(node, ">= error", xrt);
         }
@@ -612,13 +680,10 @@ public class Interpreter extends ParserV
         Object left = node.jjtGetChild(0).jjtAccept(this, data);
         Object right = node.jjtGetChild(1).jjtAccept(this, data);
         try {
-            if (operators != null) {
-                Object result = operators.tryInvokeOperator(Operator.GT, left, 
right);
-                if (result != JexlEngine.TRY_FAILED) {
-                    return result;
-                }
-            }
-            return arithmetic.greaterThan(left, right) ? Boolean.TRUE : 
Boolean.FALSE;
+            Object result = callOperator(node, Operator.GT, left, right);
+            return result != JexlEngine.TRY_FAILED
+                   ? result
+                   : arithmetic.greaterThan(left, right) ? Boolean.TRUE : 
Boolean.FALSE;
         } catch (ArithmeticException xrt) {
             throw new JexlException(node, "> error", xrt);
         }
@@ -844,13 +909,10 @@ public class Interpreter extends ParserV
         Object left = node.jjtGetChild(0).jjtAccept(this, data);
         Object right = node.jjtGetChild(1).jjtAccept(this, data);
         try {
-            if (operators != null) {
-                Object result = operators.tryInvokeOperator(Operator.LTE, 
left, right);
-                if (result != JexlEngine.TRY_FAILED) {
-                    return result;
-                }
-            }
-            return arithmetic.lessThanOrEqual(left, right) ? Boolean.TRUE : 
Boolean.FALSE;
+                Object result = callOperator(node, Operator.LTE, left, right);
+                return result != JexlEngine.TRY_FAILED
+                       ? result
+                       : arithmetic.lessThanOrEqual(left, right) ? 
Boolean.TRUE : Boolean.FALSE;
         } catch (ArithmeticException xrt) {
             throw new JexlException(node, "<= error", xrt);
         }
@@ -861,13 +923,10 @@ public class Interpreter extends ParserV
         Object left = node.jjtGetChild(0).jjtAccept(this, data);
         Object right = node.jjtGetChild(1).jjtAccept(this, data);
         try {
-            if (operators != null) {
-                Object result = operators.tryInvokeOperator(Operator.LT, left, 
right);
-                if (result != JexlEngine.TRY_FAILED) {
-                    return result;
-                }
-            }
-            return arithmetic.lessThan(left, right) ? Boolean.TRUE : 
Boolean.FALSE;
+                Object result = callOperator(node, Operator.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);
         }
@@ -912,16 +971,11 @@ public class Interpreter extends ParserV
         Object left = node.jjtGetChild(0).jjtAccept(this, data);
         Object right = node.jjtGetChild(1).jjtAccept(this, data);
         try {
-            if (operators != null) {
-                Object result = operators.tryInvokeOperator(Operator.MOD, 
left, right);
-                if (result != JexlEngine.TRY_FAILED) {
-                    return result;
-                }
-            }
-            return arithmetic.mod(left, right);
+            Object result = callOperator(node, Operator.MOD, left, right);
+            return result != JexlEngine.TRY_FAILED? result : 
arithmetic.mod(left, right);
         } catch (ArithmeticException xrt) {
             if (!strictArithmetic) {
-                return new Double(0.0);
+                return 0.0d;
             }
             JexlNode xnode = findNullOperand(xrt, node, left, right);
             throw new JexlException(xnode, "% error", xrt);
@@ -933,13 +987,8 @@ public class Interpreter extends ParserV
         Object left = node.jjtGetChild(0).jjtAccept(this, data);
         Object right = node.jjtGetChild(1).jjtAccept(this, data);
         try {
-            if (operators != null) {
-                Object result = operators.tryInvokeOperator(Operator.MULTIPLY, 
left, right);
-                if (result != JexlEngine.TRY_FAILED) {
-                    return result;
-                }
-            }
-            return arithmetic.multiply(left, right);
+            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);
@@ -951,13 +1000,10 @@ public class Interpreter extends ParserV
         Object left = node.jjtGetChild(0).jjtAccept(this, data);
         Object right = node.jjtGetChild(1).jjtAccept(this, data);
         try {
-            if (operators != null) {
-                Object result = operators.tryInvokeOperator(Operator.EQ, left, 
right);
-                if (result != JexlEngine.TRY_FAILED) {
-                    return arithmetic.toBoolean(result)? Boolean.FALSE : 
Boolean.TRUE;
-                }
-            }
-            return arithmetic.equals(left, right) ? Boolean.FALSE : 
Boolean.TRUE;
+            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);
@@ -968,13 +1014,10 @@ public class Interpreter extends ParserV
     protected Object visit(ASTNotNode node, Object data) {
         Object val = node.jjtGetChild(0).jjtAccept(this, data);
         try {
-            if (operators != null) {
-                Object result = operators.tryInvokeOperator(Operator.NOT, val);
-                if (result != JexlEngine.TRY_FAILED) {
-                    return result;
-                }
-            }
-            return arithmetic.toBoolean(val) ? Boolean.FALSE : Boolean.TRUE;
+            Object result = callOperator(node, Operator.NOT, val);
+            return result != JexlEngine.TRY_FAILED
+                   ? result
+                   : arithmetic.toBoolean(val) ? Boolean.FALSE : Boolean.TRUE;
         } catch (ArithmeticException xrt) {
             throw new JexlException(node, "arithmetic error", xrt);
         }
@@ -1054,13 +1097,11 @@ public class Interpreter extends ParserV
         JexlNode valNode = node.jjtGetChild(0);
         Object val = valNode.jjtAccept(this, data);
         try {
-            if (operators != null) {
-                Object result = operators.tryInvokeOperator(Operator.NEGATE, 
val);
-                if (result != JexlEngine.TRY_FAILED) {
-                    return result;
-                }
+            Object result = callOperator(node, Operator.NEGATE, val);
+            if (result != JexlEngine.TRY_FAILED) {
+                return result;
             }
-            Object number = arithmetic.negate(val);
+            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());
@@ -1096,11 +1137,14 @@ public class Interpreter extends ParserV
 
     @Override
     protected Object visit(ASTSizeFunction node, Object data) {
-        Object val = node.jjtGetChild(0).jjtAccept(this, data);
-        if (val == null) {
-            throw new JexlException(node, "size() : argument is null", null);
+        boolean isStrict = this.strictEngine;
+        try {
+            strictEngine = false;
+            Object val = node.jjtGetChild(0).jjtAccept(this, data);
+            return sizeOf(node, val);
+        } finally {
+            strictEngine = isStrict;
         }
-        return sizeOf(node, val);
     }
 
     @Override
@@ -1111,13 +1155,20 @@ public class Interpreter extends ParserV
 
     @Override
     protected Object visit(ASTEmptyFunction node, Object data) {
-        return isEmpty(node, node.jjtGetChild(0).jjtAccept(this, data));
+        boolean isStrict = this.strictEngine;
+        try {
+            strictEngine = false;
+            Object value = node.jjtGetChild(0).jjtAccept(this, data);
+            return callEmpty(node, value);
+        } finally {
+            strictEngine = isStrict;
+        }
     }
 
     @Override
     protected Object visit(ASTEmptyMethod node, Object data) {
         Object val = node.jjtGetChild(0).jjtAccept(this, data);
-        return isEmpty(node, val);
+        return callEmpty(node, val);
     }
 
     /**
@@ -1125,18 +1176,16 @@ public class Interpreter extends ParserV
      * method.
      *
      * @param node   the node holding the object
-     * @param object the object to check the rmptyness of.
+     * @param object the object to check the emptyness of.
      * @return the boolean
      */
-    private Object isEmpty(JexlNode node, Object object) {
+    private Object callEmpty(JexlNode node, Object object) {
         if (object == null) {
             return Boolean.TRUE;
         }
-        if (operators != null) {
-            Object result = operators.tryInvokeOperator(Operator.EMPTY, 
object);
-            if (result != JexlEngine.TRY_FAILED) {
-                return result;
-            }
+        Object opcall = callOperator(node, Operator.EMPTY, object);
+        if (opcall != JexlEngine.TRY_FAILED) {
+            return opcall;
         }
         if (object instanceof Number) {
             return ((Number) object).intValue() == 0 ? Boolean.TRUE : 
Boolean.FALSE;
@@ -1181,11 +1230,9 @@ public class Interpreter extends ParserV
         if (object == null) {
             return 0;
         }
-        if (operators != null) {
-            Object result = operators.tryInvokeOperator(Operator.SIZE, object);
-            if (result != JexlEngine.TRY_FAILED) {
-                return result;
-            }
+        Object opcall = callOperator(node, Operator.SIZE, object);
+        if (opcall != JexlEngine.TRY_FAILED) {
+            return opcall;
         }
         if (object instanceof Collection<?>) {
             return ((Collection<?>) object).size();
@@ -1245,8 +1292,7 @@ public class Interpreter extends ParserV
                     && !(node.jjtGetParent() instanceof ASTReference)
                     && !context.has(name)
                     && !isTernaryProtected(node)) {
-                JexlException xjexl = new JexlException.Variable(node, name);
-                return unknownVariable(xjexl);
+                return unsolvableVariable(node, name, true);
             }
             return value;
         } else {
@@ -1331,6 +1377,9 @@ public class Interpreter extends ParserV
                 throw new JexlException.Cancel(node);
             }
             objectNode = node.jjtGetChild(c);
+            if (objectNode instanceof ASTMethodNode && object == null) {
+                break;
+            }
             // attempt to evaluate the property within the object
             object = objectNode.jjtAccept(this, object);
             if (object == null && isVariable) {
@@ -1348,21 +1397,22 @@ public class Interpreter extends ParserV
                     // 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());
                     } else {
                         break main;
                     }
                 }
+                // variableName can *not* be null; the code before this line 
made sure of that
                 object = context.get(variableName.toString());
             }
             isVariable &= object == null;
         }
-        if (object == null && isVariable && variableName != null
-                && !isTernaryProtected(node) && 
!(context.has(variableName.toString()) || isLocalVariable(node, 0))) {
-            JexlException xjexl = new JexlException.Variable(node, 
variableName.toString());
+        if (object == null && isVariable && variableName != null && 
!isTernaryProtected(node)) {
+            boolean undefined = !(context.has(variableName.toString()) || 
isLocalVariable(node, 0));
             // variable unknown in context and not a local
-            return unknownVariable(xjexl);
+            return unsolvableVariable(node, variableName.toString(), 
undefined);
         }
         return object;
     }
@@ -1384,6 +1434,10 @@ public class Interpreter extends ParserV
                 // check we are not assigning a symbol itself
                 if (last < 0) {
                     frame.set(symbol, right);
+                    // make the closure accessible to itself, ie hoist the 
currently set variable after frame creation
+                    if (right instanceof Closure) {
+                        ((Closure) right).setHoisted(symbol, right);
+                    }
                     return right;
                 }
                 object = frame.get(symbol);
@@ -1627,8 +1681,10 @@ public class Interpreter extends ParserV
                 }
                 return eval;
             } else {
-                xjexl = new JexlException.Method(node, methodName, null);
+                return unsolvableMethod(node, methodName);
             }
+        } catch(JexlException.Method xmethod) {
+            throw xmethod;
         } catch (Exception xany) {
             xjexl = new JexlException(node, methodName, xany);
         }
@@ -1692,17 +1748,17 @@ public class Interpreter extends ParserV
                 }
                 if (ctor == null) {
                     String dbgStr = cobject != null ? cobject.toString() : 
null;
-                    xjexl = new JexlException.Method(node, dbgStr, null);
+                    return unsolvableMethod(node, dbgStr);
                 }
             }
-            if (xjexl == null) {
-                Object instance = ctor.invoke(cobject, argv);
-                // cache executor in volatile JexlNode.value
-                if (cache && ctor.isCacheable()) {
-                    node.jjtSetValue(ctor);
-                }
-                return instance;
+            Object instance = ctor.invoke(cobject, argv);
+            // cache executor in volatile JexlNode.value
+            if (cache && ctor.isCacheable()) {
+                node.jjtSetValue(ctor);
             }
+            return instance;
+        } catch(JexlException.Method xmethod) {
+            throw xmethod;
         } catch (Exception xany) {
             String dbgStr = cobject != null ? cobject.toString() : null;
             xjexl = new JexlException(node, dbgStr, xany);
@@ -1759,7 +1815,7 @@ public class Interpreter extends ParserV
                 return value;
             } catch (Exception xany) {
                 String attrStr = attribute != null ? attribute.toString() : 
null;
-                xjexl = new JexlException.Property(node, attrStr, xany);
+                return unsolvableProperty(node, attrStr, xany);
             }
         }
         if (xjexl == null) {
@@ -1770,7 +1826,7 @@ public class Interpreter extends ParserV
                 throw new UnsupportedOperationException(error);
             }
             String attrStr = attribute != null ? attribute.toString() : null;
-            xjexl = new JexlException.Property(node, attrStr, null);
+            return unsolvableProperty(node, attrStr, null);
         }
         if (strictEngine) {
             throw xjexl;
@@ -1842,7 +1898,8 @@ public class Interpreter extends ParserV
                     }
                 }
                 String attrStr = attribute != null ? attribute.toString() : 
null;
-                xjexl = new JexlException.Property(node, attrStr, xany);
+                unsolvableProperty(node, attrStr, xany);
+                return;
             }
         }
         if (xjexl == null) {
@@ -1854,7 +1911,8 @@ public class Interpreter extends ParserV
                 throw new UnsupportedOperationException(error);
             }
             String attrStr = attribute != null ? attribute.toString() : null;
-            xjexl = new JexlException.Property(node, attrStr, null);
+            unsolvableProperty(node, attrStr, null);
+            return;
         }
         if (strictEngine) {
             throw xjexl;

Modified: 
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/Scope.java
URL: 
http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/Scope.java?rev=1628650&r1=1628649&r2=1628650&view=diff
==============================================================================
--- 
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/Scope.java
 (original)
+++ 
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/Scope.java
 Wed Oct  1 08:57:12 2014
@@ -194,6 +194,23 @@ public final class Scope {
     }
 
     /**
+     * Gets the hoisted index of a given symbol, ie the target index of a 
symbol in a child frame.
+     * @param symbol the symbol index
+     * @return the target symbol index or null if the symbol is not hoisted
+     */
+    public Integer getHoisted(int symbol) {
+        if (hoistedVariables != null) {
+            for (Map.Entry<Integer, Integer> hoist : 
hoistedVariables.entrySet()) {
+                Integer source = hoist.getValue();
+                if (source == symbol) {
+                    return hoist.getKey();
+                }
+            }
+        }
+        return null;
+    }
+
+    /**
      * Gets the (maximum) number of arguments this script expects.
      * @return the number of parameters
      */

Modified: 
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/TemplateEngine.java
URL: 
http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/TemplateEngine.java?rev=1628650&r1=1628649&r2=1628650&view=diff
==============================================================================
--- 
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/TemplateEngine.java
 (original)
+++ 
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/TemplateEngine.java
 Wed Oct  1 08:57:12 2014
@@ -210,6 +210,7 @@ public final class TemplateEngine extend
          */
         abstract ExpressionType getType();
 
+        /** @return the info */
         JexlInfo getInfo() {
             return null;
         }
@@ -644,6 +645,7 @@ public final class TemplateEngine extend
 
     /**
      * Creates a JxltEngine.Exception from a JexlException.
+     * @param info   the source info
      * @param action createExpression, prepare, evaluate
      * @param expr   the template expression
      * @param xany   the exception
@@ -686,6 +688,7 @@ public final class TemplateEngine extend
 
     /**
      * Parses a unified expression.
+     * @param info  the source info
      * @param expr  the string expression
      * @param scope the template scope
      * @return the unified expression instance
@@ -878,7 +881,8 @@ public final class TemplateEngine extend
 
         /**
          * Creates a new block.
-         * @param theType  the type
+         * @param theType  the block type
+         * @param theLine the line number
          * @param theBlock the content
          */
         Block(BlockType theType, int theLine, String theBlock) {
@@ -898,6 +902,11 @@ public final class TemplateEngine extend
             }
         }
 
+        /**
+         * Appends this block string representation to a builder.
+         * @param strb the string builder to append to
+         * @param prefix the line prefix (immediate or deferred)
+         */
         protected void toString(StringBuilder strb, String prefix) {
             if (BlockType.VERBATIM.equals(type)) {
                 strb.append(body);
@@ -926,6 +935,7 @@ public final class TemplateEngine extend
 
         /**
          * Creates a new template from an character input.
+         * @param info the source info
          * @param directive the prefix for lines of code; can not be "$", 
"${", "#" or "#{"
          *                  since this would preclude being able to 
differentiate directives and template expressions
          * @param reader    the input reader
@@ -978,7 +988,7 @@ public final class TemplateEngine extend
             for (int b = 0; b < blocks.size(); ++b) {
                 Block block = blocks.get(b);
                 if (block.type == BlockType.VERBATIM) {
-                    
uexprs.add(TemplateEngine.this.parseExpression(info.at(block.line, 0), 
block.body, b > codeStart ? scope : null));
+                    uexprs.add(parseExpression(info.at(block.line, 0), 
block.body, b > codeStart ? scope : null));
                 }
             }
             source = blocks.toArray(new Block[blocks.size()]);
@@ -1181,7 +1191,8 @@ public final class TemplateEngine extend
          * <p>This will dynamically try to find the best suitable method in 
the writer through uberspection.
          * Subclassing Writer by adding 'print' methods should be the 
preferred way to specialize output.
          * </p>
-         * @param arg the argument to print out
+         * @param info   the source info
+         * @param arg    the argument to print out
          */
         private void doPrint(JexlInfo info, Object arg) {
             try {
@@ -1237,7 +1248,7 @@ public final class TemplateEngine extend
             throw new IllegalArgumentException("mark support in reader 
required");
         }
         return new Iterator<CharSequence>() {
-            CharSequence next = doNext();
+            private CharSequence next = doNext();
 
             private CharSequence doNext() {
                 StringBuffer strb = new StringBuffer(64);
@@ -1284,7 +1295,6 @@ public final class TemplateEngine extend
 
     /**
      * Reads lines of a template grouping them by typed blocks.
-     * @param info the source info
      * @param prefix the directive prefix
      * @param source the source reader
      * @return the list of blocks

Modified: 
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/introspection/MethodExecutor.java
URL: 
http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/introspection/MethodExecutor.java?rev=1628650&r1=1628649&r2=1628650&view=diff
==============================================================================
--- 
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/introspection/MethodExecutor.java
 (original)
+++ 
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/introspection/MethodExecutor.java
 Wed Oct  1 08:57:12 2014
@@ -114,11 +114,6 @@ public final class MethodExecutor extend
 
     /**
      * Reassembles arguments if the method is a vararg method.
-     * @param type   The vararg class type (aka component type
-     *               of the expected array arg)
-     * @param index  The index of the vararg in the method declaration
-     *               (This will always be one less than the number of
-     *               expected arguments.)
      * @param actual The actual arguments being passed to this method
      * @return The actual parameters adjusted for the varargs in order
      * to fit the method declaration.

Modified: 
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/introspection/MethodKey.java
URL: 
http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/introspection/MethodKey.java?rev=1628650&r1=1628649&r2=1628650&view=diff
==============================================================================
--- 
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/introspection/MethodKey.java
 (original)
+++ 
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/introspection/MethodKey.java
 Wed Oct  1 08:57:12 2014
@@ -451,6 +451,11 @@ public final class MethodKey {
          */
         protected abstract Class<?>[] getParameterTypes(T app);
 
+        /**
+         * Whether a constructor or method handles varargs.
+         * @param app the constructor or method
+         * @return true if varargs, false otherwise
+         */
         protected abstract boolean isVarArgs(T app);
 
         // CSOFF: RedundantThrows
@@ -605,7 +610,7 @@ public final class MethodKey {
         /**
          * Checks whether a parameter class is a primitive.
          * @param c              the parameter class
-         * @param possibleVararg true if this is the last parameter which can 
be a primitive array (vararg call)
+         * @param possibleVarArg true if this is the last parameter which can 
be a primitive array (vararg call)
          * @return true if primitive, false otherwise
          */
         private boolean isPrimitive(Class<?> c, boolean possibleVarArg) {
@@ -644,7 +649,7 @@ public final class MethodKey {
          * argument types.
          *
          * @param method  method that will be called
-         * @param classes arguments to method
+         * @param actuals arguments signature for method
          * @return true if method is applicable to arguments
          */
         private boolean isApplicable(T method, Class<?>[] actuals) {

Modified: 
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/introspection/PropertySetExecutor.java
URL: 
http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/introspection/PropertySetExecutor.java?rev=1628650&r1=1628649&r2=1628650&view=diff
==============================================================================
--- 
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/introspection/PropertySetExecutor.java
 (original)
+++ 
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/introspection/PropertySetExecutor.java
 Wed Oct  1 08:57:12 2014
@@ -150,12 +150,12 @@ public class PropertySetExecutor extends
      * <p>This checks only one method with that name accepts an array as sole 
parameter.
      * @param is       the introspector
      * @param clazz    the class to find the get method from
-     * @param methodName the method name to find
+     * @param mname    the method name to find
      * @return         the sole method that accepts an array as parameter
      */
-    private static java.lang.reflect.Method lookupSetEmptyArray(Introspector 
is, final Class<?> clazz, String methodName) {
+    private static java.lang.reflect.Method lookupSetEmptyArray(Introspector 
is, final Class<?> clazz, String mname) {
         java.lang.reflect.Method candidate = null;
-        java.lang.reflect.Method[] methods = is.getMethods(clazz, methodName);
+        java.lang.reflect.Method[] methods = is.getMethods(clazz, mname);
         if (methods != null) {
             for (java.lang.reflect.Method method : methods) {
                 Class<?>[] paramTypes = method.getParameterTypes();

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=1628650&r1=1628649&r2=1628650&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
 Wed Oct  1 08:57:12 2014
@@ -367,50 +367,40 @@ public class Uberspect implements JexlUb
      * The concrete uberspect Arithmetic class.
      */
     protected class ArithmeticUberspect implements JexlArithmetic.Uberspect {
+        /** The arithmetic instance being analyzed. */
         private final JexlArithmetic arithmetic;
+        /** The set of overloaded operators. */
         private final EnumSet<Operator> overloads;
 
-        private ArithmeticUberspect(JexlArithmetic arithmetic, Set<Operator> 
overloads) {
-            this.arithmetic = arithmetic;
-            this.overloads = EnumSet.copyOf(overloads);
+        /**
+         * Creates an instance.
+         * @param theArithmetic the arithmetic instance
+         * @param theOverloads  the overloaded operators
+         */
+        private ArithmeticUberspect(JexlArithmetic theArithmetic, 
Set<Operator> theOverloads) {
+            this.arithmetic = theArithmetic;
+            this.overloads = EnumSet.copyOf(theOverloads);
+            // register this arithmetic class in the operator map
+            operatorMap.put(arithmetic.getClass(), overloads);
         }
 
         @Override
         public JexlMethod getOperator(JexlArithmetic.Operator operator, Object 
arg) {
-            return overloads.contains(operator) && arg != null?
-                   getMethod(arithmetic, operator.getMethodName(), arg) : null;
+            return overloads.contains(operator) && arg != null
+                   ? getMethod(arithmetic, operator.getMethodName(), arg)
+                   : null;
         }
 
         @Override
         public JexlMethod getOperator(JexlArithmetic.Operator operator, Object 
lhs, Object rhs) {
-            return overloads.contains(operator) && lhs != null && rhs != null?
-                   getMethod(arithmetic, operator.getMethodName(), lhs, rhs) : 
null;
+            return overloads.contains(operator) && lhs != null && rhs != null
+                   ? getMethod(arithmetic, operator.getMethodName(), lhs, rhs)
+                   : null;
         }
 
         @Override
-        public Object tryInvokeOperator(JexlArithmetic.Operator operator, 
Object lhs, Object rhs) {
-            JexlMethod method = getOperator(operator, lhs, rhs);
-            if (method != null) {
-                try {
-                    return method.invoke(arithmetic, lhs, rhs);
-                } catch(Exception xany) {
-                    throw new ArithmeticException(xany.getMessage());
-                }
-            }
-            return JexlEngine.TRY_FAILED;
-        }
-
-        @Override
-        public Object tryInvokeOperator(JexlArithmetic.Operator operator, 
Object arg) {
-            JexlMethod method = getOperator(operator, arg);
-            if (method != null) {
-                try {
-                    return method.invoke(arithmetic, arg);
-                } catch(Exception xany) {
-                    throw new ArithmeticException(xany.getMessage());
-                }
-            }
-            return JexlEngine.TRY_FAILED;
+        public boolean overloads(Operator operator) {
+            return overloads.contains(operator);
         }
     }
 
@@ -429,6 +419,7 @@ public class Uberspect implements JexlUb
                             if (parms.length != op.getArity()) {
                                 continue;
                             }
+                            // eliminate method(Object) and method(Object, 
Object)
                             boolean root = true;
                             for (int p = 0; root && p < parms.length; ++p) {
                                 if (!Object.class.equals(parms[p])) {
@@ -441,7 +432,6 @@ public class Uberspect implements JexlUb
                         }
                     }
                 }
-                operatorMap.put(arithmetic.getClass(), ops);
             }
             if (!ops.isEmpty()) {
                 jau = new ArithmeticUberspect(arithmetic, ops);

Modified: 
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/parser/ASTArrayLiteral.java
URL: 
http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/parser/ASTArrayLiteral.java?rev=1628650&r1=1628649&r2=1628650&view=diff
==============================================================================
--- 
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/parser/ASTArrayLiteral.java
 (original)
+++ 
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/parser/ASTArrayLiteral.java
 Wed Oct  1 08:57:12 2014
@@ -18,6 +18,9 @@ package org.apache.commons.jexl3.parser;
 
 import org.apache.commons.jexl3.internal.Debugger;
 
+/**
+ * An array literal.
+ */
 public final class ASTArrayLiteral extends JexlNode {
     /** Whether this array is constant or not. */
     private boolean constant = false;

Modified: 
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/parser/ASTJexlLambda.java
URL: 
http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/parser/ASTJexlLambda.java?rev=1628650&r1=1628649&r2=1628650&view=diff
==============================================================================
--- 
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/parser/ASTJexlLambda.java
 (original)
+++ 
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/parser/ASTJexlLambda.java
 Wed Oct  1 08:57:12 2014
@@ -30,12 +30,16 @@ public final class ASTJexlLambda extends
         super(p, id);
     }
 
+    /**
+     * @return true if outermost script.
+     */
     public boolean isTopLevel() {
         return parent == null;
     }
 
     /**
      * Creates an array of arguments by copying values up to the number of 
parameters.
+     * @param frame the calling frame
      * @param values the argument values
      * @return the arguments array
      */

Modified: 
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/parser/ASTJexlScript.java
URL: 
http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/parser/ASTJexlScript.java?rev=1628650&r1=1628649&r2=1628650&view=diff
==============================================================================
--- 
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/parser/ASTJexlScript.java
 (original)
+++ 
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/parser/ASTJexlScript.java
 Wed Oct  1 08:57:12 2014
@@ -74,7 +74,7 @@ public class ASTJexlScript extends JexlN
     }
 
     /**
-     * Gets this script scope.
+     * @return this script scope
      */
     public Scope getScope() {
         return scope;

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=1628650&r1=1628649&r2=1628650&view=diff
==============================================================================
--- commons/proper/jexl/trunk/src/site/xdoc/changes.xml (original)
+++ commons/proper/jexl/trunk/src/site/xdoc/changes.xml Wed Oct  1 08:57:12 2014
@@ -26,6 +26,18 @@
     </properties>
     <body>
         <release version="3.0.1" date="unreleased">
+            <action dev="henrib" type="fix" issue="JEXL-146" due-to="David 
Maplesden">
+                Performance problem in Interpreter.unknownVariable mechanism
+            </action>
+            <action dev="henrib" type="fix">
+                Functions assigned to local variables can not perform 
recursive calls
+            </action>
+            <action dev="henrib" type="fix">
+                Improved error reporting on undefined or null variables
+            </action>
+            <action dev="henrib" type="fix">
+                Improved operator overloading logic in JexlArithmeric (caching)
+            </action>
             <action dev="henrib" type="fix" issue="JEXL-145" due-to="Ian 
Connor">
                 Sandbox calling wrong check (classname vs class)
             </action>

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=1628650&r1=1628649&r2=1628650&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
 Wed Oct  1 08:57:12 2014
@@ -83,7 +83,6 @@ public class ArithmeticTest extends Jexl
     public void testNullOperand() throws Exception {
         asserter.setVariable("right", null);
         asserter.failExpression("~right", ".*null.*");
-        asserter.failExpression("-right", ".*arithmetic.*");
     }
 
     public void testBigDecimal() throws Exception {
@@ -411,4 +410,189 @@ public class ArithmeticTest extends Jexl
             assertEquals("failed on " + stext, expected, result);
         }
     }
+
+    public static class Var {
+        final int value;
+        Var(int v) {
+            value = v;
+        }
+    }
+
+    // an arithmetic that know how to subtract strings
+    public static class ArithmeticPlus extends JexlArithmetic {
+        public ArithmeticPlus(boolean strict) {
+            super(strict);
+        }
+        public boolean equals(Var lhs, Var rhs) {
+            return lhs.value == rhs.value;
+        }
+        public boolean lessThan(Var lhs, Var rhs) {
+            return lhs.value < rhs.value;
+        }
+        public boolean lessThanOrEqual(Var lhs, Var rhs) {
+            return lhs.value <= rhs.value;
+        }
+        public boolean greaterThan(Var lhs, Var rhs) {
+            return lhs.value > rhs.value;
+        }
+        public boolean greaterThanOrEqual(Var lhs, Var rhs) {
+            return lhs.value >= rhs.value;
+        }
+        public Var add(Var lhs, Var rhs) {
+            return new Var(lhs.value + rhs.value);
+        }
+        public Var subtract(Var lhs, Var rhs) {
+            return new Var(lhs.value - rhs.value);
+        }
+        public Var divide(Var lhs, Var rhs) {
+            return new Var(lhs.value / rhs.value);
+        }
+        public Var multiply(Var lhs, Var rhs) {
+            return new Var(lhs.value * rhs.value);
+        }
+        public Var mod(Var lhs, Var rhs) {
+            return new Var(lhs.value / rhs.value);
+        }
+        public Var negate(Var arg) {
+            return new Var(-arg.value);
+        }
+        public Var bitwiseAnd(Var lhs, Var rhs) {
+            return new Var(lhs.value & rhs.value);
+        }
+        public Var bitwiseOr(Var lhs, Var rhs) {
+            return new Var(lhs.value | rhs.value);
+        }
+        public Var bitwiseXor(Var lhs, Var rhs) {
+            return new Var(lhs.value ^ rhs.value);
+        }
+        public Var bitwiseComplement(Var arg) {
+            return new Var(~arg.value);
+        }
+
+        public Object subtract(String x, String y) {
+            int ix = x.indexOf(y);
+            if (ix < 0) {
+                return x;
+            }
+            StringBuilder strb = new StringBuilder(x.substring(0, ix));
+            strb.append(x.substring(ix + y.length()));
+            return strb.toString();
+        }
+
+        public Object negate(final String str) {
+            final int length = str.length();
+            StringBuilder strb = new StringBuilder(str.length());
+            for(int c = length - 1; c >= 0; --c) {
+                strb.append(str.charAt(c));
+            }
+            return strb.toString();
+        }
+    }
+
+    public void testArithmeticPlus() throws Exception {
+        JexlEngine jexl = new JexlBuilder().cache(64).arithmetic(new 
ArithmeticPlus(false)).create();
+        JexlContext jc = new EmptyTestContext();
+        runOverload(jexl, jc);
+    }
+
+    public void testArithmeticPlusNoCache() throws Exception {
+        JexlEngine jexl = new JexlBuilder().cache(0).arithmetic(new 
ArithmeticPlus(false)).create();
+        JexlContext jc = new EmptyTestContext();
+        runOverload(jexl, jc);
+    }
+
+    protected void runOverload(JexlEngine jexl,JexlContext jc) {
+        JexlScript script;
+        Object result;
+
+        script = jexl.createScript("(x, y)->{ x < y }");
+        result = script.execute(jc, 42, 43);
+        assertEquals(true, result);
+        result = script.execute(jc, new Var(42), new Var(43));
+        assertEquals(true, result);
+        result = script.execute(jc, new Var(42), new Var(43));
+        assertEquals(true, result);
+        result = script.execute(jc, 43, 42);
+        assertEquals(false, result);
+        result = script.execute(jc, new Var(43), new Var(42));
+        assertEquals(false, result);
+        result = script.execute(jc, new Var(43), new Var(42));
+        assertEquals(false, result);
+
+        script = jexl.createScript("(x, y)->{ x <= y }");
+        result = script.execute(jc, 42, 43);
+        assertEquals(true, result);
+        result = script.execute(jc, new Var(42), new Var(43));
+        assertEquals(true, result);
+        result = script.execute(jc, new Var(41), new Var(44));
+        assertEquals(true, result);
+        result = script.execute(jc, 43, 42);
+        assertEquals(false, result);
+        result = script.execute(jc, new Var(45), new Var(40));
+        assertEquals(false, result);
+        result = script.execute(jc, new Var(46), new Var(39));
+        assertEquals(false, result);
+
+        script = jexl.createScript("(x, y)->{ x == y }");
+        result = script.execute(jc, 42, 43);
+        assertEquals(false, result);
+        result = script.execute(jc, new Var(42), new Var(43));
+        assertEquals(false, result);
+        result = script.execute(jc, new Var(41), new Var(44));
+        assertEquals(false, result);
+        result = script.execute(jc, 43, 42);
+        assertEquals(false, result);
+        result = script.execute(jc, new Var(45), new Var(40));
+        assertEquals(false, result);
+        result = script.execute(jc, new Var(46), new Var(39));
+        assertEquals(false, result);
+
+        script = jexl.createScript("(x, y)->{ x % y }");
+        result = script.execute(jc, 4242, 100);
+        assertEquals(42, result);
+        result = script.execute(jc, new Var(4242), new Var(100));
+        assertEquals(42, ((Var) result).value);
+        result = script.execute(jc, new Var(4242), new Var(100));
+        assertEquals(42, ((Var) result).value);
+
+        script = jexl.createScript("(x, y)->{ x * y }");
+        result = script.execute(jc, 6, 7);
+        assertEquals(42, result);
+        result = script.execute(jc, new Var(6), new Var(7));
+        assertEquals(42, ((Var) result).value);
+        result = script.execute(jc, new Var(6), new Var(7));
+        assertEquals(42, ((Var) result).value);
+
+        script = jexl.createScript("(x, y)->{ x + y }");
+        result = script.execute(jc, 35, 7);
+        assertEquals(42, result);
+        result = script.execute(jc, new Var(35), new Var(7));
+        assertEquals(42, ((Var) result).value);
+        result = script.execute(jc, new Var(35), new Var(7));
+        assertEquals(42, ((Var) result).value);
+
+        script = jexl.createScript("(x, y)->{ x - y }");
+        result = script.execute(jc, 49, 7);
+        assertEquals(42, result);
+        result = script.execute(jc, "foobarquux", "bar");
+        assertEquals("fooquux", result);
+        result = script.execute(jc, 50, 8);
+        assertEquals(42, result);
+        result = script.execute(jc, new Var(50), new Var(8));
+        assertEquals(42, ((Var) result).value);
+        result = script.execute(jc, new Var(50), new Var(8));
+        assertEquals(42, ((Var) result).value);
+
+        script = jexl.createScript("(x)->{ -x }");
+        result = script.execute(jc, -42);
+        assertEquals(42, result);
+        result = script.execute(jc, new Var(-42));
+        assertEquals(42, ((Var) result).value);
+        result = script.execute(jc, new Var(-42));
+        assertEquals(42, ((Var) result).value);
+        result = script.execute(jc, "pizza");
+        assertEquals("azzip", result);
+        result = script.execute(jc, -142);
+        assertEquals(142, result);
+    }
 }
\ No newline at end of file

Modified: 
commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/ClassCreator.java
URL: 
http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/ClassCreator.java?rev=1628650&r1=1628649&r2=1628650&view=diff
==============================================================================
--- 
commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/ClassCreator.java
 (original)
+++ 
commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/ClassCreator.java
 Wed Oct  1 08:57:12 2014
@@ -22,6 +22,12 @@ import java.io.FileWriter;
 import java.lang.reflect.Method;
 import java.net.URL;
 import java.net.URLClassLoader;
+import java.util.Arrays;
+import javax.tools.DiagnosticCollector;
+import javax.tools.JavaCompiler;
+import javax.tools.JavaFileObject;
+import javax.tools.StandardJavaFileManager;
+import javax.tools.ToolProvider;
 
 /**
  * Helper class to test GC / reference interactions.
@@ -36,10 +42,11 @@ public class ClassCreator {
     private String className = null;
     private String sourceName = null;
     private ClassLoader loader = null;
-    public static final boolean canRun = comSunToolsJavacMain();
+    public static final boolean canRun = true;//comSunToolsJavacMain();
 
     static final String GEN_PATH = "/org/apache/commons/jexl3/generated";
-    static final String GEN_CLASS = "org.apache.commons.jexl3.generated.";
+    static final String GEN_PACKAGE = "org.apache.commons.jexl3.generated";
+    static final String GEN_CLASS = GEN_PACKAGE + ".";
     /**
      * Check if we can invoke Sun's java compiler.
      * @return true if it is possible, false otherwise
@@ -109,7 +116,9 @@ public class ClassCreator {
 
     void generate() throws Exception {
         FileWriter aWriter = new FileWriter(new File(packageDir, sourceName), 
false);
-        aWriter.write("package org.apache.commons.jexl3.generated;");
+        aWriter.write("package ");
+        aWriter.write(GEN_PACKAGE);
+        aWriter.write(";\n");
         aWriter.write("public class " + className + "{\n");
         aWriter.write("private int value =");
         aWriter.write(Integer.toString(seed));
@@ -125,7 +134,7 @@ public class ClassCreator {
         aWriter.close();
     }
 
-    Class<?> compile() throws Exception {
+    Class<?> compile0() throws Exception {
         String source = packageDir.getPath() + "/" + sourceName;
         Class<?> javac = 
getClassLoader().loadClass("com.sun.tools.javac.Main");
         if (javac == null) {
@@ -135,18 +144,36 @@ public class ClassCreator {
         try {
             r = (Integer) jexl.invokeMethod(javac, "compile", source);
             if (r.intValue() >= 0) {
-                return 
getClassLoader().loadClass("org.apache.commons.jexl3.generated." + className);
+                return getClassLoader().loadClass(GEN_CLASS + className);
             }
         } catch (JexlException xignore) {
             // ignore
         }
         r = (Integer) jexl.invokeMethod(javac, "compile", (Object) new 
String[]{source});
         if (r.intValue() >= 0) {
-            return 
getClassLoader().loadClass("org.apache.commons.jexl3.generated." + className);
+            return getClassLoader().loadClass(GEN_CLASS + className);
         }
         return null;
     }
 
+    Class<?> compile() throws Exception {
+        String source = packageDir.getPath() + "/" + sourceName;
+        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
+        DiagnosticCollector<JavaFileObject> diagnostics = new 
DiagnosticCollector<JavaFileObject>();
+        StandardJavaFileManager fileManager = 
compiler.getStandardFileManager(diagnostics, null, null);
+        Iterable<? extends JavaFileObject> compilationUnits = fileManager
+                .getJavaFileObjectsFromStrings(Arrays.asList(source));
+        JavaCompiler.CompilationTask task = compiler.getTask(null, 
fileManager, diagnostics, null,
+                null, compilationUnits);
+        boolean success = task.call();
+        fileManager.close();
+        if (success) {
+            return getClassLoader().loadClass(GEN_CLASS +  className);
+        } else {
+            return null;
+        }
+    }
+
     Object validate(Class<?> clazz) throws Exception {
         Class<?> params[] = {};
         Object paramsObj[] = {};

Modified: 
commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/ClassCreatorTest.java
URL: 
http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/ClassCreatorTest.java?rev=1628650&r1=1628649&r2=1628650&view=diff
==============================================================================
--- 
commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/ClassCreatorTest.java
 (original)
+++ 
commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/ClassCreatorTest.java
 Wed Oct  1 08:57:12 2014
@@ -165,7 +165,7 @@ public class ClassCreatorTest extends Je
 
                 // attempt to force GC:
                 // while we still have a MB free, create & store big objects
-                for (int b = 0; b < 64 && Runtime.getRuntime().freeMemory() > 
MEGA; ++b) {
+                for (int b = 0; b < 1024 && Runtime.getRuntime().freeMemory() 
> MEGA; ++b) {
                     BigObject big = new BigObject(b);
                     stuff.add(new InstanceReference(big, queue));
                 }


Reply via email to