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/09/03 19:05:23 UTC

svn commit: r1701076 - 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/ main...

Author: henrib
Date: Thu Sep  3 17:05:22 2015
New Revision: 1701076

URL: http://svn.apache.org/r1701076
Log:
JEXL:
Better location information, tracks tokens from source;
Cleaned method/function resolution;
More tests (coverage up);
Various nitpicks on Javadoc;

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/JexlInfo.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/introspection/ConstructorMethod.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/introspection/JexlUberspect.java
    commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/parser/JexlNode.java
    commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/parser/Parser.jjt
    commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/ArithmeticOperatorTest.java
    commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/IssuesTest.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/StrategyTest.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=1701076&r1=1701075&r2=1701076&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 Thu Sep  3 17:05:22 2015
@@ -519,7 +519,7 @@ public class JexlArithmetic {
      * Replace all numbers in an arguments array with the smallest type that will fit.
      * @param args the argument array
      * @return true if some arguments were narrowed and args array is modified,
-     *         false if no narrowing occured and args array has not been modified
+     *         false if no narrowing occurred and args array has not been modified
      */
     public boolean narrowArguments(Object[] args) {
         boolean narrowed = false;
@@ -529,9 +529,9 @@ public class JexlArithmetic {
                 Number narg = (Number) arg;
                 Number narrow = narrow(narg);
                 if (!narg.equals(narrow)) {
+                    args[a] = narrow;
                     narrowed = true;
                 }
-                args[a] = narrow;
             }
         }
         return narrowed;

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=1701076&r1=1701075&r2=1701076&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 Thu Sep  3 17:05:22 2015
@@ -41,7 +41,7 @@ public class JexlInfo {
      */
     public interface Detail {
         /**
-         * @return he start column on the line that triggered the error
+         * @return the start column on the line that triggered the error
          */
         int start();
         /**

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=1701076&r1=1701075&r2=1701076&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 Thu Sep  3 17:05:22 2015
@@ -102,7 +102,6 @@ import org.apache.commons.jexl3.parser.A
 import org.apache.commons.jexl3.parser.JexlNode;
 import org.apache.commons.jexl3.parser.Node;
 import org.apache.commons.jexl3.parser.ParserVisitor;
-import org.apache.commons.jexl3.parser.StringParser;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
@@ -268,12 +267,12 @@ public class Interpreter extends ParserV
      * @return throws JexlException if isStrict, null otherwise
      */
     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));
         }
+        if (strictEngine && (undef || arithmetic.isStrict())) {
+            throw new JexlException.Variable(node, var, undef);
+        }
         return null;
     }
 
@@ -284,12 +283,12 @@ public class Interpreter extends ParserV
      * @return throws JexlException if isStrict, null otherwise
      */
     protected Object unsolvableMethod(JexlNode node, String method) {
-        if (strictEngine) {
-            throw new JexlException.Method(node, method);
-        }
         if (!silent) {
             logger.warn(JexlException.methodError(node, method));
         }
+        if (strictEngine) {
+            throw new JexlException.Method(node, method);
+        }
         return null;
     }
 
@@ -301,12 +300,12 @@ public class Interpreter extends ParserV
      * @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), cause);
         }
+        if (strictEngine) {
+            throw new JexlException.Property(node, var, cause);
+        }
         return null;
     }
 
@@ -319,12 +318,12 @@ public class Interpreter extends ParserV
      */
     protected void operatorError(JexlNode node, JexlOperator operator, Throwable cause) {
         if (cause != null) {
-            if (strictEngine) {
-                throw new JexlException.Operator(node, operator.getOperatorSymbol(), cause);
-            }
             if (!silent) {
                 logger.warn(JexlException.operatorError(node, operator.getOperatorSymbol()), cause);
             }
+            if (strictEngine) {
+                throw new JexlException.Operator(node, operator.getOperatorSymbol(), cause);
+            }
         }
     }
 
