b8e11fb240094ebf0525bd015e5457fee25c8f6d
[phpeclipse.git] / net.sourceforge.phpeclipse / src / net / sourceforge / phpdt / internal / corext / textmanipulation / TextEditNode.java
1 /*
2  * (c) Copyright IBM Corp. 2000, 2001.
3  * All Rights Reserved.
4  */
5 package net.sourceforge.phpdt.internal.corext.textmanipulation;
6
7 import java.util.ArrayList;
8 import java.util.Iterator;
9 import java.util.List;
10
11 import org.eclipse.core.runtime.CoreException;
12 import org.eclipse.core.runtime.IProgressMonitor;
13 import org.eclipse.jface.text.DocumentEvent;
14 import org.eclipse.jface.text.IDocumentListener;
15
16 /**
17  * A helper class to arrange <code>TextEdit</code>s into a tree to optimize
18  * their execution.
19  */
20 /* package */abstract class TextEditNode {
21
22         /* package */TextEditNode fParent;
23
24         /* package */List fChildren;
25
26         /* package */TextEdit fEdit;
27
28         /* package */static class DefaultNode extends TextEditNode {
29                 public DefaultNode(TextEdit edit) {
30                         super(edit);
31                 }
32         }
33
34         /* package */static class RootNode extends TextEditNode {
35                 private int fUndoIndex;
36
37                 public RootNode(int length) {
38                         super(new NopTextEdit(new TextRange(0, length)));
39                         fEdit.isSynthetic = true;
40                 }
41
42                 public boolean covers(TextEditNode node) {
43                         return true;
44                 }
45
46                 public UndoMemento performDo(TextBuffer buffer, IProgressMonitor pm)
47                                 throws CoreException {
48                         DoRangeUpdater updater = new DoRangeUpdater();
49                         UndoMemento undo = new UndoMemento(TextBufferEditor.UNDO);
50                         try {
51                                 buffer.registerUpdater(updater);
52                                 performDo(buffer, updater, undo, pm);
53                         } finally {
54                                 buffer.unregisterUpdater(updater);
55                                 updater.setActiveNode(null);
56                         }
57                         return undo;
58                 }
59
60                 public UndoMemento performUndo(TextBuffer buffer, IProgressMonitor pm)
61                                 throws CoreException {
62                         UndoRangeUpdater updater = new UndoRangeUpdater(this);
63                         UndoMemento undo = new UndoMemento(TextBufferEditor.REDO);
64                         try {
65                                 buffer.registerUpdater(updater);
66                                 performUndo(buffer, updater, undo, pm);
67                         } finally {
68                                 buffer.unregisterUpdater(updater);
69                                 updater.setActiveNode(null);
70                         }
71                         return undo;
72                 }
73
74                 protected void setUndoIndex(int index) {
75                         fUndoIndex = index;
76                 }
77
78                 protected int getUndoIndex() {
79                         return fUndoIndex;
80                 }
81         }
82
83         /* package */abstract static class AbstractMoveNode extends TextEditNode {
84                 private int state;
85
86                 private int fTargetIndex;
87
88                 private int fSourceIndex;
89
90                 private List fAffectedChildren;
91
92                 public AbstractMoveNode(TextEdit edit) {
93                         super(edit);
94                         reset();
95                 }
96
97                 protected abstract TextRange getSourceRange();
98
99                 protected abstract TextRange getTargetRange();
100
101                 protected abstract boolean isUpMove();
102
103                 protected boolean isDownMove() {
104                         return !isUpMove();
105                 }
106
107                 public boolean isMove() {
108                         return true;
109                 }
110
111                 protected void checkRange(DocumentEvent event) {
112                         TextRange range = getChildRange();
113                         int eventOffset = event.getOffset();
114                         int eventLength = event.getLength();
115                         int eventEnd = eventOffset + eventLength - 1;
116                         // "Edit changes text that lies outside its defined range"
117                         // Assert.isTrue(range.fOffset <= eventOffset && eventEnd <=
118                         // range.getInclusiveEnd());
119                 }
120
121                 protected boolean activeNodeChanged(int delta) {
122                         TextRange targetRange = getTargetRange();
123                         TextRange sourceRange = getSourceRange();
124                         switch (state) {
125                         case 0: // the move delete
126                                 init();
127                                 // Assert.isTrue(Math.abs(delta) == sourceRange.fLength);
128                                 if (isUpMove()) {
129                                         updateOffset(fAffectedChildren, delta);
130                                         targetRange.fOffset += delta;
131                                 }
132                                 sourceRange.fLength = 0;
133                                 state = 1;
134                                 break;
135                         case 1:
136                                 TextEditNode target = (TextEditNode) fParent.fChildren
137                                                 .get(fTargetIndex);
138                                 TextEditNode source = (TextEditNode) fParent.fChildren
139                                                 .get(fSourceIndex);
140                                 updateOffset(source.fChildren, targetRange.fOffset
141                                                 - sourceRange.fOffset);
142                                 target.fChildren = source.fChildren;
143                                 if (target.fChildren != null) {
144                                         for (Iterator iter = target.fChildren.iterator(); iter
145                                                         .hasNext();) {
146                                                 ((TextEditNode) iter.next()).fParent = target;
147                                         }
148                                 }
149                                 source.fChildren = null;
150                                 if (isDownMove()) {
151                                         updateOffset(fAffectedChildren, delta);
152                                         sourceRange.fOffset += delta;
153                                 }
154                                 targetRange.fLength = delta;
155                                 reset();
156                                 break;
157                         }
158                         return true;
159                 }
160
161                 private static void updateOffset(List nodes, int delta) {
162                         if (nodes == null)
163                                 return;
164                         for (int i = nodes.size() - 1; i >= 0; i--) {
165                                 TextEditNode node = (TextEditNode) nodes.get(i);
166                                 TextRange range = node.getTextRange();
167                                 range.fOffset += delta;
168                                 updateOffset(node.fChildren, delta);
169                         }
170                 }
171
172                 private void init() {
173                         TextRange source = getSourceRange();
174                         TextRange target = getTargetRange();
175                         List children = fParent.fChildren;
176                         for (int i = children.size() - 1; i >= 0; i--) {
177                                 TextEditNode child = (TextEditNode) children.get(i);
178                                 TextRange range = child.fEdit.getTextRange();
179                                 if (range == source)
180                                         fSourceIndex = i;
181                                 else if (range == target)
182                                         fTargetIndex = i;
183                         }
184                         int start = Math.min(fTargetIndex, fSourceIndex);
185                         int end = Math.max(fTargetIndex, fSourceIndex);
186                         fAffectedChildren = new ArrayList(3);
187                         for (int i = start + 1; i < end; i++) {
188                                 fAffectedChildren.add(children.get(i));
189                         }
190                 }
191
192                 private void reset() {
193                         state = 0;
194                         fSourceIndex = -1;
195                         fTargetIndex = -1;
196                 }
197         }
198
199         /* package */static class MoveNode extends AbstractMoveNode {
200                 public MoveNode(TextEdit edit) {
201                         super(edit);
202                 }
203
204                 protected TextRange getChildRange() {
205                         return ((MoveTextEdit) fEdit).getChildRange();
206                 }
207
208                 protected TextRange getSourceRange() {
209                         return ((MoveTextEdit) fEdit).getSourceRange();
210                 }
211
212                 protected TextRange getTargetRange() {
213                         return ((MoveTextEdit) fEdit).getTargetRange();
214                 }
215
216                 protected boolean isUpMove() {
217                         return ((MoveTextEdit) fEdit).isUpMove();
218                 }
219
220                 public boolean isMovePartner(TextEditNode other) {
221                         if (!(other instanceof TargetMarkNode))
222                                 return false;
223                         return fEdit == ((MoveTextEdit.TargetMark) other.fEdit)
224                                         .getMoveTextEdit();
225                 }
226
227                 public boolean covers(TextEditNode node) {
228                         if (node instanceof TargetMarkNode) {
229                                 MoveTextEdit.TargetMark edit = (MoveTextEdit.TargetMark) node.fEdit;
230                                 if (edit.getMoveTextEdit() == fEdit)
231                                         return false;
232                         }
233                         return getParentRange().covers(node.getChildRange());
234                 }
235         }
236
237         /* package */static class TargetMarkNode extends AbstractMoveNode {
238                 public TargetMarkNode(TextEdit edit) {
239                         super(edit);
240                 }
241
242                 protected TextRange getChildRange() {
243                         return ((MoveTextEdit.TargetMark) fEdit).getMoveTextEdit()
244                                         .getChildRange();
245                 }
246
247                 protected TextRange getSourceRange() {
248                         return ((MoveTextEdit.TargetMark) fEdit).getMoveTextEdit()
249                                         .getSourceRange();
250                 }
251
252                 protected TextRange getTargetRange() {
253                         return ((MoveTextEdit.TargetMark) fEdit).getMoveTextEdit()
254                                         .getTargetRange();
255                 }
256
257                 protected boolean isUpMove() {
258                         return ((MoveTextEdit.TargetMark) fEdit).getMoveTextEdit()
259                                         .isUpMove();
260                 }
261
262                 public boolean isMovePartner(TextEditNode other) {
263                         return ((MoveTextEdit.TargetMark) fEdit).getMoveTextEdit() == other.fEdit;
264                 }
265         }
266
267         // ---- Range updating
268         // ---------------------------------------------------------------------------
269
270         private static abstract class RangeUpdater implements IDocumentListener {
271                 protected TextEditNode fActiveNode;
272
273                 public void documentAboutToBeChanged(DocumentEvent event) {
274                 }
275
276                 public void setActiveNode(TextEditNode node) {
277                         fActiveNode = node;
278                 }
279
280                 public void updateParents(int delta) {
281                         TextEditNode node = fActiveNode.fParent;
282                         while (node != null) {
283                                 node.childNodeChanged(delta);
284                                 node = node.fParent;
285                         }
286                 }
287
288                 public static int getDelta(DocumentEvent event) {
289                         return (event.getText() == null ? 0 : event.getText().length())
290                                         - event.getLength();
291                 }
292         }
293
294         private static class DoRangeUpdater extends RangeUpdater {
295                 private List fProcessedNodes = new ArrayList(10);
296
297                 public void setActiveNode(TextEditNode node) {
298                         if (fActiveNode != null)
299                                 fProcessedNodes.add(fActiveNode);
300                         super.setActiveNode(node);
301                 }
302
303                 public void documentChanged(DocumentEvent event) {
304                         fActiveNode.checkRange(event);
305                         int delta = getDelta(event);
306                         if (!fActiveNode.activeNodeChanged(delta)) {
307                                 for (Iterator iter = fProcessedNodes.iterator(); iter.hasNext();) {
308                                         ((TextEditNode) iter.next()).previousNodeChanged(delta);
309                                 }
310                         }
311                         updateParents(delta);
312                 }
313         }
314
315         private static class UndoRangeUpdater extends RangeUpdater {
316                 private RootNode fRootNode;
317
318                 public UndoRangeUpdater(RootNode root) {
319                         fRootNode = root;
320                 }
321
322                 public void setActiveNode(TextEditNode node) {
323                         super.setActiveNode(node);
324                 }
325
326                 public void documentChanged(DocumentEvent event) {
327                         fActiveNode.checkRange(event);
328                         int delta = getDelta(event);
329                         if (!fActiveNode.activeNodeChanged(delta)) {
330                                 int start = fRootNode.getUndoIndex() + 1;
331                                 List children = fRootNode.fChildren;
332                                 int size = children != null ? children.size() : 0;
333                                 for (int i = start; i < size; i++) {
334                                         updateUndo((TextEditNode) children.get(i), delta);
335                                 }
336                         }
337                         updateParents(delta);
338                 }
339
340                 private void updateUndo(TextEditNode node, int delta) {
341                         node.previousNodeChanged(delta);
342                         List children = node.fChildren;
343                         int size = children != null ? children.size() : 0;
344                         for (int i = 0; i < size; i++) {
345                                 updateUndo((TextEditNode) children.get(i), delta);
346                         }
347                 }
348         }
349
350         // ---- Creating instances
351         // ---------------------------------------------------------------------------
352
353         static TextEditNode create(TextEdit edit) {
354                 if (edit instanceof MoveTextEdit)
355                         return new MoveNode(edit);
356                 if (edit instanceof MoveTextEdit.TargetMark)
357                         return new TargetMarkNode(edit);
358                 return new DefaultNode(edit);
359         }
360
361         static RootNode createRoot(int length) {
362                 return new RootNode(length);
363         }
364
365         private TextEditNode(TextEdit edit) {
366                 fEdit = edit;
367         }
368
369         // ---- Adding children
370         // ---------------------------------------------------------------------------
371
372         protected void add(TextEditNode node) {
373                 if (fChildren == null) {
374                         fChildren = new ArrayList(1);
375                         node.fParent = this;
376                         fChildren.add(node);
377                         return;
378                 }
379                 // Optimize using binary search
380                 for (Iterator iter = fChildren.iterator(); iter.hasNext();) {
381                         TextEditNode child = (TextEditNode) iter.next();
382                         if (child.covers(node)) {
383                                 child.add(node);
384                                 return;
385                         }
386                 }
387                 for (int i = 0; i < fChildren.size();) {
388                         TextEditNode child = (TextEditNode) fChildren.get(i);
389                         if (node.covers(child)) {
390                                 fChildren.remove(i);
391                                 node.add(child);
392                         } else {
393                                 i++;
394                         }
395                 }
396                 node.fParent = this;
397                 fChildren.add(node);
398         }
399
400         public boolean covers(TextEditNode node) {
401                 return false;
402         }
403
404         // ---- Accessing
405         // --------------------------------------------------------------------------------------
406
407         protected RootNode getRoot() {
408                 TextEditNode candidate = this;
409                 while (candidate.fParent != null)
410                         candidate = candidate.fParent;
411                 return (RootNode) candidate;
412         }
413
414         // ---- Query interface
415         // --------------------------------------------------------------------------------
416
417         protected boolean isSynthetic() {
418                 return fEdit.isSynthetic;
419         }
420
421         public boolean isMove() {
422                 return false;
423         }
424
425         // ---- Accessing Ranges
426         // ------------------------------------------------------------------------------
427
428         protected void checkRange(DocumentEvent event) {
429                 TextRange range = getTextRange();
430                 int eventOffset = event.getOffset();
431                 int eventLength = event.getLength();
432                 int eventEnd = eventOffset + eventLength - 1;
433                 // "Edit changes text that lies outside its defined range"
434                 // Assert.isTrue(range.fOffset <= eventOffset && eventEnd <=
435                 // range.getInclusiveEnd());
436         }
437
438         protected TextRange getTextRange() {
439                 return fEdit.getTextRange();
440         }
441
442         protected TextRange getChildRange() {
443                 return getTextRange();
444         }
445
446         protected TextRange getParentRange() {
447                 return getTextRange();
448         }
449
450         public boolean validate(int bufferLength) {
451                 if (fChildren == null)
452                         return true;
453                 // Only Moves and Nops can be parents
454                 if (!(fEdit instanceof MoveTextEdit || fEdit instanceof NopTextEdit))
455                         return false;
456                 TextRange lastRange = null;
457                 for (Iterator iter = fChildren.iterator(); iter.hasNext();) {
458                         TextEditNode node = (TextEditNode) iter.next();
459                         if (!node.validate(bufferLength))
460                                 return false;
461                         TextRange range = node.fEdit.getTextRange();
462                         if (!range.isValid()
463                                         || range.fOffset + range.fLength > bufferLength)
464                                 return false;
465                         if (lastRange != null
466                                         && !(range.isInsertionPointAt(lastRange.fOffset) || range
467                                                         .liesBehind(lastRange)))
468                                 return false;
469                         lastRange = range;
470                 }
471                 return true;
472         }
473
474         // ---- Updating
475         // ----------------------------------------------------------------------------------------
476
477         protected boolean activeNodeChanged(int delta) {
478                 TextRange range = getTextRange();
479                 range.fLength += delta;
480                 // we didn't adjust any processed nodes.
481                 return false;
482         }
483
484         protected void previousNodeChanged(int delta) {
485                 TextRange range = getTextRange();
486                 range.fOffset += delta;
487         }
488
489         protected void childNodeChanged(int delta) {
490                 getTextRange().fLength += delta;
491         }
492
493         // ---- Do it
494         // ---------------------------------------------------------------------------------------------
495
496         protected void performDo(TextBuffer buffer, RangeUpdater updater,
497                         UndoMemento undo, IProgressMonitor pm) throws CoreException {
498                 int size = fChildren != null ? fChildren.size() : 0;
499                 for (int i = size - 1; i >= 0; i--) {
500                         TextEditNode child = (TextEditNode) fChildren.get(i);
501                         child.performDo(buffer, updater, undo, pm);
502                 }
503                 updater.setActiveNode(this);
504                 if (isSynthetic())
505                         fEdit.perform(buffer);
506                 else
507                         undo.add(fEdit.perform(buffer));
508                 pm.worked(1);
509         }
510
511         public void performedDo() {
512                 int size = fChildren != null ? fChildren.size() : 0;
513                 for (int i = size - 1; i >= 0; i--) {
514                         TextEditNode child = (TextEditNode) fChildren.get(i);
515                         child.performedDo();
516                 }
517                 fEdit.performed();
518         }
519
520         // ---- Undo it
521         // -------------------------------------------------------------------------------------------
522
523         protected void performUndo(TextBuffer buffer, RangeUpdater updater,
524                         UndoMemento undo, IProgressMonitor pm) throws CoreException {
525                 int size = fChildren != null ? fChildren.size() : 0;
526                 for (int i = 0; i < size; i++) {
527                         setUndoIndex(i);
528                         TextEditNode child = (TextEditNode) fChildren.get(i);
529                         child.performUndo(buffer, updater, undo, pm);
530                 }
531                 updater.setActiveNode(this);
532                 if (isSynthetic())
533                         fEdit.perform(buffer);
534                 else
535                         undo.add(fEdit.perform(buffer));
536                 pm.worked(1);
537         }
538
539         protected void setUndoIndex(int index) {
540         }
541
542         public void performedUndo() {
543                 int size = fChildren != null ? fChildren.size() : 0;
544                 for (int i = 0; i < size; i++) {
545                         TextEditNode child = (TextEditNode) fChildren.get(i);
546                         child.performedUndo();
547                 }
548                 fEdit.performed();
549         }
550
551         // protected void createUndoList(List list) {
552         // int size= fChildren != null ? fChildren.size() : 0;
553         // for (int i= 0; i < size; i++) {
554         // TextEditNode child= (TextEditNode)fChildren.get(i);
555         // child.createUndoList(list);
556         // }
557         // list.add(this);
558         // }
559 }