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