@@ -334,12 +333,12 @@ public class Interpreter extends ParserV
      * @return throws JexlException if isStrict, null otherwise
      */
     protected Object invocationFailed(JexlException xjexl) {
-        if (strictEngine || xjexl instanceof JexlException.Return) {
-            throw xjexl;
-        }
         if (!silent) {
             logger.warn(xjexl.getMessage(), xjexl.getCause());
         }
+        if (strictEngine || xjexl instanceof JexlException.Return) {
+            throw xjexl;
+        }
         return null;
     }
 
@@ -1413,29 +1412,155 @@ public class Interpreter extends ParserV
         return result;
     }
 
+    @Override
+    protected Object visit(ASTFunctionNode node, Object data) {
+        int argc = node.jjtGetNumChildren();
+        if (argc == 2) {
+            Object namespace = resolveNamespace(null, node);
+            if (namespace == null) {
+                namespace = context;
+            }
+            ASTIdentifier functionNode = (ASTIdentifier) node.jjtGetChild(0);
+            ASTArguments argNode = (ASTArguments) node.jjtGetChild(1);
+            return call(node, namespace, functionNode, argNode);
+        } else {
+            // objectNode 0 is the prefix
+            String prefix = ((ASTIdentifier) node.jjtGetChild(0)).getName();
+            Object namespace = resolveNamespace(prefix, node);
+            // objectNode 1 is the identifier , the others are parameters.
+            ASTIdentifier functionNode = (ASTIdentifier) node.jjtGetChild(1);
+            ASTArguments argNode = (ASTArguments) node.jjtGetChild(2);
+            return call(node, namespace, functionNode, argNode);
+        }
+    }
+
+    /**
+     * Concatenate arguments in call(...).
+     * <p>When target == context, we are dealing with a global namespace function call
+     * @param target    the pseudo-method owner, first to-be argument
+     * @param args the other arguments
+     * @return the arguments array
+     */
+    private Object[] functionArguments(Object target, Object[] args) {
+        if (args == null) {
+            return new Object[]{target};
+        }
+        if (context == target) {
+            return args;
+        }
+        Object[] nargv = new Object[args.length + 1];
+        nargv[0] = target;
+        System.arraycopy(args, 0, nargv, 1, args.length);
+        return nargv;
+    }
+
+    /**
+     * Narrow arguments in call(...).
+     * @param narrow predicate
+     * @param args the arguments to narrow
+     * @return the narrowed arguments (or the original ones)
+     */
+    private Object[] narrowArguments(boolean narrow, Object[] args) {
+        if (narrow) {
+            arithmetic.narrowArguments(args);
+        }
+        return args;
+    }
+
+    /**
+     * Cached function call.
+     */
+    private static class Funcall {
+        /** Whether narrow should be applied to arguments. */
+        protected final boolean narrow;
+        /** The JexlMethod to delegate the call to. */
+        protected final JexlMethod me;
+        /**
+         * Constructor.
+         * @param jme the method
+         * @param flag the narrow flag
+         */
+        protected Funcall(JexlMethod jme, boolean flag) {
+            this.me = jme;
+            this.narrow = flag;
+        }
+
+        /**
+         * Try invocation.
+         * @param ii the interpreter
+         * @param name the method name
+         * @param target the method target
+         * @param args the method arguments
+         * @return the method invocation result (or JexlEngine.TRY_FAILED)
+         */
+        protected Object tryInvoke(Interpreter ii, String name, Object target, Object[] args) {
+            return me.tryInvoke(name, target, ii.narrowArguments(narrow, args));
+        }
+    }
+
+    /**
+     * Cached arithmetic function call.
+     */
+    private static class ArithmeticFuncall extends Funcall {
+        /**
+         * Constructor.
+         * @param jme the method
+         * @param flag the narrow flag
+         */
+        protected ArithmeticFuncall(JexlMethod jme, boolean flag) {
+            super(jme, flag);
+        }
+
+        @Override
+        protected Object tryInvoke(Interpreter ii, String name, Object target, Object[] args) {
+            Object[] nargs = ii.functionArguments(target, args);
+            return me.tryInvoke(name, ii.arithmetic, ii.narrowArguments(narrow, nargs));
+        }
+    }
+    /**
+     * Cached context function call.
+     */
+    private static class ContextFuncall extends Funcall {
+        /**
+         * Constructor.
+         * @param jme the method
+         * @param flag the narrow flag
+         */
+        protected ContextFuncall(JexlMethod jme, boolean flag) {
+            super(jme, flag);
+        }
+
+        @Override
+        protected Object tryInvoke(Interpreter ii, String name, Object target, Object[] args) {
+            Object[] nargs = ii.functionArguments(target, args);
+            return me.tryInvoke(name, ii.context, ii.narrowArguments(narrow, nargs));
+        }
+    }
+
     /**
      * Calls a method (or function).
      * <p>
      * Method resolution is a follows:
-     * 1 - attempt to find a method in the bean passed as parameter;
-     * 3 - if this fails, seeks a JexlScript or JexlMethod as a property of that bean;
-     * 2 - if this fails, narrow the arguments and try again
+     * 1 - attempt to find a method in the target passed as parameter;
+     * 2 - if this fails, seeks a JexlScript or JexlMethod or a duck-callable* as a property of that target;
+     * 3 - if this fails, narrow the arguments and try again 1
+     * 4 - if this fails, seeks a context or arithmetic method with the proper name taking the target as first argument;
      * </p>
+     * *duck-callable: an object where a "call" function exists
      *
      * @param node    the method node
-     * @param bean    the bean this method should be invoked upon
-     * @param functor the object carrying the method or function
+     * @param target  the target of the method, what it should be invoked upon
+     * @param functor the object carrying the method or function or the method identifier
      * @param argNode the node carrying the arguments
      * @return the result of the method invocation
      */
