summaryrefslogtreecommitdiff
path: root/libjava/classpath/javax/swing/text/StringContent.java
diff options
context:
space:
mode:
Diffstat (limited to 'libjava/classpath/javax/swing/text/StringContent.java')
-rw-r--r--libjava/classpath/javax/swing/text/StringContent.java569
1 files changed, 569 insertions, 0 deletions
diff --git a/libjava/classpath/javax/swing/text/StringContent.java b/libjava/classpath/javax/swing/text/StringContent.java
new file mode 100644
index 000000000..a017de1c9
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/StringContent.java
@@ -0,0 +1,569 @@
+/* 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();
+ }
+ }
+}