commons-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From hen...@apache.org
Subject svn commit: r1811336 - in /commons/proper/jexl/trunk/src: main/java/org/apache/commons/jexl3/ main/java/org/apache/commons/jexl3/internal/ test/java/org/apache/commons/jexl3/
Date Fri, 06 Oct 2017 14:31:39 GMT
Author: henrib
Date: Fri Oct  6 14:31:39 2017
New Revision: 1811336

URL: http://svn.apache.org/viewvc?rev=1811336&view=rev
Log:
JEXL-240:
Expose the Jexl engine evaluating a script/expression as a thread local;
Make classes functors, ie class(arg) will attempt to call a ctor, a simpler version of new(class,
arg);
Fix antish variable used as method/function call; 

Added:
    commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/AntishCallTest.java 
 (with props)
Modified:
    commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/JexlEngine.java
    commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/Engine.java
    commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java

Modified: commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/JexlEngine.java
URL: http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/JexlEngine.java?rev=1811336&r1=1811335&r2=1811336&view=diff
==============================================================================
--- commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/JexlEngine.java (original)
+++ commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/JexlEngine.java Fri Oct
 6 14:31:39 2017
@@ -75,6 +75,27 @@ public abstract class JexlEngine {
     }
 
     /**
+     * The thread local engine.
+     */
+    protected static final java.lang.ThreadLocal<JexlEngine> ENGINE =
+            new java.lang.ThreadLocal<JexlEngine>() {
+                @Override
+                protected JexlEngine initialValue() {
+                    return null;
+                }
+            };
+
+    /**
+     * Accesses the current thread local engine.
+     * <p>Advanced: you should only use this to retrieve the engine within a method/ctor
called through the evaluation
+     * of a script/expression.</p>
+     * @return the engine or null
+     */
+    public static JexlEngine getThreadEngine() {
+        return ENGINE.get();
+    }
+
+    /**
      * Sets the current thread local context.
      * <p>This should only be used carefully, for instance when re-evaluating a "stored"
script that requires a
      * given Namespace resolver. Remember to synchronize access if context is shared between
threads.

Modified: commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/Engine.java
URL: http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/Engine.java?rev=1811336&r1=1811335&r2=1811336&view=diff
==============================================================================
--- commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/Engine.java
(original)
+++ commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/Engine.java
Fri Oct  6 14:31:39 2017
@@ -419,6 +419,17 @@ public class Engine extends JexlEngine {
     }
 
     /**
+     * Swaps the current thread local engine.
+     * @param jexl the engine or null
+     * @return the previous thread local engine
+     */
+    protected JexlEngine putThreadEngine(JexlEngine jexl) {
+        JexlEngine pjexl = ENGINE.get();
+        ENGINE.set(jexl);
+        return pjexl;
+    }
+
+    /**
      * Gets the list of variables accessed by a script.
      * <p>This method will visit all nodes of a script and extract all variables whether
they
      * are written in 'dot' or 'bracketed' notation. (a.b is equivalent to a['b']).</p>

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=1811336&r1=1811335&r2=1811336&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
Fri Oct  6 14:31:39 2017
@@ -176,14 +176,16 @@ public class Interpreter extends Interpr
      * @throws JexlException if any error occurs during interpretation.
      */
     public Object interpret(JexlNode node) {
-        JexlContext.ThreadLocal local = null;
+        JexlContext.ThreadLocal tcontext = null;
+        JexlEngine tjexl = null;
         try {
             if (isCancelled()) {
                 throw new JexlException.Cancel(node);
             }
             if (context instanceof JexlContext.ThreadLocal) {
-                local = jexl.putThreadLocal((JexlContext.ThreadLocal) context);
+                tcontext = jexl.putThreadLocal((JexlContext.ThreadLocal) context);
             }
+            tjexl = jexl.putThreadEngine(jexl);
             return node.jjtAccept(this, null);
         } catch (JexlException.Return xreturn) {
             return xreturn.getValue();
@@ -211,8 +213,9 @@ public class Interpreter extends Interpr
                     functors = null;
                 }
             }
+            jexl.putThreadEngine(tjexl);
             if (context instanceof JexlContext.ThreadLocal) {
-                jexl.putThreadLocal(local);
+                jexl.putThreadLocal(tcontext);
             }
         }
         return null;
