/******************************************************************************* * Copyright (c) 2012-2015 Codenvy, S.A. * 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: * Codenvy, S.A. - initial API and implementation *******************************************************************************/ package org.eclipse.che.ide.ext.java.jdt.text; import org.eclipse.che.ide.api.editor.text.BadLocationException; import org.eclipse.che.ide.api.editor.text.BadPositionCategoryException; import org.eclipse.che.ide.api.editor.text.Position; import org.eclipse.che.ide.api.editor.text.Region; import org.eclipse.che.ide.api.editor.text.TypedRegion; import org.eclipse.che.ide.api.editor.text.TypedRegionImpl; import org.eclipse.che.ide.runtime.Assert; import com.google.gwt.core.client.JavaScriptException; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; /** * Abstract default implementation of <code>IDocument</code> and its extension interfaces * {@link org.eclipse.jface.text.IDocumentExtension}, {@link org.eclipse.jface.text.IDocumentExtension2}, * {@link org.eclipse.jface.text.IDocumentExtension3}, {@link org.eclipse.jface.text.IDocumentExtension4}, as well as * {@link org.eclipse.jface.text.IRepairableDocument}. * <p/> * <p/> * An <code>AbstractDocument</code> supports the following implementation plug-ins: * <ul> * <li>a text store implementing {@link TextStore.text.ITextStore} for storing and managing the document's content,</li> * <li>a line tracker implementing {@link org.eclipse.che.ide.legacy.client.api.text.eclipse.LineTracker.text.ILineTracker} to map character positions to line numbers and vice * versa</li> * </ul> * The document can dynamically change the text store when switching between sequential rewrite mode and normal mode. * <p/> * <p/> * This class must be subclassed. Subclasses must configure which implementation plug-ins the document instance should use. * Subclasses are not intended to overwrite existing methods. * * @see TextStore.text.ITextStore * @see org.eclipse.che.ide.legacy.client.api.text.eclipse.LineTracker.text.ILineTracker */ public abstract class AbstractDocument implements Document { /** The document's text store */ private TextStore fStore; /** The document's line tracker */ private LineTracker fTracker; /** The registered document listeners */ private ListenerList fDocumentListeners; /** The registered pre-notified document listeners */ private ListenerList fPrenotifiedDocumentListeners; /** The registered document partitioning listeners */ private ListenerList fDocumentPartitioningListeners; /** All positions managed by the document ordered by their start positions. */ private Map<String, List<Position>> fPositions; /** All positions managed by the document ordered by there end positions. */ private Map<String, List<Position>> fEndPositions; /** All registered document position updaters */ private List<PositionUpdater> fPositionUpdaters; /** Indicates whether post notification change processing has been stopped. */ private int fStoppedCount = 0; /** Indicates whether the registration of post notification changes should be ignored. */ private boolean fAcceptPostNotificationReplaces = true; /** Indicates whether the notification of listeners has been stopped. */ private int fStoppedListenerNotification = 0; /** The document event to be sent after listener notification has been resumed. */ private DocumentEvent fDeferredDocumentEvent; /** The registered document partitioners. */ private Map<String, DocumentPartitioner> fDocumentPartitioners; /** The partitioning changed event. */ private DocumentPartitioningChangedEvent fDocumentPartitioningChangedEvent; /** * The current modification stamp. * * @since 3.1 */ private long fModificationStamp = UNKNOWN_MODIFICATION_STAMP; /** * Keeps track of next modification stamp. * * @since 3.1.1 */ private long fNextModificationStamp = UNKNOWN_MODIFICATION_STAMP; /** * This document's default line delimiter. * * @since 3.1 */ private String fInitialLineDelimiter; /** * 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() { fModificationStamp = getNextModificationStamp(); } /** * Returns the document's text store. Assumes that the document has been initialized with a text store. * * @return the document's text store */ protected TextStore 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 LineTracker getTracker() { Assert.isNotNull(fTracker); return fTracker; } /** * Returns the document's document listeners. * * @return the document's document listeners */ protected List<Object> getDocumentListeners() { return Arrays.asList(fDocumentListeners.getListeners()); } /** * Returns the document's partitioning listeners. * * @return the document's partitioning listeners */ protected List getDocumentPartitioningListeners() { return Arrays.asList(fDocumentPartitioningListeners.getListeners()); } /** * Returns all positions managed by the document grouped by category. * * @return the document's positions */ protected Map<String, List<Position>> getDocumentManagedPositions() { return fPositions; } /* @see org.eclipse.jface.text.IDocument#getDocumentPartitioner() */ public DocumentPartitioner getDocumentPartitioner() { return getDocumentPartitioner(DEFAULT_PARTITIONING); } // --- implementation configuration interface ------------ /** * Sets the document's text store. Must be called at the beginning of the constructor. * * @param store * the document's text store */ protected void setTextStore(TextStore store) { fStore = store; } /** * Sets the document's line tracker. Must be called at the beginning of the constructor. * * @param tracker * the document's line tracker */ protected void setLineTracker(LineTracker tracker) { fTracker = tracker; } /* * @see org.eclipse.jface.text.IDocument#setDocumentPartitioner(org.eclipse.jface .text.IDocumentPartitioner) */ public void setDocumentPartitioner(DocumentPartitioner partitioner) { setDocumentPartitioner(DEFAULT_PARTITIONING, partitioner); } /** * 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<>(); fEndPositions = new HashMap<>(); fPositionUpdaters = new ArrayList<>(); fDocumentListeners = new ListenerList(ListenerList.IDENTITY); fPrenotifiedDocumentListeners = new ListenerList(ListenerList.IDENTITY); fDocumentPartitioningListeners = new ListenerList(ListenerList.IDENTITY); // fDocumentRewriteSessionListeners = new ArrayList(); addPositionCategory(DEFAULT_CATEGORY); addPositionUpdater(new DefaultPositionUpdater(DEFAULT_CATEGORY)); } // ------------------------------------------------------- /* * @see org.eclipse.jface.text.IDocument#addDocumentListener(org.eclipse.jface .text.IDocumentListener) */ public void addDocumentListener(DocumentListener listener) { Assert.isNotNull(listener); fDocumentListeners.add(listener); } /* * @see org.eclipse.jface.text.IDocument#removeDocumentListener(org.eclipse.jface .text.IDocumentListener) */ public void removeDocumentListener(DocumentListener listener) { Assert.isNotNull(listener); fDocumentListeners.remove(listener); } /* * @see org.eclipse.jface.text.IDocument#addPrenotifiedDocumentListener(org.eclipse .jface.text.IDocumentListener) */ public void addPrenotifiedDocumentListener(DocumentListener listener) { Assert.isNotNull(listener); fPrenotifiedDocumentListeners.add(listener); } /* * @see org.eclipse.jface.text.IDocument#removePrenotifiedDocumentListener(org .eclipse.jface.text.IDocumentListener) */ public void removePrenotifiedDocumentListener(DocumentListener listener) { Assert.isNotNull(listener); fPrenotifiedDocumentListeners.remove(listener); } /* * @see org.eclipse.jface.text.IDocument#addDocumentPartitioningListener(org.eclipse .jface.text.IDocumentPartitioningListener) */ public void addDocumentPartitioningListener(DocumentPartitioningListener listener) { Assert.isNotNull(listener); fDocumentPartitioningListeners.add(listener); } /* * @see org.eclipse.jface.text.IDocument#removeDocumentPartitioningListener(org * .eclipse.jface.text.IDocumentPartitioningListener) */ public void removeDocumentPartitioningListener(DocumentPartitioningListener listener) { Assert.isNotNull(listener); fDocumentPartitioningListeners.remove(listener); } /* * @see org.eclipse.jface.text.IDocument#addPosition(java.lang.String, org.eclipse.jface.text.Position) */ public void addPosition(String category, Position position) throws BadLocationException, BadPositionCategoryException { if (category == null) throw new BadPositionCategoryException(); if ((0 > position.offset) || (0 > position.length) || (position.offset + position.length > getLength())) throw new BadLocationException(); List<Position> list = fPositions.get(category); if (list == null) throw new BadPositionCategoryException(); list.add(computeIndexInPositionList(list, position.offset, true), position); List<Position> endPositions = fEndPositions.get(category); if (endPositions == null) throw new BadPositionCategoryException(); endPositions .add(computeIndexInPositionList(endPositions, position.offset + position.length - 1, false), position); } /* * @see org.eclipse.jface.text.IDocument#addPosition(org.eclipse.jface.text.Position ) */ public void addPosition(Position position) throws BadLocationException { try { addPosition(DEFAULT_CATEGORY, position); } catch (BadPositionCategoryException e) { } } /* * @see org.eclipse.jface.text.IDocument#addPositionCategory(java.lang.String) */ public void addPositionCategory(String category) { if (category == null) return; if (!containsPositionCategory(category)) { fPositions.put(category, new ArrayList<>()); fEndPositions.put(category, new ArrayList<>()); } } /* * @see org.eclipse.jface.text.IDocument#addPositionUpdater(org.eclipse.jface. text.IPositionUpdater) */ public void addPositionUpdater(PositionUpdater updater) { insertPositionUpdater(updater, fPositionUpdaters.size()); } /* * @see org.eclipse.jface.text.IDocument#containsPosition(java.lang.String, int, int) */ public boolean containsPosition(String category, int offset, int length) { if (category == null) return false; List<Position> list = fPositions.get(category); if (list == null) return false; int size = list.size(); if (size == 0) return false; int index = computeIndexInPositionList(list, offset, true); if (index < size) { Position p = list.get(index); while (p != null && p.offset == offset) { if (p.length == length) return true; ++index; p = (index < size) ? list.get(index) : null; } } return false; } /* * @see org.eclipse.jface.text.IDocument#containsPositionCategory(java.lang.String ) */ public boolean containsPositionCategory(String category) { return category != null && fPositions.containsKey(category); } /** * Computes the index in the list of positions at which a position with the given position would be inserted. The position to * insert is supposed to become the first in this list of all positions with the same position. * * @param positions * the list in which the index is computed * @param offset * the offset for which the index is computed * @param orderedByOffset * <code>true</code> if ordered by offset, false if ordered by end position * @return the computed index * @since 3.4 */ protected int computeIndexInPositionList(List<Position> positions, int offset, boolean orderedByOffset) { if (positions.size() == 0) return 0; int left = 0; int right = positions.size() - 1; int mid = 0; Position p; while (left < right) { mid = (left + right) / 2; p = positions.get(mid); int pOffset = getOffset(orderedByOffset, p); if (offset < pOffset) { if (left == mid) right = left; else right = mid - 1; } else if (offset > pOffset) { if (right == mid) left = right; else left = mid + 1; } else if (offset == pOffset) { left = right = mid; } } int pos = left; p = positions.get(pos); int pPosition = getOffset(orderedByOffset, p); if (offset > pPosition) { // append to the end pos++; } else { // entry will become the first of all entries with the same offset do { --pos; if (pos < 0) break; p = positions.get(pos); pPosition = getOffset(orderedByOffset, p); } while (offset == pPosition); ++pos; } Assert.isTrue(0 <= pos && pos <= positions.size()); return pos; } /* @since 3.4 */ private int getOffset(boolean orderedByOffset, Position position) { if (orderedByOffset || position.getLength() == 0) return position.getOffset(); return position.getOffset() + position.getLength() - 1; } /* * @see org.eclipse.jface.text.IDocument#computeIndexInCategory(java.lang.String, int) */ public int computeIndexInCategory(String category, int offset) throws BadLocationException, BadPositionCategoryException { if (0 > offset || offset > getLength()) throw new BadLocationException(); List<Position> c = fPositions.get(category); if (c == null) throw new BadPositionCategoryException(); return computeIndexInPositionList(c, offset, true); } /** * Fires the document partitioning changed notification to all registered document partitioning listeners. Uses a robust * iterator. * * @param event * the document partitioning changed event * @see DocumentPartitioningListener * @since 3.0 */ protected void fireDocumentPartitioningChanged(DocumentPartitioningChangedEvent event) { if (fDocumentPartitioningListeners == null) return; Object[] listeners = fDocumentPartitioningListeners.getListeners(); for (Object listener : listeners) { DocumentPartitioningListener l = (DocumentPartitioningListener) listener; try { l.documentPartitioningChanged(event); } catch (Exception ex) { fail(ex); } } } /** * 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) { if (fDocumentPartitioners != null) { for (DocumentPartitioner p : fDocumentPartitioners.values()) { try { p.documentAboutToBeChanged(event); } catch (Exception ex) { fail(ex); } } } aboutToBeChangedForAll(event, fPrenotifiedDocumentListeners.getListeners()); aboutToBeChangedForAll(event, fDocumentListeners.getListeners()); } private void aboutToBeChangedForAll(DocumentEvent event, Object[] listeners) { for (Object listener : listeners) { try { ((DocumentListener) listener).documentAboutToBeChanged(event); } catch (Exception ex) { fail(ex); } } } /** * 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) { if (fDocumentPartitioners != null) { fDocumentPartitioningChangedEvent = new DocumentPartitioningChangedEvent(this); for (String partitioning : fDocumentPartitioners.keySet()) { DocumentPartitioner partitioner = fDocumentPartitioners.get(partitioning); if (partitioner.documentChanged(event)) fDocumentPartitioningChangedEvent .setPartitionChange(partitioning, 0, event.getDocument().getLength()); } } if (fPositions.size() > 0) updatePositions(event); } /** * Notifies all listeners about the given document change. * <p/> * Executes all registered post notification replace operation. * <p/> * * @param event * the event to be sent out */ protected void doFireDocumentChanged(DocumentEvent event) { DocumentPartitioningChangedEvent p = fDocumentPartitioningChangedEvent; fDocumentPartitioningChangedEvent = null; if (p != null && !p.isEmpty()) fireDocumentPartitioningChanged(p); documentChangedForAll(event, fPrenotifiedDocumentListeners.getListeners()); documentChangedForAll(event, fDocumentListeners.getListeners()); } private void documentChangedForAll(DocumentEvent event, Object[] listeners) { for (Object listener : listeners) { try { ((DocumentListener) listener).documentChanged(event); } catch (Exception ex) { fail(ex); } } } /** * Updates the internal document structures and informs all document listeners if listener notification has been enabled. * Otherwise it remembers the event to be sent to the listeners on resume. * * @param event * the document event to be sent out */ protected void fireDocumentChanged(DocumentEvent event) { updateDocumentStructures(event); if (fStoppedListenerNotification == 0) doFireDocumentChanged(event); else fDeferredDocumentEvent = event; } /* @see org.eclipse.jface.text.IDocument#getChar(int) */ public char getChar(int pos) throws BadLocationException { if ((0 > pos) || (pos >= getLength())) throw new BadLocationException(); return getStore().get(pos); } /* @see org.eclipse.jface.text.IDocument#getContentType(int) */ public String getContentType(int offset) throws BadLocationException { String contentType = null; try { contentType = getContentType(DEFAULT_PARTITIONING, offset, false); Assert.isNotNull(contentType); } catch (BadPartitioningException e) { Assert.isTrue(false); } return contentType; } /* @see org.eclipse.jface.text.IDocument#getLegalContentTypes() */ public String[] getLegalContentTypes() { String[] contentTypes = null; try { contentTypes = getLegalContentTypes(DEFAULT_PARTITIONING); Assert.isNotNull(contentTypes); } catch (BadPartitioningException e) { Assert.isTrue(false); } return contentTypes; } /* @see org.eclipse.jface.text.IDocument#getLength() */ public int getLength() { return getStore().getLength(); } /* @see org.eclipse.jface.text.IDocument#getLineDelimiter(int) */ public String getLineDelimiter(int line) throws BadLocationException { return getTracker().getLineDelimiter(line); } /* @see org.eclipse.jface.text.IDocument#getLegalLineDelimiters() */ public String[] getLegalLineDelimiters() { return getTracker().getLegalLineDelimiters(); } /* * @see org.eclipse.jface.text.IDocumentExtension4#getDefaultLineDelimiter() * @since 3.1 */ public String getDefaultLineDelimiter() { String lineDelimiter = null; try { lineDelimiter = getLineDelimiter(0); } catch (BadLocationException x) { } if (lineDelimiter != null) return lineDelimiter; if (fInitialLineDelimiter != null) return fInitialLineDelimiter; String sysLineDelimiter = "\n"; //$NON-NLS-1$ String[] delimiters = getLegalLineDelimiters(); Assert.isTrue(delimiters.length > 0); for (String delimiter : delimiters) { if (delimiter.equals(sysLineDelimiter)) { lineDelimiter = sysLineDelimiter; break; } } if (lineDelimiter == null) lineDelimiter = delimiters[0]; return lineDelimiter; } /* * @see org.eclipse.jface.text.IDocumentExtension4#setInitialLineDelimiter(java .lang.String) * @since 3.1 */ public void setInitialLineDelimiter(String lineDelimiter) { Assert.isNotNull(lineDelimiter); fInitialLineDelimiter = lineDelimiter; } /* @see org.eclipse.jface.text.IDocument#getLineLength(int) */ public int getLineLength(int line) throws BadLocationException { return getTracker().getLineLength(line); } /* @see org.eclipse.jface.text.IDocument#getLineOfOffset(int) */ public int getLineOfOffset(int pos) throws BadLocationException { return getTracker().getLineNumberOfOffset(pos); } /* @see org.eclipse.jface.text.IDocument#getLineOffset(int) */ public int getLineOffset(int line) throws BadLocationException { return getTracker().getLineOffset(line); } /* @see org.eclipse.jface.text.IDocument#getLineInformation(int) */ public Region getLineInformation(int line) throws BadLocationException { return getTracker().getLineInformation(line); } /* @see org.eclipse.jface.text.IDocument#getLineInformationOfOffset(int) */ public Region getLineInformationOfOffset(int offset) throws BadLocationException { return getTracker().getLineInformationOfOffset(offset); } /* @see org.eclipse.jface.text.IDocument#getNumberOfLines() */ public int getNumberOfLines() { return getTracker().getNumberOfLines(); } /* @see org.eclipse.jface.text.IDocument#getNumberOfLines(int, int) */ public int getNumberOfLines(int offset, int length) throws BadLocationException { return getTracker().getNumberOfLines(offset, length); } /* * @see org.eclipse.jface.text.IDocument#computeNumberOfLines(java.lang.String) */ public int computeNumberOfLines(String text) { return getTracker().computeNumberOfLines(text); } /* @see org.eclipse.jface.text.IDocument#getPartition(int) */ public TypedRegion getPartition(int offset) throws BadLocationException { TypedRegion partition = null; try { partition = getPartition(DEFAULT_PARTITIONING, offset, false); Assert.isNotNull(partition); } catch (BadPartitioningException e) { Assert.isTrue(false); } return partition; } /* @see org.eclipse.jface.text.IDocument#computePartitioning(int, int) */ public TypedRegion[] computePartitioning(int offset, int length) throws BadLocationException { TypedRegion[] partitioning = null; try { partitioning = computePartitioning(DEFAULT_PARTITIONING, offset, length, false); Assert.isNotNull(partitioning); } catch (BadPartitioningException e) { Assert.isTrue(false); } return partitioning; } /* @see org.eclipse.jface.text.IDocument#getPositions(java.lang.String) */ public Position[] getPositions(String category) throws BadPositionCategoryException { if (category == null) throw new BadPositionCategoryException(); List<Position> c = fPositions.get(category); if (c == null) throw new BadPositionCategoryException(); Position[] positions = new Position[c.size()]; c.toArray(positions); return positions; } /* @see org.eclipse.jface.text.IDocument#getPositionCategories() */ public String[] getPositionCategories() { String[] categories = new String[fPositions.size()]; Iterator<String> keys = fPositions.keySet().iterator(); for (int i = 0; i < categories.length; i++) categories[i] = keys.next(); return categories; } /* @see org.eclipse.jface.text.IDocument#getPositionUpdaters() */ public PositionUpdater[] getPositionUpdaters() { PositionUpdater[] updaters = new PositionUpdater[fPositionUpdaters.size()]; fPositionUpdaters.toArray(updaters); return updaters; } /* @see org.eclipse.jface.text.IDocument#get() */ public String get() { return getStore().get(0, getLength()); } /* @see org.eclipse.jface.text.IDocument#get(int, int) */ 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 org.eclipse.jface.text.IDocument#insertPositionUpdater(org.eclipse.jface .text.IPositionUpdater, int) */ public void insertPositionUpdater(PositionUpdater 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 org.eclipse.jface.text.IDocument#removePosition(java.lang.String, org.eclipse.jface.text.Position) */ public void removePosition(String category, Position position) throws BadPositionCategoryException { if (position == null) return; if (category == null) throw new BadPositionCategoryException(); List<Position> c = fPositions.get(category); if (c == null) throw new BadPositionCategoryException(); removeFromPositionsList(c, position, true); List<Position> endPositions = fEndPositions.get(category); if (endPositions == null) throw new BadPositionCategoryException(); removeFromPositionsList(endPositions, position, false); } /** * Remove the given position form the given list of positions based on identity not equality. * * @param positions * a list of positions * @param position * the position to remove * @param orderedByOffset * true if <code>positions</code> is ordered by offset, false if ordered by end position * @since 3.4 */ private void removeFromPositionsList(List<Position> positions, Position position, boolean orderedByOffset) { int size = positions.size(); // Assume position is somewhere near it was before int index = computeIndexInPositionList(positions, orderedByOffset ? position.offset : position.offset + position.length - 1, orderedByOffset); if (index < size && positions.get(index) == position) { positions.remove(index); return; } int back = index - 1; int forth = index + 1; while (back >= 0 || forth < size) { if (back >= 0) { if (position == positions.get(back)) { positions.remove(back); return; } back--; } if (forth < size) { if (position == positions.get(forth)) { positions.remove(forth); return; } forth++; } } } /* * @see org.eclipse.jface.text.IDocument#removePosition(org.eclipse.jface.text .Position) */ public void removePosition(Position position) { try { removePosition(DEFAULT_CATEGORY, position); } catch (BadPositionCategoryException e) { } } /* * @see org.eclipse.jface.text.IDocument#removePositionCategory(java.lang.String) */ public void removePositionCategory(String category) throws BadPositionCategoryException { if (category == null) return; if (!containsPositionCategory(category)) throw new BadPositionCategoryException(); fPositions.remove(category); fEndPositions.remove(category); } /* * @see org.eclipse.jface.text.IDocument#removePositionUpdater(org.eclipse.jface .text.IPositionUpdater) */ public void removePositionUpdater(PositionUpdater updater) { for (int i = fPositionUpdaters.size() - 1; i >= 0; i--) { if (fPositionUpdaters.get(i) == updater) { fPositionUpdaters.remove(i); return; } } } private long getNextModificationStamp() { if (fNextModificationStamp == Long.MAX_VALUE || fNextModificationStamp == UNKNOWN_MODIFICATION_STAMP) fNextModificationStamp = 0; else fNextModificationStamp = fNextModificationStamp + 1; return fNextModificationStamp; } /* * @see org.eclipse.jface.text.IDocumentExtension4#getModificationStamp() */ public long getModificationStamp() { return fModificationStamp; } /* * @see org.eclipse.jface.text.IDocument#replace(int, int, java.lang.String) */ public void replace(int pos, int length, String text, long modificationStamp) 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); fModificationStamp = modificationStamp; fNextModificationStamp = Math.max(fModificationStamp, fNextModificationStamp); e.fModificationStamp = fModificationStamp; fireDocumentChanged(e); } /** * {@inheritDoc} * * @since 3.4 */ public boolean isLineInformationRepairNeeded(int offset, int length, String text) throws BadLocationException { return false; } /* @see org.eclipse.jface.text.IDocument#replace(int, int, java.lang.String) */ public void replace(int pos, int length, String text) throws BadLocationException { if (length == 0 && (text == null || text.length() == 0)) replace(pos, length, text, getModificationStamp()); else replace(pos, length, text, getNextModificationStamp()); } /* @see org.eclipse.jface.text.IDocument#set(java.lang.String) */ public void set(String text) { set(text, getNextModificationStamp()); } /* * @see org.eclipse.jface.text.IDocumentExtension4#set(java.lang.String, long) * @since 3.1 */ public void set(String text, long modificationStamp) { int length = getStore().getLength(); DocumentEvent e = new DocumentEvent(this, 0, length, text); fireDocumentAboutToBeChanged(e); getStore().set(text); getTracker().set(text); fModificationStamp = modificationStamp; fNextModificationStamp = Math.max(fModificationStamp, fNextModificationStamp); e.fModificationStamp = fModificationStamp; 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. * * @param event * the document event describing the change to which to adapt the positions */ protected void updatePositions(DocumentEvent event) { for (PositionUpdater u : fPositionUpdaters) { u.update(event); } } /* * @see org.eclipse.jface.text.IDocumentExtension2#acceptPostNotificationReplaces () * @since 2.1 */ public void acceptPostNotificationReplaces() { fAcceptPostNotificationReplaces = true; } /* * @see org.eclipse.jface.text.IDocumentExtension2#ignorePostNotificationReplaces () * @since 2.1 */ public void ignorePostNotificationReplaces() { fAcceptPostNotificationReplaces = false; } /* * @see org.eclipse.jface.text.IDocumentExtension#stopPostNotificationProcessing() * @since 2.0 */ public void stopPostNotificationProcessing() { ++fStoppedCount; } /** * {@inheritDoc} * * @since 2.0 * @deprecated since 3.1. Use {@link IDocumentExtension4#startRewriteSession(DocumentRewriteSessionType)} instead. */ public void startSequentialRewrite(boolean normalized) { } /** * {@inheritDoc} * * @since 2.0 * @deprecated As of 3.1, replaced by {@link IDocumentExtension4#stopRewriteSession(DocumentRewriteSession)} */ public void stopSequentialRewrite() { } /* * @see org.eclipse.jface.text.IDocumentExtension2#resumeListenerNotification() * @since 2.1 */ public void resumeListenerNotification() { --fStoppedListenerNotification; if (fStoppedListenerNotification == 0) { resumeDocumentListenerNotification(); } } /* * @see org.eclipse.jface.text.IDocumentExtension2#stopListenerNotification() * @since 2.1 */ public void stopListenerNotification() { ++fStoppedListenerNotification; } /** * Resumes the document listener notification by sending out the remembered partition changed and document event. * * @since 2.1 */ private void resumeDocumentListenerNotification() { if (fDeferredDocumentEvent != null) { DocumentEvent event = fDeferredDocumentEvent; fDeferredDocumentEvent = null; doFireDocumentChanged(event); } } /* * @see org.eclipse.jface.text.IDocumentExtension3#computeZeroLengthPartitioning (java.lang.String, int, int) * @since 3.0 */ public TypedRegion[] computePartitioning(String partitioning, int offset, int length, boolean includeZeroLengthPartitions) throws BadLocationException, BadPartitioningException { if ((0 > offset) || (0 > length) || (offset + length > getLength())) throw new BadLocationException(); DocumentPartitioner partitioner = getDocumentPartitioner(partitioning); // // if (partitioner instanceof IDocumentPartitionerExtension2) // { // checkStateOfPartitioner(partitioner, partitioning); // return ((IDocumentPartitionerExtension2)partitioner).computePartitioning(offset, length, // includeZeroLengthPartitions); // } // else if (partitioner != null) { checkStateOfPartitioner(partitioner, partitioning); return partitioner.computePartitioning(offset, length); } else if (DEFAULT_PARTITIONING.equals(partitioning)) return new TypedRegionImpl[]{new TypedRegionImpl(offset, length, DEFAULT_CONTENT_TYPE)}; else throw new BadPartitioningException(); } /* * @see org.eclipse.jface.text.IDocumentExtension3#getZeroLengthContentType(java .lang.String, int) * @since 3.0 */ public String getContentType(String partitioning, int offset, boolean preferOpenPartitions) throws BadLocationException, BadPartitioningException { if ((0 > offset) || (offset > getLength())) throw new BadLocationException(); DocumentPartitioner partitioner = getDocumentPartitioner(partitioning); // if (partitioner instanceof IDocumentPartitionerExtension2) // { // checkStateOfPartitioner(partitioner, partitioning); // return ((IDocumentPartitionerExtension2)partitioner).getContentType(offset, preferOpenPartitions); // } // else if (partitioner != null) { checkStateOfPartitioner(partitioner, partitioning); return partitioner.getContentType(offset); } else if (DEFAULT_PARTITIONING.equals(partitioning)) return DEFAULT_CONTENT_TYPE; else throw new BadPartitioningException(); } /* * @see org.eclipse.jface.text.IDocumentExtension3#getDocumentPartitioner(java .lang.String) * @since 3.0 */ public DocumentPartitioner getDocumentPartitioner(String partitioning) { return fDocumentPartitioners.get(partitioning); } /* * @see org.eclipse.jface.text.IDocumentExtension3#getLegalContentTypes(java.lang .String) * @since 3.0 */ public String[] getLegalContentTypes(String partitioning) throws BadPartitioningException { DocumentPartitioner partitioner = getDocumentPartitioner(partitioning); if (partitioner != null) return partitioner.getLegalContentTypes(); if (DEFAULT_PARTITIONING.equals(partitioning)) return new String[]{DEFAULT_CONTENT_TYPE}; throw new BadPartitioningException(); } /* * @see org.eclipse.jface.text.IDocumentExtension3#getZeroLengthPartition(java .lang.String, int) * @since 3.0 */ public TypedRegion getPartition(String partitioning, int offset, boolean preferOpenPartitions) throws BadLocationException, BadPartitioningException { if ((0 > offset) || (offset > getLength())) throw new BadLocationException(); DocumentPartitioner partitioner = getDocumentPartitioner(partitioning); // if (partitioner instanceof IDocumentPartitionerExtension2) // { // checkStateOfPartitioner(partitioner, partitioning); // return ((IDocumentPartitionerExtension2)partitioner).getPartition(offset, preferOpenPartitions); // } // else if (partitioner != null) { checkStateOfPartitioner(partitioner, partitioning); return partitioner.getPartition(offset); } else if (DEFAULT_PARTITIONING.equals(partitioning)) return new TypedRegionImpl(0, getLength(), DEFAULT_CONTENT_TYPE); else throw new BadPartitioningException(); } /* * @see org.eclipse.jface.text.IDocumentExtension3#getPartitionings() * @since 3.0 */ public String[] getPartitionings() { if (fDocumentPartitioners == null) return new String[0]; String[] partitionings = new String[fDocumentPartitioners.size()]; fDocumentPartitioners.keySet().toArray(partitionings); return partitionings; } /* * @see org.eclipse.jface.text.IDocumentExtension3#setDocumentPartitioner(java .lang.String, * org.eclipse.jface.text.IDocumentPartitioner) * @since 3.0 */ public void setDocumentPartitioner(String partitioning, DocumentPartitioner partitioner) { if (partitioner == null) { if (fDocumentPartitioners != null) { fDocumentPartitioners.remove(partitioning); if (fDocumentPartitioners.size() == 0) fDocumentPartitioners = null; } } else { if (fDocumentPartitioners == null) fDocumentPartitioners = new HashMap<>(); fDocumentPartitioners.put(partitioning, partitioner); } DocumentPartitioningChangedEvent event = new DocumentPartitioningChangedEvent(this); event.setPartitionChange(partitioning, 0, getLength()); fireDocumentPartitioningChanged(event); } /* * @see org.eclipse.jface.text.IRepairableDocument#repairLineInformation() * @since 3.0 */ public void repairLineInformation() { getTracker().set(get()); } // /** // * Fires the given event to all registered rewrite session listeners. Uses robust iterators. // * // * @param event the event to be fired // * @since 3.1 // */ // protected void fireRewriteSessionChanged(DocumentRewriteSessionEvent event) // { // if (fDocumentRewriteSessionListeners.size() > 0) // { // List list = new ArrayList(fDocumentRewriteSessionListeners); // Iterator e = list.iterator(); // while (e.hasNext()) // { // try // { // IDocumentRewriteSessionListener l = (IDocumentRewriteSessionListener)e.next(); // l.documentRewriteSessionChanged(event); // } // catch (Exception ex) // { // log(ex); // } // } // } // } // /* // * @see org.eclipse.jface.text.IDocumentExtension4#getActiveRewriteSession() // */ // public final DocumentRewriteSession getActiveRewriteSession() // { // return fDocumentRewriteSession; // } // // /* // * @see org.eclipse.jface.text.IDocumentExtension4#startRewriteSession(org.eclipse.jface.text.DocumentRewriteSessionType) // * @since 3.1 // */ // public DocumentRewriteSession startRewriteSession(DocumentRewriteSessionType sessionType) // { // // if (getActiveRewriteSession() != null) // throw new IllegalStateException(); // // fDocumentRewriteSession = new DocumentRewriteSession(sessionType); // if (DEBUG) // System.out.println("AbstractDocument: Starting rewrite session: " + fDocumentRewriteSession); //$NON-NLS-1$ // // fireRewriteSessionChanged(new DocumentRewriteSessionEvent(this, fDocumentRewriteSession, // DocumentRewriteSessionEvent.SESSION_START)); // // startRewriteSessionOnPartitioners(fDocumentRewriteSession); // // ILineTracker tracker = getTracker(); // if (tracker instanceof ILineTrackerExtension) // { // ILineTrackerExtension extension = (ILineTrackerExtension)tracker; // extension.startRewriteSession(fDocumentRewriteSession); // } // // if (DocumentRewriteSessionType.SEQUENTIAL == sessionType) // startSequentialRewrite(false); // else if (DocumentRewriteSessionType.STRICTLY_SEQUENTIAL == sessionType) // startSequentialRewrite(true); // // return fDocumentRewriteSession; // } // /** // * Starts the given rewrite session. // * // * @param session the rewrite session // * @since 3.1 // */ // protected final void startRewriteSessionOnPartitioners(DocumentRewriteSession session) // { // if (fDocumentPartitioners != null) // { // Iterator e = fDocumentPartitioners.values().iterator(); // while (e.hasNext()) // { // Object partitioner = e.next(); // if (partitioner instanceof IDocumentPartitionerExtension3) // { // IDocumentPartitionerExtension3 extension = (IDocumentPartitionerExtension3)partitioner; // extension.startRewriteSession(session); // } // } // } // } // // /* // * @see org.eclipse.jface.text.IDocumentExtension4#stopRewriteSession(org.eclipse.jface.text.DocumentRewriteSession) // * @since 3.1 // */ // public void stopRewriteSession(DocumentRewriteSession session) // { // if (fDocumentRewriteSession == session) // { // // if (DEBUG) // System.out.println("AbstractDocument: Stopping rewrite session: " + session); //$NON-NLS-1$ // // DocumentRewriteSessionType sessionType = session.getSessionType(); // if (DocumentRewriteSessionType.SEQUENTIAL == sessionType // || DocumentRewriteSessionType.STRICTLY_SEQUENTIAL == sessionType) // stopSequentialRewrite(); // // ILineTracker tracker = getTracker(); // if (tracker instanceof ILineTrackerExtension) // { // ILineTrackerExtension extension = (ILineTrackerExtension)tracker; // extension.stopRewriteSession(session, get()); // } // // stopRewriteSessionOnPartitioners(fDocumentRewriteSession); // // fDocumentRewriteSession = null; // fireRewriteSessionChanged(new DocumentRewriteSessionEvent(this, session, // DocumentRewriteSessionEvent.SESSION_STOP)); // } // } // // /** // * Stops the given rewrite session. // * // * @param session the rewrite session // * @since 3.1 // */ // protected final void stopRewriteSessionOnPartitioners(DocumentRewriteSession session) // { // if (fDocumentPartitioners != null) // { // DocumentPartitioningChangedEvent event = new DocumentPartitioningChangedEvent(this); // Iterator e = fDocumentPartitioners.keySet().iterator(); // while (e.hasNext()) // { // String partitioning = (String)e.next(); // IDocumentPartitioner partitioner = (IDocumentPartitioner)fDocumentPartitioners.get(partitioning); // if (partitioner instanceof IDocumentPartitionerExtension3) // { // IDocumentPartitionerExtension3 extension = (IDocumentPartitionerExtension3)partitioner; // extension.stopRewriteSession(session); // event.setPartitionChange(partitioning, 0, getLength()); // } // } // if (!event.isEmpty()) // fireDocumentPartitioningChanged(event); // } // } // /* // * @see // org.eclipse.jface.text.IDocumentExtension4#addDocumentRewriteSessionListener(org.eclipse.jface.text.IDocumentRewriteSessionListener) // * @since 3.1 // */ // public void addDocumentRewriteSessionListener(IDocumentRewriteSessionListener listener) // { // Assert.isNotNull(listener); // if (!fDocumentRewriteSessionListeners.contains(listener)) // fDocumentRewriteSessionListeners.add(listener); // } // // /* // * @see // org.eclipse.jface.text.IDocumentExtension4#removeDocumentRewriteSessionListener(org.eclipse.jface.text // .IDocumentRewriteSessionListener) // * @since 3.1 // */ // public void removeDocumentRewriteSessionListener(IDocumentRewriteSessionListener listener) // { // Assert.isNotNull(listener); // fDocumentRewriteSessionListeners.remove(listener); // } /** * Checks the state for the given partitioner and stops the active rewrite session. * * @param partitioner * the document partitioner to be checked * @param partitioning * the document partitioning the partitioner is registered for * @since 3.1 */ protected final void checkStateOfPartitioner(DocumentPartitioner partitioner, String partitioning) { // if (!(partitioner instanceof IDocumentPartitionerExtension3)) // return; // // IDocumentPartitionerExtension3 extension = (IDocumentPartitionerExtension3)partitioner; // DocumentRewriteSession session = extension.getActiveRewriteSession(); // if (session != null) // { // extension.stopRewriteSession(session); // // if (DEBUG) // System.out.println("AbstractDocument: Flushing rewrite session for partition type: " + partitioning); //$NON-NLS-1$ // // DocumentPartitioningChangedEvent event = new DocumentPartitioningChangedEvent(this); // event.setPartitionChange(partitioning, 0, getLength()); // fireDocumentPartitioningChanged(event); // } } /** {@inheritDoc} */ @Override public Position[] getPositions(String category, int offset, int length, boolean canStartBefore, boolean canEndAfter) throws BadPositionCategoryException { if (canStartBefore && canEndAfter || (!canStartBefore && !canEndAfter)) { List<Position> documentPositions; if (canStartBefore) { if (offset < getLength() / 2) { documentPositions = getStartingPositions(category, 0, offset + length); } else { documentPositions = getEndingPositions(category, offset, getLength() - offset + 1); } } else { documentPositions = getStartingPositions(category, offset, length); } ArrayList<Position> list = new ArrayList<Position>(documentPositions.size()); Position region = new Position(offset, length); for (Position position : documentPositions) { if (isWithinRegion(region, position, canStartBefore, canEndAfter)) { list.add(position); } } Position[] positions = new Position[list.size()]; list.toArray(positions); return positions; } else if (canStartBefore) { List<Position> list = getEndingPositions(category, offset, length); Position[] positions = new Position[list.size()]; list.toArray(positions); return positions; } else { List<Position> list = getStartingPositions(category, offset, length); Position[] positions = new Position[list.size()]; list.toArray(positions); return positions; } } /* @since 3.4 */ private boolean isWithinRegion(Position region, Position position, boolean canStartBefore, boolean canEndAfter) { if (canStartBefore && canEndAfter) { return region.overlapsWith(position.getOffset(), position.getLength()); } else if (canStartBefore) { return region.includes(position.getOffset() + position.getLength() - 1); } else if (canEndAfter) { return region.includes(position.getOffset()); } else { int start = position.getOffset(); return region.includes(start) && region.includes(start + position.getLength() - 1); } } /** * A list of positions in the given category with an offset inside the given region. The order of the positions is arbitrary. * * @param category * the position category * @param offset * the offset of the region * @param length * the length of the region * @return a list of the positions in the region * @throws BadPositionCategoryException * if category is undefined in this document */ private List<Position> getStartingPositions(String category, int offset, int length) throws BadPositionCategoryException { List<Position> positions = fPositions.get(category); if (positions == null) throw new BadPositionCategoryException(); int indexStart = computeIndexInPositionList(positions, offset, true); int indexEnd = computeIndexInPositionList(positions, offset + length, true); return positions.subList(indexStart, indexEnd); } /** * A list of positions in the given category with an end position inside the given region. The order of the positions is * arbitrary. * * @param category * the position category * @param offset * the offset of the region * @param length * the length of the region * @return a list of the positions in the region * @throws BadPositionCategoryException * if category is undefined in this document * @since 3.4 */ private List<Position> getEndingPositions(String category, int offset, int length) throws BadPositionCategoryException { List<Position> positions = fEndPositions.get(category); if (positions == null) throw new BadPositionCategoryException(); int indexStart = computeIndexInPositionList(positions, offset, false); int indexEnd = computeIndexInPositionList(positions, offset + length, false); return positions.subList(indexStart, indexEnd); } /** * Logs the given exception by reusing the code in {@link SafeRunner}. * * @param ex * the exception * @since 3.6 */ private static void fail(final Exception ex) { throw new JavaScriptException(ex); } }