Skip to content

Commit

Permalink
added return expression as a rule action allowing a return from the t…
Browse files Browse the repository at this point in the history
…riggering method at the point of the trigger call
  • Loading branch information
adinn committed Sep 23, 2008
1 parent f36cf74 commit 845db5c
Show file tree
Hide file tree
Showing 14 changed files with 1,976 additions and 1,497 deletions.
21 changes: 13 additions & 8 deletions README
Expand Up @@ -64,14 +64,19 @@ operation appropriate to the test. Normally, control returns to the
trigger method and execution continues by executing the target line.

It is possible for a rule to alter the flow of control. A rule action
can invoke a builtin to 'kill' the current thread, by throwing a
runtime exception. This may not actually kill the thread if the
trigger method code or one of its callers catches a generic exception
but it can be used effectively. Another builtin can be used to signal
other waiting threads and cause them to 'abort'. Finally, a third
builtin allows the JVM to be halted. It is possible to call arbitrary
Java code inside rule actions so the opportunites for messing around
with control flow extend beyond these builtin options.
may employ the return builtin to force a return from the triggering
method at the point where the trigger call was made. If the triggering
method is is not void then the return expression must include an
argument whose type is compatible with the method return type. A rule
action can also invoke a builtin to 'kill' the current thread, by
throwing a runtime exception. This may not actually kill the thread if
the trigger method code or one of its callers catches a generic
exception but it can still be used effectively in many cases. Another
builtin can be used to signal other waiting threads and cause them to
'abort'. Finally, a third builtin allows the JVM to be halted. It is
possible to call arbitrary Java code inside rule actions so the
opportunites for messing around with control flow extend beyond these
builtin options.

The target class may be specified with or without a full package
qualification. If no package is specified then all classes whose
Expand Down
4 changes: 3 additions & 1 deletion dd/grammar/ECAGrammar.g
Expand Up @@ -93,7 +93,9 @@ action_expr_list
| action_expr
;
action_expr : expr
action_expr : RETURN -> ^(RETURN)
| RETURN expr -> ^(RETURN expr)
| expr
;
expr : simple_expr infix_oper expr -> ^(BINOP infix_oper simple_expr expr)
Expand Down
3 changes: 3 additions & 0 deletions dd/grammar/ECAToken.g
Expand Up @@ -76,6 +76,9 @@ TRUE : 'TRUE' | 'true'
FALSE : 'FALSE'|'false'
;

RETURN : 'RETURN'|'return'
;

// various bracket pairs

LPAREN : '('
Expand Down
2 changes: 1 addition & 1 deletion handler.txt
Expand Up @@ -159,7 +159,7 @@ IF recovered
AND
getCountDown(identifier)
DO debug("!!!killing committed thread for " + identifier + "!!!"),
killThread()
return
ENDRULE

#######################################################################
Expand Down
8 changes: 4 additions & 4 deletions src/TestScript.java
Expand Up @@ -81,9 +81,9 @@ private void checkRules(List<String> ruleScripts)
ClassLoader loader = getClass().getClassLoader();

