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 2018/05/20 19:11:25 UTC

[commons-jexl] branch master updated: JEXL-260: added logic in interpreter, added test, updated release notes & changes

This is an automated email from the ASF dual-hosted git repository.

henrib pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-jexl.git


The following commit(s) were added to refs/heads/master by this push:
     new cd40852  JEXL-260: added logic in interpreter, added test, updated release notes & changes
cd40852 is described below

commit cd40852bf7063bb5aee438129015e085b51d68fe
Author: henrib <he...@apache.org>
AuthorDate: Sun May 20 21:10:58 2018 +0200

    JEXL-260: added logic in interpreter, added test, updated release notes & changes
---
 RELEASE-NOTES.txt                                  |   1 +
 .../apache/commons/jexl3/internal/Interpreter.java | 116 ++++++++++++++++-----
 src/site/xdoc/changes.xml                          |   3 +
 .../org/apache/commons/jexl3/ClassCreatorTest.java |  28 +++++
 4 files changed, 124 insertions(+), 24 deletions(-)

diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt
index 48a5f1a..f7b5af5 100644
--- a/RELEASE-NOTES.txt
+++ b/RELEASE-NOTES.txt
@@ -39,6 +39,7 @@ What's new in 3.2:
 New Features in 3.2:
 ====================
 
+* JEXL-260:      Automatically inject JexlContext in constructor call when possible
 * JEXL-252:      Allow for interpolated strings to be used in property access operators
 * JEXL-250:      Safe navigation operator
 * JEXL-248:      Allow range subexpression as an array property assignment identifier
