comparison dwtx/text/edits/TextEdit.d @ 129:eb30df5ca28b

Added JFace Text sources
author Frank Benoit <benoit@tionex.de>
date Sat, 23 Aug 2008 19:10:48 +0200
parents
children b56e9be9fe88
comparison
equal deleted inserted replaced
128:8df1d4193877 129:eb30df5ca28b
1 /*******************************************************************************
2 * Copyright (c) 2000, 2008 IBM Corporation and others.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the Eclipse Public License v1.0
5 * which accompanies this distribution, and is available at
6 * http://www.eclipse.org/legal/epl-v10.html
7 *
8 * Contributors:
9 * IBM Corporation - initial API and implementation
10 * Port to the D programming language:
11 * Frank Benoit <benoit@tionex.de>
12 *******************************************************************************/
13 module dwtx.text.edits.TextEdit;
14
15 import dwt.dwthelper.utils;
16
17 import java.util.ArrayList;
18 import java.util.Collections;
19 import java.util.Comparator;
20 import java.util.Iterator;
21 import java.util.List;
22
23 import dwtx.core.runtime.Assert;
24 import dwtx.jface.text.BadLocationException;
25 import dwtx.jface.text.IDocument;
26 import dwtx.jface.text.IRegion;
27 import dwtx.jface.text.Region;
28
29
30 /**
31 * A text edit describes an elementary text manipulation operation. Edits are
32 * executed by applying them to a document (e.g. an instance of <code>IDocument
33 * </code>).
34 * <p>
35 * Text edits form a tree. Clients can navigate the tree upwards, from child to
36 * parent, as well as downwards. Newly created edits are un-parented. New edits
37 * are added to the tree by calling one of the <code>add</code> methods on a parent
38 * edit.
39 * </p>
40 * <p>
41 * An edit tree is well formed in the following sense:
42 * <ul>
43 * <li>a parent edit covers all its children</li>
44 * <li>children don't overlap</li>
45 * <li>an edit with length 0 can't have any children</li>
46 * </ul>
47 * Any manipulation of the tree that violates one of the above requirements results
48 * in a <code>MalformedTreeException</code>.
49 * </p>
50 * <p>
51 * Insert edits are represented by an edit of length 0. If more than one insert
52 * edit exists at the same offset then the edits are executed in the order in which
53 * they have been added to a parent. The following code example:
54 * <pre>
55 * IDocument document= new Document("org");
56 * MultiTextEdit edit= new MultiTextEdit();
57 * edit.addChild(new InsertEdit(0, "www."));
58 * edit.addChild(new InsertEdit(0, "eclipse."));
59 * edit.apply(document);
60 * </pre>
61 * therefore results in string: "www.eclipse.org".
62 * </p>
63 * <p>
64 * Text edits can be executed in a mode where the edit's region is updated to
65 * reflect the edit's position in the changed document. Region updating is enabled
66 * by default or can be requested by passing <code>UPDATE_REGIONS</code> to the
67 * {@link #apply(IDocument, int) apply(IDocument, int)} method. In the above example
68 * the region of the <code>InsertEdit(0, "eclipse.")</code> edit after executing
69 * the root edit is <code>[3, 8]</code>. If the region of an edit got deleted during
70 * change execution the region is set to <code>[-1, -1]</code> and the method {@link
71 * #isDeleted() isDeleted} returns <code>true</code>.
72 * </p>
73 * This class isn't intended to be subclassed outside of the edit framework. Clients
74 * are only allowed to subclass <code>MultiTextEdit</code>.
75 *
76 * @since 3.0
77 * @noextend This class is not intended to be subclassed by clients.
78 */
79 public abstract class TextEdit {
80
81 /**
82 * Flags indicating that neither <code>CREATE_UNDO</code> nor
83 * <code>UPDATE_REGIONS</code> is set.
84 */
85 public static final int NONE= 0;
86
87 /**
88 * Flags indicating that applying an edit tree to a document
89 * is supposed to create a corresponding undo edit. If not
90 * specified <code>null</code> is returned from method <code>
91 * apply</code>.
92 */
93 public static final int CREATE_UNDO= 1 << 0;
94
95 /**
96 * Flag indicating that the edit's region will be updated to
97 * reflect its position in the changed document. If not specified
98 * when applying an edit tree to a document the edit's region will
99 * be arbitrary. It is even not guaranteed that the tree is still
100 * well formed.
101 */
102 public static final int UPDATE_REGIONS= 1 << 1;
103
104 private static class InsertionComparator : Comparator {
105 public int compare(Object o1, Object o2) throws MalformedTreeException {
106 TextEdit edit1= (TextEdit)o1;
107 TextEdit edit2= (TextEdit)o2;
108
109 int offset1= edit1.getOffset();
110 int length1= edit1.getLength();
111
112 int offset2= edit2.getOffset();
113 int length2= edit2.getLength();
114
115 if (offset1 is offset2 && length1 is 0 && length2 is 0) {
116 return 0;
117 }
118 if (offset1 + length1 <= offset2) {
119 return -1;
120 }
121 if (offset2 + length2 <= offset1) {
122 return 1;
123 }
124 throw new MalformedTreeException(
125 null, edit1,
126 TextEditMessages.getString("TextEdit.overlapping")); //$NON-NLS-1$
127 }
128 }
129
130 private static final TextEdit[] EMPTY_ARRAY= new TextEdit[0];
131 private static final InsertionComparator INSERTION_COMPARATOR= new InsertionComparator();
132
133 private static final int DELETED_VALUE= -1;
134
135 private int fOffset;
136 private int fLength;
137
138 private TextEdit fParent;
139 private List fChildren;
140
141 int fDelta;
142
143 /**
144 * Create a new text edit. Parent is initialized to <code>
145 * null<code> and the edit doesn't have any children.
146 *
147 * @param offset the edit's offset
148 * @param length the edit's length
149 */
150 protected TextEdit(int offset, int length) {
151 Assert.isTrue(offset >= 0 && length >= 0);
152 fOffset= offset;
153 fLength= length;
154 fDelta= 0;
155 }
156
157 /**
158 * Copy constructor
159 *
160 * @param source the source to copy form
161 */
162 protected TextEdit(TextEdit source) {
163 fOffset= source.fOffset;
164 fLength= source.fLength;
165 fDelta= 0;
166 }
167
168 //---- Region management -----------------------------------------------
169
170 /**
171 * Returns the range that this edit is manipulating. The returned
172 * <code>IRegion</code> contains the edit's offset and length at
173 * the point in time when this call is made. Any subsequent changes
174 * to the edit's offset and length aren't reflected in the returned
175 * region object.
176 * <p>
177 * Creating a region for a deleted edit will result in an assertion
178 * failure.
179 *
180 * @return the manipulated region
181 */
182 public final IRegion getRegion() {
183 return new Region(getOffset(), getLength());
184 }
185
186 /**
187 * Returns the offset of the edit. An offset is a 0-based
188 * character index. Returns <code>-1</code> if the edit
189 * is marked as deleted.
190 *
191 * @return the offset of the edit
192 */
193 public int getOffset() {
194 return fOffset;
195 }
196
197 /**
198 * Returns the length of the edit. Returns <code>-1</code>
199 * if the edit is marked as deleted.
200 *
201 * @return the length of the edit
202 */
203 public int getLength() {
204 return fLength;
205 }
206
207 /**
208 * Returns the inclusive end position of this edit. The inclusive end
209 * position denotes the last character of the region manipulated by
210 * this edit. The returned value is the result of the following
211 * calculation:
212 * <pre>
213 * getOffset() + getLength() - 1;
214 * <pre>
215 *
216 * @return the inclusive end position
217 */
218 public final int getInclusiveEnd() {
219 return getOffset() + getLength() - 1;
220 }
221
222 /**
223 * Returns the exclusive end position of this edit. The exclusive end
224 * position denotes the next character of the region manipulated by
225 * this edit. The returned value is the result of the following
226 * calculation:
227 * <pre>
228 * getOffset() + getLength();
229 * </pre>
230 *
231 * @return the exclusive end position
232 */
233 public final int getExclusiveEnd() {
234 return getOffset() + getLength();
235 }
236
237 /**
238 * Returns whether this edit has been deleted or not.
239 *
240 * @return <code>true</code> if the edit has been
241 * deleted; otherwise <code>false</code> is returned.
242 */
243 public final bool isDeleted() {
244 return fOffset is DELETED_VALUE && fLength is DELETED_VALUE;
245 }
246
247 /**
248 * Move all offsets in the tree by the given delta. This node must be a
249 * root node. The resulting offsets must be greater or equal to zero.
250 *
251 * @param delta the delta
252 * @since 3.1
253 */
254 public final void moveTree(int delta) {
255 Assert.isTrue(fParent is null);
256 Assert.isTrue(getOffset() + delta >= 0);
257 internalMoveTree(delta);
258 }
259
260 /**
261 * Returns <code>true</code> if the edit covers the given edit
262 * <code>other</code>. It is up to the concrete text edit to
263 * decide if a edit of length zero can cover another edit.
264 *
265 * @param other the other edit
266 * @return <code>true<code> if the edit covers the other edit;
267 * otherwise <code>false</code> is returned.
268 */
269 public bool covers(TextEdit other) {
270 if (getLength() is 0 && !canZeroLengthCover())
271 return false;
272
273 if (!other.isDefined())
274 return true;
275
276 int thisOffset= getOffset();
277 int otherOffset= other.getOffset();
278 return thisOffset <= otherOffset && otherOffset + other.getLength() <= thisOffset + getLength();
279 }
280
281 /**
282 * Returns <code>true</code> if an edit with length zero can cover
283 * another edit. Returns <code>false</code> otherwise.
284 *
285 * @return whether an edit of length zero can cover another edit
286 */
287 protected bool canZeroLengthCover() {
288 return false;
289 }
290
291 /**
292 * Returns whether the region of this edit is defined or not.
293 *
294 * @return whether the region of this edit is defined or not
295 *
296 * @since 3.1
297 */
298 bool isDefined() {
299 return true;
300 }
301
302 //---- parent and children management -----------------------------
303
304 /**
305 * Returns the edit's parent. The method returns <code>null</code>
306 * if this edit hasn't been add to another edit.
307 *
308 * @return the edit's parent
309 */
310 public final TextEdit getParent() {
311 return fParent;
312 }
313
314 /**
315 * Returns the root edit of the edit tree.
316 *
317 * @return the root edit of the edit tree
318 * @since 3.1
319 */
320 public final TextEdit getRoot() {
321 TextEdit result= this;
322 while (result.fParent !is null) {
323 result= result.fParent;
324 }
325 return result;
326 }
327
328 /**
329 * Adds the given edit <code>child</code> to this edit.
330 *
331 * @param child the child edit to add
332 * @exception MalformedTreeException is thrown if the child
333 * edit can't be added to this edit. This is the case if the child
334 * overlaps with one of its siblings or if the child edit's region
335 * isn't fully covered by this edit.
336 */
337 public final void addChild(TextEdit child) throws MalformedTreeException {
338 internalAdd(child);
339 }
340
341 /**
342 * Adds all edits in <code>edits</code> to this edit.
343 *
344 * @param edits the text edits to add
345 * @exception MalformedTreeException is thrown if one of
346 * the given edits can't be added to this edit.
347 *
348 * @see #addChild(TextEdit)
349 */
350 public final void addChildren(TextEdit[] edits) throws MalformedTreeException {
351 for (int i= 0; i < edits.length; i++) {
352 internalAdd(edits[i]);
353 }
354 }
355
356 /**
357 * Removes the edit specified by the given index from the list
358 * of children. Returns the child edit that was removed from
359 * the list of children. The parent of the returned edit is
360 * set to <code>null</code>.
361 *
362 * @param index the index of the edit to remove
363 * @return the removed edit
364 * @exception IndexOutOfBoundsException if the index
365 * is out of range
366 */
367 public final TextEdit removeChild(int index) {
368 if (fChildren is null)
369 throw new IndexOutOfBoundsException("Index: " + index + " Size: 0"); //$NON-NLS-1$//$NON-NLS-2$
370 TextEdit result= (TextEdit)fChildren.remove(index);
371 result.internalSetParent(null);
372 if (fChildren.isEmpty())
373 fChildren= null;
374 return result;
375 }
376
377 /**
378 * Removes the first occurrence of the given child from the list
379 * of children.
380 *
381 * @param child the child to be removed
382 * @return <code>true</code> if the edit contained the given
383 * child; otherwise <code>false</code> is returned
384 */
385 public final bool removeChild(TextEdit child) {
386 Assert.isNotNull(child);
387 if (fChildren is null)
388 return false;
389 bool result= fChildren.remove(child);
390 if (result) {
391 child.internalSetParent(null);
392 if (fChildren.isEmpty())
393 fChildren= null;
394 }
395 return result;
396 }
397
398 /**
399 * Removes all child edits from and returns them. The parent
400 * of the removed edits is set to <code>null</code>.
401 *
402 * @return an array of the removed edits
403 */
404 public final TextEdit[] removeChildren() {
405 if (fChildren is null)
406 return EMPTY_ARRAY;
407 int size= fChildren.size();
408 TextEdit[] result= new TextEdit[size];
409 for (int i= 0; i < size; i++) {
410 result[i]= (TextEdit)fChildren.get(i);
411 result[i].internalSetParent(null);
412 }
413 fChildren= null;
414 return result;
415 }
416
417 /**
418 * Returns <code>true</code> if this edit has children. Otherwise
419 * <code>false</code> is returned.
420 *
421 * @return <code>true</code> if this edit has children; otherwise
422 * <code>false</code> is returned
423 */
424 public final bool hasChildren() {
425 return fChildren !is null && ! fChildren.isEmpty();
426 }
427
428 /**
429 * Returns the edit's children. If the edit doesn't have any
430 * children an empty array is returned.
431 *
432 * @return the edit's children
433 */
434 public final TextEdit[] getChildren() {
435 if (fChildren is null)
436 return EMPTY_ARRAY;
437 return (TextEdit[])fChildren.toArray(new TextEdit[fChildren.size()]);
438 }
439
440 /**
441 * Returns the size of the managed children.
442 *
443 * @return the size of the children
444 */
445 public final int getChildrenSize() {
446 if (fChildren is null)
447 return 0;
448 return fChildren.size();
449 }
450
451 /**
452 * Returns the text range spawned by the given array of text edits.
453 * The method requires that the given array contains at least one
454 * edit. If all edits passed are deleted the method returns <code>
455 * null</code>.
456 *
457 * @param edits an array of edits
458 * @return the text range spawned by the given array of edits or
459 * <code>null</code> if all edits are marked as deleted
460 */
461 public static IRegion getCoverage(TextEdit[] edits) {
462 Assert.isTrue(edits !is null && edits.length > 0);
463
464 int offset= Integer.MAX_VALUE;
465 int end= Integer.MIN_VALUE;
466 int deleted= 0;
467 for (int i= 0; i < edits.length; i++) {
468 TextEdit edit= edits[i];
469 if (edit.isDeleted()) {
470 deleted++;
471 } else {
472 offset= Math.min(offset, edit.getOffset());
473 end= Math.max(end, edit.getExclusiveEnd());
474 }
475 }
476 if (edits.length is deleted)
477 return null;
478
479 return new Region(offset, end - offset);
480 }
481
482 /*
483 * Hook called before this edit gets added to the passed
484 * parent.
485 */
486 void aboutToBeAdded(TextEdit parent) {
487 }
488
489 //---- Object methods ------------------------------------------------------
490
491 /**
492 * The <code>Edit</code> implementation of this <code>Object</code>
493 * method uses object identity (is).
494 *
495 * @param obj the other object
496 * @return <code>true</code> iff <code>this is obj</code>; otherwise
497 * <code>false</code> is returned
498 *
499 * @see Object#equals(java.lang.Object)
500 */
501 public final bool equals(Object obj) {
502 return this is obj; // equivalent to Object.equals
503 }
504
505 /**
506 * The <code>Edit</code> implementation of this <code>Object</code>
507 * method calls uses <code>Object#hashCode()</code> to compute its
508 * hash code.
509 *
510 * @return the object's hash code value
511 *
512 * @see Object#hashCode()
513 */
514 public final int hashCode() {
515 return super.hashCode();
516 }
517
518 /*
519 * @see java.lang.Object#toString()
520 */
521 public String toString() {
522 StringBuffer buffer= new StringBuffer();
523 toStringWithChildren(buffer, 0);
524 return buffer.toString();
525 }
526
527 /**
528 * Adds the string representation of this text edit without
529 * children information to the given string buffer.
530 *
531 * @param buffer the string buffer
532 * @param indent the indent level in number of spaces
533 * @since 3.3
534 */
535 void internalToString(StringBuffer buffer, int indent) {
536 for (int i= indent; i > 0; i--) {
537 buffer.append(" "); //$NON-NLS-1$
538 }
539 buffer.append("{"); //$NON-NLS-1$
540 String name= getClass().getName();
541 int index= name.lastIndexOf('.');
542 if (index !is -1) {
543 buffer.append(name.substring(index + 1));
544 } else {
545 buffer.append(name);
546 }
547 buffer.append("} "); //$NON-NLS-1$
548 if (isDeleted()) {
549 buffer.append("[deleted]"); //$NON-NLS-1$
550 } else {
551 buffer.append("["); //$NON-NLS-1$
552 buffer.append(getOffset());
553 buffer.append(","); //$NON-NLS-1$
554 buffer.append(getLength());
555 buffer.append("]"); //$NON-NLS-1$
556 }
557 }
558
559 /**
560 * Adds the string representation for this text edit
561 * and its children to the given string buffer.
562 *
563 * @param buffer the string buffer
564 * @param indent the indent level in number of spaces
565 * @since 3.3
566 */
567 private void toStringWithChildren(StringBuffer buffer, int indent) {
568 internalToString(buffer, indent);
569 if (fChildren !is null) {
570 for (Iterator iterator= fChildren.iterator(); iterator.hasNext();) {
571 TextEdit child= (TextEdit) iterator.next();
572 buffer.append('\n');
573 child.toStringWithChildren(buffer, indent + 1);
574 }
575 }
576 }
577
578 //---- Copying -------------------------------------------------------------
579
580 /**
581 * Creates a deep copy of the edit tree rooted at this
582 * edit.
583 *
584 * @return a deep copy of the edit tree
585 * @see #doCopy()
586 */
587 public final TextEdit copy() {
588 TextEditCopier copier= new TextEditCopier(this);
589 return copier.perform();
590 }
591
592 /**
593 * Creates and returns a copy of this edit. The copy method should be
594 * implemented in a way so that the copy can executed without causing
595 * any harm to the original edit. Implementors of this method are
596 * responsible for creating deep or shallow copies of referenced
597 * object to fulfill this requirement.
598 * <p>
599 * Implementers of this method should use the copy constructor <code>
600 * Edit#Edit(Edit source) to initialize the edit part of the copy.
601 * Implementors aren't responsible to actually copy the children or
602 * to set the right parent.
603 * <p>
604 * This method <b>should not be called</b> from outside the framework.
605 * Please use <code>copy</code> to create a copy of a edit tree.
606 *
607 * @return a copy of this edit.
608 * @see #copy()
609 * @see #postProcessCopy(TextEditCopier)
610 * @see TextEditCopier
611 */
612 protected abstract TextEdit doCopy();
613
614 /**
615 * This method is called on every edit of the copied tree to do some
616 * post-processing like connected an edit to a different edit in the tree.
617 * <p>
618 * This default implementation does nothing
619 *
620 * @param copier the copier that manages a map between original and
621 * copied edit.
622 * @see TextEditCopier
623 */
624 protected void postProcessCopy(TextEditCopier copier) {
625 }
626
627 //---- Visitor support -------------------------------------------------
628
629 /**
630 * Accepts the given visitor on a visit of the current edit.
631 *
632 * @param visitor the visitor object
633 * @exception IllegalArgumentException if the visitor is null
634 */
635 public final void accept(TextEditVisitor visitor) {
636 Assert.isNotNull(visitor);
637 // begin with the generic pre-visit
638 visitor.preVisit(this);
639 // dynamic dispatch to internal method for type-specific visit/endVisit
640 accept0(visitor);
641 // end with the generic post-visit
642 visitor.postVisit(this);
643 }
644
645 /**
646 * Accepts the given visitor on a type-specific visit of the current edit.
647 * This method must be implemented in all concrete text edits.
648 * <p>
649 * General template for implementation on each concrete TextEdit class:
650 * <pre>
651 * <code>
652 * bool visitChildren= visitor.visit(this);
653 * if (visitChildren) {
654 * acceptChildren(visitor);
655 * }
656 * </code>
657 * </pre>
658 * Note that the caller (<code>accept</code>) takes care of invoking
659 * <code>visitor.preVisit(this)</code> and <code>visitor.postVisit(this)</code>.
660 * </p>
661 *
662 * @param visitor the visitor object
663 */
664 protected abstract void accept0(TextEditVisitor visitor);
665
666
667 /**
668 * Accepts the given visitor on the edits children.
669 * <p>
670 * This method must be used by the concrete implementations of
671 * <code>accept</code> to traverse list-values properties; it
672 * encapsulates the proper handling of on-the-fly changes to the list.
673 * </p>
674 *
675 * @param visitor the visitor object
676 */
677 protected final void acceptChildren(TextEditVisitor visitor) {
678 if (fChildren is null)
679 return;
680 Iterator iterator= fChildren.iterator();
681 while (iterator.hasNext()) {
682 TextEdit curr= (TextEdit) iterator.next();
683 curr.accept(visitor);
684 }
685 }
686
687 //---- Execution -------------------------------------------------------
688
689 /**
690 * Applies the edit tree rooted by this edit to the given document. To check
691 * if the edit tree can be applied to the document either catch <code>
692 * MalformedTreeException</code> or use <code>TextEditProcessor</code> to
693 * execute an edit tree.
694 *
695 * @param document the document to be manipulated
696 * @param style flags controlling the execution of the edit tree. Valid
697 * flags are: <code>CREATE_UNDO</code> and </code>UPDATE_REGIONS</code>.
698 * @return a undo edit, if <code>CREATE_UNDO</code> is specified. Otherwise
699 * <code>null</code> is returned.
700 *
701 * @exception MalformedTreeException is thrown if the tree isn't
702 * in a valid state. This exception is thrown before any edit
703 * is executed. So the document is still in its original state.
704 * @exception BadLocationException is thrown if one of the edits
705 * in the tree can't be executed. The state of the document is
706 * undefined if this exception is thrown.
707 *
708 * @see TextEditProcessor#performEdits()
709 */
710 public final UndoEdit apply(IDocument document, int style) throws MalformedTreeException, BadLocationException {
711 try {
712 TextEditProcessor processor= new TextEditProcessor(document, this, style);
713 return processor.performEdits();
714 } finally {
715 // disconnect from text edit processor
716 fParent= null;
717 }
718 }
719
720 /**
721 * Applies the edit tree rooted by this edit to the given document. This
722 * method is a convenience method for <code>apply(document, CREATE_UNDO | UPDATE_REGIONS)
723 * </code>
724 *
725 * @param document the document to which to apply this edit
726 * @return a undo edit, if <code>CREATE_UNDO</code> is specified. Otherwise
727 * <code>null</code> is returned.
728 * @exception MalformedTreeException is thrown if the tree isn't
729 * in a valid state. This exception is thrown before any edit
730 * is executed. So the document is still in its original state.
731 * @exception BadLocationException is thrown if one of the edits
732 * in the tree can't be executed. The state of the document is
733 * undefined if this exception is thrown.
734 * @see #apply(IDocument, int)
735 */
736 public final UndoEdit apply(IDocument document) throws MalformedTreeException, BadLocationException {
737 return apply(document, CREATE_UNDO | UPDATE_REGIONS);
738 }
739
740 UndoEdit dispatchPerformEdits(TextEditProcessor processor) throws BadLocationException {
741 return processor.executeDo();
742 }
743
744 void dispatchCheckIntegrity(TextEditProcessor processor) throws MalformedTreeException {
745 processor.checkIntegrityDo();
746 }
747
748 //---- internal state accessors ----------------------------------------------------------
749
750 void internalSetParent(TextEdit parent) {
751 if (parent !is null)
752 Assert.isTrue(fParent is null);
753 fParent= parent;
754 }
755
756 void internalSetOffset(int offset) {
757 Assert.isTrue(offset >= 0);
758 fOffset= offset;
759 }
760
761 void internalSetLength(int length) {
762 Assert.isTrue(length >= 0);
763 fLength= length;
764 }
765
766 List internalGetChildren() {
767 return fChildren;
768 }
769
770 void internalSetChildren(List children) {
771 fChildren= children;
772 }
773
774 void internalAdd(TextEdit child) throws MalformedTreeException {
775 child.aboutToBeAdded(this);
776 if (child.isDeleted())
777 throw new MalformedTreeException(this, child, TextEditMessages.getString("TextEdit.deleted_edit")); //$NON-NLS-1$
778 if (!covers(child))
779 throw new MalformedTreeException(this, child, TextEditMessages.getString("TextEdit.range_outside")); //$NON-NLS-1$
780 if (fChildren is null) {
781 fChildren= new ArrayList(2);
782 }
783 int index= computeInsertionIndex(child);
784 fChildren.add(index, child);
785 child.internalSetParent(this);
786 }
787
788 private int computeInsertionIndex(TextEdit edit) throws MalformedTreeException {
789 int size= fChildren.size();
790 if (size is 0)
791 return 0;
792 int lastIndex= size - 1;
793 TextEdit last= (TextEdit)fChildren.get(lastIndex);
794 if (last.getExclusiveEnd() <= edit.getOffset())
795 return size;
796 try {
797
798 int index= Collections.binarySearch(fChildren, edit, INSERTION_COMPARATOR);
799
800 if (index < 0)
801 // edit is not in fChildren
802 return -index - 1;
803
804 // edit is already in fChildren
805 // make sure that multiple insertion points at the same offset are inserted last.
806 while (index < lastIndex && INSERTION_COMPARATOR.compare(fChildren.get(index), fChildren.get(index + 1)) is 0)
807 index++;
808
809 return index + 1;
810
811 } catch(MalformedTreeException e) {
812 e.setParent(this);
813 throw e;
814 }
815 }
816
817 //---- Offset & Length updating -------------------------------------------------
818
819 /**
820 * Adjusts the edits offset according to the given
821 * delta. This method doesn't update any children.
822 *
823 * @param delta the delta of the text replace operation
824 */
825 void adjustOffset(int delta) {
826 if (isDeleted())
827 return;
828 fOffset+= delta;
829 Assert.isTrue(fOffset >= 0);
830 }
831
832 /**
833 * Adjusts the edits length according to the given
834 * delta. This method doesn't update any children.
835 *
836 * @param delta the delta of the text replace operation
837 */
838 void adjustLength(int delta) {
839 if (isDeleted())
840 return;
841 fLength+= delta;
842 Assert.isTrue(fLength >= 0);
843 }
844
845 /**
846 * Marks the edit as deleted. This method doesn't update
847 * any children.
848 */
849 void markAsDeleted() {
850 fOffset= DELETED_VALUE;
851 fLength= DELETED_VALUE;
852 }
853
854 //---- Edit processing ----------------------------------------------
855
856 /**
857 * Traverses the edit tree to perform the consistency check.
858 *
859 * @param processor the text edit processor
860 * @param document the document to be manipulated
861 * @param sourceEdits the list of source edits to be performed before
862 * the actual tree is applied to the document
863 *
864 * @return the number of indirect move or copy target edit children
865 */
866 int traverseConsistencyCheck(TextEditProcessor processor, IDocument document, List sourceEdits) {
867 int result= 0;
868 if (fChildren !is null) {
869 for (int i= fChildren.size() - 1; i >= 0; i--) {
870 TextEdit child= (TextEdit)fChildren.get(i);
871 result= Math.max(result, child.traverseConsistencyCheck(processor, document, sourceEdits));
872 }
873 }
874 if (processor.considerEdit(this)) {
875 performConsistencyCheck(processor, document);
876 }
877 return result;
878 }
879
880 void performConsistencyCheck(TextEditProcessor processor, IDocument document) {
881 }
882
883 void traverseSourceComputation(TextEditProcessor processor, IDocument document) {
884 }
885
886 void performSourceComputation(TextEditProcessor processor, IDocument document) {
887 }
888
889 int traverseDocumentUpdating(TextEditProcessor processor, IDocument document) throws BadLocationException {
890 int delta= 0;
891 if (fChildren !is null) {
892 for (int i= fChildren.size() - 1; i >= 0; i--) {
893 TextEdit child= (TextEdit)fChildren.get(i);
894 delta+= child.traverseDocumentUpdating(processor, document);
895 childDocumentUpdated();
896 }
897 }
898 if (processor.considerEdit(this)) {
899 if (delta !is 0)
900 adjustLength(delta);
901 int r= performDocumentUpdating(document);
902 if (r !is 0)
903 adjustLength(r);
904 delta+= r;
905 }
906 return delta;
907 }
908
909 /**
910 * Hook method called when the document updating of a child edit has been
911 * completed. When a client calls {@link #apply(IDocument)} or
912 * {@link #apply(IDocument, int)} this method is called
913 * {@link #getChildrenSize()} times.
914 * <p>
915 * May be overridden by subclasses of {@link MultiTextEdit}.
916 *
917 * @since 3.1
918 */
919 protected void childDocumentUpdated() {
920 }
921
922 abstract int performDocumentUpdating(IDocument document) throws BadLocationException;
923
924 int traverseRegionUpdating(TextEditProcessor processor, IDocument document, int accumulatedDelta, bool delete) {
925 performRegionUpdating(accumulatedDelta, delete);
926 if (fChildren !is null) {
927 bool childDelete= delete || deleteChildren();
928 for (Iterator iter= fChildren.iterator(); iter.hasNext();) {
929 TextEdit child= (TextEdit)iter.next();
930 accumulatedDelta= child.traverseRegionUpdating(processor, document, accumulatedDelta, childDelete);
931 childRegionUpdated();
932 }
933 }
934 return accumulatedDelta + fDelta;
935 }
936
937 /**
938 * Hook method called when the region updating of a child edit has been
939 * completed. When a client calls {@link #apply(IDocument)} this method is
940 * called {@link #getChildrenSize()} times. When calling
941 * {@link #apply(IDocument, int)} this method is called
942 * {@link #getChildrenSize()} times, when the style parameter contains the
943 * {@link #UPDATE_REGIONS} flag.
944 * <p>
945 * May be overridden by subclasses of {@link MultiTextEdit}.
946 *
947 * @since 3.1
948 */
949 protected void childRegionUpdated() {
950 }
951
952 void performRegionUpdating(int accumulatedDelta, bool delete) {
953 if (delete)
954 markAsDeleted();
955 else
956 adjustOffset(accumulatedDelta);
957 }
958
959 abstract bool deleteChildren();
960
961 void internalMoveTree(int delta) {
962 adjustOffset(delta);
963 if (fChildren !is null) {
964 for (Iterator iter= fChildren.iterator(); iter.hasNext();) {
965 ((TextEdit)iter.next()).internalMoveTree(delta);
966 }
967 }
968 }
969
970 void deleteTree() {
971 markAsDeleted();
972 if (fChildren !is null) {
973 for (Iterator iter= fChildren.iterator(); iter.hasNext();) {
974 TextEdit child= (TextEdit)iter.next();
975 child.deleteTree();
976 }
977 }
978 }
979 }
980