/*****************************************************************************
* This file is part of Rinzo
*
* Author: Claudio Cancinos
* WWW: https://sourceforge.net/projects/editorxml
* Copyright (C): 2008, Claudio Cancinos
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program 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 Lesser General Public
* License along with this program; If not, see <http://www.gnu.org/licenses/>
****************************************************************************/
package ar.com.tadp.xml.rinzo.core.partitioner;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.BadPositionCategoryException;
import org.eclipse.jface.text.DefaultPositionUpdater;
import org.eclipse.jface.text.DocumentEvent;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IDocumentPartitioner;
import org.eclipse.jface.text.IDocumentPartitionerExtension;
import org.eclipse.jface.text.IDocumentPartitionerExtension2;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.ITypedRegion;
import org.eclipse.jface.text.Position;
import org.eclipse.jface.text.Region;
import org.eclipse.jface.text.TypedPosition;
import org.eclipse.jface.text.TypedRegion;
import org.eclipse.jface.text.rules.IPartitionTokenScanner;
import org.eclipse.jface.text.rules.IToken;
import ar.com.tadp.xml.rinzo.core.model.XMLNode;
/**
*
* @author ccancinos
*/
public class XMLDocumentPartitioner implements IDocumentPartitioner, IDocumentPartitionerExtension,
IDocumentPartitionerExtension2 {
/**
* @deprecated Field CONTENT_TYPES_CATEGORY is deprecated
*/
public static final String CONTENT_TYPES_CATEGORY = "__content_types_category";
protected IPartitionTokenScanner partitionScanner;
protected String legalContentTypes[];
protected IDocument document;
protected int previousDocumentLength;
protected DefaultPositionUpdater positionUpdater;
protected int startOffset;
protected int endOffset;
protected int deleteOffset;
private String positionCategory;
public XMLDocumentPartitioner(IPartitionTokenScanner scanner, String legalContentTypes[]) {
partitionScanner = scanner;
this.legalContentTypes = legalContentTypes;
positionCategory = "__content_types_category" + hashCode();
positionUpdater = new DefaultPositionUpdater(positionCategory);
}
public String[] getManagingPositionCategories() {
return (new String[] { positionCategory });
}
public void connect(IDocument document) {
this.document = document;
document.addPositionCategory(positionCategory);
initialize();
}
protected void initialize() {
partitionScanner.setRange(document, 0, document.getLength());
try {
for (IToken token = partitionScanner.nextToken(); !token.isEOF(); token = partitionScanner.nextToken()) {
String contentType = getTokenContentType(token);
if (isSupportedContentType(contentType)) {
TypedPosition p = createPosition(partitionScanner.getTokenOffset(),
partitionScanner.getTokenLength(), contentType);
addPosition(document, positionCategory, p);
}
}
} catch (BadLocationException _ex) {
} catch (BadPositionCategoryException _ex) {
}
}
protected TypedPosition createPosition(int tokenOffset, int tokenLength, String contentType) {
return new XMLNode(tokenOffset, tokenLength, contentType, document);
}
public void disconnect() {
try {
document.removePositionCategory(positionCategory);
} catch (BadPositionCategoryException _ex) {
}
}
public void documentAboutToBeChanged(DocumentEvent event) {
previousDocumentLength = event.getDocument().getLength();
startOffset = -1;
endOffset = -1;
deleteOffset = -1;
}
public boolean documentChanged(DocumentEvent event) {
return this.documentChanged2(event) != null;
}
private void rememberRegion(int offset, int length) {
if (startOffset == -1) {
startOffset = offset;
} else if (offset < startOffset) {
startOffset = offset;
}
int localEndOffset = offset + length;
if (endOffset == -1) {
endOffset = localEndOffset;
} else if (localEndOffset > endOffset) {
endOffset = localEndOffset;
}
}
private void rememberDeletedOffset(int offset) {
deleteOffset = offset;
}
private IRegion createRegion() {
if (deleteOffset == -1) {
return startOffset == -1 || endOffset == -1 ?
null :
(IRegion) new Region(startOffset, endOffset - startOffset);
}
if (startOffset == -1 || endOffset == -1) {
return new Region(deleteOffset, 0);
}
int offset = Math.min(deleteOffset, startOffset);
int localEndOffset = Math.max(deleteOffset, endOffset);
return new Region(offset, localEndOffset - offset);
}
public IRegion documentChanged2(DocumentEvent e) {
try {
IDocument d = e.getDocument();
Position category[] = d.getPositions(positionCategory);
IRegion line = d.getLineInformationOfOffset(e.getOffset());
int reparseStart = line.getOffset();
int partitionStart = -1;
String contentType = null;
int first = d.computeIndexInCategory(positionCategory, reparseStart);
if (first > 0) {
TypedPosition partition = (TypedPosition) category[first - 1];
if (partition.includes(reparseStart)) {
partitionStart = partition.getOffset();
contentType = partition.getType();
if (e.getOffset() == partition.getOffset() + partition.getLength()) {
reparseStart = partitionStart;
}
first--;
} else if (reparseStart == e.getOffset()
&& reparseStart == partition.getOffset() + partition.getLength()) {
partitionStart = partition.getOffset();
contentType = partition.getType();
reparseStart = partitionStart;
first--;
} else {
partitionStart = partition.getOffset() + partition.getLength();
contentType = "__dftl_partition_content_type";
}
}
positionUpdater.update(e);
for (int i = first; i < category.length; i++) {
Position p = category[i];
if (!p.isDeleted) {
continue;
}
rememberDeletedOffset(e.getOffset());
break;
}
category = d.getPositions(positionCategory);
partitionScanner
.setPartialRange(d, reparseStart, d.getLength() - reparseStart, contentType, partitionStart);
int lastScannedPosition = reparseStart;
for (IToken token = partitionScanner.nextToken(); !token.isEOF();) {
contentType = getTokenContentType(token);
if (!isSupportedContentType(contentType)) {
token = partitionScanner.nextToken();
} else {
int start = partitionScanner.getTokenOffset();
int length = partitionScanner.getTokenLength();
lastScannedPosition = (start + length) - 1;
for (; first < category.length; first++) {
TypedPosition p = (TypedPosition) category[first];
if (lastScannedPosition < p.offset + p.length
&& (!p.overlapsWith(start, length) || d.containsPosition(positionCategory, start,
length) && contentType.equals(p.getType()))) {
break;
}
rememberRegion(p.offset, p.length);
d.removePosition(positionCategory, p);
}
if (d.containsPosition(positionCategory, start, length)) {
if (lastScannedPosition > e.getOffset()) {
return createRegion();
}
first++;
} else {
try {
TypedPosition p = createPosition(start, length, contentType);
addPosition(d, positionCategory, p);
rememberRegion(start, length);
} catch (BadPositionCategoryException _ex) {
} catch (BadLocationException _ex) {
}
}
token = partitionScanner.nextToken();
}
}
if (lastScannedPosition != reparseStart)
lastScannedPosition++;
for (first = d.computeIndexInCategory(positionCategory, lastScannedPosition); first < category.length;) {
TypedPosition p = (TypedPosition) category[first++];
d.removePosition(positionCategory, p);
rememberRegion(p.offset, p.length);
}
} catch (BadPositionCategoryException _ex) {
} catch (BadLocationException _ex) {
}
return createRegion();
}
protected void addPosition(IDocument d, String positionCategory, TypedPosition position)
throws BadLocationException, BadPositionCategoryException {
d.addPosition(positionCategory, position);
}
public TypedPosition findClosestPosition(int offset) {
try {
int index = document.computeIndexInCategory(positionCategory, offset);
Position category[] = document.getPositions(positionCategory);
if (category.length == 0) {
return null;
}
if (index < category.length && offset == category[index].offset) {
return (TypedPosition) category[index];
}
if (index > 0) {
index--;
}
return (TypedPosition) category[index];
} catch (BadPositionCategoryException _ex) {
} catch (BadLocationException _ex) {
}
return null;
}
public TypedPosition findPreviousNonWhiteSpacePosition(int offset) {
try {
int index = document.computeIndexInCategory(positionCategory, offset);
Position positions[] = document.getPositions(positionCategory);
if (positions.length == 0) {
return null;
}
if (index == positions.length) {
index--;
}
for (; index >= 0; index--) {
TypedPosition position = (TypedPosition) positions[index];
if (position instanceof XMLNode) {
XMLNode node = (XMLNode) position;
if (!node.isEmpty())
return node;
}
}
} catch (BadLocationException e) {
e.printStackTrace();
} catch (BadPositionCategoryException e) {
e.printStackTrace();
}
return null;
}
public TypedPosition findPreviousPosition(int offset) {
try {
int index = document.computeIndexInCategory(positionCategory, offset);
Position positions[] = document.getPositions(positionCategory);
if (positions.length == 0) {
return null;
}
if (index <= positions.length && index >= 1) {
return (TypedPosition) positions[index - 1];
}
} catch (BadLocationException e) {
e.printStackTrace();
} catch (BadPositionCategoryException e) {
e.printStackTrace();
}
return null;
}
public String getContentType(int offset) {
TypedPosition p = findClosestPosition(offset);
return p != null && p.includes(offset) ? p.getType() : "__dftl_partition_content_type";
}
public ITypedRegion getPartition(int offset) {
try {
Position category[] = document.getPositions(positionCategory);
if (category == null || category.length == 0) {
return new TypedRegion(0, document.getLength(), "__dftl_partition_content_type");
}
int index = document.computeIndexInCategory(positionCategory, offset);
if (index < category.length) {
TypedPosition next = (TypedPosition) category[index];
if (offset == next.offset) {
return new TypedRegion(next.getOffset(), next.getLength(), next.getType());
}
if (index == 0) {
return new TypedRegion(0, next.offset, "__dftl_partition_content_type");
}
TypedPosition previous = (TypedPosition) category[index - 1];
if (previous.includes(offset)) {
return new TypedRegion(previous.getOffset(), previous.getLength(), previous.getType());
}
int endOffset = previous.getOffset() + previous.getLength();
return new TypedRegion(endOffset, next.getOffset() - endOffset, "__dftl_partition_content_type");
}
TypedPosition previous = (TypedPosition) category[category.length - 1];
if (previous.includes(offset)) {
return new TypedRegion(previous.getOffset(), previous.getLength(), previous.getType());
}
int endOffset = previous.getOffset() + previous.getLength();
return new TypedRegion(endOffset, document.getLength() - endOffset, "__dftl_partition_content_type");
} catch (BadPositionCategoryException _ex) {
} catch (BadLocationException _ex) {
}
return new TypedRegion(0, document.getLength(), "__dftl_partition_content_type");
}
public ITypedRegion[] computePartitioning(int offset, int length) {
return computePartitioning(offset, length, false);
}
public String[] getLegalContentTypes() {
return legalContentTypes;
}
protected boolean isSupportedContentType(String contentType) {
if (contentType != null) {
for (int i = 0; i < legalContentTypes.length; i++)
if (legalContentTypes[i].equals(contentType)) {
return true;
}
}
return false;
}
protected String getTokenContentType(IToken token) {
Object data = token.getData();
return (data instanceof String) ? (String) data : null;
}
public String getContentType(int offset, boolean preferOpenPartitions) {
return getPartition(offset, preferOpenPartitions).getType();
}
public ITypedRegion getPartition(int offset, boolean preferOpenPartitions) {
ITypedRegion region = getPartition(offset);
if (preferOpenPartitions && region.getOffset() == offset
&& !region.getType().equals("__dftl_partition_content_type")) {
if (offset > 0) {
region = getPartition(offset - 1);
if (region.getType().equals("__dftl_partition_content_type")) {
return region;
}
}
return new TypedRegion(offset, 0, "__dftl_partition_content_type");
}
return region;
}
public ITypedRegion[] computePartitioning(int offset, int length, boolean includeZeroLengthPartitions) {
List<TypedRegion> list = new ArrayList<TypedRegion>();
try {
int endOffset = offset + length;
Position category[] = document.getPositions(positionCategory);
TypedPosition previous = null;
TypedPosition current = null;
Position gap = new Position(0);
int startIndex = getFirstIndexEndingAfterOffset(category, offset);
int endIndex = getFirstIndexStartingAfterOffset(category, endOffset);
for (int i = startIndex; i < endIndex; i++) {
current = (TypedPosition) category[i];
int gapOffset = previous == null ? 0 : previous.getOffset() + previous.getLength();
gap.setOffset(gapOffset);
gap.setLength(current.getOffset() - gapOffset);
if (includeZeroLengthPartitions && overlapsOrTouches(gap, offset, length) || gap.getLength() > 0
&& gap.overlapsWith(offset, length)) {
int start = Math.max(offset, gapOffset);
int end = Math.min(endOffset, gap.getOffset() + gap.getLength());
list.add(new TypedRegion(start, end - start, "__dftl_partition_content_type"));
}
if (current.overlapsWith(offset, length)) {
int start = Math.max(offset, current.getOffset());
int end = Math.min(endOffset, current.getOffset() + current.getLength());
list.add(new TypedRegion(start, end - start, current.getType()));
}
previous = current;
}
if (previous != null) {
int gapOffset = previous.getOffset() + previous.getLength();
gap.setOffset(gapOffset);
gap.setLength(document.getLength() - gapOffset);
if (includeZeroLengthPartitions && overlapsOrTouches(gap, offset, length) || gap.getLength() > 0
&& gap.overlapsWith(offset, length)) {
int start = Math.max(offset, gapOffset);
int end = Math.min(endOffset, document.getLength());
list.add(new TypedRegion(start, end - start, "__dftl_partition_content_type"));
}
}
if (list.isEmpty()) {
list.add(new TypedRegion(offset, length, "__dftl_partition_content_type"));
}
} catch (BadPositionCategoryException _ex) {
}
TypedRegion result[] = new TypedRegion[list.size()];
list.toArray(result);
return result;
}
private boolean overlapsOrTouches(Position gap, int offset, int length) {
return gap.getOffset() <= offset + length && offset <= gap.getOffset() + gap.getLength();
}
private int getFirstIndexEndingAfterOffset(Position positions[], int offset) {
int i = -1;
int j;
for (j = positions.length; j - i > 1;) {
int k = i + j >> 1;
Position p = positions[k];
if (p.getOffset() + p.getLength() > offset) {
j = k;
} else {
i = k;
}
}
return j;
}
private int getFirstIndexStartingAfterOffset(Position positions[], int offset) {
int i = -1;
int j;
for (j = positions.length; j - i > 1;) {
int k = i + j >> 1;
Position p = positions[k];
if (p.getOffset() >= offset) {
j = k;
} else {
i = k;
}
}
return j;
}
}