diff --git a/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java b/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java
index bc006f6..6eb0fa0 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java
@@ -1397,6 +1397,28 @@ public class Interpreter extends InterpreterBase {
     }
 
     /**
+     * Concatenate arguments in call(...).
+     * @param target the pseudo-method owner, first to-be argument
+     * @param narrow whether we should attempt to narrow number arguments
+     * @param args   the other (non null) arguments
+     * @return the arguments array
+     */
+    private Object[] callArguments(Object target, boolean narrow, Object[] args) {
+        // makes target 1st args, copy others - optionally narrow numbers
+        Object[] nargv = new Object[args.length + 1];
+        if (narrow) {
+            nargv[0] = functionArgument(true, target);
+            for (int a = 1; a <= args.length; ++a) {
+                nargv[a] = functionArgument(true, args[a - 1]);
+            }
+        } else {
+            nargv[0] = target;
+            System.arraycopy(args, 0, nargv, 1, args.length);
+        }
+        return nargv;
+    }
+    
+    /**
      * Optionally narrows an argument for a function call.
      * @param narrow whether narrowing should occur
      * @param arg    the argument
@@ -1474,7 +1496,26 @@ public class Interpreter extends InterpreterBase {
             return me.tryInvoke(name, ii.context, ii.functionArguments(target, narrow, args));
         }
     }
+    
+    /**
+     * A ctor that needs a context as 1st argument.
+     */
+    private static class ContextualCtor extends Funcall {
+        /**
+         * Constructor.
+         * @param jme the method
+         * @param flag the narrow flag
+         */
+        protected ContextualCtor(JexlMethod jme, boolean flag) {
+            super(jme, flag);
+        }
 
+        @Override
+        protected Object tryInvoke(Interpreter ii, String name, Object target, Object[] args) {
+            return me.tryInvoke(name, target, ii.callArguments(ii.context, narrow, args));
+        }
+    }
+    
     /**
      * Calls a method (or function).
      * <p>
@@ -1656,9 +1697,11 @@ public class Interpreter extends InterpreterBase {
 
     @Override
     protected Object visit(ASTConstructorNode node, Object data) {
-        cancelCheck(node);
+        if (isCancelled()) {
+            throw new JexlException.Cancel(node);
+        }
         // first child is class or class name
-        Object cobject = node.jjtGetChild(0).jjtAccept(this, data);
+        final Object target = node.jjtGetChild(0).jjtAccept(this, data);
         // get the ctor args
         int argc = node.jjtGetNumChildren() - 1;
         Object[] argv = new Object[argc];
@@ -1667,39 +1710,64 @@ public class Interpreter extends InterpreterBase {
         }
 
         try {
-            // attempt to reuse last constructor cached in volatile JexlNode.value
+            boolean cacheable = cache;
+            // attempt to reuse last funcall cached in volatile JexlNode.value
             if (cache) {
                 Object cached = node.jjtGetValue();
-                if (cached instanceof JexlMethod) {
-                    JexlMethod mctor = (JexlMethod) cached;
-                    Object eval = mctor.tryInvoke(null, cobject, argv);
-                    if (!mctor.tryFailed(eval)) {
+                if (cached instanceof Funcall) {
+                    Object eval = ((Funcall) cached).tryInvoke(this, null, target, argv);
+                    if (JexlEngine.TRY_FAILED != eval) {
                         return eval;
                     }
                 }
             }
-            JexlMethod ctor = uberspect.getConstructor(cobject, argv);
-            // DG: If we can't find an exact match, narrow the parameters and try again
-            if (ctor == null) {
-                if (arithmetic.narrowArguments(argv)) {
-                    ctor = uberspect.getConstructor(cobject, argv);
+            boolean narrow = false;
+            JexlMethod ctor = null;
+            Funcall funcall = null;
+            while (true) {
+                // try as stated
+                ctor = uberspect.getConstructor(target, argv);
+                if (ctor != null) {
+                    if (cacheable && ctor.isCacheable()) {
+                        funcall = new Funcall(ctor, narrow);
+                    }
+                    break;
+                }
+                // try with prepending context as first argument
+                Object[] nargv = callArguments(context, narrow, argv);
+                ctor = uberspect.getConstructor(target, nargv);
+                if (ctor != null) {
+                    if (cacheable && ctor.isCacheable()) {
+                        funcall = new ContextualCtor(ctor, narrow);
+                    }
+                    argv = nargv;
+                    break;
                 }
-                if (ctor == null) {
-                    String dbgStr = cobject != null ? cobject.toString() : null;
-                    return unsolvableMethod(node, dbgStr);
+                // 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;
             }
-            Object instance = ctor.invoke(cobject, argv);
-            // cache executor in volatile JexlNode.value
-            if (cache && ctor.isCacheable()) {
-                node.jjtSetValue(ctor);
+            // we have either evaluated and returned or might have found a ctor
+            if (ctor != null) {
+                Object eval = ctor.invoke(target, argv);
+                // cache executor in volatile JexlNode.value
+                if (funcall != null) {
+                    node.jjtSetValue(funcall);
+                }
+                return eval;
             }
-            return instance;
-        } catch (JexlException xthru) {
-            throw xthru;
+            String tstr = target != null ? target.toString() : "?";
+            return unsolvableMethod(node, tstr);
+        } catch (JexlException.Method xmethod) {
+            throw xmethod;
         } catch (Exception xany) {
-            String dbgStr = cobject != null ? cobject.toString() : null;
-            throw invocationException(node, dbgStr, xany);
+            String tstr = target != null ? target.toString() : "?";
+            throw invocationException(node, tstr, xany);
         }
     }
 
diff --git a/src/site/xdoc/changes.xml b/src/site/xdoc/changes.xml
index b038449..225cf4e 100644
--- a/src/site/xdoc/changes.xml
+++ b/src/site/xdoc/changes.xml
@@ -26,6 +26,9 @@
     </properties>
     <body>
         <release version="3.2" date="unreleased">
+            <action dev="henrib" type="add" issue="JEXL-260">
+                Automatically inject JexlContext in constructor call when possible
+            </action>
             <action dev="henrib" type="add" issue="JEXL-252" due-to="Dmitri Blinov">
                 Allow for interpolated strings to be used in property access operators
             </action>
diff --git a/src/test/java/org/apache/commons/jexl3/ClassCreatorTest.java b/src/test/java/org/apache/commons/jexl3/ClassCreatorTest.java
index aec7c44..abde7a6 100644
--- a/src/test/java/org/apache/commons/jexl3/ClassCreatorTest.java
+++ b/src/test/java/org/apache/commons/jexl3/ClassCreatorTest.java
@@ -233,4 +233,32 @@ public class ClassCreatorTest extends JexlTestCase {
         r = s.execute(null, TwoCtors.class, 100f);
         Assert.assertEquals(-100, r);
     }
+        
+    public static class ContextualCtor {
+        int value = -1;
+        
+        public ContextualCtor(JexlContext ctxt) {
+            value = (Integer) ctxt.get("value");
+        }
+        
+        public ContextualCtor(JexlContext ctxt, int v) {
+            value = (Integer) ctxt.get("value") + v;
+        }
+        
+        public int getValue() {
+            return value;
+        }
+    }
+    
+    @Test
+    public void testContextualCtor() throws Exception {
+        MapContext ctxt = new MapContext();
+        ctxt.set("value", 42);
+        JexlScript s = jexl.createScript("(c)->{ new(c).value }");
+        Object r = s.execute(ctxt, ContextualCtor.class);
+        Assert.assertEquals(42, r);
+        s = jexl.createScript("(c, v)->{ new(c, v).value }");
+        r = s.execute(ctxt, ContextualCtor.class, 100);
+        Assert.assertEquals(142, r);
+    }
 }

-- 
To stop receiving notification emails like this one, please contact
henrib@apache.org.