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