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 &gt;= 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 &gt;= 0, &lt; 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 &gt;= 0, where + nitems &lt; length()
 141      * @param nitems the number of characters to remove &gt;= 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 &gt;= 0
 161      * @param len the length to retrieve &gt;= 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 &gt;= 0, where + len &lt;= length()
 179      * @param len the number of characters to retrieve &gt;= 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 &gt;= 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 &gt;= 0
 710      * @param length the length &gt;= 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 }