FileDocCategorySizeDatePackage
GapContent.javaAPI DocJava SE 5 API25649Fri Aug 26 14:58:16 BST 2005javax.swing.text

GapContent

public class GapContent extends GapVector implements Serializable, AbstractDocument$Content
An implementation of the AbstractDocument.Content interface implemented using a gapped buffer similar to that used by emacs. The underlying storage is a array of unicode characters with a gap somewhere. The gap is moved to the location of changes to take advantage of common behavior where most changes are in the same location. Changes that occur at a gap boundary are generally cheap and moving the gap is generally cheaper than moving the array contents directly to accomodate the change.

The positions tracking change are also generally cheap to maintain. The Position implementations (marks) store the array index and can easily calculate the sequential position from the current gap location. Changes only require update to the the marks between the old and new gap boundaries when the gap is moved, so generally updating the marks is pretty cheap. The marks are stored sorted so they can be located quickly with a binary search. This increases the cost of adding a mark, and decreases the cost of keeping the mark updated.

author
Timothy Prinzing
version
1.21 12/03/01

Fields Summary
private static final char[]
empty
private transient MarkVector
marks
private transient MarkData
search
Record used for searching for the place to start updating mark indexs when the gap boundaries are moved.
private transient int
unusedMarks
The number of unused mark entries
private transient ReferenceQueue
queue
static final int
GROWTH_SIZE
Constructors Summary
public GapContent()
Creates a new GapContent object. Initial size defaults to 10.

	this(10);
    
public GapContent(int initialLength)
Creates a new GapContent object, with the initial size specified. The initial size will not be allowed to go below 2, to give room for the implied break and the gap.

param
initialLength the initial size

	super(Math.max(initialLength,2));
	char[] implied = new char[1];
	implied[0] = '\n";
	replace(0, 0, implied, implied.length);

	marks = new MarkVector();
	search = new MarkData(0);
	queue = new ReferenceQueue();
    
Methods Summary
protected java.lang.ObjectallocateArray(int len)
Allocate an array to store items of the type appropriate (which is determined by the subclass).

	return new char[len];
    
final intcompare(javax.swing.text.GapContent$MarkData o1, javax.swing.text.GapContent$MarkData o2)
Compares two marks.

param
o1 the first object
param
o2 the second object
return
< 0 if o1 < o2, 0 if the same, > 0 if o1 > o2

	if (o1.index < o2.index) {
	    return -1;
	} else if (o1.index > o2.index) {
	    return 1;
	} else {
	    return 0;
	}
    
public javax.swing.text.PositioncreatePosition(int offset)
Creates a position within the content that will track change as the content is mutated.

param
offset the offset to track >= 0
return
the position
exception
BadLocationException if the specified position is invalid

	while ( queue.poll() != null ) {
	    unusedMarks++;
	}
	if (unusedMarks > Math.max(5, (marks.size() / 10))) {
	    removeUnusedMarks();
	}
	int g0 = getGapStart();
	int g1 = getGapEnd();
	int index = (offset < g0) ? offset : offset + (g1 - g0);
	search.index = index;
	int sortIndex = findSortIndex(search);
	MarkData m;
	StickyPosition position;
	if (sortIndex < marks.size()
	    && (m = marks.elementAt(sortIndex)).index == index
	    && (position = m.getPosition()) != null) {
	    //position references the correct StickyPostition
	} else {
	    position = new StickyPosition();
	    m = new MarkData(index,position,queue);
	    position.setMark(m);
	    marks.insertElementAt(m, sortIndex);
	}

	return position;
    
final intfindMarkAdjustIndex(int searchIndex)
Finds the index to start mark adjustments given some search index.

	search.index = Math.max(searchIndex, 1);
	int index = findSortIndex(search);

	// return the first in the series
	// (ie. there may be duplicates).
	for (int i = index - 1; i >= 0; i--) {
	    MarkData d = marks.elementAt(i);
	    if (d.index != search.index) {
		break;
	    }
	    index -= 1;
	}
	return index;
    
final intfindSortIndex(javax.swing.text.GapContent$MarkData o)
Finds the index of where to insert a new mark.

