Package me.pixodro.j2cpp.core.rewrite.changegenerator

Source Code of me.pixodro.j2cpp.core.rewrite.changegenerator.ChangeGenerator

/*******************************************************************************
* Copyright (c) 2008, 2012 Institute for Software, HSR Hochschule fuer Technik
* Rapperswil, University of applied sciences and others
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Institute for Software - initial API and implementation
* Markus Schorn (Wind River Systems)
* Sergey Prigogin (Google)
*******************************************************************************/
package me.pixodro.j2cpp.core.rewrite.changegenerator;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

import me.pixodro.j2cpp.core.rewrite.ASTWriter;
import me.pixodro.j2cpp.core.rewrite.ContainerNode;
import me.pixodro.j2cpp.core.rewrite.ProblemRuntimeException;

import org.eclipse.cdt.core.CCorePlugin;
import org.eclipse.cdt.core.ToolFactory;
import org.eclipse.cdt.core.dom.ast.ASTVisitor;
import org.eclipse.cdt.core.dom.ast.IASTArrayModifier;
import org.eclipse.cdt.core.dom.ast.IASTComment;
import org.eclipse.cdt.core.dom.ast.IASTCompositeTypeSpecifier;
import org.eclipse.cdt.core.dom.ast.IASTCompoundStatement;
import org.eclipse.cdt.core.dom.ast.IASTDeclSpecifier;
import org.eclipse.cdt.core.dom.ast.IASTDeclaration;
import org.eclipse.cdt.core.dom.ast.IASTDeclarator;
import org.eclipse.cdt.core.dom.ast.IASTExpression;
import org.eclipse.cdt.core.dom.ast.IASTExpressionList;
import org.eclipse.cdt.core.dom.ast.IASTFileLocation;
import org.eclipse.cdt.core.dom.ast.IASTInitializer;
import org.eclipse.cdt.core.dom.ast.IASTName;
import org.eclipse.cdt.core.dom.ast.IASTNode;
import org.eclipse.cdt.core.dom.ast.IASTParameterDeclaration;
import org.eclipse.cdt.core.dom.ast.IASTPointerOperator;
import org.eclipse.cdt.core.dom.ast.IASTPreprocessorStatement;
import org.eclipse.cdt.core.dom.ast.IASTStandardFunctionDeclarator;
import org.eclipse.cdt.core.dom.ast.IASTStatement;
import org.eclipse.cdt.core.dom.ast.IASTTranslationUnit;
import org.eclipse.cdt.core.dom.ast.IASTTypeId;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTFunctionDeclarator;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTFunctionDefinition;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTNamespaceDefinition;
import org.eclipse.cdt.core.formatter.CodeFormatter;
import org.eclipse.cdt.core.formatter.DefaultCodeFormatterConstants;
import org.eclipse.cdt.core.model.ICProject;
import org.eclipse.cdt.core.model.ITranslationUnit;
import org.eclipse.cdt.internal.core.dom.rewrite.ASTModification;
import org.eclipse.cdt.internal.core.dom.rewrite.ASTModification.ModificationKind;
import org.eclipse.cdt.internal.core.dom.rewrite.ASTModificationMap;
import org.eclipse.cdt.internal.core.dom.rewrite.ASTModificationStore;
import org.eclipse.cdt.internal.core.dom.rewrite.ASTRewriteAnalyzer;
import org.eclipse.cdt.internal.core.dom.rewrite.commenthandler.NodeCommentMap;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.runtime.Assert;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.Document;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.Region;
import org.eclipse.jface.text.TextUtilities;
import org.eclipse.ltk.core.refactoring.Change;
import org.eclipse.ltk.core.refactoring.CompositeChange;
import org.eclipse.ltk.core.refactoring.TextFileChange;
import org.eclipse.text.edits.DeleteEdit;
import org.eclipse.text.edits.InsertEdit;
import org.eclipse.text.edits.MalformedTreeException;
import org.eclipse.text.edits.MultiTextEdit;
import org.eclipse.text.edits.ReplaceEdit;
import org.eclipse.text.edits.TextEdit;
import org.eclipse.text.edits.TextEditGroup;

public class ChangeGenerator extends ASTVisitor {
  private final Map<IASTNode, Map<ModificationKind, List<ASTModification>>> classifiedModifications = new HashMap<IASTNode, Map<ModificationKind, List<ASTModification>>>();
  private int processedOffset;
  private MultiTextEdit rootEdit;
  private CompositeChange change;

  private final ASTModificationStore modificationStore;
  private final NodeCommentMap commentMap;

  {
    shouldVisitArrayModifiers = true;
    shouldVisitBaseSpecifiers = true;
    shouldVisitNames = true;
    shouldVisitDeclarations = true;
    shouldVisitDeclarators = true;
    shouldVisitDeclSpecifiers = true;
    shouldVisitExpressions = true;
    shouldVisitInitializers = true;
    shouldVisitNamespaces = true;
    shouldVisitParameterDeclarations = true;
    shouldVisitPointerOperators = true;
    shouldVisitStatements = true;
    shouldVisitTemplateParameters = true;
    shouldVisitTranslationUnit = true;
    shouldVisitTypeIds = true;
  }

  public ChangeGenerator(final ASTModificationStore modificationStore, final NodeCommentMap commentMap) {
    this.modificationStore = modificationStore;
    this.commentMap = commentMap;
  }

