Index: ../src/main/org/codehaus/groovy/transform/ConditionalInterruptibleASTTransformation.groovy
===================================================================
--- ../src/main/org/codehaus/groovy/transform/ConditionalInterruptibleASTTransformation.groovy	(revision )
+++ ../src/main/org/codehaus/groovy/transform/ConditionalInterruptibleASTTransformation.groovy	(revision )
@@ -0,0 +1,122 @@
+/*
+ * Copyright 2008-2010 the original author or authors.
+ *
+ * Licensed 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.codehaus.groovy.transform;
+
+
+import groovy.transform.ConditionalInterrupt
+import org.codehaus.groovy.ast.stmt.DoWhileStatement
+import org.codehaus.groovy.ast.stmt.ForStatement
+import org.codehaus.groovy.ast.stmt.WhileStatement
+import org.codehaus.groovy.control.CompilePhase
+import org.codehaus.groovy.ast.*
+import org.codehaus.groovy.ast.expr.*
+
+/**
+ * Allows "interrupt-safe" executions of scripts by adding a custom conditional
+ * check on loops (for, while, do) and first statement of closures. By default, also adds an interrupt check
+ * statement on the beginning of method calls.
+ *
+ * @see groovy.transform.ConditionalInterrupt
+ *
+ * @author Cédric Champeau
+ * @author Hamlet D'Arcy
+ *
+ * @since 1.8.0
+ */
+@GroovyASTTransformation(phase = CompilePhase.CANONICALIZATION)
+public class ConditionalInterruptibleASTTransformation extends AbstractInterruptibleASTTransformation {
+
+    private static final ClassNode MY_TYPE = new ClassNode(ConditionalInterrupt)
+    private static final String CONDITION_METHOD = 'conditionalTransform$condition';
+
+    private ClosureExpression conditionNode
+    private MethodCallExpression conditionCallExpression
+
+    protected ClassNode type() {
+        return MY_TYPE;
+    }
+
+    protected void setupTransform(AnnotationNode node) {
+        checkOnMethodStart = getBooleanAnnotationParameter(node, CHECK_METHOD_START_MEMBER, true);
+        applyToAllClasses = getBooleanAnnotationParameter(node, PROPAGATE_TO_COMPILE_UNIT, false); // setting default to false avoids scoping issues
+        def member = node.getMember("condition")
+        if (!member || !(member instanceof ClosureExpression)) internalError("Expected closure value for annotation parameter. Found " + member + "member")
+        conditionNode = member;
+        conditionCallExpression = new MethodCallExpression(new VariableExpression('this'), CONDITION_METHOD, new ArgumentListExpression())
+    }
+
+    def void visitClass(ClassNode type) {
+        def method = type.addMethod(CONDITION_METHOD, ACC_PROTECTED | ACC_SYNTHETIC, ClassHelper.Boolean_TYPE, Parameter.EMPTY_ARRAY, ClassNode.EMPTY_ARRAY, conditionNode.code);
+        method.synthetic = true
+        super.visitClass(type);
+    }
+
+    protected Expression createCondition() {
+        conditionCallExpression
+    }
+
+    def void visitAnnotations(AnnotatedNode node) {
+        // this transformation does not apply on annotation nodes
+        // visiting could lead to stack overflows
+    }
+
+
+    @Override
+    public void visitClosureExpression(ClosureExpression closureExpr) {
+        if (closureExpr == conditionNode) return // no not visit the closure from the annotation itself
+        def code = closureExpr.code
+        closureExpr.code = wrapBlock(code)
+        super.visitClosureExpression closureExpr
+    }
+
+    /**
+     * Shortcut method which avoids duplicating code for every type of loop.
+     * Actually wraps the loopBlock of different types of loop statements.
+     */
+    private def visitLoop(loopStatement) {
+        def statement = loopStatement.loopBlock
+        loopStatement.loopBlock = wrapBlock(statement)
+    }
+
+    @Override
+    public void visitForLoop(ForStatement forStatement) {
+        visitLoop(forStatement)
+        super.visitForLoop(forStatement)
+    }
+
+    @Override
+    public void visitDoWhileLoop(final DoWhileStatement doWhileStatement) {
+        visitLoop(doWhileStatement)
+        super.visitDoWhileLoop(doWhileStatement)
+    }
+
+    @Override
+    public void visitWhileLoop(final WhileStatement whileStatement) {
+        visitLoop(whileStatement)
+        super.visitWhileLoop(whileStatement)
+    }
+
+    @Override
+    public void visitMethod(MethodNode node) {
+        if (node.name==CONDITION_METHOD && !node.synthetic) return // do not visit the generated method
+        if (checkOnMethodStart && !node.isSynthetic()) {
+            def code = node.code
+            node.code = wrapBlock(code);
+        }
+        if (!node.isSynthetic() && !node.isStatic()) super.visitMethod(node)
+    }
+
+}
Index: ../src/main/groovy/transform/ConditionalInterrupt.groovy
===================================================================
--- ../src/main/groovy/transform/ConditionalInterrupt.groovy	(revision )
+++ ../src/main/groovy/transform/ConditionalInterrupt.groovy	(revision )
@@ -0,0 +1,134 @@
+/*
+ * Copyright 2008-2010 the original author or authors.
+ *
+ * Licensed 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 groovy.transform;
+
+
+import java.lang.annotation.ElementType
+import java.lang.annotation.Retention
+import java.lang.annotation.RetentionPolicy
+import java.lang.annotation.Target
+import org.codehaus.groovy.transform.GroovyASTTransformationClass
+
+/**
+ * Allows "interrupt-safe" executions of scripts by adding Thread.currentThread().isInterrupted()
+ * checks on loops (for, while, do), the first statement of closures, and the first statement of methods.
+ * <br/>
+ * <br/>
+ * This is especially useful when executing foreign scripts that you do not have control over. Inject this
+ * transformation into a script that you need to interrupt.
+ * <br/>
+ * <br/>
+ * Annotating anything in a script will cause for loops, while loops, methods, and closures to make an
+ * isInterruptedCheck and throw a InterruptedException if the check yields true. The annotation by default
+ * will apply to any classes defined in the script as well. Annotated a class will cause (by default) all classes
+ * in the entire file ('Compilation Unit') to be enhanced. You can fine tune what is enhanced using the annotation
+ * parameters. 
+ * <br/>
+ * <br/>
+ * Extensive usage examples can be found in the unit test for this class. A smaller example is presented here.
+ * The following is sample usage of the annotation:
+ * <br/>
+ * <pre>
+ * <code>@groovy.transform.ThreadInterrupt</code>
+ * def scriptMethod() {
+ *     4.times {
+ *         println 'executing script method...'
+ *     }
+ * }
+ *
+ * class MyClass {
+ *
+ *   def myMethod() {
+ *       for (i in (1..10)) {
+ *           println 'executing method...'
+ *       }
+ *   }
+ * }
+ *
+ * scriptMethod()
+ * new MyClass().myMethod()
+ * </pre>
+ * <br/>
+ * Which results in the following code being generated. Notice the checks and exceptions:
+ * <br/>
+ * <pre>
+ * public class script1290627909406 extends groovy.lang.Script {
+ *
+ *     public java.lang.Object scriptMethod() {
+ *         if (java.lang.Thread.currentThread().isInterrupted()) {
+ *             throw new java.lang.InterruptedException('Execution Interrupted')
+ *         }
+ *         4.times({
+ *             if (java.lang.Thread.currentThread().isInterrupted()) {
+ *                 throw new java.lang.InterruptedException('Execution Interrupted')
+ *             }
+ *             this.println('executing script method...')
+ *         })
+ *     }
+ * }
+ * public class MyClass extends java.lang.Object {
+ *
+ *     public java.lang.Object myMethod() {
+ *         if (java.lang.Thread.currentThread().isInterrupted()) {
+ *             throw new java.lang.InterruptedException('Execution Interrupted')
+ *         }
+ *         for (java.lang.Object i : (1..10)) {
+ *             if (java.lang.Thread.currentThread().isInterrupted()) {
+ *                 throw new java.lang.InterruptedException('Execution Interrupted')
+ *             }
+ *             this.println('executing method...')
+ *         }
+ *     }
+ * }
+ *
+ * this.scriptMethod()
+ * new MyClass().myMethod()
+ * <br/>
+ *
+ * @author Cédric Champeau
+ * @author Hamlet D'Arcy
+ *
+ * @since 1.8.0
+ */
+@Retention(RetentionPolicy.SOURCE)
+@Target([ ElementType.METHOD, ElementType.TYPE])
+@GroovyASTTransformationClass(["org.codehaus.groovy.transform.ConditionalInterruptibleASTTransformation"])
+public @interface ConditionalInterrupt {
+    /**
+     * By default, annotating anything in a source file ('Compilation Unit') will trigger this transformation
+     * for all classes and scripts in that file. If you add the Annotation to an import statement, then all
+     * scripts and Classes will be enhanced. If you want to change this behavior then set applyToAllClasses
+     * to false. If you annotate a type then only that type will be augmented, not other types or the surrounding
+     * script. If you annotate a script, then any enclosed types will not be augmented.
+     * @return
+     */
+    boolean applyToAllClasses() default true;
+    /**
+     * By default an isInterrupted check is added to the start of all user-defined methods. To turn this off simply
+     * set this parameter to false.  
+     * @return
+     */
+    boolean checkOnMethodStart() default true;
+    
+
+    /**
+     * Condition should be set as a closure expression. The closure will automatically be converted to a
+     * boolean expression statement.
+     * @return
+     */
+    Class condition();
+}
Index: ../src/main/org/codehaus/groovy/transform/ThreadInterruptibleASTTransformation.groovy
===================================================================
--- ../src/main/org/codehaus/groovy/transform/ThreadInterruptibleASTTransformation.groovy	(revision 21137)
+++ ../src/main/org/codehaus/groovy/transform/ThreadInterruptibleASTTransformation.groovy	(revision )
@@ -16,19 +16,15 @@
 package org.codehaus.groovy.transform;
 
 