@@ -1020,6 +1023,19 @@ public class Interpreter extends Interpr
             objectNode = node.jjtGetChild(c);
             if (objectNode instanceof ASTMethodNode) {
                 if (object == null) {
+                    // we may be performing a method call on an antish var
+                    if (ant != null) {
+                        JexlNode child = objectNode.jjtGetChild(0);
+                        if (child instanceof ASTIdentifierAccess) {
+                            ant.append('.');
+                            ant.append(((ASTIdentifierAccess) child).getName());
+                            object = context.get(ant.toString());
+                            if (object != null) {
+                                object = visit((ASTMethodNode) objectNode, object, context);
+                            }
+                            continue;
+                        }
+                    }
                     break;
                 } else {
                     antish = false;
@@ -1291,18 +1307,32 @@ public class Interpreter extends Interpr
 
     @Override
     protected Object visit(final ASTMethodNode node, Object data) {
+        return visit(node, null, data);
+    }
+
+    /**
+     * Execute a method call, ie syntactically written as name.call(...)
+     * @param node the actual method call node
+     * @param object non null when name.call is an antish variable
+     * @param data the context
+     * @return the method call result
+     */
+    private Object visit(final ASTMethodNode node, Object object, Object data) {
         // left contains the reference to the method
         final JexlNode methodNode = node.jjtGetChild(0);
-        Object object = null;
-        JexlNode objectNode = null;
         Object method;
         // 1: determine object and method or functor
         if (methodNode instanceof ASTIdentifierAccess) {
             method = methodNode;
-            object = data;
             if (object == null) {
-                // no object, we fail
-                return unsolvableMethod(objectNode, "<null>.<?>(...)");
+                object = data;
+                if (object == null) {
+                    // no object, we fail
+                    return unsolvableMethod(methodNode, "<null>.<?>(...)");
+                }
+            } else {
+                // edge case of antish var used as functor
+                method = object;
             }
         } else {
             method = methodNode.jjtAccept(this, data);
@@ -1483,7 +1513,7 @@ public class Interpreter extends Interpr
             symbol = -1;
             functor = null;
         } else if (functor != null) {
-            symbol = -2;
+            symbol = -1 - 1; // -2;
             methodName = null;
         } else {
             return unsolvableMethod(node, "?");
@@ -1522,6 +1552,9 @@ public class Interpreter extends Interpr
                         }
                     }
                 }
+            } else {
+                // if no name, we should not cache
+                cacheable = false;
             }
             boolean narrow = false;
             JexlMethod vm = null;
@@ -1560,6 +1593,7 @@ public class Interpreter extends Interpr
                 final Object[] nargv;
                 final String mname;
                 // this may happen without the above when we are chaining call like x(a)(b)