  public void generateChange(final IASTNode rootNode) throws ProblemRuntimeException {
    generateChange(rootNode, this);
  }

  private void generateChange(final IASTNode rootNode, final ASTVisitor pathProvider) throws ProblemRuntimeException {
    change = new CompositeChange(ChangeGeneratorMessages.ChangeGenerator_compositeChange);
    classifyModifications();
    rootNode.accept(pathProvider);
    if (rootEdit == null) {
      return;
    }
    final IASTTranslationUnit ast = rootNode.getTranslationUnit();
    final String source = ast.getRawSignature();
    final ITranslationUnit tu = ast.getOriginatingTranslationUnit();
    formatChangedCode(source, tu);
    final TextFileChange subchange = ASTRewriteAnalyzer.createCTextFileChange((IFile) tu.getResource());
    subchange.setEdit(rootEdit);
    change.add(subchange);
  }

  private void classifyModifications() {
    final ASTModificationMap rootModifications = modificationStore.getRootModifications();
    if (rootModifications == null) {
      return;
    }

    for (final IASTNode node : rootModifications.getModifiedNodes()) {
      final List<ASTModification> modifications = rootModifications.getModificationsForNode(node);
      for (final ASTModification modification : modifications) {
        Map<ModificationKind, List<ASTModification>> map = classifiedModifications.get(node);
        if (map == null) {
          map = new TreeMap<ModificationKind, List<ASTModification>>();
          classifiedModifications.put(node, map);
        }
        final ModificationKind kind = modification.getKind();
        List<ASTModification> list = map.get(kind);
        if (list == null) {
          list = new ArrayList<ASTModification>(2);
          map.put(kind, list);
        }
        list.add(modification);
      }
    }
  }

  @Override
  public int visit(final IASTTranslationUnit tu) {
    final IASTFileLocation location = tu.getFileLocation();
    processedOffset = location.getNodeOffset();
    return super.visit(tu);
  }

  @Override
  public int leave(final IASTTranslationUnit tu) {
    handleAppends(tu);
    return super.leave(tu);
  }

  @Override
  public int visit(final IASTDeclaration declaration) {
    handleInserts(declaration);
    if (requiresRewrite(declaration)) {
      handleReplace(declaration);
      return ASTVisitor.PROCESS_SKIP;
    }
    return super.visit(declaration);
  }

  @Override
  public int visit(final IASTDeclarator declarator) {
    handleInserts(declarator);
    if (requiresRewrite(declarator)) {
      handleReplace(declarator);
      return ASTVisitor.PROCESS_SKIP;
    }
    return super.visit(declarator);
  }

  @Override
  public int visit(final IASTArrayModifier arrayModifier) {
    handleInserts(arrayModifier);
    if (requiresRewrite(arrayModifier)) {
      handleReplace(arrayModifier);
      return ASTVisitor.PROCESS_SKIP;
    }
    return super.visit(arrayModifier);
  }

  @Override
  public int visit(final ICPPASTNamespaceDefinition namespaceDefinition) {
    handleInserts(namespaceDefinition);
    if (requiresRewrite(namespaceDefinition)) {
      handleReplace(namespaceDefinition);
      return ASTVisitor.PROCESS_SKIP;
    }
    return super.visit(namespaceDefinition);
  }

  @Override
  public int leave(final ICPPASTNamespaceDefinition namespaceDefinition) {
    if (!requiresRewrite(namespaceDefinition)) {
      handleAppends(namespaceDefinition);
    }
    return super.leave(namespaceDefinition);
  }

  @Override
  public int visit(final IASTDeclSpecifier declSpec) {
    handleInserts(declSpec);
    if (requiresRewrite(declSpec)) {
      handleReplace(declSpec);
      return ASTVisitor.PROCESS_SKIP;
    }
    return super.visit(declSpec);
  }

  @Override
  public int leave(final IASTDeclSpecifier declSpec) {
    if (!requiresRewrite(declSpec)) {
      handleAppends(declSpec);
    }
    return super.leave(declSpec);
  }

  @Override
  public int visit(final IASTExpression expression) {
    handleInserts(expression);
    if (requiresRewrite(expression)) {
      handleReplace(expression);
      return ASTVisitor.PROCESS_SKIP;
    }
    return super.visit(expression);
  }

  @Override
  public int visit(final IASTInitializer initializer) {
    handleInserts(initializer);
    if (requiresRewrite(initializer)) {
      handleReplace(initializer);
      return ASTVisitor.PROCESS_SKIP;
    }
    return super.visit(initializer);
  }

  @Override
  public int visit(final IASTName name) {
    handleInserts(name);
    if (requiresRewrite(name)) {
      handleReplace(name);
      return ASTVisitor.PROCESS_SKIP;
    }
    return super.visit(name);
  }

  @Override
  public int visit(final IASTParameterDeclaration parameterDeclaration) {
    handleInserts(parameterDeclaration);
    if (requiresRewrite(parameterDeclaration)) {
      handleReplace(parameterDeclaration);
      return ASTVisitor.PROCESS_SKIP;
    }
    return super.visit(parameterDeclaration);
  }

  @Override
  public int visit(final IASTPointerOperator pointerOperator) {
    handleInserts(pointerOperator);
    if (requiresRewrite(pointerOperator)) {
      handleReplace(pointerOperator);
      return ASTVisitor.PROCESS_SKIP;
    }
    return super.visit(pointerOperator);
  }

