/**
* 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.*;
import org.codehaus.groovy.ast.expr.*;
import org.codehaus.groovy.ast.stmt.BlockStatement;
import org.codehaus.groovy.ast.stmt.ExpressionStatement;
import org.codehaus.groovy.ast.stmt.ReturnStatement;
import org.codehaus.groovy.ast.stmt.Statement;
import org.codehaus.groovy.control.io.ReaderSource;
import org.codehaus.groovy.syntax.Token;
import org.codehaus.groovy.syntax.Types;
import org.gcontracts.annotations.meta.ClassInvariant;
import org.gcontracts.ast.visitor.BaseVisitor;
import org.gcontracts.util.AnnotationUtils;
import org.objectweb.asm.Opcodes;
import java.lang.annotation.Annotation;
import java.util.List;
/**
* <p>
* Code generator for class invariants.
* </p>
*
* @author ast
*/
public class ClassInvariantGenerator extends BaseGenerator {
public ClassInvariantGenerator(final ReaderSource source) {
super(source);
}
/**
* Reads the {@link org.gcontracts.annotations.Invariant} boolean expression and generates a synthetic
* method holding this class invariant. This is used for heir calls to find out about inherited class
* invariants.
*
* @param type the current {@link org.codehaus.groovy.ast.ClassNode}
* @param classInvariant the {@link org.gcontracts.domain.ClassInvariant} the assertion statement should be generated from
*/
public void generateInvariantAssertionStatement(final ClassNode type, final org.gcontracts.domain.ClassInvariant classInvariant) {
BooleanExpression classInvariantExpression = addCallsToSuperAnnotationClosure(type, ClassInvariant.class, classInvariant.booleanExpression());
final BlockStatement blockStatement = new BlockStatement();
// add a local protected method with the invariant closure - this is needed for invariant checks in inheritance lines
MethodNode methodNode = type.addMethod(getInvariantMethodName(type), Opcodes.ACC_PROTECTED | Opcodes.ACC_SYNTHETIC, ClassHelper.VOID_TYPE, Parameter.EMPTY_ARRAY, ClassNode.EMPTY_ARRAY, blockStatement);
methodNode.setSynthetic(true);
blockStatement.addStatements(wrapAssertionBooleanExpression(type, methodNode, classInvariantExpression, "invariant").getStatements());
}
private BooleanExpression addCallsToSuperAnnotationClosure(final ClassNode type, final Class<? extends Annotation> annotationType, BooleanExpression booleanExpression) {
final List<AnnotationNode> nextContractElementAnnotations = AnnotationUtils.getAnnotationNodeInHierarchyWithMetaAnnotation(type.getSuperClass(), ClassHelper.makeWithoutCaching(annotationType));
if (nextContractElementAnnotations.isEmpty()) return booleanExpression;
for (AnnotationNode nextContractElementAnnotation : nextContractElementAnnotations) {
ClassExpression classExpression = (ClassExpression) nextContractElementAnnotation.getMember(BaseVisitor.CLOSURE_ATTRIBUTE_NAME);
if (classExpression == null) continue;
MethodCallExpression doCall = new MethodCallExpression(
new ConstructorCallExpression(classExpression.getType(), new ArgumentListExpression(VariableExpression.THIS_EXPRESSION, VariableExpression.THIS_EXPRESSION)),
"doCall",
ArgumentListExpression.EMPTY_ARGUMENTS
);
doCall.setMethodTarget(classExpression.getType().getMethods("doCall").get(0));
final BooleanExpression rightExpression = new BooleanExpression(doCall);
booleanExpression.setSourcePosition(nextContractElementAnnotation);
booleanExpression = new BooleanExpression(
new BinaryExpression(
booleanExpression,
Token.newSymbol(Types.LOGICAL_AND, -1, -1),
rightExpression)
);
}
return booleanExpression;
}
/**
* Adds the current class-invariant to the given <tt>method</tt>.
*
* @param type the {@link org.codehaus.groovy.ast.ClassNode} which declared the given {@link org.codehaus.groovy.ast.MethodNode}
* @param method the current {@link org.codehaus.groovy.ast.MethodNode}
*/
public void addInvariantAssertionStatement(final ClassNode type, final MethodNode method) {
final String invariantMethodName = getInvariantMethodName(type);
final MethodNode invariantMethod = type.getDeclaredMethod(invariantMethodName, Parameter.EMPTY_ARRAY);
if (invariantMethod == null) return;
Statement invariantMethodCall = new ExpressionStatement(
new MethodCallExpression(
VariableExpression.THIS_EXPRESSION,
invariantMethod.getName(),
ArgumentListExpression.EMPTY_ARGUMENTS
)
);
final Statement statement = method.getCode();
if (statement instanceof BlockStatement && method.getReturnType() != ClassHelper.VOID_TYPE && !(method instanceof ConstructorNode)) {
final BlockStatement blockStatement = (BlockStatement) statement;
final List<ReturnStatement> returnStatements = AssertStatementCreationUtility.getReturnStatements(method);
for (ReturnStatement returnStatement : returnStatements) {
AssertStatementCreationUtility.addAssertionCallStatementToReturnStatement (blockStatement, returnStatement, invariantMethodCall);
}
if (returnStatements.isEmpty()) blockStatement.addStatement(invariantMethodCall);
} else if (statement instanceof BlockStatement) {
final BlockStatement blockStatement = (BlockStatement) statement;
blockStatement.addStatement(invariantMethodCall);
} else {
final BlockStatement assertionBlock = new BlockStatement();
assertionBlock.addStatement(statement);
assertionBlock.addStatement(invariantMethodCall);
method.setCode(assertionBlock);
}
}
}