Author: henrib
Date: Fri Aug 12 13:32:42 2011
New Revision: 1157103
URL: http://svn.apache.org/viewvc?rev=1157103&view=rev
Log:
Script are now callable from scripts (ie a variable containing a script - or
jexlMethod - can be called with arguments)
Modified:
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/Interpreter.java
commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl2/JexlTest.java
commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl2/MethodTest.java
Modified:
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/Interpreter.java
URL:
http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/Interpreter.java?rev=1157103&r1=1157102&r2=1157103&view=diff
==============================================================================
---
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/Interpreter.java
(original)
+++
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/Interpreter.java
Fri Aug 12 13:32:42 2011
@@ -194,15 +194,7 @@ public class Interpreter implements Pars
return node.jjtAccept(this, null);
} catch (JexlException.Return xreturn) {
Object value = xreturn.getValue();
- if (value instanceof JexlException.Return) {
- if (silent) {
- logger.warn(xreturn.getMessage(), xreturn.getCause());
- return null;
- }
- throw xreturn;
- } else {
- return value;
- }
+ return value;
} catch (JexlException xjexl) {
if (silent) {
logger.warn(xjexl.getMessage(), xjexl.getCause());
@@ -786,8 +778,8 @@ public class Interpreter implements Pars
return ((Set<?>) right).contains(left) ? Boolean.TRUE :
Boolean.FALSE;
}
// try contains on map key
- if (right instanceof Map<?,?>) {
- return ((Map<?,?>) right).containsKey(left) ? Boolean.TRUE :
Boolean.FALSE;
+ if (right instanceof Map<?, ?>) {
+ return ((Map<?, ?>) right).containsKey(left) ? Boolean.TRUE :
Boolean.FALSE;
}
// try contains on collection
if (right instanceof Collection<?>) {
@@ -946,32 +938,30 @@ public class Interpreter implements Pars
return map;
}
- /** {@inheritDoc} */
- public Object visit(ASTMethodNode node, Object data) {
+ /**
+ * Calls a method (or function).
+ * <p>
+ * Method resolution is a follows:
+ * 1 - attempt to find a method in the bean passed as parameter;
+ * 2 - if this fails, narrow the arguments and try again
+ * 3 - if this still fails, seeks a Script or JexlMethod as a property of
that bean.
+ * </p>
+ * @param node the method node
+ * @param bean the bean this method should be invoked upon
+ * @param methodNode the node carrying the method name
+ * @param argb the first argument index, child of the method node
+ * @return the result of the method invocation
+ */
+ private Object call(JexlNode node, Object bean, ASTIdentifier methodNode,
int argb) {
if (isCancelled()) {
throw new JexlException.Cancel(node);
}
- // the object to invoke the method on should be in the data argument
- if (data == null) {
- // if the first child of the (ASTReference) parent,
- // it is considered as calling a 'top level' function
- if (node.jjtGetParent().jjtGetChild(0) == node) {
- data = resolveNamespace(null, node);
- if (data == null) {
- data = context;
- }
- } else {
- throw new JexlException(node, "attempting to call method on
null");
- }
- }
- // objectNode 0 is the identifier (method name), the others are
parameters.
- String methodName = node.jjtGetChild(0).image;
-
- // get our arguments
- int argc = node.jjtGetNumChildren() - 1;
+ String methodName = methodNode.image;
+ // evaluate the arguments
+ int argc = node.jjtGetNumChildren() - argb;
Object[] argv = new Object[argc];
for (int i = 0; i < argc; i++) {
- argv[i] = node.jjtGetChild(i + 1).jjtAccept(this, null);
+ argv[i] = node.jjtGetChild(i + argb).jjtAccept(this, null);
}
JexlException xjexl = null;
@@ -981,26 +971,50 @@ public class Interpreter implements Pars
Object cached = node.jjtGetValue();
if (cached instanceof JexlMethod) {
JexlMethod me = (JexlMethod) cached;
- Object eval = me.tryInvoke(methodName, data, argv);
+ Object eval = me.tryInvoke(methodName, bean, argv);
if (!me.tryFailed(eval)) {
return eval;
}
}
}
- JexlMethod vm = uberspect.getMethod(data, methodName, argv, node);
- // DG: If we can't find an exact match, narrow the parameters and
try again!
+ boolean cacheable = cache;
+ JexlMethod vm = uberspect.getMethod(bean, methodName, argv, node);
+ // DG: If we can't find an exact match, narrow the parameters and
try again
if (vm == null) {
if (arithmetic.narrowArguments(argv)) {
- vm = uberspect.getMethod(data, methodName, argv, node);
+ vm = uberspect.getMethod(bean, methodName, argv, node);
}
if (vm == null) {
- xjexl = new JexlException.Method(node, methodName);
+ Object functor = null;
+ // could not find a method, try as a var
+ if (bean == context) {
+ int register = methodNode.getRegister();
+ if (register >= 0) {
+ functor = registers[register];
+ } else {
+ functor = context.get(methodName);
+ }
+ } else {
+ JexlPropertyGet gfunctor =
uberspect.getPropertyGet(bean, methodName, node);
+ if (gfunctor != null) {
+ functor = gfunctor.tryInvoke(bean, methodName);
+ }
+ }
+ // script of jexl method will do
+ if (functor instanceof Script) {
+ return ((Script) functor).execute(context, argv.length
> 0 ? argv : null);
+ } else if (functor instanceof JexlMethod) {
+ vm = (JexlMethod) functor;
+ cacheable = false;
+ } else {
+ xjexl = new JexlException.Method(node, methodName);
+ }
}
}
if (xjexl == null) {
- Object eval = vm.invoke(data, argv); // vm cannot be null if
xjexl is null
+ Object eval = vm.invoke(bean, argv); // vm cannot be null if
xjexl is null
// cache executor in volatile JexlNode.value
- if (cache && vm.isCacheable()) {
+ if (cacheable && vm.isCacheable()) {
node.jjtSetValue(vm);
}
return eval;
@@ -1014,6 +1028,36 @@ public class Interpreter implements Pars
}
/** {@inheritDoc} */
+ public Object visit(ASTMethodNode node, Object data) {
+ // the object to invoke the method on should be in the data argument
+ if (data == null) {
+ // if the method node is the first child of the (ASTReference)
parent,
+ // it is considered as calling a 'top level' function
+ if (node.jjtGetParent().jjtGetChild(0) == node) {
+ data = resolveNamespace(null, node);
+ if (data == null) {
+ data = context;
+ }
+ } else {
+ throw new JexlException(node, "attempting to call method on
null");
+ }
+ }
+ // objectNode 0 is the identifier (method name), the others are
parameters.
+ ASTIdentifier methodNode = (ASTIdentifier) node.jjtGetChild(0);
+ return call(node, data, methodNode, 1);
+ }
+
+ /** {@inheritDoc} */
+ public Object visit(ASTFunctionNode node, Object data) {
+ // objectNode 0 is the prefix
+ String prefix = node.jjtGetChild(0).image;
+ Object namespace = resolveNamespace(prefix, node);
+ // objectNode 1 is the identifier , the others are parameters.
+ ASTIdentifier functionNode = (ASTIdentifier) node.jjtGetChild(1);
+ return call(node, namespace, functionNode, 2);
+ }
+
+ /** {@inheritDoc} */
public Object visit(ASTConstructorNode node, Object data) {
if (isCancelled()) {
throw new JexlException.Cancel(node);
@@ -1052,65 +1096,6 @@ public class Interpreter implements Pars
}
/** {@inheritDoc} */
- public Object visit(ASTFunctionNode node, Object data) {
- if (isCancelled()) {
- throw new JexlException.Cancel(node);
- }
- // objectNode 0 is the prefix
- String prefix = node.jjtGetChild(0).image;
- Object namespace = resolveNamespace(prefix, node);
- // objectNode 1 is the identifier , the others are parameters.
- String function = node.jjtGetChild(1).image;
-
- // get our args
- int argc = node.jjtGetNumChildren() - 2;
- Object[] argv = new Object[argc];
- for (int i = 0; i < argc; i++) {
- argv[i] = node.jjtGetChild(i + 2).jjtAccept(this, null);
- }
-
- JexlException xjexl = null;
- try {
- // 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(function, namespace, argv);
- if (!me.tryFailed(eval)) {
- return eval;
- }
- }
- }
- JexlMethod vm = uberspect.getMethod(namespace, function, argv,
node);
- // DG: If we can't find an exact match, narrow the parameters and
- // try again!
- if (vm == null) {
- // replace all numbers with the smallest type that will fit
- if (arithmetic.narrowArguments(argv)) {
- vm = uberspect.getMethod(namespace, function, argv, node);
- }
- if (vm == null) {
- xjexl = new JexlException.Method(node, function);
- }
- }
- if (xjexl == null) {
- Object eval = vm.invoke(namespace, argv); // vm cannot be null
if xjexl is null
- // cache executor in volatile JexlNode.value
- if (cache && vm.isCacheable()) {
- node.jjtSetValue(vm);
- }
- return eval;
- }
- } catch (InvocationTargetException e) {
- xjexl = new JexlException(node, "function invocation error",
e.getCause());
- } catch (Exception e) {
- xjexl = new JexlException(node, "function error", e);
- }
- return invocationFailed(xjexl);
- }
-
- /** {@inheritDoc} */
public Object visit(ASTModNode node, Object data) {
Object left = node.jjtGetChild(0).jjtAccept(this, data);
Object right = node.jjtGetChild(1).jjtAccept(this, data);
@@ -1163,8 +1148,8 @@ public class Interpreter implements Pars
return ((Set<?>) right).contains(left) ? Boolean.FALSE :
Boolean.TRUE;
}
// try contains on map key
- if (right instanceof Map<?,?>) {
- return ((Map<?,?>) right).containsKey(left) ? Boolean.FALSE :
Boolean.TRUE;
+ if (right instanceof Map<?, ?>) {
+ return ((Map<?, ?>) right).containsKey(left) ? Boolean.FALSE :
Boolean.TRUE;
}
// try contains on collection
if (right instanceof Collection<?>) {
Modified:
commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl2/JexlTest.java
URL:
http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl2/JexlTest.java?rev=1157103&r1=1157102&r2=1157103&view=diff
==============================================================================
---
commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl2/JexlTest.java
(original)
+++
commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl2/JexlTest.java
Fri Aug 12 13:32:42 2011
@@ -497,7 +497,7 @@ public class JexlTest extends JexlTestCa
jc.set("foo", foo );
assertExpression(jc, "foo.bar", "123");
- }
+ }
/**
* Tests string literals
Modified:
commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl2/MethodTest.java
URL:
http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl2/MethodTest.java?rev=1157103&r1=1157102&r2=1157103&view=diff
==============================================================================
---
commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl2/MethodTest.java
(original)
+++
commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl2/MethodTest.java
Fri Aug 12 13:32:42 2011
@@ -16,6 +16,9 @@
*/
package org.apache.commons.jexl2;
+import java.util.HashMap;
+import java.util.Map;
+import org.apache.commons.jexl2.introspection.JexlMethod;
import org.apache.commons.jexl2.junit.Asserter;
/**
@@ -24,9 +27,7 @@ import org.apache.commons.jexl2.junit.As
* @since 2.0
*/
public class MethodTest extends JexlTestCase {
-
private Asserter asserter;
-
private static final String METHOD_STRING = "Method string";
public static class VarArgs {
@@ -34,12 +35,12 @@ public class MethodTest extends JexlTest
int result = 0;
if (args != null) {
for (int i = 0; i < args.length; i++) {
- result += args[i] != null ? args[i].intValue() : -100;
+ result += args[i] != null ? args[i].intValue() : -100;
}
} else {
result = -1000;
}
- return "Varargs:"+result;
+ return "Varargs:" + result;
}
public String callMixed(Integer fixed, Integer... args) {
@@ -51,9 +52,9 @@ public class MethodTest extends JexlTest
} else {
result -= 1000;
}
- return "Mixed:"+result;
+ return "Mixed:" + result;
}
-
+
public String callMixed(String mixed, Integer... args) {
int result = 0;
if (args != null) {
@@ -63,7 +64,7 @@ public class MethodTest extends JexlTest
} else {
result = -1000;
}
- return mixed+":"+result;
+ return mixed + ":" + result;
}
}
@@ -71,29 +72,35 @@ public class MethodTest extends JexlTest
public int ten() {
return 10;
}
+
public int plus10(int num) {
return num + 10;
}
+
public static int TWENTY() {
return 20;
}
+
public static int PLUS20(int num) {
return num + 20;
}
+
public static Class<?> NPEIfNull(Object x) {
return x.getClass();
}
}
-
+
public static class EnhancedContext extends MapContext {
int factor = 6;
}
public static class ContextualFunctor {
private final EnhancedContext context;
+
public ContextualFunctor(EnhancedContext theContext) {
context = theContext;
}
+
public int ratio(int n) {
context.factor -= 1;
return n / context.factor;
@@ -134,7 +141,7 @@ public class MethodTest extends JexlTest
asserter.assertExpression("test.callMixed('jexl')", "jexl:0");
// Java and JEXL equivalent behavior: 'jexl:-1000' expected
//{
- assertEquals("jexl:-1000", test.callMixed("jexl", (Integer []) null));
+ assertEquals("jexl:-1000", test.callMixed("jexl", (Integer[]) null));
asserter.assertExpression("test.callMixed('jexl', null)",
"jexl:-1000");
//}
asserter.assertExpression("test.callMixed('jexl', 2)", "jexl:2");
@@ -148,13 +155,13 @@ public class MethodTest extends JexlTest
try {
JEXL.invokeMethod(func, "nonExistentMethod");
fail("method does not exist!");
- } catch(Exception xj0) {
+ } catch (Exception xj0) {
// ignore
}
try {
JEXL.invokeMethod(func, "NPEIfNull", (Object[]) null);
fail("method should have thrown!");
- } catch(Exception xj0) {
+ } catch (Exception xj0) {
// ignore
}
}
@@ -204,7 +211,7 @@ public class MethodTest extends JexlTest
}
}
- public void testTopLevelCall() throws Exception {
+ public void testTopLevelCall() throws Exception {
java.util.Map<String, Object> funcs = new java.util.HashMap<String,
Object>();
funcs.put(null, new Functor());
funcs.put("math", new MyMath());
@@ -228,11 +235,11 @@ public class MethodTest extends JexlTest
jc.set("pi", new Double(Math.PI));
e = JEXL.createExpression("math:cos(pi)");
o = e.evaluate(jc);
- assertEquals(Double.valueOf(-1),o);
-
+ assertEquals(Double.valueOf(-1), o);
+
e = JEXL.createExpression("cx:ratio(10) + cx:ratio(20)");
o = e.evaluate(jc);
- assertEquals(Integer.valueOf(7),o);
+ assertEquals(Integer.valueOf(7), o);
}
public void testNamespaceCall() throws Exception {
@@ -267,4 +274,87 @@ public class MethodTest extends JexlTest
assertEquals("Result is not 40", new Integer(40), o);
}
+ public static class ScriptContext extends MapContext implements
NamespaceResolver {
+ Map<String, Object> nsScript;
+
+ ScriptContext(Map<String, Object> ns) {
+ nsScript = ns;
+ }
+
+ public Object resolveNamespace(String name) {
+ if (name == null) {
+ return this;
+ }
+ if ("script".equals(name)) {
+ return nsScript;
+ }
+ return null;
+ }
+ }
+
+ public void testScriptCall() throws Exception {
+ JexlContext context = new MapContext();
+ Script plus = JEXL.createScript("a + b", new String[]{"a", "b"});
+ context.set("plus", plus);
+ Script forty2 = JEXL.createScript("plus(4, 2) * plus(4, 3)");
+ Object o = forty2.execute(context);
+ assertEquals("Result is not 42", new Integer(42), o);
+
+ Map<String, Object> foo = new HashMap<String, Object>();
+ foo.put("plus", plus);
+ context.set("foo", foo);
+ forty2 = JEXL.createScript("foo.plus(4, 2) * foo.plus(4, 3)");
+ o = forty2.execute(context);
+ assertEquals("Result is not 42", new Integer(42), o);
+
+ context = new ScriptContext(foo);
+ forty2 = JEXL.createScript("script:plus(4, 2) * script:plus(4, 3)");
+ o = forty2.execute(context);
+ assertEquals("Result is not 42", new Integer(42), o);
+
+ final JexlArithmetic ja = JEXL.getArithmetic();
+ JexlMethod mplus = new JexlMethod() {
+ public Object invoke(Object obj, Object[] params) throws Exception
{
+ if (obj instanceof Map<?,?>) {
+ return ja.add(params[0], params[1]);
+ } else {
+ throw new Exception("not a script context");
+ }
+ }
+
+ public Object tryInvoke(String name, Object obj, Object[] params) {
+ try {
+ if ("plus".equals(name)) {
+ return invoke(obj, params);
+ }
+ } catch (Exception xany) {
+ // ignore and fail by returning this
+ }
+ return this;
+ }
+
+ public boolean tryFailed(Object rval) {
+ // this is the marker for failure
+ return rval == this;
+ }
+
+ public boolean isCacheable() {
+ return true;
+ }
+
+ public Class<?> getReturnType() {
+ return Object.class;
+ }
+ };
+
+ foo.put("PLUS", mplus);
+ forty2 = JEXL.createScript("script:PLUS(4, 2) * script:PLUS(4, 3)");
+ o = forty2.execute(context);
+ assertEquals("Result is not 42", new Integer(42), o);
+
+ context.set("foo.bar", foo);
+ forty2 = JEXL.createScript("foo.'bar'.PLUS(4, 2) * foo.bar.PLUS(4,
3)");
+ o = forty2.execute(context);
+ assertEquals("Result is not 42", new Integer(42), o);
+ }
}
\ No newline at end of file