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