-    protected Object call(JexlNode node, Object bean, Object functor, ASTArguments argNode) {
+    protected Object call(final JexlNode node, Object target, Object functor, final ASTArguments argNode) {
         if (isCancelled()) {
             throw new JexlException.Cancel(node);
         }
         JexlException xjexl;
         // evaluate the arguments
         Object[] argv = visit(argNode, null);
-
         // get the method name if identifier
         String methodName = null;
         int symbol = -1;
@@ -1450,47 +1575,51 @@ public class Interpreter extends ParserV
         }
         try {
             boolean cacheable = cache;
-            boolean narrow = true;
+            // attempt to reuse last funcall cached in volatile JexlNode.value
+            if (cache) {
+                Object cached = node.jjtGetValue();
+                if (cached instanceof Funcall) {
+                    Object eval = ((Funcall) cached).tryInvoke(this, methodName, target, argv);
+                    if (JexlEngine.TRY_FAILED != eval) {
+                        return eval;
+                    }
+                }
+            }
+            boolean narrow = false;
             JexlMethod vm = null;
+            Funcall funcall = null;
             // pseudo loop and a half
             while (true) {
                 if (methodName != null) {
-                    // attempt to reuse last executor cached in volatile JexlNode.value
-                    if (cache) {
-                        Object cached = node.jjtGetValue();
-                        if (cached instanceof JexlMethod) {
-                            JexlMethod me = (JexlMethod) cached;
-                            Object eval = me.tryInvoke(methodName, bean, argv);
-                            if (!me.tryFailed(eval)) {
-                                return eval;
-                            }
-                        }
-                    }
                     // try a method
-                    vm = uberspect.getMethod(bean, methodName, argv);
-                    if (vm != null || !narrow) {
-                        // if there is a method name, we will exit here on first or second pass
+                    vm = uberspect.getMethod(target, methodName, argv);
+                    if (vm != null) {
+                        if (cacheable && vm.isCacheable()) {
+                            funcall = new Funcall(vm, narrow);
+                        }
                         break;
                     }
                 }
-                // could not find a method, try as a var
-                if (functor == null) {
+                // could not find a method, try as a var (local, global) or property (performed once)
+                if (functor == null && !narrow) {
                     if (symbol >= 0) {
                         functor = frame.get(symbol);
                     } else {
-                        JexlPropertyGet get = uberspect.getPropertyGet(bean, methodName);
+                        // the method may be a functor stored in a property of the target
+                        JexlPropertyGet get = uberspect.getPropertyGet(target, methodName);
                         if (get != null) {
-                            functor = get.tryInvoke(bean, methodName);
+                            functor = get.tryInvoke(target, methodName);
                         }
                     }
                 }
+                // this may happen without the above when we are chaining call like x(a)(b)
                 if (functor != null) {
                     // lambda, script or jexl method will do
                     if (functor instanceof JexlScript) {
                         return ((JexlScript) functor).execute(context, argv);
                     }
                     if (functor instanceof JexlMethod) {
-                        return ((JexlMethod) functor).invoke(bean, argv);
+                        return ((JexlMethod) functor).invoke(target, argv);
                     }
                     // a generic callable
                     vm = uberspect.getMethod(functor, "call", argv);
@@ -1498,26 +1627,49 @@ public class Interpreter extends ParserV
                         return vm.invoke(functor, argv);
                     }
                 }
-                // if we did not find an exact method by name and we haven't tried yet,
-                // attempt to narrow the parameters and if this succeeds, try again in next loop
-                if (methodName == null || !arithmetic.narrowArguments(argv)) {
-                    break;
-                } else {
-                    narrow = false;
+                // try JexlArithmetic or JexlContext function
+                if (methodName != null) {
+                    // when target == context, we are dealing with a global namespace function call
+                    Object[] nargv = functionArguments(target, argv);
+                    vm = uberspect.getMethod(context, methodName, nargv);
+                    if (vm != null) {
+                        argv = nargv;
+                        target = context;
+                        if (cacheable && vm.isCacheable()) {
+                            funcall = new ContextFuncall(vm, narrow);
+                        }
+                        break;
+                    }
+                    vm = uberspect.getMethod(arithmetic, methodName, nargv);
+                    if (vm != null) {
+                        argv = nargv;
+                        target = arithmetic;
+                        if (cacheable && vm.isCacheable()) {
+                            funcall = new ArithmeticFuncall(vm, narrow);
+                        }
+                        break;
+                    }
+                    // if we did not find an exact method by name and we haven't tried yet,
+                    // attempt to narrow the parameters and if this succeeds, try again in next loop
+                    if (arithmetic.narrowArguments(argv)) {
+                        narrow = true;
+                        continue;
+                    }
                 }
+                // we are done trying
+                break;
             }
             // we have either evaluated and returned or might have found a method
             if (vm != null) {
                 // vm cannot be null if xjexl is null
-                Object eval = vm.invoke(bean, argv);
+                Object eval = vm.invoke(target, argv);
                 // cache executor in volatile JexlNode.value
-                if (cacheable && vm.isCacheable()) {
-                    node.jjtSetValue(vm);
+                if (funcall != null) {
+                    node.jjtSetValue(funcall);
                 }
                 return eval;
-            } else {
-                return unsolvableMethod(node, methodName);
             }
+            return unsolvableMethod(node, methodName);
         } catch (JexlException.Method xmethod) {
             throw xmethod;
         } catch (Exception xany) {
@@ -1527,28 +1679,6 @@ public class Interpreter extends ParserV
     }
 
     @Override
-    protected Object visit(ASTFunctionNode node, Object data) {
-        int argc = node.jjtGetNumChildren();
-        if (argc == 2) {
-            Object namespace = resolveNamespace(null, node);
-            if (namespace == null) {
-                namespace = context;
-            }
-            ASTIdentifier functionNode = (ASTIdentifier) node.jjtGetChild(0);
-            ASTArguments argNode = (ASTArguments) node.jjtGetChild(1);
-            return call(node, namespace, functionNode, argNode);
-        } else {
-            // objectNode 0 is the prefix
-            String prefix = ((ASTIdentifier) node.jjtGetChild(0)).getName();
-            Object namespace = resolveNamespace(prefix, node);
-            // objectNode 1 is the identifier , the others are parameters.
-            ASTIdentifier functionNode = (ASTIdentifier) node.jjtGetChild(1);
-            ASTArguments argNode = (ASTArguments) node.jjtGetChild(2);
-            return call(node, namespace, functionNode, argNode);
-        }
-    }
-
-    @Override
     protected Object visit(ASTConstructorNode node, Object data) {
         if (isCancelled()) {
             throw new JexlException.Cancel(node);
@@ -1763,4 +1893,5 @@ public class Interpreter extends ParserV
         }
         return null;
     }
+
 }

Modified: commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/introspection/ConstructorMethod.java
URL: http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/introspection/ConstructorMethod.java?rev=1701076&r1=1701075&r2=1701076&view=diff
==============================================================================
--- commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/introspection/ConstructorMethod.java (original)
+++ commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/introspection/ConstructorMethod.java Thu Sep  3 17:05:22 2015
@@ -120,5 +120,5 @@ public final class ConstructorMethod imp
     public Class<?> getReturnType() {
         return ctor.getDeclaringClass();
     }
-
+    
 }

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=1701076&r1=1701075&r2=1701076&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 Thu Sep  3 17:05:22 2015
@@ -506,14 +506,10 @@ public final class MethodKey {
             LinkedList<T> maximals = new LinkedList<T>();
             for (T app : applicables) {
                 Class<?>[] appArgs = getParameterTypes(app);
-
                 boolean lessSpecific = false;
-
-                for (Iterator<T> maximal = maximals.iterator();
-                        !lessSpecific && maximal.hasNext();) {
+                Iterator<T> maximal = maximals.iterator();
+                while(!lessSpecific && maximal.hasNext()) {
                     T max = maximal.next();
-
-                    // CSOFF: MissingSwitchDefault
                     switch (moreSpecific(appArgs, getParameterTypes(max))) {
                         case MORE_SPECIFIC:
                             /*
@@ -533,8 +529,10 @@ public final class MethodKey {
 
                             lessSpecific = true;
                             break;
+                        default:
+                            // nothing do do
                     }
-                } // CSON: MissingSwitchDefault
+                }
 
                 if (!lessSpecific) {
                     maximals.addLast(app);

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=1701076&r1=1701075&r2=1701076&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 Thu Sep  3 17:05:22 2015
@@ -105,7 +105,7 @@ public interface JexlUberspect {
 
         @Override
         public final JexlPropertySet getPropertySet(JexlUberspect uber, Object obj, Object identifier, Object arg) {
-            return uber.getPropertySet(Collections.<PropertyResolver>singletonList(this), obj, identifier);
+            return uber.getPropertySet(Collections.<PropertyResolver>singletonList(this), obj, identifier, arg);
         }
     }
 

Modified: commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/parser/JexlNode.java
URL: http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/parser/JexlNode.java?rev=1701076&r1=1701075&r2=1701076&view=diff
==============================================================================
--- commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/parser/JexlNode.java (original)
+++ commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/parser/JexlNode.java Thu Sep  3 17:05:22 2015
@@ -27,6 +27,9 @@ import org.apache.commons.jexl3.introspe
  * @since 2.0
  */
 public abstract class JexlNode extends SimpleNode {
+    // line + column encoded: up to 4096 columns (ie 20 bits for line + 12 bits for column)
+    private int lc = -1;
+
     /**
      * A marker interface for constants.
      * @param <T> the literal type
@@ -43,6 +46,16 @@ public abstract class JexlNode extends S
         super(p, id);
     }
 
+    public void jjtSetFirstToken(Token t) {
+        // 0xc = 12, 12 bits -> 4096
+        // 0xfff, 12 bits mask
+        this.lc = (t.beginLine << 0xc) | (0xfff & t.beginColumn);
+    }
+
+    public void jjtSetLastToken(Token t) {
+        // nothing
+    }
+
     /**
      * Gets the associated JexlInfo instance.
      *
@@ -52,7 +65,15 @@ public abstract class JexlNode extends S
         JexlNode node = this;
         while (node != null) {
             if (node.value instanceof JexlInfo) {
-                return (JexlInfo) node.value;
+                JexlInfo info = (JexlInfo) node.value;
+                if (lc >= 0) {
+                    int c = lc & 0xfff;
+                    int l = lc >> 0xc;
+                    return info.at(l, c);
+                } else {
+                    // weird though; no jjSetFirstToken(...) ever called?
+                    return info;
+                }
             }
             node = node.jjtGetParent();
         }
@@ -61,13 +82,14 @@ public abstract class JexlNode extends S
 
     /**
      * Clears any cached value of type JexlProperty{G,S}et or JexlMethod.
-     * <p>This is called when the engine detects the evaluation of a script occurs with a class loader
+     * <p>
+     * This is called when the engine detects the evaluation of a script occurs with a class loader
      * different that the one that created it.</p>
      */
     public void clearCache() {
         if (value instanceof JexlPropertyGet
-                || value instanceof JexlPropertySet
-                || value instanceof JexlMethod) {
+            || value instanceof JexlPropertySet
+            || value instanceof JexlMethod) {
             value = null;
         }
         if (children != null) {

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=1701076&r1=1701075&r2=1701076&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 Thu Sep  3 17:05:22 2015
@@ -24,6 +24,8 @@ options
    NODE_SCOPE_HOOK=true;
    NODE_CLASS="JexlNode";
    UNICODE_INPUT=true;
+   KEEP_LINE_COLUMN=true;
+   TRACK_TOKENS=true;
    //DEBUG_PARSER=true;
 }
 

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=1701076&r1=1701075&r2=1701076&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 Thu Sep  3 17:05:22 2015
@@ -26,7 +26,12 @@ import java.util.Set;
 import java.util.SortedSet;
 import java.util.TreeSet;
 import org.apache.commons.jexl3.junit.Asserter;
+import java.text.DecimalFormat;
+import java.text.SimpleDateFormat;
 import java.util.Arrays;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.Locale;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
@@ -284,4 +289,129 @@ public class ArithmeticOperatorTest exte
         result = script.execute(null);
         Assert.assertEquals(Integer.valueOf(0), result);
     }
+
+    public static class DateArithmetic extends JexlArithmetic {
+        DateArithmetic(boolean flag) {
+            super(flag);
+        }
+
+        protected Object getDateValue(Date date, String key) {
+            try {
+                Calendar cal = Calendar.getInstance();
+                cal.setTime(date);
+                if ("yyyy".equals(key)) {
+                    return cal.get(Calendar.YEAR);
+                } else if ("MM".equals(key)) {
+                    return cal.get(Calendar.MONTH) + 1;
+                } else if ("dd".equals(key)) {
+                    return cal.get(Calendar.DAY_OF_MONTH);
+                }
+                // Otherwise treat as format mask
+                SimpleDateFormat df = new SimpleDateFormat(key);//, dfs);
+                return df.format(date);
+
+            } catch (Exception ex) {
+                return null;
+            }
+        }
+
+
+        protected Object setDateValue(Date date, String key, Object value) throws Exception {
+            Calendar cal = Calendar.getInstance();
+            cal.setTime(date);
+            if ("yyyy".equals(key)) {
+                cal.set(Calendar.YEAR, toInteger(value));
+            } else if ("MM".equals(key)) {
+                cal.set(Calendar.MONTH, toInteger(value) - 1);
+            } else if ("dd".equals(key)) {
+                cal.set(Calendar.DAY_OF_MONTH, toInteger(value));
+            }
+            date.setTime(cal.getTimeInMillis());
+            return date;
+        }
+
+        public Object propertyGet(Date date, String identifier) {
+            return getDateValue(date, identifier);
+        }
+
+        public Object propertySet(Date date, String identifier, Object value) throws Exception {
+            return setDateValue(date, identifier, value);
+        }
+
+        public Object arrayGet(Date date, String identifier) {
+            return getDateValue(date, identifier);
+        }
+
+        public Object arraySet(Date date, String identifier, Object value) throws Exception {
+            return setDateValue(date, identifier, value);
+        }
+
+        public String format(Number number, String fmt) {
+            return new DecimalFormat(fmt).format(number);
+        }
+    }
+
+    public static class DateContext extends MapContext {
+        private Locale locale = Locale.US;
+
+        void setLocale(Locale l10n) {
+            this.locale = l10n;
+        }
+
+        public String format(Date date, String fmt) {
+            SimpleDateFormat sdf = new SimpleDateFormat(fmt, locale);
+            return sdf.format(date);
+        }
+    }
+
+    @Test
+    public void testDateArithmetic() throws Exception {
+        Date d = new Date();
+        JexlContext jc = new MapContext();
+        JexlEngine jexl = new JexlBuilder().cache(32).arithmetic(new DateArithmetic(true)).create();
+        JexlScript expr0 = jexl.createScript("date.yyyy = 1969; date.MM=7; date.dd=20; ", "date");
+        Object value0 = expr0.execute(jc, d);
+        Assert.assertNotNull(value0);
+        value0 = d;
+        //d = new Date();
+        Assert.assertEquals(1969, jexl.createScript("date.yyyy", "date").execute(jc, value0));
+        Assert.assertEquals(7, jexl.createScript("date.MM", "date").execute(jc, value0));
+        Assert.assertEquals(20, jexl.createScript("date.dd", "date").execute(jc, value0));
+    }
+
+    @Test
+    public void testFormatArithmetic() throws Exception {
+        Calendar cal = Calendar.getInstance();
+        cal.set(1969, 7, 20);
+        Date x0 = cal.getTime();
+        String y0 =  "MM/yy/dd";
+        Number x1 = 42.12345;
+        String y1 = "##0.##";
+        DateContext jc = new DateContext();
+        JexlEngine jexl = new JexlBuilder().cache(32).arithmetic(new DateArithmetic(true)).create();
+        JexlScript expr0 = jexl.createScript("x.format(y)", "x", "y");
+        Object value10 = expr0.execute(jc, x0, y0);
+        Object value20 = expr0.execute(jc, x0, y0);
+        Assert.assertEquals(value10, value20);
+        Object value11 = expr0.execute(jc, x1, y1);
+        Object value21 = expr0.execute(jc, x1, y1);
+        Assert.assertEquals(value11, value21);
+        value10 = expr0.execute(jc, x0, y0);
+        Assert.assertEquals(value10, value20);
+        value11 = expr0.execute(jc, x1, y1);
+        Assert.assertEquals(value11, value21);
+        value10 = expr0.execute(jc, x0, y0);
+        Assert.assertEquals(value10, value20);
+        value11 = expr0.execute(jc, x1, y1);
+        Assert.assertEquals(value11, value21);
+
+        JexlScript expr1 = jexl.createScript("format(x, y)", "x", "y");
+        value10 = expr1.execute(jc, x0, y0);
+        Assert.assertEquals(value10, value20);
+        Object s0 = expr1.execute(jc, x0, "EEE dd MMM yyyy");
+        Assert.assertEquals("Wed 20 Aug 1969", s0);
+        jc.setLocale(Locale.FRANCE);
+        s0 = expr1.execute(jc, x0, "EEE dd MMM yyyy");
+        Assert.assertEquals("mer. 20 août 1969", s0);
+    }
 }

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=1701076&r1=1701075&r2=1701076&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 Thu Sep  3 17:05:22 2015
@@ -24,9 +24,14 @@ import java.util.Map;
 import org.apache.commons.jexl3.internal.introspection.Uberspect;
 import java.io.File;
 import java.net.URL;
+import java.text.DecimalFormat;
+import java.text.SimpleDateFormat;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Calendar;
+import java.util.Date;
 import java.util.List;
+import java.util.Set;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
@@ -1110,6 +1115,16 @@ public class IssuesTest extends JexlTest
         Assert.assertEquals("CD", value1);
     }
 
+    @Test
+    public void test179() throws Exception {
+        JexlContext jc = new MapContext();
+        JexlEngine jexl = new JexlBuilder().create();
+        String src = "x = new ('java.util.HashSet'); x.add(1); x";
+        JexlScript e = jexl.createScript(src);
+        Object o = e.execute(jc);
+        Assert.assertTrue(o instanceof Set);
+        Assert.assertTrue(((Set) o).contains(1));
+    }
 //
 //
 //	@Test
@@ -1163,4 +1178,5 @@ public class IssuesTest extends JexlTest
 //
 //	    Assert.assertEquals(null, ((Double)e.evaluate(jc)).doubleValue(), 50d, 0d);
 //	}
+
 }

Modified: commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/LambdaTest.java
URL: http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/LambdaTest.java?rev=1701076&r1=1701075&r2=1701076&view=diff
==============================================================================
--- commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/LambdaTest.java (original)
+++ commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/LambdaTest.java Thu Sep  3 17:05:22 2015
@@ -128,8 +128,15 @@ public class LambdaTest extends JexlTest
         JexlContext ctx = null;
         String strs = "(x)->{ (y)->{ x + y } }";
         JexlScript s42 = jexl.createScript(strs);
+        JexlScript s42b = jexl.createScript(s42.toString());
+        Assert.assertEquals(s42.hashCode(), s42b.hashCode());
+        Assert.assertEquals(s42, s42b);
         Object result = s42.execute(ctx, 15);
         Assert.assertTrue(result instanceof JexlScript);
+        Object resultb = s42.execute(ctx, 15);
+        Assert.assertEquals(result.hashCode(), resultb.hashCode());
+        Assert.assertEquals(result, resultb);
+        Assert.assertEquals(result, jexl.createScript(resultb.toString(), "x").execute(ctx, 15));
         JexlScript s15 = (JexlScript) result;
         Callable<Object> s15b = s15.callable(ctx, 27);
         result = s15.execute(ctx, 27);

Modified: commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/StrategyTest.java
URL: http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/StrategyTest.java?rev=1701076&r1=1701075&r2=1701076&view=diff
==============================================================================
--- commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/StrategyTest.java (original)
+++ commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/StrategyTest.java Thu Sep  3 17:05:22 2015
@@ -17,6 +17,9 @@
 package org.apache.commons.jexl3;
 
 import org.apache.commons.jexl3.internal.Engine;
+import org.apache.commons.jexl3.introspection.JexlPropertyGet;
+import org.apache.commons.jexl3.introspection.JexlPropertySet;
+import org.apache.commons.jexl3.introspection.JexlUberspect;
 import java.util.HashMap;
 import java.util.Map;
 import org.junit.Assert;
@@ -58,17 +61,40 @@ public class StrategyTest extends JexlTe
     }
 
     @Test
+    public void testRawResolvers() throws Exception {
+        Object map  = new HashMap<String, Object>();
+        final JexlEngine jexl = new JexlBuilder().create();
+        JexlUberspect uberspect = jexl.getUberspect();
+        JexlUberspect.PropertyResolver rfieldp = JexlUberspect.JexlResolver.FIELD;
+        JexlPropertyGet fget = rfieldp.getPropertyGet(uberspect, map, "key");
+        Assert.assertNull(fget);
+        JexlPropertySet fset = rfieldp.getPropertySet(uberspect, map, "key", "value");
+        Assert.assertNull(fset);
+        JexlUberspect.PropertyResolver rmap = JexlUberspect.JexlResolver.MAP;
+        JexlPropertyGet mget = rmap.getPropertyGet(uberspect, map, "key");
+        Assert.assertNotNull(mget);
+        JexlPropertySet mset = rmap.getPropertySet(uberspect, map, "key", "value");
+        Assert.assertNotNull(mset);
+    }
+
+    @Test
     public void testJexlStrategy() throws Exception {
         final JexlEngine jexl = new Engine();
         run171(jexl, true);
     }
 
     @Test
-    public void testMapStrategy() throws Exception {
+    public void testMyMapStrategy() throws Exception {
         final JexlEngine jexl = new JexlBuilder().arithmetic( new MapArithmetic(true)).create();
         run171(jexl, false);
     }
 
+    @Test
+    public void testMapStrategy() throws Exception {
+        final JexlEngine jexl = new JexlBuilder().strategy(JexlUberspect.MAP_STRATEGY).create();
+        run171(jexl, false);
+    }
+
     public void run171(JexlEngine jexl, boolean std) throws Exception {
         Object result;
         Map<String, Object> i = new HashMap<String, Object>();