/*
* Sun Public License Notice
*
* The contents of this file are subject to the Sun Public License
* Version 1.0 (the "License"). You may not use this file except in
* compliance with the License. A copy of the License is available at
* http://www.sun.com/
*
* The Original Code is NetBeans. The Initial Developer of the Original
* Code is Sun Microsystems, Inc. Portions Copyright 1997-2003 Sun
* Microsystems, Inc. All Rights Reserved.
*/
package org.netbeans.editor;
import javax.swing.text.AbstractDocument;
import javax.swing.text.BadLocationException;
import javax.swing.text.Position;
import javax.swing.text.Segment;
import javax.swing.undo.AbstractUndoableEdit;
import javax.swing.undo.CannotRedoException;
import javax.swing.undo.CannotUndoException;
import javax.swing.undo.UndoableEdit;
/**
* Content of the document.
*
* @author Miloslav Metelka
* @version 1.00
*/
final class DocumentContent implements AbstractDocument.Content, CharSeq, GapStart {
private static final char[] EMPTY_CHAR_ARRAY = new char[0];
/**
* Invalid undoable edit being used to mark that the line undo was already
* processed. It must never be undone/redone as it's used in a flyweight
* way but the undomanager's operation changes states of undoable edits
* being undone/redone.
*/
private static final UndoableEdit INVALID_EDIT = new AbstractUndoableEdit();
/** Vector holding the marks for the document */
private final MarkVector markVector;
/** Array with gap holding the text of the document */
private char[] charArray;
/** Start index of the gap */
private int gapStart;
/** Length of the gap */
private int gapLength;
// Unfortunately needed for syntax updates
private BaseDocument doc;
DocumentContent() {
charArray = EMPTY_CHAR_ARRAY;
markVector = new MarkVector();
// Insert implied '\n'
insertText(0, "\n");
}
public final int getGapStart() { // to implement GapStart
return gapStart;
}
public UndoableEdit insertString(int offset, String text)
throws BadLocationException {
checkBounds(offset, 0, length() - 1);
return new Edit(offset, text);
}
public UndoableEdit remove(int offset, int length)
throws BadLocationException {
checkBounds(offset, length, length() - 1);
return new Edit(offset, length);
}
public Position createPosition(int offset) throws BadLocationException {
return new BasePosition(createMark(offset));
}
public Position createBiasPosition(int offset, Position.Bias bias)
throws BadLocationException {
return new BasePosition(createBiasMark(offset, bias));
}
MultiMark createBiasMark(int offset, Position.Bias bias) {
return markVector.insert(markVector.createBiasMark(offset, bias));
}
MultiMark createMark(int offset) {
return markVector.insert(markVector.createMark(offset));
}
public int length() {
return charArray.length - gapLength;
}
public void getChars(int offset, int length, Segment chars)
throws BadLocationException {
checkBounds(offset, length, length());
if ((offset + length) <= gapStart) { // completely below gap
chars.array = charArray;
chars.offset = offset;
} else if (offset >= gapStart) { // completely above gap
chars.array = charArray;
chars.offset = offset + gapLength;
} else { // spans the gap, must copy
chars.array = copySpanChars(offset, length);
chars.offset = 0;
}
chars.count = length;
}
public String getString(int offset, int length)
throws BadLocationException {
checkBounds(offset, length, length());
return getText(offset, length);
}
String getText(int offset, int length) {
if (offset < 0 || length < 0) {
throw new IllegalStateException("offset=" + offset + ", length=" + length);
}
String ret;
if ((offset + length) <= gapStart) { // completely below gap
ret = new String(charArray, offset, length);
} else if (offset >= gapStart) { // completely above gap
ret = new String(charArray, offset + gapLength, length);
} else { // spans the gap, must copy
ret = new String(copySpanChars(offset, length));
}
return ret;
}
public char charAt(int index) {
return charArray[getRawIndex(index)];
}
void compact() {
if (gapLength > 0) {
int newLength = charArray.length - gapLength;
char[] newCharArray = new char[newLength];
int gapEnd = gapStart + gapLength;
System.arraycopy(charArray, 0, newCharArray, 0, gapStart);
System.arraycopy(charArray, gapEnd, newCharArray, gapStart,
charArray.length - gapEnd);
charArray = newCharArray;
gapStart = charArray.length;
gapLength = 0;
}
markVector.compact();
}
private int getRawIndex(int index) {
return (index < gapStart) ? index : (index + gapLength);
}
private void moveGap(int index) {
if (index <= gapStart) { // move gap down
int moveSize = gapStart - index;
System.arraycopy(charArray, index, charArray,
gapStart + gapLength - moveSize, moveSize);
gapStart = index;
} else { // above gap
int gapEnd = gapStart + gapLength;
int moveSize = index - gapStart;
System.arraycopy(charArray, gapEnd, charArray, gapStart, moveSize);
gapStart += moveSize;
}
}
private void enlargeGap(int extraLength) {
int newLength = Math.max(10, charArray.length * 3 / 2 + extraLength);
int gapEnd = gapStart + gapLength;
int afterGapLength = (charArray.length - gapEnd);
int newGapEnd = newLength - afterGapLength;
char[] newCharArray = new char[newLength];
System.arraycopy(charArray, 0, newCharArray, 0, gapStart);
System.arraycopy(charArray, gapEnd, newCharArray, newGapEnd, afterGapLength);
charArray = newCharArray;
gapLength = newGapEnd - gapStart;
}
private char[] copyChars(int offset, int length) {
char[] ret;
if ((offset + length) <= gapStart) { // completely below gap
ret = new char[length];
System.arraycopy(charArray, offset, ret, 0, length);
} else if (offset >= gapStart) { // completely above gap
ret = new char[length];
System.arraycopy(charArray, offset + gapLength, ret, 0, length);
} else { // spans the gap, must copy
ret = copySpanChars(offset, length);
}
return ret;
}
private char[] copySpanChars(int offset, int length) {
char[] ret = new char[length];
int belowGap = gapStart - offset;
System.arraycopy(charArray, offset, ret, 0, belowGap);
System.arraycopy(charArray, gapStart + gapLength,
ret, belowGap, length - belowGap);
return ret;
}
void insertText(int offset, String text) {
int textLength = text.length();
int extraLength = textLength - gapLength;
if (extraLength > 0) {
enlargeGap(extraLength);
}
if (offset != gapStart) {
moveGap(offset);
}
text.getChars(0, textLength, charArray, gapStart);
gapStart += textLength;
gapLength -= textLength;
}
void removeText(int offset, int length) {
if (offset >= gapStart) { // completely over gap
if (offset > gapStart) {
moveGap(offset);
}
} else { // completely below gap or spans the gap
int endOffset = offset + length;
if (endOffset <= gapStart) {
if (endOffset < gapStart) {
moveGap(endOffset);
}
gapStart -= length;
} else { // spans gap
gapStart = offset;
}
}
gapLength += length;
}
private void checkBounds(int offset, int length, int limitOffset)
throws BadLocationException {
if (offset < 0) {
throw new BadLocationException("Invalid offset=" + offset, offset);
}
if (length < 0) {
throw new BadLocationException("Invalid length" + length, length);
}
if (offset + length > limitOffset) {
throw new BadLocationException(
"docLength=" + (length() - 1)
+ ": Invalid offset"
+ ((length != 0) ? "+length" : "")
+ "=" + (offset + length),
(offset + length)
);
}
}
void setBaseDocument(BaseDocument doc) {
this.doc = doc;
}
class Edit extends AbstractUndoableEdit {
/** Constructor used for insert.
* @param offset offset of insert.
* @param text inserted text.
*/
Edit(int offset, String text) {
this.offset = offset;
this.length = text.length();
this.text = text;
undoOrRedo(length, false); // pretend redo
}
/** Constructor used for remove.
* @param offset offset of remove.
* @param length length of the removed text.
*/
Edit(int offset, int length) {
this.offset = offset;
this.length = -length;
// Added to make sure the text is not inited later at unappropriate time
this.text = getText(offset, length);
undoOrRedo(-length, false); // pretend redo
}
private int offset;
private int length;
private String text;
private MarkVector.Undo markVectorUndo;
private UndoableEdit lineUndo;
private int syntaxUpdateOffset;
public void undo() throws CannotUndoException {
super.undo();
undoOrRedo(-length, true);
}
public void redo() throws CannotRedoException {
super.redo();
undoOrRedo(length, false);
}
private LineRootElement getLineRoot() {
return (LineRootElement)doc.getParagraphElement(0).getParentElement();
}
private void undoOrRedo(int len, boolean undo) {
boolean lineUndoAssigned = false; // whether lineUndo was assigned in this body
// Line undo information must be obtained before doing the actual remove
if (lineUndo == null && len < 0) {
lineUndo = getLineRoot().removeUpdate(offset, -len);
if (lineUndo == null) { // must be valid value
lineUndo = INVALID_EDIT; // must not be undone - can't be flyweight!!!
}
lineUndoAssigned = true;
}
// Fix text content
if (len < 0) { // do remove
removeText(offset, -len);
} else { // do insert
insertText(offset, text);
}
// Update marks
markVectorUndo = markVector.update(offset, len, markVectorUndo);
// Update document's compatible marks storage - no undo
doc.marksStorage.update(offset, len, null);
// Fix line elements structure
if (lineUndo != null) {
if (!lineUndoAssigned && lineUndo != INVALID_EDIT) {
if (undo) {
lineUndo.undo();
} else {
lineUndo.redo();
}
}
} else { // line undo not yet assigned
if (len < 0) { // had to be assigned before
throw new IllegalStateException();
}
lineUndo = getLineRoot().insertUpdate(offset, length);
if (lineUndo == null) { // must be valid value
lineUndo = INVALID_EDIT; // must not be undone - can't be flyweight!!!
}
lineUndoAssigned = true;
}
// Update syntax state infos (based on updated text and line structures
syntaxUpdateOffset = getLineRoot().fixSyntaxStateInfos(offset, len);
}
/**
* @return text of the modification.
*/
final String getUndoRedoText() {
return text;
}
final int getSyntaxUpdateOffset() {
return syntaxUpdateOffset;
}
}
}