  @Override
  public int visit(final IASTTypeId typeId) {
    handleInserts(typeId);
    if (requiresRewrite(typeId)) {
      handleReplace(typeId);
      return ASTVisitor.PROCESS_SKIP;
    }
    return super.visit(typeId);
  }

  @Override
  public int visit(final IASTStatement statement) {
    handleInserts(statement);
    if (requiresRewrite(statement)) {
      handleReplace(statement);
      return ASTVisitor.PROCESS_SKIP;
    }
    return super.visit(statement);
  }

  @Override
  public int leave(final IASTStatement statement) {
    if (!requiresRewrite(statement)) {
      handleAppends(statement);
    }
    return super.leave(statement);
  }

  private void addChildEdit(final TextEdit edit) {
    rootEdit.addChild(edit);
    processedOffset = edit.getExclusiveEnd();
  }

  private TextEdit clippedEdit(final TextEdit edit, final IRegion region) {
    if (((edit.getOffset() < region.getOffset()) && (edit.getExclusiveEnd() <= region.getOffset())) || (edit.getOffset() >= endOffset(region))) {
      return null;
    }
    final int offset = Math.max(edit.getOffset(), region.getOffset());
    final int length = Math.min(endOffset(edit), endOffset(region)) - offset;
    if ((offset == edit.getOffset()) && (length == edit.getLength())) {
      // InsertEdit always satisfies the above condition.
      return edit;
    }
    if (edit instanceof DeleteEdit) {
      return new DeleteEdit(offset, length);
    }
    if (edit instanceof ReplaceEdit) {
      final String replacement = ((ReplaceEdit) edit).getText();
      final int start = Math.max(offset - edit.getOffset(), 0);
      final int end = Math.min(endOffset(region) - offset, replacement.length());
      if (end <= start) {
        return new DeleteEdit(offset, length);
      }
      return new ReplaceEdit(offset, length, replacement.substring(start, end));
    } else {
      throw new IllegalArgumentException("Unexpected edit type: " + edit.getClass().getSimpleName()); //$NON-NLS-1$
    }
  }

  /**
   * Applies the C++ code formatter to the code affected by refactoring.
   *
   * @param code
   *          The code being modified.
   * @param tu
   *          The translation unit containing the code.
   */
  private void formatChangedCode(String code, final ITranslationUnit tu) {
    final IDocument document = new Document(code);
    try {
      TextEdit edit = rootEdit.copy();
      // Apply refactoring changes to a temporary document.
      edit.apply(document, TextEdit.UPDATE_REGIONS);

      // Expand regions affected by the changes to cover complete lines. We calculate two
      // sets of regions, reflecting the state of the document before and after
      // the refactoring changes.
      final TextEdit[] appliedEdits = edit.getChildren();
      final TextEdit[] edits = rootEdit.removeChildren();
      IRegion[] regions = new IRegion[appliedEdits.length];
      int numRegions = 0;
      int prevEnd = -1;
      for (int i = 0; i < appliedEdits.length; i++) {
        edit = appliedEdits[i];
        final int offset = edit.getOffset();
        final int end = offset + edit.getLength();
        int newOffset = document.getLineInformationOfOffset(offset).getOffset();
        edit = edits[i];
        final int originalEnd = edit.getExclusiveEnd();
        // Expand to the end of the line unless the end of the edit region is at
        // the beginning of line both, before and after the change.
        final int newEnd = ((originalEnd == 0) || (code.charAt(originalEnd - 1) == '\n')) && (end == newOffset) ? end : endOffset(document.getLineInformationOfOffset(end));
        if (newOffset <= prevEnd) {
          numRegions--;
          newOffset = regions[numRegions].getOffset();
        }
        prevEnd = newEnd;
        regions[numRegions] = new Region(newOffset, newEnd - newOffset);
        numRegions++;
      }

      if (numRegions < regions.length) {
        regions = Arrays.copyOf(regions, numRegions);
      }

      // Calculate formatting changes for the regions after the refactoring changes.
      final ICProject project = tu.getCProject();
      final Map<String, Object> options = new HashMap<String, Object>(project.getOptions(true));
      options.put(DefaultCodeFormatterConstants.FORMATTER_TRANSLATION_UNIT, tu);
      // Allow all comments to be indented.
      options.put(DefaultCodeFormatterConstants.FORMATTER_COMMENT_NEVER_INDENT_LINE_COMMENTS_ON_FIRST_COLUMN, DefaultCodeFormatterConstants.FALSE);
      final CodeFormatter formatter = ToolFactory.createCodeFormatter(options);
      code = document.get();
      TextEdit[] formatEdits = formatter.format(CodeFormatter.K_TRANSLATION_UNIT, code, regions, TextUtilities.getDefaultLineDelimiter(document));

      TextEdit combinedFormatEdit = new MultiTextEdit();
      for (final TextEdit formatEdit : formatEdits) {
        combinedFormatEdit = TextEditUtil.merge(combinedFormatEdit, formatEdit);
      }
      formatEdits = TextEditUtil.flatten(combinedFormatEdit).removeChildren();

      final MultiTextEdit result = new MultiTextEdit();
      int delta = 0;
      TextEdit edit1 = null;
      TextEdit edit2 = null;
      int i = 0;
      int j = 0;
      while (true) {
        if ((edit1 == null) && (i < edits.length)) {
          edit1 = edits[i++];
        }
        if ((edit2 == null) && (j < formatEdits.length)) {
          edit2 = formatEdits[j++];
        }
        if (edit1 == null) {
          if (edit2 == null) {
            break;
          }
          edit2.moveTree(-delta);
          result.addChild(edit2);
          edit2 = null;
        } else if (edit2 == null) {
          delta += TextEditUtil.delta(edit1);
          result.addChild(edit1);
          edit1 = null;
        } else {
          if ((edit2.getExclusiveEnd() - delta) <= edit1.getOffset()) {
            edit2.moveTree(-delta);
            result.addChild(edit2);
            edit2 = null;
          } else {
            TextEdit piece = clippedEdit(edit2, new Region(-1, edit1.getOffset() + delta));
            if (piece != null) {
              piece.moveTree(-delta);
              result.addChild(piece);
            }
            final int d = TextEditUtil.delta(edit1);
            final Region region = new Region(edit1.getOffset() + delta, edit1.getLength() + d);
            final int end = endOffset(region);
            final MultiTextEdit format = new MultiTextEdit();
            while ((piece = clippedEdit(edit2, region)) != null) {
              format.addChild(piece);
              // The warning "The variable edit2 may be null at this location" is bogus.
              // Make the compiler happy:
              if (edit2 != null) {
                if ((edit2.getExclusiveEnd() >= end) || (j >= formatEdits.length)) {
                  break;
                }
              }
              edit2 = formatEdits[j++];
            }
            if (format.hasChildren()) {
              format.moveTree(-delta);
              edit1 = applyEdit(format, edit1);
            }
            delta += d;
            result.addChild(edit1);
            edit1 = null;

            edit2 = clippedEdit(edit2, new Region(end, Integer.MAX_VALUE - end));
          }
        }
      }
      rootEdit = result;
    } catch (final MalformedTreeException e) {
      CCorePlugin.log(e);
    } catch (final BadLocationException e) {
      CCorePlugin.log(e);
    }
  }

