blob: 0bc1790b132165bdaac4bf92d676b90b4eee7cc8 [file] [log] [blame]
package org.eclipse.cdt.internal.corext.textmanipulation;
/*
* (c) Copyright IBM Corp. 2000, 2001.
* All Rights Reserved.
*/
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jface.text.DocumentEvent;
import org.eclipse.jface.text.IDocumentListener;
import org.eclipse.jface.util.Assert;
/**
* A helper class to arrange <code>TextEdit</code>s into a tree to optimize their
* execution.
*/
/* package */ abstract class TextEditNode {
/* package */ TextEditNode fParent;
/* package */ List fChildren;
/* package */ TextEdit fEdit;
/* package */ static class DefaultNode extends TextEditNode {
public DefaultNode(TextEdit edit) {
super(edit);
}
}
/* package */ static class RootNode extends TextEditNode {
private int fUndoIndex;
public RootNode(int length) {
super(new NopTextEdit(new TextRange(0, length)));
fEdit.isSynthetic= true;
}
public boolean covers(TextEditNode node) {
return true;
}
public UndoMemento performDo(TextBuffer buffer, IProgressMonitor pm) throws CoreException {
DoRangeUpdater updater= new DoRangeUpdater();
UndoMemento undo= new UndoMemento(TextBufferEditor.UNDO);
try {
buffer.registerUpdater(updater);
performDo(buffer, updater, undo, pm);
} finally {
buffer.unregisterUpdater(updater);
updater.setActiveNode(null);
}
return undo;
}
public UndoMemento performUndo(TextBuffer buffer, IProgressMonitor pm) throws CoreException {
UndoRangeUpdater updater= new UndoRangeUpdater(this);
UndoMemento undo= new UndoMemento(TextBufferEditor.REDO);
try {
buffer.registerUpdater(updater);
performUndo(buffer, updater, undo, pm);
} finally {
buffer.unregisterUpdater(updater);
updater.setActiveNode(null);
}
return undo;
}
protected void setUndoIndex(int index) {
fUndoIndex= index;
}
protected int getUndoIndex() {
return fUndoIndex;
}
}
/* package */ abstract static class AbstractMoveNode extends TextEditNode {
private int state;
private int fTargetIndex;
private int fSourceIndex;
private List fAffectedChildren;
public AbstractMoveNode(TextEdit edit) {
super(edit);
reset();
}
protected abstract TextRange getSourceRange();
protected abstract TextRange getTargetRange();
protected abstract boolean isUpMove();
protected boolean isDownMove() {
return !isUpMove();
}
public boolean isMove() {
return true;
}
protected void checkRange(DocumentEvent event) {
TextRange range= getChildRange();
int eventOffset= event.getOffset();
int eventLength= event.getLength();
int eventEnd = eventOffset + eventLength - 1;
// "Edit changes text that lies outside its defined range"
Assert.isTrue(range.fOffset <= eventOffset && eventEnd <= range.getInclusiveEnd());
}
protected boolean activeNodeChanged(int delta) {
TextRange targetRange= getTargetRange();
TextRange sourceRange= getSourceRange();
switch (state) {
case 0: // the move delete
init();
Assert.isTrue(Math.abs(delta) == sourceRange.fLength);
if (isUpMove()) {
updateOffset(fAffectedChildren, delta);
targetRange.fOffset+= delta;
}
sourceRange.fLength= 0;
state= 1;
break;
case 1:
TextEditNode target= (TextEditNode)fParent.fChildren.get(fTargetIndex);
TextEditNode source= (TextEditNode)fParent.fChildren.get(fSourceIndex);
updateOffset(source.fChildren, targetRange.fOffset - sourceRange.fOffset);
target.fChildren= source.fChildren;
if (target.fChildren != null) {
for (Iterator iter= target.fChildren.iterator(); iter.hasNext();) {
((TextEditNode)iter.next()).fParent= target;
}
}
source.fChildren= null;
if (isDownMove()) {
updateOffset(fAffectedChildren, delta);
sourceRange.fOffset+= delta;
}
targetRange.fLength= delta;
reset();
break;
}
return true;
}
private static void updateOffset(List nodes, int delta) {
if (nodes == null)
return;
for (int i= nodes.size() - 1; i >= 0; i--) {
TextEditNode node= (TextEditNode)nodes.get(i);
TextRange range= node.getTextRange();
range.fOffset+= delta;
updateOffset(node.fChildren, delta);
}
}
private void init() {
TextRange source= getSourceRange();
TextRange target= getTargetRange();
List children= fParent.fChildren;
for (int i= children.size() - 1; i >= 0; i--) {
TextEditNode child= (TextEditNode)children.get(i);
TextRange range= child.fEdit.getTextRange();
if (range == source)
fSourceIndex= i;
else if (range == target)
fTargetIndex= i;
}
int start= Math.min(fTargetIndex, fSourceIndex);
int end= Math.max(fTargetIndex, fSourceIndex);
fAffectedChildren= new ArrayList(3);
for (int i= start + 1; i < end; i++) {
fAffectedChildren.add(children.get(i));
}
}
private void reset() {
state= 0;
fSourceIndex= -1;
fTargetIndex= -1;
}
}
/* package */ static class MoveNode extends AbstractMoveNode {
public MoveNode(TextEdit edit) {
super(edit);
}
protected TextRange getChildRange() {
return ((MoveTextEdit)fEdit).getChildRange();
}
protected TextRange getSourceRange() {
return ((MoveTextEdit)fEdit).getSourceRange();
}
protected TextRange getTargetRange() {
return ((MoveTextEdit)fEdit).getTargetRange();
}
protected boolean isUpMove() {
return ((MoveTextEdit)fEdit).isUpMove();
}
public boolean isMovePartner(TextEditNode other) {
if (!(other instanceof TargetMarkNode))
return false;
return fEdit == ((MoveTextEdit.TargetMark)other.fEdit).getMoveTextEdit();
}
public boolean covers(TextEditNode node) {
if (node instanceof TargetMarkNode) {
MoveTextEdit.TargetMark edit= (MoveTextEdit.TargetMark)node.fEdit;
if (edit.getMoveTextEdit() == fEdit)
return false;
}
return getParentRange().covers(node.getChildRange());
}
}
/* package */ static class TargetMarkNode extends AbstractMoveNode {
public TargetMarkNode(TextEdit edit) {
super(edit);
}
protected TextRange getChildRange() {
return ((MoveTextEdit.TargetMark)fEdit).getMoveTextEdit().getChildRange();
}
protected TextRange getSourceRange() {
return ((MoveTextEdit.TargetMark)fEdit).getMoveTextEdit().getSourceRange();
}
protected TextRange getTargetRange() {
return ((MoveTextEdit.TargetMark)fEdit).getMoveTextEdit().getTargetRange();
}
protected boolean isUpMove() {
return ((MoveTextEdit.TargetMark)fEdit).getMoveTextEdit().isUpMove();
}
public boolean isMovePartner(TextEditNode other) {
return ((MoveTextEdit.TargetMark)fEdit).getMoveTextEdit() == other.fEdit;
}
}
//---- Range updating ---------------------------------------------------------------------------
private static abstract class RangeUpdater implements IDocumentListener {
protected TextEditNode fActiveNode;
public void documentAboutToBeChanged(DocumentEvent event) {
}
public void setActiveNode(TextEditNode node) {
fActiveNode= node;
}
public void updateParents(int delta) {
TextEditNode node= fActiveNode.fParent;
while (node != null) {
node.childNodeChanged(delta);
node= node.fParent;
}
}
public static int getDelta(DocumentEvent event) {
return (event.getText() == null ? 0 : event.getText().length()) - event.getLength();
}
}
private static class DoRangeUpdater extends RangeUpdater {
private List fProcessedNodes= new ArrayList(10);
public void setActiveNode(TextEditNode node) {
if (fActiveNode != null)
fProcessedNodes.add(fActiveNode);
super.setActiveNode(node);
}
public void documentChanged(DocumentEvent event) {
fActiveNode.checkRange(event);
int delta= getDelta(event);
if (!fActiveNode.activeNodeChanged(delta)) {
for (Iterator iter= fProcessedNodes.iterator(); iter.hasNext();) {
((TextEditNode)iter.next()).previousNodeChanged(delta);
}
}
updateParents(delta);
}
}
private static class UndoRangeUpdater extends RangeUpdater {
private RootNode fRootNode;
public UndoRangeUpdater(RootNode root) {
fRootNode= root;
}
public void setActiveNode(TextEditNode node) {
super.setActiveNode(node);
}
public void documentChanged(DocumentEvent event) {
fActiveNode.checkRange(event);
int delta= getDelta(event);
if (!fActiveNode.activeNodeChanged(delta)) {
int start= fRootNode.getUndoIndex() + 1;
List children= fRootNode.fChildren;
int size= children != null ? children.size() : 0;
for (int i= start; i < size; i++) {
updateUndo((TextEditNode)children.get(i), delta);
}
}
updateParents(delta);
}
private void updateUndo(TextEditNode node, int delta) {
node.previousNodeChanged(delta);
List children= node.fChildren;
int size= children != null ? children.size() : 0;
for (int i= 0; i < size; i++) {
updateUndo((TextEditNode)children.get(i), delta);
}
}
}
//---- Creating instances ---------------------------------------------------------------------------
static TextEditNode create(TextEdit edit) {
if (edit instanceof MoveTextEdit)
return new MoveNode(edit);
if (edit instanceof MoveTextEdit.TargetMark)
return new TargetMarkNode(edit);
return new DefaultNode(edit);
}
static RootNode createRoot(int length) {
return new RootNode(length);
}
protected TextEditNode(TextEdit edit) {
fEdit= edit;
}
//---- Adding children ---------------------------------------------------------------------------
protected void add(TextEditNode node) {
if (fChildren == null) {
fChildren= new ArrayList(1);
node.fParent= this;
fChildren.add(node);
return;
}
// Optimize using binary search
for (Iterator iter= fChildren.iterator(); iter.hasNext();) {
TextEditNode child= (TextEditNode)iter.next();
if (child.covers(node)) {
child.add(node);
return;
}
}
for (int i= 0; i < fChildren.size(); ) {
TextEditNode child= (TextEditNode)fChildren.get(i);
if (node.covers(child)) {
fChildren.remove(i);
node.add(child);
} else {
i++;
}
}
node.fParent= this;
fChildren.add(node);
}
public boolean covers(TextEditNode node) {
return false;
}
//---- Accessing --------------------------------------------------------------------------------------
protected RootNode getRoot() {
TextEditNode candidate= this;
while(candidate.fParent != null)
candidate= candidate.fParent;
return (RootNode)candidate;
}
//---- Query interface --------------------------------------------------------------------------------
protected boolean isSynthetic() {
return fEdit.isSynthetic;
}
public boolean isMove() {
return false;
}
//---- Accessing Ranges ------------------------------------------------------------------------------
protected void checkRange(DocumentEvent event) {
TextRange range= getTextRange();
int eventOffset= event.getOffset();
int eventLength= event.getLength();
int eventEnd = eventOffset + eventLength - 1;
// "Edit changes text that lies outside its defined range"
Assert.isTrue(range.fOffset <= eventOffset && eventEnd <= range.getInclusiveEnd());
}
protected TextRange getTextRange() {
return fEdit.getTextRange();
}
protected TextRange getChildRange() {
return getTextRange();
}
protected TextRange getParentRange() {
return getTextRange();
}
public boolean validate(int bufferLength) {
if (fChildren == null)
return true;
// Only Moves and Nops can be parents
if (!(fEdit instanceof MoveTextEdit || fEdit instanceof NopTextEdit))
return false;
TextRange lastRange= null;
for (Iterator iter= fChildren.iterator(); iter.hasNext(); ) {
TextEditNode node= (TextEditNode)iter.next();
if (!node.validate(bufferLength))
return false;
TextRange range= node.fEdit.getTextRange();
if (!range.isValid() || range.fOffset + range.fLength > bufferLength)
return false;
if (lastRange != null && !(range.isInsertionPointAt(lastRange.fOffset) || range.liesBehind(lastRange)))
return false;
lastRange= range;
}
return true;
}
//---- Updating ----------------------------------------------------------------------------------------
protected boolean activeNodeChanged(int delta) {
TextRange range= getTextRange();
range.fLength+= delta;
// we didn't adjust any processed nodes.
return false;
}
protected void previousNodeChanged(int delta) {
TextRange range= getTextRange();
range.fOffset+= delta;
}
protected void childNodeChanged(int delta) {
getTextRange().fLength+= delta;
}
//---- Do it ---------------------------------------------------------------------------------------------
protected void performDo(TextBuffer buffer, RangeUpdater updater, UndoMemento undo, IProgressMonitor pm) throws CoreException {
int size= fChildren != null ? fChildren.size() : 0;
for (int i= size - 1; i >= 0; i--) {
TextEditNode child= (TextEditNode)fChildren.get(i);
child.performDo(buffer, updater, undo, pm);
}
updater.setActiveNode(this);
if (isSynthetic())
fEdit.perform(buffer);
else
undo.add(fEdit.perform(buffer));
pm.worked(1);
}
public void performedDo() {
int size= fChildren != null ? fChildren.size() : 0;
for (int i= size - 1; i >= 0; i--) {
TextEditNode child= (TextEditNode)fChildren.get(i);
child.performedDo();
}
fEdit.performed();
}
//---- Undo it -------------------------------------------------------------------------------------------
protected void performUndo(TextBuffer buffer, RangeUpdater updater, UndoMemento undo, IProgressMonitor pm) throws CoreException {
int size= fChildren != null ? fChildren.size() : 0;
for (int i= 0; i < size; i++) {
setUndoIndex(i);
TextEditNode child= (TextEditNode)fChildren.get(i);
child.performUndo(buffer, updater, undo, pm);
}
updater.setActiveNode(this);
if (isSynthetic())
fEdit.perform(buffer);
else
undo.add(fEdit.perform(buffer));
pm.worked(1);
}
protected void setUndoIndex(int index) {
}
public void performedUndo() {
int size= fChildren != null ? fChildren.size() : 0;
for (int i= 0; i < size; i++) {
TextEditNode child= (TextEditNode)fChildren.get(i);
child.performedUndo();
}
fEdit.performed();
}
// protected void createUndoList(List list) {
// int size= fChildren != null ? fChildren.size() : 0;
// for (int i= 0; i < size; i++) {
// TextEditNode child= (TextEditNode)fChildren.get(i);
// child.createUndoList(list);
// }
// list.add(this);
// }
}