Author: henrib
Date: Wed Jul 13 21:05:13 2011
New Revision: 1146477

URL: http://svn.apache.org/viewvc?rev=1146477&view=rev
Log:
JEXL-115
* Added method to create Callable in/from Script (as well as JEXL-114 related 
getVariables, getParameters, getLocalVariables)
* Added checks in Interpreter wrt thread interruption
* Added specific exception to let the Interpreter unwind its stack under 
cancellation
* Added tests

Added:
    
commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl2/ScriptCallableTest.java
   (with props)
Modified:
    
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/ExpressionImpl.java
    
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/Interpreter.java
    
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/JexlException.java
    commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/Script.java

Modified: 
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/ExpressionImpl.java
URL: 
http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/ExpressionImpl.java?rev=1146477&r1=1146476&r2=1146477&view=diff
==============================================================================
--- 
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/ExpressionImpl.java
 (original)
+++ 
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/ExpressionImpl.java
 Wed Jul 13 21:05:13 2011
@@ -14,9 +14,12 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
 package org.apache.commons.jexl2;
 
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.Callable;
+
 import org.apache.commons.jexl2.parser.ASTJexlScript;
 
 /**
@@ -37,7 +40,6 @@ public class ExpressionImpl implements E
      */
     protected final ASTJexlScript script;
 
-
     /**
      * Do not let this be generally instantiated with a 'new'.
      *
@@ -69,7 +71,7 @@ public class ExpressionImpl implements E
     public String dump() {
         Debugger debug = new Debugger();
         boolean d = debug.debug(script);
-        return debug.data() + (d? " /*" + debug.start() + ":" + debug.end() + 
"*/" : "/*?:?*/ ");
+        return debug.data() + (d ? " /*" + debug.start() + ":" + debug.end() + 
"*/" : "/*?:?*/ ");
     }
 
     /**
@@ -78,26 +80,9 @@ public class ExpressionImpl implements E
     public String getExpression() {
         return expression;
     }
-    
-    /**
-     * Gets this script parameters.
-     * @return the parameters or null
-     */
-    public String[] getParameters() {
-        return script.getParameters();
-    }
-       
-    /**
-     * Gets this script local variables.
-     * @return the local variables or null
-     */
-    public String[] getLocalVariables() {
-        return script.getLocalVariables();
-    }
 
     /**
-     * Provide a string representation of the expression.
-     *
+     * Provide a string representation of this expression.
      * @return the expression or blank if it's null.
      */
     @Override
@@ -121,14 +106,63 @@ public class ExpressionImpl implements E
         interpreter.setArguments(script.getRegisters(), 
script.createArguments((Object[]) null));
         return interpreter.interpret(script);
     }
-    
+
     /**
      * {@inheritDoc}
      */
