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 &gt;= 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 &gt;= 0, &lt; 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 &gt;= 0, where + nitems &lt; length()
148      * @param nitems the number of characters to remove &gt;= 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 &gt;= 0
168      * @param len the length to retrieve &gt;= 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 &gt;= 0, where + len &lt;= length()
186      * @param len the number of characters to retrieve &gt;= 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 &gt;= 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 &gt;= 0
721      * @param length the length &gt;= 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