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