blob: c533950eb9e03246439edbc49661959f33cbf38a [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2002 International Business Machines Corp. and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Common Public License v0.5
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/cpl-v05.html
*
* Contributors:
* IBM Corporation - initial API and implementation
******************************************************************************/
package org.eclipse.jdt.internal.corext.dom;
import java.util.List;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.compiler.IScanner;
import org.eclipse.jdt.core.compiler.ITerminalSymbols;
import org.eclipse.jdt.core.compiler.InvalidInputException;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.Block;
import org.eclipse.jdt.core.dom.Expression;
import org.eclipse.jdt.core.dom.FieldDeclaration;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.Name;
import org.eclipse.jdt.core.dom.ReturnStatement;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.core.dom.Statement;
import org.eclipse.jdt.core.dom.Type;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.eclipse.jdt.internal.corext.codemanipulation.StubUtility;
import org.eclipse.jdt.internal.corext.refactoring.changes.CompilationUnitChange;
import org.eclipse.jdt.internal.corext.textmanipulation.SimpleTextEdit;
import org.eclipse.jdt.internal.corext.textmanipulation.TextBuffer;
import org.eclipse.jdt.internal.corext.textmanipulation.TextRegion;
import org.eclipse.jdt.internal.corext.util.CodeFormatterUtil;
import org.eclipse.jdt.internal.corext.util.Strings;
import org.eclipse.jdt.internal.ui.JavaPlugin;
import org.eclipse.jdt.internal.ui.text.correction.ASTResolving;
/**
*/
public class ASTRewriteAnalyzer extends ASTVisitor {
private static final String KEY= "ASTChangeData";
public static void markAsInserted(ASTNode node) {
node.setProperty(KEY, new ASTInsert());
}
public static void markAsReplaced(ASTNode node, ASTNode modifiedNode) {
ASTReplace replace= new ASTReplace();
replace.modifiedNode= modifiedNode;
node.setProperty(KEY, replace);
}
public static void markFlagsChanged(ASTNode node, int changedModifiers, boolean invertType) {
ASTModifiedFlags modifiedFlags= new ASTModifiedFlags();
modifiedFlags.changedModifiers= changedModifiers;
modifiedFlags.invertType= invertType;
node.setProperty(KEY, modifiedFlags);
}
public static boolean isInserted(ASTNode node) {
return node.getProperty(KEY) instanceof ASTInsert;
}
public static boolean isReplaced(ASTNode node) {
return node.getProperty(KEY) instanceof ASTReplace;
}
public static ASTModifiedFlags getModifiedFlags(ASTNode node) {
Object info= node.getProperty(KEY);
if (info instanceof ASTModifiedFlags) {
return (ASTModifiedFlags) info;
}
return null;
}
public static ASTNode getReplacingNode(ASTNode node) {
return ((ASTReplace) node.getProperty(KEY)).modifiedNode;
}
private static final class ASTReplace {
public ASTNode modifiedNode;
}
private static final class ASTInsert {
}
private static final class ASTModifiedFlags {
public int changedModifiers;
public boolean invertType;
}
private CompilationUnitChange fChange;
private TextBuffer fTextBuffer;
private final int[] MODIFIERS= new int[] { ITerminalSymbols.TokenNamepublic, ITerminalSymbols.TokenNameprotected, ITerminalSymbols.TokenNameprivate,
ITerminalSymbols.TokenNamestatic, ITerminalSymbols.TokenNamefinal, ITerminalSymbols.TokenNameabstract, ITerminalSymbols.TokenNamenative,
ITerminalSymbols.TokenNamevolatile, ITerminalSymbols.TokenNamestrictfp, ITerminalSymbols.TokenNametransient, ITerminalSymbols.TokenNamesynchronized };
/**
* Constructor for ASTChangeAnalyzer.
*/
public ASTRewriteAnalyzer(TextBuffer textBuffer, CompilationUnitChange change) {
fTextBuffer= textBuffer;
fChange= change;
}
/* (non-Javadoc)
* @see org.eclipse.jdt.core.dom.ASTVisitor#visit(ReturnStatement)
*/
public boolean visit(ReturnStatement node) {
Expression expression= node.getExpression();
if (expression != null) {
if (isReplaced(expression)) {
replaceNode(expression, (Expression) getReplacingNode(expression));
} else if (isInserted(expression)) {
insertNode(expression, node.getStartPosition(), new int[] { ITerminalSymbols.TokenNamereturn });
} else {
expression.accept(this);
}
}
return false;
}
private void insertNode(ASTNode inserted, int offset, int[] prevTokens) {
try {
IScanner scanner= ASTResolving.createScanner(fChange.getCompilationUnit(), offset);
ASTResolving.overreadToken(scanner, prevTokens);
int pos= scanner.getCurrentTokenStartPosition();
String str= generateSource(inserted, 0);
if (Character.isLetterOrDigit(fTextBuffer.getChar(pos))) {
str= str + " ";
}
if (pos > 0 && Character.isLetterOrDigit(fTextBuffer.getChar(pos - 1))) {
str= " " + str;
}
fChange.addTextEdit("Add Node", SimpleTextEdit.createInsert(pos, str));
} catch (JavaModelException e) {
JavaPlugin.log(e);
} catch (InvalidInputException e) {
JavaPlugin.log(e);
}
}
private void replaceNode(ASTNode old, ASTNode modified) {
if (modified == null) {
int startPos= old.getStartPosition();
int endPos= startPos + old.getLength();
TextRegion lineStart= fTextBuffer.getLineInformationOfOffset(startPos);
while (startPos > lineStart.getOffset() && Character.isWhitespace(fTextBuffer.getChar(startPos - 1))) {
startPos--;
}
fChange.addTextEdit("Remove Node", SimpleTextEdit.createReplace(startPos, endPos - startPos, ""));
} else {
String str= generateSource(modified, 0);
fChange.addTextEdit("Replace Node", SimpleTextEdit.createReplace(old.getStartPosition(), old.getLength(), str));
}
}
/* (non-Javadoc)
* @see org.eclipse.jdt.core.dom.ASTVisitor#visit(Block)
*/
public boolean visit(Block block) {
List list= block.statements();
Statement last= null;
for (int i= 0; i < list.size(); i++) {
Statement elem= (Statement) list.get(i);
if (isReplaced(elem)) {
replaceStatement(elem, (Statement) getReplacingNode(elem));
last= elem;
} else if (isInserted(elem)) {
insertStatement(elem, last, block.getStartPosition(), false);
} else {
elem.accept(this);
last= elem;
}
}
return false;
}
/* (non-Javadoc)
* @see org.eclipse.jdt.core.dom.ASTVisitor#visit(MethodDeclaration)
*/
public boolean visit(MethodDeclaration methodDecl) {
Type returnType= methodDecl.getReturnType();
if (isReplaced(returnType)) {
replaceNode(returnType, getReplacingNode(returnType));
} else if (isInserted(returnType)) {
insertNode(returnType, methodDecl.getStartPosition(), MODIFIERS);
}
SimpleName simpleName= methodDecl.getName();
if (isReplaced(simpleName)) {
replaceNode(simpleName, getReplacingNode(simpleName));
}
List parameters= methodDecl.parameters();
List exceptions= methodDecl.thrownExceptions();
boolean changedParams= hasChanges(parameters);
boolean changedExc= hasChanges(exceptions);
if (changedParams || changedExc) {
try {
int offset= methodDecl.getStartPosition(); // simpleName.getStartPosition() + simpleName.getLength();
IScanner scanner= ASTResolving.createScanner(fChange.getCompilationUnit(), offset);
if (changedParams) {
ASTResolving.readToToken(scanner, ITerminalSymbols.TokenNameLPAREN);
rewriteList(scanner.getCurrentTokenEndPosition() + 1, "", parameters);
}
if (changedExc) {
ASTResolving.readToToken(scanner, ITerminalSymbols.TokenNameRPAREN);
rewriteList(scanner.getCurrentTokenEndPosition() + 1, " throws ", exceptions);
}
} catch (InvalidInputException e) {
// ignore
} catch (JavaModelException e) {
JavaPlugin.log(e);
}
}
Block body= methodDecl.getBody();
if (body != null) {
rewriteMethodBody(methodDecl, body);
}
return false;
}
public void rewriteMethodBody(MethodDeclaration methodDecl, Block body) {
if (isInserted(body) || isReplaced(body)) {
try {
IScanner scanner= ASTResolving.createScanner(fChange.getCompilationUnit(), methodDecl.getStartPosition());
if (isInserted(body)) {
ASTResolving.readToToken(scanner, ITerminalSymbols.TokenNameSEMICOLON);
int startPos= scanner.getCurrentTokenStartPosition();
int endPos= methodDecl.getStartPosition() + methodDecl.getLength();
String str= " " + Strings.trimLeadingTabsAndSpaces(generateSource(body, getIndent(methodDecl.getStartPosition())));
fChange.addTextEdit("Insert body", SimpleTextEdit.createReplace(startPos, endPos - startPos, str));
} else if (isReplaced(body)) {
ASTNode changed= getReplacingNode(body);
if (changed == null) {
fChange.addTextEdit("Remove body", SimpleTextEdit.createReplace(body.getStartPosition(), body.getLength(), ";"));
} else {
String str= Strings.trimLeadingTabsAndSpaces(generateSource(changed, getIndent(body.getStartPosition())));
fChange.addTextEdit("Replace body", SimpleTextEdit.createReplace(body.getStartPosition(), body.getLength(), str));
}
}
} catch (InvalidInputException e) {
// ignore
} catch (JavaModelException e) {
JavaPlugin.log(e);
}
} else {
body.accept(this);
}
}
private int getNextExistingStartPos(List list, int startIndex, int defaultPos) {
for (int i= startIndex; i < list.size(); i++) {
ASTNode elem= (ASTNode) list.get(i);
if (!isInserted(elem)) {
return elem.getStartPosition();
}
}
return defaultPos;
}
private boolean hasChanges(List list) {
for (int i= 0; i < list.size(); i++) {
ASTNode elem= (ASTNode) list.get(i);
if (isInserted(elem) || isReplaced(elem)) {
return true;
}
}
return false;
}
private void rewriteList(int startPos, String keyword, List list) {
int currPos= startPos;
int endPos= startPos;
// count number of nodes before and after the rewrite
int before= 0;
int after= 0;
for (int i= 0; i < list.size(); i++) {
ASTNode elem= (ASTNode) list.get(i);
if (isInserted(elem)) {
after++;
} else {
before++;
if (before == 1) { // first entry
currPos= elem.getStartPosition();
}
if (!isReplaced(elem) || getReplacingNode(elem) != null) {
after++;
}
}
endPos= elem.getStartPosition() + elem.getLength();
}
if (after == 0) {
if (before != 0) { // deleting the list
fChange.addTextEdit("Remove all", SimpleTextEdit.createReplace(startPos, endPos - startPos, ""));
}
return;
}
if (before == 0) { // creating a new list -> insert keyword first (e.g. " throws ")
fChange.addTextEdit("keyword", SimpleTextEdit.createInsert(startPos, keyword));
}
for (int i= 0; i < list.size(); i++) {
ASTNode elem= (ASTNode) list.get(i);
if (isInserted(elem)) {
after--;
String str= generateSource(elem, 0);
if (after != 0) { // not the last that will be entered
str= str + ", ";
}
fChange.addTextEdit("Insert", SimpleTextEdit.createInsert(currPos, str));
} else {
before--;
int currEnd= elem.getStartPosition() + elem.getLength();
int nextStart= getNextExistingStartPos(list, i + 1, currEnd); // start of next
if (isReplaced(elem)) {
ASTNode changed= getReplacingNode(elem);
if (changed == null) {
fChange.addTextEdit("Remove", SimpleTextEdit.createReplace(currPos, nextStart - currPos, ""));
} else {
after--;
String str= generateSource(changed, 0);
if (after == 0) { // will be last node -> remove comma
fChange.addTextEdit("Replace", SimpleTextEdit.createReplace(currPos, nextStart - currPos, str));
} else if (before == 0) { // was last, but not anymore -> add comma
fChange.addTextEdit("Replace", SimpleTextEdit.createReplace(currPos, currEnd - currPos, str + ", "));
} else {
fChange.addTextEdit("Replace", SimpleTextEdit.createReplace(currPos, currEnd - currPos, str));
}
}
} else { // no change
after--;
if (after == 0 && before != 0) { // will be last node -> remove comma
fChange.addTextEdit("Remove comma", SimpleTextEdit.createReplace(currEnd, nextStart - currEnd, ""));
} else if (after != 0 && before == 0) { // was last, but not anymore -> add comma
fChange.addTextEdit("Add comma", SimpleTextEdit.createInsert(currEnd, ", "));
}
}
currPos= nextStart;
}
}
}
private void replaceStatement(ASTNode elem, ASTNode changed) {
if (changed == null) {
int start= elem.getStartPosition();
int end= start + elem.getLength();
TextRegion endRegion= fTextBuffer.getLineInformationOfOffset(end);
int lineEnd= endRegion.getOffset() + endRegion.getLength();
// move end to include all spaces and tabs
while (end < lineEnd && Character.isWhitespace(fTextBuffer.getChar(end))) {
end++;
}
if (lineEnd == end) { // if there is no comment / other statement remove the line (indent + new line)
int startLine= fTextBuffer.getLineOfOffset(start);
if (startLine > 0) {
TextRegion prevRegion= fTextBuffer.getLineInformation(startLine - 1);
int cutPos= prevRegion.getOffset() + prevRegion.getLength();
String str= fTextBuffer.getContent(cutPos, start - cutPos);
if (Strings.containsOnlyWhitespaces(str)) {
start= cutPos;
}
}
}
fChange.addTextEdit("Remove Statement", SimpleTextEdit.createReplace(start, end - start, ""));
} else {
int startLine= fTextBuffer.getLineOfOffset(elem.getStartPosition());
int indent= fTextBuffer.getLineIndent(startLine, CodeFormatterUtil.getTabWidth());
String str= Strings.trimLeadingTabsAndSpaces(generateSource(changed, indent));
fChange.addTextEdit("Replace Statement", SimpleTextEdit.createReplace(elem.getStartPosition(), elem.getLength(), str));
}
}
private void insertStatement(ASTNode elem, ASTNode sibling, int parentBracketPos, boolean additionalNewLine) {
int insertPos;
int indent;
if (sibling == null) {
indent= getIndent(parentBracketPos) + 1;
insertPos= parentBracketPos + 1;
} else {
indent= getIndent(sibling.getStartPosition());
insertPos= sibling.getStartPosition() + sibling.getLength();
}
StringBuffer buf= new StringBuffer();
buf.append(fTextBuffer.getLineDelimiter());
buf.append(generateSource(elem, indent));
if (additionalNewLine) {
buf.append(fTextBuffer.getLineDelimiter());
}
fChange.addTextEdit("Add Statement", SimpleTextEdit.createInsert(insertPos, buf.toString()));
}
private int getIndent(int pos) {
int line= fTextBuffer.getLineOfOffset(pos);
return fTextBuffer.getLineIndent(line, CodeFormatterUtil.getTabWidth());
}
private String generateSource(ASTNode node, int indent) {
String str= ASTNodes.asString(node);
return StubUtility.codeFormat(str, indent, fTextBuffer.getLineDelimiter());
}
/**
* @see org.eclipse.jdt.core.dom.ASTVisitor#visit(TypeDeclaration)
*/
public boolean visit(TypeDeclaration typeDecl) {
SimpleName simpleName= typeDecl.getName();
if (isReplaced(simpleName)) {
replaceNode(simpleName, getReplacingNode(simpleName));
}
Name superClass= typeDecl.getSuperclass();
if (!typeDecl.isInterface() && superClass != null) {
if (isInserted(superClass)) {
String str= " extends " + ASTNodes.asString(superClass);
int pos= simpleName.getStartPosition() + simpleName.getLength();
fChange.addTextEdit("Insert Supertype", SimpleTextEdit.createInsert(pos, str));
} else if (isReplaced(superClass)) {
ASTNode changed= getReplacingNode(superClass);
if (changed == null) {
int startPos= simpleName.getStartPosition() + simpleName.getLength();
int endPos= superClass.getStartPosition() + superClass.getLength();
fChange.addTextEdit("Remove Supertype", SimpleTextEdit.createReplace(startPos, endPos - startPos, ""));
} else {
String str= ASTNodes.asString(changed);
fChange.addTextEdit("Replace Supertype", SimpleTextEdit.createReplace(superClass.getStartPosition(), superClass.getLength(), str));
}
}
}
List interfaces= typeDecl.superInterfaces();
if (hasChanges(interfaces)) {
int startPos;
if (typeDecl.isInterface() || superClass == null || isInserted(superClass)) {
startPos= simpleName.getStartPosition() + simpleName.getLength();
} else {
startPos= superClass.getStartPosition() + superClass.getLength();
}
rewriteList(startPos, " implements ", interfaces);
}
List members= typeDecl.bodyDeclarations();
ASTNode last= null;
for (int i= 0; i < members.size(); i++) {
ASTNode elem= (ASTNode) members.get(i);
if (isInserted(elem)) {
int offset= typeDecl.getStartPosition() + typeDecl.getLength() - 1;
if (last == null) {
try {
int pos= typeDecl.getStartPosition(); //simpleName.getStartPosition() + simpleName.getLength();
IScanner scanner= ASTResolving.createScanner(fChange.getCompilationUnit(), pos);
ASTResolving.readToToken(scanner, ITerminalSymbols.TokenNameLBRACE);
offset= scanner.getCurrentTokenStartPosition();
} catch (JavaModelException e) {
JavaPlugin.log(e);
} catch (InvalidInputException e) {
// ignore
}
}
insertStatement(elem, last, offset, false);
} else {
last= elem;
if (isReplaced(elem)) {
replaceStatement(elem, getReplacingNode(elem));
} else {
elem.accept(this);
}
}
}
return false;
}
}