-import org.codehaus.groovy.ast.builder.AstBuilder
-import org.codehaus.groovy.ast.expr.ClosureExpression
-import org.codehaus.groovy.ast.expr.ConstantExpression
-import org.codehaus.groovy.control.CompilePhase
-import org.codehaus.groovy.control.SourceUnit
-import org.codehaus.groovy.ast.*
-import org.codehaus.groovy.ast.stmt.*
 import groovy.transform.ThreadInterrupt
-import org.codehaus.groovy.ast.expr.BooleanExpression
-import org.codehaus.groovy.ast.expr.MethodCallExpression
-import org.codehaus.groovy.ast.expr.StaticMethodCallExpression
-import org.codehaus.groovy.ast.expr.ArgumentListExpression
-import org.codehaus.groovy.ast.expr.ConstructorCallExpression
+import org.codehaus.groovy.ast.ClassHelper
+import org.codehaus.groovy.ast.ClassNode
+import org.codehaus.groovy.ast.MethodNode
+import org.codehaus.groovy.ast.stmt.DoWhileStatement
+import org.codehaus.groovy.ast.stmt.ForStatement
+import org.codehaus.groovy.ast.stmt.WhileStatement
+import org.codehaus.groovy.control.CompilePhase
+import org.codehaus.groovy.ast.expr.*
 
 /**
  * Allows "interrupt-safe" executions of scripts by adding Thread.currentThread().isInterrupted()
@@ -36,111 +32,30 @@
  * statement on the beginning of method calls.
  *
  * @see groovy.transform.ThreadInterrupt
- * 
+ *
  * @author Cédric Champeau
  * @author Hamlet D'Arcy
  *
  * @since 1.8.0
  */
 @GroovyASTTransformation(phase = CompilePhase.CANONICALIZATION)