for (String script : ruleScripts) {
String ruleName = "";
try {
String[] lines = script.split("\n");
String ruleName;
String targetClassName;
String targetMethodName;
int targetLine;
Expand Down Expand Up @@ -205,13 +205,13 @@ private void checkRules(List<String> ruleScripts)
System.err.println("TestJar: multiple matching methods for rule " + ruleName);
}
} catch (ParseException e) {
System.err.println("TestScript: parse exception for rule " + script + " : " + e);
System.err.println("TestScript: parse exception for rule " + ruleName + " : " + e);
e.printStackTrace(System.err);
} catch (TypeException e) {
System.err.println("TestScript: type exception for rule " + script + " : " + e);
System.err.println("TestScript: type exception for rule " + ruleName + " : " + e);
e.printStackTrace(System.err);
} catch (CompileException e) {
System.err.println("TestScript: compile exception for rule " + " : " + script + e);
System.err.println("TestScript: compile exception for rule " + " : " + ruleName + e);
e.printStackTrace(System.err);
}
}
Expand Down
15 changes: 15 additions & 0 deletions src/org/jboss/jbossts/orchestration/agent/RuleAdapter.java
Expand Up @@ -120,6 +120,21 @@ public void visitLineNumber(final int line, final Label start) {
public void visitEnd()
{
Type exceptionType = Type.getType(TypeHelper.externalizeType("org.jboss.jbossts.orchestration.rule.exception.ExecuteException"));
Type earlyReturnExceptionType = Type.getType(TypeHelper.externalizeType("org.jboss.jbossts.orchestration.rule.exception.EarlyReturnException"));
Type returnType = Type.getReturnType(descriptor);
// add exception handling code subclass first
super.catchException(startLabel, endLabel, earlyReturnExceptionType);
if (returnType == Type.VOID_TYPE) {
// drop exception and just return
super.pop();
super.visitInsn(Opcodes.RETURN);
} else {
// fetch value from exception, unbox if needed and return value
Method getReturnValueMethod = Method.getMethod("Object getReturnValue()");
super.invokeVirtual(earlyReturnExceptionType, getReturnValueMethod);
super.unbox(returnType);
super.returnValue();
}
super.catchException(startLabel, endLabel, exceptionType);
super.throwException(exceptionType, rule.getName() + " execution exception ");
super.visitEnd();
Expand Down
15 changes: 12 additions & 3 deletions src/org/jboss/jbossts/orchestration/rule/Rule.java
Expand Up @@ -368,20 +368,27 @@ private void installParameters(boolean isStatic, String className, String descri
Binding binding = new Binding("0", type);
parameterBindings.add(binding);
}
List<String> parameterTypenames = Type.parseDescriptor(descriptor, false);
List<String> parameterTypenames = Type.parseDescriptor(descriptor, true);
int paramIdx = 1;
int last = parameterTypenames.size();
if (parameterTypenames != null) {
for (String typeName : parameterTypenames) {
String[] typeAndArrayBounds = typeName.split("\\[");
Type baseType = typeGroup.create(typeAndArrayBounds[0]);
Type paramType = baseType;
Binding binding;
if (baseType.isUndefined()) {
throw new TypeException("Rule.installParameters : Rule " + name + " unable to load class " + baseType);
}
for (int i = 1; i < typeAndArrayBounds.length ; i++) {
paramType = typeGroup.createArray(baseType);
}
Binding binding = new Binding(Integer.toString(paramIdx++), paramType);
if (paramIdx == last) {
// we also add a special binding to allow us to identify the return type
binding = new Binding("$!", paramType);
} else {
binding = new Binding(Integer.toString(paramIdx++), paramType);
}
parameterBindings.add(binding);
}
}
Expand Down Expand Up @@ -801,12 +808,14 @@ public void execute(Bindings bindings, Object recipient, Object[] args)
Type type = binding.getType();
if (binding.isHelper()) {
bindingMap.put(name, this);
bindingTypeMap.put(name, type);
} else if (binding.isRecipient()) {
bindingMap.put(name, recipient);
bindingTypeMap.put(name, type);
} else if (binding.isParam()) {
bindingMap.put(name, args[binding.getIndex() - 1]);
bindingTypeMap.put(name, type);
}
bindingTypeMap.put(name, type);
}

// now do the actual execution
Expand Down
11 changes: 9 additions & 2 deletions src/org/jboss/jbossts/orchestration/rule/binding/Binding.java
Expand Up @@ -32,8 +32,10 @@ public Binding(String name, Type type, Expression value)
index = -1;
} else if (name.matches("[0-9].*")) {
index = Integer.valueOf(name);
} else {
} else if (name.equals("$!")) {
index = -2;
} else {
index = -3;
}
}

Expand Down Expand Up @@ -97,9 +99,14 @@ public boolean isParam()
return index > 0;
}

public boolean isReturn()
{
return index == -2;
}

public boolean isVar()
{
return index < -1;
return index < -2;
}

public int getIndex()
Expand Down
@@ -0,0 +1,37 @@
package org.jboss.jbossts.orchestration.rule.exception;

