/*
* Copyright (c) 2002-2004 Widespace, OU and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Common Public License v1.0
* which accompanies this distribution, and is available at
* http://solareclipse.sourceforge.net/legal/cpl-v10.html
*
* Contributors:
* Igor Malinin - initial contribution
*
* $Id: AbstractPartitioner.java,v 1.4 2006-10-21 23:13:53 pombredanne Exp $
*/
package net.sourceforge.phpeclipse.ui.text.rules;
import java.util.ArrayList;
import java.util.List;
//incastrix
//import org.eclipse.jface.text.Assert;
import org.eclipse.core.runtime.Assert;
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.IRegion;
import org.eclipse.jface.text.ITypedRegion;
import org.eclipse.jface.text.Region;
import org.eclipse.jface.text.TypedRegion;
import org.eclipse.jface.text.rules.IPartitionTokenScanner;
import org.eclipse.jface.text.rules.IToken;
/**
* Advanced partitioner which maintains partitions as views to connected
* document. Views have own partitioners themselves. This class is designed as a
* base for complex partitioners such as for JSP, PHP, ASP, etc. languages.
*
* @author Igor Malinin
*/
public abstract class AbstractPartitioner implements IDocumentPartitioner,
IDocumentPartitionerExtension {
public final static boolean DEBUG = false;
/** Partition scanner */
protected IPartitionTokenScanner scanner;
/** Connected document */
protected IDocument document;
/** Flat structure of the document */
protected List nodes = new ArrayList();
/** The offset at which the first changed partition starts */
protected int regionStart;
/** The offset at which the last changed partition ends */
protected int regionEnd;
public AbstractPartitioner(IPartitionTokenScanner scanner) {
this.scanner = scanner;
}
protected FlatNode createNode(String type, int offset, int length) {
if (DEBUG) {
Assert.isTrue(offset >= 0, Integer.toString(offset));
}
FlatNode node = new FlatNode(type);
node.offset = offset;
node.length = length;
return node;
}
protected void addInnerRegion(FlatNode position) {
nodes.add(computeFlatNodeIndex(position.offset), position);
}
protected void removeInnerRegion(FlatNode position) {
nodes.remove(position); // TODO: Indexed remove?
}
protected void deleteInnerRegion(FlatNode position) {
nodes.remove(position); // TODO: Indexed remove?
}
protected void resizeInnerRegion(FlatNode position) {
}
/*
* @see org.eclipse.jface.text.IDocumentPartitioner#connect(IDocument)
*/
public void connect(IDocument document) {
this.document = document;
initialize();
}
/*
* @see org.eclipse.jface.text.IDocumentPartitioner#disconnect()
*/
public void disconnect() {
nodes.clear();
document = null;
}
/**
* Performs the initial partitioning of the partitioner's document.
*/
protected void initialize() {
scanner.setRange(document, 0, document.getLength());
// axelcl start
nodes.clear();
// axelcl end
IToken token = scanner.nextToken();
while (!token.isEOF()) {
String contentType = getTokenContentType(token);
if (isSupportedContentType(contentType)) {
addInnerRegion(createNode(contentType,
scanner.getTokenOffset(), scanner.getTokenLength()));
}
token = scanner.nextToken();
}
}
/*
* @see org.eclipse.jface.text.IDocumentPartitioner#documentAboutToBeChanged(DocumentEvent)
*/
public void documentAboutToBeChanged(DocumentEvent event) {
regionStart = regionEnd = -1;
}
/*
* @see org.eclipse.jface.text.IDocumentPartitioner#documentChanged(DocumentEvent)
*/
public boolean documentChanged(DocumentEvent event) {
return (documentChanged2(event) != null);
}
/*
* @see org.eclipse.jface.text.IDocumentPartitionerExtension#documentChanged2(DocumentEvent)
*/
public IRegion documentChanged2(DocumentEvent event) {
int first = fixupPartitions(event);
FlatNode[] category = (FlatNode[]) nodes.toArray(new FlatNode[nodes
.size()]);
// repartition changed region
String contentType = IDocument.DEFAULT_CONTENT_TYPE;
int offset;
if (first == 0) {
offset = 0; // Bug #697414: first offset
} else {
offset = event.getOffset();
FlatNode partition = category[first - 1];
if (partition.includes(offset)) {
offset = partition.offset;
contentType = partition.type;
--first;
} else if (offset == partition.offset + partition.length) {
offset = partition.offset;
contentType = partition.type;
--first;
} else {
offset = partition.offset + partition.length;
}
}
// should not be changed since last conversion
// category = (FlatNode[]) nodes.toArray(new FlatNode[nodes.size()]);
if (DEBUG) {
Assert.isTrue(offset >= 0, Integer.toString(offset));
}
scanner.setPartialRange(document, offset, document.getLength(),
contentType, offset);
int lastScannedPosition = offset;
IToken token = scanner.nextToken();
while (!token.isEOF()) {
contentType = getTokenContentType(token);
if (!isSupportedContentType(contentType)) {
token = scanner.nextToken();
continue;
}
offset = scanner.getTokenOffset();
if (DEBUG) {
Assert.isTrue(offset >= 0, scanner.toString());
}
int length = scanner.getTokenLength();
lastScannedPosition = offset + length;
// remove all affected positions
while (first < category.length) {
FlatNode p = category[first];
if (p.offset + p.length < lastScannedPosition
|| (p.overlapsWith(offset, length) && (!containsPosition(
offset, length) || !contentType.equals(p.type)))) {
removeInnerRegion(p);
rememberRegion(p.offset, p.length);
++first;
} else {
break;
}
}
// if position already exists we are done
if (containsPosition(offset, length)) {
if (lastScannedPosition > event.getOffset()) {
// TODO: optional repartition till end of doc
return createRegion();
}
++first;
} else {
// insert the new type position
addInnerRegion(createNode(contentType, offset, length));
rememberRegion(offset, length);
}
// try {
token = scanner.nextToken();
// } catch (ArrayIndexOutOfBoundsException e) {
// System.out.println(this.getClass().toString());
// throw e;
// }
}
// remove all positions behind lastScannedPosition
// since there aren't any further types
// Do not need to recalculate (lost remove events)!
// first = computeIndexInInnerDocuments(lastScannedPosition);
while (first < category.length) {
FlatNode p = category[first++];
removeInnerRegion(p);
rememberRegion(p.offset, p.length);
}
return createRegion();
}
protected int fixupPartitions(DocumentEvent event) {
int offset = event.getOffset();
int length = event.getLength();
int end = offset + length;
// fixup flat nodes laying on change boundaries
int first = computeFlatNodeIndex(offset);
if (first > 0) {
FlatNode p = (FlatNode) nodes.get(first - 1);
int right = p.offset + p.length;
if (offset < right) {
// change overlaps with partition
if (end < right) {
// cahnge completely inside partition
String text = event.getText();
p.length -= length;
if (text != null) {
p.length += text.length();
}
} else {
// cut partition at right
int cut = p.offset + p.length - offset;
p.length -= cut;
}
}
}
int last = computeFlatNodeIndex(end);
if (first < last) {
FlatNode p = (FlatNode) nodes.get(last - 1);
int right = p.offset + p.length;
if (end < right) {
// cut partition at left
int cut = end - p.offset;
p.length -= cut;
p.offset = offset;
String text = event.getText();
if (text != null) {
p.offset += text.length();
}
--last;
}
}
// fixup flat nodes laying afrer change
String text = event.getText();
if (text != null) {
length -= text.length();
}
for (int i = last, size = nodes.size(); i < size; i++) {
((FlatNode) nodes.get(i)).offset -= length;
}
// delete flat nodes laying completely inside change boundaries
if (first < last) {
do {
deleteInnerRegion((FlatNode) nodes.get(--last));
} while (first < last);
rememberRegion(offset, 0);
}
return first;
}
/**
* Returns whether the given type is one of the legal content types.
*
* @param contentType
* the content type to check
* @return true
if the content type is a legal content type
*/
protected boolean isSupportedContentType(String contentType) {
/* TODO: implementation */
// if (contentType != null) {
// for (int i= 0; i < fLegalContentTypes.length; i++) {
// if (fLegalContentTypes[i].equals(contentType)) {
// return true;
// }
// }
// }
// return false;
return (contentType != null);
}
/**
* Returns a content type encoded in the given token. If the token's data is
* not null
and a string it is assumed that it is the encoded
* content type.
*
* @param token
* the token whose content type is to be determined
* @return the token's content type
*/
protected String getTokenContentType(IToken token) {
Object data = token.getData();
if (data instanceof String) {
return (String) data;
}
return null;
}
/*
* @see org.eclipse.jface.text.IDocumentPartitioner#getLegalContentTypes()
*/
public String[] getLegalContentTypes() {
// TODO: implementation
return null;
}
/*
* @see org.eclipse.jface.text.IDocumentPartitioner#getContentType(int)
*/
public String getContentType(int offset) {
return getPartition(offset).getType();
}
/*
* @see org.eclipse.jface.text.IDocumentPartitioner#getPartition(int)
*/
public ITypedRegion getPartition(int offset) {
if (nodes.size() == 0) {
return new TypedRegion(0, document.getLength(),
IDocument.DEFAULT_CONTENT_TYPE);
}
int index = computeFlatNodeIndex(offset);
if (index < nodes.size()) {
FlatNode next = (FlatNode) nodes.get(index);
if (offset == next.offset) {
return new TypedRegion(next.offset, next.length, next.type);
}
if (index == 0) {
return new TypedRegion(0, next.offset,
IDocument.DEFAULT_CONTENT_TYPE);
}
FlatNode prev = (FlatNode) nodes.get(index - 1);
if (prev.includes(offset)) {
return new TypedRegion(prev.offset, prev.length, prev.type);
}
int end = prev.offset + prev.length;
return new TypedRegion(end, next.offset - end,
IDocument.DEFAULT_CONTENT_TYPE);
}
FlatNode prev = (FlatNode) nodes.get(nodes.size() - 1);
if (prev.includes(offset)) {
return new TypedRegion(prev.offset, prev.length, prev.type);
}
int end = prev.offset + prev.length;
return new TypedRegion(end, document.getLength() - end,
IDocument.DEFAULT_CONTENT_TYPE);
}
/*
* @see org.eclipse.jface.text.IDocumentPartitioner#computePartitioning(int,
* int)
*/
public ITypedRegion[] computePartitioning(int offset, int length) {
List list = new ArrayList();
int end = offset + length;
int index = computeFlatNodeIndex(offset);
while (true) {
FlatNode prev = (index > 0) ? (FlatNode) nodes.get(index - 1)
: null;
if (prev != null) {
if (prev.overlapsWith(offset, length)) {
list.add(new TypedRegion(prev.offset, prev.length,
prev.type));
}
if (end <= prev.offset + prev.length) {
break;
}
}
FlatNode next = (index < nodes.size()) ? (FlatNode) nodes
.get(index) : null;
if (next == null || offset < next.offset) {
int off0 = offset;
int off1 = offset + length;
if (prev != null && off0 < prev.offset + prev.length) {
off0 = prev.offset + prev.length;
}
if (next != null && next.offset < off1) {
off1 = next.offset;
}
if (off0 < off1) {
list.add(new TypedRegion(off0, off1 - off0,
IDocument.DEFAULT_CONTENT_TYPE));
}
}
if (next == null) {
break;
}
++index;
}
return (TypedRegion[]) list.toArray(new TypedRegion[list.size()]);
}
/**
* Computes the index in the list of flat nodes at which an flat node with
* the given offset would be inserted. The flat node is supposed to become
* the first in this list of all flat nodes with the same offset.
*
* @param offset
* the offset for which the index is computed
* @return the computed index
*/
protected int computeFlatNodeIndex(int offset) {
if (nodes.size() == 0) {
return 0;
}
int left = 0, mid = 0;
int right = nodes.size() - 1;
FlatNode p = null;
while (left < right) {
mid = (left + right) / 2;
p = (FlatNode) nodes.get(mid);
if (offset < p.offset) {
right = (left == mid) ? left : mid - 1;
} else if (offset > p.offset) {
left = (right == mid) ? right : mid + 1;
} else if (offset == p.offset) {
left = right = mid;
}
}
int pos = left;
p = (FlatNode) nodes.get(pos);
if (offset > p.offset) {
// append to the end
pos++;
} else {
// entry will became the first of all entries with the same offset
do {
--pos;
if (pos < 0) {
break;
}
p = (FlatNode) nodes.get(pos);
} while (offset == p.offset);
++pos;
}
return pos;
}
public boolean containsPosition(int offset, int length) {
int size = nodes.size();
if (size == 0) {
return false;
}
int index = computeFlatNodeIndex(offset);
if (index < size) {
FlatNode p = (FlatNode) nodes.get(index);
while (p.offset == offset) {
if (p.length == length) {
return true;
}
if (++index < size) {
p = (FlatNode) nodes.get(index);
} else {
break;
}
}
}
return false;
}
/**
* Helper method for tracking the minimal region containg all partition
* changes. If offset
is smaller than the remembered offset,
* offset
will from now on be remembered. If
* offset + length
is greater than the remembered end offset,
* it will be remembered from now on.
*
* @param offset
* the offset
* @param length
* the length
*/
protected final void rememberRegion(int offset, int length) {
// remember start offset
if (regionStart == -1) {
regionStart = offset;
} else if (offset < regionStart) {
regionStart = offset;
}
// remember end offset
int endOffset = offset + length;
if (regionEnd == -1) {
regionEnd = endOffset;
} else if (endOffset > regionEnd) {
regionEnd = endOffset;
}
}
/**
* Creates the minimal region containing all partition changes using the
* remembered offsets.
*
* @return the minimal region containing all the partition changes
*/
protected final IRegion createRegion() {
if (regionStart == -1 || regionEnd == -1) {
return null;
}
return new Region(regionStart, regionEnd - regionStart);
}
}