/*
* 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.*;
import javax.swing.undo.UndoableEdit;
import java.util.ArrayList;
/**
* Line root element implementation.
*
* @author Miloslav Metelka
* @version 1.00
*/
final class LineRootElement extends GapBranchElement {
private static final String NAME
= AbstractDocument.ParagraphElementName + "Root";
private BaseDocument doc;
private ArrayList addedLines = new ArrayList();
LineRootElement(BaseDocument doc) {
this.doc = doc;
replace(0, 0, new Element[]{ new LineElement(this, 0, null) });
}
public Element getElement(int index) {
if (index < 0) {
throw new IndexOutOfBoundsException("Invalid line index=" + index + " < 0");
}
int elementCount = getElementCount();
if (index >= elementCount) {
throw new IndexOutOfBoundsException("Invalid line index=" + index
+ " >= lineCount=" + elementCount);
}
return super.getElement(index);
}
UndoableEdit insertUpdate(int offset, int length) {
int startOffset = offset;
offset += length;
LineElement lastCreatedLine = null;
CharSeq docText = doc.getText();
while (--offset >= startOffset) {
if (docText.charAt(offset) == '\n') { // line break at offset
lastCreatedLine = new LineElement(this, offset + 1, lastCreatedLine);
addedLines.add(lastCreatedLine);
}
}
if (lastCreatedLine != null) {
Element[] linesAdded = new Element[addedLines.size()];
addedLines.toArray(linesAdded);
addedLines.clear();
ObjectArrayUtilities.reverse(linesAdded);
int index = getElementIndex(startOffset) + 1;
replace(index, 0, linesAdded);
return new Undo(index, EMPTY_ELEMENT_ARRAY, linesAdded);
}
return null;
}
UndoableEdit removeUpdate(int offset, int length) {
int endOffset = offset + length;
CharSeq docText = doc.getText();
while (offset < endOffset) {
if (docText.charAt(offset) == '\n') { // at least one line removed
int index = getElementIndex(offset + 1);
LineElement lineElem = getLineElement(index);
int removeCount = 0;
do {
removeCount++;
lineElem = lineElem.getNext();
} while (lineElem != null
&& lineElem.getStartOffset() <= endOffset
);
Element[] linesRemoved = new Element[removeCount];
copyElements(index, index + removeCount, linesRemoved, 0);
replace(index, linesRemoved.length, EMPTY_ELEMENT_ARRAY);
return new Undo(index, linesRemoved, EMPTY_ELEMENT_ARRAY);
}
offset++;
}
return null;
}
protected void replace(int index, int removeCount, Element[] addedElems) {
if (removeCount > 0) { // Unlink the line elements
// allow possible garbage collection
LineElement lastRemoved = getLineElement(index + removeCount - 1);
if (index > 0) { // not removing the first one
getLineElement(index - 1).setNext(lastRemoved.getNext());
}
lastRemoved.setNext(null);
}
super.replace(index, removeCount, addedElems);
if (addedElems.length > 0) { // Link the new line elements
// Relink the "next" field to first inserted element
if (index > 0) {
LineElement firstAdded = (LineElement)(addedElems[0]);
((LineElement)getElement(index - 1)).setNext(firstAdded);
}
int firstAfterAddedIndex = index + addedElems.length - removeCount;
((LineElement)addedElems[addedElems.length - 1]).setNext(
(firstAfterAddedIndex < getElementCount())
? getLineElement(firstAfterAddedIndex)
: null
);
}
}
public Document getDocument() {
return doc;
}
public Element getParentElement() {
return null;
}
public String getName() {
return NAME;
}
public AttributeSet getAttributes() {
return StyleContext.getDefaultStyleContext().getEmptySet();
}
public int getStartOffset() {
return 0;
}
public int getEndOffset() {
return doc.getLength() + 1;
}
public int getElementIndex(int offset) {
if (offset == 0) { // NB uses this frequently to just get the parent
return 0;
}
return super.getElementIndex(offset);
}
void invalidateAllSyntaxStateInfos() {
int elemCount = getElementCount();
for (int i = elemCount - 1; i >= 0; i--) {
LineElement line = getValidLineElement(i);
line.clearSyntaxStateInfo();
}
}
/** Prepare syntax scanner so that it's ready to scan from requested
* position.
* @param text text segment to be used. Method ensures it will
* be filled so that <CODE>text.array</CODE> contains the character data
* <BR><CODE>text.offset</CODE> logically points to <CODE>reqPos</CODE>
* <BR><CODE>text.count</CODE> equals to <CODE>reqLen</CODE>.
* @param syntax syntax scanner to be used
* @param reqPos position to which the syntax should be prepared
* @param reqLen length that will be scanned by the caller after the syntax
* is prepared. The prepareSyntax() automatically preloads this area
* into the given text segment.
* @param forceLastBuffer force the syntax to think that the scanned area is the last
* in the document. This is useful for forcing the syntax to process all the characters
* in the given area.
* @param forceNotLastBuffer force the syntax to think that the scanned area is NOT
* the last buffer in the document. This is useful when the syntax will continue
* scanning on another buffer.
*/
void prepareSyntax(Segment text, Syntax syntax, int reqPos, int reqLen,
boolean forceLastBuffer, boolean forceNotLastBuffer) throws BadLocationException {
if (reqPos < 0 || reqLen < 0 || reqPos + reqLen > doc.getLength()) {
throw new BadLocationException("reqPos=" + reqPos
+ ", reqLen=" + reqLen + ", doc.getLength()=" + doc.getLength(),
-1 // getting rid of it
);
}
// Find line element that covers the reqPos
int reqPosLineIndex = getElementIndex(reqPos);
LineElement reqPosLineElem = getValidSyntaxStateInfoLineElement(reqPosLineIndex);
// Get nearest previous syntax mark
int lineStartOffset = reqPosLineElem.getStartOffset();
int preScan;
Syntax.StateInfo stateInfo = reqPosLineElem.getSyntaxStateInfo();
if (reqPosLineIndex > 0) {
preScan = stateInfo.getPreScan();
} else { // index is 0
preScan = 0;
if (stateInfo != null) {
throw new IllegalStateException("stateInfo=" + stateInfo);
}
}
// load syntax segment
int intraLineLength = reqPos - lineStartOffset;
doc.getText(lineStartOffset - preScan, preScan + intraLineLength + reqLen, text);
text.offset += preScan;
text.count -= preScan;
// load state into syntax scanner - will scan from mark up to reqPos
syntax.load(stateInfo, text.array, text.offset, intraLineLength, false, reqPos);
// [CAUTION] instead of false used to be forceNotLastBuffer ? false : (reqPos >= docLen)
// ignore tokens until reqPos is reached
while (syntax.nextToken() != null) { }
text.offset += intraLineLength;
text.count -= intraLineLength;
boolean forceLB = forceNotLastBuffer
? false
: (forceLastBuffer || (reqPos + reqLen >= getDocument().getLength()));
syntax.relocate(text.array, text.offset, text.count, forceLB, reqPos + reqLen);
}
private LineElement getValidSyntaxStateInfoLineElement(int lineIndex) throws BadLocationException {
LineElement lineElem = getValidLineElement(lineIndex);
Syntax.StateInfo stateInfo = lineElem.getSyntaxStateInfo();
if (lineIndex > 0 && stateInfo == null) { // need to update
// Find the last line with the valid state info
int validLineIndex = lineIndex - 1; // is >= 0
LineElement validLineElem = null;
while (validLineIndex > 0) {
validLineElem = getValidLineElement(validLineIndex);
stateInfo = validLineElem.getSyntaxStateInfo() ;
if (stateInfo != null) {
break;
}
validLineIndex--;
}
/* validLineIndex now contains index of last line
* that has valid syntax state info. Or it's zero (always valid).
* stateInfo contains state info of last valid line
* or undefined value if validLineIndex == 0.
* validLineElem contains valid line element
* or undefined value if validLineIndex == 0.
*/
Segment text = DocumentUtilities.SEGMENT_CACHE.getSegment();
try {
Syntax syntax = doc.getFreeSyntax();
try {
int lineElemOffset = lineElem.getStartOffset();
int validLineOffset = 0;
int preScan = 0;
if (validLineIndex > 0) {
validLineOffset = validLineElem.getStartOffset();
preScan = stateInfo.getPreScan();
} else { // validLineIndex == 0
stateInfo = null;
}
doc.getText(validLineOffset - preScan,
(lineElemOffset - validLineOffset) + preScan,
text
);
text.offset += preScan;
text.count -= preScan;
/* text segment contains all the required data including preScan
* but "officially" it points to validLineOffset offset.
*/
syntax.load(stateInfo, text.array, text.offset,
text.count, false, lineElemOffset);
int textEndOffset = text.offset + text.count;
do {
validLineIndex++;
validLineElem = getValidLineElement(validLineIndex);
validLineOffset = validLineElem.getStartOffset();
syntax.relocate(text.array, syntax.getOffset(),
textEndOffset - syntax.getOffset(),
false, validLineOffset
);
while (syntax.nextToken() != null) {
// ignore returned tokens
}
validLineElem.updateSyntaxStateInfo(syntax);
} while (validLineIndex != lineIndex);
} finally {
doc.releaseSyntax(syntax);
}
} finally {
DocumentUtilities.SEGMENT_CACHE.releaseSegment(text);
}
}
return lineElem;
}
/**
* Fix state infos after insertion/removal.
* @param offset offset of the modification
* @param length length of the modification. It's lower than zero for removals.
* @return offset of the last line where the syntax stateinfo was modified.
*/
int fixSyntaxStateInfos(int offset, int length) {
if (offset < 0) {
throw new IllegalStateException("offset=" + offset);
}
int lineIndex = getElementIndex(offset);
int addedLinesCount = (length > 0)
? getElementIndex(offset + length) - lineIndex
: 0;
LineElement lineElem = getValidLineElement(lineIndex);
// System.out.println("Fixing lineIndex=" + lineIndex + ", addedLinesCount=" + addedLinesCount);
Segment text = DocumentUtilities.SEGMENT_CACHE.getSegment();
try {
Syntax syntax = doc.getFreeSyntax();
try {
int docLastLineIndex = getElementCount() - 1;
if (lineIndex == docLastLineIndex) { // modification in last line
if (lineIndex == 0 && lineElem.getSyntaxStateInfo() != null) {
// System.out.println("CCCCCCCCCClearing syntax state info");
lineElem.clearSyntaxStateInfo();
}
return doc.getLength();
}
int maybeMatchLineIndex = Math.min(
lineIndex + addedLinesCount + 1, docLastLineIndex);
Syntax.StateInfo stateInfo = null;
int lineStartOffset = 0;
int preScan = 0;
if (lineIndex > 0) {
stateInfo = lineElem.getSyntaxStateInfo();
preScan = stateInfo.getPreScan();
lineStartOffset = lineElem.getStartOffset();
}
lineIndex++; // line index now points to line that follows the modified one
LineElement nextLineElem = getValidLineElement(lineIndex); // should be valid
int nextLineStartOffset = nextLineElem.getStartOffset();
doc.getText(lineStartOffset - preScan,
(nextLineStartOffset - lineStartOffset) + preScan, text);
text.offset += preScan;
text.count -= preScan;
syntax.load(stateInfo, text.array, text.offset, text.count,
false, nextLineStartOffset);
int lineCount = getElementCount();
while (true) {
while (syntax.nextToken() != null) {
// go through the tokens
}
if (lineIndex >= maybeMatchLineIndex) {
stateInfo = nextLineElem.getSyntaxStateInfo();
if (stateInfo != null
&& syntax.compareState(stateInfo) == Syntax.EQUAL_STATE
) {
// System.out.println("SAME-INFO lineIndex=" + lineIndex + ", stateInfo=" + ((Syntax.BaseStateInfo)nextLineElem.getSyntaxStateInfo()).toString(syntax));
break;
}
}
nextLineElem.updateSyntaxStateInfo(syntax);
// System.out.println("FFFFixed lineIndex=" + lineIndex + ", offset=" + nextLineElem.getStartOffset() + ", stateInfo=" + ((Syntax.BaseStateInfo)nextLineElem.getSyntaxStateInfo()).toString(syntax));
lineIndex++;
if (lineIndex >= lineCount) { // still not match at begining of last line
return doc.getLength();
}
lineElem = nextLineElem;
lineStartOffset = nextLineStartOffset;
nextLineElem = getValidLineElement(lineIndex);
nextLineStartOffset = nextLineElem.getStartOffset();
preScan = syntax.getPreScan();
doc.getText(lineStartOffset - preScan,
(nextLineStartOffset - lineStartOffset) + preScan, text);
text.offset += preScan;
text.count -= preScan;
syntax.relocate(text.array, text.offset, text.count,
false, nextLineStartOffset);
}
return lineStartOffset;
} finally {
doc.releaseSyntax(syntax);
}
} catch (BadLocationException e) {
throw new IllegalStateException(e.toString());
} finally {
DocumentUtilities.SEGMENT_CACHE.releaseSegment(text);
}
}
private LineElement getLineElement(int index) {
return (LineElement)getElement(index);
}
private LineElement getValidLineElement(int lineIndex) {
if (lineIndex < 0 || lineIndex >= getElementCount()) {
throw new IllegalArgumentException("lineIndex=" + lineIndex
+ ", lineCount=" + getElementCount());
}
return getLineElement(lineIndex);
}
/**
* @param offset to be examined.
* @return offset that will be high enough to ensure that the given offset
* will be covered by token that can be returned from the syntax.nextToken()
* assuming that the syntax will be prepared with the returned token.
* <BR>It's not guaranteed how much bigger the returned offset will be.
*/
int getTokenSafeOffset(int offset) {
if (offset == 0) { // no valid state-info at offset 0
return offset;
}
try {
int lineIndex = getElementIndex(offset);
LineElement lineElem = getValidSyntaxStateInfoLineElement(lineIndex);
int lineStartOffset = lineElem.getStartOffset();
Syntax.StateInfo stateInfo = lineElem.getSyntaxStateInfo();
if (offset == lineStartOffset && stateInfo.getPreScan() == 0) {
// can be done with the given offset
// System.out.println("getTokenSafeOffset() offset=" + offset + " unchanged");
return offset;
}
// go to next line and maybe further for tokens
// crossing several lines
int lineCount = getElementCount();
while (++lineIndex < lineCount) {
lineElem = getValidSyntaxStateInfoLineElement(lineIndex);
lineStartOffset = lineElem.getStartOffset();
stateInfo = lineElem.getSyntaxStateInfo();
if (lineStartOffset - stateInfo.getPreScan() >= offset) {
// System.out.println("getTokenSafeOffset() offset=" + offset + " to safe offset=" + lineStartOffset + ", preScan=" + stateInfo.getPreScan() + ", lineIndex=" + lineIndex);
return lineStartOffset;
}
}
} catch (BadLocationException e) {
throw new IllegalStateException(e.toString());
}
// System.out.println("getTokenSafeOffset() offset=" + offset + " to DOCLEN=" + doc.getLength());
return doc.getLength();
}
}