  /**
   * Applies source edit to the target one and returns the combined edit.
   */
  private TextEdit applyEdit(final TextEdit source, final TextEdit target) throws MalformedTreeException, BadLocationException {
    source.moveTree(-target.getOffset());
    String text;
    if (target instanceof InsertEdit) {
      text = ((InsertEdit) target).getText();
    } else if (target instanceof ReplaceEdit) {
      text = ((ReplaceEdit) target).getText();
    } else {
      text = ""; //$NON-NLS-1$
    }

    final IDocument document = new Document(text);
    source.apply(document, TextEdit.NONE);
    text = document.get();
    if (target.getLength() == 0) {
      return new InsertEdit(target.getOffset(), text);
    } else {
      return new ReplaceEdit(target.getOffset(), target.getLength(), text);
    }
  }

  private int endOffset(final IRegion region) {
    return region.getOffset() + region.getLength();
  }

  private int endOffset(final TextEdit edit) {
    return edit.getOffset() + edit.getLength();
  }

  private int endOffset(final IASTFileLocation nodeLocation) {
    return nodeLocation.getNodeOffset() + nodeLocation.getNodeLength();
  }

  private int endOffset(final IASTNode node) {
    return endOffset(node.getFileLocation());
  }

  private int offset(final IASTNode node) {
    return node.getFileLocation().getNodeOffset();
  }

  private void handleInserts(final IASTNode anchorNode) {
    final List<ASTModification> modifications = getModifications(anchorNode, ModificationKind.INSERT_BEFORE);
    if (modifications.isEmpty()) {
      return;
    }
    final ChangeGeneratorWriterVisitor writer = new ChangeGeneratorWriterVisitor(modificationStore, commentMap);
    IASTNode newNode = null;
    for (final ASTModification modification : modifications) {
      final boolean first = newNode == null;
      newNode = modification.getNewNode();
      if (first) {
        final IASTNode prevNode = getPreviousSiblingOrPreprocessorNode(anchorNode);
        if (prevNode != null) {
          if (ASTWriter.requireBlankLineInBetween(prevNode, newNode)) {
            writer.newLine();
          }
        } else if (anchorNode.getParent() instanceof ICPPASTNamespaceDefinition) {
          writer.newLine();
        }
      }
      newNode.accept(writer);
      if (getContainingNodeList(anchorNode) != null) {
        writer.getScribe().print(", "); //$NON-NLS-1$
      }
    }
    if (ASTWriter.requireBlankLineInBetween(newNode, anchorNode)) {
      writer.newLine();
    }
    int insertPos = getOffsetIncludingComments(anchorNode);
    int length = 0;
    if (writer.getScribe().isAtBeginningOfLine()) {
      final String tuCode = anchorNode.getTranslationUnit().getRawSignature();
      insertPos = skipPrecedingWhitespace(tuCode, insertPos);
      length = insertPos;
      insertPos = skipPrecedingBlankLines(tuCode, insertPos);
      length -= insertPos;
    }
    addToRootEdit(anchorNode);
    final String code = writer.toString();
    if (!code.isEmpty()) {
      addChildEdit(new InsertEdit(insertPos, code));
    }
    if (length != 0) {
      addChildEdit(new DeleteEdit(insertPos, length));
    }
  }

