498 lines · 17.0 KB · Java
RawDownload
/* * Copyright (c) 1997, 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.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<sup><font size="-2">TM</font></sup> * 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;    }}