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