  private void handleReplace(final IASTNode node) {
    final List<ASTModification> modifications = getModifications(node, ModificationKind.REPLACE);
    final String source = node.getTranslationUnit().getRawSignature();
    final ChangeGeneratorWriterVisitor writer = new ChangeGeneratorWriterVisitor(modificationStore, commentMap);
    final IASTFileLocation fileLocation = node.getFileLocation();
    addToRootEdit(node);
    if ((modifications.size() == 1) && (modifications.get(0).getNewNode() == null)) {
      // There is no replacement. We are deleting a piece of existing code.
      int offset = getOffsetIncludingComments(node);
      int endOffset = getEndOffsetIncludingComments(node);
      offset = Math.max(skipPrecedingBlankLines(source, offset), processedOffset);
      endOffset = skipTrailingBlankLines(source, endOffset);
      final IASTNode[] siblingsList = getContainingNodeList(node);
      if (siblingsList != null) {
        if (siblingsList.length > 1) {
          if (node == siblingsList[0]) {
            endOffset = skipToTrailingDelimiter(source, ',', endOffset);
          } else {
            offset = skipToPrecedingDelimiter(source, ',', offset);
          }
        } else if (node.getPropertyInParent() == ICPPASTFunctionDefinition.MEMBER_INITIALIZER) {
          offset = skipToPrecedingDelimiter(source, ':', offset);
        }
      }
      final IASTNode prevNode = getPreviousSiblingOrPreprocessorNode(node);
      final IASTNode nextNode = getNextSiblingOrPreprocessorNode(node);
      if ((prevNode != null) && (nextNode != null)) {
        if (ASTWriter.requireBlankLineInBetween(prevNode, nextNode)) {
          writer.newLine();
        }
      } else if (node.getParent() instanceof ICPPASTNamespaceDefinition) {
        writer.newLine();
      }
      final String code = writer.toString();
      if (endOffset > offset) {
        addChildEdit(new DeleteEdit(offset, endOffset - offset));
      }
      if (!code.isEmpty()) {
        addChildEdit(new InsertEdit(endOffset, code));
      }
    } else {
      node.accept(writer);
      String code = writer.toString();
      final int offset = fileLocation.getNodeOffset();
      final int endOffset = offset + fileLocation.getNodeLength();
      final String lineSeparator = writer.getScribe().getLineSeparator();
      if (code.endsWith(lineSeparator)) {
        code = code.substring(0, code.length() - lineSeparator.length());
      }
      addChildEdit(new ReplaceEdit(offset, endOffset - offset, code));
      if ((node instanceof IASTStatement) || (node instanceof IASTDeclaration)) {
        // Include trailing comments in the area to be replaced.
        final int commentEnd = getEndOffsetIncludingTrailingComments(node);
        if (commentEnd > endOffset) {
          addChildEdit(new DeleteEdit(endOffset, commentEnd - endOffset));
        }
      }
    }
  }

  private void handleAppends(final IASTNode node) {
    final List<ASTModification> modifications = getModifications(node, ModificationKind.APPEND_CHILD);
    if (modifications.isEmpty()) {
      return;
    }
    final ChangeGeneratorWriterVisitor writer = new ChangeGeneratorWriterVisitor(modificationStore, commentMap);
    final ReplaceEdit anchor = getAppendAnchor(node);
    Assert.isNotNull(anchor);
    IASTNode precedingNode = getLastNodeBeforeAppendPoint(node);
    for (final ASTModification modification : modifications) {
      final IASTNode newNode = modification.getNewNode();
      if (precedingNode != null) {
        if (ASTWriter.requireBlankLineInBetween(precedingNode, newNode)) {
          writer.newLine();
        }
      } else if (node instanceof ICPPASTNamespaceDefinition) {
        writer.newLine();
      }
      precedingNode = null;
      newNode.accept(writer);
    }
    if (node instanceof ICPPASTNamespaceDefinition) {
      writer.newLine();
    }
    addToRootEdit(node);
    final String code = writer.toString();
    if (!code.isEmpty()) {
      addChildEdit(new InsertEdit(anchor.getOffset(), code));
    }
    addChildEdit(new ReplaceEdit(anchor.getOffset(), anchor.getLength(), anchor.getText()));
    processedOffset = endOffset(node);
  }

  private void handleAppends(final IASTTranslationUnit node) {
    final List<ASTModification> modifications = getModifications(node, ModificationKind.APPEND_CHILD);
    if (modifications.isEmpty()) {
      return;
    }

    IASTNode prevNode = null;
    final IASTDeclaration[] declarations = node.getDeclarations();
    if (declarations.length != 0) {
      prevNode = declarations[declarations.length - 1];
    } else {
      final IASTPreprocessorStatement[] preprocessorStatements = node.getAllPreprocessorStatements();
      if (preprocessorStatements.length != 0) {
        prevNode = preprocessorStatements[preprocessorStatements.length - 1];
      }
    }
    final int offset = prevNode != null ? getEndOffsetIncludingComments(prevNode) : 0;
    final String source = node.getRawSignature();
    final int endOffset = skipTrailingBlankLines(source, offset);

    addToRootEdit(node);
    final ChangeGeneratorWriterVisitor writer = new ChangeGeneratorWriterVisitor(modificationStore, commentMap);
    IASTNode newNode = null;
    for (final ASTModification modification : modifications) {
      final boolean first = newNode == null;
      newNode = modification.getNewNode();
      if (first) {
        if (prevNode != null) {
          writer.newLine();
          if (ASTWriter.requireBlankLineInBetween(prevNode, newNode)) {
            writer.newLine();
          }
        }
      }
      newNode.accept(writer);
      // TODO(sprigogin): Temporary workaround for invalid handling of line breaks in
      // StatementWriter
      if (!writer.toString().endsWith("\n")) {
        writer.newLine();
      }

    }
    if (prevNode != null) {
      final IASTNode nextNode = getNextSiblingOrPreprocessorNode(prevNode);
      if ((nextNode != null) && ASTWriter.requireBlankLineInBetween(newNode, nextNode)) {
        writer.newLine();
      }
    }

    final String code = writer.toString();
    if (!code.isEmpty()) {
      addChildEdit(new InsertEdit(offset, code));
    }
    if (endOffset > offset) {
      addChildEdit(new DeleteEdit(offset, endOffset - offset));
    }
  }

