/******************************************************************************* * Copyright (c) 2000, 2007 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM Corporation - initial API and implementation *******************************************************************************/ package org.eclipse.jface.text.rules; import java.util.ArrayList; import java.util.List; import org.eclipse.core.runtime.Assert; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.BadPositionCategoryException; import org.eclipse.jface.text.DefaultPositionUpdater; 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.IDocumentPartitionerExtension2; import org.eclipse.jface.text.IRegion; import org.eclipse.jface.text.ITypedRegion; import org.eclipse.jface.text.Position; import org.eclipse.jface.text.Region; import org.eclipse.jface.text.TextUtilities; import org.eclipse.jface.text.TypedPosition; import org.eclipse.jface.text.TypedRegion; /** * A standard implementation of a syntax driven document partitioner. * It uses a rule based scanner to scan the document and to determine * the document's partitioning. The tokens returned by the rules the * scanner is configured with are supposed to return the partition type * as their data. The partitioner remembers the document's partitions * in the document itself rather than maintaining its own data structure. * * @see IRule * @see RuleBasedScanner * * @deprecated use <code>FastPartitioner</code> instead */ @Deprecated public class RuleBasedPartitioner implements IDocumentPartitioner, IDocumentPartitionerExtension, IDocumentPartitionerExtension2 { /** * The position category this partitioner uses to store the document's partitioning information * @deprecated As of 3.0, use <code>getManagingPositionCategories()</code>. */ @Deprecated public final static String CONTENT_TYPES_CATEGORY= "__content_types_category"; //$NON-NLS-1$ /** The partitioner's scanner */ protected RuleBasedScanner fScanner; /** The legal content types of this partitioner */ protected String[] fLegalContentTypes; /** The partitioner's document */ protected IDocument fDocument; /** The document length before a document change occurred */ protected int fPreviousDocumentLength; /** The position updater used to for the default updating of partitions */ protected DefaultPositionUpdater fPositionUpdater; /** The offset at which the first changed partition starts */ protected int fStartOffset; /** The offset at which the last changed partition ends */ protected int fEndOffset; /**The offset at which a partition has been deleted */ protected int fDeleteOffset; /** * The position category for managing partitioning information. * @since 3.0 */ private String fPositionCategory; /** * Creates a new partitioner that uses the given scanner and may return * partitions of the given legal content types. * * @param scanner the scanner this partitioner is supposed to use * @param legalContentTypes the legal content types of this partitioner */ public RuleBasedPartitioner(RuleBasedScanner scanner, String[] legalContentTypes) { fScanner= scanner; fLegalContentTypes= TextUtilities.copy(legalContentTypes); fPositionCategory= CONTENT_TYPES_CATEGORY + hashCode(); fPositionUpdater= new DefaultPositionUpdater(fPositionCategory); } @Override public String[] getManagingPositionCategories() { return new String[] { fPositionCategory }; } /* * @see IDocumentPartitioner#connect */ @Override public void connect(IDocument document) { Assert.isNotNull(document); Assert.isTrue(!document.containsPositionCategory(fPositionCategory)); fDocument= document; fDocument.addPositionCategory(fPositionCategory); initialize(); } /** * Performs the initial partitioning of the partitioner's document. */ protected void initialize() { fScanner.setRange(fDocument, 0, fDocument.getLength()); try { IToken token= fScanner.nextToken(); while (!token.isEOF()) { String contentType= getTokenContentType(token); if (isSupportedContentType(contentType)) { TypedPosition p= new TypedPosition(fScanner.getTokenOffset(), fScanner.getTokenLength(), contentType); fDocument.addPosition(fPositionCategory, p); } token= fScanner.nextToken(); } } catch (BadLocationException x) { // cannot happen as offsets come from scanner } catch (BadPositionCategoryException x) { // cannot happen if document has been connected before } } /* * @see IDocumentPartitioner#disconnect */ @Override public void disconnect() { Assert.isTrue(fDocument.containsPositionCategory(fPositionCategory)); try { fDocument.removePositionCategory(fPositionCategory); } catch (BadPositionCategoryException x) { // can not happen because of Assert } } /* * @see IDocumentPartitioner#documentAboutToBeChanged */ @Override public void documentAboutToBeChanged(DocumentEvent e) { Assert.isTrue(e.getDocument() == fDocument); fPreviousDocumentLength= e.getDocument().getLength(); fStartOffset= -1; fEndOffset= -1; fDeleteOffset= -1; } /* * @see IDocumentPartitioner#documentChanged */ @Override public boolean documentChanged(DocumentEvent e) { IRegion region= documentChanged2(e); return (region != null); } /** * Helper method for tracking the minimal region containing 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 */ private void rememberRegion(int offset, int length) { // remember start offset if (fStartOffset == -1) fStartOffset= offset; else if (offset < fStartOffset) fStartOffset= offset; // remember end offset int endOffset= offset + length; if (fEndOffset == -1) fEndOffset= endOffset; else if (endOffset > fEndOffset) fEndOffset= endOffset; } /** * Remembers the given offset as the deletion offset. * * @param offset the offset */ private void rememberDeletedOffset(int offset) { fDeleteOffset= offset; } /** * Creates the minimal region containing all partition changes using the * remembered offset, end offset, and deletion offset. * @return the minimal region containing all the partition changes */ private IRegion createRegion() { if (fDeleteOffset == -1) { if (fStartOffset == -1 || fEndOffset == -1) return null; return new Region(fStartOffset, fEndOffset - fStartOffset); } else if (fStartOffset == -1 || fEndOffset == -1) { return new Region(fDeleteOffset, 0); } else { int offset= Math.min(fDeleteOffset, fStartOffset); int endOffset= Math.max(fDeleteOffset, fEndOffset); return new Region(offset, endOffset - offset); } } @Override public IRegion documentChanged2(DocumentEvent e) { try { IDocument d= e.getDocument(); Position[] category= d.getPositions(fPositionCategory); int first= 0; int reparseStart= 0; int originalSize= category.length; if (originalSize > 0) { /* * determine character position at which the scanner starts: * first position behind the last non-default partition the actual position is not involved with */ first= d.computeIndexInCategory(fPositionCategory, e.getOffset()); Position p= null; do { --first; if (first < 0) break; p= category[first]; } while (p.overlapsWith(e.getOffset(), e.getLength()) || (e.getOffset() == fPreviousDocumentLength && (p.getOffset() + p.getLength() == fPreviousDocumentLength))); fPositionUpdater.update(e); for (Position element : category) { p= element; if (p.isDeleted) { rememberDeletedOffset(e.getOffset()); break; } } category= d.getPositions(fPositionCategory); if (first >= 0) { p= category[first]; reparseStart= p.getOffset() + p.getLength(); } ++first; } fScanner.setRange(d, reparseStart, d.getLength() - reparseStart); int lastScannedPosition= reparseStart; IToken token= fScanner.nextToken(); while (!token.isEOF()) { String contentType= getTokenContentType(token); if (!isSupportedContentType(contentType)) { token= fScanner.nextToken(); continue; } int start= fScanner.getTokenOffset(); int length= fScanner.getTokenLength(); lastScannedPosition= start + length - 1; // remove all affected positions while (first < category.length) { TypedPosition p= (TypedPosition) category[first]; if (lastScannedPosition >= p.offset + p.length || (p.overlapsWith(start, length) && (!d.containsPosition(fPositionCategory, start, length) || !contentType.equals(p.getType())))) { rememberRegion(p.offset, p.length); d.removePosition(fPositionCategory, p); ++ first; } else break; } // if position already exists we are done if (d.containsPosition(fPositionCategory, start, length)) return createRegion(); // insert the new type position try { d.addPosition(fPositionCategory, new TypedPosition(start, length, contentType)); rememberRegion(start, length); } catch (BadPositionCategoryException x) { } catch (BadLocationException x) { } token= fScanner.nextToken(); } // remove all positions behind lastScannedPosition since there aren't any further types if (lastScannedPosition != reparseStart) { // if this condition is not met, nothing has been scanned because of a delete ++ lastScannedPosition; } first= d.computeIndexInCategory(fPositionCategory, lastScannedPosition); TypedPosition p; while (first < category.length) { p= (TypedPosition) category[first++]; d.removePosition(fPositionCategory, p); rememberRegion(p.offset, p.length); } } catch (BadPositionCategoryException x) { // should never happen on connected documents } catch (BadLocationException x) { } return createRegion(); } /** * Returns the position in the partitoner's position category which is * close to the given offset. This is, the position has either an offset which * is the same as the given offset or an offset which is smaller than the given * offset. This method profits from the knowledge that a partitioning is * a ordered set of disjoint position. * * @param offset the offset for which to search the closest position * @return the closest position in the partitioner's category */ protected TypedPosition findClosestPosition(int offset) { try { int index= fDocument.computeIndexInCategory(fPositionCategory, offset); Position[] category= fDocument.getPositions(fPositionCategory); if (category.length == 0) return null; if (index < category.length) { if (offset == category[index].offset) return (TypedPosition) category[index]; } if (index > 0) index--; return (TypedPosition) category[index]; } catch (BadPositionCategoryException x) { } catch (BadLocationException x) { } return null; } /* * @see IDocumentPartitioner#getContentType */ @Override public String getContentType(int offset) { TypedPosition p= findClosestPosition(offset); if (p != null && p.includes(offset)) return p.getType(); return IDocument.DEFAULT_CONTENT_TYPE; } /* * @see IDocumentPartitioner#getPartition */ @Override public ITypedRegion getPartition(int offset) { try { Position[] category = fDocument.getPositions(fPositionCategory); if (category == null || category.length == 0) return new TypedRegion(0, fDocument.getLength(), IDocument.DEFAULT_CONTENT_TYPE); int index= fDocument.computeIndexInCategory(fPositionCategory, offset); if (index < category.length) { TypedPosition next= (TypedPosition) category[index]; if (offset == next.offset) return new TypedRegion(next.getOffset(), next.getLength(), next.getType()); if (index == 0) return new TypedRegion(0, next.offset, IDocument.DEFAULT_CONTENT_TYPE); TypedPosition previous= (TypedPosition) category[index - 1]; if (previous.includes(offset)) return new TypedRegion(previous.getOffset(), previous.getLength(), previous.getType()); int endOffset= previous.getOffset() + previous.getLength(); return new TypedRegion(endOffset, next.getOffset() - endOffset, IDocument.DEFAULT_CONTENT_TYPE); } TypedPosition previous= (TypedPosition) category[category.length - 1]; if (previous.includes(offset)) return new TypedRegion(previous.getOffset(), previous.getLength(), previous.getType()); int endOffset= previous.getOffset() + previous.getLength(); return new TypedRegion(endOffset, fDocument.getLength() - endOffset, IDocument.DEFAULT_CONTENT_TYPE); } catch (BadPositionCategoryException x) { } catch (BadLocationException x) { } return new TypedRegion(0, fDocument.getLength(), IDocument.DEFAULT_CONTENT_TYPE); } /* * @see IDocumentPartitioner#computePartitioning */ @Override public ITypedRegion[] computePartitioning(int offset, int length) { return computePartitioning(offset, length, false); } /* * @see IDocumentPartitioner#getLegalContentTypes */ @Override public String[] getLegalContentTypes() { return TextUtilities.copy(fLegalContentTypes); } /** * 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) { if (contentType != null) { for (String fLegalContentType : fLegalContentTypes) { if (fLegalContentType.equals(contentType)) return true; } } return false; } /** * 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; } /* zero-length partition support */ @Override public String getContentType(int offset, boolean preferOpenPartitions) { return getPartition(offset, preferOpenPartitions).getType(); } @Override public ITypedRegion getPartition(int offset, boolean preferOpenPartitions) { ITypedRegion region= getPartition(offset); if (preferOpenPartitions) { if (region.getOffset() == offset && !region.getType().equals(IDocument.DEFAULT_CONTENT_TYPE)) { if (offset > 0) { region= getPartition(offset - 1); if (region.getType().equals(IDocument.DEFAULT_CONTENT_TYPE)) return region; } return new TypedRegion(offset, 0, IDocument.DEFAULT_CONTENT_TYPE); } } return region; } @Override public ITypedRegion[] computePartitioning(int offset, int length, boolean includeZeroLengthPartitions) { List<TypedRegion> list= new ArrayList<>(); try { int endOffset= offset + length; Position[] category= fDocument.getPositions(fPositionCategory); TypedPosition previous= null, current= null; int start, end, gapOffset; Position gap= null; for (Position element : category) { current= (TypedPosition) element; gapOffset= (previous != null) ? previous.getOffset() + previous.getLength() : 0; gap= new Position(gapOffset, current.getOffset() - gapOffset); if ((includeZeroLengthPartitions || gap.getLength() > 0) && gap.overlapsWith(offset, length)) { start= Math.max(offset, gapOffset); end= Math.min(endOffset, gap.getOffset() + gap.getLength()); list.add(new TypedRegion(start, end - start, IDocument.DEFAULT_CONTENT_TYPE)); } if (current.overlapsWith(offset, length)) { start= Math.max(offset, current.getOffset()); end= Math.min(endOffset, current.getOffset() + current.getLength()); list.add(new TypedRegion(start, end - start, current.getType())); } previous= current; } if (previous != null) { gapOffset= previous.getOffset() + previous.getLength(); gap= new Position(gapOffset, fDocument.getLength() - gapOffset); if ((includeZeroLengthPartitions || gap.getLength() > 0) && ((includeZeroLengthPartitions && offset + length == gapOffset && gap.length == 0) || gap.overlapsWith(offset, length))) { start= Math.max(offset, gapOffset); end= Math.min(endOffset, fDocument.getLength()); list.add(new TypedRegion(start, end - start, IDocument.DEFAULT_CONTENT_TYPE)); } } if (list.isEmpty()) list.add(new TypedRegion(offset, length, IDocument.DEFAULT_CONTENT_TYPE)); } catch (BadPositionCategoryException x) { } TypedRegion[] result= new TypedRegion[list.size()]; list.toArray(result); return result; } }