param
o the mark to insert
return
the index

	int lower = 0; 
	int upper = marks.size() - 1;
	int mid = 0;
	
	if (upper == -1) {
	    return 0;
	}

	int cmp = 0;
	MarkData last = marks.elementAt(upper);
	cmp = compare(o, last);
	if (cmp > 0)
	    return upper + 1;
	
	while (lower <= upper) {
	    mid = lower + ((upper - lower) / 2);
	    MarkData entry = marks.elementAt(mid);
	    cmp = compare(o, entry);

	    if (cmp == 0) {
		// found a match
		return mid;
	    } else if (cmp < 0) {        
		upper = mid - 1;
	    } else {
		lower = mid + 1;
	    }
	}

	// didn't find it, but we indicate the index of where it would belong.
	return (cmp < 0) ? mid : mid + 1;
    
protected intgetArrayLength()
Get the length of the allocated array.

	char[] carray = (char[]) getArray();
	return carray.length;
    
public voidgetChars(int where, int len, javax.swing.text.Segment chars)
Retrieves a portion of the content. If the desired content spans the gap, we copy the content. If the desired content does not span the gap, the actual store is returned to avoid the copy since it is contiguous.

param
where the starting position >= 0, where + len <= length()
param
len the number of characters to retrieve >= 0
param
chars the Segment object to return the characters in
exception
BadLocationException if the specified position is invalid
see
AbstractDocument.Content#getChars

	int end = where + len;
	if (where < 0 || end < 0) {
	    throw new BadLocationException("Invalid location", -1);
	}
	if (end > length() || where > length()) {
	    throw new BadLocationException("Invalid location", length() + 1);
	}
	int g0 = getGapStart();
	int g1 = getGapEnd();
	char[] array = (char[]) getArray();
	if ((where + len) <= g0) {
	    // below gap
	    chars.array = array;
	    chars.offset = where;
	} else if (where >= g0) {
	    // above gap
	    chars.array = array;
	    chars.offset = g1 + where - g0;
	} else {
	    // spans the gap
	    int before = g0 - where;
	    if (chars.isPartialReturn()) {
		// partial return allowed, return amount before the gap
		chars.array = array;
		chars.offset = where;
		chars.count = before;
		return;
	    }
	    // partial return not allowed, must copy
	    chars.array = new char[len];
	    chars.offset = 0;
	    System.arraycopy(array, where, chars.array, 0, before);
	    System.arraycopy(array, g1, chars.array, before, len - before);
	}
	chars.count = len;
    
intgetNewArraySize(int reqSize)
Overridden to make growth policy less agressive for large text amount.

        if (reqSize < GROWTH_SIZE) {
            return super.getNewArraySize(reqSize);
        } else {
            return reqSize + GROWTH_SIZE;
        }
    
protected java.util.VectorgetPositionsInRange(java.util.Vector v, int offset, int length)
Returns a Vector containing instances of UndoPosRef for the Positions in the range offset to offset + length. If v is not null the matching Positions are placed in there. The vector with the resulting Positions are returned.

param
v the Vector to use, with a new one created on null
param
offset the starting offset >= 0
param
length the length >= 0
return
the set of instances

	int endOffset = offset + length;
	int startIndex;
	int endIndex;
	int g0 = getGapStart();
	int g1 = getGapEnd();

	// Find the index of the marks.
	if (offset < g0) {
	    if (offset == 0) {
		// findMarkAdjustIndex start at 1!
		startIndex = 0;
	    }
	    else {
		startIndex = findMarkAdjustIndex(offset);
	    }
	    if (endOffset >= g0) {
		endIndex = findMarkAdjustIndex(endOffset + (g1 - g0) + 1);
	    }
	    else {
		endIndex = findMarkAdjustIndex(endOffset + 1);
	    }
	}
	else {
	    startIndex = findMarkAdjustIndex(offset + (g1 - g0));
	    endIndex = findMarkAdjustIndex(endOffset + (g1 - g0) + 1);
	}

	Vector placeIn = (v == null) ? new Vector(Math.max(1, endIndex -
							   startIndex)) : v;

	for (int counter = startIndex; counter < endIndex; counter++) {
	    placeIn.addElement(new UndoPosRef(marks.elementAt(counter)));
	}
	return placeIn;
    
public java.lang.StringgetString(int where, int len)
Retrieves a portion of the content.

