001: /*******************************************************************************
002: * Copyright (c) 2000, 2006 IBM Corporation and others.
003: * All rights reserved. This program and the accompanying materials
004: * are made available under the terms of the Eclipse Public License v1.0
005: * which accompanies this distribution, and is available at
006: * http://www.eclipse.org/legal/epl-v10.html
007: *
008: * Contributors:
009: * IBM Corporation - initial API and implementation
010: *******************************************************************************/package org.eclipse.jface.text.rules;
011:
012: import java.util.ArrayList;
013: import java.util.List;
014:
015: import org.eclipse.core.runtime.Assert;
016: import org.eclipse.core.runtime.Platform;
017:
018: import org.eclipse.jface.text.BadLocationException;
019: import org.eclipse.jface.text.BadPositionCategoryException;
020: import org.eclipse.jface.text.DefaultPositionUpdater;
021: import org.eclipse.jface.text.DocumentEvent;
022: import org.eclipse.jface.text.DocumentRewriteSession;
023: import org.eclipse.jface.text.IDocument;
024: import org.eclipse.jface.text.IDocumentPartitioner;
025: import org.eclipse.jface.text.IDocumentPartitionerExtension;
026: import org.eclipse.jface.text.IDocumentPartitionerExtension2;
027: import org.eclipse.jface.text.IDocumentPartitionerExtension3;
028: import org.eclipse.jface.text.IRegion;
029: import org.eclipse.jface.text.ITypedRegion;
030: import org.eclipse.jface.text.Position;
031: import org.eclipse.jface.text.Region;
032: import org.eclipse.jface.text.TextUtilities;
033: import org.eclipse.jface.text.TypedPosition;
034: import org.eclipse.jface.text.TypedRegion;
035:
036: /**
037: * A standard implementation of a document partitioner. It uses an
038: * {@link IPartitionTokenScanner} to scan the document and to determine the
039: * document's partitioning. The tokens returned by the scanner must return the
040: * partition type as their data. The partitioner remembers the document's
041: * partitions in the document itself rather than maintaining its own data
042: * structure.
043: * <p>
044: * To reduce array creations in {@link IDocument#getPositions(String)}, the
045: * positions get cached. The cache is cleared after updating the positions in
046: * {@link #documentChanged2(DocumentEvent)}. Subclasses need to call
047: * {@link #clearPositionCache()} after modifying the partitioner's positions.
048: * The cached positions may be accessed through {@link #getPositions()}.
049: * </p>
050: *
051: * @see IPartitionTokenScanner
052: * @since 3.1
053: */
054: public class FastPartitioner implements IDocumentPartitioner,
055: IDocumentPartitionerExtension, IDocumentPartitionerExtension2,
056: IDocumentPartitionerExtension3 {
057:
058: /**
059: * The position category this partitioner uses to store the document's partitioning information.
060: */
061: private static final String CONTENT_TYPES_CATEGORY = "__content_types_category"; //$NON-NLS-1$
062: /** The partitioner's scanner */
063: protected final IPartitionTokenScanner fScanner;
064: /** The legal content types of this partitioner */
065: protected final String[] fLegalContentTypes;
066: /** The partitioner's document */
067: protected IDocument fDocument;
068: /** The document length before a document change occurred */
069: protected int fPreviousDocumentLength;
070: /** The position updater used to for the default updating of partitions */
071: protected final DefaultPositionUpdater fPositionUpdater;
072: /** The offset at which the first changed partition starts */
073: protected int fStartOffset;
074: /** The offset at which the last changed partition ends */
075: protected int fEndOffset;
076: /**The offset at which a partition has been deleted */
077: protected int fDeleteOffset;
078: /**
079: * The position category this partitioner uses to store the document's partitioning information.
080: */
081: private final String fPositionCategory;
082: /**
083: * The active document rewrite session.
084: */
085: private DocumentRewriteSession fActiveRewriteSession;
086: /**
087: * Flag indicating whether this partitioner has been initialized.
088: */
089: private boolean fIsInitialized = false;
090: /**
091: * The cached positions from our document, so we don't create a new array every time
092: * someone requests partition information.
093: */
094: private Position[] fCachedPositions = null;
095: /** Debug option for cache consistency checking. */
096: private static final boolean CHECK_CACHE_CONSISTENCY = "true".equalsIgnoreCase(Platform.getDebugOption("org.eclipse.jface.text/debug/FastPartitioner/PositionCache")); //$NON-NLS-1$//$NON-NLS-2$;
097:
098: /**
099: * Creates a new partitioner that uses the given scanner and may return
100: * partitions of the given legal content types.
101: *
102: * @param scanner the scanner this partitioner is supposed to use
103: * @param legalContentTypes the legal content types of this partitioner
104: */
105: public FastPartitioner(IPartitionTokenScanner scanner,
106: String[] legalContentTypes) {
107: fScanner = scanner;
108: fLegalContentTypes = TextUtilities.copy(legalContentTypes);
109: fPositionCategory = CONTENT_TYPES_CATEGORY + hashCode();
110: fPositionUpdater = new DefaultPositionUpdater(fPositionCategory);
111: }
112:
113: /*
114: * @see org.eclipse.jface.text.IDocumentPartitionerExtension2#getManagingPositionCategories()
115: */
116: public String[] getManagingPositionCategories() {
117: return new String[] { fPositionCategory };
118: }
119:
120: /*
121: * @see org.eclipse.jface.text.IDocumentPartitioner#connect(org.eclipse.jface.text.IDocument)
122: */
123: public final void connect(IDocument document) {
124: connect(document, false);
125: }
126:
127: /**
128: * {@inheritDoc}
129: * <p>
130: * May be extended by subclasses.
131: * </p>
132: */
133: public void connect(IDocument document, boolean delayInitialization) {
134: Assert.isNotNull(document);
135: Assert.isTrue(!document
136: .containsPositionCategory(fPositionCategory));
137:
138: fDocument = document;
139: fDocument.addPositionCategory(fPositionCategory);
140:
141: fIsInitialized = false;
142: if (!delayInitialization)
143: checkInitialization();
144: }
145:
146: /**
147: * Calls {@link #initialize()} if the receiver is not yet initialized.
148: */
149: protected final void checkInitialization() {
150: if (!fIsInitialized)
151: initialize();
152: }
153:
154: /**
155: * Performs the initial partitioning of the partitioner's document.
156: * <p>
157: * May be extended by subclasses.
158: * </p>
159: */
160: protected void initialize() {
161: fIsInitialized = true;
162: clearPositionCache();
163: fScanner.setRange(fDocument, 0, fDocument.getLength());
164:
165: try {
166: IToken token = fScanner.nextToken();
167: while (!token.isEOF()) {
168:
169: String contentType = getTokenContentType(token);
170:
171: if (isSupportedContentType(contentType)) {
172: TypedPosition p = new TypedPosition(fScanner
173: .getTokenOffset(), fScanner
174: .getTokenLength(), contentType);
175: fDocument.addPosition(fPositionCategory, p);
176: }
177:
178: token = fScanner.nextToken();
179: }
180: } catch (BadLocationException x) {
181: // cannot happen as offsets come from scanner
182: } catch (BadPositionCategoryException x) {
183: // cannot happen if document has been connected before
184: }
185: }
186:
187: /**
188: * {@inheritDoc}
189: * <p>
190: * May be extended by subclasses.
191: * </p>
192: */
193: public void disconnect() {
194:
195: Assert.isTrue(fDocument
196: .containsPositionCategory(fPositionCategory));
197:
198: try {
199: fDocument.removePositionCategory(fPositionCategory);
200: } catch (BadPositionCategoryException x) {
201: // can not happen because of Assert
202: }
203: }
204:
205: /**
206: * {@inheritDoc}
207: * <p>
208: * May be extended by subclasses.
209: * </p>
210: */
211: public void documentAboutToBeChanged(DocumentEvent e) {
212: if (fIsInitialized) {
213:
214: Assert.isTrue(e.getDocument() == fDocument);
215:
216: fPreviousDocumentLength = e.getDocument().getLength();
217: fStartOffset = -1;
218: fEndOffset = -1;
219: fDeleteOffset = -1;
220: }
221: }
222:
223: /*
224: * @see IDocumentPartitioner#documentChanged(DocumentEvent)
225: */
226: public final boolean documentChanged(DocumentEvent e) {
227: if (fIsInitialized) {
228: IRegion region = documentChanged2(e);
229: return (region != null);
230: }
231: return false;
232: }
233:
234: /**
235: * Helper method for tracking the minimal region containing all partition changes.
236: * If <code>offset</code> is smaller than the remembered offset, <code>offset</code>
237: * will from now on be remembered. If <code>offset + length</code> is greater than
238: * the remembered end offset, it will be remembered from now on.
239: *
240: * @param offset the offset
241: * @param length the length
242: */
243: private void rememberRegion(int offset, int length) {
244: // remember start offset
245: if (fStartOffset == -1)
246: fStartOffset = offset;
247: else if (offset < fStartOffset)
248: fStartOffset = offset;
249:
250: // remember end offset
251: int endOffset = offset + length;
252: if (fEndOffset == -1)
253: fEndOffset = endOffset;
254: else if (endOffset > fEndOffset)
255: fEndOffset = endOffset;
256: }
257:
258: /**
259: * Remembers the given offset as the deletion offset.
260: *
261: * @param offset the offset
262: */
263: private void rememberDeletedOffset(int offset) {
264: fDeleteOffset = offset;
265: }
266:
267: /**
268: * Creates the minimal region containing all partition changes using the
269: * remembered offset, end offset, and deletion offset.
270: *
271: * @return the minimal region containing all the partition changes
272: */
273: private IRegion createRegion() {
274: if (fDeleteOffset == -1) {
275: if (fStartOffset == -1 || fEndOffset == -1)
276: return null;
277: return new Region(fStartOffset, fEndOffset - fStartOffset);
278: } else if (fStartOffset == -1 || fEndOffset == -1) {
279: return new Region(fDeleteOffset, 0);
280: } else {
281: int offset = Math.min(fDeleteOffset, fStartOffset);
282: int endOffset = Math.max(fDeleteOffset, fEndOffset);
283: return new Region(offset, endOffset - offset);
284: }
285: }
286:
287: /**
288: * {@inheritDoc}
289: * <p>
290: * May be extended by subclasses.
291: * </p>
292: */
293: public IRegion documentChanged2(DocumentEvent e) {
294:
295: if (!fIsInitialized)
296: return null;
297:
298: try {
299: Assert.isTrue(e.getDocument() == fDocument);
300:
301: Position[] category = getPositions();
302: IRegion line = fDocument.getLineInformationOfOffset(e
303: .getOffset());
304: int reparseStart = line.getOffset();
305: int partitionStart = -1;
306: String contentType = null;
307: int newLength = e.getText() == null ? 0 : e.getText()
308: .length();
309:
310: int first = fDocument.computeIndexInCategory(
311: fPositionCategory, reparseStart);
312: if (first > 0) {
313: TypedPosition partition = (TypedPosition) category[first - 1];
314: if (partition.includes(reparseStart)) {
315: partitionStart = partition.getOffset();
316: contentType = partition.getType();
317: if (e.getOffset() == partition.getOffset()
318: + partition.getLength())
319: reparseStart = partitionStart;
320: --first;
321: } else if (reparseStart == e.getOffset()
322: && reparseStart == partition.getOffset()
323: + partition.getLength()) {
324: partitionStart = partition.getOffset();
325: contentType = partition.getType();
326: reparseStart = partitionStart;
327: --first;
328: } else {
329: partitionStart = partition.getOffset()
330: + partition.getLength();
331: contentType = IDocument.DEFAULT_CONTENT_TYPE;
332: }
333: }
334:
335: fPositionUpdater.update(e);
336: for (int i = first; i < category.length; i++) {
337: Position p = category[i];
338: if (p.isDeleted) {
339: rememberDeletedOffset(e.getOffset());
340: break;
341: }
342: }
343: clearPositionCache();
344: category = getPositions();
345:
346: fScanner.setPartialRange(fDocument, reparseStart, fDocument
347: .getLength()
348: - reparseStart, contentType, partitionStart);
349:
350: int behindLastScannedPosition = reparseStart;
351: IToken token = fScanner.nextToken();
352:
353: while (!token.isEOF()) {
354:
355: contentType = getTokenContentType(token);
356:
357: if (!isSupportedContentType(contentType)) {
358: token = fScanner.nextToken();
359: continue;
360: }
361:
362: int start = fScanner.getTokenOffset();
363: int length = fScanner.getTokenLength();
364:
365: behindLastScannedPosition = start + length;
366: int lastScannedPosition = behindLastScannedPosition - 1;
367:
368: // remove all affected positions
369: while (first < category.length) {
370: TypedPosition p = (TypedPosition) category[first];
371: if (lastScannedPosition >= p.offset + p.length
372: || (p.overlapsWith(start, length) && (!fDocument
373: .containsPosition(
374: fPositionCategory, start,
375: length) || !contentType
376: .equals(p.getType())))) {
377:
378: rememberRegion(p.offset, p.length);
379: fDocument.removePosition(fPositionCategory, p);
380: ++first;
381:
382: } else
383: break;
384: }
385:
386: // if position already exists and we have scanned at least the
387: // area covered by the event, we are done
388: if (fDocument.containsPosition(fPositionCategory,
389: start, length)) {
390: if (lastScannedPosition >= e.getOffset()
391: + newLength)
392: return createRegion();
393: ++first;
394: } else {
395: // insert the new type position
396: try {
397: fDocument.addPosition(fPositionCategory,
398: new TypedPosition(start, length,
399: contentType));
400: rememberRegion(start, length);
401: } catch (BadPositionCategoryException x) {
402: } catch (BadLocationException x) {
403: }
404: }
405:
406: token = fScanner.nextToken();
407: }
408:
409: first = fDocument.computeIndexInCategory(fPositionCategory,
410: behindLastScannedPosition);
411:
412: clearPositionCache();
413: category = getPositions();
414: TypedPosition p;
415: while (first < category.length) {
416: p = (TypedPosition) category[first++];
417: fDocument.removePosition(fPositionCategory, p);
418: rememberRegion(p.offset, p.length);
419: }
420:
421: } catch (BadPositionCategoryException x) {
422: // should never happen on connected documents
423: } catch (BadLocationException x) {
424: } finally {
425: clearPositionCache();
426: }
427:
428: return createRegion();
429: }
430:
431: /**
432: * Returns the position in the partitoner's position category which is
433: * close to the given offset. This is, the position has either an offset which
434: * is the same as the given offset or an offset which is smaller than the given
435: * offset. This method profits from the knowledge that a partitioning is
436: * a ordered set of disjoint position.
437: * <p>
438: * May be extended or replaced by subclasses.
439: * </p>
440: * @param offset the offset for which to search the closest position
441: * @return the closest position in the partitioner's category
442: */
443: protected TypedPosition findClosestPosition(int offset) {
444:
445: try {
446:
447: int index = fDocument.computeIndexInCategory(
448: fPositionCategory, offset);
449: Position[] category = getPositions();
450:
451: if (category.length == 0)
452: return null;
453:
454: if (index < category.length) {
455: if (offset == category[index].offset)
456: return (TypedPosition) category[index];
457: }
458:
459: if (index > 0)
460: index--;
461:
462: return (TypedPosition) category[index];
463:
464: } catch (BadPositionCategoryException x) {
465: } catch (BadLocationException x) {
466: }
467:
468: return null;
469: }
470:
471: /**
472: * {@inheritDoc}
473: * <p>
474: * May be replaced or extended by subclasses.
475: * </p>
476: */
477: public String getContentType(int offset) {
478: checkInitialization();
479:
480: TypedPosition p = findClosestPosition(offset);
481: if (p != null && p.includes(offset))
482: return p.getType();
483:
484: return IDocument.DEFAULT_CONTENT_TYPE;
485: }
486:
487: /**
488: * {@inheritDoc}
489: * <p>
490: * May be replaced or extended by subclasses.
491: * </p>
492: */
493: public ITypedRegion getPartition(int offset) {
494: checkInitialization();
495:
496: try {
497:
498: Position[] category = getPositions();
499:
500: if (category == null || category.length == 0)
501: return new TypedRegion(0, fDocument.getLength(),
502: IDocument.DEFAULT_CONTENT_TYPE);
503:
504: int index = fDocument.computeIndexInCategory(
505: fPositionCategory, offset);
506:
507: if (index < category.length) {
508:
509: TypedPosition next = (TypedPosition) category[index];
510:
511: if (offset == next.offset)
512: return new TypedRegion(next.getOffset(), next
513: .getLength(), next.getType());
514:
515: if (index == 0)
516: return new TypedRegion(0, next.offset,
517: IDocument.DEFAULT_CONTENT_TYPE);
518:
519: TypedPosition previous = (TypedPosition) category[index - 1];
520: if (previous.includes(offset))
521: return new TypedRegion(previous.getOffset(),
522: previous.getLength(), previous.getType());
523:
524: int endOffset = previous.getOffset()
525: + previous.getLength();
526: return new TypedRegion(endOffset, next.getOffset()
527: - endOffset, IDocument.DEFAULT_CONTENT_TYPE);
528: }
529:
530: TypedPosition previous = (TypedPosition) category[category.length - 1];
531: if (previous.includes(offset))
532: return new TypedRegion(previous.getOffset(), previous
533: .getLength(), previous.getType());
534:
535: int endOffset = previous.getOffset() + previous.getLength();
536: return new TypedRegion(endOffset, fDocument.getLength()
537: - endOffset, IDocument.DEFAULT_CONTENT_TYPE);
538:
539: } catch (BadPositionCategoryException x) {
540: } catch (BadLocationException x) {
541: }
542:
543: return new TypedRegion(0, fDocument.getLength(),
544: IDocument.DEFAULT_CONTENT_TYPE);
545: }
546:
547: /*
548: * @see IDocumentPartitioner#computePartitioning(int, int)
549: */
550: public final ITypedRegion[] computePartitioning(int offset,
551: int length) {
552: return computePartitioning(offset, length, false);
553: }
554:
555: /**
556: * {@inheritDoc}
557: * <p>
558: * May be replaced or extended by subclasses.
559: * </p>
560: */
561: public String[] getLegalContentTypes() {
562: return TextUtilities.copy(fLegalContentTypes);
563: }
564:
565: /**
566: * Returns whether the given type is one of the legal content types.
567: * <p>
568: * May be extended by subclasses.
569: * </p>
570: *
571: * @param contentType the content type to check
572: * @return <code>true</code> if the content type is a legal content type
573: */
574: protected boolean isSupportedContentType(String contentType) {
575: if (contentType != null) {
576: for (int i = 0; i < fLegalContentTypes.length; i++) {
577: if (fLegalContentTypes[i].equals(contentType))
578: return true;
579: }
580: }
581:
582: return false;
583: }
584:
585: /**
586: * Returns a content type encoded in the given token. If the token's
587: * data is not <code>null</code> and a string it is assumed that
588: * it is the encoded content type.
589: * <p>
590: * May be replaced or extended by subclasses.
591: * </p>
592: *
593: * @param token the token whose content type is to be determined
594: * @return the token's content type
595: */
596: protected String getTokenContentType(IToken token) {
597: Object data = token.getData();
598: if (data instanceof String)
599: return (String) data;
600: return null;
601: }
602:
603: /* zero-length partition support */
604:
605: /**
606: * {@inheritDoc}
607: * <p>
608: * May be replaced or extended by subclasses.
609: * </p>
610: */
611: public String getContentType(int offset,
612: boolean preferOpenPartitions) {
613: return getPartition(offset, preferOpenPartitions).getType();
614: }
615:
616: /**
617: * {@inheritDoc}
618: * <p>
619: * May be replaced or extended by subclasses.
620: * </p>
621: */
622: public ITypedRegion getPartition(int offset,
623: boolean preferOpenPartitions) {
624: ITypedRegion region = getPartition(offset);
625: if (preferOpenPartitions) {
626: if (region.getOffset() == offset
627: && !region.getType().equals(
628: IDocument.DEFAULT_CONTENT_TYPE)) {
629: if (offset > 0) {
630: region = getPartition(offset - 1);
631: if (region.getType().equals(
632: IDocument.DEFAULT_CONTENT_TYPE))
633: return region;
634: }
635: return new TypedRegion(offset, 0,
636: IDocument.DEFAULT_CONTENT_TYPE);
637: }
638: }
639: return region;
640: }
641:
642: /**
643: * {@inheritDoc}
644: * <p>
645: * May be replaced or extended by subclasses.
646: * </p>
647: */
648: public ITypedRegion[] computePartitioning(int offset, int length,
649: boolean includeZeroLengthPartitions) {
650: checkInitialization();
651: List list = new ArrayList();
652:
653: try {
654:
655: int endOffset = offset + length;
656:
657: Position[] category = getPositions();
658:
659: TypedPosition previous = null, current = null;
660: int start, end, gapOffset;
661: Position gap = new Position(0);
662:
663: int startIndex = getFirstIndexEndingAfterOffset(category,
664: offset);
665: int endIndex = getFirstIndexStartingAfterOffset(category,
666: endOffset);
667: for (int i = startIndex; i < endIndex; i++) {
668:
669: current = (TypedPosition) category[i];
670:
671: gapOffset = (previous != null) ? previous.getOffset()
672: + previous.getLength() : 0;
673: gap.setOffset(gapOffset);
674: gap.setLength(current.getOffset() - gapOffset);
675: if ((includeZeroLengthPartitions && overlapsOrTouches(
676: gap, offset, length))
677: || (gap.getLength() > 0 && gap.overlapsWith(
678: offset, length))) {
679: start = Math.max(offset, gapOffset);
680: end = Math.min(endOffset, gap.getOffset()
681: + gap.getLength());
682: list.add(new TypedRegion(start, end - start,
683: IDocument.DEFAULT_CONTENT_TYPE));
684: }
685:
686: if (current.overlapsWith(offset, length)) {
687: start = Math.max(offset, current.getOffset());
688: end = Math.min(endOffset, current.getOffset()
689: + current.getLength());
690: list.add(new TypedRegion(start, end - start,
691: current.getType()));
692: }
693:
694: previous = current;
695: }
696:
697: if (previous != null) {
698: gapOffset = previous.getOffset() + previous.getLength();
699: gap.setOffset(gapOffset);
700: gap.setLength(fDocument.getLength() - gapOffset);
701: if ((includeZeroLengthPartitions && overlapsOrTouches(
702: gap, offset, length))
703: || (gap.getLength() > 0 && gap.overlapsWith(
704: offset, length))) {
705: start = Math.max(offset, gapOffset);
706: end = Math.min(endOffset, fDocument.getLength());
707: list.add(new TypedRegion(start, end - start,
708: IDocument.DEFAULT_CONTENT_TYPE));
709: }
710: }
711:
712: if (list.isEmpty())
713: list.add(new TypedRegion(offset, length,
714: IDocument.DEFAULT_CONTENT_TYPE));
715:
716: } catch (BadPositionCategoryException ex) {
717: // Make sure we clear the cache
718: clearPositionCache();
719: } catch (RuntimeException ex) {
720: // Make sure we clear the cache
721: clearPositionCache();
722: throw ex;
723: }
724:
725: TypedRegion[] result = new TypedRegion[list.size()];
726: list.toArray(result);
727: return result;
728: }
729:
730: /**
731: * Returns <code>true</code> if the given ranges overlap with or touch each other.
732: *
733: * @param gap the first range
734: * @param offset the offset of the second range
735: * @param length the length of the second range
736: * @return <code>true</code> if the given ranges overlap with or touch each other
737: */
738: private boolean overlapsOrTouches(Position gap, int offset,
739: int length) {
740: return gap.getOffset() <= offset + length
741: && offset <= gap.getOffset() + gap.getLength();
742: }
743:
744: /**
745: * Returns the index of the first position which ends after the given offset.
746: *
747: * @param positions the positions in linear order
748: * @param offset the offset
749: * @return the index of the first position which ends after the offset
750: */
751: private int getFirstIndexEndingAfterOffset(Position[] positions,
752: int offset) {
753: int i = -1, j = positions.length;
754: while (j - i > 1) {
755: int k = (i + j) >> 1;
756: Position p = positions[k];
757: if (p.getOffset() + p.getLength() > offset)
758: j = k;
759: else
760: i = k;
761: }
762: return j;
763: }
764:
765: /**
766: * Returns the index of the first position which starts at or after the given offset.
767: *
768: * @param positions the positions in linear order
769: * @param offset the offset
770: * @return the index of the first position which starts after the offset
771: */
772: private int getFirstIndexStartingAfterOffset(Position[] positions,
773: int offset) {
774: int i = -1, j = positions.length;
775: while (j - i > 1) {
776: int k = (i + j) >> 1;
777: Position p = positions[k];
778: if (p.getOffset() >= offset)
779: j = k;
780: else
781: i = k;
782: }
783: return j;
784: }
785:
786: /*
787: * @see org.eclipse.jface.text.IDocumentPartitionerExtension3#startRewriteSession(org.eclipse.jface.text.DocumentRewriteSession)
788: */
789: public void startRewriteSession(DocumentRewriteSession session)
790: throws IllegalStateException {
791: if (fActiveRewriteSession != null)
792: throw new IllegalStateException();
793: fActiveRewriteSession = session;
794: }
795:
796: /**
797: * {@inheritDoc}
798: * <p>
799: * May be extended by subclasses.
800: * </p>
801: */
802: public void stopRewriteSession(DocumentRewriteSession session) {
803: if (fActiveRewriteSession == session)
804: flushRewriteSession();
805: }
806:
807: /**
808: * {@inheritDoc}
809: * <p>
810: * May be extended by subclasses.
811: * </p>
812: */
813: public DocumentRewriteSession getActiveRewriteSession() {
814: return fActiveRewriteSession;
815: }
816:
817: /**
818: * Flushes the active rewrite session.
819: */
820: protected final void flushRewriteSession() {
821: fActiveRewriteSession = null;
822:
823: // remove all position belonging to the partitioner position category
824: try {
825: fDocument.removePositionCategory(fPositionCategory);
826: } catch (BadPositionCategoryException x) {
827: }
828: fDocument.addPositionCategory(fPositionCategory);
829:
830: fIsInitialized = false;
831: }
832:
833: /**
834: * Clears the position cache. Needs to be called whenever the positions have
835: * been updated.
836: */
837: protected final void clearPositionCache() {
838: if (fCachedPositions != null) {
839: fCachedPositions = null;
840: }
841: }
842:
843: /**
844: * Returns the partitioners positions.
845: *
846: * @return the partitioners positions
847: * @throws BadPositionCategoryException if getting the positions from the
848: * document fails
849: */
850: protected final Position[] getPositions()
851: throws BadPositionCategoryException {
852: if (fCachedPositions == null) {
853: fCachedPositions = fDocument
854: .getPositions(fPositionCategory);
855: } else if (CHECK_CACHE_CONSISTENCY) {
856: Position[] positions = fDocument
857: .getPositions(fPositionCategory);
858: int len = Math.min(positions.length,
859: fCachedPositions.length);
860: for (int i = 0; i < len; i++) {
861: if (!positions[i].equals(fCachedPositions[i]))
862: System.err
863: .println("FastPartitioner.getPositions(): cached position is not up to date: from document: " + toString(positions[i]) + " in cache: " + toString(fCachedPositions[i])); //$NON-NLS-1$ //$NON-NLS-2$
864: }
865: for (int i = len; i < positions.length; i++)
866: System.err
867: .println("FastPartitioner.getPositions(): new position in document: " + toString(positions[i])); //$NON-NLS-1$
868: for (int i = len; i < fCachedPositions.length; i++)
869: System.err
870: .println("FastPartitioner.getPositions(): stale position in cache: " + toString(fCachedPositions[i])); //$NON-NLS-1$
871: }
872: return fCachedPositions;
873: }
874:
875: /**
876: * Pretty print a <code>Position</code>.
877: *
878: * @param position the position to format
879: * @return a formatted string
880: */
881: private String toString(Position position) {
882: return "P[" + position.getOffset() + "+" + position.getLength() + "]"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
883: }
884: }
|