  /**
   * Returns the list of nodes the given node is part of, for example function parameters if
   * the node is a parameter.
   *
   * @param node
   *          the node possibly belonging to a list.
   * @return the list of nodes containing the given node, or <code>null</code> if the node
   *         does not belong to a list
   */
  private IASTNode[] getContainingNodeList(final IASTNode node) {
    if (node.getPropertyInParent() == IASTStandardFunctionDeclarator.FUNCTION_PARAMETER) {
      return ((IASTStandardFunctionDeclarator) node.getParent()).getParameters();
    } else if (node.getPropertyInParent() == IASTExpressionList.NESTED_EXPRESSION) {
      return ((IASTExpressionList) node.getParent()).getExpressions();
    } else if (node.getPropertyInParent() == ICPPASTFunctionDefinition.MEMBER_INITIALIZER) {
      return ((ICPPASTFunctionDefinition) node.getParent()).getMemberInitializers();
    } else if (node.getPropertyInParent() == ICPPASTFunctionDeclarator.EXCEPTION_TYPEID) {
      return ((ICPPASTFunctionDeclarator) node.getParent()).getExceptionSpecification();
    }

    return null;
  }

  private IASTNode getLastNodeBeforeAppendPoint(final IASTNode node) {
    IASTNode[] children;
    if (node instanceof ICPPASTNamespaceDefinition) {
      children = ((ICPPASTNamespaceDefinition) node).getDeclarations(true);
    } else if (node instanceof IASTCompositeTypeSpecifier) {
      children = ((IASTCompositeTypeSpecifier) node).getDeclarations(true);
    } else {
      children = node.getChildren();
    }
    for (int i = children.length; --i >= 0;) {
      final IASTNode child = getReplacementNode(children[i]);
      if (child != null) {
        return child;
      }
    }
    return null;
  }

  private IASTNode getReplacementNode(IASTNode node) {
    final List<ASTModification> modifications = getModifications(node, ModificationKind.REPLACE);
    if (!modifications.isEmpty()) {
      node = modifications.get(modifications.size() - 1).getNewNode();
    }
    return node;
  }

  private IASTNode getPreviousSiblingNode(final IASTNode node) {
    final IASTNode parent = node.getParent();
    IASTNode[] siblings;
    if (parent instanceof ICPPASTNamespaceDefinition) {
      siblings = ((ICPPASTNamespaceDefinition) parent).getDeclarations(true);
    } else if (parent instanceof IASTCompositeTypeSpecifier) {
      siblings = ((IASTCompositeTypeSpecifier) parent).getDeclarations(true);
    } else {
      siblings = parent.getChildren();
    }
    boolean beforeNode = false;
    for (int i = siblings.length; --i >= 0;) {
      final IASTNode sibling = siblings[i];
      if (sibling == node) {
        beforeNode = true;
      } else if (beforeNode && (getReplacementNode(sibling) != null)) {
        return sibling;
      }
    }
    return null;
  }

  private IASTNode getPreviousSiblingOrPreprocessorNode(final IASTNode node) {
    final int offset = offset(node);
    final IASTTranslationUnit ast = node.getTranslationUnit();
    final IASTPreprocessorStatement[] preprocessorStatements = ast.getAllPreprocessorStatements();
    int low = 0;
    int high = preprocessorStatements.length;
    while (low < high) {
      final int mid = (low + high) / 2;
      final IASTNode statement = preprocessorStatements[mid];
      if (statement.isPartOfTranslationUnitFile() && (endOffset(statement) > offset)) {
        high = mid;
      } else {
        low = mid + 1;
      }
    }
    final IASTNode statement = --low >= 0 ? preprocessorStatements[low] : null;

    final IASTNode originalSibling = getPreviousSiblingNode(node);
    final IASTNode sibling = originalSibling == null ? null : getReplacementNode(originalSibling);
    if ((statement == null) || !statement.isPartOfTranslationUnitFile()) {
      return sibling;
    }
    if (sibling == null) {
      final IASTNode parent = node.getParent();
      if (offset(parent) >= endOffset(statement)) {
        return null;
      }
      return statement;
    }

    return endOffset(originalSibling) >= endOffset(statement) ? sibling : statement;
  }

