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


Reply via email to