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.4 2006-10-21 23:13:53 pombredanne 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
33 * document. Views have own partitioners themselves. This class is designed as a
34 * base for complex partitioners such as for JSP, PHP, ASP, etc. languages.
36 * @author Igor Malinin
38 public abstract class AbstractPartitioner implements IDocumentPartitioner,
39 IDocumentPartitionerExtension {
40 public final static boolean DEBUG = false;
42 /** Partition scanner */
43 protected IPartitionTokenScanner scanner;
45 /** Connected document */
46 protected IDocument document;
48 /** Flat structure of the document */
49 protected List nodes = new ArrayList();
51 /** The offset at which the first changed partition starts */
52 protected int regionStart;
54 /** The offset at which the last changed partition ends */
55 protected int regionEnd;
57 public AbstractPartitioner(IPartitionTokenScanner scanner) {
58 this.scanner = scanner;
61 protected FlatNode createNode(String type, int offset, int length) {
63 Assert.isTrue(offset >= 0, Integer.toString(offset));
65 FlatNode node = new FlatNode(type);
71 protected void addInnerRegion(FlatNode position) {
72 nodes.add(computeFlatNodeIndex(position.offset), position);
75 protected void removeInnerRegion(FlatNode position) {
76 nodes.remove(position); // TODO: Indexed remove?
79 protected void deleteInnerRegion(FlatNode position) {
80 nodes.remove(position); // TODO: Indexed remove?
83 protected void resizeInnerRegion(FlatNode position) {
87 * @see org.eclipse.jface.text.IDocumentPartitioner#connect(IDocument)
89 public void connect(IDocument document) {
90 this.document = document;
96 * @see org.eclipse.jface.text.IDocumentPartitioner#disconnect()
98 public void disconnect() {
104 * Performs the initial partitioning of the partitioner's document.
106 protected void initialize() {
107 scanner.setRange(document, 0, document.getLength());
111 IToken token = scanner.nextToken();
112 while (!token.isEOF()) {
113 String contentType = getTokenContentType(token);
115 if (isSupportedContentType(contentType)) {
116 addInnerRegion(createNode(contentType,
117 scanner.getTokenOffset(), scanner.getTokenLength()));
120 token = scanner.nextToken();
125 * @see org.eclipse.jface.text.IDocumentPartitioner#documentAboutToBeChanged(DocumentEvent)
127 public void documentAboutToBeChanged(DocumentEvent event) {
128 regionStart = regionEnd = -1;
132 * @see org.eclipse.jface.text.IDocumentPartitioner#documentChanged(DocumentEvent)
134 public boolean documentChanged(DocumentEvent event) {
135 return (documentChanged2(event) != null);
139 * @see org.eclipse.jface.text.IDocumentPartitionerExtension#documentChanged2(DocumentEvent)
141 public IRegion documentChanged2(DocumentEvent event) {
142 int first = fixupPartitions(event);
144 FlatNode[] category = (FlatNode[]) nodes.toArray(new FlatNode[nodes
147 // repartition changed region
149 String contentType = IDocument.DEFAULT_CONTENT_TYPE;
154 offset = 0; // Bug #697414: first offset
156 offset = event.getOffset();
158 FlatNode partition = category[first - 1];
159 if (partition.includes(offset)) {
160 offset = partition.offset;
161 contentType = partition.type;
163 } else if (offset == partition.offset + partition.length) {
164 offset = partition.offset;
165 contentType = partition.type;
168 offset = partition.offset + partition.length;
172 // should not be changed since last conversion
173 // category = (FlatNode[]) nodes.toArray(new FlatNode[nodes.size()]);
175 Assert.isTrue(offset >= 0, Integer.toString(offset));
177 scanner.setPartialRange(document, offset, document.getLength(),
178 contentType, offset);
180 int lastScannedPosition = offset;
181 IToken token = scanner.nextToken();
182 while (!token.isEOF()) {
183 contentType = getTokenContentType(token);
185 if (!isSupportedContentType(contentType)) {
186 token = scanner.nextToken();
190 offset = scanner.getTokenOffset();
192 Assert.isTrue(offset >= 0, scanner.toString());
194 int length = scanner.getTokenLength();
196 lastScannedPosition = offset + length;
198 // remove all affected positions
199 while (first < category.length) {
200 FlatNode p = category[first];
201 if (p.offset + p.length < lastScannedPosition
202 || (p.overlapsWith(offset, length) && (!containsPosition(
203 offset, length) || !contentType.equals(p.type)))) {
204 removeInnerRegion(p);
205 rememberRegion(p.offset, p.length);
212 // if position already exists we are done
213 if (containsPosition(offset, length)) {
214 if (lastScannedPosition > event.getOffset()) {
215 // TODO: optional repartition till end of doc
216 return createRegion();
221 // insert the new type position
222 addInnerRegion(createNode(contentType, offset, length));
223 rememberRegion(offset, length);
226 token = scanner.nextToken();
227 // } catch (ArrayIndexOutOfBoundsException e) {
228 // System.out.println(this.getClass().toString());
233 // remove all positions behind lastScannedPosition
234 // since there aren't any further types
236 // Do not need to recalculate (lost remove events)!
237 // first = computeIndexInInnerDocuments(lastScannedPosition);
238 while (first < category.length) {
239 FlatNode p = category[first++];
240 removeInnerRegion(p);
241 rememberRegion(p.offset, p.length);
244 return createRegion();
247 protected int fixupPartitions(DocumentEvent event) {
248 int offset = event.getOffset();
249 int length = event.getLength();
250 int end = offset + length;
252 // fixup flat nodes laying on change boundaries
254 int first = computeFlatNodeIndex(offset);
256 FlatNode p = (FlatNode) nodes.get(first - 1);
258 int right = p.offset + p.length;
259 if (offset < right) {
260 // change overlaps with partition
262 // cahnge completely inside partition
263 String text = event.getText();
266 p.length += text.length();
269 // cut partition at right
270 int cut = p.offset + p.length - offset;
276 int last = computeFlatNodeIndex(end);
278 FlatNode p = (FlatNode) nodes.get(last - 1);
280 int right = p.offset + p.length;
282 // cut partition at left
283 int cut = end - p.offset;
287 String text = event.getText();
289 p.offset += text.length();
296 // fixup flat nodes laying afrer change
298 String text = event.getText();
300 length -= text.length();
303 for (int i = last, size = nodes.size(); i < size; i++) {
304 ((FlatNode) nodes.get(i)).offset -= length;
307 // delete flat nodes laying completely inside change boundaries
311 deleteInnerRegion((FlatNode) nodes.get(--last));
312 } while (first < last);
314 rememberRegion(offset, 0);
321 * Returns whether the given type is one of the legal content types.
324 * the content type to check
325 * @return <code>true</code> if the content type is a legal content type
327 protected boolean isSupportedContentType(String contentType) {
328 /* TODO: implementation */
329 // if (contentType != null) {
330 // for (int i= 0; i < fLegalContentTypes.length; i++) {
331 // if (fLegalContentTypes[i].equals(contentType)) {
337 return (contentType != null);
341 * Returns a content type encoded in the given token. If the token's data is
342 * not <code>null</code> and a string it is assumed that it is the encoded
346 * the token whose content type is to be determined
347 * @return the token's content type
349 protected String getTokenContentType(IToken token) {
350 Object data = token.getData();
351 if (data instanceof String) {
352 return (String) data;
359 * @see org.eclipse.jface.text.IDocumentPartitioner#getLegalContentTypes()
361 public String[] getLegalContentTypes() {
362 // TODO: implementation
367 * @see org.eclipse.jface.text.IDocumentPartitioner#getContentType(int)
369 public String getContentType(int offset) {
370 return getPartition(offset).getType();
374 * @see org.eclipse.jface.text.IDocumentPartitioner#getPartition(int)
376 public ITypedRegion getPartition(int offset) {
377 if (nodes.size() == 0) {
378 return new TypedRegion(0, document.getLength(),
379 IDocument.DEFAULT_CONTENT_TYPE);
382 int index = computeFlatNodeIndex(offset);
383 if (index < nodes.size()) {
384 FlatNode next = (FlatNode) nodes.get(index);
386 if (offset == next.offset) {
387 return new TypedRegion(next.offset, next.length, next.type);
391 return new TypedRegion(0, next.offset,
392 IDocument.DEFAULT_CONTENT_TYPE);
395 FlatNode prev = (FlatNode) nodes.get(index - 1);
397 if (prev.includes(offset)) {
398 return new TypedRegion(prev.offset, prev.length, prev.type);
401 int end = prev.offset + prev.length;
402 return new TypedRegion(end, next.offset - end,
403 IDocument.DEFAULT_CONTENT_TYPE);
406 FlatNode prev = (FlatNode) nodes.get(nodes.size() - 1);
408 if (prev.includes(offset)) {
409 return new TypedRegion(prev.offset, prev.length, prev.type);
412 int end = prev.offset + prev.length;
414 return new TypedRegion(end, document.getLength() - end,
415 IDocument.DEFAULT_CONTENT_TYPE);
419 * @see org.eclipse.jface.text.IDocumentPartitioner#computePartitioning(int,
422 public ITypedRegion[] computePartitioning(int offset, int length) {
423 List list = new ArrayList();
425 int end = offset + length;
427 int index = computeFlatNodeIndex(offset);
429 FlatNode prev = (index > 0) ? (FlatNode) nodes.get(index - 1)
433 if (prev.overlapsWith(offset, length)) {
434 list.add(new TypedRegion(prev.offset, prev.length,
438 if (end <= prev.offset + prev.length) {
443 FlatNode next = (index < nodes.size()) ? (FlatNode) nodes
446 if (next == null || offset < next.offset) {
448 int off1 = offset + length;
450 if (prev != null && off0 < prev.offset + prev.length) {
451 off0 = prev.offset + prev.length;
454 if (next != null && next.offset < off1) {
459 list.add(new TypedRegion(off0, off1 - off0,
460 IDocument.DEFAULT_CONTENT_TYPE));
471 return (TypedRegion[]) list.toArray(new TypedRegion[list.size()]);
475 * Computes the index in the list of flat nodes at which an flat node with
476 * the given offset would be inserted. The flat node is supposed to become
477 * the first in this list of all flat nodes with the same offset.
480 * the offset for which the index is computed
481 * @return the computed index
483 protected int computeFlatNodeIndex(int offset) {
484 if (nodes.size() == 0) {
488 int left = 0, mid = 0;
489 int right = nodes.size() - 1;
493 while (left < right) {
494 mid = (left + right) / 2;
496 p = (FlatNode) nodes.get(mid);
498 if (offset < p.offset) {
499 right = (left == mid) ? left : mid - 1;
500 } else if (offset > p.offset) {
501 left = (right == mid) ? right : mid + 1;
502 } else if (offset == p.offset) {
508 p = (FlatNode) nodes.get(pos);
509 if (offset > p.offset) {
513 // entry will became the first of all entries with the same offset
519 p = (FlatNode) nodes.get(pos);
520 } while (offset == p.offset);
527 public boolean containsPosition(int offset, int length) {
528 int size = nodes.size();
533 int index = computeFlatNodeIndex(offset);
535 FlatNode p = (FlatNode) nodes.get(index);
537 while (p.offset == offset) {
538 if (p.length == length) {
542 if (++index < size) {
543 p = (FlatNode) nodes.get(index);
554 * Helper method for tracking the minimal region containg all partition
555 * changes. If <code>offset</code> is smaller than the remembered offset,
556 * <code>offset</code> will from now on be remembered. If
557 * <code>offset + length</code> is greater than the remembered end offset,
558 * it will be remembered from now on.
565 protected final void rememberRegion(int offset, int length) {
566 // remember start offset
567 if (regionStart == -1) {
568 regionStart = offset;
569 } else if (offset < regionStart) {
570 regionStart = offset;
573 // remember end offset
574 int endOffset = offset + length;
576 if (regionEnd == -1) {
577 regionEnd = endOffset;
578 } else if (endOffset > regionEnd) {
579 regionEnd = endOffset;
584 * Creates the minimal region containing all partition changes using the
585 * remembered offsets.
587 * @return the minimal region containing all the partition changes
589 protected final IRegion createRegion() {
590 if (regionStart == -1 || regionEnd == -1) {
594 return new Region(regionStart, regionEnd - regionStart);