/**
* Specialization of ExecuteException which is used to cause a trigger method to return
* early the trigger point, possibly supplying an object to be returned. This is used
* to implement the RETURN action
*
*/
public class EarlyReturnException extends ExecuteException
{
public EarlyReturnException(String message) {
super(message);
this.returnValue = null;
}

public EarlyReturnException(String message, Throwable th) {
super(message, th);
this.returnValue = null;
}

public EarlyReturnException(String message, Object returnValue) {
super(message);
this.returnValue = returnValue;
}

public EarlyReturnException(String message, Throwable th, Object returnValue) {
super(message, th);
this.returnValue = returnValue;
}

public Object getReturnValue()
{
return returnValue;
}

private Object returnValue;
}
Expand Up @@ -37,6 +37,8 @@ public static Expression createExpression(Bindings bindings, CommonTree exprTree
// (METH SYMBOL expr_list)
// (NUMBER)
// (STRING)
// (RETURN)
// (RETURN expr)
// expr

Token token = exprTree.getToken();
Expand Down Expand Up @@ -161,6 +163,18 @@ public static Expression createExpression(Bindings bindings, CommonTree exprTree
expr = new StringLiteral(token);
}
break;
case RETURN:
{
Expression returnValue;
if (exprTree.getChildCount() > 0) {
CommonTree child0 = (CommonTree) exprTree.getChild(0);
returnValue = createExpression(bindings,child0);
} else {
returnValue = null;
}
expr = new ReturnExpression(token, returnValue);
}
break;
case UNOP:
{
expr = createUnaryExpression(bindings, exprTree, type);
Expand Down
@@ -0,0 +1,111 @@
package org.jboss.jbossts.orchestration.rule.expression;

import org.jboss.jbossts.orchestration.rule.binding.Bindings;
import org.jboss.jbossts.orchestration.rule.binding.Binding;
import org.jboss.jbossts.orchestration.rule.type.Type;
import org.jboss.jbossts.orchestration.rule.type.TypeGroup;
import org.jboss.jbossts.orchestration.rule.exception.TypeException;
import org.jboss.jbossts.orchestration.rule.exception.ExecuteException;
import org.jboss.jbossts.orchestration.rule.exception.EarlyReturnException;
import org.jboss.jbossts.orchestration.rule.Rule;
import org.antlr.runtime.Token;

import java.io.StringWriter;

/**
* A return expression which is used in a rule action to cause a return from the rule trigger
* method, supplying a return value where appropriate.
*/

public class ReturnExpression extends Expression
{
private Expression returnValue;

public ReturnExpression(Token token, Expression returnValue)
{
// the trigger method may return any old tyep but the return expression can only occur
// at the top level in a rule action seuqence so it is actually a VOID expression

super(Type.VOID, token);

this.returnValue = returnValue;
}
/**
* verify that variables mentioned in this expression are actually available in the supplied
* bindings list and infer/validate the type of this expression or its subexpressions
* where possible
*
* @param bindings the set of bindings in place at the point of evaluation of this expression
* @return true if all variables in this expression are bound and no type mismatches have
* been detected during inference/validation.
*/
public boolean bind(Bindings bindings) {
if (returnValue != null) {
// ensure the return value expression has all its bindings
return returnValue.bind(bindings);
}
return true;
}

/**
* ensure that all type references in the expression and its component expressions
* can be resolved, that the type of the expression is well-defined and that it is
* compatible with the type expected in the context in which it occurs.
*
* @param bindings the bound variable in scope at the point where the expression is
* to be evaluate
* @param typegroup the set of types employed by the rule
* @param expected the type expected for the expression in the contxt in which it occurs. this
* may be void but shoudl not be undefined at the point where type checking is performed.
* @return
* @throws org.jboss.jbossts.orchestration.rule.exception.TypeException
*
*/
public Type typeCheck(Bindings bindings, TypeGroup typegroup, Type expected) throws TypeException {
// we need to check the returnValue expression against the type of the trigger method
Binding returnBinding = bindings.lookup("$!");
Type returnBindingType = (returnBinding != null ? returnBinding.getType() : Type.VOID);
if (returnValue == null && !returnBindingType.isVoid()) {
throw new TypeException("ReturnExpression.typeCheck : return expression must supply argument when triggered from method with return type " + returnBindingType.getName() + getPos());
} else if (returnValue != null) {
if (returnBindingType.isVoid()) {
throw new TypeException("ReturnExpression.typeCheck : return expression must not supply argument when triggered from void method" + getPos());
}
returnValue.typeCheck(bindings, typegroup, returnBindingType);
}
return type;
}

/**
* evaluate the expression by interpreting the expression tree
*
* @param helper an execution context associated with the rule whcih contains a map of
* current bindings for rule variables and another map of their declared types both of which
* are indexed by varoable name. This includes entries for the helper (name "-1"), the
* recipient if the trigger method is not static (name "0") and the trigger method arguments
* (names "1", ...)
* @return the result of evaluation as an Object
* @throws org.jboss.jbossts.orchestration.rule.exception.ExecuteException
*
*/
public Object interpret(Rule.BasicHelper helper) throws ExecuteException
{
// time to take an early bath -- the code compield into the trigger method should
// catch this and return as appropriate
if (returnValue != null) {
Object value = returnValue.interpret(helper);
throw new EarlyReturnException("return from " + helper.getName(), value);
} else {
throw new EarlyReturnException("return from " + helper.getName());
}
}

public void writeTo(StringWriter stringWriter) {
if (returnValue != null) {
stringWriter.write("RETURN ");
returnValue.writeTo(stringWriter);
} else {
stringWriter.write("RETURN");
}
}
}

0 comments on commit 845db5c

Please sign in to comment.