/**
* Copyright (c) 2013, Andre Steingress
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
* following conditions are met:
*
* 1.) Redistributions of source code must retain the above copyright notice, this list of conditions and the following
* disclaimer.
* 2.) Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided with the distribution.
* 3.) Neither the name of Andre Steingress nor the names of its contributors may be used to endorse or
* promote products derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
* USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.gcontracts.generation;
import org.codehaus.groovy.ast.ClassCodeVisitorSupport;
import org.codehaus.groovy.ast.ClassHelper;
import org.codehaus.groovy.ast.ClassNode;
import org.codehaus.groovy.ast.MethodNode;
import org.codehaus.groovy.ast.expr.BooleanExpression;
import org.codehaus.groovy.ast.expr.ClosureExpression;
import org.codehaus.groovy.ast.expr.DeclarationExpression;
import org.codehaus.groovy.ast.expr.VariableExpression;
import org.codehaus.groovy.ast.stmt.*;
import org.codehaus.groovy.control.SourceUnit;
import org.codehaus.groovy.syntax.Token;
import org.codehaus.groovy.syntax.Types;
import java.util.ArrayList;
import java.util.List;
/**
* Central place to create {@link org.codehaus.groovy.ast.stmt.AssertStatement} instances in GContracts. Utilized
* to centralize {@link AssertionError} message generation.
*
* @see org.codehaus.groovy.ast.stmt.AssertStatement
* @see AssertionError
*
* @author ast
*/
public final class AssertStatementCreationUtility {
/**
* Reusable method for creating assert statements for the given <tt>booleanExpression</tt>.
*
* @param booleanExpressions the assertion's {@link org.codehaus.groovy.ast.expr.BooleanExpression} instances
*
* @return a newly created {@link org.codehaus.groovy.ast.stmt.AssertStatement}
*/
public static BlockStatement getAssertionStatemens(final List<BooleanExpression> booleanExpressions) {
List<AssertStatement> assertStatements = new ArrayList<AssertStatement>();
for (BooleanExpression booleanExpression : booleanExpressions) {
assertStatements.add(getAssertionStatement(booleanExpression));
}
final BlockStatement blockStatement = new BlockStatement();
blockStatement.getStatements().addAll(assertStatements);
return blockStatement;
}
/**
* Reusable method for creating assert statements for the given <tt>booleanExpression</tt>.
*
* @param booleanExpression the assertion's {@link org.codehaus.groovy.ast.expr.BooleanExpression}
*
* @return a newly created {@link org.codehaus.groovy.ast.stmt.AssertStatement}
*/
public static AssertStatement getAssertionStatement(final BooleanExpression booleanExpression) {
final AssertStatement assertStatement = new AssertStatement(booleanExpression);
assertStatement.setStatementLabel((String) booleanExpression.getNodeMetaData("statementLabel"));
assertStatement.setSourcePosition(booleanExpression);
return assertStatement;
}
/**
* Gets a list of {@link org.codehaus.groovy.ast.stmt.ReturnStatement} instances from the given {@link MethodNode}.
*
* @param method the {@link org.codehaus.groovy.ast.MethodNode} that holds the given <tt>lastStatement</tt>
* @return a {@link org.codehaus.groovy.ast.stmt.ReturnStatement} or <tt>null</tt>
*/
public static List<ReturnStatement> getReturnStatements(MethodNode method) {
final ReturnStatementVisitor returnStatementVisitor = new ReturnStatementVisitor();
returnStatementVisitor.visitMethod(method);
final List<ReturnStatement> returnStatements = returnStatementVisitor.getReturnStatements();
final BlockStatement blockStatement = (BlockStatement) method.getCode();
if (returnStatements.isEmpty()) {
final int statementCount = blockStatement.getStatements().size();
if (statementCount > 0) {
final Statement lastStatement = blockStatement.getStatements().get(statementCount - 1);
if (lastStatement instanceof ExpressionStatement) {
final ReturnStatement returnStatement = new ReturnStatement((ExpressionStatement) lastStatement);
returnStatement.setSourcePosition(lastStatement);
blockStatement.getStatements().remove(lastStatement);
blockStatement.addStatement(returnStatement);
returnStatements.add(returnStatement);
}
}
}
return returnStatements;
}
/**
* Removes a {@link org.codehaus.groovy.ast.stmt.ReturnStatement} from the given {@link org.codehaus.groovy.ast.stmt.Statement}.
*/
public static void removeReturnStatement(BlockStatement statement, ReturnStatement returnStatement) {
List<Statement> statements = statement.getStatements();
for (int i = statements.size() - 1; i >= 0; i--) {
Statement stmt = statements.get(i);
if (stmt == returnStatement) {
statements.remove(i);
return;
} else if (stmt instanceof BlockStatement) {
removeReturnStatement((BlockStatement) stmt, returnStatement);
return;
}
}
}
public static void injectResultVariableReturnStatementAndAssertionCallStatement(BlockStatement statement, ClassNode returnType, ReturnStatement returnStatement, BlockStatement assertionCallStatement) {
final AddResultReturnStatementVisitor addResultReturnStatementVisitor = new AddResultReturnStatementVisitor(returnStatement, returnType, assertionCallStatement);
addResultReturnStatementVisitor.visitBlockStatement(statement);
}
public static void addAssertionCallStatementToReturnStatement(BlockStatement statement, ReturnStatement returnStatement, Statement assertionCallStatement) {
final AddAssertionCallStatementToReturnStatementVisitor addAssertionCallStatementToReturnStatementVisitor = new AddAssertionCallStatementToReturnStatementVisitor(returnStatement, assertionCallStatement);
addAssertionCallStatementToReturnStatementVisitor.visitBlockStatement(statement);
}
/**
* Collects all {@link ReturnStatement} instances from a given code block.
*/
public static class ReturnStatementVisitor extends ClassCodeVisitorSupport {
private List<ReturnStatement> returnStatements = new ArrayList<ReturnStatement>();
@Override
protected SourceUnit getSourceUnit() {
return null;
}
@Override
public void visitReturnStatement(ReturnStatement statement) {
returnStatements.add(statement);
}
@Override
public void visitClosureExpression(ClosureExpression expression) {
// do nothing to prevent getting return statements from closures
}
public List<ReturnStatement> getReturnStatements() {
return returnStatements;
}
}
/**
* Replaces a given {@link ReturnStatement} with the appropriate assertion call statement and returns a result variable expression.
*/
public static class AddResultReturnStatementVisitor extends ClassCodeVisitorSupport {
@Override
protected SourceUnit getSourceUnit() {
return null;
}
private BlockStatement blockStatement;
private BlockStatement blockStatementCopy;
private final ReturnStatement returnStatement;
private final ClassNode returnType;
private final BlockStatement assertionCallStatement;
public AddResultReturnStatementVisitor(ReturnStatement returnStatement, ClassNode returnType, BlockStatement assertionCallStatement) {
this.returnStatement = returnStatement;
this.returnType = returnType;
this.assertionCallStatement = assertionCallStatement;
}
@Override
public void visitBlockStatement(BlockStatement block) {
blockStatement = block;
blockStatementCopy = new BlockStatement(new ArrayList<Statement>(blockStatement.getStatements()), blockStatement.getVariableScope());
blockStatementCopy.copyNodeMetaData(blockStatement);
blockStatementCopy.setSourcePosition(blockStatement);
for (Statement statement : blockStatementCopy.getStatements()) {
if (statement == returnStatement) {
blockStatement.getStatements().remove(statement);
blockStatement.addStatements(assertionCallStatement.getStatements());
VariableExpression variableExpression = new VariableExpression("result", returnType);
variableExpression.setAccessedVariable(variableExpression);
blockStatement.addStatement(new ReturnStatement(variableExpression));
return; // we found the return statement under target, let's cancel tree traversal
}
}
super.visitBlockStatement(blockStatement);
}
}
/**
* Replaces a given {@link ReturnStatement} with the appropriate assertion call statement and returns a result variable expression.
*/
public static class AddAssertionCallStatementToReturnStatementVisitor extends ClassCodeVisitorSupport {
@Override
protected SourceUnit getSourceUnit() {
return null;
}
private BlockStatement blockStatement;
private BlockStatement blockStatementCopy;
private final ReturnStatement returnStatement;
private final Statement assertionCallStatement;
public AddAssertionCallStatementToReturnStatementVisitor(ReturnStatement returnStatement, Statement assertionCallStatement) {
this.returnStatement = returnStatement;
this.assertionCallStatement = assertionCallStatement;
}
@Override
public void visitBlockStatement(BlockStatement block) {
blockStatement = block;
blockStatementCopy = new BlockStatement(new ArrayList<Statement>(blockStatement.getStatements()), blockStatement.getVariableScope());
blockStatementCopy.copyNodeMetaData(blockStatement);
blockStatementCopy.setSourcePosition(blockStatement);
for (Statement statement : blockStatementCopy.getStatements()) {
if (statement == returnStatement) {
blockStatement.getStatements().remove(statement);
final VariableExpression $_gc_result = new VariableExpression("$_gc_result", ClassHelper.DYNAMIC_TYPE);
$_gc_result.setAccessedVariable($_gc_result);
blockStatement.addStatement(new ExpressionStatement(
new DeclarationExpression($_gc_result, Token.newSymbol(Types.ASSIGN, -1, -1), returnStatement.getExpression())
));
blockStatement.addStatement(assertionCallStatement);
ReturnStatement gcResultReturn = new ReturnStatement($_gc_result);
gcResultReturn.setSourcePosition(returnStatement);
blockStatement.addStatement(gcResultReturn);
return; // we found the return statement under target, let's cancel tree traversal
}
}
super.visitBlockStatement(blockStatement);
}
}
}