-public class ThreadInterruptibleASTTransformation extends ClassCodeVisitorSupport implements ASTTransformation {
+public class ThreadInterruptibleASTTransformation extends AbstractInterruptibleASTTransformation {
 
-    private static final ClassNode MY_TYPE = new ClassNode(ThreadInterrupt.class)
-    private static final String CHECK_METHOD_START_MEMBER = 'checkOnMethodStart'
-    private static final String PROPAGATE_TO_COMPILE_UNIT = 'applyToAllClasses'
-    private final static def INTERRUPT_STATEMENT = createInterruptStatement()
-    private SourceUnit source
-    private boolean checkOnMethodStart
-    private boolean applyToAllClasses  
+    private static final ClassNode MY_TYPE = new ClassNode(ThreadInterrupt)
 
-    public void visit(ASTNode[] nodes, SourceUnit source) {
-        if (nodes.length != 2 || !(nodes[0] instanceof AnnotationNode) || !(nodes[1] instanceof AnnotatedNode)) {
-            internalError("Expecting [AnnotationNode, AnnotatedClass] but got: " + Arrays.asList(nodes))
+    protected ClassNode type() {
+        return MY_TYPE;
-        }
+    }
 
-        this.source = source
-        AnnotationNode node = nodes[0]
-        AnnotatedNode annotatedNode = nodes[1]
-
-        if (!MY_TYPE.equals(node.getClassNode())) {
-            internalError("Transformation called from wrong annotation: " + node.getClassNode().getName())
-        }
-
-        checkOnMethodStart = getBooleanAnnotationParameter(node, CHECK_METHOD_START_MEMBER, true)
-        applyToAllClasses  = getBooleanAnnotationParameter(node, PROPAGATE_TO_COMPILE_UNIT, true)
-
-        // should be limited to the current SourceUnit or propagated to the whole CompilationUnit
-        if (applyToAllClasses) {
-            // guard every class and method defined in this script
-            source.getAST()?.classes?.each { ClassNode it ->
-                this.visitClass(it)
-            }
-        } else if (annotatedNode instanceof ClassNode) {
-            // only guard this particular class
-            this.visitClass annotatedNode
-        } else {
-            // only guard the script class
-            source.getAST()?.classes?.each { ClassNode it ->
-                if (it.isScript()) {
-                    this.visitClass(it)
-                }
-            }
-        }
-    }
-
-    public static boolean getBooleanAnnotationParameter(AnnotationNode node, String parameterName, boolean defaultValue) {
-        def member = node.getMember(parameterName)
-        if (member) {
-            if (member instanceof ConstantExpression) {
-                try {
-                    return Boolean.valueOf(member.value)
-                } catch (e) {
-                    internalError("Expecting boolean value for ${parameterName} annotation parameter. Found $member")
-                }
-            } else {
-                internalError("Expecting boolean value for ${parameterName} annotation parameter. Found $member")
-            }
-        }
-        return defaultValue
-    }
-
-    private static void internalError(String message) {
-        throw new RuntimeException("Internal error: " + message)
-    }
-
-    /**
-     * @return Returns the interruption check statement.
-     */
-    final static def createInterruptStatement() {
-        new IfStatement(
-                new BooleanExpression(
+    protected Expression createCondition() {
-                        new MethodCallExpression(
-                                new StaticMethodCallExpression(ClassHelper.make(Thread),
+        new MethodCallExpression(
+                new StaticMethodCallExpression(ClassHelper.make(Thread),
-                                        'currentThread',
+                        "currentThread",
-                                        ArgumentListExpression.EMPTY_ARGUMENTS),
+                        ArgumentListExpression.EMPTY_ARGUMENTS),
-                                'isInterrupted', ArgumentListExpression.EMPTY_ARGUMENTS)
-                ),
-                new ThrowStatement(
-                        new ConstructorCallExpression(ClassHelper.make(InterruptedException),
-                        new ArgumentListExpression(new ConstantExpression("Execution Interrupted")))
-                ),
-                new EmptyStatement()
-        )
+                "isInterrupted", ArgumentListExpression.EMPTY_ARGUMENTS)
     }
 
 
-    /**
-     * Takes a statement and wraps it into a block statement which first element is the interruption check statement.
-     * @param statement the statement to be wrapped
-     * @return a {@link BlockStatement block statement}   which first element is for checking interruption, and the
-     * second one the statement to be wrapped.
-     */
-    static private def wrapBlock(statement) {
-        def stmt = new BlockStatement();
-        stmt.addStatement(INTERRUPT_STATEMENT);
-        stmt.addStatement(statement);
-        stmt
-    }
     @Override
     public void visitClosureExpression(ClosureExpression closureExpr) {
         def code = closureExpr.code
@@ -183,8 +98,4 @@
         }
         super.visitMethod(node)
     }
-
-    protected SourceUnit getSourceUnit() {
-        return source;
-    }
+}
-}
Index: ../src/main/org/codehaus/groovy/transform/AbstractInterruptibleASTTransformation.java
===================================================================
--- ../src/main/org/codehaus/groovy/transform/AbstractInterruptibleASTTransformation.java	(revision )
+++ ../src/main/org/codehaus/groovy/transform/AbstractInterruptibleASTTransformation.java	(revision )
@@ -0,0 +1,153 @@
+/*
+ * Copyright 2008-2010 the original author or authors.
+ *
+ * Licensed 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.codehaus.groovy.transform;
+
+import org.codehaus.groovy.GroovyBugError;
+import org.codehaus.groovy.ast.*;
+import org.codehaus.groovy.ast.expr.*;
+import org.codehaus.groovy.ast.stmt.*;
+import org.codehaus.groovy.control.SourceUnit;
+import org.codehaus.groovy.runtime.DefaultGroovyMethods;
+import org.objectweb.asm.Opcodes;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Base class for AST Transformations which will automatically throw an {@link InterruptedException} when
+ * some conditions are met.
+ *
+ * @author Cédric Champeau
+ * @author Hamlet D'Arcy
+ * @since 1.8.0
+ */
+public abstract class AbstractInterruptibleASTTransformation extends ClassCodeVisitorSupport implements ASTTransformation, Opcodes {
+
+    protected static final String CHECK_METHOD_START_MEMBER = "checkOnMethodStart";
+    protected static final String PROPAGATE_TO_COMPILE_UNIT = "applyToAllClasses";
+    protected SourceUnit source;
+    protected boolean checkOnMethodStart;
+    protected boolean applyToAllClasses;
+
+    protected SourceUnit getSourceUnit() {
+        return source;
+    }
+
+    protected abstract ClassNode type();
+
+    /**
+     * Subclasses should implement this method to set the condition of the interruption statement
+     */
+    protected abstract Expression createCondition();
+
+    protected void setupTransform(AnnotationNode node) {
+        checkOnMethodStart = getBooleanAnnotationParameter(node, CHECK_METHOD_START_MEMBER, true);
+        applyToAllClasses = getBooleanAnnotationParameter(node, PROPAGATE_TO_COMPILE_UNIT, true);
+    }
+
+    public void visit(ASTNode[] nodes, SourceUnit source) {
+        if (nodes.length != 2 || !(nodes[0] instanceof AnnotationNode) || !(nodes[1] instanceof AnnotatedNode)) {
+            internalError("Expecting [AnnotationNode, AnnotatedClass] but got: " + Arrays.asList(nodes));
+        }
+
+        this.source = source;
+        AnnotationNode node = (AnnotationNode) nodes[0];
+        AnnotatedNode annotatedNode = (AnnotatedNode) nodes[1];
+
+        if (!type().equals(node.getClassNode())) {
+            internalError("Transformation called from wrong annotation: " + node.getClassNode().getName());
+        }
+
+        setupTransform(node);
+
+        // should be limited to the current SourceUnit or propagated to the whole CompilationUnit
+        final ModuleNode tree = source.getAST();
+        if (applyToAllClasses) {
+            // guard every class and method defined in this script
+            if (tree != null) {
+                final List<ClassNode> classes = tree.getClasses();
+                for (ClassNode classNode : classes) {
+                    visitClass(classNode);
+                }
+            }
+        } else if (annotatedNode instanceof ClassNode) {
+            // only guard this particular class
+            this.visitClass((ClassNode) annotatedNode);
+        } else {
+            // only guard the script class
+            if (tree != null) {
+                final List<ClassNode> classes = tree.getClasses();
+                for (ClassNode classNode : classes) {
+                    if (classNode.isScript()) {
+                        visitClass(classNode);
+                    }
+                }
+            }
+        }
+    }
+
+
+    protected static boolean getBooleanAnnotationParameter(AnnotationNode node, String parameterName, boolean defaultValue) {
+        Expression member = node.getMember(parameterName);
+        if (member != null) {
+            if (member instanceof ConstantExpression) {
+                try {
+                    return DefaultGroovyMethods.asType(((ConstantExpression) member).getValue(), Boolean.class);
+                } catch (Exception e) {
+                    internalError("Expecting boolean value for " + parameterName + " annotation parameter. Found " + member + "member");
+                }
+            } else {
+                internalError("Expecting boolean value for " + parameterName + " annotation parameter. Found " + member + "member");
+            }
+        }
+        return defaultValue;
+    }
+
+    protected static void internalError(String message) {
+        throw new GroovyBugError("Internal error: " + message);
+    }
+
+    /**
+     * @return Returns the interruption check statement.
+     */
+    protected Statement createInterruptStatement() {
+        return new IfStatement(
+                new BooleanExpression(
+                        createCondition()
+                ),
+                new ThrowStatement(
+                        new ConstructorCallExpression(ClassHelper.make(InterruptedException.class),
+                                new ArgumentListExpression(new ConstantExpression("Execution Interrupted")))
+                ),
+                new EmptyStatement()
+        );
+    }
+
+    /**
+     * Takes a statement and wraps it into a block statement which first element is the interruption check statement.
+     *
+     * @param statement the statement to be wrapped
+     * @return a {@link BlockStatement block statement}   which first element is for checking interruption, and the
+     *         second one the statement to be wrapped.
+     */
+    protected Statement wrapBlock(Statement statement) {
+        BlockStatement stmt = new BlockStatement();
+        stmt.addStatement(createInterruptStatement());
+        stmt.addStatement(statement);
+        return stmt;
+    }
+}
