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