0N/A/*
2362N/A * Copyright (c) 1998, 2008, Oracle and/or its affiliates. All rights reserved.
0N/A * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
0N/A *
0N/A * This code is free software; you can redistribute it and/or modify it
0N/A * under the terms of the GNU General Public License version 2 only, as
2362N/A * published by the Free Software Foundation. Oracle designates this
0N/A * particular file as subject to the "Classpath" exception as provided
2362N/A * by Oracle in the LICENSE file that accompanied this code.
0N/A *
0N/A * This code is distributed in the hope that it will be useful, but WITHOUT
0N/A * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
0N/A * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
0N/A * version 2 for more details (a copy is included in the LICENSE file that
0N/A * accompanied this code).
0N/A *
0N/A * You should have received a copy of the GNU General Public License version
0N/A * 2 along with this work; if not, write to the Free Software Foundation,
0N/A * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
0N/A *
2362N/A * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
2362N/A * or visit www.oracle.com if you need additional information or have any
2362N/A * questions.
0N/A */
0N/Apackage javax.swing.text;
0N/A
0N/Aimport java.util.Vector;
0N/Aimport java.io.IOException;
0N/Aimport java.io.ObjectInputStream;
0N/Aimport java.io.Serializable;
0N/Aimport javax.swing.undo.AbstractUndoableEdit;
0N/Aimport javax.swing.undo.CannotRedoException;
0N/Aimport javax.swing.undo.CannotUndoException;
0N/Aimport javax.swing.undo.UndoableEdit;
0N/Aimport javax.swing.SwingUtilities;
0N/Aimport java.lang.ref.WeakReference;
0N/Aimport java.lang.ref.ReferenceQueue;
0N/A
0N/A/**
0N/A * An implementation of the AbstractDocument.Content interface
0N/A * implemented using a gapped buffer similar to that used by emacs.
0N/A * The underlying storage is a array of unicode characters with
0N/A * a gap somewhere. The gap is moved to the location of changes
0N/A * to take advantage of common behavior where most changes are
0N/A * in the same location. Changes that occur at a gap boundary are
0N/A * generally cheap and moving the gap is generally cheaper than
0N/A * moving the array contents directly to accomodate the change.
0N/A * <p>
0N/A * The positions tracking change are also generally cheap to
0N/A * maintain. The Position implementations (marks) store the array
0N/A * index and can easily calculate the sequential position from
0N/A * the current gap location. Changes only require update to the
0N/A * the marks between the old and new gap boundaries when the gap
0N/A * is moved, so generally updating the marks is pretty cheap.
0N/A * The marks are stored sorted so they can be located quickly
0N/A * with a binary search. This increases the cost of adding a
0N/A * mark, and decreases the cost of keeping the mark updated.
0N/A *
0N/A * @author Timothy Prinzing
0N/A */
0N/Apublic class GapContent extends GapVector implements AbstractDocument.Content, Serializable {
0N/A
0N/A /**
0N/A * Creates a new GapContent object. Initial size defaults to 10.
0N/A */
0N/A public GapContent() {
0N/A this(10);
0N/A }
0N/A
0N/A /**
0N/A * Creates a new GapContent object, with the initial
0N/A * size specified. The initial size will not be allowed
0N/A * to go below 2, to give room for the implied break and
0N/A * the gap.
0N/A *
0N/A * @param initialLength the initial size
0N/A */
0N/A public GapContent(int initialLength) {
0N/A super(Math.max(initialLength,2));
0N/A char[] implied = new char[1];
0N/A implied[0] = '\n';
0N/A replace(0, 0, implied, implied.length);
0N/A
0N/A marks = new MarkVector();
0N/A search = new MarkData(0);
611N/A queue = new ReferenceQueue<StickyPosition>();
0N/A }
0N/A
0N/A /**
0N/A * Allocate an array to store items of the type
0N/A * appropriate (which is determined by the subclass).
0N/A */
0N/A protected Object allocateArray(int len) {
0N/A return new char[len];
0N/A }
0N/A
0N/A /**
0N/A * Get the length of the allocated array.
0N/A */
0N/A protected int getArrayLength() {
0N/A char[] carray = (char[]) getArray();
0N/A return carray.length;
0N/A }
0N/A
0N/A // --- AbstractDocument.Content methods -------------------------
0N/A
0N/A /**
0N/A * Returns the length of the content.
0N/A *
0N/A * @return the length >= 1
0N/A * @see AbstractDocument.Content#length
0N/A */
0N/A public int length() {
0N/A int len = getArrayLength() - (getGapEnd() - getGapStart());
0N/A return len;
0N/A }
0N/A
0N/A /**
0N/A * Inserts a string into the content.
0N/A *
0N/A * @param where the starting position >= 0, < length()
0N/A * @param str the non-null string to insert
0N/A * @return an UndoableEdit object for undoing
0N/A * @exception BadLocationException if the specified position is invalid
0N/A * @see AbstractDocument.Content#insertString
0N/A */
0N/A public UndoableEdit insertString(int where, String str) throws BadLocationException {
0N/A if (where > length() || where < 0) {
0N/A throw new BadLocationException("Invalid insert", length());
0N/A }
0N/A char[] chars = str.toCharArray();
0N/A replace(where, 0, chars, chars.length);
0N/A return new InsertUndo(where, str.length());
0N/A }
0N/A
0N/A /**
0N/A * Removes part of the content.
0N/A *
0N/A * @param where the starting position >= 0, where + nitems < length()
0N/A * @param nitems the number of characters to remove >= 0
0N/A * @return an UndoableEdit object for undoing
0N/A * @exception BadLocationException if the specified position is invalid
0N/A * @see AbstractDocument.Content#remove
0N/A */
0N/A public UndoableEdit remove(int where, int nitems) throws BadLocationException {
0N/A if (where + nitems >= length()) {
0N/A throw new BadLocationException("Invalid remove", length() + 1);
0N/A }
0N/A String removedString = getString(where, nitems);
0N/A UndoableEdit edit = new RemoveUndo(where, removedString);
0N/A replace(where, nitems, empty, 0);
0N/A return edit;
0N/A
0N/A }
0N/A
0N/A /**
0N/A * Retrieves a portion of the content.
0N/A *
0N/A * @param where the starting position >= 0
0N/A * @param len the length to retrieve >= 0
0N/A * @return a string representing the content
0N/A * @exception BadLocationException if the specified position is invalid
0N/A * @see AbstractDocument.Content#getString
0N/A */
0N/A public String getString(int where, int len) throws BadLocationException {
0N/A Segment s = new Segment();
0N/A getChars(where, len, s);
0N/A return new String(s.array, s.offset, s.count);
0N/A }
0N/A
0N/A /**
0N/A * Retrieves a portion of the content. If the desired content spans
0N/A * the gap, we copy the content. If the desired content does not
0N/A * span the gap, the actual store is returned to avoid the copy since
0N/A * it is contiguous.
0N/A *
0N/A * @param where the starting position >= 0, where + len <= length()
0N/A * @param len the number of characters to retrieve >= 0
0N/A * @param chars the Segment object to return the characters in
0N/A * @exception BadLocationException if the specified position is invalid
0N/A * @see AbstractDocument.Content#getChars
0N/A */
0N/A public void getChars(int where, int len, Segment chars) throws BadLocationException {
0N/A int end = where + len;
0N/A if (where < 0 || end < 0) {
0N/A throw new BadLocationException("Invalid location", -1);
0N/A }
0N/A if (end > length() || where > length()) {
0N/A throw new BadLocationException("Invalid location", length() + 1);
0N/A }
0N/A int g0 = getGapStart();
0N/A int g1 = getGapEnd();
0N/A char[] array = (char[]) getArray();
0N/A if ((where + len) <= g0) {
0N/A // below gap
0N/A chars.array = array;
0N/A chars.offset = where;
0N/A } else if (where >= g0) {
0N/A // above gap
0N/A chars.array = array;
0N/A chars.offset = g1 + where - g0;
0N/A } else {
0N/A // spans the gap
0N/A int before = g0 - where;
0N/A if (chars.isPartialReturn()) {
0N/A // partial return allowed, return amount before the gap
0N/A chars.array = array;
0N/A chars.offset = where;
0N/A chars.count = before;
0N/A return;
0N/A }
0N/A // partial return not allowed, must copy
0N/A chars.array = new char[len];
0N/A chars.offset = 0;
0N/A System.arraycopy(array, where, chars.array, 0, before);
0N/A System.arraycopy(array, g1, chars.array, before, len - before);
0N/A }
0N/A chars.count = len;
0N/A }
0N/A
0N/A /**
0N/A * Creates a position within the content that will
0N/A * track change as the content is mutated.
0N/A *
0N/A * @param offset the offset to track >= 0
0N/A * @return the position
0N/A * @exception BadLocationException if the specified position is invalid
0N/A */
0N/A public Position createPosition(int offset) throws BadLocationException {
0N/A while ( queue.poll() != null ) {
0N/A unusedMarks++;
0N/A }
0N/A if (unusedMarks > Math.max(5, (marks.size() / 10))) {
0N/A removeUnusedMarks();
0N/A }
0N/A int g0 = getGapStart();
0N/A int g1 = getGapEnd();
0N/A int index = (offset < g0) ? offset : offset + (g1 - g0);
0N/A search.index = index;
0N/A int sortIndex = findSortIndex(search);
0N/A MarkData m;
0N/A StickyPosition position;
0N/A if (sortIndex < marks.size()
0N/A && (m = marks.elementAt(sortIndex)).index == index
0N/A && (position = m.getPosition()) != null) {
0N/A //position references the correct StickyPostition
0N/A } else {
0N/A position = new StickyPosition();
0N/A m = new MarkData(index,position,queue);
0N/A position.setMark(m);
0N/A marks.insertElementAt(m, sortIndex);
0N/A }
0N/A
0N/A return position;
0N/A }
0N/A
0N/A /**
0N/A * Holds the data for a mark... separately from
0N/A * the real mark so that the real mark (Position
0N/A * that the caller of createPosition holds) can be
0N/A * collected if there are no more references to
0N/A * it. The update table holds only a reference
0N/A * to this data.
0N/A */
611N/A final class MarkData extends WeakReference<StickyPosition> {
0N/A
0N/A MarkData(int index) {
0N/A super(null);
0N/A this.index = index;
0N/A }
611N/A MarkData(int index, StickyPosition position, ReferenceQueue<? super StickyPosition> queue) {
0N/A super(position, queue);
0N/A this.index = index;
0N/A }
0N/A
0N/A /**
0N/A * Fetch the location in the contiguous sequence
0N/A * being modeled. The index in the gap array
0N/A * is held by the mark, so it is adjusted according
0N/A * to it's relationship to the gap.
0N/A */
0N/A public final int getOffset() {
0N/A int g0 = getGapStart();
0N/A int g1 = getGapEnd();
0N/A int offs = (index < g0) ? index : index - (g1 - g0);
0N/A return Math.max(offs, 0);
0N/A }
0N/A
0N/A StickyPosition getPosition() {
611N/A return get();
0N/A }
0N/A int index;
0N/A }
0N/A
0N/A final class StickyPosition implements Position {
0N/A
0N/A StickyPosition() {
0N/A }
0N/A
0N/A void setMark(MarkData mark) {
0N/A this.mark = mark;
0N/A }
0N/A
0N/A public final int getOffset() {
0N/A return mark.getOffset();
0N/A }
0N/A
0N/A public String toString() {
0N/A return Integer.toString(getOffset());
0N/A }
0N/A
0N/A MarkData mark;
0N/A }
0N/A
0N/A // --- variables --------------------------------------
0N/A
0N/A private static final char[] empty = new char[0];
0N/A private transient MarkVector marks;
0N/A
0N/A /**
0N/A * Record used for searching for the place to
0N/A * start updating mark indexs when the gap
0N/A * boundaries are moved.
0N/A */
0N/A private transient MarkData search;
0N/A
0N/A /**
0N/A * The number of unused mark entries
0N/A */
0N/A private transient int unusedMarks = 0;
0N/A
611N/A private transient ReferenceQueue<StickyPosition> queue;
0N/A
0N/A final static int GROWTH_SIZE = 1024 * 512;
0N/A
0N/A // --- gap management -------------------------------
0N/A
0N/A /**
0N/A * Make the gap bigger, moving any necessary data and updating
0N/A * the appropriate marks
0N/A */
0N/A protected void shiftEnd(int newSize) {
0N/A int oldGapEnd = getGapEnd();
0N/A
0N/A super.shiftEnd(newSize);
0N/A
0N/A // Adjust marks.
0N/A int dg = getGapEnd() - oldGapEnd;
0N/A int adjustIndex = findMarkAdjustIndex(oldGapEnd);
0N/A int n = marks.size();
0N/A for (int i = adjustIndex; i < n; i++) {
0N/A MarkData mark = marks.elementAt(i);
0N/A mark.index += dg;
0N/A }
0N/A }
0N/A
0N/A /**
0N/A * Overridden to make growth policy less agressive for large
0N/A * text amount.
0N/A */
0N/A int getNewArraySize(int reqSize) {
0N/A if (reqSize < GROWTH_SIZE) {
0N/A return super.getNewArraySize(reqSize);
0N/A } else {
0N/A return reqSize + GROWTH_SIZE;
0N/A }
0N/A }
0N/A
0N/A /**
0N/A * Move the start of the gap to a new location,
0N/A * without changing the size of the gap. This
0N/A * moves the data in the array and updates the
0N/A * marks accordingly.
0N/A */
0N/A protected void shiftGap(int newGapStart) {
0N/A int oldGapStart = getGapStart();
0N/A int dg = newGapStart - oldGapStart;
0N/A int oldGapEnd = getGapEnd();
0N/A int newGapEnd = oldGapEnd + dg;
0N/A int gapSize = oldGapEnd - oldGapStart;
0N/A
0N/A // shift gap in the character array
0N/A super.shiftGap(newGapStart);
0N/A
0N/A // update the marks
0N/A if (dg > 0) {
0N/A // Move gap up, move data and marks down.
0N/A int adjustIndex = findMarkAdjustIndex(oldGapStart);
0N/A int n = marks.size();
0N/A for (int i = adjustIndex; i < n; i++) {
0N/A MarkData mark = marks.elementAt(i);
0N/A if (mark.index >= newGapEnd) {
0N/A break;
0N/A }
0N/A mark.index -= gapSize;
0N/A }
0N/A } else if (dg < 0) {
0N/A // Move gap down, move data and marks up.
0N/A int adjustIndex = findMarkAdjustIndex(newGapStart);
0N/A int n = marks.size();
0N/A for (int i = adjustIndex; i < n; i++) {
0N/A MarkData mark = marks.elementAt(i);
0N/A if (mark.index >= oldGapEnd) {
0N/A break;
0N/A }
0N/A mark.index += gapSize;
0N/A }
0N/A }
0N/A resetMarksAtZero();
0N/A }
0N/A
0N/A /**
0N/A * Resets all the marks that have an offset of 0 to have an index of
0N/A * zero as well.
0N/A */
0N/A protected void resetMarksAtZero() {
0N/A if (marks != null && getGapStart() == 0) {
0N/A int g1 = getGapEnd();
0N/A for (int counter = 0, maxCounter = marks.size();
0N/A counter < maxCounter; counter++) {
0N/A MarkData mark = marks.elementAt(counter);
0N/A if (mark.index <= g1) {
0N/A mark.index = 0;
0N/A }
0N/A else {
0N/A break;
0N/A }
0N/A }
0N/A }
0N/A }
0N/A
0N/A /**
0N/A * Adjust the gap end downward. This doesn't move
0N/A * any data, but it does update any marks affected
0N/A * by the boundary change. All marks from the old
0N/A * gap start down to the new gap start are squeezed
0N/A * to the end of the gap (their location has been
0N/A * removed).
0N/A */
0N/A protected void shiftGapStartDown(int newGapStart) {
0N/A // Push aside all marks from oldGapStart down to newGapStart.
0N/A int adjustIndex = findMarkAdjustIndex(newGapStart);
0N/A int n = marks.size();
0N/A int g0 = getGapStart();
0N/A int g1 = getGapEnd();
0N/A for (int i = adjustIndex; i < n; i++) {
0N/A MarkData mark = marks.elementAt(i);
0N/A if (mark.index > g0) {
0N/A // no more marks to adjust
0N/A break;
0N/A }
0N/A mark.index = g1;
0N/A }
0N/A
0N/A // shift the gap in the character array
0N/A super.shiftGapStartDown(newGapStart);
0N/A
0N/A resetMarksAtZero();
0N/A }
0N/A
0N/A /**
0N/A * Adjust the gap end upward. This doesn't move
0N/A * any data, but it does update any marks affected
0N/A * by the boundary change. All marks from the old
0N/A * gap end up to the new gap end are squeezed
0N/A * to the end of the gap (their location has been
0N/A * removed).
0N/A */
0N/A protected void shiftGapEndUp(int newGapEnd) {
0N/A int adjustIndex = findMarkAdjustIndex(getGapEnd());
0N/A int n = marks.size();
0N/A for (int i = adjustIndex; i < n; i++) {
0N/A MarkData mark = marks.elementAt(i);
0N/A if (mark.index >= newGapEnd) {
0N/A break;
0N/A }
0N/A mark.index = newGapEnd;
0N/A }
0N/A
0N/A // shift the gap in the character array
0N/A super.shiftGapEndUp(newGapEnd);
0N/A
0N/A resetMarksAtZero();
0N/A }
0N/A
0N/A /**
0N/A * Compares two marks.
0N/A *
0N/A * @param o1 the first object
0N/A * @param o2 the second object
0N/A * @return < 0 if o1 < o2, 0 if the same, > 0 if o1 > o2
0N/A */
0N/A final int compare(MarkData o1, MarkData o2) {
0N/A if (o1.index < o2.index) {
0N/A return -1;
0N/A } else if (o1.index > o2.index) {
0N/A return 1;
0N/A } else {
0N/A return 0;
0N/A }
0N/A }
0N/A
0N/A /**
0N/A * Finds the index to start mark adjustments given
0N/A * some search index.
0N/A */
0N/A final int findMarkAdjustIndex(int searchIndex) {
0N/A search.index = Math.max(searchIndex, 1);
0N/A int index = findSortIndex(search);
0N/A
0N/A // return the first in the series
0N/A // (ie. there may be duplicates).
0N/A for (int i = index - 1; i >= 0; i--) {
0N/A MarkData d = marks.elementAt(i);
0N/A if (d.index != search.index) {
0N/A break;
0N/A }
0N/A index -= 1;
0N/A }
0N/A return index;
0N/A }
0N/A
0N/A /**
0N/A * Finds the index of where to insert a new mark.
0N/A *
0N/A * @param o the mark to insert
0N/A * @return the index
0N/A */
0N/A final int findSortIndex(MarkData o) {
0N/A int lower = 0;
0N/A int upper = marks.size() - 1;
0N/A int mid = 0;
0N/A
0N/A if (upper == -1) {
0N/A return 0;
0N/A }
0N/A
611N/A int cmp;
0N/A MarkData last = marks.elementAt(upper);
0N/A cmp = compare(o, last);
0N/A if (cmp > 0)
0N/A return upper + 1;
0N/A
0N/A while (lower <= upper) {
0N/A mid = lower + ((upper - lower) / 2);
0N/A MarkData entry = marks.elementAt(mid);
0N/A cmp = compare(o, entry);
0N/A
0N/A if (cmp == 0) {
0N/A // found a match
0N/A return mid;
0N/A } else if (cmp < 0) {
0N/A upper = mid - 1;
0N/A } else {
0N/A lower = mid + 1;
0N/A }
0N/A }
0N/A
0N/A // didn't find it, but we indicate the index of where it would belong.
0N/A return (cmp < 0) ? mid : mid + 1;
0N/A }
0N/A
0N/A /**
0N/A * Remove all unused marks out of the sorted collection
0N/A * of marks.
0N/A */
0N/A final void removeUnusedMarks() {
0N/A int n = marks.size();
0N/A MarkVector cleaned = new MarkVector(n);
0N/A for (int i = 0; i < n; i++) {
0N/A MarkData mark = marks.elementAt(i);
0N/A if (mark.get() != null) {
0N/A cleaned.addElement(mark);
0N/A }
0N/A }
0N/A marks = cleaned;
0N/A unusedMarks = 0;
0N/A }
0N/A
0N/A
0N/A static class MarkVector extends GapVector {
0N/A
0N/A MarkVector() {
0N/A super();
0N/A }
0N/A
0N/A MarkVector(int size) {
0N/A super(size);
0N/A }
0N/A
0N/A /**
0N/A * Allocate an array to store items of the type
0N/A * appropriate (which is determined by the subclass).
0N/A */
0N/A protected Object allocateArray(int len) {
0N/A return new MarkData[len];
0N/A }
0N/A
0N/A /**
0N/A * Get the length of the allocated array
0N/A */
0N/A protected int getArrayLength() {
0N/A MarkData[] marks = (MarkData[]) getArray();
0N/A return marks.length;
0N/A }
0N/A
0N/A /**
0N/A * Returns the number of marks currently held
0N/A */
0N/A public int size() {
0N/A int len = getArrayLength() - (getGapEnd() - getGapStart());
0N/A return len;
0N/A }
0N/A
0N/A /**
0N/A * Inserts a mark into the vector
0N/A */
0N/A public void insertElementAt(MarkData m, int index) {
0N/A oneMark[0] = m;
0N/A replace(index, 0, oneMark, 1);
0N/A }
0N/A
0N/A /**
0N/A * Add a mark to the end
0N/A */
0N/A public void addElement(MarkData m) {
0N/A insertElementAt(m, size());
0N/A }
0N/A
0N/A /**
0N/A * Fetches the mark at the given index
0N/A */
0N/A public MarkData elementAt(int index) {
0N/A int g0 = getGapStart();
0N/A int g1 = getGapEnd();
0N/A MarkData[] array = (MarkData[]) getArray();
0N/A if (index < g0) {
0N/A // below gap
0N/A return array[index];
0N/A } else {
0N/A // above gap
0N/A index += g1 - g0;
0N/A return array[index];
0N/A }
0N/A }
0N/A
0N/A /**
0N/A * Replaces the elements in the specified range with the passed
0N/A * in objects. This will NOT adjust the gap. The passed in indices
0N/A * do not account for the gap, they are the same as would be used
0N/A * int <code>elementAt</code>.
0N/A */
0N/A protected void replaceRange(int start, int end, Object[] marks) {
0N/A int g0 = getGapStart();
0N/A int g1 = getGapEnd();
0N/A int index = start;
0N/A int newIndex = 0;
0N/A Object[] array = (Object[]) getArray();
0N/A if (start >= g0) {
0N/A // Completely passed gap
0N/A index += (g1 - g0);
0N/A end += (g1 - g0);
0N/A }
0N/A else if (end >= g0) {
0N/A // straddles gap
0N/A end += (g1 - g0);
0N/A while (index < g0) {
0N/A array[index++] = marks[newIndex++];
0N/A }
0N/A index = g1;
0N/A }
0N/A else {
0N/A // below gap
0N/A while (index < end) {
0N/A array[index++] = marks[newIndex++];
0N/A }
0N/A }
0N/A while (index < end) {
0N/A array[index++] = marks[newIndex++];
0N/A }
0N/A }
0N/A
0N/A MarkData[] oneMark = new MarkData[1];
0N/A
0N/A }
0N/A
0N/A // --- serialization -------------------------------------
0N/A
0N/A private void readObject(ObjectInputStream s)
0N/A throws ClassNotFoundException, IOException {
0N/A s.defaultReadObject();
0N/A marks = new MarkVector();
0N/A search = new MarkData(0);
611N/A queue = new ReferenceQueue<StickyPosition>();
0N/A }
0N/A
0N/A
0N/A // --- undo support --------------------------------------
0N/A
0N/A /**
0N/A * Returns a Vector containing instances of UndoPosRef for the
0N/A * Positions in the range
0N/A * <code>offset</code> to <code>offset</code> + <code>length</code>.
0N/A * If <code>v</code> is not null the matching Positions are placed in
0N/A * there. The vector with the resulting Positions are returned.
0N/A *
0N/A * @param v the Vector to use, with a new one created on null
0N/A * @param offset the starting offset >= 0
0N/A * @param length the length >= 0
0N/A * @return the set of instances
0N/A */
0N/A protected Vector getPositionsInRange(Vector v, int offset, int length) {
0N/A int endOffset = offset + length;
0N/A int startIndex;
0N/A int endIndex;
0N/A int g0 = getGapStart();
0N/A int g1 = getGapEnd();
0N/A
0N/A // Find the index of the marks.
0N/A if (offset < g0) {
0N/A if (offset == 0) {
0N/A // findMarkAdjustIndex start at 1!
0N/A startIndex = 0;
0N/A }
0N/A else {
0N/A startIndex = findMarkAdjustIndex(offset);
0N/A }
0N/A if (endOffset >= g0) {
0N/A endIndex = findMarkAdjustIndex(endOffset + (g1 - g0) + 1);
0N/A }
0N/A else {
0N/A endIndex = findMarkAdjustIndex(endOffset + 1);
0N/A }
0N/A }
0N/A else {
0N/A startIndex = findMarkAdjustIndex(offset + (g1 - g0));
0N/A endIndex = findMarkAdjustIndex(endOffset + (g1 - g0) + 1);
0N/A }
0N/A
0N/A Vector placeIn = (v == null) ? new Vector(Math.max(1, endIndex -
0N/A startIndex)) : v;
0N/A
0N/A for (int counter = startIndex; counter < endIndex; counter++) {
0N/A placeIn.addElement(new UndoPosRef(marks.elementAt(counter)));
0N/A }
0N/A return placeIn;
0N/A }
0N/A
0N/A /**
0N/A * Resets the location for all the UndoPosRef instances
0N/A * in <code>positions</code>.
0N/A * <p>
0N/A * This is meant for internal usage, and is generally not of interest
0N/A * to subclasses.
0N/A *
0N/A * @param positions the UndoPosRef instances to reset
0N/A */
0N/A protected void updateUndoPositions(Vector positions, int offset,
0N/A int length) {
0N/A // Find the indexs of the end points.
0N/A int endOffset = offset + length;
0N/A int g1 = getGapEnd();
0N/A int startIndex;
0N/A int endIndex = findMarkAdjustIndex(g1 + 1);
0N/A
0N/A if (offset != 0) {
0N/A startIndex = findMarkAdjustIndex(g1);
0N/A }
0N/A else {
0N/A startIndex = 0;
0N/A }
0N/A
0N/A // Reset the location of the refenences.
0N/A for(int counter = positions.size() - 1; counter >= 0; counter--) {
0N/A UndoPosRef ref = (UndoPosRef)positions.elementAt(counter);
0N/A ref.resetLocation(endOffset, g1);
0N/A }
0N/A // We have to resort the marks in the range startIndex to endIndex.
0N/A // We can take advantage of the fact that it will be in
0N/A // increasing order, accept there will be a bunch of MarkData's with
0N/A // the index g1 (or 0 if offset == 0) interspersed throughout.
0N/A if (startIndex < endIndex) {
0N/A Object[] sorted = new Object[endIndex - startIndex];
0N/A int addIndex = 0;
0N/A int counter;
0N/A if (offset == 0) {
0N/A // If the offset is 0, the positions won't have incremented,
0N/A // have to do the reverse thing.
0N/A // Find the elements in startIndex whose index is 0
0N/A for (counter = startIndex; counter < endIndex; counter++) {
0N/A MarkData mark = marks.elementAt(counter);
0N/A if (mark.index == 0) {
0N/A sorted[addIndex++] = mark;
0N/A }
0N/A }
0N/A for (counter = startIndex; counter < endIndex; counter++) {
0N/A MarkData mark = marks.elementAt(counter);
0N/A if (mark.index != 0) {
0N/A sorted[addIndex++] = mark;
0N/A }
0N/A }
0N/A }
0N/A else {
0N/A for (counter = startIndex; counter < endIndex; counter++) {
0N/A MarkData mark = marks.elementAt(counter);
0N/A if (mark.index != g1) {
0N/A sorted[addIndex++] = mark;
0N/A }
0N/A }
0N/A for (counter = startIndex; counter < endIndex; counter++) {
0N/A MarkData mark = marks.elementAt(counter);
0N/A if (mark.index == g1) {
0N/A sorted[addIndex++] = mark;
0N/A }
0N/A }
0N/A }
0N/A // And replace
0N/A marks.replaceRange(startIndex, endIndex, sorted);
0N/A }
0N/A }
0N/A
0N/A /**
0N/A * Used to hold a reference to a Mark that is being reset as the
0N/A * result of removing from the content.
0N/A */
0N/A final class UndoPosRef {
0N/A UndoPosRef(MarkData rec) {
0N/A this.rec = rec;
0N/A this.undoLocation = rec.getOffset();
0N/A }
0N/A
0N/A /**
0N/A * Resets the location of the Position to the offset when the
0N/A * receiver was instantiated.
0N/A *
0N/A * @param endOffset end location of inserted string.
0N/A * @param g1 resulting end of gap.
0N/A */
0N/A protected void resetLocation(int endOffset, int g1) {
0N/A if (undoLocation != endOffset) {
0N/A this.rec.index = undoLocation;
0N/A }
0N/A else {
0N/A this.rec.index = g1;
0N/A }
0N/A }
0N/A
0N/A /** Previous Offset of rec. */
0N/A protected int undoLocation;
0N/A /** Mark to reset offset. */
0N/A protected MarkData rec;
0N/A } // End of GapContent.UndoPosRef
0N/A
0N/A
0N/A /**
0N/A * UnoableEdit created for inserts.
0N/A */
0N/A class InsertUndo extends AbstractUndoableEdit {
0N/A protected InsertUndo(int offset, int length) {
0N/A super();
0N/A this.offset = offset;
0N/A this.length = length;
0N/A }
0N/A
0N/A public void undo() throws CannotUndoException {
0N/A super.undo();
0N/A try {
0N/A // Get the Positions in the range being removed.
0N/A posRefs = getPositionsInRange(null, offset, length);
0N/A string = getString(offset, length);
0N/A remove(offset, length);
0N/A } catch (BadLocationException bl) {
0N/A throw new CannotUndoException();
0N/A }
0N/A }
0N/A
0N/A public void redo() throws CannotRedoException {
0N/A super.redo();
0N/A try {
0N/A insertString(offset, string);
0N/A string = null;
0N/A // Update the Positions that were in the range removed.
0N/A if(posRefs != null) {
0N/A updateUndoPositions(posRefs, offset, length);
0N/A posRefs = null;
0N/A }
0N/A } catch (BadLocationException bl) {
0N/A throw new CannotRedoException();
0N/A }
0N/A }
0N/A
0N/A /** Where string was inserted. */
0N/A protected int offset;
0N/A /** Length of string inserted. */
0N/A protected int length;
0N/A /** The string that was inserted. This will only be valid after an
0N/A * undo. */
0N/A protected String string;
0N/A /** An array of instances of UndoPosRef for the Positions in the
0N/A * range that was removed, valid after undo. */
0N/A protected Vector posRefs;
0N/A } // GapContent.InsertUndo
0N/A
0N/A
0N/A /**
0N/A * UndoableEdit created for removes.
0N/A */
0N/A class RemoveUndo extends AbstractUndoableEdit {
0N/A protected RemoveUndo(int offset, String string) {
0N/A super();
0N/A this.offset = offset;
0N/A this.string = string;
0N/A this.length = string.length();
0N/A posRefs = getPositionsInRange(null, offset, length);
0N/A }
0N/A
0N/A public void undo() throws CannotUndoException {
0N/A super.undo();
0N/A try {
0N/A insertString(offset, string);
0N/A // Update the Positions that were in the range removed.
0N/A if(posRefs != null) {
0N/A updateUndoPositions(posRefs, offset, length);
0N/A posRefs = null;
0N/A }
0N/A string = null;
0N/A } catch (BadLocationException bl) {
0N/A throw new CannotUndoException();
0N/A }
0N/A }
0N/A
0N/A public void redo() throws CannotRedoException {
0N/A super.redo();
0N/A try {
0N/A string = getString(offset, length);
0N/A // Get the Positions in the range being removed.
0N/A posRefs = getPositionsInRange(null, offset, length);
0N/A remove(offset, length);
0N/A } catch (BadLocationException bl) {
0N/A throw new CannotRedoException();
0N/A }
0N/A }
0N/A
0N/A /** Where the string was removed from. */
0N/A protected int offset;
0N/A /** Length of string removed. */
0N/A protected int length;
0N/A /** The string that was removed. This is valid when redo is valid. */
0N/A protected String string;
0N/A /** An array of instances of UndoPosRef for the Positions in the
0N/A * range that was removed, valid before undo. */
0N/A protected Vector posRefs;
0N/A } // GapContent.RemoveUndo
0N/A}