/* * Copyright (c) 1998, 2008, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package javax.swing.text; import java.util.Vector; import java.io.IOException; import java.io.ObjectInputStream; import java.io.Serializable; import javax.swing.undo.AbstractUndoableEdit; import javax.swing.undo.CannotRedoException; import javax.swing.undo.CannotUndoException; import javax.swing.undo.UndoableEdit; import javax.swing.SwingUtilities; import java.lang.ref.WeakReference; import java.lang.ref.ReferenceQueue; /** * An implementation of the AbstractDocument.Content interface * implemented using a gapped buffer similar to that used by emacs. * The underlying storage is a array of unicode characters with * a gap somewhere. The gap is moved to the location of changes * to take advantage of common behavior where most changes are * in the same location. Changes that occur at a gap boundary are * generally cheap and moving the gap is generally cheaper than * moving the array contents directly to accomodate the change. *
* The positions tracking change are also generally cheap to
* maintain. The Position implementations (marks) store the array
* index and can easily calculate the sequential position from
* the current gap location. Changes only require update to the
* the marks between the old and new gap boundaries when the gap
* is moved, so generally updating the marks is pretty cheap.
* The marks are stored sorted so they can be located quickly
* with a binary search. This increases the cost of adding a
* mark, and decreases the cost of keeping the mark updated.
*
* @author Timothy Prinzing
*/
public class GapContent extends GapVector implements AbstractDocument.Content, Serializable {
/**
* Creates a new GapContent object. Initial size defaults to 10.
*/
public GapContent() {
this(10);
}
/**
* Creates a new GapContent object, with the initial
* size specified. The initial size will not be allowed
* to go below 2, to give room for the implied break and
* the gap.
*
* @param initialLength the initial size
*/
public GapContent(int initialLength) {
super(Math.max(initialLength,2));
char[] implied = new char[1];
implied[0] = '\n';
replace(0, 0, implied, implied.length);
marks = new MarkVector();
search = new MarkData(0);
queue = new ReferenceQueue
* This is meant for internal usage, and is generally not of interest
* to subclasses.
*
* @param positions the UndoPosRef instances to reset
*/
protected void updateUndoPositions(Vector positions, int offset,
int length) {
// Find the indexs of the end points.
int endOffset = offset + length;
int g1 = getGapEnd();
int startIndex;
int endIndex = findMarkAdjustIndex(g1 + 1);
if (offset != 0) {
startIndex = findMarkAdjustIndex(g1);
}
else {
startIndex = 0;
}
// Reset the location of the refenences.
for(int counter = positions.size() - 1; counter >= 0; counter--) {
UndoPosRef ref = (UndoPosRef)positions.elementAt(counter);
ref.resetLocation(endOffset, g1);
}
// We have to resort the marks in the range startIndex to endIndex.
// We can take advantage of the fact that it will be in
// increasing order, accept there will be a bunch of MarkData's with
// the index g1 (or 0 if offset == 0) interspersed throughout.
if (startIndex < endIndex) {
Object[] sorted = new Object[endIndex - startIndex];
int addIndex = 0;
int counter;
if (offset == 0) {
// If the offset is 0, the positions won't have incremented,
// have to do the reverse thing.
// Find the elements in startIndex whose index is 0
for (counter = startIndex; counter < endIndex; counter++) {
MarkData mark = marks.elementAt(counter);
if (mark.index == 0) {
sorted[addIndex++] = mark;
}
}
for (counter = startIndex; counter < endIndex; counter++) {
MarkData mark = marks.elementAt(counter);
if (mark.index != 0) {
sorted[addIndex++] = mark;
}
}
}
else {
for (counter = startIndex; counter < endIndex; counter++) {
MarkData mark = marks.elementAt(counter);
if (mark.index != g1) {
sorted[addIndex++] = mark;
}
}
for (counter = startIndex; counter < endIndex; counter++) {
MarkData mark = marks.elementAt(counter);
if (mark.index == g1) {
sorted[addIndex++] = mark;
}
}
}
// And replace
marks.replaceRange(startIndex, endIndex, sorted);
}
}
/**
* Used to hold a reference to a Mark that is being reset as the
* result of removing from the content.
*/
final class UndoPosRef {
UndoPosRef(MarkData rec) {
this.rec = rec;
this.undoLocation = rec.getOffset();
}
/**
* Resets the location of the Position to the offset when the
* receiver was instantiated.
*
* @param endOffset end location of inserted string.
* @param g1 resulting end of gap.
*/
protected void resetLocation(int endOffset, int g1) {
if (undoLocation != endOffset) {
this.rec.index = undoLocation;
}
else {
this.rec.index = g1;
}
}
/** Previous Offset of rec. */
protected int undoLocation;
/** Mark to reset offset. */
protected MarkData rec;
} // End of GapContent.UndoPosRef
/**
* UnoableEdit created for inserts.
*/
class InsertUndo extends AbstractUndoableEdit {
protected InsertUndo(int offset, int length) {
super();
this.offset = offset;
this.length = length;
}
public void undo() throws CannotUndoException {
super.undo();
try {
// Get the Positions in the range being removed.
posRefs = getPositionsInRange(null, offset, length);
string = getString(offset, length);
remove(offset, length);
} catch (BadLocationException bl) {
throw new CannotUndoException();
}
}
public void redo() throws CannotRedoException {
super.redo();
try {
insertString(offset, string);
string = null;
// Update the Positions that were in the range removed.
if(posRefs != null) {
updateUndoPositions(posRefs, offset, length);
posRefs = null;
}
} catch (BadLocationException bl) {
throw new CannotRedoException();
}
}
/** Where string was inserted. */
protected int offset;
/** Length of string inserted. */
protected int length;
/** The string that was inserted. This will only be valid after an
* undo. */
protected String string;
/** An array of instances of UndoPosRef for the Positions in the
* range that was removed, valid after undo. */
protected Vector posRefs;
} // GapContent.InsertUndo
/**
* UndoableEdit created for removes.
*/
class RemoveUndo extends AbstractUndoableEdit {
protected RemoveUndo(int offset, String string) {
super();
this.offset = offset;
this.string = string;
this.length = string.length();
posRefs = getPositionsInRange(null, offset, length);
}
public void undo() throws CannotUndoException {
super.undo();
try {
insertString(offset, string);
// Update the Positions that were in the range removed.
if(posRefs != null) {
updateUndoPositions(posRefs, offset, length);
posRefs = null;
}
string = null;
} catch (BadLocationException bl) {
throw new CannotUndoException();
}
}
public void redo() throws CannotRedoException {
super.redo();
try {
string = getString(offset, length);
// Get the Positions in the range being removed.
posRefs = getPositionsInRange(null, offset, length);
remove(offset, length);
} catch (BadLocationException bl) {
throw new CannotRedoException();
}
}
/** Where the string was removed from. */
protected int offset;
/** Length of string removed. */
protected int length;
/** The string that was removed. This is valid when redo is valid. */
protected String string;
/** An array of instances of UndoPosRef for the Positions in the
* range that was removed, valid before undo. */
protected Vector posRefs;
} // GapContent.RemoveUndo
}
elementAt
.
*/
protected void replaceRange(int start, int end, Object[] marks) {
int g0 = getGapStart();
int g1 = getGapEnd();
int index = start;
int newIndex = 0;
Object[] array = (Object[]) getArray();
if (start >= g0) {
// Completely passed gap
index += (g1 - g0);
end += (g1 - g0);
}
else if (end >= g0) {
// straddles gap
end += (g1 - g0);
while (index < g0) {
array[index++] = marks[newIndex++];
}
index = g1;
}
else {
// below gap
while (index < end) {
array[index++] = marks[newIndex++];
}
}
while (index < end) {
array[index++] = marks[newIndex++];
}
}
MarkData[] oneMark = new MarkData[1];
}
// --- serialization -------------------------------------
private void readObject(ObjectInputStream s)
throws ClassNotFoundException, IOException {
s.defaultReadObject();
marks = new MarkVector();
search = new MarkData(0);
queue = new ReferenceQueueoffset
to offset
+ length
.
* If v
is not null the matching Positions are placed in
* there. The vector with the resulting Positions are returned.
*
* @param v the Vector to use, with a new one created on null
* @param offset the starting offset >= 0
* @param length the length >= 0
* @return the set of instances
*/
protected Vector getPositionsInRange(Vector v, int offset, int length) {
int endOffset = offset + length;
int startIndex;
int endIndex;
int g0 = getGapStart();
int g1 = getGapEnd();
// Find the index of the marks.
if (offset < g0) {
if (offset == 0) {
// findMarkAdjustIndex start at 1!
startIndex = 0;
}
else {
startIndex = findMarkAdjustIndex(offset);
}
if (endOffset >= g0) {
endIndex = findMarkAdjustIndex(endOffset + (g1 - g0) + 1);
}
else {
endIndex = findMarkAdjustIndex(endOffset + 1);
}
}
else {
startIndex = findMarkAdjustIndex(offset + (g1 - g0));
endIndex = findMarkAdjustIndex(endOffset + (g1 - g0) + 1);
}
Vector placeIn = (v == null) ? new Vector(Math.max(1, endIndex -
startIndex)) : v;
for (int counter = startIndex; counter < endIndex; counter++) {
placeIn.addElement(new UndoPosRef(marks.elementAt(counter)));
}
return placeIn;
}
/**
* Resets the location for all the UndoPosRef instances
* in positions
.
*