--- /dev/null
+/*
+ * 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.1 2004-09-02 18:26:29 jsurfer Exp $
+ */
+
+package net.sourceforge.phpeclipse.ui.text.rules;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.jface.text.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 = true;
+
+ /** 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());
+
+ 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 <code>true</code> 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 <code>null</code> 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 <code>offset</code> is smaller than the
+ * remembered offset, <code>offset</code> will from now on be remembered. If <code>offset + length</code> 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);
+ }
+}
\ No newline at end of file