diff options
Diffstat (limited to 'org.eclipse.text/src/org/eclipse/jface/text/AbstractDocument.java')
-rw-r--r-- | org.eclipse.text/src/org/eclipse/jface/text/AbstractDocument.java | 1177 |
1 files changed, 1177 insertions, 0 deletions
diff --git a/org.eclipse.text/src/org/eclipse/jface/text/AbstractDocument.java b/org.eclipse.text/src/org/eclipse/jface/text/AbstractDocument.java new file mode 100644 index 00000000000..8e822aa7e68 --- /dev/null +++ b/org.eclipse.text/src/org/eclipse/jface/text/AbstractDocument.java @@ -0,0 +1,1177 @@ +/********************************************************************** +Copyright (c) 2000, 2002 IBM Corp. and others. +All rights reserved. This program and the accompanying materials +are made available under the terms of the Common Public License v1.0 +which accompanies this distribution, and is available at +http://www.eclipse.org/legal/cpl-v10.html + +Contributors: + IBM Corporation - Initial implementation +**********************************************************************/ + +package org.eclipse.jface.text; + + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + + +/** + * Abstract implementation of <code>IDocument</code>. + * Implements the complete contract of <code>IDocument</code> and <code>IDocumentExtension</code>. + * An <code>AbstractDocument</code> supports the following implementation plug-ins: + * <ul> + * <li> a text store for storing and managing the document's content + * <li> a line tracker to map character positions to line numbers and vice versa + * </ul> + * This class must be subclassed. Subclasses must configure which implementation + * plug-ins the document should use. Subclasses are not intended to overwrite existing methods. + * + * @see IDocument + * @see ITextStore + * @see ILineTracker + */ +public abstract class AbstractDocument implements IDocument, IDocumentExtension { + + /** The document's text store */ + private ITextStore fStore; + /** The document's line tracker */ + private ILineTracker fTracker; + /** The document's partitioner */ + private IDocumentPartitioner fDocumentPartitioner; + /** + * The document's partitioner casted to <code>IDocumentPartitionerExtension</code>. + * @since 2.0 + */ + private IDocumentPartitionerExtension fDocumentPartitionerExtension; + /** The registered document listeners */ + private List fDocumentListeners; + /** The registered prenotified document listeners */ + private List fPrenotifiedDocumentListeners; + /** The registered document partitioning listeners */ + private List fDocumentPartitioningListeners; + /** All positions managed by the document */ + private Map fPositions; + /** All registered document position updaters */ + private List fPositionUpdaters; + + /** + * The list of post notification changes + * @since 2.0 + */ + private List fPostNotificationChanges; + /** + * The reentrance count for post notification changes. + * @since 2.0 + */ + private int fReentranceCount= 0; + /** + * Indicates whether post notification change processing has been stopped. + * @since 2.0 + */ + private int fStoppedCount= 0; + + /** + * The default constructor does not perform any configuration + * but leaves it to the clients who must first initialize the + * implementation plug-ins and then call <code>completeInitialization</code>. + * Results in the construction of an empty document. + */ + protected AbstractDocument() { + } + + + //--- accessor to fields ------------------------------- + + /** + * Returns the document's text store. Assumes that the + * document has been initialized with a text store. + * + * @return the document's text store + */ + protected ITextStore getStore() { + Assert.isNotNull(fStore); + return fStore; + } + + /** + * Returns the document's line tracker. Assumes that the + * document has been initialized with a line tracker. + * + * @return the document's line tracker + */ + protected ILineTracker getTracker() { + Assert.isNotNull(fTracker); + return fTracker; + } + + /** + * Returns the document's document listeners. + * + * @return the document's document listeners + */ + protected List getDocumentListeners() { + return fDocumentListeners; + } + + /** + * Returns the document's partitioning listeners . + * + * @return the document's partitioning listeners + */ + protected List getDocumentPartitioningListeners() { + return fDocumentPartitioningListeners; + } + + /** + * Returns all positions managed by the document grouped by category. + * + * @return the document's positions + */ + protected Map getDocumentManagedPositions() { + return fPositions; + } + + /* + * @see IDocument#getDocumentPartitioner + */ + public IDocumentPartitioner getDocumentPartitioner() { + return fDocumentPartitioner; + } + + + + //--- implementation configuration interface ------------ + + /** + * Sets the document's text store. + * Must be called first inside the constructor. + * + * @param store the document's text store + */ + protected void setTextStore(ITextStore store) { + fStore= store; + } + + /** + * Sets the document's line tracker. + * Must be called first inside the constructor. + * + * @param tracker the document's line tracker + */ + protected void setLineTracker(ILineTracker tracker) { + fTracker= tracker; + } + + /* + * @see IDocument#setDocumentPartitioner + */ + public void setDocumentPartitioner(IDocumentPartitioner partitioner) { + fDocumentPartitioner= partitioner; + if (fDocumentPartitioner instanceof IDocumentPartitionerExtension) + fDocumentPartitionerExtension= (IDocumentPartitionerExtension) fDocumentPartitioner; + + fireDocumentPartitioningChanged(new Region(0, getLength())); + } + + /** + * Initializes document listeners, positions, and position updaters. + * Must be called inside the constructor after the implementation plug-ins + * have been set. + */ + protected void completeInitialization() { + + fPositions= new HashMap(); + fPositionUpdaters= new ArrayList(); + fDocumentListeners= new ArrayList(); + fPrenotifiedDocumentListeners= new ArrayList(); + fDocumentPartitioningListeners= new ArrayList(); + + addPositionCategory(DEFAULT_CATEGORY); + addPositionUpdater(new DefaultPositionUpdater(DEFAULT_CATEGORY)); + } + + + //------------------------------------------------------- + + /* + * @see IDocument#addDocumentListener + */ + public void addDocumentListener(IDocumentListener listener) { + Assert.isNotNull(listener); + if (! fDocumentListeners.contains(listener)) + fDocumentListeners.add(listener); + } + + /* + * @see IDocument#removeDocumentListener + */ + public void removeDocumentListener(IDocumentListener listener) { + Assert.isNotNull(listener); + fDocumentListeners.remove(listener); + } + + /* + * @see IDocument#addPrenotifiedDocumentListener(IDocumentListener) + */ + public void addPrenotifiedDocumentListener(IDocumentListener listener) { + Assert.isNotNull(listener); + if (! fPrenotifiedDocumentListeners.contains(listener)) + fPrenotifiedDocumentListeners.add(listener); + } + + /* + * @see IDocument#removePrenotifiedDocumentListener(IDocumentListener) + */ + public void removePrenotifiedDocumentListener(IDocumentListener listener) { + Assert.isNotNull(listener); + fPrenotifiedDocumentListeners.remove(listener); + } + + /* + * @see IDocument#addDocumentPartitioningListener + */ + public void addDocumentPartitioningListener(IDocumentPartitioningListener listener) { + Assert.isNotNull(listener); + if (! fDocumentPartitioningListeners.contains(listener)) + fDocumentPartitioningListeners.add(listener); + } + + /* + * @see IDocument#removeDocumentPartitioningListener + */ + public void removeDocumentPartitioningListener(IDocumentPartitioningListener listener) { + Assert.isNotNull(listener); + fDocumentPartitioningListeners.remove(listener); + } + + /* + * @see IDocument#addPosition + */ + public void addPosition(String category, Position position) throws BadLocationException, BadPositionCategoryException { + + if ((0 > position.offset) || (0 > position.length) || (position.offset + position.length > getLength())) + throw new BadLocationException(); + + if (category == null) + throw new BadPositionCategoryException(); + + List list= (List) fPositions.get(category); + if (list == null) + throw new BadPositionCategoryException(); + + list.add(computeIndexInPositionList(list, position.offset), position); + } + + /* + * @see IDocument#addPosition + */ + public void addPosition(Position position) throws BadLocationException { + try { + addPosition(DEFAULT_CATEGORY, position); + } catch (BadPositionCategoryException e) { + } + } + + /* + * @see IDocument#addPositionCategory + */ + public void addPositionCategory(String category) { + + if (category == null) + return; + + if (!containsPositionCategory(category)) + fPositions.put(category, new ArrayList()); + } + + /* + * @see IDocument#addPositionUpdater + */ + public void addPositionUpdater(IPositionUpdater updater) { + insertPositionUpdater(updater, fPositionUpdaters.size()); + } + + /* + * @see IDocument#containsPosition + */ + public boolean containsPosition(String category, int offset, int length) { + + if (category == null) + return false; + + List list= (List) fPositions.get(category); + if (list == null) + return false; + + int size= list.size(); + if (size == 0) + return false; + + int index= computeIndexInPositionList(list, offset); + if (index < size) { + Position p= (Position) list.get(index); + while (p != null && p.offset == offset) { + if (p.length == length) + return true; + ++ index; + p= (index < size) ? (Position) list.get(index) : null; + } + } + + return false; + } + + /* + * @see IDocument#containsPositionCategory + */ + public boolean containsPositionCategory(String category) { + if (category != null) + return fPositions.containsKey(category); + return false; + } + + + /** + * Computes the index in the list of positions at which a position with the given + * offset would be inserted. The position is supposed to become the first in this list + * of all positions with the same offset. + * + * @param positions the list in which the index is computed + * @param offset the offset for which the index is computed + * @return the computed index + * + * @see IDocument#computeIndexInCategory(String, int) + */ + protected int computeIndexInPositionList(List positions, int offset) { + + if (positions.size() == 0) + return 0; + + int left= 0; + int right= positions.size() -1; + int mid= 0; + Position p= null; + + while (left < right) { + + mid= (left + right) / 2; + + p= (Position) positions.get(mid); + if (offset < p.getOffset()) { + if (left == mid) + right= left; + else + right= mid -1; + } else if (offset > p.getOffset()) { + if (right == mid) + left= right; + else + left= mid +1; + } else if (offset == p.getOffset()) { + left= right= mid; + } + + } + + int pos= left; + p= (Position) positions.get(pos); + if (offset > p.getOffset()) { + // append to the end + pos++; + } else { + // entry will became the first of all entries with the same offset + do { + --pos; + if (pos < 0) + break; + p= (Position) positions.get(pos); + } while (offset == p.getOffset()); + ++pos; + } + + Assert.isTrue(0 <= pos && pos <= positions.size()); + + return pos; + } + + + /* + * @see IDocument#computeIndexInCategory + */ + public int computeIndexInCategory(String category, int offset) throws BadLocationException, BadPositionCategoryException { + + if (0 > offset || offset > getLength()) + throw new BadLocationException(); + + List c= (List) fPositions.get(category); + if (c == null) + throw new BadPositionCategoryException(); + + return computeIndexInPositionList(c, offset); + } + + /** + * Fires the document partitioning changed notification to all registered + * document partitioning listeners. Uses a robust iterator. + * @deprecated use <code>fireDocumentPartitioningChanged(IRegion)</code> instead + */ + protected void fireDocumentPartitioningChanged() { + + if (fDocumentPartitioningListeners != null && fDocumentPartitioningListeners.size() > 0) { + + List list= new ArrayList(fDocumentPartitioningListeners); + Iterator e= list.iterator(); + while (e.hasNext()) { + IDocumentPartitioningListener l= (IDocumentPartitioningListener) e.next(); + l.documentPartitioningChanged(this); + } + } + } + + /** + * Fires the document partitioning changed notification to all registered + * document partitioning listeners. Uses a robust iterator. + * + * @param region the region in which partitioning has changed + * @see IDocumentPartitioningListenerExtension + * @since 2.0 + */ + protected void fireDocumentPartitioningChanged(IRegion region) { + + if (fDocumentPartitioningListeners != null && fDocumentPartitioningListeners.size() > 0) { + + List list= new ArrayList(fDocumentPartitioningListeners); + Iterator e= list.iterator(); + while (e.hasNext()) { + IDocumentPartitioningListener l= (IDocumentPartitioningListener) e.next(); + if (l instanceof IDocumentPartitioningListenerExtension) + ((IDocumentPartitioningListenerExtension) l).documentPartitioningChanged(this, region); + else + l.documentPartitioningChanged(this); + } + } + } + + /** + * Fires the given document event to all registers document listeners informing them + * about the forthcoming document manipulation. Uses a robust iterator. + * + * @param event the event to be sent out + */ + protected void fireDocumentAboutToBeChanged(DocumentEvent event) { + + // IDocumentExtension + if (fReentranceCount == 0) + flushPostNotificationChanges(); + + if (fDocumentPartitioner != null) + fDocumentPartitioner.documentAboutToBeChanged(event); + + if (fPrenotifiedDocumentListeners.size() > 0) { + + List list= new ArrayList(fPrenotifiedDocumentListeners); + Iterator e= list.iterator(); + while (e.hasNext()) { + IDocumentListener l= (IDocumentListener) e.next(); + l.documentAboutToBeChanged(event); + } + } + + if (fDocumentListeners.size() > 0) { + + List list= new ArrayList(fDocumentListeners); + Iterator e= list.iterator(); + while (e.hasNext()) { + IDocumentListener l= (IDocumentListener) e.next(); + l.documentAboutToBeChanged(event); + } + } + } + + /** + * Updates document partitioning and document positions according to the + * specification given by the document event. + * + * @param event the document event describing the change to which structures must be adapted + */ + protected void updateDocumentStructures(DocumentEvent event) { + boolean partitioningChanged= false; + IRegion changedRegion= null; + + if (fDocumentPartitioner != null) { + if (fDocumentPartitionerExtension != null) { + changedRegion= fDocumentPartitionerExtension.documentChanged2(event); + partitioningChanged= (changedRegion != null); + } else + partitioningChanged= fDocumentPartitioner.documentChanged(event); + } + + if (fPositions.size() > 0) + updatePositions(event); + + if (partitioningChanged) + fireDocumentPartitioningChanged(changedRegion); + } + + /** + * Updates the internal document structures and informs all document listeners. + * Uses a robust iterator. <p> + * Executes all registered post notification replace operation. + * + * @param event the document event to be sent out + * @see IDocumentExtension + */ + protected void fireDocumentChanged(DocumentEvent event) { + updateDocumentStructures(event); + + if (fPrenotifiedDocumentListeners.size() > 0) { + + List list= new ArrayList(fPrenotifiedDocumentListeners); + Iterator e= list.iterator(); + while (e.hasNext()) { + IDocumentListener l= (IDocumentListener) e.next(); + l.documentChanged(event); + } + } + + if (fDocumentListeners.size() > 0) { + + List list= new ArrayList(fDocumentListeners); + Iterator e= list.iterator(); + while (e.hasNext()) { + IDocumentListener l= (IDocumentListener) e.next(); + l.documentChanged(event); + } + } + + // IDocumentExtension + ++ fReentranceCount; + try { + if (fReentranceCount == 1) + executePostNotificationChanges(); + } finally { + -- fReentranceCount; + } + } + + /* + * @see IDocument#getChar + */ + public char getChar(int pos) throws BadLocationException { + if ((0 > pos) || (pos >= getLength())) + throw new BadLocationException(); + return getStore().get(pos); + } + + /* + * @see IDocument#getContentType + */ + public String getContentType(int offset) throws BadLocationException { + if ((0 > offset) || (offset > getLength())) + throw new BadLocationException(); + + if (fDocumentPartitioner == null) + return DEFAULT_CONTENT_TYPE; + + return fDocumentPartitioner.getContentType(offset); + } + + /* + * @see IDocument#getLegalContentTypes + */ + public String[] getLegalContentTypes() { + if (fDocumentPartitioner == null) + return new String[] { DEFAULT_CONTENT_TYPE }; + return fDocumentPartitioner.getLegalContentTypes(); + } + + /* + * @see IDocument#getLength + */ + public int getLength() { + return getStore().getLength(); + } + + /* + * @see IDocument#getLineDelimiter + */ + public String getLineDelimiter(int line) throws BadLocationException { + return getTracker().getLineDelimiter(line); + } + + /* + * @see IDocument#getLegalLineDelimiters + */ + public String[] getLegalLineDelimiters() { + return getTracker().getLegalLineDelimiters(); + } + + /* + * @see IDocument#getLineLength + */ + public int getLineLength(int line) throws BadLocationException { + return getTracker().getLineLength(line); + } + + /* + * @see IDocument#getLineOfOffset + */ + public int getLineOfOffset(int pos) throws BadLocationException { + return getTracker().getLineNumberOfOffset(pos); + } + + /* + * @see IDocument#getLineOffset + */ + public int getLineOffset(int line) throws BadLocationException { + return getTracker().getLineOffset(line); + } + + /* + * @see IDocument#getLineInformation + */ + public IRegion getLineInformation(int line) throws BadLocationException { + return getTracker().getLineInformation(line); + } + + /* + * @see IDocument#getLineInformationOfOffset + */ + public IRegion getLineInformationOfOffset(int offset) throws BadLocationException { + return getTracker().getLineInformationOfOffset(offset); + } + + /* + * @see IDocument#getNumberOfLines + */ + public int getNumberOfLines() { + return getTracker().getNumberOfLines(); + } + + /* + * @see IDocument#getNumberOfLines(int, int) + */ + public int getNumberOfLines(int offset, int length) throws BadLocationException { + return getTracker().getNumberOfLines(offset, length); + } + + /* + * @see IDocument#computeNumberOfLines(String) + */ + public int computeNumberOfLines(String text) { + return getTracker().computeNumberOfLines(text); + } + + /* + * @see IDocument#getPartition + */ + public ITypedRegion getPartition(int offset) throws BadLocationException { + if ((0 > offset) || (offset > getLength())) + throw new BadLocationException(); + + if (fDocumentPartitioner == null) + return new TypedRegion(0, getLength(), DEFAULT_CONTENT_TYPE); + + return fDocumentPartitioner.getPartition(offset); + } + + /* + * @see IDocument#computePartitioning + */ + public ITypedRegion[] computePartitioning(int offset, int length) throws BadLocationException { + if ((0 > offset) || (0 > length) || (offset + length > getLength())) + throw new BadLocationException(); + + if (fDocumentPartitioner == null) + return new TypedRegion[] { new TypedRegion(offset, length, DEFAULT_CONTENT_TYPE) }; + + return fDocumentPartitioner.computePartitioning(offset, length); + } + + /* + * @see IDocument#getPositions + */ + public Position[] getPositions(String category) throws BadPositionCategoryException { + + if (category == null) + throw new BadPositionCategoryException(); + + List c= (List) fPositions.get(category); + if (c == null) + throw new BadPositionCategoryException(); + + Position[] positions= new Position[c.size()]; + c.toArray(positions); + return positions; + } + + /* + * @see IDocument#getPositionCategories + */ + public String[] getPositionCategories() { + String[] categories= new String[fPositions.size()]; + Iterator keys= fPositions.keySet().iterator(); + for (int i= 0; i < categories.length; i++) + categories[i]= (String) keys.next(); + return categories; + } + + /* + * @see IDocument#getPositionUpdaters + */ + public IPositionUpdater[] getPositionUpdaters() { + IPositionUpdater[] updaters= new IPositionUpdater[fPositionUpdaters.size()]; + fPositionUpdaters.toArray(updaters); + return updaters; + } + + /* + * @see IDocument#get + */ + public String get() { + return getStore().get(0, getLength()); + } + + /* + * @see IDocument#get + */ + public String get(int pos, int length) throws BadLocationException { + int myLength= getLength(); + if ((0 > pos) || (0 > length) || (pos + length > myLength)) + throw new BadLocationException(); + return getStore().get(pos, length); + } + + /* + * @see IDocument#insertPositionUpdater + */ + public void insertPositionUpdater(IPositionUpdater updater, int index) { + + for (int i= fPositionUpdaters.size() - 1; i >= 0; i--) { + if (fPositionUpdaters.get(i) == updater) + return; + } + + if (index == fPositionUpdaters.size()) + fPositionUpdaters.add(updater); + else + fPositionUpdaters.add(index, updater); + } + + /* + * @see IDocument#removePosition + */ + public void removePosition(String category, Position position) throws BadPositionCategoryException { + + if (position == null) + return; + + if (category == null) + throw new BadPositionCategoryException(); + + List c= (List) fPositions.get(category); + if (c == null) + throw new BadPositionCategoryException(); + + // remove based on identity not equality + int size= c.size(); + for (int i= 0; i < size; i++) { + if (position == c.get(i)) { + c.remove(i); + return; + } + } + } + + /* + * @see IDocument#removePosition + */ + public void removePosition(Position position) { + try { + removePosition(DEFAULT_CATEGORY, position); + } catch (BadPositionCategoryException e) { + } + } + + /* + * @see IDocument#removePositionCategory + */ + public void removePositionCategory(String category) throws BadPositionCategoryException { + + if (category == null) + return; + + if ( !containsPositionCategory(category)) + throw new BadPositionCategoryException(); + + fPositions.remove(category); + } + + /* + * @see IDocument#removePositionUpdater + */ + public void removePositionUpdater(IPositionUpdater updater) { + for (int i= fPositionUpdaters.size() - 1; i >= 0; i--) { + if (fPositionUpdaters.get(i) == updater) { + fPositionUpdaters.remove(i); + return; + } + } + } + + /* + * @see IDocument#replace + */ + public void replace(int pos, int length, String text) throws BadLocationException { + if ((0 > pos) || (0 > length) || (pos + length > getLength())) + throw new BadLocationException(); + + DocumentEvent e= new DocumentEvent(this, pos, length, text); + fireDocumentAboutToBeChanged(e); + + getStore().replace(pos, length, text); + getTracker().replace(pos, length, text); + + fireDocumentChanged(e); + } + + /* + * @see IDocument#set + */ + public void set(String text) { + int length= getStore().getLength(); + DocumentEvent e= new DocumentEvent(this, 0, length, text); + fireDocumentAboutToBeChanged(e); + + getStore().set(text); + getTracker().set(text); + + fireDocumentChanged(e); + } + + /** + * Updates all positions of all categories to the change + * described by the document event. All registered document + * updaters are called in the sequence they have been arranged. + * Uses a robust iterator. + * + * @param event the document event describing the change to which to adapt the positions + */ + protected void updatePositions(DocumentEvent event) { + List list= new ArrayList(fPositionUpdaters); + Iterator e= list.iterator(); + while (e.hasNext()) { + IPositionUpdater u= (IPositionUpdater) e.next(); + u.update(event); + } + } + + /* + * @see IDocument#search + */ + public int search(int startPosition, String findString, boolean forwardSearch, boolean caseSensitive, boolean wholeWord) throws BadLocationException { + + if (findString == null || findString.length() == 0) + return -1; + + ITextStore store= getStore(); + + if (startPosition < -1 || startPosition > store.getLength()) + throw new BadLocationException(); + + if (!caseSensitive) + findString= findString.toLowerCase(); + + char[] fs= new char[findString.length()]; + findString.getChars(0, fs.length, fs, 0); + + + if (forwardSearch) { + if (startPosition == -1) + startPosition= 0; + int end= getLength(); + while (startPosition < end) { + int pos= indexOf(store, fs, startPosition, caseSensitive); + if (!wholeWord || pos == -1 || isWholeWord(store, pos, pos + fs.length)) { + return pos; + } + startPosition= pos + 1; + } + } else { + if (startPosition == -1) + startPosition= getLength(); + while (startPosition >= 0) { + int pos= lastIndexOf(store, fs, startPosition, caseSensitive); + if (!wholeWord || pos == -1 || isWholeWord(store, pos, pos + fs.length)) { + return pos; + } + startPosition= pos - 1; + } + } + return -1; + } + + /** + * Returns the first index greater than <code>fromIndex</code> at which <code>str</code> + * can be found in the <code>store</code>. + * + * @param store the text store to search + * @param str the string to search + * @param fromIndex the start offset + * @param caseSensitive <code>true</code> if capitalization should be honored, <code>false</code> otherwise + * @return the offset greater than the start offset at which the search string has been found + */ + static private int indexOf(ITextStore store, char[] str, int fromIndex, boolean caseSensitive) { + int count= store.getLength(); + + if (fromIndex >= count) + return -1; + + if (fromIndex < 0) + fromIndex= 0; + + int strLen= str.length; + if (strLen == 0) // empty string always matches + return fromIndex; + + char first= str[0]; + int i= fromIndex; + int max= count - strLen; + + restart: + while (true) { + + // Look for first character + if (caseSensitive) { + while (i <= max && store.get(i) != first) + i++; + } else { + while (i <= max && Character.toLowerCase(store.get(i)) != first) + i++; + } + + if (i > max) + return -1; + + // Found first character + int j= i + 1; + int end= j + strLen - 1; + int k= 1; + if (caseSensitive) { + while (j < end) { + if (store.get(j++) != str[k++]) { + i++; + continue restart; + } + } + } else { + while (j < end) { + if (Character.toLowerCase(store.get(j++)) != str[k++]) { + i++; + continue restart; + } + } + } + + return i; // Found + } + } + + /** + * Returns the first index smaller than <code>fromIndex</code> at which <code>str</code> + * can be found in the <code>store</code>. + * + * @param store the text store to search + * @param str the string to search + * @param fromIndex the start offset + * @param caseSensitive <code>true</code> if capitalization should be honored, <code>false</code> otherwise + * @return the offset smaller than the start offset at which the search string has been found + */ + static private int lastIndexOf(ITextStore store, char[] str, int fromIndex, boolean caseSensitive) { + + if (fromIndex < 0) + return -1; + + int count= store.getLength(); + int strLen= str.length; + int rightIndex= count - strLen; + + if (fromIndex > rightIndex) + fromIndex= rightIndex; + + if (strLen == 0) // empty string always matches + return fromIndex; + + int strLastIndex= strLen - 1; + char strLastChar= str[strLastIndex]; + int min= strLen - 1; + int i= min + fromIndex; + + restart: + while (true) { + + // Look for the last character + if (caseSensitive) { + while (i >= min && store.get(i) != strLastChar) + i--; + } else { + while (i >= min && Character.toLowerCase(store.get(i)) != strLastChar) + i--; + } + + if (i < min) + return -1; + + // Found last character + int j= i - 1; + int start= j - (strLen - 1); + int k= strLastIndex - 1; + + if (caseSensitive) { + while (j > start) { + if (store.get(j--) != str[k--]) { + i--; + continue restart; + } + } + } else { + while (j > start) { + if (Character.toLowerCase(store.get(j--)) != str[k--]) { + i--; + continue restart; + } + } + } + + return start + 1; /* Found whole string. */ + } + } + + /** + * Tests if the substring is a whole word. + * + * @param store the store in which to find the string + * @param from the substring start offset + * @param to the substring endoffset + * @return <code>true</code> if the string is a whole word, otherwise <code>false</code> + */ + private static boolean isWholeWord(ITextStore store, int from, int to) { + + if (from > 0) { + char ch= store.get(from-1); + if (Character.isLetterOrDigit(ch) || ch == '_') { + return false; + } + } + if (to < store.getLength()) { + char ch= store.get(to); + if (Character.isLetterOrDigit(ch) || ch == '_' ) { + return false; + } + } + return true; + } + + + // ---------- implementation of IDocumentExtension -------------- + + /** + * Inner class to bundle a registered post notifcation replace operation together with its + * owner. + * + * @since 2.0 + */ + static private class RegisteredReplace { + /** The owner of this replace operation. */ + IDocumentListener fOwner; + /** The replace operation */ + IDocumentExtension.IReplace fReplace; + + /** + * Creates a new bundle object. + */ + RegisteredReplace(IDocumentListener owner, IDocumentExtension.IReplace replace) { + fOwner= owner; + fReplace= replace; + } + }; + + /** + * Flushs all registered post notification changes. + * + * @since 2.0 + */ + private void flushPostNotificationChanges() { + if (fPostNotificationChanges != null) + fPostNotificationChanges.clear(); + } + + /** + * Executes all registered post notification changes. The process is + * repeated until no new post notification changes are added. + * + * @since 2.0 + */ + private void executePostNotificationChanges() { + + if (fStoppedCount > 0) + return; + + while (fPostNotificationChanges != null) { + List changes= fPostNotificationChanges; + fPostNotificationChanges= null; + + Iterator e= changes.iterator(); + while (e.hasNext()) { + RegisteredReplace replace = (RegisteredReplace) e.next(); + replace.fReplace.perform(this, replace.fOwner); + } + } + } + + /* + * @see IDocumentExtension#registerPostNotificationReplace(IDocumentListener, IDocumentExtension.IReplace) + * @since 2.0 + */ + public void registerPostNotificationReplace(IDocumentListener owner, IDocumentExtension.IReplace replace) { + if (fPostNotificationChanges == null) + fPostNotificationChanges= new ArrayList(1); + fPostNotificationChanges.add(new RegisteredReplace(owner, replace)); + } + + /* + * @see IDocumentExtension#stopPostNotificationProcessing() + * @since 2.0 + */ + public void stopPostNotificationProcessing() { + ++ fStoppedCount; + } + + /* + * @see IDocumentExtension#resumePostNotificationProcessing() + * @since 2.0 + */ + public void resumePostNotificationProcessing() { + -- fStoppedCount; + if (fStoppedCount == 0 && fReentranceCount == 0) + executePostNotificationChanges(); + } + + /* + * @see IDocumentExtension#startSequentialRewrite(boolean) + * @since 2.0 + */ + public void startSequentialRewrite(boolean normalized) { + } + + /* + * @see IDocumentExtension#stopSequentialRewrite() + * @since 2.0 + */ + public void stopSequentialRewrite() { + } +} |