/*******************************************************************************
* 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.text.BadLocationException;
import org.eclipse.che.ide.api.text.BadPositionCategoryException;
import org.eclipse.che.ide.api.text.Position;
import org.eclipse.che.ide.api.text.Region;
import org.eclipse.che.ide.api.text.TypedRegion;
/**
* An <code>Document</code> represents text providing support for
* <ul>
* <li>text manipulation
* <li>positions
* <li>partitions
* <li>line information
* <li>document change listeners
* <li>document partition change listeners
* </ul>
* <p/>
* A document allows to set its content and to manipulate it. For manipulation a document provides the <code>replace</code> method
* which substitutes a given string for a specified text range in the document. On each document change, all registered document
* listeners are informed exactly once.
* <p>
* Positions are stickers to the document's text that are updated when the document is changed. Positions are updated by
* {@link PositionUpdater}s. Position updaters are managed as a list. The list defines the sequence in
* which position updaters are invoked. This way, position updaters may rely on each other. Positions are grouped into categories.
* A category is a ordered list of positions. the document defines the order of position in a category based on the position's
* offset based on the implementation of the method <code>computeIndexInCategory</code>. Each document must support a default
* position category whose name is specified by this interface.
* </p>
* <p>
* A document can be considered consisting of a sequence of not overlapping partitions. A partition is defined by its offset, its
* length, and its type. Partitions are updated on every document manipulation and ensured to be up-to-date when the document
* listeners are informed. A document uses an <code>DocumentPartitioner</code> to manage its partitions. A document may be
* unpartitioned which happens when there is no partitioner. In this case, the document is considered as one single partition of a
* default type. The default type is specified by this interface. If a document change changes the document's partitioning all
* registered partitioning listeners are informed exactly once. Each partitioning has an id which must be used to refer to a
* particular partitioning.
* </p>
* <p>
* An <code>Document</code> provides methods to map line numbers and character positions onto each other based on the document's
* line delimiters. When moving text between documents using different line delimiters, the text must be converted to use the
* target document's line delimiters.
* </p>
* <p>
* An <code>Document</code> does not care about mixed line delimiters. Clients who want to ensure a single line delimiter in
* their document should use the line delimiter returned by
* {@link TextUtilities#getDefaultLineDelimiter(Document)} .
* </p>
* <p>
* <code>Document</code> throws <code>BadLocationException</code> if the parameters of queries or manipulation requests are not
* inside the bounds of the document.
* </p>
* <p>
* Clients may implement this interface and its extension interfaces or use the default implementation provided by
* <code>AbstractDocument</code> and <code>DocumentImpl</code>.
* </p>
*/
public interface Document {
/** The identifier of the default position category. */
final static String DEFAULT_CATEGORY = "__dflt_position_category"; //$NON-NLS-1$
/** The identifier of the default partition content type. */
final static String DEFAULT_CONTENT_TYPE = "__dftl_partition_content_type"; //$NON-NLS-1$
/** The identifier of the default partitioning. */
final static String DEFAULT_PARTITIONING = "__dftl_partitioning"; //$NON-NLS-1$
/** The unknown modification stamp. */
long UNKNOWN_MODIFICATION_STAMP = -1;
/**
* Returns the modification stamp of this document. The modification stamp
* is updated each time a modifying operation is called on this document. If
* two modification stamps of the same document are identical then the document
* content is too, however, same content does not imply same modification stamp.
* <p>
* The magnitude or sign of the numerical difference between two modification stamps
* is not significant.
* </p>
*
* @return the modification stamp of this document or <code>UNKNOWN_MODIFICATION_STAMP</code>
*/
long getModificationStamp();
/**
* Substitutes the given text for the specified document range.
* Sends a <code>DocumentEvent</code> to all registered <code>DocumentListener</code>.
*
* @param offset
* the document offset
* @param length
* the length of the specified range
* @param text
* the substitution text
* @param modificationStamp
* of the document after replacing
* @throws BadLocationException
* if the offset is invalid in this document
* @see DocumentEvent
* @see DocumentListener
*/
void replace(int offset, int length, String text, long modificationStamp) throws BadLocationException;
/**
* Returns the existing partitionings for this document. This includes the default partitioning.
*
* @return the existing partitionings for this document
*/
String[] getPartitionings();
/**
* Returns the set of legal content types of document partitions for the given partitioning This set can be empty. The set can
* contain more content types than contained by the result of <code>getPartitioning(partitioning, 0, getLength())</code>.
*
* @param partitioning
* the partitioning for which to return the legal content types
* @return the set of legal content types
* @throws BadPartitioningException
* if partitioning is invalid for this document
*/
String[] getLegalContentTypes(String partitioning) throws BadPartitioningException;
/**
* Returns the type of the document partition containing the given offset for the given partitioning. This is a convenience
* method for <code>getPartition(partitioning, offset, boolean).getType()</code>.
* <p>
* If <code>preferOpenPartitions</code> is <code>true</code>, precedence is given to an open partition ending at
* <code>offset</code> over a delimited partition starting at <code>offset</code>. If it is <code>false</code>, precedence is
* given to the partition that does not end at <code>offset</code>.
* </p>
* This is only supported if the connected <code>DocumentPartitioner</code> supports it, i.e. implements
* <code>DocumentPartitionerExtension2</code>. Otherwise, <code>preferOpenPartitions</code> is ignored. </p>
*
* @param partitioning
* the partitioning
* @param offset
* the document offset
* @param preferOpenPartitions
* <code>true</code> if precedence should be given to a open partition ending at
* <code>offset</code> over a closed partition starting at <code>offset</code>
* @return the partition type
* @throws BadLocationException
* if offset is invalid in this document
* @throws BadPartitioningException
* if partitioning is invalid for this document
*/
String getContentType(String partitioning, int offset, boolean preferOpenPartitions) throws BadLocationException,
BadPartitioningException;
/**
* Returns the document partition of the given partitioning in which the given offset is located.
* <p>
* If <code>preferOpenPartitions</code> is <code>true</code>, precedence is given to an open partition ending at
* <code>offset</code> over a delimited partition starting at <code>offset</code>. If it is <code>false</code>, precedence is
* given to the partition that does not end at <code>offset</code>.
* </p>
* This is only supported if the connected <code>DocumentPartitioner</code> supports it, i.e. implements
* <code>DocumentPartitionerExtension2</code>. Otherwise, <code>preferOpenPartitions</code> is ignored. </p>
*
* @param partitioning
* the partitioning
* @param offset
* the document offset
* @param preferOpenPartitions
* <code>true</code> if precedence should be given to a open partition ending at
* <code>offset</code> over a closed partition starting at <code>offset</code>
* @return a specification of the partition
* @throws BadLocationException
* if offset is invalid in this document
* @throws BadPartitioningException
* if partitioning is invalid for this document
*/
TypedRegion getPartition(String partitioning, int offset, boolean preferOpenPartitions)
throws BadLocationException, BadPartitioningException;
/**
* Computes the partitioning of the given document range based on the given partitioning type.
* <p>
* If <code>includeZeroLengthPartitions</code> is <code>true</code>, a zero-length partition of an open partition type (usually
* the default partition) is included between two closed partitions. If it is <code>false</code>, no zero-length partitions are
* included.
* </p>
* This is only supported if the connected <code>DocumentPartitioner</code> supports it, i.e. implements
* <code>DocumentPartitionerExtension2</code>. Otherwise, <code>includeZeroLengthPartitions</code> is ignored. </p>
*
* @param partitioning
* the document's partitioning type
* @param offset
* the document offset at which the range starts
* @param length
* the length of the document range
* @param includeZeroLengthPartitions
* <code>true</code> if zero-length partitions should be returned as part of the computed
* partitioning
* @return a specification of the range's partitioning
* @throws BadLocationException
* if the range is invalid in this document$
* @throws BadPartitioningException
* if partitioning is invalid for this document
*/
TypedRegion[] computePartitioning(String partitioning, int offset, int length, boolean includeZeroLengthPartitions)
throws BadLocationException, BadPartitioningException;
/**
* Sets this document's partitioner. The caller of this method is responsible for disconnecting the document's old partitioner
* from the document and to connect the new partitioner to the document. Informs all document partitioning listeners about this
* change.
*
* @param partitioning
* the partitioning for which to set the partitioner
* @param partitioner
* the document's new partitioner
* @see DocumentPartitioningListener
*/
void setDocumentPartitioner(String partitioning, DocumentPartitioner partitioner);
/**
* Returns the partitioner for the given partitioning or <code>null</code> if no partitioner is registered.
*
* @param partitioning
* the partitioning for which to set the partitioner
* @return the partitioner for the given partitioning
*/
DocumentPartitioner getDocumentPartitioner(String partitioning);
/* --------------- text access and manipulation --------------------------- */
/**
* Returns the character at the given document offset in this document.
*
* @param offset
* a document offset
* @return the character at the offset
* @throws BadLocationException
* if the offset is invalid in this document
*/
char getChar(int offset) throws BadLocationException;
/**
* Returns the number of characters in this document.
*
* @return the number of characters in this document
*/
int getLength();
/**
* Returns this document's complete text.
*
* @return the document's complete text
*/
String get();
/**
* Returns this document's text for the specified range.
*
* @param offset
* the document offset
* @param length
* the length of the specified range
* @return the document's text for the specified range
* @throws BadLocationException
* if the range is invalid in this document
*/
String get(int offset, int length) throws BadLocationException;
/**
* Replaces the content of the document with the given text. Sends a <code>DocumentEvent</code> to all registered
* <code>DocumentListener</code>. This method is a convenience method for <code>replace(0, getLength(), text)</code>.
*
* @param text
* the new content of the document
* @see DocumentEvent
* @see DocumentListener
*/
void set(String text);
/**
* Substitutes the given text for the specified document range. Sends a <code>DocumentEvent</code> to all registered
* <code>DocumentListener</code>.
*
* @param offset
* the document offset
* @param length
* the length of the specified range
* @param text
* the substitution text
* @throws BadLocationException
* if the offset is invalid in this document
* @see DocumentEvent
* @see DocumentListener
*/
void replace(int offset, int length, String text) throws BadLocationException;
/**
* Registers the document listener with the document. After registration
* the DocumentListener is informed about each change of this document.
* If the listener is already registered nothing happens.<p>
* An <code>DocumentListener</code> may call back to this method
* when being inside a document notification.
*
* @param listener
* the listener to be registered
*/
void addDocumentListener(DocumentListener listener);
/**
* Removes the listener from the document's list of document listeners.
* If the listener is not registered with the document nothing happens.<p>
* An <code>DocumentListener</code> may call back to this method
* when being inside a document notification.
*
* @param listener
* the listener to be removed
*/
void removeDocumentListener(DocumentListener listener);
// /**
// * Adds the given document listener as one which is notified before
// * those document listeners added with <code>addDocumentListener</code>
// * are notified. If the given listener is also registered using
// * <code>addDocumentListener</code> it will be notified twice.
// * If the listener is already registered nothing happens.<p>
// *
// * This method is not for public use.
// *
// * @param documentAdapter the listener to be added as pre-notified document listener
// *
// * @see #removePrenotifiedDocumentListener(DocumentListener)
// */
// void addPrenotifiedDocumentListener(DocumentListener documentAdapter);
//
// /**
// * Removes the given document listener from the document's list of
// * pre-notified document listeners. If the listener is not registered
// * with the document nothing happens. <p>
// *
// * This method is not for public use.
// *
// * @param documentAdapter the listener to be removed
// *
// * @see #addPrenotifiedDocumentListener(DocumentListener)
// */
// void removePrenotifiedDocumentListener(DocumentListener documentAdapter);
/* -------------------------- positions ----------------------------------- */
/**
* Adds a new position category to the document. If the position category already exists nothing happens.
*
* @param category
* the category to be added
*/
void addPositionCategory(String category);
/**
* Deletes the position category from the document. All positions in this category are thus deleted as well.
*
* @param category
* the category to be removed
* @throws BadPositionCategoryException
* if category is undefined in this document
*/
void removePositionCategory(String category) throws BadPositionCategoryException;
/**
* Returns all position categories of this document. This includes the default position category.
*
* @return the document's position categories
*/
String[] getPositionCategories();
/**
* Checks the presence of the specified position category.
*
* @param category
* the category to check
* @return <code>true</code> if category is defined
*/
boolean containsPositionCategory(String category);
/**
* Adds the position to the document's default position category. This is a convenience method for
* <code>addPosition(DEFAULT_CATEGORY, position)</code>.
*
* @param position
* the position to be added
* @throws BadLocationException
* if position describes an invalid range in this document
*/
void addPosition(Position position) throws BadLocationException;
/**
* Removes the given position from the document's default position category. This is a convenience method for
* <code>removePosition(DEFAULT_CATEGORY, position)</code>.
*
* @param position
* the position to be removed
*/
void removePosition(Position position);
/**
* Adds the position to the specified position category of the document. Positions may be added multiple times. The order of
* the category is maintained.
* <p>
* <strong>Note:</strong> The position is only updated on each change applied to the document if a {@link PositionUpdater} has
* been registered that handles the given category.
* </p>
*
* @param category
* the category to which to add
* @param position
* the position to be added
* @throws BadLocationException
* if position describes an invalid range in this document
* @throws BadPositionCategoryException
* if the category is undefined in this document
*/
void addPosition(String category, Position position) throws BadLocationException, BadPositionCategoryException;
/**
* Removes the given position from the specified position category. If the position is not part of the specified category
* nothing happens. If the position has been added multiple times, only the first occurrence is deleted.
*
* @param category
* the category from which to delete
* @param position
* the position to be deleted
* @throws BadPositionCategoryException
* if category is undefined in this document
*/
void removePosition(String category, Position position) throws BadPositionCategoryException;
/**
* Returns all positions of the given position category. The positions are ordered according to the category's order.
* Manipulating this list does not affect the document, but manipulating the position does affect the document.
*
* @param category
* the category
* @return the list of all positions
* @throws BadPositionCategoryException
* if category is undefined in this document
*/
Position[] getPositions(String category) throws BadPositionCategoryException;
/**
* Determines whether a position described by the parameters is managed by this document.
*
* @param category
* the category to check
* @param offset
* the offset of the position to find
* @param length
* the length of the position to find
* @return <code>true</code> if position is found
*/
boolean containsPosition(String category, int offset, int length);
/**
* Computes the index at which a <code>Position</code> with the
* specified offset would be inserted into the given category. As the
* ordering inside a category only depends on the offset, the index must be
* chosen to be the first of all positions with the same offset.
*
* @param category
* the category in which would be added
* @param offset
* the position offset to be considered
* @return the index into the category
* @throws BadLocationException
* if offset is invalid in this document
* @throws BadPositionCategoryException
* if category is undefined in this document
*/
int computeIndexInCategory(String category, int offset) throws BadLocationException, BadPositionCategoryException;
/**
* Appends a new position updater to the document's list of position updaters.
* Position updaters may be added multiple times.<p>
* An <code>IPositionUpdater</code> may call back to this method
* when being inside a document notification.
*
* @param updater
* the updater to be added
*/
void addPositionUpdater(PositionUpdater updater);
/**
* Removes the position updater from the document's list of position updaters.
* If the position updater has multiple occurrences only the first occurrence is
* removed. If the position updater is not registered with this document, nothing
* happens.<p>
* An <code>IPositionUpdater</code> may call back to this method
* when being inside a document notification.
*
* @param updater
* the updater to be removed
*/
void removePositionUpdater(PositionUpdater updater);
/**
* Inserts the position updater at the specified index in the document's
* list of position updaters. Positions updaters may be inserted multiple times.<p>
* An <code>IPositionUpdater</code> may call back to this method
* when being inside a document notification.
*
* @param updater
* the updater to be inserted
* @param index
* the index in the document's updater list
*/
void insertPositionUpdater(PositionUpdater updater, int index);
/**
* Returns the list of position updaters attached to the document.
*
* @return the list of position updaters
*/
PositionUpdater[] getPositionUpdaters();
/* -------------------------- partitions ---------------------------------- */
/**
* Returns the set of legal content types of document partitions. This set can be empty. The set can contain more content types
* than contained by the result of <code>getPartitioning(0, getLength())</code>.
* <p/>
* Use {@link #getLegalContentTypes(String)} when the document supports multiple partitionings. In that case
* this method is equivalent to:
* <p/>
* <pre>
* DocumentExtension3 extension = (DocumentExtension3)document;
* return extension.getLegalContentTypes(DocumentExtension3.DEFAULT_PARTITIONING);
* </pre>
*
* @return the set of legal content types
*/
String[] getLegalContentTypes();
/**
* Returns the type of the document partition containing the given offset. This is a convenience method for
* <code>getPartition(offset).getType()</code>.
* <p/>
* Use {@link #getContentType(String, int, boolean)} when the document supports multiple partitionings. In
* that case this method is equivalent to:
* <p/>
* <pre>
* DocumentExtension3 extension = (DocumentExtension3)document;
* return extension.getContentType(DocumentExtension3.DEFAULT_PARTITIONING, offset, false);
* </pre>
*
* @param offset
* the document offset
* @return the partition type
* @throws BadLocationException
* if offset is invalid in this document
*/
String getContentType(int offset) throws BadLocationException;
/**
* Returns the document partition in which the position is located.
* <p/>
* Use {@link #getPartition(String, int, boolean)} when
* the document supports multiple partitionings. In that case this method is
* equivalent:
* <pre>
* DocumentExtension3 extension= (DocumentExtension3) document;
* return extension.getPartition(DocumentExtension3.DEFAULT_PARTITIONING, offset, false);
* </pre>
*
* @param offset
* the document offset
* @return a specification of the partition
* @throws BadLocationException
* if offset is invalid in this document
*/
TypedRegion getPartition(int offset) throws BadLocationException;
/**
* Computes the partitioning of the given document range using the
* document's partitioner.
* <p/>
* Use {@link #computePartitioning(String, int, int, boolean)} when
* the document supports multiple partitionings. In that case this method is
* equivalent:
* <pre>
* DocumentExtension3 extension= (DocumentExtension3) document;
* return extension.computePartitioning(DocumentExtension3.DEFAULT_PARTITIONING, offset, length, false);
* </pre>
*
* @param offset
* the document offset at which the range starts
* @param length
* the length of the document range
* @return a specification of the range's partitioning
* @throws BadLocationException
* if the range is invalid in this document
*/
TypedRegion[] computePartitioning(int offset, int length) throws BadLocationException;
/**
* Registers the document partitioning listener with the document. After registration
* the document partitioning listener is informed about each partition change
* cause by a document manipulation or by changing the document's partitioner.
* If a document partitioning listener is also
* a document listener, the following notification sequence is guaranteed if a
* document manipulation changes the document partitioning:
* <ul>
* <li>listener.documentAboutToBeChanged(DocumentEvent);
* <li>listener.documentPartitioningChanged();
* <li>listener.documentChanged(DocumentEvent);
* </ul>
* If the listener is already registered nothing happens.<p>
* An <code>DocumentPartitioningListener</code> may call back to this method
* when being inside a document notification.
*
* @param listener
* the listener to be added
*/
void addDocumentPartitioningListener(DocumentPartitioningListener listener);
/**
* Removes the listener from this document's list of document partitioning
* listeners. If the listener is not registered with the document nothing
* happens.<p>
* An <code>DocumentPartitioningListener</code> may call back to this method
* when being inside a document notification.
*
* @param listener
* the listener to be removed
*/
void removeDocumentPartitioningListener(DocumentPartitioningListener listener);
/**
* Sets this document's partitioner. The caller of this method is responsible for
* disconnecting the document's old partitioner from the document and to
* connect the new partitioner to the document. Informs all document partitioning
* listeners about this change.
* <p/>
* Use {@link #setDocumentPartitioner(String, DocumentPartitioner)} when
* the document supports multiple partitionings. In that case this method is equivalent to:
* <pre>
* DocumentExtension3 extension= (DocumentExtension3) document;
* extension.setDocumentPartitioner(DocumentExtension3.DEFAULT_PARTITIONING, partitioner);
* </pre>
*
* @param partitioner
* the document's new partitioner
* @see DocumentPartitioningListener
*/
void setDocumentPartitioner(DocumentPartitioner partitioner);
/**
* Returns this document's partitioner.
* <p/>
* Use {@link #getDocumentPartitioner(String)} when
* the document supports multiple partitionings. In that case this method is
* equivalent to:
* <pre>
* DocumentExtension3 extension= (DocumentExtension3) document;
* return extension.getDocumentPartitioner(DocumentExtension3.DEFAULT_PARTITIONING);
* </pre>
*
* @return this document's partitioner
*/
DocumentPartitioner getDocumentPartitioner();
/* ---------------------- line information -------------------------------- */
/**
* Returns the length of the given line including the line's delimiter.
*
* @param line
* the line of interest
* @return the length of the line
* @throws BadLocationException
* if the line number is invalid in this document
*/
int getLineLength(int line) throws BadLocationException;
/**
* Returns the number of the line at which the character of the specified position is located. The first line has the line
* number 0. A new line starts directly after a line delimiter. <code>(offset == document length)</code> is a valid argument
* although there is no corresponding character.
*
* @param offset
* the document offset
* @return the number of the line
* @throws BadLocationException
* if the offset is invalid in this document
*/
int getLineOfOffset(int offset) throws BadLocationException;
/**
* Determines the offset of the first character of the given line.
*
* @param line
* the line of interest
* @return the document offset
* @throws BadLocationException
* if the line number is invalid in this document
*/
int getLineOffset(int line) throws BadLocationException;
/**
* Returns a description of the specified line. The line is described by its
* offset and its length excluding the line's delimiter.
*
* @param line
* the line of interest
* @return a line description
* @throws BadLocationException
* if the line number is invalid in this document
*/
Region getLineInformation(int line) throws BadLocationException;
/**
* Returns a description of the line at the given offset.
* The description contains the offset and the length of the line
* excluding the line's delimiter.
*
* @param offset
* the offset whose line should be described
* @return a region describing the line
* @throws BadLocationException
* if offset is invalid in this document
*/
Region getLineInformationOfOffset(int offset) throws BadLocationException;
/**
* Returns the number of lines in this document
*
* @return the number of lines in this document
*/
int getNumberOfLines();
/**
* Returns the number of lines which are occupied by a given text range.
*
* @param offset
* the offset of the specified text range
* @param length
* the length of the specified text range
* @return the number of lines occupied by the specified range
* @throws BadLocationException
* if specified range is invalid in this tracker
*/
int getNumberOfLines(int offset, int length) throws BadLocationException;
/**
* Computes the number of lines in the given text. For a given implementer of this interface this method returns the same
* result as <code>set(text); getNumberOfLines()</code>.
*
* @param text
* the text whose number of lines should be computed
* @return the number of lines in the given text
*/
int computeNumberOfLines(String text);
/* ------------------ line delimiter conversion --------------------------- */
/**
* Returns the document's legal line delimiters.
*
* @return the document's legal line delimiters
*/
String[] getLegalLineDelimiters();
/**
* Returns the line delimiter of that line or <code>null</code> if the line is not closed with a line delimiter.
*
* @param line
* the line of interest
* @return the line's delimiter or <code>null</code> if line does not have a delimiter
* @throws BadLocationException
* if the line number is invalid in this document
*/
String getLineDelimiter(int line) throws BadLocationException;
/**
* Returns all positions of the given category that are inside the given region.
*
* @param category
* the position category
* @param offset
* the start position of the region, must be >= 0
* @param length
* the length of the region, must be >= 0
* @param canStartBefore
* if <code>true</code> then positions are included which start before the region if they end at or after
* the regions start
* @param canEndAfter
* if <code>true</code> then positions are included which end after the region if they start at or before
* the regions end
* @return all positions inside the region of the given category
* @throws BadPositionCategoryException
* if category is undefined in this document
*/
Position[] getPositions(String category, int offset, int length, boolean canStartBefore, boolean canEndAfter)
throws BadPositionCategoryException;
}