| /* 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.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.Vector; |
| |
| 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>. |
| */ |
| class GapContentPosition |
| implements Position |
| { |
| |
| /** |
| * The index to the positionMarks array entry, which in turn holds the |
| * mark into the buffer array. |
| */ |
| Mark mark; |
| |
| /** |
| * 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 |
| extends WeakReference |
| { |
| /** |
| * The actual mark into the buffer. |
| */ |
| int mark; |
| |
| /** |
| * Creates a new Mark object for the specified offset. |
| * |
| * @param offset the offset |
| */ |
| Mark(int offset) |
| { |
| super(null); |
| mark = offset; |
| } |
| |
| Mark(int offset, GapContentPosition pos, ReferenceQueue queue) |
| { |
| super(pos, queue); |
| mark = offset; |
| } |
| |
| /** |
| * Returns the offset of the mark. |
| * |
| * @return the offset of the mark |
| */ |
| int getOffset() |
| { |
| int res = mark; |
| if (mark >= gapStart) |
| res -= (gapEnd - gapStart); |
| return Math.max(0, res); |
| } |
| |
| /** |
| * Returns the GapContentPosition that is associated ith this mark. |
| * This fetches the weakly referenced position object. |
| * |
| * @return the GapContentPosition that is associated ith this mark |
| */ |
| GapContentPosition getPosition() |
| { |
| return (GapContentPosition) get(); |
| } |
| |
| } |
| |
| /** |
| * 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.getOffset(); |
| } |
| |
| /** |
| * Resets the position of the mark to the value that it had when |
| * creating this UndoPosRef. |
| */ |
| void reset() |
| { |
| if (undoOffset <= gapStart) |
| mark.mark = undoOffset; |
| else |
| mark.mark = (gapEnd - gapStart) + undoOffset; |
| } |
| } |
| |
| private class InsertUndo extends AbstractUndoableEdit |
| { |
| public int where, length; |
| String text; |
| private Vector positions; |
| |
| public InsertUndo(int start, int len) |
| { |
| where = start; |
| length = len; |
| } |
| |
| public void undo () throws CannotUndoException |
| { |
| super.undo(); |
| try |
| { |
| positions = getPositionsInRange(null, where, length); |
| text = getString(where, length); |
| remove(where, length); |
| } |
| catch (BadLocationException ble) |
| { |
| throw new CannotUndoException(); |
| } |
| } |
| |
| public void redo () throws CannotUndoException |
| { |
| super.redo(); |
| try |
| { |
| insertString(where, text); |
| if (positions != null) |
| { |
| updateUndoPositions(positions, where, length); |
| positions = null; |
| } |
| } |
| catch (BadLocationException ble) |
| { |
| throw new CannotRedoException(); |
| } |
| } |
| |
| } |
| |
| private class UndoRemove extends AbstractUndoableEdit |
| { |
| public int where; |
| String text; |
| |
| /** |
| * The positions in the removed range. |
| */ |
| private Vector positions; |
| |
| public UndoRemove(int start, String removedText) |
| { |
| where = start; |
| text = removedText; |
| positions = getPositionsInRange(null, start, removedText.length()); |
| } |
| |
| public void undo () throws CannotUndoException |
| { |
| super.undo(); |
| try |
| { |
| insertString(where, text); |
| if (positions != null) |
| updateUndoPositions(positions, where, text.length()); |
| } |
| catch (BadLocationException ble) |
| { |
| throw new CannotUndoException(); |
| } |
| } |
| |
| public void redo () throws CannotUndoException |
| { |
| super.redo(); |
| try |
| { |
| text = getString(where, text.length()); |
| positions = getPositionsInRange(null, where, text.length()); |
| 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; |
| |
| /** |
| * The number of unused marks. |
| */ |
| private int garbageMarks; |
| |
| /** |
| * A 'static' mark that is used for searching. |
| */ |
| private Mark searchMark = new Mark(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 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'; |
| 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); |
| |
| InsertUndo undo = new InsertUndo(where, strLen); |
| replace(where, 0, str.toCharArray(), strLen); |
| |
| return undo; |
| } |
| |
| /** |
| * 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); |
| UndoRemove undoRemove = new UndoRemove(where, removedText); |
| replace(where, nitems, null, 0); |
| |
| return undoRemove; |
| } |
| |
| /** |
| * 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); |
| |
| // Optimized to copy only when really needed. |
| if (where + len <= gapStart) |
| { |
| // Simple case: completely before gap. |
| txt.array = buffer; |
| txt.offset = where; |
| txt.count = len; |
| } |
| else if (where > gapStart) |
| { |
| // Completely after gap, adjust offset. |
| txt.array = buffer; |
| txt.offset = gapEnd + where - gapStart; |
| txt.count = len; |
| } |
| else |
| { |
| // Spans the gap. |
| int beforeGap = gapStart - where; |
| if (txt.isPartialReturn()) |
| { |
| // Return the part before the gap when partial return is allowed. |
| txt.array = buffer; |
| txt.offset = where; |
| txt.count = beforeGap; |
| } |
| else |
| { |
| // Copy pieces together otherwise. |
| txt.array = new char[len]; |
| txt.offset = 0; |
| System.arraycopy(buffer, where, txt.array, 0, beforeGap); |
| System.arraycopy(buffer, gapEnd, txt.array, beforeGap, |
| len - beforeGap); |
| 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. |
| |
| // First do some garbage collections. |
| while (queueOfDeath.poll() != null) |
| garbageMarks++; |
| if (garbageMarks > Math.max(5, marks.size() / 10)) |
| garbageCollect(); |
| |
| // We try to find a GapContentPosition at the specified offset and return |
| // that. Otherwise we must create a new one. |
| Mark m; |
| GapContentPosition pos; |
| int index = offset; |
| if (offset >= gapStart) |
| index += (gapEnd - gapStart); |
| searchMark.mark = index; |
| int insertIndex = search(searchMark); |
| if (!(insertIndex < marks.size() |
| && (m = (Mark) marks.get(insertIndex)).mark == index |
| && (pos = m.getPosition()) != null)) |
| { |
| // Create new position if none was found. |
| pos = new GapContentPosition(); |
| m = new Mark(index, pos, queueOfDeath); |
| pos.mark = m; |
| marks.add(insertIndex, m); |
| } |
| // Otherwise use the found position. |
| |
| 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 oldEnd = getGapEnd(); |
| int oldSize = getArrayLength(); |
| int upper = oldSize - oldEnd; |
| int size = (newSize + 1) * 2; |
| int newEnd = size - upper; |
| |
| // Copy the data around. |
| char[] newBuf = (char[]) allocateArray(size); |
| System.arraycopy(buffer, 0, newBuf, 0, Math.min(size, oldSize)); |
| buffer = newBuf; |
| gapEnd = newEnd; |
| if (upper != 0) |
| System.arraycopy(buffer, oldEnd, buffer, newEnd, upper); |
| |
| // Adjust marks. |
| int delta = gapEnd - oldEnd; |
| int adjIndex = searchFirst(oldEnd); |
| int count = marks.size(); |
| for (int i = adjIndex; i < count; i++) |
| { |
| Mark m = (Mark) marks.get(i); |
| m.mark += delta; |
| } |
| } |
| |
| /** |
| * Shifts the gap to the specified position. |
| * |
| * @param newGapStart the new start position of the gap |
| */ |
| protected void shiftGap(int newGapStart) |
| { |
| int oldStart = gapStart; |
| int delta = newGapStart - oldStart; |
| int oldEnd = gapEnd; |
| int newGapEnd = oldEnd + delta; |
| int size = oldEnd - oldStart; |
| |
| // Shift gap in array. |
| gapStart = newGapStart; |
| gapEnd = newGapEnd; |
| if (delta > 0) |
| System.arraycopy(buffer, oldEnd, buffer, oldStart, delta); |
| else |
| System.arraycopy(buffer, newGapStart, buffer, newGapEnd, -delta); |
| |
| // Adjust marks. |
| if (delta > 0) |
| { |
| int adjIndex = searchFirst(oldStart); |
| int count = marks.size(); |
| for (int i = adjIndex; i < count; i++) |
| { |
| Mark m = (Mark) marks.get(i); |
| if (m.mark >= newGapEnd) |
| break; |
| m.mark -= size; |
| } |
| } |
| else if (delta < 0) |
| { |
| int adjIndex = searchFirst(newGapStart); |
| int count = marks.size(); |
| for (int i = adjIndex; i < count; i++) |
| { |
| Mark m = (Mark) marks.get(i); |
| if (m.mark >= oldEnd) |
| break; |
| m.mark += size; |
| } |
| } |
| 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."; |
| |
| // Adjust positions. |
| int adjIndex = searchFirst(newGapStart); |
| int count = marks.size(); |
| for (int i = adjIndex; i < count; i++) |
| { |
| Mark m = (Mark) marks.get(i); |
| if (m.mark > gapStart) |
| break; |
| m.mark = gapEnd; |
| } |
| |
| 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."; |
| |
| // Adjust marks. |
| int adjIndex = searchFirst(gapEnd); |
| int count = marks.size(); |
| for (int i = adjIndex; i < count; i++) |
| { |
| Mark m = (Mark) marks.get(i); |
| if (m.mark >= newGapEnd) |
| break; |
| m.mark = newGapEnd; |
| } |
| |
| |
| 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 (addSize == 0) |
| { |
| removeImpl(position, rmSize); |
| return; |
| } |
| else if (rmSize > addSize) |
| { |
| removeImpl(position + addSize, rmSize - addSize); |
| } |
| else |
| { |
| int endSize = addSize - rmSize; |
| int end = addImpl(position + rmSize, endSize); |
| System.arraycopy(addItems, rmSize, buffer, end, endSize); |
| addSize = rmSize; |
| } |
| System.arraycopy(addItems, 0, buffer, position, addSize); |
| } |
| |
| /** |
| * Adjusts the positions and gap in response to a remove operation. |
| * |
| * @param pos the position at which to remove |
| * @param num the number of removed items |
| */ |
| private void removeImpl(int pos, int num) |
| { |
| if (num > 0) |
| { |
| int end = pos + num; |
| int newGapSize = (gapEnd - gapStart) + num; |
| if (end <= gapStart) |
| { |
| if (gapStart != end) |
| { |
| shiftGap(end); |
| } |
| shiftGapStartDown(gapStart - num); |
| } |
| else if (pos >= gapStart) |
| { |
| if (gapStart != pos) |
| { |
| shiftGap(pos); |
| } |
| shiftGapEndUp(gapStart + newGapSize); |
| } |
| else |
| { |
| shiftGapStartDown(pos); |
| shiftGapEndUp(gapStart + newGapSize); |
| } |
| } |
| } |
| |
| /** |
| * Adjusts the positions and gap in response to an add operation. |
| * |
| * @param pos the position at which to add |
| * @param num the number of added items |
| * |
| * @return the adjusted position |
| */ |
| private int addImpl(int pos, int num) |
| { |
| int size = gapEnd - gapStart; |
| if (num == 0) |
| { |
| if (pos > gapStart) |
| pos += size; |
| return pos; |
| } |
| |
| shiftGap(pos); |
| if (num >= size) |
| { |
| shiftEnd(getArrayLength() - size + num); |
| size = gapEnd - gapStart; |
| } |
| |
| gapStart += num; |
| return pos; |
| } |
| |
| /** |
| * 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) |
| { |
| int end = offset + length; |
| int startIndex; |
| int endIndex; |
| if (offset < gapStart) |
| { |
| if (offset == 0) |
| startIndex = 0; |
| else |
| startIndex = searchFirst(offset); |
| if (end >= gapStart) |
| endIndex = searchFirst(end + (gapEnd - gapStart) + 1); |
| else |
| endIndex = searchFirst(end + 1); |
| } |
| else |
| { |
| startIndex = searchFirst(offset + (gapEnd - gapStart)); |
| endIndex = searchFirst(end + (gapEnd - gapStart) + 1); |
| } |
| if (v == null) |
| v = new Vector(); |
| for (int i = startIndex; i < endIndex; i++) |
| { |
| v.add(new UndoPosRef((Mark) marks.get(i))); |
| } |
| return v; |
| } |
| |
| /** |
| * 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; |
| } |
| } |
| |
| /** |
| * Resets the positions in the specified range 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. |
| * |
| * @param positions the positions to update |
| * @param offset |
| * @param length |
| */ |
| protected void updateUndoPositions(Vector positions, int offset, int length) |
| { |
| for (Iterator i = positions.iterator(); i.hasNext();) |
| { |
| UndoPosRef undoPosRef = (UndoPosRef) i.next(); |
| undoPosRef.reset(); |
| } |
| |
| // Resort marks. |
| Collections.sort(marks); |
| } |
| |
| /** |
| * 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(); |
| } |
| |
| /** |
| * 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 o the object to be searched |
| * |
| * @return the index of the first occurance of o in l, or -i + 1 if not found |
| */ |
| int search(Mark o) |
| { |
| int foundInd = 0; |
| boolean found = false; |
| int low = 0; |
| int up = marks.size() - 1; |
| int mid = 0; |
| if (up > -1) |
| { |
| int cmp = 0; |
| Mark last = (Mark) marks.get(up); |
| cmp = compare(o, last); |
| if (cmp > 0) |
| { |
| foundInd = up + 1; |
| found = true; |
| } |
| else |
| { |
| while (low <= up && ! found) |
| { |
| mid = low + (up - low) / 2; |
| Mark m = (Mark) marks.get(mid); |
| cmp = compare(o, m); |
| if (cmp == 0) |
| { |
| foundInd = mid; |
| found = true; |
| } |
| else if (cmp < 0) |
| up = mid - 1; |
| else |
| low = mid + 1; |
| } |
| |
| if (! found) |
| foundInd = cmp < 0 ? mid : mid + 1; |
| } |
| } |
| return foundInd; |
| } |
| |
| private int searchFirst(int index) |
| { |
| searchMark.mark = Math.max(index, 1); |
| int i = search(searchMark); |
| for (int j = i - 1; j >= 0; j--) |
| { |
| Mark m = (Mark) marks.get(j); |
| if (m.mark != index) |
| break; |
| i--; |
| } |
| return i; |
| } |
| |
| /** |
| * Compares two marks. |
| * |
| * @param m1 the first mark |
| * @param m2 the second mark |
| * |
| * @return negative when m1 < m2, positive when m1 > m2 and 0 when equal |
| */ |
| private int compare(Mark m1, Mark m2) |
| { |
| return m1.mark - m2.mark; |
| } |
| |
| /** |
| * Collects and frees unused marks. |
| */ |
| private void garbageCollect() |
| { |
| int count = marks.size(); |
| ArrayList clean = new ArrayList(); |
| for (int i = 0; i < count; i++) |
| { |
| Mark m = (Mark) marks.get(i); |
| if (m.get() != null) |
| clean.add(m); |
| } |
| marks = clean; |
| garbageMarks = 0; |
| } |
| } |