1 /* 2 * Copyright (c) 1998, 2013, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 package javax.swing.text; 26 27 import java.util.Vector; 28 import java.io.IOException; 29 import java.io.ObjectInputStream; 30 import java.io.Serializable; 31 import javax.swing.undo.AbstractUndoableEdit; 32 import javax.swing.undo.CannotRedoException; 33 import javax.swing.undo.CannotUndoException; 34 import javax.swing.undo.UndoableEdit; 35 import javax.swing.SwingUtilities; 36 import java.lang.ref.WeakReference; 37 import java.lang.ref.ReferenceQueue; 38 39 /** 40 * An implementation of the AbstractDocument.Content interface 41 * implemented using a gapped buffer similar to that used by emacs. 42 * The underlying storage is a array of unicode characters with 43 * a gap somewhere. The gap is moved to the location of changes 44 * to take advantage of common behavior where most changes are 45 * in the same location. Changes that occur at a gap boundary are 46 * generally cheap and moving the gap is generally cheaper than 47 * moving the array contents directly to accommodate the change. 48 * <p> 49 * The positions tracking change are also generally cheap to 50 * maintain. The Position implementations (marks) store the array 51 * index and can easily calculate the sequential position from 52 * the current gap location. Changes only require update to the 53 * the marks between the old and new gap boundaries when the gap 54 * is moved, so generally updating the marks is pretty cheap. 55 * The marks are stored sorted so they can be located quickly 56 * with a binary search. This increases the cost of adding a 57 * mark, and decreases the cost of keeping the mark updated. 58 * 59 * @author Timothy Prinzing 60 */ 61 @SuppressWarnings("serial") // Superclass is not serializable across versions 62 public class GapContent extends GapVector implements AbstractDocument.Content, Serializable { 63 64 /** 65 * Creates a new GapContent object. Initial size defaults to 10. 66 */ 67 public GapContent() { 68 this(10); 69 } 70 71 /** 72 * Creates a new GapContent object, with the initial 73 * size specified. The initial size will not be allowed 74 * to go below 2, to give room for the implied break and 75 * the gap. 76 * 77 * @param initialLength the initial size 78 */ 79 public GapContent(int initialLength) { 80 super(Math.max(initialLength,2)); 81 char[] implied = new char[1]; 82 implied[0] = '\n'; 83 replace(0, 0, implied, implied.length); 84 85 marks = new MarkVector(); 86 search = new MarkData(0); 87 queue = new ReferenceQueue<StickyPosition>(); 88 } 89 90 /** 91 * Allocate an array to store items of the type 92 * appropriate (which is determined by the subclass). 93 */ 94 protected Object allocateArray(int len) { 95 return new char[len]; 96 } 97 98 /** 99 * Get the length of the allocated array. 100 */ 101 protected int getArrayLength() { 102 char[] carray = (char[]) getArray(); 103 return carray.length; 104 } 105 106 // --- AbstractDocument.Content methods ------------------------- 107 108 /** 109 * Returns the length of the content. 110 * 111 * @return the length >= 1 112 * @see AbstractDocument.Content#length 113 */ 114 public int length() { 115 int len = getArrayLength() - (getGapEnd() - getGapStart()); 116 return len; 117 } 118 119 /** 120 * Inserts a string into the content. 121 * 122 * @param where the starting position >= 0, < length() 123 * @param str the non-null string to insert 124 * @return an UndoableEdit object for undoing 125 * @exception BadLocationException if the specified position is invalid 126 * @see AbstractDocument.Content#insertString 127 */ 128 public UndoableEdit insertString(int where, String str) throws BadLocationException { 129 if (where > length() || where < 0) { 130 throw new BadLocationException("Invalid insert", length()); 131 } 132 char[] chars = str.toCharArray(); 133 replace(where, 0, chars, chars.length); 134 return new InsertUndo(where, str.length()); 135 } 136 137 /** 138 * Removes part of the content. 139 * 140 * @param where the starting position >= 0, where + nitems < length() 141 * @param nitems the number of characters to remove >= 0 142 * @return an UndoableEdit object for undoing 143 * @exception BadLocationException if the specified position is invalid 144 * @see AbstractDocument.Content#remove 145 */ 146 public UndoableEdit remove(int where, int nitems) throws BadLocationException { 147 if (where + nitems >= length()) { 148 throw new BadLocationException("Invalid remove", length() + 1); 149 } 150 String removedString = getString(where, nitems); 151 UndoableEdit edit = new RemoveUndo(where, removedString); 152 replace(where, nitems, empty, 0); 153 return edit; 154 155 } 156 157 /** 158 * Retrieves a portion of the content. 159 * 160 * @param where the starting position >= 0 161 * @param len the length to retrieve >= 0 162 * @return a string representing the content 163 * @exception BadLocationException if the specified position is invalid 164 * @see AbstractDocument.Content#getString 165 */ 166 public String getString(int where, int len) throws BadLocationException { 167 Segment s = new Segment(); 168 getChars(where, len, s); 169 return new String(s.array, s.offset, s.count); 170 } 171 172 /** 173 * Retrieves a portion of the content. If the desired content spans 174 * the gap, we copy the content. If the desired content does not 175 * span the gap, the actual store is returned to avoid the copy since 176 * it is contiguous. 177 * 178 * @param where the starting position >= 0, where + len <= length() 179 * @param len the number of characters to retrieve >= 0 180 * @param chars the Segment object to return the characters in 181 * @exception BadLocationException if the specified position is invalid 182 * @see AbstractDocument.Content#getChars 183 */ 184 public void getChars(int where, int len, Segment chars) throws BadLocationException { 185 int end = where + len; 186 if (where < 0 || end < 0) { 187 throw new BadLocationException("Invalid location", -1); 188 } 189 if (end > length() || where > length()) { 190 throw new BadLocationException("Invalid location", length() + 1); 191 } 192 int g0 = getGapStart(); 193 int g1 = getGapEnd(); 194 char[] array = (char[]) getArray(); 195 if ((where + len) <= g0) { 196 // below gap 197 chars.array = array; 198 chars.offset = where; 199 } else if (where >= g0) { 200 // above gap 201 chars.array = array; 202 chars.offset = g1 + where - g0; 203 } else { 204 // spans the gap 205 int before = g0 - where; 206 if (chars.isPartialReturn()) { 207 // partial return allowed, return amount before the gap 208 chars.array = array; 209 chars.offset = where; 210 chars.count = before; 211 return; 212 } 213 // partial return not allowed, must copy 214 chars.array = new char[len]; 215 chars.offset = 0; 216 System.arraycopy(array, where, chars.array, 0, before); 217 System.arraycopy(array, g1, chars.array, before, len - before); 218 } 219 chars.count = len; 220 } 221 222 /** 223 * Creates a position within the content that will 224 * track change as the content is mutated. 225 * 226 * @param offset the offset to track >= 0 227 * @return the position 228 * @exception BadLocationException if the specified position is invalid 229 */ 230 public Position createPosition(int offset) throws BadLocationException { 231 while ( queue.poll() != null ) { 232 unusedMarks++; 233 } 234 if (unusedMarks > Math.max(5, (marks.size() / 10))) { 235 removeUnusedMarks(); 236 } 237 int g0 = getGapStart(); 238 int g1 = getGapEnd(); 239 int index = (offset < g0) ? offset : offset + (g1 - g0); 240 search.index = index; 241 int sortIndex = findSortIndex(search); 242 MarkData m; 243 StickyPosition position; 244 if (sortIndex < marks.size() 245 && (m = marks.elementAt(sortIndex)).index == index 246 && (position = m.getPosition()) != null) { 247 //position references the correct StickyPostition 248 } else { 249 position = new StickyPosition(); 250 m = new MarkData(index,position,queue); 251 position.setMark(m); 252 marks.insertElementAt(m, sortIndex); 253 } 254 255 return position; 256 } 257 258 /** 259 * Holds the data for a mark... separately from 260 * the real mark so that the real mark (Position 261 * that the caller of createPosition holds) can be 262 * collected if there are no more references to 263 * it. The update table holds only a reference 264 * to this data. 265 */ 266 final class MarkData extends WeakReference<StickyPosition> { 267 268 MarkData(int index) { 269 super(null); 270 this.index = index; 271 } 272 MarkData(int index, StickyPosition position, ReferenceQueue<? super StickyPosition> queue) { 273 super(position, queue); 274 this.index = index; 275 } 276 277 /** 278 * Fetch the location in the contiguous sequence 279 * being modeled. The index in the gap array 280 * is held by the mark, so it is adjusted according 281 * to it's relationship to the gap. 282 */ 283 public int getOffset() { 284 int g0 = getGapStart(); 285 int g1 = getGapEnd(); 286 int offs = (index < g0) ? index : index - (g1 - g0); 287 return Math.max(offs, 0); 288 } 289 290 StickyPosition getPosition() { 291 return get(); 292 } 293 int index; 294 } 295 296 final class StickyPosition implements Position { 297 298 StickyPosition() { 299 } 300 301 void setMark(MarkData mark) { 302 this.mark = mark; 303 } 304 305 public int getOffset() { 306 return mark.getOffset(); 307 } 308 309 public String toString() { 310 return Integer.toString(getOffset()); 311 } 312 313 MarkData mark; 314 } 315 316 // --- variables -------------------------------------- 317 318 private static final char[] empty = new char[0]; 319 private transient MarkVector marks; 320 321 /** 322 * Record used for searching for the place to 323 * start updating mark indexs when the gap 324 * boundaries are moved. 325 */ 326 private transient MarkData search; 327 328 /** 329 * The number of unused mark entries 330 */ 331 private transient int unusedMarks = 0; 332 333 private transient ReferenceQueue<StickyPosition> queue; 334 335 final static int GROWTH_SIZE = 1024 * 512; 336 337 // --- gap management ------------------------------- 338 339 /** 340 * Make the gap bigger, moving any necessary data and updating 341 * the appropriate marks 342 */ 343 protected void shiftEnd(int newSize) { 344 int oldGapEnd = getGapEnd(); 345 346 super.shiftEnd(newSize); 347 348 // Adjust marks. 349 int dg = getGapEnd() - oldGapEnd; 350 int adjustIndex = findMarkAdjustIndex(oldGapEnd); 351 int n = marks.size(); 352 for (int i = adjustIndex; i < n; i++) { 353 MarkData mark = marks.elementAt(i); 354 mark.index += dg; 355 } 356 } 357 358 /** 359 * Overridden to make growth policy less agressive for large 360 * text amount. 361 */ 362 int getNewArraySize(int reqSize) { 363 if (reqSize < GROWTH_SIZE) { 364 return super.getNewArraySize(reqSize); 365 } else { 366 return reqSize + GROWTH_SIZE; 367 } 368 } 369 370 /** 371 * Move the start of the gap to a new location, 372 * without changing the size of the gap. This 373 * moves the data in the array and updates the 374 * marks accordingly. 375 */ 376 protected void shiftGap(int newGapStart) { 377 int oldGapStart = getGapStart(); 378 int dg = newGapStart - oldGapStart; 379 int oldGapEnd = getGapEnd(); 380 int newGapEnd = oldGapEnd + dg; 381 int gapSize = oldGapEnd - oldGapStart; 382 383 // shift gap in the character array 384 super.shiftGap(newGapStart); 385 386 // update the marks 387 if (dg > 0) { 388 // Move gap up, move data and marks down. 389 int adjustIndex = findMarkAdjustIndex(oldGapStart); 390 int n = marks.size(); 391 for (int i = adjustIndex; i < n; i++) { 392 MarkData mark = marks.elementAt(i); 393 if (mark.index >= newGapEnd) { 394 break; 395 } 396 mark.index -= gapSize; 397 } 398 } else if (dg < 0) { 399 // Move gap down, move data and marks up. 400 int adjustIndex = findMarkAdjustIndex(newGapStart); 401 int n = marks.size(); 402 for (int i = adjustIndex; i < n; i++) { 403 MarkData mark = marks.elementAt(i); 404 if (mark.index >= oldGapEnd) { 405 break; 406 } 407 mark.index += gapSize; 408 } 409 } 410 resetMarksAtZero(); 411 } 412 413 /** 414 * Resets all the marks that have an offset of 0 to have an index of 415 * zero as well. 416 */ 417 protected void resetMarksAtZero() { 418 if (marks != null && getGapStart() == 0) { 419 int g1 = getGapEnd(); 420 for (int counter = 0, maxCounter = marks.size(); 421 counter < maxCounter; counter++) { 422 MarkData mark = marks.elementAt(counter); 423 if (mark.index <= g1) { 424 mark.index = 0; 425 } 426 else { 427 break; 428 } 429 } 430 } 431 } 432 433 /** 434 * Adjust the gap end downward. This doesn't move 435 * any data, but it does update any marks affected 436 * by the boundary change. All marks from the old 437 * gap start down to the new gap start are squeezed 438 * to the end of the gap (their location has been 439 * removed). 440 */ 441 protected void shiftGapStartDown(int newGapStart) { 442 // Push aside all marks from oldGapStart down to newGapStart. 443 int adjustIndex = findMarkAdjustIndex(newGapStart); 444 int n = marks.size(); 445 int g0 = getGapStart(); 446 int g1 = getGapEnd(); 447 for (int i = adjustIndex; i < n; i++) { 448 MarkData mark = marks.elementAt(i); 449 if (mark.index > g0) { 450 // no more marks to adjust 451 break; 452 } 453 mark.index = g1; 454 } 455 456 // shift the gap in the character array 457 super.shiftGapStartDown(newGapStart); 458 459 resetMarksAtZero(); 460 } 461 462 /** 463 * Adjust the gap end upward. This doesn't move 464 * any data, but it does update any marks affected 465 * by the boundary change. All marks from the old 466 * gap end up to the new gap end are squeezed 467 * to the end of the gap (their location has been 468 * removed). 469 */ 470 protected void shiftGapEndUp(int newGapEnd) { 471 int adjustIndex = findMarkAdjustIndex(getGapEnd()); 472 int n = marks.size(); 473 for (int i = adjustIndex; i < n; i++) { 474 MarkData mark = marks.elementAt(i); 475 if (mark.index >= newGapEnd) { 476 break; 477 } 478 mark.index = newGapEnd; 479 } 480 481 // shift the gap in the character array 482 super.shiftGapEndUp(newGapEnd); 483 484 resetMarksAtZero(); 485 } 486 487 /** 488 * Compares two marks. 489 * 490 * @param o1 the first object 491 * @param o2 the second object 492 * @return < 0 if o1 < o2, 0 if the same, > 0 if o1 > o2 493 */ 494 final int compare(MarkData o1, MarkData o2) { 495 if (o1.index < o2.index) { 496 return -1; 497 } else if (o1.index > o2.index) { 498 return 1; 499 } else { 500 return 0; 501 } 502 } 503 504 /** 505 * Finds the index to start mark adjustments given 506 * some search index. 507 */ 508 final int findMarkAdjustIndex(int searchIndex) { 509 search.index = Math.max(searchIndex, 1); 510 int index = findSortIndex(search); 511 512 // return the first in the series 513 // (ie. there may be duplicates). 514 for (int i = index - 1; i >= 0; i--) { 515 MarkData d = marks.elementAt(i); 516 if (d.index != search.index) { 517 break; 518 } 519 index -= 1; 520 } 521 return index; 522 } 523 524 /** 525 * Finds the index of where to insert a new mark. 526 * 527 * @param o the mark to insert 528 * @return the index 529 */ 530 final int findSortIndex(MarkData o) { 531 int lower = 0; 532 int upper = marks.size() - 1; 533 int mid = 0; 534 535 if (upper == -1) { 536 return 0; 537 } 538 539 int cmp; 540 MarkData last = marks.elementAt(upper); 541 cmp = compare(o, last); 542 if (cmp > 0) 543 return upper + 1; 544 545 while (lower <= upper) { 546 mid = lower + ((upper - lower) / 2); 547 MarkData entry = marks.elementAt(mid); 548 cmp = compare(o, entry); 549 550 if (cmp == 0) { 551 // found a match 552 return mid; 553 } else if (cmp < 0) { 554 upper = mid - 1; 555 } else { 556 lower = mid + 1; 557 } 558 } 559 560 // didn't find it, but we indicate the index of where it would belong. 561 return (cmp < 0) ? mid : mid + 1; 562 } 563 564 /** 565 * Remove all unused marks out of the sorted collection 566 * of marks. 567 */ 568 final void removeUnusedMarks() { 569 int n = marks.size(); 570 MarkVector cleaned = new MarkVector(n); 571 for (int i = 0; i < n; i++) { 572 MarkData mark = marks.elementAt(i); 573 if (mark.get() != null) { 574 cleaned.addElement(mark); 575 } 576 } 577 marks = cleaned; 578 unusedMarks = 0; 579 } 580 581 @SuppressWarnings("serial") // Superclass is not serializable across versions 582 static class MarkVector extends GapVector { 583 584 MarkVector() { 585 super(); 586 } 587 588 MarkVector(int size) { 589 super(size); 590 } 591 592 /** 593 * Allocate an array to store items of the type 594 * appropriate (which is determined by the subclass). 595 */ 596 protected Object allocateArray(int len) { 597 return new MarkData[len]; 598 } 599 600 /** 601 * Get the length of the allocated array 602 */ 603 protected int getArrayLength() { 604 MarkData[] marks = (MarkData[]) getArray(); 605 return marks.length; 606 } 607 608 /** 609 * Returns the number of marks currently held 610 */ 611 public int size() { 612 int len = getArrayLength() - (getGapEnd() - getGapStart()); 613 return len; 614 } 615 616 /** 617 * Inserts a mark into the vector 618 */ 619 public void insertElementAt(MarkData m, int index) { 620 oneMark[0] = m; 621 replace(index, 0, oneMark, 1); 622 } 623 624 /** 625 * Add a mark to the end 626 */ 627 public void addElement(MarkData m) { 628 insertElementAt(m, size()); 629 } 630 631 /** 632 * Fetches the mark at the given index 633 */ 634 public MarkData elementAt(int index) { 635 int g0 = getGapStart(); 636 int g1 = getGapEnd(); 637 MarkData[] array = (MarkData[]) getArray(); 638 if (index < g0) { 639 // below gap 640 return array[index]; 641 } else { 642 // above gap 643 index += g1 - g0; 644 return array[index]; 645 } 646 } 647 648 /** 649 * Replaces the elements in the specified range with the passed 650 * in objects. This will NOT adjust the gap. The passed in indices 651 * do not account for the gap, they are the same as would be used 652 * int <code>elementAt</code>. 653 */ 654 protected void replaceRange(int start, int end, Object[] marks) { 655 int g0 = getGapStart(); 656 int g1 = getGapEnd(); 657 int index = start; 658 int newIndex = 0; 659 Object[] array = (Object[]) getArray(); 660 if (start >= g0) { 661 // Completely passed gap 662 index += (g1 - g0); 663 end += (g1 - g0); 664 } 665 else if (end >= g0) { 666 // straddles gap 667 end += (g1 - g0); 668 while (index < g0) { 669 array[index++] = marks[newIndex++]; 670 } 671 index = g1; 672 } 673 else { 674 // below gap 675 while (index < end) { 676 array[index++] = marks[newIndex++]; 677 } 678 } 679 while (index < end) { 680 array[index++] = marks[newIndex++]; 681 } 682 } 683 684 MarkData[] oneMark = new MarkData[1]; 685 686 } 687 688 // --- serialization ------------------------------------- 689 690 private void readObject(ObjectInputStream s) 691 throws ClassNotFoundException, IOException { 692 s.defaultReadObject(); 693 marks = new MarkVector(); 694 search = new MarkData(0); 695 queue = new ReferenceQueue<StickyPosition>(); 696 } 697 698 699 // --- undo support -------------------------------------- 700 701 /** 702 * Returns a Vector containing instances of UndoPosRef for the 703 * Positions in the range 704 * <code>offset</code> to <code>offset</code> + <code>length</code>. 705 * If <code>v</code> is not null the matching Positions are placed in 706 * there. The vector with the resulting Positions are returned. 707 * 708 * @param v the Vector to use, with a new one created on null 709 * @param offset the starting offset >= 0 710 * @param length the length >= 0 711 * @return the set of instances 712 */ 713 protected Vector<UndoPosRef> getPositionsInRange(Vector<UndoPosRef> v, 714 int offset, int length) { 715 int endOffset = offset + length; 716 int startIndex; 717 int endIndex; 718 int g0 = getGapStart(); 719 int g1 = getGapEnd(); 720 721 // Find the index of the marks. 722 if (offset < g0) { 723 if (offset == 0) { 724 // findMarkAdjustIndex start at 1! 725 startIndex = 0; 726 } 727 else { 728 startIndex = findMarkAdjustIndex(offset); 729 } 730 if (endOffset >= g0) { 731 endIndex = findMarkAdjustIndex(endOffset + (g1 - g0) + 1); 732 } 733 else { 734 endIndex = findMarkAdjustIndex(endOffset + 1); 735 } 736 } 737 else { 738 startIndex = findMarkAdjustIndex(offset + (g1 - g0)); 739 endIndex = findMarkAdjustIndex(endOffset + (g1 - g0) + 1); 740 } 741 742 Vector<UndoPosRef> placeIn = (v == null) ? 743 new Vector<>(Math.max(1, endIndex - startIndex)) : 744 v; 745 746 for (int counter = startIndex; counter < endIndex; counter++) { 747 placeIn.addElement(new UndoPosRef(marks.elementAt(counter))); 748 } 749 return placeIn; 750 } 751 752 /** 753 * Resets the location for all the UndoPosRef instances 754 * in <code>positions</code>. 755 * <p> 756 * This is meant for internal usage, and is generally not of interest 757 * to subclasses. 758 * 759 * @param positions the UndoPosRef instances to reset 760 */ 761 protected void updateUndoPositions(Vector<UndoPosRef> positions, int offset, 762 int length) { 763 // Find the indexs of the end points. 764 int endOffset = offset + length; 765 int g1 = getGapEnd(); 766 int startIndex; 767 int endIndex = findMarkAdjustIndex(g1 + 1); 768 769 if (offset != 0) { 770 startIndex = findMarkAdjustIndex(g1); 771 } 772 else { 773 startIndex = 0; 774 } 775 776 // Reset the location of the refenences. 777 for(int counter = positions.size() - 1; counter >= 0; counter--) { 778 UndoPosRef ref = positions.elementAt(counter); 779 ref.resetLocation(endOffset, g1); 780 } 781 // We have to resort the marks in the range startIndex to endIndex. 782 // We can take advantage of the fact that it will be in 783 // increasing order, accept there will be a bunch of MarkData's with 784 // the index g1 (or 0 if offset == 0) interspersed throughout. 785 if (startIndex < endIndex) { 786 Object[] sorted = new Object[endIndex - startIndex]; 787 int addIndex = 0; 788 int counter; 789 if (offset == 0) { 790 // If the offset is 0, the positions won't have incremented, 791 // have to do the reverse thing. 792 // Find the elements in startIndex whose index is 0 793 for (counter = startIndex; counter < endIndex; counter++) { 794 MarkData mark = marks.elementAt(counter); 795 if (mark.index == 0) { 796 sorted[addIndex++] = mark; 797 } 798 } 799 for (counter = startIndex; counter < endIndex; counter++) { 800 MarkData mark = marks.elementAt(counter); 801 if (mark.index != 0) { 802 sorted[addIndex++] = mark; 803 } 804 } 805 } 806 else { 807 for (counter = startIndex; counter < endIndex; counter++) { 808 MarkData mark = marks.elementAt(counter); 809 if (mark.index != g1) { 810 sorted[addIndex++] = mark; 811 } 812 } 813 for (counter = startIndex; counter < endIndex; counter++) { 814 MarkData mark = marks.elementAt(counter); 815 if (mark.index == g1) { 816 sorted[addIndex++] = mark; 817 } 818 } 819 } 820 // And replace 821 marks.replaceRange(startIndex, endIndex, sorted); 822 } 823 } 824 825 /** 826 * Used to hold a reference to a Mark that is being reset as the 827 * result of removing from the content. 828 */ 829 final class UndoPosRef { 830 UndoPosRef(MarkData rec) { 831 this.rec = rec; 832 this.undoLocation = rec.getOffset(); 833 } 834 835 /** 836 * Resets the location of the Position to the offset when the 837 * receiver was instantiated. 838 * 839 * @param endOffset end location of inserted string. 840 * @param g1 resulting end of gap. 841 */ 842 protected void resetLocation(int endOffset, int g1) { 843 if (undoLocation != endOffset) { 844 this.rec.index = undoLocation; 845 } 846 else { 847 this.rec.index = g1; 848 } 849 } 850 851 /** Previous Offset of rec. */ 852 protected int undoLocation; 853 /** Mark to reset offset. */ 854 protected MarkData rec; 855 } // End of GapContent.UndoPosRef 856 857 858 /** 859 * UnoableEdit created for inserts. 860 */ 861 @SuppressWarnings("serial") // Superclass is a JDK-implementation class 862 class InsertUndo extends AbstractUndoableEdit { 863 protected InsertUndo(int offset, int length) { 864 super(); 865 this.offset = offset; 866 this.length = length; 867 } 868 869 public void undo() throws CannotUndoException { 870 super.undo(); 871 try { 872 // Get the Positions in the range being removed. 873 posRefs = getPositionsInRange(null, offset, length); 874 string = getString(offset, length); 875 remove(offset, length); 876 } catch (BadLocationException bl) { 877 throw new CannotUndoException(); 878 } 879 } 880 881 public void redo() throws CannotRedoException { 882 super.redo(); 883 try { 884 insertString(offset, string); 885 string = null; 886 // Update the Positions that were in the range removed. 887 if(posRefs != null) { 888 updateUndoPositions(posRefs, offset, length); 889 posRefs = null; 890 } 891 } catch (BadLocationException bl) { 892 throw new CannotRedoException(); 893 } 894 } 895 896 /** Where string was inserted. */ 897 protected int offset; 898 /** Length of string inserted. */ 899 protected int length; 900 /** The string that was inserted. This will only be valid after an 901 * undo. */ 902 protected String string; 903 /** An array of instances of UndoPosRef for the Positions in the 904 * range that was removed, valid after undo. */ 905 protected Vector<UndoPosRef> posRefs; 906 } // GapContent.InsertUndo 907 908 909 /** 910 * UndoableEdit created for removes. 911 */ 912 @SuppressWarnings("serial") // JDK-implementation class 913 class RemoveUndo extends AbstractUndoableEdit { 914 protected RemoveUndo(int offset, String string) { 915 super(); 916 this.offset = offset; 917 this.string = string; 918 this.length = string.length(); 919 posRefs = getPositionsInRange(null, offset, length); 920 } 921 922 public void undo() throws CannotUndoException { 923 super.undo(); 924 try { 925 insertString(offset, string); 926 // Update the Positions that were in the range removed. 927 if(posRefs != null) { 928 updateUndoPositions(posRefs, offset, length); 929 posRefs = null; 930 } 931 string = null; 932 } catch (BadLocationException bl) { 933 throw new CannotUndoException(); 934 } 935 } 936 937 public void redo() throws CannotRedoException { 938 super.redo(); 939 try { 940 string = getString(offset, length); 941 // Get the Positions in the range being removed. 942 posRefs = getPositionsInRange(null, offset, length); 943 remove(offset, length); 944 } catch (BadLocationException bl) { 945 throw new CannotRedoException(); 946 } 947 } 948 949 /** Where the string was removed from. */ 950 protected int offset; 951 /** Length of string removed. */ 952 protected int length; 953 /** The string that was removed. This is valid when redo is valid. */ 954 protected String string; 955 /** An array of instances of UndoPosRef for the Positions in the 956 * range that was removed, valid before undo. */ 957 protected Vector<UndoPosRef> posRefs; 958 } // GapContent.RemoveUndo 959 }