+                // or when a var/symbol or antish var is used as a "function" name
                 if (functor != null) {
                     // lambda, script or jexl method will do
                     if (functor instanceof JexlScript) {
@@ -1568,6 +1602,12 @@ public class Interpreter extends Interpr
                     if (functor instanceof JexlMethod) {
                         return ((JexlMethod) functor).invoke(target, argv);
                     }
+                    if (functor instanceof Class<?>) {
+                        vm = uberspect.getConstructor(functor, argv);
+                        if (vm != null) {
+                            return vm.invoke(functor, argv);
+                        }
+                    }
                     // a generic callable
                     mname = "call";
                     vm = uberspect.getMethod(functor, mname, argv);

Added: commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/AntishCallTest.java
URL: http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/AntishCallTest.java?rev=1811336&view=auto
==============================================================================
--- commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/AntishCallTest.java (added)
+++ commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/AntishCallTest.java Fri
Oct  6 14:31:39 2017
@@ -0,0 +1,177 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.jexl3;
+
+import java.util.Map;
+import java.util.TreeMap;
+import org.junit.Assert;
+import org.junit.Test;
+
+/**
+ * Test cases for calling antish variables as method names (JEXL-240);
+ * Also tests that a class instance is a functor that invokes the constructor when called.
+ */
+@SuppressWarnings({"UnnecessaryBoxing", "AssertEqualsBetweenInconvertibleTypes"})
+public class AntishCallTest extends JexlTestCase {
+
+    public AntishCallTest() {
+        super("AntishCallTest");
+    }
+
+    /**
+     * Wraps a class.
+     */
+    public class ClassReference {
+        Class<?> clazz;
+        ClassReference(Class<?> c) {
+            this.clazz = c;
+        }
+    }
+    /**
+     * Considers any call using a class reference as functor as a call to its constructor.
+     * <p>Note that before 3.2, a class was not considered a functor.
+     * @param clazz the class we seek to instantiate
+     * @param args the constructor arguments
+     * @return an instance if that was possible
+     */
+    public static Object callConstructor(JexlEngine engine, ClassReference ref, Object...
args) {
+        return callConstructor(engine, ref.clazz, args);
+    }
+    public static Object callConstructor(JexlEngine engine, Class<?> clazz, Object...
args) {
+        if (clazz == null || clazz.isPrimitive() || clazz.isInterface()
+            || clazz.isMemberClass() || clazz.isAnnotation() || clazz.isArray()) {
+            throw new ArithmeticException("not a constructible object");
+        }
+        JexlEngine jexl = engine;
+        if (jexl == null) {
+            jexl = JexlEngine.getThreadEngine();
+            if (jexl == null) {
+                throw new ArithmeticException("no engine to solve constructor");
+            }
+        }
+        return jexl.newInstance(clazz, args);
+    }
+
+    /**
+     * An arithmetic that considers class objects as callable.
+     */
+    public class CallSupportArithmetic extends JexlArithmetic {
+        public CallSupportArithmetic(boolean strict) {
+            super(strict);
+        }
+
+        public Object call(ClassReference clazz, Object... args) {
+            return callConstructor(null, clazz, args);
+        }
+    }
+
+    /**
+     * A context that considers class references as callable.
+     */
+    public static class CallSupportContext extends MapContext {
+        CallSupportContext(Map<String, Object> map) {
+            super(map);
+        }
+        private JexlEngine engine;
+
+        @Override public Object get(String str) {
+            if (!super.has(str)) {
+                try {
+                    return CallSupportContext.class.getClassLoader().loadClass(str);
+                } catch(Exception xany) {
+                    return null;
+                }
+            }
+            return super.get(str);
+        }
+
+        @Override public boolean has(String str) {
+            if (!super.has(str)){
+                try {
+                    return CallSupportContext.class.getClassLoader().loadClass(str) != null;
+                } catch(Exception xany) {
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        CallSupportContext engine(JexlEngine j) {
+            engine = j;
+            return this;
+        }
+
+        public Object call(ClassReference clazz, Object... args) {
+            return callConstructor(engine, clazz, args);
+        }
+    }
+
+    @Test
+    public void testAntishAContextVar() throws Exception {
+        JexlEngine jexl = new JexlBuilder().cache(512).strict(true).silent(false).create();
+        Map<String,Object> lmap = new TreeMap<String,Object>();
+        JexlContext jc = new CallSupportContext(lmap).engine(jexl);
+        runTestCall(jexl, jc);
+        lmap.put("java.math.BigInteger", new ClassReference(java.math.BigInteger.class));
+        runTestCall(jexl, jc);
+        lmap.remove("java.math.BigInteger");
+        runTestCall(jexl, jc);
+    }
+
+    @Test
+    public void testAntishAContextVar2() throws Exception {
+        JexlEngine jexl = new JexlBuilder().cache(512).strict(true).silent(false).create();
+        Map<String,Object> lmap = new TreeMap<String,Object>();
+        JexlContext jc = new CallSupportContext(lmap);
+        runTestCall(jexl, jc);
+        lmap.put("java.math.BigInteger", new ClassReference(java.math.BigInteger.class));
+        runTestCall(jexl, jc);
+        lmap.remove("java.math.BigInteger");
+        runTestCall(jexl, jc);
+    }
+
+    @Test
+    public void testAntishArithmetic() throws Exception {
+        CallSupportArithmetic ja = new CallSupportArithmetic(true);
+        JexlEngine jexl = new JexlBuilder().cache(512).strict(true).silent(false).arithmetic(ja).create();
+        Map<String,Object> lmap = new TreeMap<String,Object>();
+        JexlContext jc = new MapContext(lmap);
+        lmap.put("java.math.BigInteger", java.math.BigInteger.class);
+        runTestCall(jexl, jc);
+        lmap.put("java.math.BigInteger", new ClassReference(java.math.BigInteger.class));
+        runTestCall(jexl, jc);
+        lmap.remove("java.math.BigInteger");
+        try {
+            runTestCall(jexl, jc);
+        Assert.fail("should have failed");
+        } catch(JexlException xjexl) {
+            //
+        }
+    }
+
+    void runTestCall(JexlEngine jexl, JexlContext jc) throws Exception {
+        JexlScript check1 = jexl.createScript("var x = java.math.BigInteger; x('1234')");
+        JexlScript check2 = jexl.createScript("java.math.BigInteger('1234')");
+
+        Object o1 = check1.execute(jc);
+        Assert.assertEquals("Result is not 1234", new java.math.BigInteger("1234"), o1);
+
+        Object o2 = check2.execute(jc);
+        Assert.assertEquals("Result is not 1234", new java.math.BigInteger("1234"), o2);
+    }
+
+}
\ No newline at end of file

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



Mime
View raw message