content: /**
   * 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);
          }
      }
  }
  