  private IASTNode getNextSiblingNode(final IASTNode node) {
    final IASTNode parent = node.getParent();
    IASTNode[] siblings;
    if (parent instanceof ICPPASTNamespaceDefinition) {
      siblings = ((ICPPASTNamespaceDefinition) parent).getDeclarations(true);
    } else if (parent instanceof IASTCompositeTypeSpecifier) {
      siblings = ((IASTCompositeTypeSpecifier) parent).getDeclarations(true);
    } else {
      siblings = parent.getChildren();
    }
    boolean beforeNode = false;
    for (final IASTNode sibling : siblings) {
      if (sibling == node) {
        beforeNode = true;
      } else if (beforeNode && (getReplacementNode(sibling) != null)) {
        return sibling;
      }
    }
    return null;
  }

  private IASTNode getNextSiblingOrPreprocessorNode(final IASTNode node) {
    final int endOffset = endOffset(node);
    final IASTTranslationUnit ast = node.getTranslationUnit();
    final IASTPreprocessorStatement[] preprocessorStatements = ast.getAllPreprocessorStatements();
    int low = 0;
    int high = preprocessorStatements.length;
    while (low < high) {
      final int mid = (low + high) / 2;
      final IASTNode statement = preprocessorStatements[mid];
      if (statement.isPartOfTranslationUnitFile() && (offset(statement) > endOffset)) {
        high = mid;
      } else {
        low = mid + 1;
      }
    }
    final IASTNode statement = high < preprocessorStatements.length ? preprocessorStatements[high] : null;

    final IASTNode originalSibling = getNextSiblingNode(node);
    final IASTNode sibling = originalSibling == null ? null : getReplacementNode(originalSibling);
    if ((statement == null) || !statement.isPartOfTranslationUnitFile()) {
      return sibling;
    }
    if (sibling == null) {
      final IASTNode parent = node.getParent();
      if (endOffset(parent) <= offset(statement)) {
        return null;
      }
      return statement;
    }

    return offset(originalSibling) <= offset(statement) ? sibling : statement;
  }

  /**
   * Returns a replace edit whose offset is the position where child appended nodes should be
   * inserted at. The text contains the content of the code region that will be disturbed by
   * the insertion.
   *
   * @param node
   *          The node to append children to.
   * @return a ReplaceEdit object, or <code>null</code> if the node does not support appending
   *         children to it.
   */
  private ReplaceEdit getAppendAnchor(final IASTNode node) {
    if (!((node instanceof IASTCompositeTypeSpecifier) || (node instanceof IASTCompoundStatement) || (node instanceof ICPPASTNamespaceDefinition))) {
      return null;
    }
    final String code = node.getRawSignature();
    final IASTFileLocation location = node.getFileLocation();
    final int pos = location.getNodeOffset() + location.getNodeLength();
    final int len = code.endsWith("}") ? 1 : 0; //$NON-NLS-1$
    final int insertPos = code.length() - len;
    final int startOfLine = skipPrecedingBlankLines(code, insertPos);
    if (startOfLine == insertPos) {
      // Include the closing brace in the region that will be reformatted.
      return new ReplaceEdit(pos - len, len, code.substring(insertPos));
    }
    return new ReplaceEdit(location.getNodeOffset() + startOfLine, insertPos - startOfLine, ""); //$NON-NLS-1$
  }

  /**
   * Skips whitespace between the beginning of the line and the given position.
   *
   * @param text
   *          The text to scan.
   * @param startPos
   *          The start position.
   * @return The beginning of the line containing the start position, if there are no
   *         non-whitespace characters between the beginning of the line and the start position.
   *         Otherwise returns the start position.
   */
  private int skipPrecedingWhitespace(final String text, final int startPos) {
    for (int pos = startPos; --pos >= 0;) {
      final char c = text.charAt(pos);
      if (c == '\n') {
        return pos + 1;
      } else if (!Character.isWhitespace(c)) {
        return startPos;
      }
    }
    return 0;
  }

  /**
   * Skips whitespace between the beginning of the line and the given position and blank lines
   * above that.
   *
   * @param text
   *          The text to scan.
   * @param startPos
   *          The start position.
   * @return The beginning of the first blank line preceding the start position,
   *         or beginning of the current line, if there are no non-whitespace characters between
   *         the beginning of the line and the start position.
   *         Otherwise returns the start position.
   */
  private int skipPrecedingBlankLines(final String text, int startPos) {
    for (int pos = startPos; --pos >= 0;) {
      final char c = text.charAt(pos);
      if (c == '\n') {
        startPos = pos + 1;
      } else if (!Character.isWhitespace(c)) {
        return startPos;
      }
    }
    return 0;
  }

  /**
   * Skips whitespace between the given position and the end of the line and blank lines
   * below that.
   *
   * @param text
   *          The text to scan.
   * @param startPos
   *          The start position.
   * @return The beginning of the first non-blank line following the start position, if there are
   *         no non-whitespace characters between the start position and the end of the line.
   *         Otherwise returns the start position.
   */
  private int skipTrailingBlankLines(final String text, int startPos) {
    for (int pos = startPos; pos < text.length(); pos++) {
      final char c = text.charAt(pos);
      if (c == '\n') {
        startPos = pos + 1;
      } else if (!Character.isWhitespace(c)) {
        return startPos;
      }
    }
    return text.length();
  }

  /**
   * Skips whitespace to the left of the given position until the given delimiter character
   * is found.
   *
   * @param text
   *          The text to scan.
   * @param delimiter
   *          the delimiter to find
   * @param startPos
   *          The start position.
   * @return The position of the given delimiter, or the start position if a non-whitespace
   *         character is encountered before the given delimiter.
   */
  private int skipToPrecedingDelimiter(final String text, final char delimiter, final int startPos) {
    for (int pos = startPos; --pos >= 0;) {
      final char c = text.charAt(pos);
      if (c == delimiter) {
        return pos;
      } else if (!Character.isWhitespace(c)) {
        return startPos;
      }
    }
    return startPos;
  }

