/* * 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(); /** * Subclasses should implement this method to provide good error resolution. */ protected abstract String getErrorMessage(); 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(getErrorMessage()))) ), 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 final Statement wrapBlock(Statement statement) { BlockStatement stmt = new BlockStatement(); stmt.addStatement(createInterruptStatement()); stmt.addStatement(statement); return stmt; } @Override public final void visitForLoop(ForStatement forStatement) { visitLoop(forStatement); super.visitForLoop(forStatement); } /** * Shortcut method which avoids duplicating code for every type of loop. * Actually wraps the loopBlock of different types of loop statements. */ private void visitLoop(LoopingStatement loopStatement) { Statement statement = loopStatement.getLoopBlock(); loopStatement.setLoopBlock(wrapBlock(statement)); } @Override public final void visitDoWhileLoop(DoWhileStatement doWhileStatement) { visitLoop(doWhileStatement); super.visitDoWhileLoop(doWhileStatement); } @Override public final void visitWhileLoop(WhileStatement whileStatement) { visitLoop(whileStatement); super.visitWhileLoop(whileStatement); } }