/* * (c) Copyright IBM Corp. 2000, 2001. * All Rights Reserved. */ package net.sourceforge.phpdt.internal.corext.textmanipulation; 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; /** * A helper class to arrange TextEdits 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); } private 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); // } }