/**
* 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.syntax.Token;
import org.codehaus.groovy.syntax.Types;
import org.objectweb.asm.Opcodes;
import java.util.Map;
/**
* <p>Central place where code generation for the <tt>old</tt> closure variable
* takes place.</p>
*
* @author ast
*/
public class OldVariableGenerationUtility {
public static final String OLD_VARIABLES_METHOD = "$_gc_computeOldVariables";
/**
* Creates a synthetic method handling generation of the <tt>old</tt> variable map. If a super class declares
* the same synthetic method it will be called and the results will be merged.
*
* @param classNode which contains postconditions, so an old variable generating method makes sense here.
*/
public static void addOldVariableMethodNode(final ClassNode classNode) {
if (classNode.getDeclaredMethod(OLD_VARIABLES_METHOD, Parameter.EMPTY_ARRAY) != null) return;
final BlockStatement methodBlockStatement = new BlockStatement();
final MapExpression oldVariablesMap = new MapExpression();
// create variable assignments for old variables
for (final FieldNode fieldNode : classNode.getFields()) {
if (fieldNode.getName().startsWith("$")) continue;
final ClassNode fieldType = ClassHelper.getWrapper(fieldNode.getType());
if (fieldType.getName().startsWith("java.lang") || ClassHelper.isPrimitiveType(fieldType) || fieldType.getName().startsWith("java.math") ||
fieldType.getName().startsWith("java.util") ||
fieldType.getName().startsWith("java.sql") ||
fieldType.getName().equals("groovy.lang.GString") ||
fieldType.getName().equals("java.lang.String")) {
MethodNode cloneMethod = fieldType.getMethod("clone", Parameter.EMPTY_ARRAY);
// if a clone classNode is available, the value is cloned
if (cloneMethod != null && fieldType.implementsInterface(ClassHelper.make("java.lang.Cloneable"))) {
VariableExpression oldVariable = new VariableExpression("$old$" + fieldNode.getName(), fieldNode.getType());
oldVariable.setAccessedVariable(oldVariable);
final MethodCallExpression methodCall = new MethodCallExpression(new FieldExpression(fieldNode), "clone", ArgumentListExpression.EMPTY_ARGUMENTS);
// return null if field is null
methodCall.setSafe(true);
ExpressionStatement oldVariableAssignment = new ExpressionStatement(
new DeclarationExpression(oldVariable,
Token.newSymbol(Types.ASSIGN, -1, -1),
methodCall));
methodBlockStatement.addStatement(oldVariableAssignment);
oldVariablesMap.addMapEntryExpression(new MapEntryExpression(new ConstantExpression(oldVariable.getName().substring("$old$".length())), oldVariable));
} else if (ClassHelper.isPrimitiveType(fieldType)
|| ClassHelper.isNumberType(fieldType)
|| fieldType.getName().startsWith("java.math")
|| fieldType.getName().equals("groovy.lang.GString")
|| fieldType.getName().equals("java.lang.String")) {
VariableExpression oldVariable = new VariableExpression("$old$" + fieldNode.getName(), fieldNode.getType());
oldVariable.setAccessedVariable(oldVariable);
ExpressionStatement oldVariableAssignment = new ExpressionStatement(
new DeclarationExpression(oldVariable,
Token.newSymbol(Types.ASSIGN, -1, -1),
new FieldExpression(fieldNode)));
methodBlockStatement.addStatement(oldVariableAssignment);
oldVariablesMap.addMapEntryExpression(new MapEntryExpression(new ConstantExpression(oldVariable.getName().substring("$old$".length())), oldVariable));
}
}
}
VariableExpression oldVariable = new VariableExpression("old", new ClassNode(Map.class));
oldVariable.setAccessedVariable(oldVariable);
ExpressionStatement oldVariabeStatement = new ExpressionStatement(
new DeclarationExpression(oldVariable,
Token.newSymbol(Types.ASSIGN, -1, -1),
oldVariablesMap));
methodBlockStatement.addStatement(oldVariabeStatement);
VariableExpression mergedOldVariables = null;
// let's ask the super class for old variables...
if (classNode.getSuperClass() != null && classNode.getSuperClass().getMethod(OLD_VARIABLES_METHOD, Parameter.EMPTY_ARRAY) != null) {
mergedOldVariables = new VariableExpression("mergedOldVariables", new ClassNode(Map.class));
mergedOldVariables.setAccessedVariable(mergedOldVariables);
ExpressionStatement mergedOldVariablesStatement = new ExpressionStatement(
new DeclarationExpression(mergedOldVariables,
Token.newSymbol(Types.ASSIGN, -1, -1),
new MethodCallExpression(oldVariable, "plus", new ArgumentListExpression(new MethodCallExpression(VariableExpression.SUPER_EXPRESSION, OLD_VARIABLES_METHOD, ArgumentListExpression.EMPTY_ARGUMENTS)))));
methodBlockStatement.addStatement(mergedOldVariablesStatement);
}
methodBlockStatement.addStatement(new ReturnStatement(mergedOldVariables != null ? mergedOldVariables : oldVariable));
final MethodNode preconditionMethodNode = classNode.addMethod(OLD_VARIABLES_METHOD, Opcodes.ACC_PROTECTED, new ClassNode(Map.class), Parameter.EMPTY_ARRAY, ClassNode.EMPTY_ARRAY, methodBlockStatement);
preconditionMethodNode.setSynthetic(true);
}
}