param
where the starting position >= 0
param
len the length to retrieve >= 0
return
a string representing the content
exception
BadLocationException if the specified position is invalid
see
AbstractDocument.Content#getString

	Segment s = new Segment();
	getChars(where, len, s);
	return new String(s.array, s.offset, s.count);
    
public javax.swing.undo.UndoableEditinsertString(int where, java.lang.String str)
Inserts a string into the content.

param
where the starting position >= 0, < length()
param
str the non-null string to insert
return
an UndoableEdit object for undoing
exception
BadLocationException if the specified position is invalid
see
AbstractDocument.Content#insertString

	if (where > length() || where < 0) {
	    throw new BadLocationException("Invalid insert", length());
	}
	char[] chars = str.toCharArray();
	replace(where, 0, chars, chars.length);
	return new InsertUndo(where, str.length());
    
public intlength()
Returns the length of the content.

return
the length >= 1
see
AbstractDocument.Content#length

	int len = getArrayLength() - (getGapEnd() - getGapStart());
	return len;
    
private voidreadObject(java.io.ObjectInputStream s)


    

    // --- serialization -------------------------------------

       
         
        s.defaultReadObject();
	marks = new MarkVector();
	search = new MarkData(0);
        queue = new ReferenceQueue();
    
public javax.swing.undo.UndoableEditremove(int where, int nitems)
Removes part of the content.

param
where the starting position >= 0, where + nitems < length()
param
nitems the number of characters to remove >= 0
return
an UndoableEdit object for undoing
exception
BadLocationException if the specified position is invalid
see
AbstractDocument.Content#remove

	if (where + nitems >= length()) {
	    throw new BadLocationException("Invalid remove", length() + 1);
	}
	String removedString = getString(where, nitems);
	UndoableEdit edit = new RemoveUndo(where, removedString);
	replace(where, nitems, empty, 0);
	return edit;
	
    
final voidremoveUnusedMarks()
Remove all unused marks out of the sorted collection of marks.

	int n = marks.size();
	MarkVector cleaned = new MarkVector(n);
	for (int i = 0; i < n; i++) {
	    MarkData mark = marks.elementAt(i);
	    if (mark.get() != null) {
		cleaned.addElement(mark);
	    }
	}
	marks = cleaned;
	unusedMarks = 0;
    
protected voidresetMarksAtZero()
Resets all the marks that have an offset of 0 to have an index of zero as well.

	if (marks != null && getGapStart() == 0) {
	    int g1 = getGapEnd();
	    for (int counter = 0, maxCounter = marks.size();
		 counter < maxCounter; counter++) {
		MarkData mark = marks.elementAt(counter);
		if (mark.index <= g1) {
		    mark.index = 0;
		}
		else {
		    break;
		}
	    }
	}
    
protected voidshiftEnd(int newSize)
Make the gap bigger, moving any necessary data and updating the appropriate marks


    // --- gap management -------------------------------

                       
        
	int oldGapEnd = getGapEnd();

	super.shiftEnd(newSize);

	// Adjust marks.
	int dg = getGapEnd() - oldGapEnd;
	int adjustIndex = findMarkAdjustIndex(oldGapEnd);
	int n = marks.size();
	for (int i = adjustIndex; i < n; i++) {
	    MarkData mark = marks.elementAt(i);
	    mark.index += dg;
	}
    
protected voidshiftGap(int newGapStart)
Move the start of the gap to a new location, without changing the size of the gap. This moves the data in the array and updates the marks accordingly.

	int oldGapStart = getGapStart();
	int dg = newGapStart - oldGapStart;
	int oldGapEnd = getGapEnd();
	int newGapEnd = oldGapEnd + dg;
	int gapSize = oldGapEnd - oldGapStart;

	// shift gap in the character array
	super.shiftGap(newGapStart);

	// update the marks
	if (dg > 0) {
	    // Move gap up, move data and marks down.
	    int adjustIndex = findMarkAdjustIndex(oldGapStart);
	    int n = marks.size();
	    for (int i = adjustIndex; i < n; i++) {
		MarkData mark = marks.elementAt(i);
		if (mark.index >= newGapEnd) {
		    break;
		}
		mark.index -= gapSize;
	    }
	} else if (dg < 0) {
	    // Move gap down, move data and marks up.
	    int adjustIndex = findMarkAdjustIndex(newGapStart);
	    int n = marks.size();
	    for (int i = adjustIndex; i < n; i++) {
		MarkData mark = marks.elementAt(i);
		if (mark.index >= oldGapEnd) {
		    break;
		}
		mark.index += gapSize;
	    }
	}
	resetMarksAtZero();
    
