| /* GapContent.java -- |
| Copyright (C) 2002, 2004, 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.ArrayList; |
| import java.util.Collections; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Set; |
| import java.util.Vector; |
| import java.util.WeakHashMap; |
| |
| import javax.swing.undo.AbstractUndoableEdit; |
| import javax.swing.undo.CannotRedoException; |
| import javax.swing.undo.CannotUndoException; |
| import javax.swing.undo.UndoableEdit; |
| |
| /** |
| * This implementation of {@link AbstractDocument.Content} uses a gapped buffer. |
| * This takes advantage of the fact that text area content is mostly inserted |
| * sequentially. The buffer is a char array that maintains a gap at the current |
| * insertion point. If characters a inserted at gap boundaries, the cost is |
| * minimal (simple array access). The array only has to be shifted around when |
| * the insertion point moves (then the gap also moves and one array copy is |
| * necessary) or when the gap is filled up and the buffer has to be enlarged. |
| */ |
| public class GapContent |
| implements AbstractDocument.Content, Serializable |
| { |
| |
| /** |
| * A {@link Position} implementation for <code>GapContent</code>. |
| */ |
| private class GapContentPosition |
| implements Position |
| { |
| |
| /** |
| * The index to the positionMarks array entry, which in turn holds the |
| * mark into the buffer array. |
| */ |
| Mark mark; |
| |
| /** |
| * Creates a new GapContentPosition object. |
| * |
| * @param offset the offset of this Position |
| */ |
| GapContentPosition(int offset) |
| { |
| // Try to find the mark in the positionMarks array, and store the index |
| // to it. |
| synchronized (GapContent.this) |
| { |
| // Try to make space. |
| garbageCollect(); |
| Mark m = new Mark(offset); |
| int i = search(marks, m); |
| if (i >= 0) // mark found |
| { |
| m = (Mark) marks.get(i); |
| } |
| else |
| { |
| i = -i - 1; |
| marks.add(i, m); |
| } |
| m.refCount++; |
| mark = m; |
| } |
| |
| // Register this position in the death queue, so we can cleanup the marks |
| // when this position object gets GC'ed. |
| new WeakReference(this, queueOfDeath); |
| } |
| |
| /** |
| * Returns the current offset of this Position within the content. |
| * |
| * @return the current offset of this Position within the content. |
| */ |
| public int getOffset() |
| { |
| return mark.getOffset(); |
| } |
| } |
| |
| /** |
| * Holds a mark into the buffer that is used by GapContentPosition 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 GapContentPosition can |
| * be garbage collected while we still hold a reference to the Mark object. |
| */ |
| private class Mark |
| implements Comparable |
| { |
| /** |
| * 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 GapContent#garbageCollect()}. |
| */ |
| int refCount; |
| |
| /** |
| * Creates a new Mark object for the specified offset. |
| * |
| * @param offset the offset |
| */ |
| Mark(int offset) |
| { |
| mark = offset; |
| if (mark >= gapStart && mark != 0) |
| mark += (gapEnd - gapStart); |
| } |
| |
| /** |
| * Returns the offset of the mark. |
| * |
| * @return the offset of the mark |
| */ |
| int getOffset() |
| { |
| assert mark == 0 || mark < gapStart || mark >= gapEnd : |
| "Invalid mark: " + mark + ", gapStart: " + gapStart |
| + ", gapEnd: " + gapEnd; |
| |
| int res = mark; |
| if (mark >= gapEnd) |
| res -= (gapEnd - gapStart); |
| return res; |
| } |
| |
| /** |
| * Implementation of Comparable. |
| */ |
| public int compareTo(Object o) |
| { |
| Mark other = (Mark) o; |
| return mark - other.mark; |
| } |
| /** |
| * Adjustment for equals(). |
| */ |
| public boolean equals(Object o) |
| { |
| if (o == null || !(o instanceof Mark)) |
| return false; |
| else |
| return ((Mark) o).mark == mark; |
| } |
| } |
| |
| private class InsertUndo extends AbstractUndoableEdit |
| { |
| public int where, length; |
| String text; |
| public InsertUndo(int start, int len) |
| { |
| where = start; |
| length = len; |
| } |
| |
| public void undo () throws CannotUndoException |
| { |
| super.undo(); |
| try |
| { |
| text = getString(where, length); |
| remove(where, length); |
| } |
| catch (BadLocationException ble) |
| { |
| throw new CannotUndoException(); |
| } |
| } |
| |
| public void redo () throws CannotUndoException |
| { |
| super.redo(); |
| try |
| { |
| insertString(where, text); |
| } |
| catch (BadLocationException ble) |
| { |
| throw new CannotRedoException(); |
| } |
| } |
| |
| } |
| |
| private class UndoRemove extends AbstractUndoableEdit |
| { |
| public int where; |
| String text; |
| public UndoRemove(int start, String removedText) |
| { |
| where = start; |
| text = removedText; |
| } |
| |
| public void undo () throws CannotUndoException |
| { |
| super.undo(); |
| try |
| { |
| insertString(where, text); |
| } |
| catch (BadLocationException ble) |
| { |
| throw new CannotUndoException(); |
| } |
| } |
| |
| public void redo () throws CannotUndoException |
| { |
| super.redo(); |
| try |
| { |
| remove(where, text.length()); |
| } |
| catch (BadLocationException ble) |
| { |
| throw new CannotRedoException(); |
| } |
| } |
| |
| } |
| |
| /** The serialization UID (compatible with JDK1.5). */ |
| private static final long serialVersionUID = -6226052713477823730L; |
| |
| /** |
| * This is the default buffer size and the amount of bytes that a buffer is |
| * extended if it is full. |
| */ |
| static final int DEFAULT_BUFSIZE = 10; |
| |
| /** |
| * The text buffer. |
| */ |
| char[] buffer; |
| |
| /** |
| * The index of the first character of the gap. |
| */ |
| int gapStart; |
| |
| /** |
| * The index of the character after the last character of the gap. |
| */ |
| int gapEnd; |
| |
| // FIXME: We might want to track GC'ed GapContentPositions and remove their |
| // corresponding marks, or alternativly, perform some regular cleanup of |
| // the positionMarks array. |
| |
| /** |
| * Holds the marks for positions. These marks are referenced by the |
| * GapContentPosition instances by an index into this array. |
| * |
| * This is package private to avoid accessor synthetic methods. |
| */ |
| ArrayList marks; |
| |
| WeakHashMap positions; |
| |
| /** |
| * 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 GapContent object. |
| */ |
| public GapContent() |
| { |
| this(DEFAULT_BUFSIZE); |
| } |
| |
| /** |
| * Creates a new GapContent object with a specified initial size. |
| * |
| * @param size the initial size of the buffer |
| */ |
| public GapContent(int size) |
| { |
| size = Math.max(size, 2); |
| buffer = (char[]) allocateArray(size); |
| gapStart = 1; |
| gapEnd = size; |
| buffer[0] = '\n'; |
| positions = new WeakHashMap(); |
| marks = new ArrayList(); |
| queueOfDeath = new ReferenceQueue(); |
| } |
| |
| /** |
| * Allocates an array of the specified length that can then be used as |
| * buffer. |
| * |
| * @param size the size of the array to be allocated |
| * |
| * @return the allocated array |
| */ |
| protected Object allocateArray(int size) |
| { |
| return new char[size]; |
| } |
| |
| /** |
| * Returns the length of the allocated buffer array. |
| * |
| * @return the length of the allocated buffer array |
| */ |
| protected int getArrayLength() |
| { |
| return buffer.length; |
| } |
| |
| /** |
| * Returns the length of the content. |
| * |
| * @return the length of the content |
| */ |
| public int length() |
| { |
| return buffer.length - (gapEnd - gapStart); |
| } |
| |
| /** |
| * Inserts a string at the specified position. |
| * |
| * @param where the position where the string is inserted |
| * @param str the string that is to be inserted |
| * |
| * @return an UndoableEdit object |
| * |
| * @throws BadLocationException if <code>where</code> is not a valid |
| * location in the buffer |
| */ |
| public UndoableEdit insertString(int where, String str) |
| throws BadLocationException |
| { |
| // check arguments |
| int length = length(); |
| int strLen = str.length(); |
| |
| if (where < 0) |
| throw new BadLocationException("The where argument cannot be smaller" |
| + " than the zero", where); |
| |
| if (where > length) |
| throw new BadLocationException("The where argument cannot be greater" |
| + " than the content length", where); |
| |
| replace(where, 0, str.toCharArray(), strLen); |
| |
| return new InsertUndo(where, strLen); |
| } |
| |
| /** |
| * Removes a piece of content at th specified position. |
| * |
| * @param where the position where the content is to be removed |
| * @param nitems number of characters to be removed |
| * |
| * @return an UndoableEdit object |
| * |
| * @throws BadLocationException if <code>where</code> is not a valid |
| * location in the buffer |
| */ |
| public UndoableEdit remove(int where, int nitems) throws BadLocationException |
| { |
| // check arguments |
| int length = length(); |
| |
| if ((where + nitems) >= length) |
| throw new BadLocationException("where + nitems cannot be greater" |
| + " than the content length", where + nitems); |
| |
| String removedText = getString(where, nitems); |
| replace(where, nitems, null, 0); |
| |
| return new UndoRemove(where, removedText); |
| } |
| |
| /** |
| * Returns a piece of content as String. |
| * |
| * @param where the start location of the fragment |
| * @param len the length of the fragment |
| * |
| * @throws BadLocationException if <code>where</code> or |
| * <code>where + len</code> are no valid locations in the buffer |
| */ |
| public String getString(int where, int len) throws BadLocationException |
| { |
| Segment seg = new Segment(); |
| try |
| { |
| getChars(where, len, seg); |
| return new String(seg.array, seg.offset, seg.count); |
| } |
| catch (StringIndexOutOfBoundsException ex) |
| { |
| int invalid = 0; |
| if (seg.offset < 0 || seg.offset >= seg.array.length) |
| invalid = seg.offset; |
| else |
| invalid = seg.offset + seg.count; |
| throw new BadLocationException("Illegal location: array.length = " |
| + seg.array.length + ", offset = " |
| + seg.offset + ", count = " |
| + seg.count, invalid); |
| } |
| } |
| |
| /** |
| * Fetches a piece of content and stores it in a {@link Segment} object. |
| * |
| * If the requested piece of text spans the gap, the content is copied into a |
| * new array. If it doesn't then it is contiguous and the actual content |
| * store is returned. |
| * |
| * @param where the start location of the fragment |
| * @param len the length of the fragment |
| * @param txt the Segment object to store the fragment in |
| * |
| * @throws BadLocationException if <code>where</code> or |
| * <code>where + len</code> are no valid locations in the buffer |
| */ |
| public void getChars(int where, int len, Segment txt) |
| throws BadLocationException |
| { |
| // check arguments |
| int length = length(); |
| if (where < 0) |
| throw new BadLocationException("the where argument may not be below zero", where); |
| if (where >= length) |
| throw new BadLocationException("the where argument cannot be greater" |
| + " than the content length", where); |
| if ((where + len) > length) |
| throw new BadLocationException("len plus where cannot be greater" |
| + " than the content length", len + where); |
| if (len < 0) |
| throw new BadLocationException("negative length not allowed: ", len); |
| |
| // check if requested segment is contiguous |
| if ((where < gapStart) && ((gapStart - where) < len)) |
| { |
| // requested segment is not contiguous -> copy the pieces together |
| char[] copy = new char[len]; |
| int lenFirst = gapStart - where; // the length of the first segment |
| System.arraycopy(buffer, where, copy, 0, lenFirst); |
| System.arraycopy(buffer, gapEnd, copy, lenFirst, len - lenFirst); |
| txt.array = copy; |
| txt.offset = 0; |
| txt.count = len; |
| } |
| else |
| { |
| // requested segment is contiguous -> we can simply return the |
| // actual content |
| txt.array = buffer; |
| if (where < gapStart) |
| txt.offset = where; |
| else |
| txt.offset = where + (gapEnd - gapStart); |
| txt.count = len; |
| } |
| } |
| |
| /** |
| * Creates and returns a mark at the specified position. |
| * |
| * @param offset the position at which to create the mark |
| * |
| * @return the create Position object for the mark |
| * |
| * @throws BadLocationException if the offset is not a valid position in the |
| * buffer |
| */ |
| public Position createPosition(final int offset) throws BadLocationException |
| { |
| // Implementation note: We used to perform explicit check on the offset |
| // here. However, this makes some Mauve and Intel/Harmony tests fail |
| // and luckily enough the GapContent can very well deal with offsets |
| // outside the buffer bounds. So I removed that check. |
| |
| // We try to find a GapContentPosition at the specified offset and return |
| // that. Otherwise we must create a new one. |
| GapContentPosition pos = null; |
| Set positionSet = positions.keySet(); |
| for (Iterator i = positionSet.iterator(); i.hasNext();) |
| { |
| GapContentPosition p = (GapContentPosition) i.next(); |
| if (p.getOffset() == offset) |
| { |
| pos = p; |
| break; |
| } |
| } |
| |
| // If none was found, then create and return a new one. |
| if (pos == null) |
| { |
| pos = new GapContentPosition(offset); |
| positions.put(pos, null); |
| } |
| |
| return pos; |
| } |
| |
| /** |
| * Enlarges the gap. This allocates a new bigger buffer array, copy the |
| * segment before the gap as it is and the segment after the gap at the end |
| * of the new buffer array. This does change the gapEnd mark but not the |
| * gapStart mark. |
| * |
| * @param newSize the new size of the gap |
| */ |
| protected void shiftEnd(int newSize) |
| { |
| assert newSize > (gapEnd - gapStart) : "The new gap size must be greater " |
| + "than the old gap size"; |
| |
| int delta = newSize - gapEnd + gapStart; |
| // Update the marks after the gapEnd. |
| adjustPositionsInRange(gapEnd, -1, delta); |
| |
| // Copy the data around. |
| char[] newBuf = (char[]) allocateArray(length() + newSize); |
| System.arraycopy(buffer, 0, newBuf, 0, gapStart); |
| System.arraycopy(buffer, gapEnd, newBuf, gapStart + newSize, buffer.length |
| - gapEnd); |
| gapEnd = gapStart + newSize; |
| buffer = newBuf; |
| |
| } |
| |
| /** |
| * Shifts the gap to the specified position. |
| * |
| * @param newGapStart the new start position of the gap |
| */ |
| protected void shiftGap(int newGapStart) |
| { |
| if (newGapStart == gapStart) |
| return; |
| int newGapEnd = newGapStart + gapEnd - gapStart; |
| if (newGapStart < gapStart) |
| { |
| // Update the positions between newGapStart and (old) gapStart. The marks |
| // must be shifted by (gapEnd - gapStart). |
| adjustPositionsInRange(newGapStart, gapStart, gapEnd - gapStart); |
| System.arraycopy(buffer, newGapStart, buffer, newGapEnd, gapStart |
| - newGapStart); |
| gapStart = newGapStart; |
| gapEnd = newGapEnd; |
| } |
| else |
| { |
| // Update the positions between newGapEnd and (old) gapEnd. The marks |
| // must be shifted by (gapEnd - gapStart). |
| adjustPositionsInRange(gapEnd, newGapEnd, -(gapEnd - gapStart)); |
| System.arraycopy(buffer, gapEnd, buffer, gapStart, newGapStart |
| - gapStart); |
| gapStart = newGapStart; |
| gapEnd = newGapEnd; |
| } |
| resetMarksAtZero(); |
| } |
| |
| /** |
| * Shifts the gap start downwards. This does not affect the content of the |
| * buffer. This only updates the gap start and all the marks that are between |
| * the old gap start and the new gap start. They all are squeezed to the start |
| * of the gap, because their location has been removed. |
| * |
| * @param newGapStart the new gap start |
| */ |
| protected void shiftGapStartDown(int newGapStart) |
| { |
| if (newGapStart == gapStart) |
| return; |
| |
| assert newGapStart < gapStart : "The new gap start must be less than the " |
| + "old gap start."; |
| setPositionsInRange(newGapStart, gapStart, false); |
| gapStart = newGapStart; |
| resetMarksAtZero(); |
| } |
| |
| /** |
| * Shifts the gap end upwards. This does not affect the content of the |
| * buffer. This only updates the gap end and all the marks that are between |
| * the old gap end and the new end start. They all are squeezed to the end |
| * of the gap, because their location has been removed. |
| * |
| * @param newGapEnd the new gap start |
| */ |
| protected void shiftGapEndUp(int newGapEnd) |
| { |
| if (newGapEnd == gapEnd) |
| return; |
| |
| assert newGapEnd > gapEnd : "The new gap end must be greater than the " |
| + "old gap end."; |
| setPositionsInRange(gapEnd, newGapEnd, false); |
| gapEnd = newGapEnd; |
| resetMarksAtZero(); |
| } |
| |
| /** |
| * Returns the allocated buffer array. |
| * |
| * @return the allocated buffer array |
| */ |
| protected final Object getArray() |
| { |
| return buffer; |
| } |
| |
| /** |
| * Replaces a portion of the storage with the specified items. |
| * |
| * @param position the position at which to remove items |
| * @param rmSize the number of items to remove |
| * @param addItems the items to add at location |
| * @param addSize the number of items to add |
| */ |
| protected void replace(int position, int rmSize, Object addItems, |
| int addSize) |
| { |
| if (gapStart != position) |
| shiftGap(position); |
| |
| // Remove content |
| if (rmSize > 0) |
| shiftGapEndUp(gapEnd + rmSize); |
| |
| // If gap is too small, enlarge the gap. |
| if ((gapEnd - gapStart) <= addSize) |
| shiftEnd((addSize - gapEnd + gapStart + 1) * 2 + gapEnd + DEFAULT_BUFSIZE); |
| |
| // Add new items to the buffer. |
| if (addItems != null) |
| { |
| System.arraycopy(addItems, 0, buffer, gapStart, addSize); |
| gapStart += addSize; |
| } |
| } |
| |
| /** |
| * Returns the start index of the gap within the buffer array. |
| * |
| * @return the start index of the gap within the buffer array |
| */ |
| protected final int getGapStart() |
| { |
| return gapStart; |
| } |
| |
| /** |
| * Returns the end index of the gap within the buffer array. |
| * |
| * @return the end index of the gap within the buffer array |
| */ |
| protected final int getGapEnd() |
| { |
| return gapEnd; |
| } |
| |
| /** |
| * Returns all <code>Position</code>s that are in the range specified by |
| * <code>offset</code> and </code>length</code> within the buffer array. |
| * |
| * @param v the vector to use; if <code>null</code>, a new Vector is allocated |
| * @param offset the start offset of the range to search |
| * @param length the length of the range to search |
| * |
| * @return the positions within the specified range |
| */ |
| protected Vector getPositionsInRange(Vector v, int offset, int length) |
| { |
| Vector res = v; |
| if (res == null) |
| res = new Vector(); |
| else |
| res.clear(); |
| |
| int endOffs = offset + length; |
| |
| Set positionSet = positions.keySet(); |
| for (Iterator i = positionSet.iterator(); i.hasNext();) |
| { |
| GapContentPosition p = (GapContentPosition) i.next(); |
| int offs = p.getOffset(); |
| if (offs >= offset && offs < endOffs) |
| res.add(p); |
| } |
| |
| return res; |
| } |
| |
| /** |
| * Crunches all positions in the specified range to either the start or |
| * end of that interval. The interval boundaries are meant to be inclusive |
| * [start, end]. |
| * |
| * @param start the start offset of the range |
| * @param end the end offset of the range |
| * @param toStart a boolean indicating if the positions should be crunched |
| * to the start (true) or to the end (false) |
| */ |
| private void setPositionsInRange(int start, int end, boolean toStart) |
| { |
| synchronized (this) |
| { |
| // Find the start and end indices in the positionMarks array. |
| Mark m = new Mark(0); // For comparison / search only. |
| m.mark = start; |
| int startIndex = search(marks, m); |
| if (startIndex < 0) // Translate to insertion index, if not found. |
| startIndex = - startIndex - 1; |
| m.mark = end; |
| int endIndex = search(marks, m); |
| if (endIndex < 0) // Translate to insertion index - 1, if not found. |
| endIndex = - endIndex - 2; |
| |
| // Actually adjust the marks. |
| for (int i = startIndex; i <= endIndex; i++) |
| ((Mark) marks.get(i)).mark = toStart ? start : end; |
| } |
| |
| } |
| |
| /** |
| * Adjusts the mark of all <code>Position</code>s that are in the range |
| * specified by <code>offset</code> and </code>length</code> within |
| * the buffer array by <code>increment</code> |
| * |
| * @param startOffs the start offset of the range to search |
| * @param endOffs the length of the range to search, -1 means all to the end |
| * @param incr the increment |
| */ |
| private void adjustPositionsInRange(int startOffs, int endOffs, int incr) |
| { |
| synchronized (this) |
| { |
| // Find the start and end indices in the positionMarks array. |
| Mark m = new Mark(0); // For comparison / search only. |
| |
| m.mark = startOffs; |
| int startIndex = search(marks, m); |
| if (startIndex < 0) // Translate to insertion index, if not found. |
| startIndex = - startIndex - 1; |
| |
| m.mark = endOffs; |
| int endIndex; |
| if (endOffs == -1) |
| endIndex = marks.size() - 1; |
| else |
| { |
| endIndex = search(marks, m); |
| if (endIndex < 0) // Translate to insertion index - 1, if not found. |
| endIndex = - endIndex - 2; |
| } |
| // Actually adjust the marks. |
| for (int i = startIndex; i <= endIndex; i++) { |
| ((Mark) marks.get(i)).mark += incr; |
| } |
| } |
| |
| } |
| |
| /** |
| * Resets all <code>Position</code> that have an offset of <code>0</code>, |
| * to also have an array index of <code>0</code>. This might be necessary |
| * after a call to <code>shiftGap(0)</code>, since then the marks at offset |
| * <code>0</code> get shifted to <code>gapEnd</code>. |
| */ |
| protected void resetMarksAtZero() |
| { |
| if (gapStart != 0) |
| return; |
| |
| for (int i = 0; i < marks.size(); i++) |
| { |
| Mark m = (Mark) marks.get(i); |
| if (m.mark <= gapEnd) |
| m.mark = 0; |
| } |
| } |
| |
| /** |
| * @specnote This method is not very well specified and the positions vector |
| * is implementation specific. The undo positions are managed |
| * differently in this implementation, this method is only here |
| * for binary compatibility. |
| */ |
| protected void updateUndoPositions(Vector positions, int offset, int length) |
| { |
| // We do nothing here. |
| } |
| |
| /** |
| * Outputs debugging info to System.err. It prints out the buffer array, |
| * the gapStart is marked by a < sign, the gapEnd is marked by a > |
| * sign and each position is marked by a # sign. |
| */ |
| private void dump() |
| { |
| System.err.println("GapContent debug information"); |
| System.err.println("buffer length: " + buffer.length); |
| System.err.println("gap start: " + gapStart); |
| System.err.println("gap end: " + gapEnd); |
| for (int i = 0; i < buffer.length; i++) |
| { |
| if (i == gapStart) |
| System.err.print('<'); |
| if (i == gapEnd) |
| System.err.print('>'); |
| |
| if (!Character.isISOControl(buffer[i])) |
| System.err.print(buffer[i]); |
| else |
| System.err.print('.'); |
| } |
| System.err.println(); |
| } |
| |
| /** |
| * Prints out the position marks. |
| */ |
| private void dumpMarks() |
| { |
| System.out.print("positionMarks: "); |
| for (int i = 0; i < marks.size(); i++) |
| System.out.print(((Mark) marks.get(i)).mark + ", "); |
| System.out.println(); |
| } |
| |
| /** |
| * 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) |
| { |
| GapContentPosition pos = (GapContentPosition) ref.get(); |
| Mark m = pos.mark; |
| m.refCount--; |
| if (m.refCount == 0) |
| marks.remove(m); |
| } |
| ref = queueOfDeath.poll(); |
| } |
| } |
| |
| /** |
| * Searches the first occurance of object <code>o</code> in list |
| * <code>l</code>. This performs a binary search by calling |
| * {@link Collections#binarySearch(List, Object)} and when an object has been |
| * found, it searches backwards to the first occurance of that object in the |
| * list. The meaning of the return value is the same as in |
| * <code>Collections.binarySearch()</code>. |
| * |
| * @param l the list to search through |
| * @param o the object to be searched |
| * |
| * @return the index of the first occurance of o in l, or -i + 1 if not found |
| */ |
| private int search(List l, Object o) |
| { |
| int i = Collections.binarySearch(l, o); |
| while (i > 0) |
| { |
| Object o2 = l.get(i - 1); |
| if (o2.equals(o)) |
| i--; |
| else |
| break; |
| } |
| return i; |
| } |
| } |