| /* 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; | |
| } | |
| } |