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.1 2004-09-02 18:26:29 jsurfer 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 = true;
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());
107 IToken token = scanner.nextToken();
108 while (!token.isEOF()) {
109 String contentType = getTokenContentType(token);
111 if (isSupportedContentType(contentType)) {
112 addInnerRegion(createNode(contentType, scanner.getTokenOffset(), scanner.getTokenLength()));
115 token = scanner.nextToken();
120 * @see org.eclipse.jface.text.IDocumentPartitioner#documentAboutToBeChanged(DocumentEvent)
122 public void documentAboutToBeChanged(DocumentEvent event) {
123 regionStart = regionEnd = -1;
127 * @see org.eclipse.jface.text.IDocumentPartitioner#documentChanged(DocumentEvent)
129 public boolean documentChanged(DocumentEvent event) {
130 return (documentChanged2(event) != null);
134 * @see org.eclipse.jface.text.IDocumentPartitionerExtension#documentChanged2(DocumentEvent)
136 public IRegion documentChanged2(DocumentEvent event) {
137 int first = fixupPartitions(event);
139 FlatNode[] category = (FlatNode[]) nodes.toArray(new FlatNode[nodes.size()]);
141 // repartition changed region
143 String contentType = IDocument.DEFAULT_CONTENT_TYPE;
148 offset = 0; // Bug #697414: first offset
150 offset = event.getOffset();
152 FlatNode partition = category[first - 1];
153 if (partition.includes(offset)) {
154 offset = partition.offset;
155 contentType = partition.type;
157 } else if (offset == partition.offset + partition.length) {
158 offset = partition.offset;
159 contentType = partition.type;
162 offset = partition.offset + partition.length;
166 // should not be changed since last conversion
167 // category = (FlatNode[]) nodes.toArray(new FlatNode[nodes.size()]);
169 Assert.isTrue(offset >= 0, Integer.toString(offset));
171 scanner.setPartialRange(document, offset, document.getLength(), contentType, offset);
173 int lastScannedPosition = offset;
174 IToken token = scanner.nextToken();
175 while (!token.isEOF()) {
176 contentType = getTokenContentType(token);
178 if (!isSupportedContentType(contentType)) {
179 token = scanner.nextToken();
183 offset = scanner.getTokenOffset();
185 Assert.isTrue(offset >= 0, scanner.toString());
187 int length = scanner.getTokenLength();
189 lastScannedPosition = offset + length;
191 // remove all affected positions
192 while (first < category.length) {
193 FlatNode p = category[first];
194 if (p.offset + p.length < lastScannedPosition
195 || (p.overlapsWith(offset, length) && (!containsPosition(offset, length) || !contentType.equals(p.type)))) {
196 removeInnerRegion(p);
197 rememberRegion(p.offset, p.length);
204 // if position already exists we are done
205 if (containsPosition(offset, length)) {
206 if (lastScannedPosition > event.getOffset()) {
207 // TODO: optional repartition till end of doc
208 return createRegion();
213 // insert the new type position
214 addInnerRegion(createNode(contentType, offset, length));
215 rememberRegion(offset, length);
218 token = scanner.nextToken();
219 // } catch (ArrayIndexOutOfBoundsException e) {
220 // System.out.println(this.getClass().toString());
225 // remove all positions behind lastScannedPosition
226 // since there aren't any further types
228 // Do not need to recalculate (lost remove events)!
229 // first = computeIndexInInnerDocuments(lastScannedPosition);
230 while (first < category.length) {
231 FlatNode p = category[first++];
232 removeInnerRegion(p);
233 rememberRegion(p.offset, p.length);
236 return createRegion();
239 protected int fixupPartitions(DocumentEvent event) {
240 int offset = event.getOffset();
241 int length = event.getLength();
242 int end = offset + length;
244 // fixup flat nodes laying on change boundaries
246 int first = computeFlatNodeIndex(offset);
248 FlatNode p = (FlatNode) nodes.get(first - 1);
250 int right = p.offset + p.length;
251 if (offset < right) {
252 // change overlaps with partition
254 // cahnge completely inside partition
255 String text = event.getText();
258 p.length += text.length();
261 // cut partition at right
262 int cut = p.offset + p.length - offset;
268 int last = computeFlatNodeIndex(end);
270 FlatNode p = (FlatNode) nodes.get(last - 1);
272 int right = p.offset + p.length;
274 // cut partition at left
275 int cut = end - p.offset;
279 String text = event.getText();
281 p.offset += text.length();
288 // fixup flat nodes laying afrer change
290 String text = event.getText();
292 length -= text.length();
295 for (int i = last, size = nodes.size(); i < size; i++) {
296 ((FlatNode) nodes.get(i)).offset -= length;
299 // delete flat nodes laying completely inside change boundaries
303 deleteInnerRegion((FlatNode) nodes.get(--last));
304 } while (first < last);
306 rememberRegion(offset, 0);
313 * Returns whether the given type is one of the legal content types.
316 * the content type to check
317 * @return <code>true</code> if the content type is a legal content type
319 protected boolean isSupportedContentType(String contentType) {
320 /* TODO: implementation */
321 // if (contentType != null) {
322 // for (int i= 0; i < fLegalContentTypes.length; i++) {
323 // if (fLegalContentTypes[i].equals(contentType)) {
329 return (contentType != null);
333 * 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
334 * that it is the encoded content type.
337 * the token whose content type is to be determined
338 * @return the token's content type
340 protected String getTokenContentType(IToken token) {
341 Object data = token.getData();
342 if (data instanceof String) {
343 return (String) data;
350 * @see org.eclipse.jface.text.IDocumentPartitioner#getLegalContentTypes()
352 public String[] getLegalContentTypes() {
353 // TODO: implementation
358 * @see org.eclipse.jface.text.IDocumentPartitioner#getContentType(int)
360 public String getContentType(int offset) {
361 return getPartition(offset).getType();
365 * @see org.eclipse.jface.text.IDocumentPartitioner#getPartition(int)
367 public ITypedRegion getPartition(int offset) {
368 if (nodes.size() == 0) {
369 return new TypedRegion(0, document.getLength(), IDocument.DEFAULT_CONTENT_TYPE);
372 int index = computeFlatNodeIndex(offset);
373 if (index < nodes.size()) {
374 FlatNode next = (FlatNode) nodes.get(index);
376 if (offset == next.offset) {
377 return new TypedRegion(next.offset, next.length, next.type);
381 return new TypedRegion(0, next.offset, IDocument.DEFAULT_CONTENT_TYPE);
384 FlatNode prev = (FlatNode) nodes.get(index - 1);
386 if (prev.includes(offset)) {
387 return new TypedRegion(prev.offset, prev.length, prev.type);
390 int end = prev.offset + prev.length;
391 return new TypedRegion(end, next.offset - end, IDocument.DEFAULT_CONTENT_TYPE);
394 FlatNode prev = (FlatNode) nodes.get(nodes.size() - 1);
396 if (prev.includes(offset)) {
397 return new TypedRegion(prev.offset, prev.length, prev.type);
400 int end = prev.offset + prev.length;
402 return new TypedRegion(end, document.getLength() - end, IDocument.DEFAULT_CONTENT_TYPE);
406 * @see org.eclipse.jface.text.IDocumentPartitioner#computePartitioning(int, int)
408 public ITypedRegion[] computePartitioning(int offset, int length) {
409 List list = new ArrayList();
411 int end = offset + length;
413 int index = computeFlatNodeIndex(offset);
415 FlatNode prev = (index > 0) ? (FlatNode) nodes.get(index - 1) : null;
418 if (prev.overlapsWith(offset, length)) {
419 list.add(new TypedRegion(prev.offset, prev.length, prev.type));
422 if (end <= prev.offset + prev.length) {
427 FlatNode next = (index < nodes.size()) ? (FlatNode) nodes.get(index) : null;
429 if (next == null || offset < next.offset) {
431 int off1 = offset + length;
433 if (prev != null && off0 < prev.offset + prev.length) {
434 off0 = prev.offset + prev.length;
437 if (next != null && next.offset < off1) {
442 list.add(new TypedRegion(off0, off1 - off0, IDocument.DEFAULT_CONTENT_TYPE));
453 return (TypedRegion[]) list.toArray(new TypedRegion[list.size()]);
457 * 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
458 * supposed to become the first in this list of all flat nodes with the same offset.
461 * the offset for which the index is computed
462 * @return the computed index
464 protected int computeFlatNodeIndex(int offset) {
465 if (nodes.size() == 0) {
469 int left = 0, mid = 0;
470 int right = nodes.size() - 1;
474 while (left < right) {
475 mid = (left + right) / 2;
477 p = (FlatNode) nodes.get(mid);
479 if (offset < p.offset) {
480 right = (left == mid) ? left : mid - 1;
481 } else if (offset > p.offset) {
482 left = (right == mid) ? right : mid + 1;
483 } else if (offset == p.offset) {
489 p = (FlatNode) nodes.get(pos);
490 if (offset > p.offset) {
494 // entry will became the first of all entries with the same offset
500 p = (FlatNode) nodes.get(pos);
501 } while (offset == p.offset);
508 public boolean containsPosition(int offset, int length) {
509 int size = nodes.size();
514 int index = computeFlatNodeIndex(offset);
516 FlatNode p = (FlatNode) nodes.get(index);
518 while (p.offset == offset) {
519 if (p.length == length) {
523 if (++index < size) {
524 p = (FlatNode) nodes.get(index);
535 * Helper method for tracking the minimal region containg all partition changes. If <code>offset</code> is smaller than the
536 * remembered offset, <code>offset</code> will from now on be remembered. If <code>offset + length</code> is greater than the
537 * remembered end offset, it will be remembered from now on.
544 protected final void rememberRegion(int offset, int length) {
545 // remember start offset
546 if (regionStart == -1) {
547 regionStart = offset;
548 } else if (offset < regionStart) {
549 regionStart = offset;
552 // remember end offset
553 int endOffset = offset + length;
555 if (regionEnd == -1) {
556 regionEnd = endOffset;
557 } else if (endOffset > regionEnd) {
558 regionEnd = endOffset;
563 * Creates the minimal region containing all partition changes using the remembered offsets.
565 * @return the minimal region containing all the partition changes
567 protected final IRegion createRegion() {
568 if (regionStart == -1 || regionEnd == -1) {
572 return new Region(regionStart, regionEnd - regionStart);