-    public Object execute(JexlContext context, Object...args) {
+    public Object execute(JexlContext context, Object... args) {
         Interpreter interpreter = jexl.createInterpreter(context);
         interpreter.setArguments(script.getRegisters(), 
script.createArguments(args));
         return interpreter.interpret(script);
     }
 
+    /**
+     * {@inheritDoc}
+     */
+    public String[] getParameters() {
+        return script.getParameters();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public String[] getLocalVariables() {
+        return script.getLocalVariables();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Set<List<String>> getVariables() {
+        return jexl.getVariables(this);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Callable<Object> callable(JexlContext context) {
+        return callable(context, (Object[]) null);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Callable<Object> callable(JexlContext context, Object... args) {
+        final Interpreter interpreter = jexl.createInterpreter(context);
+        interpreter.setArguments(script.getRegisters(), 
script.createArguments(args));
+
+        return new Callable<Object>() {
+            /** Use interpreter as marker for not having run. */
+            Object result = interpreter;
+
+            public Object call() throws Exception {
+                if (result == interpreter) {
+                    result = interpreter.interpret(script);
+                }
+                return result;
+            }
+
+        };
+    }
+
 }
\ No newline at end of file

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=1146477&r1=1146476&r2=1146477&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
 Wed Jul 13 21:05:13 2011
@@ -113,6 +113,8 @@ public class Interpreter implements Pars
     protected Object[] registers = null;
     /** Parameter names if any. */
     protected String[] parameters = null;
+    /** Cancellation support. */
+    protected volatile boolean cancelled = false;
     /** Empty parameters for method matching. */
     protected static final Object[] EMPTY_PARAMS = new Object[0];
 
@@ -289,6 +291,14 @@ public class Interpreter implements Pars
     }
 
     /**
+     * Checks whether this interpreter execution was cancelled due to thread 
interruption.
+     * @return true if cancelled, false otherwise
+     */
+    protected boolean isCancelled() {
+        return cancelled |= Thread.interrupted();
+    }
+    
+    /**
      * Resolves a namespace, eventually allocating an instance using context 
as constructor argument.
      * The lifetime of such instances span the current expression or script 
evaluation.
      *
@@ -702,6 +712,9 @@ public class Interpreter implements Pars
             Iterator<?> itemsIterator = 
getUberspect().getIterator(iterableValue, node);
             if (itemsIterator != null) {
                 while (itemsIterator.hasNext()) {
+                    if (isCancelled()) {
+                        throw new JexlException.Cancel(node);
+                    }
                     // set loopVariable to value of iterator
                     Object value = itemsIterator.next();
                     if (register < 0) {
@@ -752,6 +765,9 @@ public class Interpreter implements Pars
 
     /** {@inheritDoc} */
     public Object visit(ASTIdentifier node, Object data) {
+        if (isCancelled()) {
+            throw new JexlException.Cancel(node);
+        }
         String name = node.image;
         if (data == null) {
             int register = node.getRegister();
@@ -866,6 +882,9 @@ public class Interpreter implements Pars
 
     /** {@inheritDoc} */
     public Object visit(ASTMethodNode node, Object data) {
+        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,
@@ -930,6 +949,9 @@ public class Interpreter implements Pars
 
     /** {@inheritDoc} */
     public Object visit(ASTConstructorNode node, Object data) {
+        if (isCancelled()) {
+            throw new JexlException.Cancel(node);
+        }
         // first child is class or class name
         Object cobject = node.jjtGetChild(0).jjtAccept(this, data);
         // get the ctor args
@@ -965,6 +987,9 @@ 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 = ((ASTIdentifier) node.jjtGetChild(0)).image;
         Object namespace = resolveNamespace(prefix, node);
@@ -1117,6 +1142,9 @@ public class Interpreter implements Pars
         boolean isVariable = true;
         int v = 0;
         for (int c = 0; c < numChildren; c++) {
+            if (isCancelled()) {
+                throw new JexlException.Cancel(node);
+            }
             JexlNode theNode = node.jjtGetChild(c);
             // integer literals may be part of an antish var name only if no 
bean was found so far
             if (result == null && theNode instanceof ASTNumberLiteral && 
((ASTNumberLiteral) theNode).isInteger()) {
@@ -1146,13 +1174,13 @@ public class Interpreter implements Pars
         }
         return result;
     }
-    
+
     /** {@inheritDoc} */
     public Object visit(ASTReferenceExpression node, Object data) {
         ASTArrayAccess upper = node;
         return visit(upper, data);
     }
-    
+
     /** {@inheritDoc} */
     public Object visit(ASTReturnStatement node, Object data) {
         Object val = node.jjtGetChild(0).jjtAccept(this, data);
@@ -1246,10 +1274,14 @@ public class Interpreter implements Pars
         /* first objectNode is the expression */
         Node expressionNode = node.jjtGetChild(0);
         while (arithmetic.toBoolean(expressionNode.jjtAccept(this, data))) {
+            if (isCancelled()) {
+                throw new JexlException.Cancel(node);
+            }
             // execute statement
-            result = node.jjtGetChild(1).jjtAccept(this, data);
+            if (node.jjtGetNumChildren() > 1) {
+                result = node.jjtGetChild(1).jjtAccept(this, data);
+            }
         }
-
         return result;
     }
 
@@ -1312,6 +1344,9 @@ public class Interpreter implements Pars
         if (object == null) {
             throw new JexlException(node, "object is null");
         }
+        if (isCancelled()) {
+            throw new JexlException.Cancel(node);
+        }
         // attempt to reuse last executor cached in volatile JexlNode.value
         if (node != null && cache) {
             Object cached = node.jjtGetValue();
@@ -1372,6 +1407,9 @@ public class Interpreter implements Pars
      * @param node the node that evaluated as the object
      */
     protected void setAttribute(Object object, Object attribute, Object value, 
JexlNode node) {
+        if (isCancelled()) {
+            throw new JexlException.Cancel(node);
+        }
         // attempt to reuse last executor cached in volatile JexlNode.value
         if (node != null && cache) {
             Object cached = node.jjtGetValue();

Modified: 
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/JexlException.java
URL: 
http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/JexlException.java?rev=1146477&r1=1146476&r2=1146477&view=diff
==============================================================================
--- 
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/JexlException.java
 (original)
+++ 
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/JexlException.java
 Wed Jul 13 21:05:13 2011
@@ -81,9 +81,9 @@ public class JexlException extends Runti
     /**
      * The class of exceptions that will force the interpreter to return, 
allways behaving in strict mode.
      */
-    public static class Return extends JexlException {   
+    protected static class Return extends JexlException {   
         private final Object result;
-        public Return(JexlNode node, String msg, Object result) {
+        protected Return(JexlNode node, String msg, Object result) {
             super(node, msg);
             this.result = result;
         }
@@ -93,6 +93,15 @@ public class JexlException extends Runti
     }
     
     /**
+     * The class of exceptions used when the interpreter cancels a script 
execution.
+     */
+    protected static class Cancel extends JexlException {
+        protected Cancel(JexlNode node) {
+            super(node, "execution cancelled", null);
+        }
+    }
+    
+    /**
      * Gets information about the cause of this error.
      * <p>
      * The returned string represents the outermost expression in error.

Modified: 
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/Script.java
URL: 
http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/Script.java?rev=1146477&r1=1146476&r2=1146477&view=diff
==============================================================================
--- 
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/Script.java 
(original)
+++ 
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/Script.java 
Wed Jul 13 21:05:13 2011
@@ -16,6 +16,10 @@
  */
 package org.apache.commons.jexl2;
 
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.Callable;
+
 /**
  * <p>A JEXL Script.</p>
  * <p>A script is some valid JEXL syntax to be executed with
@@ -37,6 +41,7 @@ public interface Script {
      *      the last statement.
      */
     Object execute(JexlContext context);
+
     /**
      * Executes the script with the variables contained in the
      * supplied {@link JexlContext} and a set of arguments corresponding to the
@@ -55,4 +60,38 @@ public interface Script {
      */
     String getText();
 
+    /**
+     * Gets this script parameters.
+     * @return the parameters or null
+     */
+    String[] getParameters();
+
+    /**
+     * Gets this script local variables.
+     * @return the local variables or null
+     */
+    String[] getLocalVariables();
+
+    /**
+     * Gets this script variables.
+     * <p>Note that since variables can be in an ant-ish form (ie 
foo.bar.quux), each variable is returned as 
+     * a list of strings where each entry is a fragment of the variable 
({"foo", "bar", "quux"} in the example.</p>
+     * @return the variables or null
+     */
+    Set<List<String>> getVariables();
+
+    /**
+     * Creates a Callable from this script.
+     * @param context the context
+     * @return the callable
+     */
+    Callable<Object> callable(JexlContext context);
+
+    /**
+     * Creates a Callable from this script.
+     * @param context the context
+     * @param args the script arguments
+     * @return the callable
+     */
+    Callable<Object> callable(JexlContext context, Object... args);
 }

Added: 
commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl2/ScriptCallableTest.java
URL: 
http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl2/ScriptCallableTest.java?rev=1146477&view=auto
==============================================================================
--- 
commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl2/ScriptCallableTest.java
 (added)
+++ 
commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl2/ScriptCallableTest.java
 Wed Jul 13 21:05:13 2011
@@ -0,0 +1,119 @@
+/*
+ * 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.jexl2;
+
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.FutureTask;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * Tests around asynchronous script execution and interrupts.
+ */
+public class ScriptCallableTest extends JexlTestCase {
+    //Logger LOGGER = Logger.getLogger(VarTest.class.getName());
+
+    public void testFuture() throws Exception {
+        Script e = JEXL.createScript("while(true);");
+        FutureTask<Object> future = new FutureTask<Object>(e.callable(null));
+
+        ExecutorService executor = Executors.newFixedThreadPool(1);
+        executor.submit(future);
+        try {
+            future.get(100, TimeUnit.MILLISECONDS);
+            fail("should have timed out");
+        } catch (TimeoutException xtimeout) {
+            // ok, ignore
+        }
+        Thread.sleep(100);
+        future.cancel(true);
+
+        assertTrue(future.isCancelled());
+    }
+
+    public void testCallable() throws Exception {
+        Script e = JEXL.createScript("while(true);");
+        Callable<Object> c = e.callable(null);
+
+        ExecutorService executor = Executors.newFixedThreadPool(1);
+        Future<?> future = executor.submit(c);
+        try {
+            future.get(100, TimeUnit.MILLISECONDS);
+            fail("should have timed out");
+        } catch (TimeoutException xtimeout) {
+            // ok, ignore
+        }
+        future.cancel(true);
+        assertTrue(future.isCancelled());
+    }
+
+    public static class TestContext extends MapContext implements 
NamespaceResolver {
+        public Object resolveNamespace(String name) {
+            return name == null ? this : null;
+        }
+
+        public int wait(int s) throws InterruptedException {
+            Thread.sleep(1000 * s);
+            return s;
+        }
+    }
+
+    public void testWait() throws Exception {
+        Script e = JEXL.createScript("wait(1)");
+        Callable<Object> c = e.callable(new TestContext());
+
+        ExecutorService executor = Executors.newFixedThreadPool(1);
+        Future<?> future = executor.submit(c);
+        Object t = future.get(2, TimeUnit.SECONDS);
+        assertEquals(1, t);
+    }
+
+    public void testCancelWait() throws Exception {
+        Script e = JEXL.createScript("wait(10)");
+        Callable<Object> c = e.callable(new TestContext());
+
+        ExecutorService executor = Executors.newFixedThreadPool(1);
+        Future<?> future = executor.submit(c);
+        try {
+            future.get(100, TimeUnit.MILLISECONDS);
+            fail("should have timed out");
+        } catch (TimeoutException xtimeout) {
+            // ok, ignore
+        }
+        future.cancel(true);
+        assertTrue(future.isCancelled());
+    }
+    
+    public void testCancelLoopWait() throws Exception {
+        Script e = JEXL.createScript("while (true) { wait(10) }");
+        Callable<Object> c = e.callable(new TestContext());
+
+        ExecutorService executor = Executors.newFixedThreadPool(1);
+        Future<?> future = executor.submit(c);
+        try {
+            future.get(100, TimeUnit.MILLISECONDS);
+            fail("should have timed out");
+        } catch (TimeoutException xtimeout) {
+            // ok, ignore
+        }
+        future.cancel(true);
+        assertTrue(future.isCancelled());
+    }
+}

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


Reply via email to