protected voidshiftGapEndUp(int newGapEnd)
Adjust the gap end upward. This doesn't move any data, but it does update any marks affected by the boundary change. All marks from the old gap end up to the new gap end are squeezed to the end of the gap (their location has been removed).

	int adjustIndex = findMarkAdjustIndex(getGapEnd());
	int n = marks.size();
	for (int i = adjustIndex; i < n; i++) {
	    MarkData mark = marks.elementAt(i);
	    if (mark.index >= newGapEnd) {
		break;
	    }
	    mark.index = newGapEnd;
	}

	// shift the gap in the character array
	super.shiftGapEndUp(newGapEnd);

	resetMarksAtZero();
    
protected voidshiftGapStartDown(int newGapStart)
Adjust the gap end downward. This doesn't move any data, but it does update any marks affected by the boundary change. All marks from the old gap start down to the new gap start are squeezed to the end of the gap (their location has been removed).

	// Push aside all marks from oldGapStart down to newGapStart.
	int adjustIndex = findMarkAdjustIndex(newGapStart);
	int n = marks.size();
	int g0 = getGapStart();
	int g1 = getGapEnd();
	for (int i = adjustIndex; i < n; i++) {
	    MarkData mark = marks.elementAt(i);
	    if (mark.index > g0) {
		// no more marks to adjust
		break;
	    }
	    mark.index = g1;
	}

	// shift the gap in the character array
	super.shiftGapStartDown(newGapStart);

	resetMarksAtZero();
    
protected voidupdateUndoPositions(java.util.Vector positions, int offset, int length)
Resets the location for all the UndoPosRef instances in positions.

This is meant for internal usage, and is generally not of interest to subclasses.

param
positions the UndoPosRef instances to reset

	// Find the indexs of the end points.
	int endOffset = offset + length;
	int g1 = getGapEnd();
	int startIndex;
	int endIndex = findMarkAdjustIndex(g1 + 1);

	if (offset != 0) {
	    startIndex = findMarkAdjustIndex(g1);
	}
	else {
	    startIndex = 0;
	}

	// Reset the location of the refenences.
	for(int counter = positions.size() - 1; counter >= 0; counter--) {
	    UndoPosRef ref = (UndoPosRef)positions.elementAt(counter);
	    ref.resetLocation(endOffset, g1);
	}
	// We have to resort the marks in the range startIndex to endIndex.
	// We can take advantage of the fact that it will be in
	// increasing order, accept there will be a bunch of MarkData's with
	// the index g1 (or 0 if offset == 0) interspersed throughout.
	if (startIndex < endIndex) {
	    Object[] sorted = new Object[endIndex - startIndex];
	    int addIndex = 0;
	    int counter;
	    if (offset == 0) {
		// If the offset is 0, the positions won't have incremented,
		// have to do the reverse thing.
		// Find the elements in startIndex whose index is 0
		for (counter = startIndex; counter < endIndex; counter++) {
		    MarkData mark = marks.elementAt(counter);
		    if (mark.index == 0) {
			sorted[addIndex++] = mark;
		    }
		}
		for (counter = startIndex; counter < endIndex; counter++) {
		    MarkData mark = marks.elementAt(counter);
		    if (mark.index != 0) {
			sorted[addIndex++] = mark;
		    }
		}
	    }
	    else {
		for (counter = startIndex; counter < endIndex; counter++) {
		    MarkData mark = marks.elementAt(counter);
		    if (mark.index != g1) {
			sorted[addIndex++] = mark;
		    }
		}
		for (counter = startIndex; counter < endIndex; counter++) {
		    MarkData mark = marks.elementAt(counter);
		    if (mark.index == g1) {
			sorted[addIndex++] = mark;
		    }
		}
	    }
	    // And replace
	    marks.replaceRange(startIndex, endIndex, sorted);
	}