/******************************************************************************* * Copyright © 2005, 2013 IBM Corporation 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: * IBM Corporation - initial API and implementation * *******************************************************************************/ package org.eclipse.edt.ide.core.ast.rewrite; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import org.eclipse.edt.compiler.core.IEGLConstants; import org.eclipse.edt.compiler.core.ast.AbstractASTNodeVisitor; import org.eclipse.edt.compiler.core.ast.AbstractASTPartVisitor; import org.eclipse.edt.compiler.core.ast.ClassDataDeclaration; import org.eclipse.edt.compiler.core.ast.DefaultASTVisitor; import org.eclipse.edt.compiler.core.ast.DeleteStatement; import org.eclipse.edt.compiler.core.ast.ExecuteStatement; import org.eclipse.edt.compiler.core.ast.File; import org.eclipse.edt.compiler.core.ast.FromOrToExpressionClause; import org.eclipse.edt.compiler.core.ast.FunctionDataDeclaration; import org.eclipse.edt.compiler.core.ast.GetByKeyStatement; import org.eclipse.edt.compiler.core.ast.IOStatementClauseInfo; import org.eclipse.edt.compiler.core.ast.ImportDeclaration; import org.eclipse.edt.compiler.core.ast.Name; import org.eclipse.edt.compiler.core.ast.NestedFunction; import org.eclipse.edt.compiler.core.ast.NewExpression; import org.eclipse.edt.compiler.core.ast.Node; import org.eclipse.edt.compiler.core.ast.Part; import org.eclipse.edt.compiler.core.ast.ReplaceStatement; import org.eclipse.edt.compiler.core.ast.SetValuesExpression; import org.eclipse.edt.compiler.core.ast.SettingsBlock; import org.eclipse.edt.compiler.core.ast.Statement; import org.eclipse.edt.compiler.core.ast.UsingClause; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.IRegion; 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 com.ibm.icu.util.StringTokenizer; /** * @author Dave Murray */ public class ASTRewrite { private static String TAB = "\t"; private int importsRemovedCount; private boolean importsAdded = false; private AddImportEdit lastAddImportEdit = null; private static abstract class DefaultASTEdit implements ASTEdit { private int sortOrder; protected DefaultASTEdit(int sortOrder) { this.sortOrder = sortOrder; } protected DefaultASTEdit() { this(0); } public int compareTo(Object o) { return ((DefaultASTEdit) o).sortOrder - sortOrder; } public void editAdded(ASTEdit newEdit) { } } private static class AddClassFieldEdit extends DefaultASTEdit { private Part part; private String fieldName; private String fieldType; private String initialValue; private String fieldDeclaration; private int index; public AddClassFieldEdit(Part part, String fieldName, String fieldType, String initialValue, int index) { this.part = part; this.fieldName = fieldName; this.fieldType = fieldType; this.initialValue = initialValue; this.index = index; } private static class OffsetFinder extends AbstractASTPartVisitor { private IDocument document; private Part part; private SettingsBlock lastLeadingSettingsBlock; private ClassDataDeclaration lastClassDataDeclaration; private boolean readingLeadingSettingsBlocks = true; private boolean visitedPart; private int numFieldsVisited = 0; private int index; OffsetFinder(IDocument document, Part part, int index) { this.document = document; this.part = part; this.index = index; } private void visitPart() { if(!visitedPart) { part.accept(this); } } public String getPrefixForNewDataDeclaration() { visitPart(); if(lastClassDataDeclaration == null) { return TAB; } else { try { return getLeadingWhitespaceOnLine(document, lastClassDataDeclaration.getOffset()); } catch(BadLocationException e) { return ""; } } } public int getOffsetForNewDataDeclaration() { visitPart(); if(lastClassDataDeclaration == null) { if(lastLeadingSettingsBlock == null) { if(part.hasSubType()) { return part.getSubType().getOffset() + part.getSubType().getLength(); } else { return part.getName().getOffset() + part.getName().getLength(); } } else { return lastLeadingSettingsBlock.getOffset() + lastLeadingSettingsBlock.getLength(); } } else { return lastClassDataDeclaration.getOffset() + lastClassDataDeclaration.getLength(); } } public void visitPart(Part part) { for(Iterator iter = part.getContents().iterator(); iter.hasNext();) { ((Node) iter.next()).accept(new AbstractASTNodeVisitor() { public boolean visitNode(Node node) { readingLeadingSettingsBlocks = false; return false; } public void endVisitNode(Node node) { } public boolean visit(ClassDataDeclaration classDataDeclaration) { if(index == -1 || numFieldsVisited < index){ lastClassDataDeclaration = classDataDeclaration; } return visitNode(classDataDeclaration); } public void endVisit(ClassDataDeclaration classDataDeclaration){ numFieldsVisited++; } public boolean visit(SettingsBlock settingsBlock) { if(readingLeadingSettingsBlocks) { lastLeadingSettingsBlock = settingsBlock; } return false; } }); visitedPart = true; } } } public TextEdit toTextEdit(IDocument document) throws BadLocationException { final String NL = getLineDelimiter(document); OffsetFinder offsetFinder = new OffsetFinder(document, part, index); StringBuffer insertText = new StringBuffer(); if(fieldDeclaration == null){ insertText.append(NL); insertText.append(offsetFinder.getPrefixForNewDataDeclaration()); insertText.append(fieldName); insertText.append(" "); insertText.append(fieldType); if(initialValue != null) { insertText.append(" = "); insertText.append(initialValue); } insertText.append(";"); }else{ insertText.append(fieldDeclaration); } return new InsertEdit(offsetFinder.getOffsetForNewDataDeclaration(), insertText.toString()); } public boolean isInsertEdit() { return true; } } private static class AddFunctionEdit extends DefaultASTEdit { private Part part; private String functionText; public AddFunctionEdit(Part part, String functionText) { this.part = part; this.functionText = functionText; } private static class OffsetFinder extends AbstractASTPartVisitor { private IDocument document; private Part part; private SettingsBlock lastLeadingSettingsBlock; private ClassDataDeclaration lastClassDataDeclaration; private NestedFunction lastNestedFunction; private boolean readingLeadingSettingsBlocks = true; private boolean visitedPart; OffsetFinder(IDocument document, Part part) { this.document = document; this.part = part; } private void visitPart() { if(!visitedPart) { part.accept(this); } } public String getPrefixForNewFunctionDeclaration() { visitPart(); if(lastNestedFunction == null) { if(lastClassDataDeclaration == null) { return TAB; } else { try { return getLeadingWhitespaceOnLine(document, lastClassDataDeclaration.getOffset()); } catch(BadLocationException e) { return ""; } } } else { try { return getLeadingWhitespaceOnLine(document, lastNestedFunction.getOffset()); } catch(BadLocationException e) { return ""; } } } public int getOffsetForNewDataDeclaration() { visitPart(); if(lastNestedFunction == null) { if(lastClassDataDeclaration == null) { if(lastLeadingSettingsBlock == null) { if(part.hasSubType()) { return part.getSubType().getOffset() + part.getSubType().getLength(); } else { return part.getName().getOffset() + part.getName().getLength(); } } else { return lastLeadingSettingsBlock.getOffset() + lastLeadingSettingsBlock.getLength(); } } else { return lastClassDataDeclaration.getOffset() + lastClassDataDeclaration.getLength(); } } else { return lastNestedFunction.getOffset() + lastNestedFunction.getLength(); } } public void visitPart(Part part) { for(Iterator iter = part.getContents().iterator(); iter.hasNext();) { ((Node) iter.next()).accept(new AbstractASTNodeVisitor() { public boolean visitNode(Node node) { readingLeadingSettingsBlocks = false; return false; } public void endVisitNode(Node node) { } public boolean visit(ClassDataDeclaration classDataDeclaration) { lastClassDataDeclaration = classDataDeclaration; return visitNode(classDataDeclaration); } public boolean visit(NestedFunction nestedFunction) { lastNestedFunction = nestedFunction; return visitNode(nestedFunction); } public boolean visit(SettingsBlock settingsBlock) { if(readingLeadingSettingsBlocks) { lastLeadingSettingsBlock = settingsBlock; } return false; } }); visitedPart = true; } } } public TextEdit toTextEdit(IDocument document) throws BadLocationException { final String NL = getLineDelimiter(document); OffsetFinder offsetFinder = new OffsetFinder(document, part); StringBuffer insertText = new StringBuffer(); String prefix = offsetFinder.getPrefixForNewFunctionDeclaration(); insertText.append(NL); insertText.append(prefix); insertText.append(NL); insertText.append(prefix); insertText.append(functionText.replaceAll(NL, NL + prefix)); return new InsertEdit(offsetFinder.getOffsetForNewDataDeclaration(), insertText.toString()); } public boolean isInsertEdit() { return true; } } private class AddImportEdit extends DefaultASTEdit { private boolean isOnDemand; private String packageOrPartName; private File file; private boolean isFirstNewImport = true; private Comparator importComparator; public AddImportEdit(File file, String packageOrPartName, boolean isOnDemand, Comparator importComparator) { super(1); this.file = file; this.packageOrPartName = packageOrPartName; this.isOnDemand = isOnDemand; List importDeclarations = file.getImportDeclarations(); this.isFirstNewImport = (importDeclarations.isEmpty() || importsRemovedCount == importDeclarations.size()) && !importsAdded; this.importComparator = importComparator; } public TextEdit toTextEdit(IDocument document) throws BadLocationException { final String NL = getLineDelimiter(document); StringBuffer textToInsert = new StringBuffer(); textToInsert.append(IEGLConstants.KEYWORD_IMPORT); textToInsert.append(" "); textToInsert.append(packageOrPartName); if(isOnDemand) { if(packageOrPartName.length()>0) textToInsert.append("."); textToInsert.append("*"); } textToInsert.append(";"); String prefix = new String(); String suffix = new String(); List importDeclarationsInFile = file.getImportDeclarations(); if(isFirstNewImport || importDeclarationsInFile.isEmpty() || importsRemovedCount == importDeclarationsInFile.size()) { if(file.hasPackageDeclaration()) { prefix = isFirstNewImport ? NL + NL : NL; return new InsertEdit( file.getPackageDeclaration().getOffset() + file.getPackageDeclaration().getLength(), prefix + textToInsert + suffix); } else { prefix = isFirstNewImport ? "" : NL; try { if(lastAddImportEdit == this && document.getLineInformation(0).getLength() != 0 && importDeclarationsInFile.isEmpty()) { suffix = NL + NL; } } catch (BadLocationException e) { throw new RuntimeException(e); } return new InsertEdit(0, prefix + textToInsert + suffix); } } else { ImportDeclaration importBeforeDecl = getInsertBeforeDecl(packageOrPartName + (isOnDemand ? ".*" : "")); if(importBeforeDecl == null) { prefix = NL; ImportDeclaration lastImportDecl = (ImportDeclaration) importDeclarationsInFile.get(importDeclarationsInFile.size()-1); return new InsertEdit( lastImportDecl.getOffset() + lastImportDecl.getLength(), prefix + textToInsert + suffix); } else { prefix = ""; suffix = NL; return new InsertEdit( importBeforeDecl.getOffset(), prefix + textToInsert + suffix); } } } private ImportDeclaration getInsertBeforeDecl(String importText) { if(importComparator != null) { List importDeclarationsInFile = file.getImportDeclarations(); for(Iterator iter = importDeclarationsInFile.iterator(); iter.hasNext();) { ImportDeclaration next = (ImportDeclaration) iter.next(); String nextName = next.getName().getCanonicalName() + (next.isOnDemand() ? ".*" : ""); if(importComparator.compare(nextName, importText) > 0) { return next; } } } return null; } public boolean isInsertEdit() { return true; } } private class AddPartEdit extends DefaultASTEdit { private File file; private Object partText; private boolean addTrailingSpace; private boolean addLeadingSpace; public AddPartEdit(File file, String partText) { this.file = file; this.partText = partText; } public TextEdit toTextEdit(IDocument document) throws BadLocationException { final String NL = getLineDelimiter(document); List partDeclarationsInFile = file.getParts(); String trailingSpace = addTrailingSpace ? NL + NL : new String(); String leadingSpace; if(addLeadingSpace) { if(file.getImportDeclarations().isEmpty() && importsAdded) { leadingSpace = NL + NL; } else { leadingSpace = NL; } } else { leadingSpace = new String(); } if(partDeclarationsInFile == Collections.EMPTY_LIST) { List importDeclarationsInFile = file.getImportDeclarations(); if(importDeclarationsInFile == Collections.EMPTY_LIST) { if(file.hasPackageDeclaration()) { return new InsertEdit( file.getPackageDeclaration().getOffset() + file.getPackageDeclaration().getLength(), NL + NL + partText); } else { return new InsertEdit(0, leadingSpace + partText.toString() + trailingSpace); } } else { ImportDeclaration lastImportDecl = (ImportDeclaration) importDeclarationsInFile.get(importDeclarationsInFile.size()-1); return new InsertEdit( lastImportDecl.getOffset() + lastImportDecl.getLength(), NL + NL + partText); } } else { Part lastPart = (Part) partDeclarationsInFile.get(partDeclarationsInFile.size()-1); return new InsertEdit( lastPart.getOffset() + lastPart.getLength(), NL + NL + partText); } } public boolean isInsertEdit() { return true; } public void editAdded(ASTEdit newEdit) { if(newEdit.isInsertEdit() && newEdit.compareTo(this) >= 0) { addTrailingSpace = true; } else { addLeadingSpace = true; } } } private static class AddPackageEdit extends DefaultASTEdit { private String packageName; public AddPackageEdit(String packageName) { this.packageName = packageName; } public TextEdit toTextEdit(IDocument document) throws BadLocationException { final String NL = getLineDelimiter(document); StringBuffer textToInsert = new StringBuffer(); textToInsert.append(IEGLConstants.KEYWORD_PACKAGE); textToInsert.append(" "); textToInsert.append(packageName); textToInsert.append(";"); return new InsertEdit(0, textToInsert.toString() + NL + NL); } public boolean isInsertEdit() { return true; } } private static interface SettingsBlockContainer { int getStartOffset(); boolean hasSettingsBlock(); boolean isSettingsBlockRemovable(); SettingsBlock getSettingsBlock(); int getOffsetToInsertNewSettingsBlock(); } private static class SetValuesExpressionSettingsBlockContainer implements SettingsBlockContainer { private SetValuesExpression setValuesExpr; public SetValuesExpressionSettingsBlockContainer(SetValuesExpression setValuesExpr) { this.setValuesExpr = setValuesExpr; } public int getStartOffset() { return setValuesExpr.getOffset(); } public boolean hasSettingsBlock() { return true; } public SettingsBlock getSettingsBlock() { return setValuesExpr.getSettingsBlock(); } public int getOffsetToInsertNewSettingsBlock() { return setValuesExpr.getOffset() + setValuesExpr.getLength(); } public boolean isSettingsBlockRemovable() { return false; } } private static class NewExpressionSettingsBlockContainer implements SettingsBlockContainer { private NewExpression newExpr; public NewExpressionSettingsBlockContainer(NewExpression newExpr) { this.newExpr = newExpr; } public int getStartOffset() { return newExpr.getOffset(); } public boolean hasSettingsBlock() { return newExpr.hasSettingsBlock(); } public SettingsBlock getSettingsBlock() { return newExpr.getSettingsBlock(); } public int getOffsetToInsertNewSettingsBlock() { return newExpr.getOffset() + newExpr.getLength(); } public boolean isSettingsBlockRemovable() { return true; } } private static class FunctionDataDeclarationSettingsBlockContainer implements SettingsBlockContainer { private FunctionDataDeclaration functionDataDecl; public FunctionDataDeclarationSettingsBlockContainer(FunctionDataDeclaration functionDataDecl) { this.functionDataDecl = functionDataDecl; } public int getStartOffset() { return functionDataDecl.getOffset(); } public boolean hasSettingsBlock() { return functionDataDecl.hasSettingsBlock(); } public SettingsBlock getSettingsBlock() { return functionDataDecl.getSettingsBlockOpt(); } public int getOffsetToInsertNewSettingsBlock() { return functionDataDecl.getType().getOffset() + functionDataDecl.getType().getLength(); } public boolean isSettingsBlockRemovable() { return false; // If the type of this field is a reference type, we cannot remove the {} } } private static class ClassDataDeclarationSettingsBlockContainer implements SettingsBlockContainer { private ClassDataDeclaration classDataDecl; public ClassDataDeclarationSettingsBlockContainer(ClassDataDeclaration classDataDecl) { this.classDataDecl = classDataDecl; } public int getStartOffset() { return classDataDecl.getOffset(); } public boolean hasSettingsBlock() { return classDataDecl.hasSettingsBlock(); } public SettingsBlock getSettingsBlock() { return classDataDecl.getSettingsBlockOpt(); } public int getOffsetToInsertNewSettingsBlock() { return classDataDecl.getType().getOffset() + classDataDecl.getType().getLength(); } public boolean isSettingsBlockRemovable() { return false; // If the type of this field is a reference type, we cannot remove the {} } } private static class AddSimpleSettingsEdit extends DefaultASTEdit { private SettingsBlockContainer blockContainer; private String[] settingNames; private String[] settingValues; public AddSimpleSettingsEdit(SettingsBlockContainer blockContainer, String[] settingNames, String[] settingValues) { this.blockContainer = blockContainer; this.settingNames = settingNames; this.settingValues = settingValues; } public TextEdit toTextEdit(IDocument document) throws BadLocationException { final String NL = getLineDelimiter(document); String dataDeclPrefix; int insertPosition; StringBuffer textToInsert = new StringBuffer(); try { dataDeclPrefix = getLeadingWhitespaceOnLine(document, blockContainer.getStartOffset()); } catch(BadLocationException e) { dataDeclPrefix = ""; } if(blockContainer.hasSettingsBlock()) { SettingsBlock settingsBlock = (SettingsBlock)blockContainer.getSettingsBlock(); if(settingsBlock.getSettings().isEmpty()) { insertPosition = settingsBlock.getOffset()+1; textToInsert.append(NL); textToInsert.append(getSettingsText(dataDeclPrefix+TAB, NL)); textToInsert.append(NL); textToInsert.append(dataDeclPrefix); } else { Node lastSetting = (Node) settingsBlock.getSettings().get(settingsBlock.getSettings().size()-1); try { dataDeclPrefix = getLeadingWhitespaceOnLine(document, lastSetting.getOffset()); } catch(BadLocationException e) { dataDeclPrefix = ""; } int lineOfSettingsBlock = document.getLineOfOffset(settingsBlock.getOffset()); int lineOfLastSetting = document.getLineOfOffset(lastSetting.getOffset()); if ( lineOfSettingsBlock == lineOfLastSetting ) { dataDeclPrefix += TAB; } insertPosition = lastSetting.getOffset() + lastSetting.getLength(); textToInsert.append(","); textToInsert.append(NL); textToInsert.append(getSettingsText(dataDeclPrefix, NL)); } } else { insertPosition = blockContainer.getOffsetToInsertNewSettingsBlock(); textToInsert.append(" {"); textToInsert.append(NL); textToInsert.append(getSettingsText(dataDeclPrefix+TAB, NL)); textToInsert.append(NL); textToInsert.append(dataDeclPrefix); textToInsert.append("}"); } return new InsertEdit(insertPosition, textToInsert.toString()); } private String getSettingsText(String prefix, final String NL) { StringBuffer settingsText = new StringBuffer(); for(int i = 0; i < settingNames.length; i++) { settingsText.append(prefix); settingsText.append(settingNames[i]); settingsText.append(" = "); settingsText.append(settingValues[i]); if(i != settingNames.length-1) { settingsText.append(","); settingsText.append(NL); } } return settingsText.toString(); } public boolean isInsertEdit() { return true; } } private static class InsertStatementClauseEdit extends DefaultASTEdit { private static class NoClauseOrIOTargetOffsetFinder extends DefaultASTVisitor { int offset = -1; public boolean visit(ExecuteStatement executeStatement) { offset = executeStatement.getOffset() + IEGLConstants.KEYWORD_EXECUTE.length(); return false; } } Statement statement; IOStatementClauseInfo clauseInfo; Object clauseContent; InsertStatementClauseEdit(Statement statement, IOStatementClauseInfo clauseInfo, Object clauseContent) { this.statement = statement; this.clauseInfo = clauseInfo; this.clauseContent = clauseContent; } public TextEdit toTextEdit(IDocument document) throws BadLocationException { final String NL = getLineDelimiter(document); try { StringBuffer sb = new StringBuffer(); int offset = -1; if(statement.getIOClauses().isEmpty()) { if(statement.getIOObjects().isEmpty()) { NoClauseOrIOTargetOffsetFinder offsetFinder = new NoClauseOrIOTargetOffsetFinder(); statement.accept(offsetFinder); offset = offsetFinder.offset; if(offset == -1) { throw new RuntimeException("Can't locate offset to insert clause for " + statement.getClass().getName()); } } else { Node lastTarget = (Node) statement.getIOObjects().get(statement.getIOObjects().size()-1); offset = lastTarget.getOffset()+lastTarget.getLength(); } } else { Node lastClause = (Node) statement.getIOClauses().get(statement.getIOClauses().size()-1); offset = lastClause.getOffset()+lastClause.getLength(); } String prefix = getLeadingWhitespaceOnLine(document, statement.getOffset()); if(IOStatementClauseInfo.INLINE_STMT_VALUE == clauseInfo.getContentType()) { sb.append(" "); sb.append(clauseInfo.getClauseKeyword()); sb.append(NL); sb.append(prefix); sb.append(TAB); } else { sb.append(NL); sb.append(prefix); sb.append(TAB); sb.append(clauseInfo.getClauseKeyword()); sb.append(" "); } if(clauseInfo.getContentPrefix() != null) { sb.append(clauseInfo.getContentPrefix()); sb.append(NL); switch(clauseInfo.getContentType()) { case IOStatementClauseInfo.INLINE_STMT_VALUE: appendLines(sb, prefix + TAB + TAB, (String) clauseContent, NL); break; } sb.append(NL); sb.append(prefix); sb.append(TAB); sb.append(clauseInfo.getContentSuffix()); } else { if(IOStatementClauseInfo.NO_VALUE != clauseInfo.getContentType()) { switch(clauseInfo.getContentType()) { case IOStatementClauseInfo.INLINE_STMT_VALUE: case IOStatementClauseInfo.IDENTIFIER_VALUE: sb.append((String) clauseContent); break; case IOStatementClauseInfo.LIST_VALUE: for(Iterator iter = ((List) clauseContent).iterator(); iter.hasNext();) { sb.append((String) iter.next()); if(iter.hasNext()) { sb.append(", "); } } break; } } } InsertEdit result = new InsertEdit(offset, sb.toString()); return result; } catch(BadLocationException e) { throw new RuntimeException(e); } } public boolean isInsertEdit() { return true; } } private static class ComplementStatementClauseEdit extends DefaultASTEdit{ private static class NoClauseOrIOTargetOffsetFinder extends DefaultASTVisitor { int offset = -1; int optionSize = -1; public boolean visit(ExecuteStatement executeStatement) { offset = executeStatement.getOffset() + IEGLConstants.KEYWORD_EXECUTE.length(); return false; } public boolean visit(DeleteStatement deleteStatement) { if(deleteStatement.getOptions() != null && deleteStatement.getOptions().size() > 0) { Node usingClause = (Node)deleteStatement.getOptions().get(0); if(usingClause != null && usingClause instanceof UsingClause) { offset = usingClause.getOffset() + usingClause.getLength(); } } if(offset == -1) { FromOrToExpressionClause dataSourceExpression = deleteStatement.getDataSource(); offset = dataSourceExpression.getOffset() + dataSourceExpression.getLength(); } return false; } public boolean visit(GetByKeyStatement getByKeyStatement) { optionSize = getByKeyStatement.getGetByKeyOptions().size(); if(optionSize > 0) { Node lastClause = (Node)getByKeyStatement.getGetByKeyOptions().get(optionSize - 1); offset =lastClause.getOffset() + lastClause.getLength(); } return false; } public boolean visit(ReplaceStatement replaceStatement) { optionSize = replaceStatement.getReplaceOptions().size(); if(optionSize > 0) { Node lastClause = (Node)replaceStatement.getReplaceOptions().get(optionSize - 1); offset =lastClause.getOffset() + lastClause.getLength(); } return false; } } Statement statement; String clauseContent; public ComplementStatementClauseEdit(Statement statement, String clauseContent){ this.statement = statement; this.clauseContent = clauseContent; } @Override public TextEdit toTextEdit(IDocument document) throws BadLocationException { int offset = -1; if(statement.getIOClauses().isEmpty()) { NoClauseOrIOTargetOffsetFinder offsetFinder = new NoClauseOrIOTargetOffsetFinder(); statement.accept(offsetFinder); offset = offsetFinder.offset; if(offset == -1 && !statement.getIOObjects().isEmpty()) { Node lastTarget = (Node) statement.getIOObjects().get(statement.getIOObjects().size()-1); offset = lastTarget.getOffset() + lastTarget.getLength(); } if(offset == -1) { throw new RuntimeException("Can't locate offset to insert clause for " + statement.getClass().getName()); } } else { Node lastClause = (Node) statement.getIOClauses().get(statement.getIOClauses().size()-1); offset = lastClause.getOffset() + lastClause.getLength(); } if(-1 != offset){ InsertEdit result = new InsertEdit(offset, clauseContent); return result; } return(null); } @Override public boolean isInsertEdit() { return true; } } private static class RemoveNodeEdit extends DefaultASTEdit { Node nodeToRemove; RemoveNodeEdit(Node nodeToRemove) { this.nodeToRemove = nodeToRemove; } public TextEdit toTextEdit(IDocument document) { try { int leadingWhitespace = precedingWhitespaceCharacters(document, nodeToRemove).length(); int lastNonWhitespacePos = nodeToRemove.getOffset() - leadingWhitespace; return new DeleteEdit(lastNonWhitespacePos, nodeToRemove.getLength() + leadingWhitespace); } catch(BadLocationException e) { throw new RuntimeException(e); } } public boolean isInsertEdit() { return false; } } private static class RemoveSettingsEdit extends DefaultASTEdit { private SettingsBlockContainer blockContainer; private Node[] settings; public RemoveSettingsEdit(SettingsBlockContainer blockContainer, Node[] settings) { this.blockContainer = blockContainer; this.settings = settings; } public TextEdit toTextEdit(IDocument document) { TextEdit rootEdit = new MultiTextEdit(); if(blockContainer.hasSettingsBlock() && !blockContainer.getSettingsBlock().getSettings().isEmpty()) { Node firstSetting = (Node) blockContainer.getSettingsBlock().getSettings().get(0); if(settings.length == blockContainer.getSettingsBlock().getSettings().size()) { if(blockContainer.isSettingsBlockRemovable()){ return new RemoveNodeEdit(blockContainer.getSettingsBlock()).toTextEdit(document); }else{ SettingsBlock settingsBlock = blockContainer.getSettingsBlock(); rootEdit.addChild(new DeleteEdit(settingsBlock.getOffset() + 1, settingsBlock.getLength() - 2)); } } else { for(int i = 0; i < settings.length; i++) { if(settings[i] == firstSetting) { int endDelete = ((Node) blockContainer.getSettingsBlock().getSettings().get(1)).getOffset(); rootEdit.addChild(new DeleteEdit(settings[i].getOffset(), endDelete-settings[i].getOffset())); } else { try { int startDelete = offsetOfClosestCharToLeft(document, ',', settings[i].getOffset()); rootEdit.addChild(new DeleteEdit(startDelete, settings[i].getLength()+settings[i].getOffset()-startDelete)); } catch (BadLocationException e) { throw new RuntimeException(e); } } } } } return rootEdit; } public boolean isInsertEdit() { return false; } } private static class RemoveTextEdit extends DefaultASTEdit { private int offset; private int length; RemoveTextEdit(int offset, int length) { this.offset = offset; this.length = length; } public TextEdit toTextEdit(IDocument document) { return new DeleteEdit(offset, length); } public boolean isInsertEdit() { return false; } } private static class SetTextEdit extends DefaultASTEdit { Node node; String newText; SetTextEdit(Node node, String newText) { this.node = node; this.newText = newText; } public TextEdit toTextEdit(IDocument document) { return new ReplaceEdit(node.getOffset(), node.getLength(), newText); } public boolean isInsertEdit() { return false; } } private File fileAST; private Map edits = new HashMap(); private ASTRewrite(File fileAST) { this.fileAST = fileAST; } private static String getLineDelimiter(IDocument document) throws BadLocationException { String lineDelimiter = document.getLineDelimiter(0); return lineDelimiter == null ? System.getProperty("line.separator") : lineDelimiter; } public static ASTRewrite create(File fileAST) { return new ASTRewrite(fileAST); } private void addEdit(Node node, ASTEdit edit) { List editsForNode = (List) edits.get(node); if(editsForNode == null) { editsForNode = new ArrayList(); edits.put(node, editsForNode); } else { for(Iterator iter = editsForNode.iterator(); iter.hasNext();) { ((ASTEdit) iter.next()).editAdded(edit); } } editsForNode.add(edit); Collections.sort(editsForNode); } public void addClassField(Part part, String fieldName, String fieldType) { addClassFieldAtIndex(part, fieldName, fieldType, null, -1); } public void addClassField(Part part, String fieldName, String fieldType, String initialValue) { addClassFieldAtIndex(part, fieldName, fieldType, initialValue, -1); } public void addClassFieldAtIndex(Part part, String fieldName, String fieldType, int index) { addClassFieldAtIndex(part, fieldName, fieldType, null, index); } public void addClassFieldAtIndex(Part part, String fieldName, String fieldType, String initialValue, int index) { addEdit(part, new AddClassFieldEdit(part, fieldName, fieldType, initialValue, index)); } public void addFunction(Part part, String functionText) { addEdit(part, new AddFunctionEdit(part, functionText)); } public void addImport(File file, String packageOrPartName, boolean isOnDemand) { addImport(file, packageOrPartName, isOnDemand, null); } public void addImport(File file, String packageOrPartName, boolean isOnDemand, Comparator importComparator) { AddImportEdit addImportEdit = new AddImportEdit(file, packageOrPartName, isOnDemand, importComparator); addEdit(file, addImportEdit); importsAdded = true; lastAddImportEdit = addImportEdit; } public void addIOStatementClause(Statement statement, IOStatementClauseInfo clauseInfo, Object clauseContent) { addEdit(statement, new InsertStatementClauseEdit(statement, clauseInfo, clauseContent)); } public void completeIOStatement(Statement statement, String clauseContent){ addEdit(statement, new ComplementStatementClauseEdit(statement, clauseContent)); } public void addPackage(File file, String packageName) { addEdit(file, new AddPackageEdit(packageName)); } public void addPart(File file, String partText) { addEdit(file, new AddPartEdit(file, partText)); } public void addSimpleSetting(FunctionDataDeclaration dataDecl, String settingName, String settingValue) { addSimpleSettings(dataDecl, new String[] {settingName}, new String[] {settingValue}); } public void addSimpleSettings(FunctionDataDeclaration dataDecl, String[] settingNames, String[] settingValues) { addEdit(dataDecl, new AddSimpleSettingsEdit(new FunctionDataDeclarationSettingsBlockContainer(dataDecl), settingNames, settingValues)); } public void addSimpleSetting(ClassDataDeclaration dataDecl, String settingName, String settingValue) { addSimpleSettings(dataDecl, new String[] {settingName}, new String[] {settingValue}); } public void addSimpleSettings(ClassDataDeclaration dataDecl, String[] settingNames, String[] settingValues) { addEdit(dataDecl, new AddSimpleSettingsEdit(new ClassDataDeclarationSettingsBlockContainer(dataDecl), settingNames, settingValues)); } public void addSimpleSetting(NewExpression newExpr, String settingName, String settingValue) { addSimpleSettings(newExpr, new String[] {settingName}, new String[] {settingValue}); } public void addSimpleSettings(NewExpression newExpr, String[] settingNames, String[] settingValues) { addEdit(newExpr, new AddSimpleSettingsEdit(new NewExpressionSettingsBlockContainer(newExpr), settingNames, settingValues)); } public void addSimpleSetting(SetValuesExpression setValuesExpression, String settingName, String settingValue) { addSimpleSettings(setValuesExpression, new String[] {settingName}, new String[] {settingValue}); } public void addSimpleSettings(SetValuesExpression setValuesExpression, String[] settingNames, String[] settingValues) { addEdit(setValuesExpression, new AddSimpleSettingsEdit(new SetValuesExpressionSettingsBlockContainer(setValuesExpression), settingNames, settingValues)); } public void removeNode(Node node) { addEdit(node, new RemoveNodeEdit(node)); node.accept(new DefaultASTVisitor() { public boolean visit(ImportDeclaration importDeclaration) { importsRemovedCount += 1; return false; } }); } public void removeSetting(FunctionDataDeclaration dataDecl, Node setting) { removeSettings(dataDecl, new Node[] {setting}); } public void removeSettings(FunctionDataDeclaration dataDecl, Node[] settings) { addEdit(dataDecl, new RemoveSettingsEdit(new FunctionDataDeclarationSettingsBlockContainer(dataDecl), settings)); } public void removeSetting(ClassDataDeclaration dataDecl, Node setting) { removeSettings(dataDecl, new Node[] {setting}); } public void removeSettings(ClassDataDeclaration dataDecl, Node[] settings) { addEdit(dataDecl, new RemoveSettingsEdit(new ClassDataDeclarationSettingsBlockContainer(dataDecl), settings)); } public void removeSetting(NewExpression newExpr, Node setting) { removeSettings(newExpr, new Node[] {setting}); } public void removeSettings(NewExpression newExpr, Node[] settings) { addEdit(newExpr, new RemoveSettingsEdit(new NewExpressionSettingsBlockContainer(newExpr), settings)); } public void removeSetting(SetValuesExpression newExpr, Node setting) { removeSettings(newExpr, new Node[] {setting}); } public void removeSettings(SetValuesExpression newExpr, Node[] settings) { addEdit(newExpr, new RemoveSettingsEdit(new SetValuesExpressionSettingsBlockContainer(newExpr), settings)); } public void removeText(int offset, int length) { addEdit(fileAST, new RemoveTextEdit(offset, length)); } public void rename(Name nameNode, String newName) { setText(nameNode, newName); } public void setText(Node node, String newText) { addEdit(node, new SetTextEdit(node, newText)); } public TextEdit rewriteAST(final IDocument document) { TextEdit rootEdit = new MultiTextEdit(); fileAST.accept(new ASTRewriteVisitor(document, rootEdit, edits)); return rootEdit; } private static String getLeadingWhitespaceOnLine(IDocument document, int offset) throws BadLocationException { int lineOfOffset = document.getLineOfOffset(offset); int offsetOfLine = document.getLineOffset(lineOfOffset); String leadingText = document.get(offsetOfLine, offset-offsetOfLine); //If there are any non-whitespace characters, return the substring //that precedes them. return leadingText.replaceFirst("\\S.*", ""); } private static int offsetOfClosestCharToLeft(IDocument document, char ch, int startOffset) throws BadLocationException { int currentOffset = startOffset-1; while(currentOffset != 0) { if(document.getChar(currentOffset) == ch) { return currentOffset; } currentOffset -= 1; } return startOffset; } private static String precedingWhitespaceCharacters(IDocument document, Node node) throws BadLocationException { int offset = node.getOffset(); StringBuffer sb = new StringBuffer(); offset = offset - 1; boolean inBlockComent = false; while(offset-sb.length() > -1) { char ch = document.getChar(offset-sb.length()); if(!Character.isWhitespace(ch)) { boolean characterIsWhitespace = false; if('/' == ch && isPartOrChildOfPart(node)) { if(!inBlockComent) { ch = document.getChar(offset-sb.length()-1); if('*' == ch) { inBlockComent = true; characterIsWhitespace = true; sb.append('/'); } } } else if('*' == ch) { if(inBlockComent) { ch = document.getChar(offset-sb.length()-1); if('/' == ch) { inBlockComent = false; characterIsWhitespace = true; sb.append('*'); } } } if(!characterIsWhitespace && !inBlockComent) { int lineNum = document.getLineOfOffset(offset-sb.length()); IRegion lineInfo = document.getLineInformation(lineNum); String lineContents = document.get(lineInfo.getOffset(), offset-sb.length()-lineInfo.getOffset()); int indexOfSingleLineCommentStart = lineContents.indexOf("//"); if(indexOfSingleLineCommentStart != -1) { sb.append(ch); sb.append(new StringBuffer(lineContents.substring(indexOfSingleLineCommentStart)).reverse()); continue; } break; } } sb.append(ch); } return sb.reverse().toString(); } private static boolean isPartOrChildOfPart(Node node) { return node instanceof Part || node.getParent() instanceof Part; } private static void appendLines(StringBuffer sb, String prefix, String str, String NL) { StringTokenizer st = new StringTokenizer(str, NL); while(st.hasMoreTokens()) { sb.append(prefix); sb.append(st.nextToken()); if(st.hasMoreTokens()) { sb.append(NL); } } } }