2 * Copyright (c) 2002-2004 Widespace, OU and others.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the Common Public License v1.0
5 * which accompanies this distribution, and is available at
6 * http://solareclipse.sourceforge.net/legal/cpl-v10.html
9 * Igor Malinin - initial contribution
11 * $Id: AbstractPartitioner.java,v 1.3 2004-12-29 21:42:31 axelcl Exp $
14 package net.sourceforge.phpeclipse.ui.text.rules;
16 import java.util.ArrayList;
17 import java.util.List;
19 import org.eclipse.jface.text.Assert;
20 import org.eclipse.jface.text.DocumentEvent;
21 import org.eclipse.jface.text.IDocument;
22 import org.eclipse.jface.text.IDocumentPartitioner;
23 import org.eclipse.jface.text.IDocumentPartitionerExtension;
24 import org.eclipse.jface.text.IRegion;
25 import org.eclipse.jface.text.ITypedRegion;
26 import org.eclipse.jface.text.Region;
27 import org.eclipse.jface.text.TypedRegion;
28 import org.eclipse.jface.text.rules.IPartitionTokenScanner;
29 import org.eclipse.jface.text.rules.IToken;
32 * Advanced partitioner which maintains partitions as views to connected document. Views have own partitioners themselves. This
33 * class is designed as a base for complex partitioners such as for JSP, PHP, ASP, etc. languages.
35 * @author Igor Malinin
37 public abstract class AbstractPartitioner implements IDocumentPartitioner, IDocumentPartitionerExtension {
38 public final static boolean DEBUG = false;
40 /** Partition scanner */
41 protected IPartitionTokenScanner scanner;
43 /** Connected document */
44 protected IDocument document;
46 /** Flat structure of the document */
47 protected List nodes = new ArrayList();
49 /** The offset at which the first changed partition starts */
50 protected int regionStart;
52 /** The offset at which the last changed partition ends */
53 protected int regionEnd;
55 public AbstractPartitioner(IPartitionTokenScanner scanner) {
56 this.scanner = scanner;
59 protected FlatNode createNode(String type, int offset, int length) {
61 Assert.isTrue(offset >= 0, Integer.toString(offset));
63 FlatNode node = new FlatNode(type);
69 protected void addInnerRegion(FlatNode position) {
70 nodes.add(computeFlatNodeIndex(position.offset), position);
73 protected void removeInnerRegion(FlatNode position) {
74 nodes.remove(position); // TODO: Indexed remove?
77 protected void deleteInnerRegion(FlatNode position) {
78 nodes.remove(position); // TODO: Indexed remove?
81 protected void resizeInnerRegion(FlatNode position) {
85 * @see org.eclipse.jface.text.IDocumentPartitioner#connect(IDocument)
87 public void connect(IDocument document) {
88 this.document = document;
94 * @see org.eclipse.jface.text.IDocumentPartitioner#disconnect()
96 public void disconnect() {
102 * Performs the initial partitioning of the partitioner's document.
104 protected void initialize() {
105 scanner.setRange(document, 0, document.getLength());
109 IToken token = scanner.nextToken();
110 while (!token.isEOF()) {
111 String contentType = getTokenContentType(token);
113 if (isSupportedContentType(contentType)) {
114 addInnerRegion(createNode(contentType, scanner.getTokenOffset(), scanner.getTokenLength()));
117 token = scanner.nextToken();
122 * @see org.eclipse.jface.text.IDocumentPartitioner#documentAboutToBeChanged(DocumentEvent)
124 public void documentAboutToBeChanged(DocumentEvent event) {
125 regionStart = regionEnd = -1;
129 * @see org.eclipse.jface.text.IDocumentPartitioner#documentChanged(DocumentEvent)
131 public boolean documentChanged(DocumentEvent event) {
132 return (documentChanged2(event) != null);
136 * @see org.eclipse.jface.text.IDocumentPartitionerExtension#documentChanged2(DocumentEvent)
138 public IRegion documentChanged2(DocumentEvent event) {
139 int first = fixupPartitions(event);
141 FlatNode[] category = (FlatNode[]) nodes.toArray(new FlatNode[nodes.size()]);
143 // repartition changed region
145 String contentType = IDocument.DEFAULT_CONTENT_TYPE;
150 offset = 0; // Bug #697414: first offset
152 offset = event.getOffset();
154 FlatNode partition = category[first - 1];
155 if (partition.includes(offset)) {
156 offset = partition.offset;
157 contentType = partition.type;
159 } else if (offset == partition.offset + partition.length) {
160 offset = partition.offset;
161 contentType = partition.type;
164 offset = partition.offset + partition.length;
168 // should not be changed since last conversion
169 // category = (FlatNode[]) nodes.toArray(new FlatNode[nodes.size()]);
171 Assert.isTrue(offset >= 0, Integer.toString(offset));
173 scanner.setPartialRange(document, offset, document.getLength(), contentType, offset);
175 int lastScannedPosition = offset;
176 IToken token = scanner.nextToken();
177 while (!token.isEOF()) {
178 contentType = getTokenContentType(token);
180 if (!isSupportedContentType(contentType)) {
181 token = scanner.nextToken();
185 offset = scanner.getTokenOffset();
187 Assert.isTrue(offset >= 0, scanner.toString());
189 int length = scanner.getTokenLength();
191 lastScannedPosition = offset + length;
193 // remove all affected positions
194 while (first < category.length) {
195 FlatNode p = category[first];
196 if (p.offset + p.length < lastScannedPosition
197 || (p.overlapsWith(offset, length) && (!containsPosition(offset, length) || !contentType.equals(p.type)))) {
198 removeInnerRegion(p);
199 rememberRegion(p.offset, p.length);
206 // if position already exists we are done
207 if (containsPosition(offset, length)) {
208 if (lastScannedPosition > event.getOffset()) {
209 // TODO: optional repartition till end of doc
210 return createRegion();
215 // insert the new type position
216 addInnerRegion(createNode(contentType, offset, length));
217 rememberRegion(offset, length);
220 token = scanner.nextToken();
221 // } catch (ArrayIndexOutOfBoundsException e) {
222 // System.out.println(this.getClass().toString());
227 // remove all positions behind lastScannedPosition
228 // since there aren't any further types
230 // Do not need to recalculate (lost remove events)!
231 // first = computeIndexInInnerDocuments(lastScannedPosition);
232 while (first < category.length) {
233 FlatNode p = category[first++];
234 removeInnerRegion(p);
235 rememberRegion(p.offset, p.length);
238 return createRegion();
241 protected int fixupPartitions(DocumentEvent event) {
242 int offset = event.getOffset();
243 int length = event.getLength();
244 int end = offset + length;
246 // fixup flat nodes laying on change boundaries
248 int first = computeFlatNodeIndex(offset);
250 FlatNode p = (FlatNode) nodes.get(first - 1);
252 int right = p.offset + p.length;
253 if (offset < right) {
254 // change overlaps with partition
256 // cahnge completely inside partition
257 String text = event.getText();
260 p.length += text.length();
263 // cut partition at right
264 int cut = p.offset + p.length - offset;
270 int last = computeFlatNodeIndex(end);
272 FlatNode p = (FlatNode) nodes.get(last - 1);
274 int right = p.offset + p.length;
276 // cut partition at left
277 int cut = end - p.offset;
281 String text = event.getText();
283 p.offset += text.length();
290 // fixup flat nodes laying afrer change
292 String text = event.getText();
294 length -= text.length();
297 for (int i = last, size = nodes.size(); i < size; i++) {
298 ((FlatNode) nodes.get(i)).offset -= length;
301 // delete flat nodes laying completely inside change boundaries
305 deleteInnerRegion((FlatNode) nodes.get(--last));
306 } while (first < last);
308 rememberRegion(offset, 0);
315 * Returns whether the given type is one of the legal content types.
318 * the content type to check
319 * @return <code>true</code> if the content type is a legal content type
321 protected boolean isSupportedContentType(String contentType) {
322 /* TODO: implementation */
323 // if (contentType != null) {
324 // for (int i= 0; i < fLegalContentTypes.length; i++) {
325 // if (fLegalContentTypes[i].equals(contentType)) {
331 return (contentType != null);
335 * 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
336 * that it is the encoded content type.
339 * the token whose content type is to be determined
340 * @return the token's content type
342 protected String getTokenContentType(IToken token) {
343 Object data = token.getData();
344 if (data instanceof String) {
345 return (String) data;
352 * @see org.eclipse.jface.text.IDocumentPartitioner#getLegalContentTypes()
354 public String[] getLegalContentTypes() {
355 // TODO: implementation
360 * @see org.eclipse.jface.text.IDocumentPartitioner#getContentType(int)
362 public String getContentType(int offset) {
363 return getPartition(offset).getType();
367 * @see org.eclipse.jface.text.IDocumentPartitioner#getPartition(int)
369 public ITypedRegion getPartition(int offset) {
370 if (nodes.size() == 0) {
371 return new TypedRegion(0, document.getLength(), IDocument.DEFAULT_CONTENT_TYPE);
374 int index = computeFlatNodeIndex(offset);
375 if (index < nodes.size()) {
376 FlatNode next = (FlatNode) nodes.get(index);
378 if (offset == next.offset) {
379 return new TypedRegion(next.offset, next.length, next.type);
383 return new TypedRegion(0, next.offset, IDocument.DEFAULT_CONTENT_TYPE);
386 FlatNode prev = (FlatNode) nodes.get(index - 1);
388 if (prev.includes(offset)) {
389 return new TypedRegion(prev.offset, prev.length, prev.type);
392 int end = prev.offset + prev.length;
393 return new TypedRegion(end, next.offset - end, IDocument.DEFAULT_CONTENT_TYPE);
396 FlatNode prev = (FlatNode) nodes.get(nodes.size() - 1);
398 if (prev.includes(offset)) {
399 return new TypedRegion(prev.offset, prev.length, prev.type);
402 int end = prev.offset + prev.length;
404 return new TypedRegion(end, document.getLength() - end, IDocument.DEFAULT_CONTENT_TYPE);
408 * @see org.eclipse.jface.text.IDocumentPartitioner#computePartitioning(int, int)
410 public ITypedRegion[] computePartitioning(int offset, int length) {
411 List list = new ArrayList();
413 int end = offset + length;
415 int index = computeFlatNodeIndex(offset);
417 FlatNode prev = (index > 0) ? (FlatNode) nodes.get(index - 1) : null;
420 if (prev.overlapsWith(offset, length)) {
421 list.add(new TypedRegion(prev.offset, prev.length, prev.type));
424 if (end <= prev.offset + prev.length) {
429 FlatNode next = (index < nodes.size()) ? (FlatNode) nodes.get(index) : null;
431 if (next == null || offset < next.offset) {
433 int off1 = offset + length;
435 if (prev != null && off0 < prev.offset + prev.length) {
436 off0 = prev.offset + prev.length;
439 if (next != null && next.offset < off1) {
444 list.add(new TypedRegion(off0, off1 - off0, IDocument.DEFAULT_CONTENT_TYPE));
455 return (TypedRegion[]) list.toArray(new TypedRegion[list.size()]);
459 * 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
460 * supposed to become the first in this list of all flat nodes with the same offset.
463 * the offset for which the index is computed
464 * @return the computed index
466 protected int computeFlatNodeIndex(int offset) {
467 if (nodes.size() == 0) {
471 int left = 0, mid = 0;
472 int right = nodes.size() - 1;
476 while (left < right) {
477 mid = (left + right) / 2;
479 p = (FlatNode) nodes.get(mid);
481 if (offset < p.offset) {
482 right = (left == mid) ? left : mid - 1;
483 } else if (offset > p.offset) {
484 left = (right == mid) ? right : mid + 1;
485 } else if (offset == p.offset) {
491 p = (FlatNode) nodes.get(pos);
492 if (offset > p.offset) {
496 // entry will became the first of all entries with the same offset
502 p = (FlatNode) nodes.get(pos);
503 } while (offset == p.offset);
510 public boolean containsPosition(int offset, int length) {
511 int size = nodes.size();
516 int index = computeFlatNodeIndex(offset);
518 FlatNode p = (FlatNode) nodes.get(index);
520 while (p.offset == offset) {
521 if (p.length == length) {
525 if (++index < size) {
526 p = (FlatNode) nodes.get(index);
537 * Helper method for tracking the minimal region containg all partition changes. If <code>offset</code> is smaller than the
538 * remembered offset, <code>offset</code> will from now on be remembered. If <code>offset + length</code> is greater than the
539 * remembered end offset, it will be remembered from now on.
546 protected final void rememberRegion(int offset, int length) {
547 // remember start offset
548 if (regionStart == -1) {
549 regionStart = offset;
550 } else if (offset < regionStart) {
551 regionStart = offset;
554 // remember end offset
555 int endOffset = offset + length;
557 if (regionEnd == -1) {
558 regionEnd = endOffset;
559 } else if (endOffset > regionEnd) {
560 regionEnd = endOffset;
565 * Creates the minimal region containing all partition changes using the remembered offsets.
567 * @return the minimal region containing all the partition changes
569 protected final IRegion createRegion() {
570 if (regionStart == -1 || regionEnd == -1) {
574 return new Region(regionStart, regionEnd - regionStart);