  /**
   * Skips whitespace to the right of the given position until the given delimiter character
   * is found.
   *
   * @param text
   *          The text to scan.
   * @param delimiter
   *          the delimiter to find
   * @param startPos
   *          The start position.
   * @return The position after the given delimiter, or the start position if a non-whitespace
   *         character is encountered before the given delimiter.
   */
  private int skipToTrailingDelimiter(final String text, final char delimiter, final int startPos) {
    for (int pos = startPos; pos < text.length(); pos++) {
      final char c = text.charAt(pos);
      if (c == delimiter) {
        return pos + 1;
      } else if (!Character.isWhitespace(c)) {
        return startPos;
      }
    }
    return startPos;
  }

  private void addToRootEdit(final IASTNode modifiedNode) {
    if (rootEdit == null) {
      rootEdit = new MultiTextEdit();
    }
    TextEditGroup editGroup = new TextEditGroup(ChangeGeneratorMessages.ChangeGenerator_group);
    for (final List<ASTModification> modifications : getModifications(modifiedNode).values()) {
      for (final ASTModification modification : modifications) {
        if (modification.getAssociatedEditGroup() != null) {
          editGroup = modification.getAssociatedEditGroup();
          rootEdit.addChildren(editGroup.getTextEdits());
          return;
        }
      }
    }
  }

  private int getOffsetIncludingComments(final IASTNode node) {
    int nodeOffset = offset(node);

    final List<IASTComment> comments = commentMap.getAllCommentsForNode(node);
    if (!comments.isEmpty()) {
      int startOffset = nodeOffset;
      for (final IASTComment comment : comments) {
        final int commentOffset = offset(comment);
        if (commentOffset < startOffset) {
          startOffset = commentOffset;
        }
      }
      nodeOffset = startOffset;
    }
    return nodeOffset;
  }

  private int getEndOffsetIncludingComments(IASTNode node) {
    int endOffset = 0;
    while (true) {
      final IASTFileLocation fileLocation = node.getFileLocation();
      if (fileLocation != null) {
        endOffset = Math.max(endOffset, endOffset(fileLocation));
      }
      final List<IASTComment> comments = commentMap.getAllCommentsForNode(node);
      if (!comments.isEmpty()) {
        for (final IASTComment comment : comments) {
          final int commentEndOffset = endOffset(comment);
          if (commentEndOffset >= endOffset) {
            endOffset = commentEndOffset;
          }
        }
      }
      final IASTNode[] children = node.getChildren();
      if (children.length == 0) {
        break;
      }
      node = children[children.length - 1];
    }
    return endOffset;
  }

  private int getEndOffsetIncludingTrailingComments(IASTNode node) {
    int endOffset = 0;
    while (true) {
      final IASTFileLocation fileLocation = node.getFileLocation();
      if (fileLocation != null) {
        endOffset = Math.max(endOffset, endOffset(fileLocation));
      }
      final List<IASTComment> comments = commentMap.getTrailingCommentsForNode(node);
      if (!comments.isEmpty()) {
        for (final IASTComment comment : comments) {
          final int commentEndOffset = endOffset(comment);
          if (commentEndOffset >= endOffset) {
            endOffset = commentEndOffset;
          }
        }
      }
      final IASTNode[] children = node.getChildren();
      if (children.length == 0) {
        break;
      }
      node = children[children.length - 1];
    }
    return endOffset;
  }

  private Map<ModificationKind, List<ASTModification>> getModifications(final IASTNode node) {
    final Map<ModificationKind, List<ASTModification>> modifications = classifiedModifications.get(node);
    if (modifications == null) {
      return Collections.emptyMap();
    }
    return modifications;
  }

  private List<ASTModification> getModifications(final IASTNode node, final ModificationKind kind) {
    final Map<ModificationKind, List<ASTModification>> allModifications = getModifications(node);
    final List<ASTModification> modifications = allModifications.get(kind);
    if (modifications == null) {
      return Collections.emptyList();
    }
    return modifications;
  }

  private boolean requiresRewrite(final IASTNode node) {
    if (!getModifications(node, ModificationKind.REPLACE).isEmpty()) {
      return true;
    }
    for (final ASTModification modification : getModifications(node, ModificationKind.APPEND_CHILD)) {
      if (!isAppendable(modification)) {
        return true;
      }
    }
    return false;
  }

  private boolean isAppendable(final ASTModification modification) {
    if (modification.getKind() != ModificationKind.APPEND_CHILD) {
      return false;
    }
    final IASTNode node = modification.getNewNode();
    if (node instanceof ContainerNode) {
      for (final IASTNode containedNode : ((ContainerNode) node).getNodes()) {
        if (!((containedNode instanceof IASTDeclaration) || (containedNode instanceof IASTStatement))) {
          return false;
        }
      }
      return true;
    }
    return (node instanceof IASTDeclaration) || (node instanceof IASTStatement);
  }

  public Change getChange() {
    return change;
  }
}
TOP

Related Classes of me.pixodro.j2cpp.core.rewrite.changegenerator.ChangeGenerator

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.