/*
* 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;
}
}