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