- /*
- * @(#)StringContent.java 1.37 00/02/02
- *
- * Copyright 1997-2000 Sun Microsystems, Inc. All Rights Reserved.
- *
- * This software is the proprietary information of Sun Microsystems, Inc.
- * 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. A future release of Swing will provide support for
- * long term persistence.
- *
- * @author Timothy Prinzing
- * @version 1.37 02/02/00
- */
- 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) {
- 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();
- }
- 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 = (PosRec) 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 = (PosRec) 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.
- *
- * @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 = (PosRec) 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>.
- *
- * @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 marks;
-
- /**
- * holds the data for a mark... seperately 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 grungey 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;
- }
- }