/******************************************************************************* * Copyright (c) 2008, 2014 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 org.eclipse.cdt.internal.core.dom.rewrite.changegenerator; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.TreeMap; import org.eclipse.cdt.core.dom.ast.ASTVisitor; import org.eclipse.cdt.core.dom.ast.IASTArrayModifier; 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.ICPPASTCompositeTypeSpecifier.ICPPASTBaseSpecifier; 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.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.astwriter.ASTWriter; import org.eclipse.cdt.internal.core.dom.rewrite.astwriter.ContainerNode; import org.eclipse.cdt.internal.core.dom.rewrite.astwriter.ProblemRuntimeException; import org.eclipse.cdt.internal.core.dom.rewrite.commenthandler.NodeCommentMap; import org.eclipse.cdt.internal.formatter.ChangeFormatter; import org.eclipse.core.resources.IFile; import org.eclipse.core.runtime.Assert; 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.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<>(); 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(ASTModificationStore modificationStore, NodeCommentMap commentMap) { this.modificationStore = modificationStore; this.commentMap = commentMap; } public void generateChange(IASTNode rootNode) throws ProblemRuntimeException { generateChange(rootNode, this); } private void generateChange(IASTNode rootNode, ASTVisitor pathProvider) throws ProblemRuntimeException { change = new CompositeChange(ChangeGeneratorMessages.ChangeGenerator_compositeChange); classifyModifications(); rootNode.accept(pathProvider); if (rootEdit == null) return; IASTTranslationUnit ast = rootNode.getTranslationUnit(); String source = ast.getRawSignature(); ITranslationUnit tu = ast.getOriginatingTranslationUnit(); rootEdit = ChangeFormatter.formatChangedCode(source, tu, rootEdit); TextFileChange subchange= ASTRewriteAnalyzer.createCTextFileChange((IFile) tu.getResource()); subchange.setEdit(rootEdit); change.add(subchange); } private void classifyModifications() { ASTModificationMap rootModifications = modificationStore.getRootModifications(); if (rootModifications == null) return; for (IASTNode node : rootModifications.getModifiedNodes()) { List<ASTModification> modifications = rootModifications.getModificationsForNode(node); for (ASTModification modification : modifications) { Map<ModificationKind, List<ASTModification>> map = classifiedModifications.get(node); if (map == null) { map = new TreeMap<>(); classifiedModifications.put(node, map); } ModificationKind kind = modification.getKind(); List<ASTModification> list = map.get(kind); if (list == null) { list = new ArrayList<>(2); map.put(kind, list); } list.add(modification); } } } @Override public int visit(IASTTranslationUnit tu) { IASTFileLocation location = tu.getFileLocation(); processedOffset = location.getNodeOffset(); return super.visit(tu); } @Override public int leave(IASTTranslationUnit tu) { handleAppends(tu); return super.leave(tu); } @Override public int visit(IASTDeclaration declaration) { handleInserts(declaration); if (requiresRewrite(declaration)) { handleReplace(declaration); return ASTVisitor.PROCESS_SKIP; } return super.visit(declaration); } @Override public int visit(IASTDeclarator declarator) { handleInserts(declarator); if (requiresRewrite(declarator)) { handleReplace(declarator); return ASTVisitor.PROCESS_SKIP; } return super.visit(declarator); } @Override public int visit(ICPPASTBaseSpecifier baseSpecifier) { handleInserts(baseSpecifier); if (requiresRewrite(baseSpecifier)) { handleReplace(baseSpecifier); return ASTVisitor.PROCESS_SKIP; } return super.visit(baseSpecifier); } @Override public int leave(ICPPASTBaseSpecifier baseSpecifier) { if (!requiresRewrite(baseSpecifier)) { handleAppends(baseSpecifier); } return super.leave(baseSpecifier); } @Override public int visit(IASTArrayModifier arrayModifier) { handleInserts(arrayModifier); if (requiresRewrite(arrayModifier)) { handleReplace(arrayModifier); return ASTVisitor.PROCESS_SKIP; } return super.visit(arrayModifier); } @Override public int visit(ICPPASTNamespaceDefinition namespaceDefinition) { handleInserts(namespaceDefinition); if (requiresRewrite(namespaceDefinition)) { handleReplace(namespaceDefinition); return ASTVisitor.PROCESS_SKIP; } return super.visit(namespaceDefinition); } @Override public int leave(ICPPASTNamespaceDefinition namespaceDefinition) { if (!requiresRewrite(namespaceDefinition)) { handleAppends(namespaceDefinition); } return super.leave(namespaceDefinition); } @Override public int visit(IASTDeclSpecifier declSpec) { handleInserts(declSpec); if (requiresRewrite(declSpec)) { handleReplace(declSpec); return ASTVisitor.PROCESS_SKIP; } return super.visit(declSpec); } @Override public int leave(IASTDeclSpecifier declSpec) { if (!requiresRewrite(declSpec)) { handleAppends(declSpec); } return super.leave(declSpec); } @Override public int visit(IASTExpression expression) { handleInserts(expression); if (requiresRewrite(expression)) { handleReplace(expression); return ASTVisitor.PROCESS_SKIP; } return super.visit(expression); } @Override public int visit(IASTInitializer initializer) { handleInserts(initializer); if (requiresRewrite(initializer)) { handleReplace(initializer); return ASTVisitor.PROCESS_SKIP; } return super.visit(initializer); } @Override public int visit(IASTName name) { handleInserts(name); if (requiresRewrite(name)) { handleReplace(name); return ASTVisitor.PROCESS_SKIP; } return super.visit(name); } @Override public int visit(IASTParameterDeclaration parameterDeclaration) { handleInserts(parameterDeclaration); if (requiresRewrite(parameterDeclaration)) { handleReplace(parameterDeclaration); return ASTVisitor.PROCESS_SKIP; } return super.visit(parameterDeclaration); } @Override public int visit(IASTPointerOperator pointerOperator) { handleInserts(pointerOperator); if (requiresRewrite(pointerOperator)) { handleReplace(pointerOperator); return ASTVisitor.PROCESS_SKIP; } return super.visit(pointerOperator); } @Override public int visit(IASTTypeId typeId) { handleInserts(typeId); if (requiresRewrite(typeId)) { handleReplace(typeId); return ASTVisitor.PROCESS_SKIP; } return super.visit(typeId); } @Override public int visit(IASTStatement statement) { handleInserts(statement); if (requiresRewrite(statement)) { handleReplace(statement); return ASTVisitor.PROCESS_SKIP; } return super.visit(statement); } @Override public int leave(IASTStatement statement) { if (!requiresRewrite(statement)) { handleAppends(statement); } return super.leave(statement); } private void addChildEdit(TextEdit edit) { rootEdit.addChild(edit); processedOffset = edit.getExclusiveEnd(); } private int endOffset(IASTFileLocation nodeLocation) { return nodeLocation.getNodeOffset() + nodeLocation.getNodeLength(); } private int endOffset(IASTNode node) { return endOffset(node.getFileLocation()); } private int offset(IASTNode node) { return node.getFileLocation().getNodeOffset(); } private void handleInserts(IASTNode anchorNode) { List<ASTModification> modifications = getModifications(anchorNode, ModificationKind.INSERT_BEFORE); if (modifications.isEmpty()) return; ChangeGeneratorWriterVisitor writer = new ChangeGeneratorWriterVisitor(modificationStore, commentMap); IASTNode newNode = null; for (ASTModification modification : modifications) { boolean first = newNode == null; newNode = modification.getNewNode(); if (first) { 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 = commentMap.getOffsetIncludingComments(anchorNode); int length = 0; if (writer.getScribe().isAtBeginningOfLine()) { String tuCode = anchorNode.getTranslationUnit().getRawSignature(); insertPos = skipPrecedingWhitespace(tuCode, insertPos); length = insertPos; insertPos = skipPrecedingBlankLines(tuCode, insertPos); length -= insertPos; } addToRootEdit(anchorNode); String code = writer.toString(); if (!code.isEmpty()) addChildEdit(new InsertEdit(insertPos, code)); if (length != 0) addChildEdit(new DeleteEdit(insertPos, length)); } private void handleReplace(IASTNode node) { List<ASTModification> modifications = getModifications(node, ModificationKind.REPLACE); String source = node.getTranslationUnit().getRawSignature(); ChangeGeneratorWriterVisitor writer = new ChangeGeneratorWriterVisitor(modificationStore, commentMap); 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 = commentMap.getOffsetIncludingComments(node); int endOffset = commentMap.getEndOffsetIncludingComments(node); offset = Math.max(skipPrecedingBlankLines(source, offset), processedOffset); endOffset = skipTrailingBlankLines(source, endOffset); 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); } } IASTNode prevNode = getPreviousSiblingOrPreprocessorNode(node); IASTNode nextNode = getNextSiblingOrPreprocessorNode(node); if (prevNode != null && nextNode != null) { if (ASTWriter.requireBlankLineInBetween(prevNode, nextNode)) { writer.newLine(); } } else if (node.getParent() instanceof ICPPASTNamespaceDefinition) { writer.newLine(); } 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(); int offset = fileLocation.getNodeOffset(); int endOffset = offset + fileLocation.getNodeLength(); 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. int commentEnd = commentMap.getEndOffsetIncludingComments(node); if (commentEnd > endOffset) addChildEdit(new DeleteEdit(endOffset, commentEnd - endOffset)); } } } private void handleAppends(IASTNode node) { List<ASTModification> modifications = getModifications(node, ModificationKind.APPEND_CHILD); if (modifications.isEmpty()) return; ChangeGeneratorWriterVisitor writer = new ChangeGeneratorWriterVisitor(modificationStore, commentMap); ReplaceEdit anchor = getAppendAnchor(node); Assert.isNotNull(anchor); IASTNode precedingNode = getLastNodeBeforeAppendPoint(node); for (ASTModification modification : modifications) { 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); 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(IASTTranslationUnit node) { List<ASTModification> modifications = getModifications(node, ModificationKind.APPEND_CHILD); if (modifications.isEmpty()) return; IASTNode prevNode = null; IASTDeclaration[] declarations = node.getDeclarations(); if (declarations.length != 0) { prevNode = declarations[declarations.length - 1]; } else { IASTPreprocessorStatement[] preprocessorStatements = node.getAllPreprocessorStatements(); if (preprocessorStatements.length != 0) { prevNode = preprocessorStatements[preprocessorStatements.length - 1]; } } int offset = prevNode != null ? commentMap.getEndOffsetIncludingComments(prevNode) : 0; String source = node.getRawSignature(); int endOffset = skipTrailingBlankLines(source, offset); addToRootEdit(node); ChangeGeneratorWriterVisitor writer = new ChangeGeneratorWriterVisitor(modificationStore, commentMap); IASTNode newNode = null; for (ASTModification modification : modifications) { 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")) //$NON-NLS-1$ writer.newLine(); } if (prevNode != null) { IASTNode nextNode = getNextSiblingOrPreprocessorNode(prevNode); if (nextNode != null && ASTWriter.requireBlankLineInBetween(newNode, nextNode)) { writer.newLine(); } } 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(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(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;) { IASTNode child = getReplacementNode(children[i]); if (child != null) return child; } return null; } private IASTNode getReplacementNode(IASTNode node) { List<ASTModification> modifications = getModifications(node, ModificationKind.REPLACE); if (!modifications.isEmpty()) { node = modifications.get(modifications.size() - 1).getNewNode(); } return node; } private IASTNode getPreviousSiblingNode(IASTNode node) { 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;) { IASTNode sibling = siblings[i]; if (sibling == node) { beforeNode = true; } else if (beforeNode && getReplacementNode(sibling) != null) { return sibling; } } return null; } private IASTNode getPreviousSiblingOrPreprocessorNode(IASTNode node) { int offset = offset(node); IASTTranslationUnit ast = node.getTranslationUnit(); IASTPreprocessorStatement[] preprocessorStatements = ast.getAllPreprocessorStatements(); int low = 0; int high = preprocessorStatements.length; while (low < high) { int mid = (low + high) >>> 1; IASTNode statement = preprocessorStatements[mid]; if (statement.isPartOfTranslationUnitFile() && endOffset(statement) > offset) { high = mid; } else { low = mid + 1; } } IASTNode statement = --low >= 0 ? preprocessorStatements[low] : null; IASTNode originalSibling = getPreviousSiblingNode(node); IASTNode sibling = originalSibling == null ? null : getReplacementNode(originalSibling); if (statement == null || !statement.isPartOfTranslationUnitFile()) { return sibling; } if (sibling == null) { IASTNode parent = node.getParent(); if (offset(parent) >= endOffset(statement)) return null; return statement; } return endOffset(originalSibling) >= endOffset(statement) ? sibling : statement; } private IASTNode getNextSiblingNode(IASTNode node) { 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 (IASTNode sibling : siblings) { if (sibling == node) { beforeNode = true; } else if (beforeNode && getReplacementNode(sibling) != null) { return sibling; } } return null; } private IASTNode getNextSiblingOrPreprocessorNode(IASTNode node) { int endOffset = endOffset(node); IASTTranslationUnit ast = node.getTranslationUnit(); IASTPreprocessorStatement[] preprocessorStatements = ast.getAllPreprocessorStatements(); int low = 0; int high = preprocessorStatements.length; while (low < high) { int mid = (low + high) / 2; IASTNode statement = preprocessorStatements[mid]; if (statement.isPartOfTranslationUnitFile() && offset(statement) > endOffset) { high = mid; } else { low = mid + 1; } } IASTNode statement = high < preprocessorStatements.length ? preprocessorStatements[high] : null; IASTNode originalSibling = getNextSiblingNode(node); IASTNode sibling = originalSibling == null ? null : getReplacementNode(originalSibling); if (statement == null || !statement.isPartOfTranslationUnitFile()) { return sibling; } if (sibling == null) { 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(IASTNode node) { if (!(node instanceof IASTCompositeTypeSpecifier || node instanceof IASTCompoundStatement || node instanceof ICPPASTNamespaceDefinition)) { return null; } String code = node.getRawSignature(); IASTFileLocation location = node.getFileLocation(); int pos = location.getNodeOffset() + location.getNodeLength(); int len = code.endsWith("}") ? 1 : 0; //$NON-NLS-1$ int insertPos = code.length() - len; 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(String text, int startPos) { for (int pos = startPos; --pos >= 0; ) { 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(String text, int startPos) { for (int pos = startPos; --pos >= 0;) { 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(String text, int startPos) { for (int pos = startPos; pos < text.length(); pos++) { 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(String text, char delimiter, int startPos) { for (int pos = startPos; --pos >= 0; ) { 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(String text, char delimiter, int startPos) { for (int pos = startPos; pos < text.length(); pos++) { char c = text.charAt(pos); if (c == delimiter) { return pos + 1; } else if (!Character.isWhitespace(c)) { return startPos; } } return startPos; } private void addToRootEdit(IASTNode modifiedNode) { if (rootEdit == null) { rootEdit = new MultiTextEdit(); } TextEditGroup editGroup = new TextEditGroup(ChangeGeneratorMessages.ChangeGenerator_group); for (List<ASTModification> modifications : getModifications(modifiedNode).values()) { for (ASTModification modification : modifications) { if (modification.getAssociatedEditGroup() != null) { editGroup = modification.getAssociatedEditGroup(); rootEdit.addChildren(editGroup.getTextEdits()); return; } } } } private Map<ModificationKind, List<ASTModification>> getModifications(IASTNode node) { Map<ModificationKind, List<ASTModification>> modifications = classifiedModifications.get(node); if (modifications == null) return Collections.emptyMap(); return modifications; } private List<ASTModification> getModifications(IASTNode node, ModificationKind kind) { Map<ModificationKind, List<ASTModification>> allModifications = getModifications(node); List<ASTModification> modifications = allModifications.get(kind); if (modifications == null) return Collections.emptyList(); return modifications; } private boolean requiresRewrite(IASTNode node) { if (!getModifications(node, ModificationKind.REPLACE).isEmpty()) return true; for (ASTModification modification : getModifications(node, ModificationKind.APPEND_CHILD)) { if (!isAppendable(modification)) return true; } return false; } private boolean isAppendable(ASTModification modification) { if (modification.getKind() != ModificationKind.APPEND_CHILD) return false; IASTNode node = modification.getNewNode(); if (node instanceof ContainerNode) { for (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; } }