
org.gcontracts.generation.AssertStatementCreationUtility Maven / Gradle / Ivy
/**
* Copyright (c) 2010, [email protected]
* 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.GroovyBugError;
import org.codehaus.groovy.ast.*;
import org.codehaus.groovy.ast.expr.*;
import org.codehaus.groovy.ast.stmt.*;
import org.codehaus.groovy.syntax.Token;
import org.codehaus.groovy.syntax.Types;
import org.codehaus.groovy.transform.powerassert.AssertionRewriter;
import org.gcontracts.annotations.Ensures;
import org.gcontracts.annotations.Requires;
import org.gcontracts.util.AnnotationUtils;
import org.objectweb.asm.Opcodes;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
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 [email protected]
*/
public final class AssertStatementCreationUtility {
/**
* Reusable method for creating assert statements for the given invariantField.
*
* @param classNode the current {@link org.codehaus.groovy.ast.ClassNode}
* @param closureExpression the assertion's {@link org.codehaus.groovy.ast.expr.ClosureExpression}
*
* @return a newly created {@link org.codehaus.groovy.ast.stmt.AssertStatement}
*/
public static AssertStatement getInvariantAssertionStatement(final ClassNode classNode, final ClosureExpression closureExpression) {
final Expression expression = getFirstExpression(closureExpression);
if (expression == null) throw new GroovyBugError("Assertion closure does not contain assertion expression!");
final AssertStatement assertStatement = new AssertStatement(new BooleanExpression(expression), new ConstantExpression("[invariant] in class <" + classNode.getName() + "> violated"));
assertStatement.setLineNumber(classNode.getLineNumber());
return assertStatement;
}
/**
* Reusable method for creating assert statements for the given closureExpression, injected in the
* given method and with optional closure parameters.
*
* @param assertionType the name of the constraint, used for assertion messages
* @param method the current {@link org.codehaus.groovy.ast.MethodNode}
* @param closureExpression the assertion's {@link org.codehaus.groovy.ast.expr.ClosureExpression}
*
* @return a new {@link org.codehaus.groovy.ast.stmt.BlockStatement} which holds the assertion
*/
public static AssertStatement getAssertionStatement(final String assertionType, MethodNode method, ClosureExpression closureExpression) {
final Expression expression = getFirstExpression(closureExpression);
if (expression == null) throw new GroovyBugError("Assertion closure does not contain assertion expression!");
final AssertStatement assertStatement = new AssertStatement(new BooleanExpression(expression), new ConstantExpression("[" + assertionType + "] in method <" + method.getName() + "(" + getMethodParameterString(method) + ")> violated"));
assertStatement.setLineNumber(closureExpression.getLineNumber());
return assertStatement;
}
/**
* Create a backup {@link org.codehaus.groovy.ast.MethodNode} with the given assertStatement. This method will be used
* in descendants to call inherited assertions.
*
* @param assertionType the assertion type (precondition or postcondition)
* @param method the current {@link org.codehaus.groovy.ast.MethodNode}
* @param assertStatement the {@link org.codehaus.groovy.ast.stmt.AssertStatement} for which the backup should be created
* @param withOldVariable indicates whether the old variable should be added as parameter
* @param withResultVariable indicates whether the result variable should be added as parameter
*/
public static void addAssertionMethodNode(final String assertionType, final MethodNode method, final AssertStatement assertStatement, boolean withOldVariable, boolean withResultVariable) {
// creates a new closure with all method parameters as closure parameters -> this is needed in descendants
// e.g. when renaming of method parameter happens during redefinition of a method ...
final BlockStatement methodBlockStatement = new BlockStatement();
// copy the assert statement to provide a new message expression
final BooleanExpression booleanExpression = new BooleanExpression(assertStatement.getBooleanExpression().getExpression());
final AssertStatement newAssertStatement = new AssertStatement(booleanExpression);
newAssertStatement.setLineNumber(assertStatement.getLineNumber());
newAssertStatement.setColumnNumber(assertStatement.getColumnNumber());
newAssertStatement.setLastColumnNumber(assertStatement.getLastColumnNumber());
newAssertStatement.setLastLineNumber(assertStatement.getLastLineNumber());
newAssertStatement.setMessageExpression(new ConstantExpression(((ConstantExpression) assertStatement.getMessageExpression()).getText().replaceFirst(assertionType, "inherited " + assertionType)));
// add return value "true" so valid assertions in sub assertion statements get through
methodBlockStatement.addStatement(newAssertStatement);
methodBlockStatement.addStatement(new ReturnStatement(ConstantExpression.TRUE));
final ArrayList parameters = new ArrayList();
parameters.addAll(Arrays.asList(method.getParameters()));
if (withOldVariable) parameters.add(new Parameter(ClassHelper.OBJECT_TYPE, "old"));
if (withResultVariable) parameters.add(new Parameter(method.getReturnType(), "result"));
final ClassNode declaringClass = method.getDeclaringClass();
final Parameter[] parameterArray = parameters.toArray(new Parameter[parameters.size()]);
final String assertionMethodName = getAssertionMethodName(assertionType, method);
if (method.getDeclaringClass().getSuperClass() != null && method.getDeclaringClass().getSuperClass().hasDeclaredMethod(assertionMethodName, parameterArray)) {
final MethodCallExpression methodCallExpression = "precondition".equals(assertionType) ? getMethodCallExpressionToSuperClassPrecondition(method, newAssertStatement.getLineNumber()) : getMethodCallExpressionToSuperClassPostcondition(method, newAssertStatement.getLineNumber(), withOldVariable, withResultVariable);
if (methodCallExpression != null) {
addToAssertStatement(newAssertStatement, methodCallExpression, "precondition".equals(assertionType) ? Token.newSymbol(Types.LOGICAL_OR, -1, -1) : Token.newSymbol(Types.LOGICAL_AND, -1, -1));
}
}
final MethodNode preconditionMethodNode = declaringClass.addMethod(assertionMethodName, Opcodes.ACC_PROTECTED, ClassHelper.Boolean_TYPE, parameterArray, ClassNode.EMPTY_ARRAY, methodBlockStatement);
preconditionMethodNode.setSynthetic(true);
}
/**
* Looks up the next precondition in the class hierarchy of the given class and generates a method call on the previously
* generated closure in the declaration expression.
*
* @param methodNode the current {@link org.codehaus.groovy.ast.MethodNode}, the lookup mechanism starts at the superclass of the declaring class node
* @param lineNumber the line number of the current assertion
*
* @return a {@link org.codehaus.groovy.ast.expr.MethodCallExpression} to the inherited precondition
*/
public static MethodCallExpression getMethodCallExpressionToSuperClassPrecondition(final MethodNode methodNode, final int lineNumber) {
final MethodNode nextMethodNode = AnnotationUtils.getMethodNodeInHierarchyWithAnnotation(methodNode, Requires.class);
if (nextMethodNode == null) return null;
final List methodParameters = new ArrayList();
for (final Parameter param : methodNode.getParameters()) {
methodParameters.add(new VariableExpression(param));
}
final MethodCallExpression methodCallExpression = new MethodCallExpression(VariableExpression.SUPER_EXPRESSION, getAssertionMethodName("precondition", nextMethodNode), new ArgumentListExpression(methodParameters));
methodCallExpression.setLineNumber(lineNumber);
methodCallExpression.setSynthetic(true);
return methodCallExpression;
}
/**
* Looks up the next postcondition in the class hierarchy of the given class and generates a method call on the previously
* generated closure in the declaration expression.
*
* @param methodNode the current {@link org.codehaus.groovy.ast.MethodNode}, the lookup mechanism starts at the superclass of the declaring class node
* @param lineNumber the lineNumber of the current assertion
* @param withOldVariable indicates whether the call needs an old parameter
* @param withResultVariable indicates whether the call needs a result parameter
*
* @return a {@link org.codehaus.groovy.ast.expr.MethodCallExpression} to the inherited postcondition
*/
public static MethodCallExpression getMethodCallExpressionToSuperClassPostcondition(final MethodNode methodNode, final int lineNumber, boolean withOldVariable, boolean withResultVariable) {
final MethodNode nextMethodNode = AnnotationUtils.getMethodNodeInHierarchyWithAnnotation(methodNode, Ensures.class);
if (nextMethodNode == null) return null;
final List methodParameters = new ArrayList();
for (final Parameter param : methodNode.getParameters()) {
methodParameters.add(new VariableExpression(param));
}
if (withOldVariable) methodParameters.add(new VariableExpression("old"));
if (withResultVariable) methodParameters.add(new VariableExpression("result"));
final MethodCallExpression methodCallExpression = new MethodCallExpression(VariableExpression.SUPER_EXPRESSION, getAssertionMethodName("postcondition", nextMethodNode), new ArgumentListExpression(methodParameters));
methodCallExpression.setLineNumber(lineNumber);
methodCallExpression.setSynthetic(true);
return methodCallExpression;
}
/**
* Helper method to add an {@link org.codehaus.groovy.ast.expr.Expression} to the given {@link org.codehaus.groovy.ast.stmt.AssertStatement}. In fact,
* a {@link org.codehaus.groovy.ast.expr.BinaryExpression} will be added where the two expressions are concatenated using the given booleanOperator.
*
* @param assertStatement the {@link org.codehaus.groovy.ast.stmt.AssertStatement} to be modified
* @param expressionToAdd the {@link org.codehaus.groovy.ast.expr.Expression} to be added to the assert statement's expression
* @param booleanOperator the {@link org.codehaus.groovy.syntax.Token} to be used for concatenation
*/
public static void addToAssertStatement(final AssertStatement assertStatement, final Expression expressionToAdd, final Token booleanOperator) {
final BooleanExpression booleanExpressionLeft = assertStatement.getBooleanExpression();
final BinaryExpression binaryExpression = new BinaryExpression(
booleanExpressionLeft.getExpression(),
booleanOperator,
expressionToAdd
);
final BooleanExpression newBooleanExpression = new BooleanExpression(binaryExpression);
assertStatement.setBooleanExpression(newBooleanExpression);
}
/**
* Creates the assertion method node name.
*
* @param assertionType the assertion type (precondition or postcondition)
* @param method the {@link org.codehaus.groovy.ast.MethodNode} to create the assertion method name
* @return the newly created assertion method name
*/
private static String getAssertionMethodName(final String assertionType, final MethodNode method) {
return assertionType + "_" + method.getReturnType().getName().replaceAll("\\.", "_") + "_" + (method instanceof ConstructorNode ? "contructor" : method.getName());
}
/**
* Creates a representative {@link String} of the given {@link org.codehaus.groovy.ast.MethodNode}.
*
* @param method the {@link org.codehaus.groovy.ast.MethodNode} to create the representation
* @return a {@link String} representation of the given method
*/
private static String getMethodParameterString(MethodNode method) {
final StringBuilder builder = new StringBuilder();
for (Parameter parameter : method.getParameters()) {
if (builder.length() > 0) {
builder.append(", ");
}
builder.append(parameter.getName()).append(":").append(parameter.getType().getName());
}
return builder.toString();
}
/**
* Returns the first {@link Expression} in the given {@link org.codehaus.groovy.ast.expr.ClosureExpression}.
*
* @param closureExpression the assertion's {@link org.codehaus.groovy.ast.expr.ClosureExpression}
* @return the first {@link org.codehaus.groovy.ast.expr.Expression} found in the given {@link org.codehaus.groovy.ast.expr.ClosureExpression}
*/
private static Expression getFirstExpression(ClosureExpression closureExpression) {
final BlockStatement closureBlockStatement = (BlockStatement) closureExpression.getCode();
final List statementList = closureBlockStatement.getStatements();
for (Statement stmt : statementList) {
if (stmt instanceof ExpressionStatement) {
return ((ExpressionStatement) stmt).getExpression();
}
}
return null;
}
/**
* Gets a {@link org.codehaus.groovy.ast.stmt.ReturnStatement} from the given {@link org.codehaus.groovy.ast.stmt.Statement}.
*
* @param lastStatement the last {@link org.codehaus.groovy.ast.stmt.Statement} of some method code block
* @return a {@link org.codehaus.groovy.ast.stmt.ReturnStatement} or null
*/
public static ReturnStatement getReturnStatement(ClassNode declaringClass, MethodNode method, Statement lastStatement) {
if (lastStatement instanceof ReturnStatement) {
return (ReturnStatement) lastStatement;
} else if (lastStatement instanceof BlockStatement) {
BlockStatement blockStatement = (BlockStatement) lastStatement;
List statements = blockStatement.getStatements();
return statements.size() > 0 ? getReturnStatement(declaringClass, method, statements.get(statements.size() - 1)) : null;
} else {
if (!(lastStatement instanceof ExpressionStatement)) throw new GroovyBugError("Last statement in " + declaringClass.getName() + "." + method.getTypeDescriptor() + " not of type ExpressionStatement: " + lastStatement);
// the last statement in a Groovy method could also be an expression which result is treated as return value
ExpressionStatement expressionStatement = (ExpressionStatement) lastStatement;
return new ReturnStatement(expressionStatement);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy