blob: 8385f2c8a4d915132e7ea790a2bd8d7509bf21b6 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2003 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Common Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/cpl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.jdt.internal.corext.dom;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import org.eclipse.text.edits.TextEdit;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.Block;
import org.eclipse.jdt.core.dom.Statement;
import org.eclipse.jdt.internal.corext.Assert;
import org.eclipse.jdt.internal.corext.textmanipulation.GroupDescription;
import org.eclipse.jdt.internal.corext.textmanipulation.TextBuffer;
/**
* Example:
* <code>
* void foo(int i, boolean b) {
* doSomething(x + 7 - y);
* }
* MethodDeclaration existingDecl
* ASTRewrite rewrite= ASTRewrite(existingDecl);
* AST ast= existingDecl.getAST();
*
* // change return type to array of float
* ArrayType newReturnType= ast.newArrayType(ast.newPrimitiveType(PrimitiveType.FLOAT), 1);
* rewrite.markAsReplaced(existingDecl.getReturnType(), newReturnType);
*
* // change name
* SimpleName newName= ast.newSimpleName("work");
* rewrite.markAsReplaced(existingDecl.getName(), newName);
*
* // remove first parameter
* List parameters= existingDecl.parameters();
* rewrite.markAsRemoved((ASTNode) parameters.get(0));
*
* // add new throws declaration
* List thrownExceptions= existingDecl.thrownExceptions();
* SimpleName newException= ast.newSimpleName("IOException");
* thrownExceptions.add(newException);
* rewrite.markAsInserted(newException);
*
* // move statement inside if
* List statements= existingDecl.getBody().statements();
*
* Statement movedNode= (Statement) statements.get(0);
* Statement copyTarget= (Statement) rewrite.createCopy(movedNode);
*
* IfStatement newIfStatement= ast.newIfStatement();
* newIfStatement.setExpression(ast.newSimpleName("b"));
* newIfStatement.setThenStatement(copyTarget);
*
* rewrite.markAsReplaced(movedNode, newIfStatement);
*
* TextEdit resultingEdits= new MultiTextEdit();
* rewrite.rewriteNode(textBuffer, resultingEdits);
* </code>
*/
public final class ASTRewrite extends NewASTRewrite {
private HashMap fChangedProperties;
private boolean fHasASTModifications;
/**
* Creates the <code>ASTRewrite</code> object.
* @param node A node which is parent to all modified, changed or tracked nodes.
*/
public ASTRewrite(ASTNode node) {
super(node);
fChangedProperties= new HashMap();
fHasASTModifications= false;
// override the parent to child mapper to correct back modified modes from inserts
fEventStore.setNodePropertyMapper(new RewriteEventStore.INodePropertyMapper() {
public Object getOriginalValue(ASTNode parent, int childProperty) {
Object originalValue= ASTNodeConstants.getNodeChild(parent, childProperty);
if (originalValue instanceof List) {
List originalList= (List) originalValue;
ArrayList fixedList= new ArrayList(originalList.size());
for (int i= 0; i < originalList.size(); i++) {
ASTNode curr= (ASTNode) originalList.get(i);
if (!isInserted(curr)) {
fixedList.add(curr);
}
}
return fixedList;
} else if (originalValue instanceof ASTNode) {
if (isInserted((ASTNode) originalValue)) {
return null;
}
}
return originalValue;
}
});
}
/**
* Perform rewriting: Analyses AST modifications and creates text edits that describe changes to the
* underlying code. Edits do only change code when the corresponding node has changed. New code
* is formatted using the standard code formatter.
* @param textBuffer Text buffer which is describing the code of the AST passed in in the
* constructor. This buffer is accessed read-only.
* @param rootEdit
*/
public final void rewriteNode(TextBuffer textBuffer, TextEdit rootEdit) {
convertOldToNewEvents();
TextEdit res= super.rewriteAST(textBuffer.getDocument());
rootEdit.addChildren(res.removeChildren());
}
/**
* Convert the old to the new events. Can only be done when rewrite is started
* (inserted node must not yet be added to the AST when marked)
*/
private void convertOldToNewEvents() {
Set processedListEvents= new HashSet();
for (Iterator iter= fChangedProperties.keySet().iterator(); iter.hasNext(); ) {
ASTNode node= (ASTNode) iter.next();
ASTInsert object= getChangeProperty(node);
if (object != null) {
if (node.getParent().getStartPosition() != -1) { // ignore unnecessary inserts
processChange(node, null, node, object.description, processedListEvents);
if (object.isBoundToPrevious) {
fEventStore.setInsertBoundToPrevious(node);
}
}
}
}
}
private void processChange(ASTNode nodeInAST, ASTNode originalNode, ASTNode newNode, GroupDescription desc, Set processedListEvents) {
ASTNode parent= nodeInAST.getParent();
int childProperty= ASTNodeConstants.getPropertyOfNode(nodeInAST);
if (ASTNodeConstants.isListProperty(childProperty)) {
ListRewriteEvent event= fEventStore.getListEvent(parent, childProperty, true); // create
if (processedListEvents.add(event)) {
convertListChange(event, (List) ASTNodeConstants.getNodeChild(parent, childProperty));
}
} else {
NodeRewriteEvent event= fEventStore.getNodeEvent(parent, childProperty, true);
event.setNewValue(newNode);
fEventStore.setEventDescription(event, desc);
}
}
private void convertListChange(ListRewriteEvent listEvent, List modifiedList) {
int insertIndex= 0;
for (int i= 0; i < modifiedList.size(); i++) {
ASTNode curr= (ASTNode) modifiedList.get(i);
ASTInsert object= getChangeProperty(curr);
if (object != null) {
RewriteEvent change= listEvent.insertAtOriginalIndex(curr, insertIndex);
fEventStore.setEventDescription(change, object.description);
if (object.isBoundToPrevious) {
fEventStore.setInsertBoundToPrevious(curr);
}
} else {
insertIndex++;
}
}
}
/**
* Removes all modifications applied to the given AST.
*/
public final void removeModifications() {
if (fHasASTModifications) {
getRootNode().accept(new ASTRewriteClear(this));
fHasASTModifications= false;
}
fChangedProperties.clear();
clearRewrite();
}
public boolean hasASTModifications() {
return fHasASTModifications;
}
/**
* Marks a node as inserted. The node must not exist. To insert an existing node (move or copy),
* create a copy target first and insert this target node. ({@link #createCopy})
* @param node The node to be marked as inserted.
* @param description Description of the change.
*/
public final void markAsInserted(ASTNode node, GroupDescription description) {
Assert.isTrue(!isCollapsed(node), "Tries to insert a collapsed node"); //$NON-NLS-1$
ASTInsert insert= new ASTInsert();
insert.isBoundToPrevious= isInsertBoundToPreviousByDefault(node);
insert.description= description;
setChangeProperty(node, insert);
fHasASTModifications= true;
node.setSourceRange(-1, 0); // avoid troubles later when annotating extra node ranges.
}
/**
* Marks a node as inserted. The node must not exist. To insert an existing node (move or copy),
* create a copy target first and insert this target node. ({@link #createCopy})
* @param node The node to be marked as inserted.
*/
public final void markAsInserted(ASTNode node) {
markAsInserted(node, (GroupDescription) null);
}
/**
* Create a placeholder for a sequence of new statements to be inserted or placed at a single place.
* @param children The target nodes to collapse
* @return A placeholder node that stands for all of the statements
*/
public final Block getCollapseTargetPlaceholder(Statement[] children) {
Block res= createCollapsePlaceholder();
List statements= res.statements();
for (int i= 0; i < children.length; i++) {
statements.add(children[i]);
}
return res;
}
/**
* Creates a target node for a node to be copied. A target node can be inserted or used
* to replace at the target position.
* @param node
* @return
*/
public final ASTNode createCopy(ASTNode node) {
return createCopyPlaceholder(node);
}
/**
* Creates a target node for a node to be moved. A target node can be inserted or used
* to replace at the target position. The source node will be marked as removed, but the user can also
* override this by marking it as replaced.
* @param node
* @return
*/
public final ASTNode createMove(ASTNode node) {
int changeKind= fEventStore.getChangeKind(node);
if (changeKind != RewriteEvent.REMOVED && changeKind != RewriteEvent.REPLACED) {
markAsRemoved(node);
}
return createMovePlaceholder(node);
}
public final ASTNode createPlaceholder(String code, int nodeType) {
return createStringPlaceholder(code, nodeType);
}
/**
* Succeeding nodes in a list are collapsed and represented by a new 'compound' node. The new compound node is inserted in the list
* and replaces the collapsed node. The compound node can be used for rewriting, e.g. a copy can be created to move
* a whole range of statements. This operation modifies the AST.
* @param list
* @param index
* @param length
* @return
*/
public final ASTNode collapseNodes(List list, int index, int length) {
Assert.isTrue(index >= 0 && length > 0 && list.size() >= (index + length), "Index or length out of bound"); //$NON-NLS-1$
ASTNode firstNode= (ASTNode) list.get(index);
ASTNode lastNode= (ASTNode) list.get(index + length - 1);
validateIsInsideAST(firstNode);
validateIsInsideAST(lastNode);
Assert.isTrue(lastNode instanceof Statement, "Can only collapse statements"); //$NON-NLS-1$
int startPos= firstNode.getStartPosition();
int endPos= lastNode.getStartPosition() + lastNode.getLength();
Block compoundNode= createCollapsePlaceholder();
List children= compoundNode.statements();
compoundNode.setSourceRange(startPos, endPos - startPos);
int childProperty= ASTNodeConstants.getPropertyOfNode(firstNode);
ListRewriteEvent existingEvent= fEventStore.getListEvent(firstNode.getParent(), childProperty, false);
if (existingEvent != null) {
RewriteEvent[] origChildren= existingEvent.getChildren();
Assert.isTrue(origChildren.length == list.size());
RewriteEvent[] newChildren= new RewriteEvent[origChildren.length - length + 1];
System.arraycopy(origChildren, 0, newChildren, 0, index);
newChildren[index]= new NodeRewriteEvent(compoundNode, compoundNode);
System.arraycopy(origChildren, index + length, newChildren, index + 1, origChildren.length - index - length);
fEventStore.addEvent(firstNode.getParent(), childProperty, new ListRewriteEvent(newChildren)); // replace
RewriteEvent[] newCollapsedChildren= new RewriteEvent[length];
System.arraycopy(origChildren, index, newCollapsedChildren, 0, length);
fEventStore.addEvent(compoundNode, ASTNodeConstants.STATEMENTS, new ListRewriteEvent(newCollapsedChildren));
}
for (int i= 0; i < length; i++) {
Object curr= list.remove(index);
children.add(curr);
}
list.add(index, compoundNode);
fHasASTModifications= true;
return compoundNode;
}
public final boolean isInserted(ASTNode node) {
return getChangeProperty(node) != null;
}
public boolean isRemoved(ASTNode node) {
return fEventStore.getChangeKind(node) == RewriteEvent.REMOVED;
}
public boolean isReplaced(ASTNode node) {
return fEventStore.getChangeKind(node) == RewriteEvent.REPLACED;
}
public final ASTNode getReplacingNode(ASTNode node) {
RewriteEvent event= fEventStore.findEventByOriginal(node);
if (event != null && event.getChangeKind() == RewriteEvent.REPLACED) {
return (ASTNode) event.getNewValue();
}
return null;
}
private final void setChangeProperty(ASTNode node, ASTInsert change) {
fChangedProperties.put(node, change);
}
private final ASTInsert getChangeProperty(ASTNode node) {
return (ASTInsert) fChangedProperties.get(node);
}
private static class ASTInsert {
public GroupDescription description;
public boolean isBoundToPrevious;
}
}