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