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