You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
499 lines
16 KiB
499 lines
16 KiB
/*
|
|
* Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved.
|
|
* ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*/
|
|
package javax.swing.text;
|
|
|
|
import java.util.Vector;
|
|
import java.io.Serializable;
|
|
import javax.swing.undo.*;
|
|
import javax.swing.SwingUtilities;
|
|
|
|
/**
|
|
* An implementation of the AbstractDocument.Content interface that is
|
|
* a brute force implementation that is useful for relatively small
|
|
* documents and/or debugging. It manages the character content
|
|
* as a simple character array. It is also quite inefficient.
|
|
* <p>
|
|
* It is generally recommended that the gap buffer or piece table
|
|
* implementations be used instead. This buffer does not scale up
|
|
* to large sizes.
|
|
* <p>
|
|
* <strong>Warning:</strong>
|
|
* Serialized objects of this class will not be compatible with
|
|
* future Swing releases. The current serialization support is
|
|
* appropriate for short term storage or RMI between applications running
|
|
* the same version of Swing. As of 1.4, support for long term storage
|
|
* of all JavaBeans™
|
|
* has been added to the <code>java.beans</code> package.
|
|
* Please see {@link java.beans.XMLEncoder}.
|
|
*
|
|
* @author Timothy Prinzing
|
|
*/
|
|
public final class StringContent implements AbstractDocument.Content, Serializable {
|
|
|
|
/**
|
|
* Creates a new StringContent object. Initial size defaults to 10.
|
|
*/
|
|
public StringContent() {
|
|
this(10);
|
|
}
|
|
|
|
/**
|
|
* Creates a new StringContent object, with the initial
|
|
* size specified. If the length is < 1, a size of 1 is used.
|
|
*
|
|
* @param initialLength the initial size
|
|
*/
|
|
public StringContent(int initialLength) {
|
|
if (initialLength < 1) {
|
|
initialLength = 1;
|
|
}
|
|
data = new char[initialLength];
|
|
data[0] = '\n';
|
|
count = 1;
|
|
}
|
|
|
|
/**
|
|
* Returns the length of the content.
|
|
*
|
|
* @return the length >= 1
|
|
* @see AbstractDocument.Content#length
|
|
*/
|
|
public int length() {
|
|
return count;
|
|
}
|
|
|
|
/**
|
|
* Inserts a string into the content.
|
|
*
|
|
* @param where the starting position >= 0 && < length()
|
|
* @param str the non-null string to insert
|
|
* @return an UndoableEdit object for undoing
|
|
* @exception BadLocationException if the specified position is invalid
|
|
* @see AbstractDocument.Content#insertString
|
|
*/
|
|
public UndoableEdit insertString(int where, String str) throws BadLocationException {
|
|
if (where >= count || where < 0) {
|
|
throw new BadLocationException("Invalid location", count);
|
|
}
|
|
char[] chars = str.toCharArray();
|
|
replace(where, 0, chars, 0, chars.length);
|
|
if (marks != null) {
|
|
updateMarksForInsert(where, str.length());
|
|
}
|
|
return new InsertUndo(where, str.length());
|
|
}
|
|
|
|
/**
|
|
* Removes part of the content. where + nitems must be < length().
|
|
*
|
|
* @param where the starting position >= 0
|
|
* @param nitems the number of characters to remove >= 0
|
|
* @return an UndoableEdit object for undoing
|
|
* @exception BadLocationException if the specified position is invalid
|
|
* @see AbstractDocument.Content#remove
|
|
*/
|
|
public UndoableEdit remove(int where, int nitems) throws BadLocationException {
|
|
if (where + nitems >= count) {
|
|
throw new BadLocationException("Invalid range", count);
|
|
}
|
|
String removedString = getString(where, nitems);
|
|
UndoableEdit edit = new RemoveUndo(where, removedString);
|
|
replace(where, nitems, empty, 0, 0);
|
|
if (marks != null) {
|
|
updateMarksForRemove(where, nitems);
|
|
}
|
|
return edit;
|
|
|
|
}
|
|
|
|
/**
|
|
* Retrieves a portion of the content. where + len must be <= length().
|
|
*
|
|
* @param where the starting position >= 0
|
|
* @param len the length to retrieve >= 0
|
|
* @return a string representing the content; may be empty
|
|
* @exception BadLocationException if the specified position is invalid
|
|
* @see AbstractDocument.Content#getString
|
|
*/
|
|
public String getString(int where, int len) throws BadLocationException {
|
|
if (where + len > count) {
|
|
throw new BadLocationException("Invalid range", count);
|
|
}
|
|
return new String(data, where, len);
|
|
}
|
|
|
|
/**
|
|
* Retrieves a portion of the content. where + len must be <= length()
|
|
*
|
|
* @param where the starting position >= 0
|
|
* @param len the number of characters to retrieve >= 0
|
|
* @param chars the Segment object to return the characters in
|
|
* @exception BadLocationException if the specified position is invalid
|
|
* @see AbstractDocument.Content#getChars
|
|
*/
|
|
public void getChars(int where, int len, Segment chars) throws BadLocationException {
|
|
if (where + len > count) {
|
|
throw new BadLocationException("Invalid location", count);
|
|
}
|
|
chars.array = data;
|
|
chars.offset = where;
|
|
chars.count = len;
|
|
}
|
|
|
|
/**
|
|
* Creates a position within the content that will
|
|
* track change as the content is mutated.
|
|
*
|
|
* @param offset the offset to create a position for >= 0
|
|
* @return the position
|
|
* @exception BadLocationException if the specified position is invalid
|
|
*/
|
|
public Position createPosition(int offset) throws BadLocationException {
|
|
// some small documents won't have any sticky positions
|
|
// at all, so the buffer is created lazily.
|
|
if (marks == null) {
|
|
marks = new Vector<PosRec>();
|
|
}
|
|
return new StickyPosition(offset);
|
|
}
|
|
|
|
// --- local methods ---------------------------------------
|
|
|
|
/**
|
|
* Replaces some of the characters in the array
|
|
* @param offset offset into the array to start the replace
|
|
* @param length number of characters to remove
|
|
* @param replArray replacement array
|
|
* @param replOffset offset into the replacement array
|
|
* @param replLength number of character to use from the
|
|
* replacement array.
|
|
*/
|
|
void replace(int offset, int length,
|
|
char[] replArray, int replOffset, int replLength) {
|
|
int delta = replLength - length;
|
|
int src = offset + length;
|
|
int nmove = count - src;
|
|
int dest = src + delta;
|
|
if ((count + delta) >= data.length) {
|
|
// need to grow the array
|
|
int newLength = Math.max(2*data.length, count + delta);
|
|
char[] newData = new char[newLength];
|
|
System.arraycopy(data, 0, newData, 0, offset);
|
|
System.arraycopy(replArray, replOffset, newData, offset, replLength);
|
|
System.arraycopy(data, src, newData, dest, nmove);
|
|
data = newData;
|
|
} else {
|
|
// patch the existing array
|
|
System.arraycopy(data, src, data, dest, nmove);
|
|
System.arraycopy(replArray, replOffset, data, offset, replLength);
|
|
}
|
|
count = count + delta;
|
|
}
|
|
|
|
void resize(int ncount) {
|
|
char[] ndata = new char[ncount];
|
|
System.arraycopy(data, 0, ndata, 0, Math.min(ncount, count));
|
|
data = ndata;
|
|
}
|
|
|
|
synchronized void updateMarksForInsert(int offset, int length) {
|
|
if (offset == 0) {
|
|
// zero is a special case where we update only
|
|
// marks after it.
|
|
offset = 1;
|
|
}
|
|
int n = marks.size();
|
|
for (int i = 0; i < n; i++) {
|
|
PosRec mark = marks.elementAt(i);
|
|
if (mark.unused) {
|
|
// this record is no longer used, get rid of it
|
|
marks.removeElementAt(i);
|
|
i -= 1;
|
|
n -= 1;
|
|
} else if (mark.offset >= offset) {
|
|
mark.offset += length;
|
|
}
|
|
}
|
|
}
|
|
|
|
synchronized void updateMarksForRemove(int offset, int length) {
|
|
int n = marks.size();
|
|
for (int i = 0; i < n; i++) {
|
|
PosRec mark = marks.elementAt(i);
|
|
if (mark.unused) {
|
|
// this record is no longer used, get rid of it
|
|
marks.removeElementAt(i);
|
|
i -= 1;
|
|
n -= 1;
|
|
} else if (mark.offset >= (offset + length)) {
|
|
mark.offset -= length;
|
|
} else if (mark.offset >= offset) {
|
|
mark.offset = offset;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns a Vector containing instances of UndoPosRef for the
|
|
* Positions in the range
|
|
* <code>offset</code> to <code>offset</code> + <code>length</code>.
|
|
* If <code>v</code> is not null the matching Positions are placed in
|
|
* there. The vector with the resulting Positions are returned.
|
|
* <p>
|
|
* This is meant for internal usage, and is generally not of interest
|
|
* to subclasses.
|
|
*
|
|
* @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 n = marks.size();
|
|
int end = offset + length;
|
|
Vector placeIn = (v == null) ? new Vector() : v;
|
|
for (int i = 0; i < n; i++) {
|
|
PosRec mark = marks.elementAt(i);
|
|
if (mark.unused) {
|
|
// this record is no longer used, get rid of it
|
|
marks.removeElementAt(i);
|
|
i -= 1;
|
|
n -= 1;
|
|
} else if(mark.offset >= offset && mark.offset <= end)
|
|
placeIn.addElement(new UndoPosRef(mark));
|
|
}
|
|
return placeIn;
|
|
}
|
|
|
|
/**
|
|
* Resets the location for all the UndoPosRef instances
|
|
* in <code>positions</code>.
|
|
* <p>
|
|
* This is meant for internal usage, and is generally not of interest
|
|
* to subclasses.
|
|
*
|
|
* @param positions the positions of the instances
|
|
*/
|
|
protected void updateUndoPositions(Vector positions) {
|
|
for(int counter = positions.size() - 1; counter >= 0; counter--) {
|
|
UndoPosRef ref = (UndoPosRef)positions.elementAt(counter);
|
|
// Check if the Position is still valid.
|
|
if(ref.rec.unused) {
|
|
positions.removeElementAt(counter);
|
|
}
|
|
else
|
|
ref.resetLocation();
|
|
}
|
|
}
|
|
|
|
private static final char[] empty = new char[0];
|
|
private char[] data;
|
|
private int count;
|
|
transient Vector<PosRec> marks;
|
|
|
|
/**
|
|
* holds the data for a mark... separately from
|
|
* the real mark so that the real mark can be
|
|
* collected if there are no more references to
|
|
* it.... the update table holds only a reference
|
|
* to this grungy thing.
|
|
*/
|
|
final class PosRec {
|
|
|
|
PosRec(int offset) {
|
|
this.offset = offset;
|
|
}
|
|
|
|
int offset;
|
|
boolean unused;
|
|
}
|
|
|
|
/**
|
|
* This really wants to be a weak reference but
|
|
* in 1.1 we don't have a 100% pure solution for
|
|
* this... so this class trys to hack a solution
|
|
* to causing the marks to be collected.
|
|
*/
|
|
final class StickyPosition implements Position {
|
|
|
|
StickyPosition(int offset) {
|
|
rec = new PosRec(offset);
|
|
marks.addElement(rec);
|
|
}
|
|
|
|
public int getOffset() {
|
|
return rec.offset;
|
|
}
|
|
|
|
protected void finalize() throws Throwable {
|
|
// schedule the record to be removed later
|
|
// on another thread.
|
|
rec.unused = true;
|
|
}
|
|
|
|
public String toString() {
|
|
return Integer.toString(getOffset());
|
|
}
|
|
|
|
PosRec rec;
|
|
}
|
|
|
|
/**
|
|
* Used to hold a reference to a Position that is being reset as the
|
|
* result of removing from the content.
|
|
*/
|
|
final class UndoPosRef {
|
|
UndoPosRef(PosRec rec) {
|
|
this.rec = rec;
|
|
this.undoLocation = rec.offset;
|
|
}
|
|
|
|
/**
|
|
* Resets the location of the Position to the offset when the
|
|
* receiver was instantiated.
|
|
*/
|
|
protected void resetLocation() {
|
|
rec.offset = undoLocation;
|
|
}
|
|
|
|
/** Location to reset to when resetLocatino is invoked. */
|
|
protected int undoLocation;
|
|
/** Position to reset offset. */
|
|
protected PosRec rec;
|
|
}
|
|
|
|
/**
|
|
* 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 {
|
|
synchronized(StringContent.this) {
|
|
// Get the Positions in the range being removed.
|
|
if(marks != null)
|
|
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 {
|
|
synchronized(StringContent.this) {
|
|
insertString(offset, string);
|
|
string = null;
|
|
// Update the Positions that were in the range removed.
|
|
if(posRefs != null) {
|
|
updateUndoPositions(posRefs);
|
|
posRefs = null;
|
|
}
|
|
}
|
|
} catch (BadLocationException bl) {
|
|
throw new CannotRedoException();
|
|
}
|
|
}
|
|
|
|
// Where the string goes.
|
|
protected int offset;
|
|
// Length of the string.
|
|
protected int length;
|
|
// The string that was inserted. To cut down on space needed 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;
|
|
}
|
|
|
|
|
|
/**
|
|
* 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();
|
|
if(marks != null)
|
|
posRefs = getPositionsInRange(null, offset, length);
|
|
}
|
|
|
|
public void undo() throws CannotUndoException {
|
|
super.undo();
|
|
try {
|
|
synchronized(StringContent.this) {
|
|
insertString(offset, string);
|
|
// Update the Positions that were in the range removed.
|
|
if(posRefs != null) {
|
|
updateUndoPositions(posRefs);
|
|
posRefs = null;
|
|
}
|
|
string = null;
|
|
}
|
|
} catch (BadLocationException bl) {
|
|
throw new CannotUndoException();
|
|
}
|
|
}
|
|
|
|
public void redo() throws CannotRedoException {
|
|
super.redo();
|
|
try {
|
|
synchronized(StringContent.this) {
|
|
string = getString(offset, length);
|
|
// Get the Positions in the range being removed.
|
|
if(marks != null)
|
|
posRefs = getPositionsInRange(null, offset, length);
|
|
remove(offset, length);
|
|
}
|
|
} catch (BadLocationException bl) {
|
|
throw new CannotRedoException();
|
|
}
|
|
}
|
|
|
|
// Where the string goes.
|
|
protected int offset;
|
|
// Length of the string.
|
|
protected int length;
|
|
// The string that was inserted. This will be null after an undo.
|
|
protected String string;
|
|
// An array of instances of UndoPosRef for the Positions in the
|
|
// range that was removed, valid before undo.
|
|
protected Vector posRefs;
|
|
}
|
|
}
|