| /* StringContent.java -- | |
| Copyright (C) 2005, 2006 Free Software Foundation, Inc. | |
| This file is part of GNU Classpath. | |
| GNU Classpath is free software; you can redistribute it and/or modify | |
| it under the terms of the GNU General Public License as published by | |
| the Free Software Foundation; either version 2, or (at your option) | |
| any later version. | |
| GNU Classpath 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 for more details. | |
| You should have received a copy of the GNU General Public License | |
| along with GNU Classpath; see the file COPYING. If not, write to the | |
| Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA | |
| 02110-1301 USA. | |
| Linking this library statically or dynamically with other modules is | |
| making a combined work based on this library. Thus, the terms and | |
| conditions of the GNU General Public License cover the whole | |
| combination. | |
| As a special exception, the copyright holders of this library give you | |
| permission to link this library with independent modules to produce an | |
| executable, regardless of the license terms of these independent | |
| modules, and to copy and distribute the resulting executable under | |
| terms of your choice, provided that you also meet, for each linked | |
| independent module, the terms and conditions of the license of that | |
| module. An independent module is a module which is not derived from | |
| or based on this library. If you modify this library, you may extend | |
| this exception to your version of the library, but you are not | |
| obligated to do so. If you do not wish to do so, delete this | |
| exception statement from your version. */ | |
| package javax.swing.text; | |
| import java.io.Serializable; | |
| import java.lang.ref.Reference; | |
| import java.lang.ref.ReferenceQueue; | |
| import java.lang.ref.WeakReference; | |
| import java.util.Iterator; | |
| import java.util.Vector; | |
| import javax.swing.undo.AbstractUndoableEdit; | |
| import javax.swing.undo.CannotRedoException; | |
| import javax.swing.undo.CannotUndoException; | |
| import javax.swing.undo.UndoableEdit; | |
| /** | |
| * An implementation of the <code>AbstractDocument.Content</code> | |
| * interface useful for small documents or debugging. The character | |
| * content is a simple character array. It's not really efficient. | |
| * | |
| * <p>Do not use this class for large size.</p> | |
| */ | |
| public final class StringContent | |
| implements AbstractDocument.Content, Serializable | |
| { | |
| /** | |
| * Stores a reference to a mark that can be resetted to the original value | |
| * after a mark has been moved. This is used for undoing actions. | |
| */ | |
| private class UndoPosRef | |
| { | |
| /** | |
| * The mark that might need to be reset. | |
| */ | |
| private Mark mark; | |
| /** | |
| * The original offset to reset the mark to. | |
| */ | |
| private int undoOffset; | |
| /** | |
| * Creates a new UndoPosRef. | |
| * | |
| * @param m the mark | |
| */ | |
| UndoPosRef(Mark m) | |
| { | |
| mark = m; | |
| undoOffset = mark.mark; | |
| } | |
| /** | |
| * Resets the position of the mark to the value that it had when | |
| * creating this UndoPosRef. | |
| */ | |
| void reset() | |
| { | |
| mark.mark = undoOffset; | |
| } | |
| } | |
| /** | |
| * Holds a mark into the buffer that is used by StickyPosition to find | |
| * the actual offset of the position. This is pulled out of the | |
| * GapContentPosition object so that the mark and position can be handled | |
| * independently, and most important, so that the StickyPosition can | |
| * be garbage collected while we still hold a reference to the Mark object. | |
| */ | |
| private class Mark | |
| { | |
| /** | |
| * The actual mark into the buffer. | |
| */ | |
| int mark; | |
| /** | |
| * The number of GapContentPosition object that reference this mark. If | |
| * it reaches zero, it get's deleted by | |
| * {@link StringContent#garbageCollect()}. | |
| */ | |
| int refCount; | |
| /** | |
| * Creates a new Mark object for the specified offset. | |
| * | |
| * @param offset the offset | |
| */ | |
| Mark(int offset) | |
| { | |
| mark = offset; | |
| } | |
| } | |
| /** The serialization UID (compatible with JDK1.5). */ | |
| private static final long serialVersionUID = 4755994433709540381L; | |
| // This is package-private to avoid an accessor method. | |
| char[] content; | |
| private int count; | |
| /** | |
| * Holds the marks for the positions. | |
| * | |
| * This is package private to avoid accessor methods. | |
| */ | |
| Vector marks; | |
| private class InsertUndo extends AbstractUndoableEdit | |
| { | |
| private int start; | |
| private int length; | |
| private String redoContent; | |
| private Vector positions; | |
| public InsertUndo(int start, int length) | |
| { | |
| super(); | |
| this.start = start; | |
| this.length = length; | |
| } | |
| public void undo() | |
| { | |
| super.undo(); | |
| try | |
| { | |
| if (marks != null) | |
| positions = getPositionsInRange(null, start, length); | |
| redoContent = getString(start, length); | |
| remove(start, length); | |
| } | |
| catch (BadLocationException b) | |
| { | |
| throw new CannotUndoException(); | |
| } | |
| } | |
| public void redo() | |
| { | |
| super.redo(); | |
| try | |
| { | |
| insertString(start, redoContent); | |
| redoContent = null; | |
| if (positions != null) | |
| { | |
| updateUndoPositions(positions); | |
| positions = null; | |
| } | |
| } | |
| catch (BadLocationException b) | |
| { | |
| throw new CannotRedoException(); | |
| } | |
| } | |
| } | |
| private class RemoveUndo extends AbstractUndoableEdit | |
| { | |
| private int start; | |
| private int len; | |
| private String undoString; | |
| Vector positions; | |
| public RemoveUndo(int start, String str) | |
| { | |
| super(); | |
| this.start = start; | |
| len = str.length(); | |
| this.undoString = str; | |
| if (marks != null) | |
| positions = getPositionsInRange(null, start, str.length()); | |
| } | |
| public void undo() | |
| { | |
| super.undo(); | |
| try | |
| { | |
| StringContent.this.insertString(this.start, this.undoString); | |
| if (positions != null) | |
| { | |
| updateUndoPositions(positions); | |
| positions = null; | |
| } | |
| undoString = null; | |
| } | |
| catch (BadLocationException bad) | |
| { | |
| throw new CannotUndoException(); | |
| } | |
| } | |
| public void redo() | |
| { | |
| super.redo(); | |
| try | |
| { | |
| undoString = getString(start, len); | |
| if (marks != null) | |
| positions = getPositionsInRange(null, start, len); | |
| remove(this.start, len); | |
| } | |
| catch (BadLocationException bad) | |
| { | |
| throw new CannotRedoException(); | |
| } | |
| } | |
| } | |
| private class StickyPosition implements Position | |
| { | |
| Mark mark; | |
| public StickyPosition(int offset) | |
| { | |
| // Try to make space. | |
| garbageCollect(); | |
| mark = new Mark(offset); | |
| mark.refCount++; | |
| marks.add(mark); | |
| new WeakReference(this, queueOfDeath); | |
| } | |
| /** | |
| * Should be >=0. | |
| */ | |
| public int getOffset() | |
| { | |
| return mark.mark; | |
| } | |
| } | |
| /** | |
| * Used in {@link #remove(int,int)}. | |
| */ | |
| private static final char[] EMPTY = new char[0]; | |
| /** | |
| * Queues all references to GapContentPositions that are about to be | |
| * GC'ed. This is used to remove the corresponding marks from the | |
| * positionMarks array if the number of references to that mark reaches zero. | |
| * | |
| * This is package private to avoid accessor synthetic methods. | |
| */ | |
| ReferenceQueue queueOfDeath; | |
| /** | |
| * Creates a new instance containing the string "\n". This is equivalent | |
| * to calling {@link #StringContent(int)} with an <code>initialLength</code> | |
| * of 10. | |
| */ | |
| public StringContent() | |
| { | |
| this(10); | |
| } | |
| /** | |
| * Creates a new instance containing the string "\n". | |
| * | |
| * @param initialLength the initial length of the underlying character | |
| * array used to store the content. | |
| */ | |
| public StringContent(int initialLength) | |
| { | |
| super(); | |
| queueOfDeath = new ReferenceQueue(); | |
| if (initialLength < 1) | |
| initialLength = 1; | |
| this.content = new char[initialLength]; | |
| this.content[0] = '\n'; | |
| this.count = 1; | |
| } | |
| protected Vector getPositionsInRange(Vector v, | |
| int offset, | |
| int length) | |
| { | |
| Vector refPos = v == null ? new Vector() : v; | |
| Iterator iter = marks.iterator(); | |
| while(iter.hasNext()) | |
| { | |
| Mark m = (Mark) iter.next(); | |
| if (offset <= m.mark && m.mark <= offset + length) | |
| refPos.add(new UndoPosRef(m)); | |
| } | |
| return refPos; | |
| } | |
| /** | |
| * Creates a position reference for the character at the given offset. The | |
| * position offset will be automatically updated when new characters are | |
| * inserted into or removed from the content. | |
| * | |
| * @param offset the character offset. | |
| * | |
| * @throws BadLocationException if offset is outside the bounds of the | |
| * content. | |
| */ | |
| public Position createPosition(int offset) throws BadLocationException | |
| { | |
| // Lazily create marks vector. | |
| if (marks == null) | |
| marks = new Vector(); | |
| StickyPosition sp = new StickyPosition(offset); | |
| return sp; | |
| } | |
| /** | |
| * Returns the length of the string content, including the '\n' character at | |
| * the end. | |
| * | |
| * @return The length of the string content. | |
| */ | |
| public int length() | |
| { | |
| return count; | |
| } | |
| /** | |
| * Inserts <code>str</code> at the given position and returns an | |
| * {@link UndoableEdit} that enables undo/redo support. | |
| * | |
| * @param where the insertion point (must be less than | |
| * <code>length()</code>). | |
| * @param str the string to insert (<code>null</code> not permitted). | |
| * | |
| * @return An object that can undo the insertion. | |
| */ | |
| public UndoableEdit insertString(int where, String str) | |
| throws BadLocationException | |
| { | |
| checkLocation(where, 0); | |
| if (where == this.count) | |
| throw new BadLocationException("Invalid location", 1); | |
| if (str == null) | |
| throw new NullPointerException(); | |
| char[] insert = str.toCharArray(); | |
| replace(where, 0, insert); | |
| // Move all the positions. | |
| if (marks != null) | |
| { | |
| Iterator iter = marks.iterator(); | |
| int start = where; | |
| if (start == 0) | |
| start = 1; | |
| while (iter.hasNext()) | |
| { | |
| Mark m = (Mark) iter.next(); | |
| if (m.mark >= start) | |
| m.mark += str.length(); | |
| } | |
| } | |
| InsertUndo iundo = new InsertUndo(where, insert.length); | |
| return iundo; | |
| } | |
| /** | |
| * Removes the specified range of characters and returns an | |
| * {@link UndoableEdit} that enables undo/redo support. | |
| * | |
| * @param where the starting index. | |
| * @param nitems the number of characters. | |
| * | |
| * @return An object that can undo the removal. | |
| * | |
| * @throws BadLocationException if the character range extends outside the | |
| * bounds of the content OR includes the last character. | |
| */ | |
| public UndoableEdit remove(int where, int nitems) throws BadLocationException | |
| { | |
| checkLocation(where, nitems + 1); | |
| RemoveUndo rundo = new RemoveUndo(where, new String(this.content, where, | |
| nitems)); | |
| replace(where, nitems, EMPTY); | |
| // Move all the positions. | |
| if (marks != null) | |
| { | |
| Iterator iter = marks.iterator(); | |
| while (iter.hasNext()) | |
| { | |
| Mark m = (Mark) iter.next(); | |
| if (m.mark >= where + nitems) | |
| m.mark -= nitems; | |
| else if (m.mark >= where) | |
| m.mark = where; | |
| } | |
| } | |
| return rundo; | |
| } | |
| private void replace(int offs, int numRemove, char[] insert) | |
| { | |
| int insertLength = insert.length; | |
| int delta = insertLength - numRemove; | |
| int src = offs + numRemove; | |
| int numMove = count - src; | |
| int dest = src + delta; | |
| if (count + delta >= content.length) | |
| { | |
| // Grow data array. | |
| int newLength = Math.max(2 * content.length, count + delta); | |
| char[] newContent = new char[newLength]; | |
| System.arraycopy(content, 0, newContent, 0, offs); | |
| System.arraycopy(insert, 0, newContent, offs, insertLength); | |
| System.arraycopy(content, src, newContent, dest, numMove); | |
| content = newContent; | |
| } | |
| else | |
| { | |
| System.arraycopy(content, src, content, dest, numMove); | |
| System.arraycopy(insert, 0, content, offs, insertLength); | |
| } | |
| count += delta; | |
| } | |
| /** | |
| * Returns a new <code>String</code> containing the characters in the | |
| * specified range. | |
| * | |
| * @param where the start index. | |
| * @param len the number of characters. | |
| * | |
| * @return A string. | |
| * | |
| * @throws BadLocationException if the requested range of characters extends | |
| * outside the bounds of the content. | |
| */ | |
| public String getString(int where, int len) throws BadLocationException | |
| { | |
| // The RI throws a StringIndexOutOfBoundsException here, which | |
| // smells like a bug. We throw a BadLocationException instead. | |
| checkLocation(where, len); | |
| return new String(this.content, where, len); | |
| } | |
| /** | |
| * Updates <code>txt</code> to contain a direct reference to the underlying | |
| * character array. | |
| * | |
| * @param where the index of the first character. | |
| * @param len the number of characters. | |
| * @param txt a carrier for the return result (<code>null</code> not | |
| * permitted). | |
| * | |
| * @throws BadLocationException if the requested character range is not | |
| * within the bounds of the content. | |
| * @throws NullPointerException if <code>txt</code> is <code>null</code>. | |
| */ | |
| public void getChars(int where, int len, Segment txt) | |
| throws BadLocationException | |
| { | |
| if (where + len > count) | |
| throw new BadLocationException("Invalid location", where + len); | |
| txt.array = content; | |
| txt.offset = where; | |
| txt.count = len; | |
| } | |
| /** | |
| * Resets the positions in the specified vector to their original offset | |
| * after a undo operation is performed. For example, after removing some | |
| * content, the positions in the removed range will all be set to one | |
| * offset. This method restores the positions to their original offsets | |
| * after an undo. | |
| */ | |
| protected void updateUndoPositions(Vector positions) | |
| { | |
| for (Iterator i = positions.iterator(); i.hasNext();) | |
| { | |
| UndoPosRef pos = (UndoPosRef) i.next(); | |
| pos.reset(); | |
| } | |
| } | |
| /** | |
| * A utility method that checks the validity of the specified character | |
| * range. | |
| * | |
| * @param where the first character in the range. | |
| * @param len the number of characters in the range. | |
| * | |
| * @throws BadLocationException if the specified range is not within the | |
| * bounds of the content. | |
| */ | |
| void checkLocation(int where, int len) throws BadLocationException | |
| { | |
| if (where < 0) | |
| throw new BadLocationException("Invalid location", 1); | |
| else if (where > this.count) | |
| throw new BadLocationException("Invalid location", this.count); | |
| else if ((where + len) > this.count) | |
| throw new BadLocationException("Invalid range", this.count); | |
| } | |
| /** | |
| * Polls the queue of death for GapContentPositions, updates the | |
| * corresponding reference count and removes the corresponding mark | |
| * if the refcount reaches zero. | |
| * | |
| * This is package private to avoid accessor synthetic methods. | |
| */ | |
| void garbageCollect() | |
| { | |
| Reference ref = queueOfDeath.poll(); | |
| while (ref != null) | |
| { | |
| if (ref != null) | |
| { | |
| StickyPosition pos = (StickyPosition) ref.get(); | |
| Mark m = pos.mark; | |
| m.refCount--; | |
| if (m.refCount == 0) | |
| marks.remove(m); | |
| } | |
| ref = queueOfDeath.poll(); | |
| } | |
| } | |
| } |