blob: 1a4ae19b943470bfb0174d30ffb74fd287408161 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2001, 2006 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
* Jens Lukowski/Innoopract - initial renaming/restructuring
* Jesper Steen Møller - initial IDocumentExtension4 support - #102822
*
*******************************************************************************/
package org.eclipse.wst.sse.core.internal.text;
import java.io.Reader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.eclipse.core.runtime.Platform;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.BadPartitioningException;
import org.eclipse.jface.text.BadPositionCategoryException;
import org.eclipse.jface.text.DefaultLineTracker;
import org.eclipse.jface.text.DocumentEvent;
import org.eclipse.jface.text.DocumentPartitioningChangedEvent;
import org.eclipse.jface.text.DocumentRewriteSession;
import org.eclipse.jface.text.DocumentRewriteSessionEvent;
import org.eclipse.jface.text.DocumentRewriteSessionType;
import org.eclipse.jface.text.FindReplaceDocumentAdapter;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IDocumentExtension;
import org.eclipse.jface.text.IDocumentExtension3;
import org.eclipse.jface.text.IDocumentExtension4;
import org.eclipse.jface.text.IDocumentListener;
import org.eclipse.jface.text.IDocumentPartitioner;
import org.eclipse.jface.text.IDocumentPartitionerExtension;
import org.eclipse.jface.text.IDocumentPartitionerExtension2;
import org.eclipse.jface.text.IDocumentPartitioningListener;
import org.eclipse.jface.text.IDocumentPartitioningListenerExtension;
import org.eclipse.jface.text.IDocumentPartitioningListenerExtension2;
import org.eclipse.jface.text.IDocumentRewriteSessionListener;
import org.eclipse.jface.text.ILineTracker;
import org.eclipse.jface.text.ILineTrackerExtension;
import org.eclipse.jface.text.IPositionUpdater;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.ITextStore;
import org.eclipse.jface.text.ITypedRegion;
import org.eclipse.jface.text.Position;
import org.eclipse.jface.text.SequentialRewriteTextStore;
import org.eclipse.jface.text.TypedRegion;
import org.eclipse.wst.sse.core.internal.Logger;
import org.eclipse.wst.sse.core.internal.document.StructuredDocumentFactory;
import org.eclipse.wst.sse.core.internal.encoding.EncodingMemento;
import org.eclipse.wst.sse.core.internal.ltk.parser.RegionParser;
import org.eclipse.wst.sse.core.internal.provisional.events.AboutToBeChangedEvent;
import org.eclipse.wst.sse.core.internal.provisional.events.IModelAboutToBeChangedListener;
import org.eclipse.wst.sse.core.internal.provisional.events.IStructuredDocumentListener;
import org.eclipse.wst.sse.core.internal.provisional.events.NoChangeEvent;
import org.eclipse.wst.sse.core.internal.provisional.events.RegionChangedEvent;
import org.eclipse.wst.sse.core.internal.provisional.events.RegionsReplacedEvent;
import org.eclipse.wst.sse.core.internal.provisional.events.StructuredDocumentEvent;
import org.eclipse.wst.sse.core.internal.provisional.events.StructuredDocumentRegionsReplacedEvent;
import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument;
import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocumentRegion;
import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocumentRegionList;
import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredPartitioning;
import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredTextReParser;
import org.eclipse.wst.sse.core.internal.text.rules.StructuredTextPartitioner;
import org.eclipse.wst.sse.core.internal.undo.IStructuredTextUndoManager;
import org.eclipse.wst.sse.core.internal.undo.StructuredTextUndoManager;
import org.eclipse.wst.sse.core.internal.util.Assert;
import org.eclipse.wst.sse.core.internal.util.Debug;
import org.eclipse.wst.sse.core.internal.util.Utilities;
/**
* The standard implementation of structured document.
*/
public class BasicStructuredDocument implements IStructuredDocument, IDocumentExtension, IDocumentExtension3, IDocumentExtension4, CharSequence, IRegionComparible {
/**
* This ThreadLocal construct is used so each thread can maintain its only
* pointer to the double linked list that manages the documents regions.
* The only thing we "gaurd" for is that a previously cached region has
* been deleted.
*
* The object that is kept in the thread local's map, is just a pointer to
* an array position. That's because the object there needs to be "free"
* from references to other objects, or it will not be garbage collected.
*/
private class CurrentDocumentRegionCache {
// I'm assuming for now there would never be so many threads that
// this arrayList needs to be bounded, or 'cleaned up'.
// this assumption should be tested in practice and long running
// jobs -- found not to be a good assumption. See below.
private List cachedRegionPositionArray = Collections.synchronizedList(new ArrayList());
private final boolean DEBUG = false;
private final int MAX_SIZE = 50;
private ThreadLocal threadLocalCachePosition = new ThreadLocal();
IStructuredDocumentRegion get() {
IStructuredDocumentRegion region = null;
int pos = getThreadLocalPosition();
try {
region = (IStructuredDocumentRegion) cachedRegionPositionArray.get(pos);
}
catch (IndexOutOfBoundsException e) {
// even though the cachedRegionPosition is synchronized,
// that just means each access is syncronized, its
// still possible for another thread to cause it to
// be cleared, after this thread gets it position.
// So, if that happens, all we can do is reset to beginning.
// This should be extremely rare (in other words, probably
// not worth using synchronized blocks
// to access cachedRegionPositionArray.
reinitThreadLocalPosition();
resetToInitialState();
}
if (region == null) {
region = resetToInitialState();
}
else
// region not null
if (region.isDeleted()) {
region = resetToInitialState();
}
return region;
}
private int getThreadLocalPosition() {
Object threadLocalObject = threadLocalCachePosition.get();
int pos = -1;
if (threadLocalObject == null) {
pos = reinitThreadLocalPosition();
}
else {
pos = ((Integer) threadLocalObject).intValue();
}
return pos;
}
/**
* @return
*/
private int reinitThreadLocalPosition() {
Integer position;
int pos;
// TODO_future: think of a better solution that doesn't
// require this kludge. This is especially required because
// some infrasture, such as reconciler, actually null out
// their thread object and recreate it, 500 msecs later
// (approximately).
// Note: the likely solution in future is to clear after every
// heavy use of getCachedRegion, such as in creating node
// lists, or reparsing or partioning.
if (cachedRegionPositionArray.size() > MAX_SIZE) {
cachedRegionPositionArray.clear();
if (DEBUG) {
System.out.println("cachedRegionPositionArray cleared at size " + MAX_SIZE); //$NON-NLS-1$
}
}
position = new Integer(cachedRegionPositionArray.size());
threadLocalCachePosition.set(position);
cachedRegionPositionArray.add(position.intValue(), null);
pos = position.intValue();
return pos;
}
private IStructuredDocumentRegion resetToInitialState() {
IStructuredDocumentRegion region;
region = getFirstStructuredDocumentRegion();
set(region);
return region;
}
// TODO: make privite if used, else delete
void set(int pos, IStructuredDocumentRegion region) {
cachedRegionPositionArray.set(pos, region);
}
void set(IStructuredDocumentRegion region) {
try {
int pos = getThreadLocalPosition();
cachedRegionPositionArray.set(pos, region);
}
catch (IndexOutOfBoundsException e) {
// even though the cachedRegionPosition is synchronized,
// that just means each access is syncronized, its
// still possible for another thread to cause it to
// be cleared, after this thread gets it position.
// So, if that happens, all we can do is reset to beginning.
// This should be extremely rare (in other words, probably
// not worth using synchronized blocks
// to access cachedRegionPositionArray.
reinitThreadLocalPosition();
resetToInitialState();
}
}
}
/**
* This NullDocumentEvent is used to complete the "aboutToChange" and
* "changed" cycle, when in fact the original change is no longer valid.
* The only known (valid) case of this is when a model re-initialize takes
* place, which causes setText to be called in the middle of some previous
* change. [This architecture will be improved in future].
*/
public class NullDocumentEvent extends DocumentEvent {
public NullDocumentEvent() {
this(BasicStructuredDocument.this, 0, 0, ""); //$NON-NLS-1$
}
private NullDocumentEvent(IDocument doc, int offset, int length, String text) {
super(doc, offset, length, text);
}
}
class RegisteredReplace {
/** The owner of this replace operation. */
IDocumentListener fOwner;
/** The replace operation */
IDocumentExtension.IReplace fReplace;
/**
* Creates a new bundle object.
*
* @param owner
* the document listener owning the replace operation
* @param replace
* the replace operation
*/
RegisteredReplace(IDocumentListener owner, IDocumentExtension.IReplace replace) {
fOwner = owner;
fReplace = replace;
}
}
/**
* these control variable isn't mark as 'final' since there's some unit
* tests that manipulate it. For final product, it should be.
*/
private static boolean USE_LOCAL_THREAD = true;
/**
* purely for debugging/performance measurements In practice, would always
* be 'true'. (and should never be called by called by clients). Its not
* 'final' or private just so it can be varied during
* debugging/performance measurement runs.
*
* @param use_local_thread
*/
public static void setUSE_LOCAL_THREAD(final boolean use_local_thread) {
USE_LOCAL_THREAD = use_local_thread;
}
private IStructuredDocumentRegion cachedDocumentRegion;
private EncodingMemento encodingMemento;
private boolean fAcceptPostNotificationReplaces = true;
private CurrentDocumentRegionCache fCurrentDocumnetRegionCache;
private DocumentEvent fDocumentEvent;
private IDocumentListener[] fDocumentListeners;
/**
* The registered document partitioners.
*/
private Map fDocumentPartitioners;
/** The registered document partitioning listeners */
private List fDocumentPartitioningListeners;
private IStructuredDocumentRegion firstDocumentRegion;
private RegionParser fParser;
private GenericPositionManager fPositionManager;
private List fPostNotificationChanges;
private IDocumentListener[] fPrenotifiedDocumentListeners;
private int fReentranceCount = 0;
private IStructuredTextReParser fReParser;
private int fStoppedCount = 0;
private ITextStore fStore;
private Object[] fStructuredDocumentAboutToChangeListeners;
private Object[] fStructuredDocumentChangedListeners;
private Object[] fStructuredDocumentChangingListeners;
private List fDocumentRewriteSessionListeners;
private ILineTracker fTracker;
private IStructuredTextUndoManager fUndoManager;
private IStructuredDocumentRegion lastDocumentRegion;
private byte[] listenerLock = new byte[0];
private NullDocumentEvent NULL_DOCUMENT_EVENT;
//
/**
* in case preferred delimiter is not set, we'll assume the platform
* default Note: it is not final static to make sure it won't be inlined
* by compiler.
*/
private final String PlatformLineDelimiter = System.getProperty("line.separator"); //$NON-NLS-1$
/**
* theoretically, a document can contain mixed line delimiters
*/
private String preferedDelimiter;
private final String READ_ONLY_REGIONS_CATEGORY = "_READ_ONLY_REGIONS_CATEGORY_"; //$NON-NLS-1$
/**
* Current rewrite session, or none if not presently rewriting.
*/
private DocumentRewriteSession fActiveRewriteSession;
/**
* Last modification stamp, automatically updated on change.
*/
private long fModificationStamp;
/**
* debug variable only
*
* @param parser
*/
private long startStreamTime;
/**
* debug variable only
*
* @param parser
*/
private long startTime;
public BasicStructuredDocument() {
super();
fCurrentDocumnetRegionCache = new CurrentDocumentRegionCache();
fStore = new StructuredDocumentTextStore(50, 300);
setLineTracker(new DefaultLineTracker());
NULL_DOCUMENT_EVENT = new NullDocumentEvent();
internal_addPositionCategory(READ_ONLY_REGIONS_CATEGORY);
internal_addPositionUpdater(new DeleteEqualPositionUpdater(READ_ONLY_REGIONS_CATEGORY));
}
/**
* This is the primary way to get a new structuredDocument. Its best to
* use the factory methods in ModelManger to create a new
* IStructuredDocument, since it will get and initialize the parser
* according to the desired content type.
*/
public BasicStructuredDocument(RegionParser parser) {
this();
Assert.isNotNull(parser, "Program Error: IStructuredDocument can not be created with null parser"); //$NON-NLS-1$
// go through setter in case there is side effects
internal_setParser(parser);
}
private void _clearDocumentEvent() {
// no hard and fast requirement to null out ... just seems like
// a good idea, since we are done with it.
fDocumentEvent = null;
}
private void _fireDocumentAboutToChange(Object[] listeners) {
// most DocumentAboutToBeChanged listeners do not anticipate
// DocumentEvent == null. So make sure documentEvent is not
// null. (this should never happen, yet it does sometimes)
if (fDocumentEvent == null) {
fDocumentEvent = new NullDocumentEvent();
}
// we must assign listeners to local variable, since the add and
// remove
// listner
// methods can change the actual instance of the listener array from
// another thread
if (listeners != null) {
Object[] holdListeners = listeners;
// Note: the docEvent is created in replaceText API
// fire
for (int i = 0; i < holdListeners.length; i++) {
if (Debug.perfTest || Debug.perfTestStructuredDocumentEventOnly) {
startTime = System.currentTimeMillis();
}
// safeguard from listeners that throw exceptions
try {
// this is a safe cast, since addListners requires a
// IStructuredDocumentListener
((IDocumentListener) holdListeners[i]).documentAboutToBeChanged(fDocumentEvent);
}
catch (Exception exception) {
Logger.logException(exception);
}
if (Debug.perfTest || Debug.perfTestStructuredDocumentEventOnly) {
long stopTime = System.currentTimeMillis();
System.out.println("\n\t\t\t\t IStructuredDocument::fireStructuredDocumentEvent. Time was " + (stopTime - startTime) + " msecs to fire NewModelEvent to instance of " + holdListeners[i].getClass()); //$NON-NLS-2$//$NON-NLS-1$
}
}
}
}
private void notifyDocumentPartitionersAboutToChange(DocumentEvent documentEvent) {
if (fDocumentPartitioners != null) {
Iterator e = fDocumentPartitioners.values().iterator();
while (e.hasNext()) {
IDocumentPartitioner p = (IDocumentPartitioner) e.next();
// safeguard from listeners that throw exceptions
try {
p.documentAboutToBeChanged(documentEvent);
}
catch (Exception exception) {
Logger.logException(exception);
}
}
}
}
private void _fireDocumentChanged(Object[] listeners, StructuredDocumentEvent event) {
// we must assign listeners to local variable, since the add and
// remove
// listner
// methods can change the actual instance of the listener array from
// another thread
if (listeners != null) {
Object[] holdListeners = listeners;
// NOTE: document event is created in replace Text API and setText
// API
// now fire
for (int i = 0; i < holdListeners.length; i++) {
if (Debug.perfTest || Debug.perfTestStructuredDocumentEventOnly) {
startTime = System.currentTimeMillis();
}
// safeguard from listeners that throw exceptions
try {
// this is a safe cast, since addListners requires a
// IStructuredDocumentListener
// Notes: fDocumentEvent can be "suddenly" null, if one of
// the
// previous changes
// caused a "setText" to be called. The only known case of
// this
// is a model reset
// due to page directive changing. Eventually we should
// change
// archetecture to have
// event que and be able to "cancel" pending events, but
// for
// now, we'll just pass a
// NullDocumentEvent. By the way, it is important to send
// something, since clients might
// have indeterminant state due to "aboutToChange" being
// sent
// earlier.
if (fDocumentEvent == null) {
((IDocumentListener) holdListeners[i]).documentChanged(NULL_DOCUMENT_EVENT);
}
else {
((IDocumentListener) holdListeners[i]).documentChanged(fDocumentEvent);
}
}
catch (Exception exception) {
Logger.logException(exception);
}
if (Debug.perfTest || Debug.perfTestStructuredDocumentEventOnly) {
long stopTime = System.currentTimeMillis();
System.out.println("\n\t\t\t\t IStructuredDocument::fireStructuredDocumentEvent. Time was " + (stopTime - startTime) + " msecs to fire NewModelEvent to instance of " + holdListeners[i].getClass()); //$NON-NLS-2$//$NON-NLS-1$
}
}
}
}
private void notifyDocumentPartitionersDocumentChanged(DocumentEvent documentEvent) {
if (fDocumentPartitioners != null) {
Iterator e = fDocumentPartitioners.values().iterator();
while (e.hasNext()) {
IDocumentPartitioner p = (IDocumentPartitioner) e.next();
// safeguard from listeners that throw exceptions
try {
if (p instanceof IDocumentPartitionerExtension) {
// IRegion changedPartion =
((IDocumentPartitionerExtension) p).documentChanged2(documentEvent);
}
else {
p.documentChanged(documentEvent);
}
}
catch (Exception exception) {
Logger.logException(exception);
}
}
}
}
private void _fireEvent(Object[] listeners, NoChangeEvent event) {
// we must assign listeners to local variable, since the add and
// remove
// listner
// methods can change the actual instance of the listener array from
// another thread
if (listeners != null) {
Object[] holdListeners = listeners;
for (int i = 0; i < holdListeners.length; i++) {
if (Debug.perfTest || Debug.perfTestStructuredDocumentEventOnly) {
startTime = System.currentTimeMillis();
}
// safeguard from listeners that throw exceptions
try {
// this is a safe cast, since addListners requires a
// IStructuredDocumentListener
((IStructuredDocumentListener) holdListeners[i]).noChange(event);
}
catch (Exception exception) {
Logger.logException(exception);
}
if (Debug.perfTest || Debug.perfTestStructuredDocumentEventOnly) {
long stopTime = System.currentTimeMillis();
System.out.println("\n\t\t\t\t IStructuredDocument::fireStructuredDocumentEvent. Time was " + (stopTime - startTime) + " msecs to fire NewModelEvent to instance of " + holdListeners[i].getClass()); //$NON-NLS-2$//$NON-NLS-1$
}
}
}
}
private void _fireEvent(Object[] listeners, RegionChangedEvent event) {
// we must assign listeners to local variable, since the add and
// remove
// listner
// methods can change the actual instance of the listener array from
// another thread
if (listeners != null) {
Object[] holdListeners = listeners;
for (int i = 0; i < holdListeners.length; i++) {
if (Debug.perfTest || Debug.perfTestStructuredDocumentEventOnly) {
startTime = System.currentTimeMillis();
}
// safeguard from listeners that throw exceptions
try {
// this is a safe cast, since addListners requires a
// IStructuredDocumentListener
((IStructuredDocumentListener) holdListeners[i]).regionChanged(event);
}
catch (Exception exception) {
Logger.logException(exception);
}
if (Debug.perfTest || Debug.perfTestStructuredDocumentEventOnly) {
long stopTime = System.currentTimeMillis();
System.out.println("\n\t\t\t\t IStructuredDocument::fireStructuredDocumentEvent. Time was " + (stopTime - startTime) + " msecs to fire NewModelEvent to instance of " + holdListeners[i].getClass()); //$NON-NLS-2$//$NON-NLS-1$
}
}
}
}
private void _fireEvent(Object[] listeners, RegionsReplacedEvent event) {
// we must assign listeners to local variable, since the add and
// remove
// listner
// methods can change the actual instance of the listener array from
// another thread
if (listeners != null) {
Object[] holdListeners = listeners;
for (int i = 0; i < holdListeners.length; i++) {
if (Debug.perfTest || Debug.perfTestStructuredDocumentEventOnly) {
startTime = System.currentTimeMillis();
}
// safeguard from listeners that throw exceptions
try {
// this is a safe cast, since addListners requires a
// IStructuredDocumentListener
((IStructuredDocumentListener) holdListeners[i]).regionsReplaced(event);
}
catch (Exception exception) {
Logger.logException(exception);
}
if (Debug.perfTest || Debug.perfTestStructuredDocumentEventOnly) {
long stopTime = System.currentTimeMillis();
System.out.println("\n\t\t\t\t IStructuredDocument::fireStructuredDocumentEvent. Time was " + (stopTime - startTime) + " msecs to fire NewModelEvent to instance of " + holdListeners[i].getClass()); //$NON-NLS-2$//$NON-NLS-1$
}
}
}
}
private void _fireEvent(Object[] listeners, StructuredDocumentRegionsReplacedEvent event) {
// we must assign listeners to local variable, since the add and
// remove
// listner
// methods can change the actual instance of the listener array from
// another thread
if (listeners != null) {
Object[] holdListeners = listeners;
for (int i = 0; i < holdListeners.length; i++) {
if (Debug.perfTest || Debug.perfTestStructuredDocumentEventOnly) {
startTime = System.currentTimeMillis();
}
// safeguard from listeners that throw exceptions
try {
// this is a safe cast, since addListners requires a
// IStructuredDocumentListener
((IStructuredDocumentListener) holdListeners[i]).nodesReplaced(event);
}
catch (Exception exception) {
Logger.logException(exception);
}
if (Debug.perfTest || Debug.perfTestStructuredDocumentEventOnly) {
long stopTime = System.currentTimeMillis();
System.out.println("\n\t\t\t\t IStructuredDocument::fireStructuredDocumentEvent. Time was " + (stopTime - startTime) + " msecs to fire NewModelEvent to instance of " + holdListeners[i].getClass()); //$NON-NLS-2$//$NON-NLS-1$
}
}
}
}
private void _fireStructuredDocumentAboutToChange(Object[] listeners) {
// we must assign listeners to local variable, since the add and
// remove
// listner
// methods can change the actual instance of the listener array from
// another thread
if (listeners != null) {
Object[] holdListeners = listeners;
// Note: the docEvent is created in replaceText API
// fire
for (int i = 0; i < holdListeners.length; i++) {
if (Debug.perfTest || Debug.perfTestStructuredDocumentEventOnly) {
startTime = System.currentTimeMillis();
}
// safeguard from listeners that throw exceptions
try {
// notice the AboutToBeChangedEvent is created from the
// DocumentEvent, since it is (nearly)
// the same information. ?What to do about
// originalRequester?
if (fDocumentEvent == null) {
fDocumentEvent = new NullDocumentEvent();
}
AboutToBeChangedEvent aboutToBeChangedEvent = new AboutToBeChangedEvent(this, null, fDocumentEvent.getText(), fDocumentEvent.getOffset(), fDocumentEvent.getLength());
// this is a safe cast, since addListners requires a
// IStructuredDocumentListener
((IModelAboutToBeChangedListener) holdListeners[i]).modelAboutToBeChanged(aboutToBeChangedEvent);
}
catch (Exception exception) {
Logger.logException(exception);
}
if (Debug.perfTest || Debug.perfTestStructuredDocumentEventOnly) {
long stopTime = System.currentTimeMillis();
System.out.println("\n\t\t\t\t IStructuredDocument::fireStructuredDocumentEvent. Time was " + (stopTime - startTime) + " msecs to fire NewModelEvent to instance of " + holdListeners[i].getClass()); //$NON-NLS-2$//$NON-NLS-1$
}
}
}
}
protected void acquireLock() {
// do nothing here in super class
}
/**
* addModelAboutToBeChangedListener method comment.
*/
public void addDocumentAboutToChangeListener(IModelAboutToBeChangedListener listener) {
synchronized (listenerLock) {
// make sure listener is not already in listening
// (and if it is, print a warning to aid debugging, if needed)
if (!Utilities.contains(fStructuredDocumentAboutToChangeListeners, listener)) {
int oldSize = 0;
if (fStructuredDocumentAboutToChangeListeners != null) {
// normally won't be null, but we need to be sure, for
// first
// time through
oldSize = fStructuredDocumentAboutToChangeListeners.length;
}
int newSize = oldSize + 1;
Object[] newListeners = new Object[newSize];
if (fStructuredDocumentAboutToChangeListeners != null) {
System.arraycopy(fStructuredDocumentAboutToChangeListeners, 0, newListeners, 0, oldSize);
}
// add listener to last position
newListeners[newSize - 1] = listener;
//
// now switch new for old
fStructuredDocumentAboutToChangeListeners = newListeners;
//
}
}
}
/**
* The StructuredDocumentListners and ModelChagnedListeners are very
* similar. They both receive identical events. The difference is the
* timing. The "pure" StructuredDocumentListners are notified after the
* structuredDocument has been changed, but before other, related models
* may have been changed such as the Structural Model. The Structural
* model is in fact itself a "pure" StructuredDocumentListner. The
* ModelChangedListeners can rest assured that all models and data have
* been updated from the change by the tiem they are notified. This is
* especially important for the text widget, for example, which may rely
* on both structuredDocument and structural model information.
*/
public void addDocumentChangedListener(IStructuredDocumentListener listener) {
synchronized (listenerLock) {
if (Debug.debugStructuredDocument) {
System.out.println("IStructuredDocument::addModelChangedListener. Request to add an instance of " + listener.getClass() + " as a listener on structuredDocument."); //$NON-NLS-2$//$NON-NLS-1$
}
// make sure listener is not already in listening
// (and if it is, print a warning to aid debugging, if needed)
if (Utilities.contains(fStructuredDocumentChangedListeners, listener)) {
if (Debug.displayWarnings) {
System.out.println("IStructuredDocument::addModelChangedListener. listener " + listener + " was addeded more than once. "); //$NON-NLS-2$//$NON-NLS-1$
}
}
else {
if (Debug.debugStructuredDocument) {
System.out.println("IStructuredDocument::addModelChangedListener. Adding an instance of " + listener.getClass() + " as a listener on structuredDocument."); //$NON-NLS-2$//$NON-NLS-1$
}
int oldSize = 0;
if (fStructuredDocumentChangedListeners != null) {
// normally won't be null, but we need to be sure, for
// first
// time through
oldSize = fStructuredDocumentChangedListeners.length;
}
int newSize = oldSize + 1;
Object[] newListeners = new Object[newSize];
if (fStructuredDocumentChangedListeners != null) {
System.arraycopy(fStructuredDocumentChangedListeners, 0, newListeners, 0, oldSize);
}
// add listener to last position
newListeners[newSize - 1] = listener;
//
// now switch new for old
fStructuredDocumentChangedListeners = newListeners;
//
// when a listener is added,
// send the new model event to that one particular listener,
// so it
// can initialize itself with the current state of the model
// listener.newModel(new NewModelEvent(this, listener));
}
}
}
public void addDocumentChangingListener(IStructuredDocumentListener listener) {
synchronized (listenerLock) {
if (Debug.debugStructuredDocument) {
System.out.println("IStructuredDocument::addStructuredDocumentListener. Request to add an instance of " + listener.getClass() + " as a listener on structuredDocument."); //$NON-NLS-2$//$NON-NLS-1$
}
// make sure listener is not already in listening
// (and if it is, print a warning to aid debugging, if needed)
if (Utilities.contains(fStructuredDocumentChangingListeners, listener)) {
if (Debug.displayWarnings) {
System.out.println("IStructuredDocument::addStructuredDocumentListener. listener " + listener + " was addeded more than once. "); //$NON-NLS-2$//$NON-NLS-1$
}
}
else {
if (Debug.debugStructuredDocument) {
System.out.println("IStructuredDocument::addStructuredDocumentListener. Adding an instance of " + listener.getClass() + " as a listener on structuredDocument."); //$NON-NLS-2$//$NON-NLS-1$
}
int oldSize = 0;
if (fStructuredDocumentChangingListeners != null) {
// normally won't be null, but we need to be sure, for
// first
// time through
oldSize = fStructuredDocumentChangingListeners.length;
}
int newSize = oldSize + 1;
Object[] newListeners = new Object[newSize];
if (fStructuredDocumentChangingListeners != null) {
System.arraycopy(fStructuredDocumentChangingListeners, 0, newListeners, 0, oldSize);
}
// add listener to last position
newListeners[newSize - 1] = listener;
//
// now switch new for old
fStructuredDocumentChangingListeners = newListeners;
//
// when a listener is added,
// send the new model event to that one particular listener,
// so it
// can initialize itself with the current state of the model
// listener.newModel(new NewModelEvent(this, listener));
}
}
}
/**
* We manage our own document listners, instead of delegating to our
* parentDocument, so we can fire at very end (and not when the
* parentDocument changes).
*
*/
public void addDocumentListener(IDocumentListener listener) {
synchronized (listenerLock) {
// make sure listener is not already in listening
// (and if it is, print a warning to aid debugging, if needed)
if (!Utilities.contains(fDocumentListeners, listener)) {
int oldSize = 0;
if (fDocumentListeners != null) {
// normally won't be null, but we need to be sure, for
// first
// time through
oldSize = fDocumentListeners.length;
}
int newSize = oldSize + 1;
IDocumentListener[] newListeners = null;
newListeners = new IDocumentListener[newSize];
if (fDocumentListeners != null) {
System.arraycopy(fDocumentListeners, 0, newListeners, 0, oldSize);
}
// add listener to last position
newListeners[newSize - 1] = listener;
// now switch new for old
fDocumentListeners = newListeners;
}
}
}
/*
* @see org.eclipse.jface.text.IDocument#addDocumentPartitioningListener(org.eclipse.jface.text.IDocumentPartitioningListener)
*
* Registers the document partitioning listener with the document. After
* registration the IDocumentPartitioningListener is informed about each
* partition change cause by a document manipulation. If a document
* partitioning listener is also a document listener, the following
* notification sequence is guaranteed if a document manipulation changes
* the document partitioning: 1)
* listener.documentAboutToBeChanged(DocumentEvent); 2)
* listener.documentPartitioningChanged(); 3)
* listener.documentChanged(DocumentEvent); If the listener is already
* registered nothing happens.
*
* @see IDocumentPartitioningListener
*/
public void addDocumentPartitioningListener(IDocumentPartitioningListener listener) {
synchronized (listenerLock) {
Assert.isNotNull(listener);
if (fDocumentPartitioningListeners == null) {
fDocumentPartitioningListeners = new ArrayList(1);
}
if (!fDocumentPartitioningListeners.contains(listener))
fDocumentPartitioningListeners.add(listener);
}
}
/**
* Adds the position to the document's default position category. The
* default category must be specified by the implementer. A position that
* has been added to a position category is updated at each change applied
* to the document.
*
* @exception BadLocationException
* If position is not a valid range in the document
*/
public void addPosition(Position position) throws BadLocationException {
getPositionManager().addPosition(position);
}
/**
* @see IDocument#addPosition
* @exception BadLocationException
* If position is not a valid range in the document
* @exception BadPositionCategoryException
* If the category is not defined for the document
*/
public void addPosition(String category, Position position) throws BadLocationException, BadPositionCategoryException {
getPositionManager().addPosition(category, position);
}
/**
* @see IDocument#addPositionCategory
*/
public void addPositionCategory(String category) {
internal_addPositionCategory(category);
}
/**
* @see IDocument#addPositionUpdater
*/
public void addPositionUpdater(IPositionUpdater updater) {
internal_addPositionUpdater(updater);
}
/**
* 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, it may only be called by
* implementers of <code>IDocumentAdapter</code> and only if those
* implementers need to implement <code>IDocumentListener</code>.
*
* @param documentAdapter
* the listener to be added as prenotified document listener
*/
public void addPrenotifiedDocumentListener(IDocumentListener documentAdapter) {
synchronized (listenerLock) {
if (fPrenotifiedDocumentListeners != null) {
int previousSize = fPrenotifiedDocumentListeners.length;
IDocumentListener[] listeners = new IDocumentListener[previousSize + 1];
System.arraycopy(fPrenotifiedDocumentListeners, 0, listeners, 0, previousSize);
listeners[previousSize] = documentAdapter;
fPrenotifiedDocumentListeners = listeners;
}
else {
fPrenotifiedDocumentListeners = new IDocumentListener[1];
fPrenotifiedDocumentListeners[0] = documentAdapter;
}
}
}
/*
* (non-Javadoc)
*
* @see java.lang.CharSequence#charAt(int)
*/
public char charAt(int arg0) {
try {
return getChar(0);
}
catch (BadLocationException e) {
throw new IndexOutOfBoundsException();
}
}
/**
* This form of the API removes all read only positions, as should be done
* we 'setText' is called. Note: an alternative algorithm may simply
* remove the category (and it would get added back in later, if/when
* readonly regions added.
*/
private void clearReadOnly() {
Position[] positions = null;
try {
positions = getPositions(READ_ONLY_REGIONS_CATEGORY);
}
catch (BadPositionCategoryException e) {
Logger.logException("program error: should never occur", e); //$NON-NLS-1$
}
for (int i = 0; i < positions.length; i++) {
Position position = positions[i];
// note we don't fire the "about to change" or "changed" events,
// since presumably, text is all going away and being replaced
// anyway.
position.delete();
}
}
public void clearReadOnly(int startOffset, int length) {
// TODO DW I still need to implement smarter algorithm that
// adust existing RO regions, if needed. For now, I'll just
// remove any that overlap.
try {
Position[] positions = getPositions(READ_ONLY_REGIONS_CATEGORY);
for (int i = 0; i < positions.length; i++) {
Position position = positions[i];
if (position.overlapsWith(startOffset, length)) {
String effectedText = this.get(startOffset, length);
// fDocumentEvent = new DocumentEvent(this, startOffset,
// length, effectedText);
fireReadOnlyAboutToBeChanged();
position.delete();
NoChangeEvent noChangeEvent = new NoChangeEvent(this, null, effectedText, startOffset, length);
noChangeEvent.reason = NoChangeEvent.READ_ONLY_STATE_CHANGE;
fireReadOnlyStructuredDocumentEvent(noChangeEvent);
}
}
}
catch (BadPositionCategoryException e) {
// just means no readonly regions been defined yet
// so nothing to do.
}
}
/**
* 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 choosen 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
* @exception BadLocationException
* if offset is invalid in this document
* @exception BadPositionCategoryException
* if category is undefined in this document
*/
public int computeIndexInCategory(String category, int offset) throws org.eclipse.jface.text.BadPositionCategoryException, org.eclipse.jface.text.BadLocationException {
return getPositionManager().computeIndexInCategory(category, offset);
}
/**
* 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
*/
public int computeNumberOfLines(String text) {
return getTracker().computeNumberOfLines(text);
}
/**
* Computes the partitioning of the given document range using the
* document's partitioner.
*
* @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
* @throws BadPartitioningException
*/
public ITypedRegion[] computePartitioning(int offset, int length) throws BadLocationException {
ITypedRegion[] typedRegions = null;
try {
typedRegions = computePartitioning(IStructuredPartitioning.DEFAULT_STRUCTURED_PARTITIONING, offset, length, false);
}
catch (BadPartitioningException e) {
// impossible in this context
throw new Error(e);
}
if (typedRegions == null) {
typedRegions = new ITypedRegion[0];
}
return typedRegions;
}
public ITypedRegion[] computePartitioning(String partitioning, int offset, int length, boolean includeZeroLengthPartitions) throws BadLocationException, BadPartitioningException {
if ((0 > offset) || (0 > length) || (offset + length > getLength()))
throw new BadLocationException();
IDocumentPartitioner partitioner = getDocumentPartitioner(partitioning);
if (partitioner instanceof IDocumentPartitionerExtension2)
return ((IDocumentPartitionerExtension2) partitioner).computePartitioning(offset, length, includeZeroLengthPartitions);
else if (partitioner != null)
return partitioner.computePartitioning(offset, length);
else if (IStructuredPartitioning.DEFAULT_STRUCTURED_PARTITIONING.equals(partitioning))
return new TypedRegion[]{new TypedRegion(offset, length, DEFAULT_CONTENT_TYPE)};
else
throw new BadPartitioningException();
}
/**
* @see IDocument#containsPosition
*/
public boolean containsPosition(String category, int offset, int length) {
return getPositionManager().containsPosition(category, offset, length);
}
/**
* @see IDocument#containsPositionCategory
*/
public boolean containsPositionCategory(String category) {
return getPositionManager().containsPositionCategory(category);
}
public boolean containsReadOnly(int startOffset, int length) {
boolean result = false;
try {
Position[] positions = getPositions(READ_ONLY_REGIONS_CATEGORY);
for (int i = 0; i < positions.length; i++) {
Position position = positions[i];
if (position.overlapsWith(startOffset, length)) {
result = true;
break;
}
}
}
catch (BadPositionCategoryException e) {
// just means no readonly regions been defined yet
// so obviously false
result = false;
}
return result;
}
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);
}
}
}
private void fireDocumentAboutToChanged() {
// most DocumentAboutToBeChanged listeners do not anticipate
// DocumentEvent == null. So make sure documentEvent is not
// null. (this should never happen, yet it does sometimes)
if (fDocumentEvent == null) {
fDocumentEvent = new NullDocumentEvent();
}
_fireStructuredDocumentAboutToChange(fStructuredDocumentAboutToChangeListeners);
// Note: the docEvent is created in replaceText API! (or set Text)
_fireDocumentAboutToChange(fPrenotifiedDocumentListeners);
notifyDocumentPartitionersAboutToChange(fDocumentEvent);
_fireDocumentAboutToChange(fDocumentListeners);
}
/**
* Fires the document partitioning changed notification to all registered
* document partitioning listeners. Uses a robust iterator.
*
* @param event
* the document partitioning changed event
*
* @see IDocumentPartitioningListenerExtension2
*/
protected void fireDocumentPartitioningChanged(DocumentPartitioningChangedEvent event) {
if (fDocumentPartitioningListeners == null || fDocumentPartitioningListeners.size() == 0)
return;
List list = new ArrayList(fDocumentPartitioningListeners);
Iterator e = list.iterator();
while (e.hasNext()) {
IDocumentPartitioningListener l = (IDocumentPartitioningListener) e.next();
if (l instanceof IDocumentPartitioningListenerExtension2) {
IDocumentPartitioningListenerExtension2 extension2 = (IDocumentPartitioningListenerExtension2) l;
extension2.documentPartitioningChanged(event);
}
else if (l instanceof IDocumentPartitioningListenerExtension) {
IDocumentPartitioningListenerExtension extension = (IDocumentPartitioningListenerExtension) l;
extension.documentPartitioningChanged(this, event.getCoverage());
}
else {
l.documentPartitioningChanged(this);
}
}
}
private void fireReadOnlyAboutToBeChanged() {
_fireStructuredDocumentAboutToChange(fStructuredDocumentAboutToChangeListeners);
// Note: the docEvent is created in replaceText API! (or set Text)
// _fireDocumentAboutToChange(fPrenotifiedDocumentListeners);
// _fireDocumentAboutToChange(fDocumentListeners);
}
private void fireReadOnlyStructuredDocumentEvent(NoChangeEvent event) {
_fireEvent(fStructuredDocumentChangingListeners, event);
_fireEvent(fStructuredDocumentChangedListeners, event);
// _fireDocumentChanged(fPrenotifiedDocumentListeners, event);
// _fireDocumentChanged(fDocumentListeners, event);
// _clearDocumentEvent();
}
private void fireStructuredDocumentEvent(NoChangeEvent event) {
_fireEvent(fStructuredDocumentChangingListeners, event);
_fireEvent(fStructuredDocumentChangedListeners, event);
_fireDocumentChanged(fPrenotifiedDocumentListeners, event);
notifyDocumentPartitionersDocumentChanged(event);
_fireDocumentChanged(fDocumentListeners, event);
_clearDocumentEvent();
}
private void fireStructuredDocumentEvent(RegionChangedEvent event) {
_fireEvent(fStructuredDocumentChangingListeners, event);
_fireEvent(fStructuredDocumentChangedListeners, event);
_fireDocumentChanged(fPrenotifiedDocumentListeners, event);
notifyDocumentPartitionersDocumentChanged(event);
_fireDocumentChanged(fDocumentListeners, event);
_clearDocumentEvent();
}
private void fireStructuredDocumentEvent(RegionsReplacedEvent event) {
_fireEvent(fStructuredDocumentChangingListeners, event);
_fireEvent(fStructuredDocumentChangedListeners, event);
_fireDocumentChanged(fPrenotifiedDocumentListeners, event);
notifyDocumentPartitionersDocumentChanged(event);
_fireDocumentChanged(fDocumentListeners, event);
_clearDocumentEvent();
}
private void fireStructuredDocumentEvent(StructuredDocumentRegionsReplacedEvent event) {
_fireEvent(fStructuredDocumentChangingListeners, event);
_fireEvent(fStructuredDocumentChangedListeners, event);
_fireDocumentChanged(fPrenotifiedDocumentListeners, event);
notifyDocumentPartitionersDocumentChanged(event);
_fireDocumentChanged(fDocumentListeners, event);
_clearDocumentEvent();
}
/**
* Returns the document's complete text.
*/
public String get() {
return getStore().get(0, getLength());
}
/**
* Returns length characters from the document's text starting from the
* specified position.
*
* @throws BadLocationException
*
* @exception BadLocationException
* If the range is not valid in the document
*/
public String get(int offset, int length) {
String result = null;
int myLength = getLength();
if (0 > offset)
offset = 0;
if (0 > length)
length = 0;
if (offset + length > myLength) {
// first try adjusting length to fit
int lessLength = myLength - offset;
if ((lessLength >= 0) && (offset + lessLength == myLength)) {
length = lessLength;
}
else {
// second, try offset
int moreOffset = myLength - length;
if ((moreOffset >= 0) && (moreOffset + length == myLength)) {
offset = moreOffset;
}
else {
// can happen if myLength is 0.
// no adjustment possible.
result = new String();
}
}
}
if (result == null) {
result = getStore().get(offset, length);
}
return result;
}
public Object getAdapter(Class adapter) {
return Platform.getAdapterManager().getAdapter(this, adapter);
}
IStructuredDocumentRegion getCachedDocumentRegion() {
IStructuredDocumentRegion result = null;
if (USE_LOCAL_THREAD) {
result = fCurrentDocumnetRegionCache.get();
}
else {
result = cachedDocumentRegion;
}
return result;
}
/**
* @see IDocument#getChar
* @exception BadLocationException
* If position is not a valid range in the document
*/
public char getChar(int pos) throws BadLocationException {
char result = 0x00;
try {
result = getStore().get(pos);
}
catch (IndexOutOfBoundsException e) {
throw new BadLocationException(e.getLocalizedMessage());
}
return result;
}
/**
* Returns the type of the document partition containing the given
* character position.
*/
public String getContentType(int offset) throws BadLocationException {
return getDocumentPartitioner().getContentType(offset);
}
public String getContentType(String partitioning, int offset, boolean preferOpenPartitions) throws BadLocationException, BadPartitioningException {
if ((0 > offset) || (offset > getLength()))
throw new BadLocationException();
IDocumentPartitioner partitioner = getDocumentPartitioner(partitioning);
if (partitioner instanceof IDocumentPartitionerExtension2)
return ((IDocumentPartitionerExtension2) partitioner).getContentType(offset, preferOpenPartitions);
else if (partitioner != null)
return partitioner.getContentType(offset);
else if (IStructuredPartitioning.DEFAULT_STRUCTURED_PARTITIONING.equals(partitioning))
return DEFAULT_CONTENT_TYPE;
else
throw new BadPartitioningException();
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jface.text.IDocumentExtension4#getDefaultLineDelimiter()
*/
public String getDefaultLineDelimiter() {
// specific preferred line delimiter
if (preferedDelimiter != null)
return preferedDelimiter;
// no line delimiter has been used so just use platform's default
String lineDelimiter = null;
String sysLineDelimiter = PlatformLineDelimiter;
String[] delimiters = getLegalLineDelimiters();
Assert.isTrue(delimiters.length > 0);
for (int i = 0; i < delimiters.length; i++) {
if (delimiters[i].equals(sysLineDelimiter)) {
lineDelimiter = sysLineDelimiter;
break;
}
}
// no platform default so just use first legal delimiter
if (lineDelimiter == null)
lineDelimiter = delimiters[0];
return lineDelimiter;
}
/**
* Returns the document's partitioner.
*
* @see IDocumentPartitioner
*/
public IDocumentPartitioner getDocumentPartitioner() {
return getDocumentPartitioner(IDocumentExtension3.DEFAULT_PARTITIONING);
}
public IDocumentPartitioner getDocumentPartitioner(String partitioning) {
IDocumentPartitioner documentPartitioner = null;
if (fDocumentPartitioners != null) {
documentPartitioner = (IDocumentPartitioner) fDocumentPartitioners.get(partitioning);
}
return documentPartitioner;
}
public EncodingMemento getEncodingMemento() {
return encodingMemento;
}
public IStructuredDocumentRegion getFirstStructuredDocumentRegion() {
// should we update cachedNode?
// We should to keep consistent philosophy of remembering last
// requested position,
// for efficiency.
setCachedDocumentRegion(firstDocumentRegion);
return firstDocumentRegion;
}
public IStructuredDocumentRegion getLastStructuredDocumentRegion() {
// should we update cachedNode?
// We should to keep consistent philosophy of remembering last
// requested position,
// for efficiency.
setCachedDocumentRegion(lastDocumentRegion);
return lastDocumentRegion;
}
/*
* -------------------------- partitions
* ----------------------------------
*/
public String[] getLegalContentTypes() {
String[] result = null;
try {
result = getLegalContentTypes(IStructuredPartitioning.DEFAULT_STRUCTURED_PARTITIONING);
}
catch (BadPartitioningException e) {
// impossible in this context
throw new Error(e);
}
return result;
}
public String[] getLegalContentTypes(String partitioning) throws BadPartitioningException {
IDocumentPartitioner partitioner = getDocumentPartitioner(partitioning);
if (partitioner != null)
return partitioner.getLegalContentTypes();
if (IStructuredPartitioning.DEFAULT_STRUCTURED_PARTITIONING.equals(partitioning))
return new String[]{DEFAULT_CONTENT_TYPE};
throw new BadPartitioningException();
}
/*
* ------------------ line delimiter conversion
* ---------------------------
*/
public String[] getLegalLineDelimiters() {
return getTracker().getLegalLineDelimiters();
}
/**
* @see IDocument#getLength
*/
public int getLength() {
return getStore().getLength();
}
/*
* (non-Javadoc)
*
* @see org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument#getLineDelimiter()
*/
public String getLineDelimiter() {
return getDefaultLineDelimiter();
}
/**
* Returns the line delimiter of that line
*
* @exception BadLocationException
* If the line number is invalid in the document
*/
public String getLineDelimiter(int line) throws org.eclipse.jface.text.BadLocationException {
return getTracker().getLineDelimiter(line);
}
/**
* 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
* @exception BadLocationException
* if the line number is invalid in this document
*/
public org.eclipse.jface.text.IRegion getLineInformation(int line) throws org.eclipse.jface.text.BadLocationException {
return getTracker().getLineInformation(line);
}
/**
* 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
* @exception BadLocationException
* if offset is invalid in this document
*/
public org.eclipse.jface.text.IRegion getLineInformationOfOffset(int offset) throws org.eclipse.jface.text.BadLocationException {
return getTracker().getLineInformationOfOffset(offset);
}
/*
* ---------------------- line information
* --------------------------------
*/
public int getLineLength(int line) throws org.eclipse.jface.text.BadLocationException {
return getTracker().getLineLength(line);
}
/**
* Determines the offset of the first character of the given line.
*
* @param line
* the line of interest
* @return the document offset
* @exception BadLocationException
* if the line number is invalid in this document
*/
public int getLineOffset(int line) throws org.eclipse.jface.text.BadLocationException {
return getTracker().getLineOffset(line);
}
public int getLineOfOffset(int offset) {
int result = -1;
try {
result = getTracker().getLineNumberOfOffset(offset);
}
catch (BadLocationException e) {
if (Logger.DEBUG_DOCUMENT)
Logger.log(Logger.INFO, "Dev. Program Info Only: IStructuredDocument::getLineOfOffset: offset out of range, zero assumed. offset = " + offset, e); //$NON-NLS-1$ //$NON-NLS-2$
result = 0;
}
return result;
}
/**
* Returns the number of lines in this document
*
* @return the number of lines in this document
*/
public int getNumberOfLines() {
return getTracker().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
* @exception BadLocationException
* if specified range is invalid in this tracker
*/
public int getNumberOfLines(int offset, int length) throws org.eclipse.jface.text.BadLocationException {
return getTracker().getNumberOfLines(offset, length);
}
/**
* This is public, temporarily, for use by tag lib classes.
*/
public RegionParser getParser() {
if (fParser == null) {
throw new IllegalStateException("IStructuredDocument::getParser. Parser needs to be set before use"); //$NON-NLS-1$
}
return fParser;
}
/**
* Returns the document partition in which the position is located. The
* partition is specified as typed region.
*/
public ITypedRegion getPartition(int offset) throws BadLocationException {
ITypedRegion partition = null;
try {
partition = getPartition(IStructuredPartitioning.DEFAULT_STRUCTURED_PARTITIONING, offset, false);
}
catch (BadPartitioningException e) {
throw new Error(e);
}
if (partition == null) {
throw new Error();
}
return partition;
}
public ITypedRegion getPartition(String partitioning, int offset, boolean preferOpenPartitions) throws BadLocationException, BadPartitioningException {
if ((0 > offset) || (offset > getLength()))
throw new BadLocationException();
ITypedRegion result = null;
IDocumentPartitioner partitioner = getDocumentPartitioner(partitioning);
if (partitioner instanceof IDocumentPartitionerExtension2) {
result = ((IDocumentPartitionerExtension2) partitioner).getPartition(offset, preferOpenPartitions);
}
else if (partitioner != null) {
result = partitioner.getPartition(offset);
}
else if (IStructuredPartitioning.DEFAULT_STRUCTURED_PARTITIONING.equals(partitioning)) {
result = new TypedRegion(0, getLength(), DEFAULT_CONTENT_TYPE);
}
else
throw new BadPartitioningException();
return result;
}
public String[] getPartitionings() {
if (fDocumentPartitioners == null)
return new String[0];
String[] partitionings = new String[fDocumentPartitioners.size()];
fDocumentPartitioners.keySet().toArray(partitionings);
return partitionings;
}
/**
* Returns all position categories added to this document.
*/
public String[] getPositionCategories() {
return getPositionManager().getPositionCategories();
}
/**
* @return Returns the positionManager.
*/
private GenericPositionManager getPositionManager() {
if (fPositionManager == null) {
fPositionManager = new GenericPositionManager(this);
}
return fPositionManager;
}
/**
* Returns all Positions of the given position category.
*
* @exception BadPositionCategoryException
* If category is not defined for the document
*/
public Position[] getPositions(String category) throws org.eclipse.jface.text.BadPositionCategoryException {
return getPositionManager().getPositions(category);
}
/**
* @see IDocument#getPositionUpdaters
*/
public IPositionUpdater[] getPositionUpdaters() {
return getPositionManager().getPositionUpdaters();
}
/**
* This method can return null, which is the case if the offset is just
* before or just after the existing text. Compare with
* getNodeAtCharacterOffset.
*/
public IStructuredDocumentRegion getRegionAtCharacterOffset(int offset) {
IStructuredDocumentRegion result = null;
// FIXME: need to synch on 'cachedRegion' (but since that's a
// constantly changing object, we
// can't, so need to add a "region_lock" object, and use it here, and
// in re-parser.
// Oh, and need to make sure, after synch, that the region is not
// deleted, and if so, I guess go back
// to the beginning!
// cached node can be null when document is empty
IStructuredDocumentRegion potentialCachedRegion = getCachedDocumentRegion();
if (potentialCachedRegion != null) {
//
// if we already have the right node, return that.
if (potentialCachedRegion.containsOffset(offset)) {
result = potentialCachedRegion;
}
else {
// first, find out what direction to go, relative to
// cachedNode.
// negative means "towards the front" of the file,
// postitive
// means
// towards the end.
int direction = offset - potentialCachedRegion.getStart();
if (direction < 0) {
// search towards beginning
while (!potentialCachedRegion.containsOffset(offset)) {
IStructuredDocumentRegion tempNode = potentialCachedRegion.getPrevious();
if (tempNode == null) {
break;
}
else {
potentialCachedRegion = tempNode;
}
}
}
else {
// search towards end
// There is a legitamat condition where the
// offset will not be contained in any node,
// which is if the offset is just past the last
// character of text.
// And, we must gaurd against setting cachedNode to
// null!
while (!potentialCachedRegion.containsOffset(offset)) {
IStructuredDocumentRegion tempNode = potentialCachedRegion.getNext();
if (tempNode == null)
break;
else
potentialCachedRegion = tempNode;
}
}
}
result = potentialCachedRegion;
}
// just to be doubly sure we never assign null to an already valid
// cachedRegion.
// I believe any time 'result' is null at this point, that just means
// we have an
// empty document, and the cachedRegion is already null, but we check
// and print
// warning, just so during development we be sure we never accidently
// break this assumption.
if (result != null)
setCachedDocumentRegion(result);
else if (getCachedDocumentRegion() != null) {
throw new IllegalStateException("Program Error: no region could be found to cache, but cache was non null. Indicates corrupted model or region list"); //$NON-NLS-1$
}
return result;
}
public IStructuredDocumentRegionList getRegionList() {
CoreNodeList result = null;
if (getCachedDocumentRegion() == null)
result = new CoreNodeList(null);
else
result = new CoreNodeList(getFirstStructuredDocumentRegion());
return result;
}
public IStructuredDocumentRegion[] getStructuredDocumentRegions() {
return getStructuredDocumentRegions(0, getLength());
}
/**
* <p>
* In the case of 0 length, the <code>IStructuredDocumentRegion</code>
* at the character offset is returened. In other words, the region to the
* right of the caret is returned. except for at the end of the document,
* then the last region is returned.
* </p>
* <p>
* Otherwise all the regions "inbetween" the indicated range are returned,
* including the regions which overlap the region.
* </p>
*
* <br>
* eg.
* <p>
* <br>
* eg.
*
* <pre>
* &lt;html&gt;[&lt;head&gt;&lt;/head&gt;]&lt;/html&gt; returns &lt;head&gt;,&lt;/head&gt;
* </pre>
* <pre>
* &lt;ht[ml&gt;&lt;head&gt;&lt;/he]ad&gt;&lt;/html&gt; returns &lt;html&gt;,&lt;head&gt;,&lt;/head&gt;
* </pre>
*
* <pre>
* &lt;html&gt;[&lt;head&gt;&lt;/head&gt;]&lt;/html&gt; returns &lt;head&gt;,&lt;/head&gt;
* </pre>
* <pre>
* &lt;ht[ml&gt;&lt;head&gt;&lt;/he]ad&gt;&lt;/html&gt; returns &lt;html&gt;,&lt;head&gt;,&lt;/head&gt;
* </pre>
*
* </p>
*/
public IStructuredDocumentRegion[] getStructuredDocumentRegions(int start, int length) {
if (length < 0)
throw new IllegalArgumentException("can't have negative length"); //$NON-NLS-1$
// this will make the right edge of the range point into the selection
// eg. <html>[<head></head>]</html>
// will return <head>,</head> instead of <head>,</head>,</html>
if (length > 0)
length--;
List results = new ArrayList();
// start thread safe block
try {
acquireLock();
IStructuredDocumentRegion currentRegion = getRegionAtCharacterOffset(start);
IStructuredDocumentRegion endRegion = getRegionAtCharacterOffset(start + length);
while (currentRegion != endRegion && currentRegion != null) {
results.add(currentRegion);
currentRegion = currentRegion.getNext();
}
// need to add that last end region
// can be null in the case of an empty document
if (endRegion != null)
results.add(endRegion);
}
finally {
releaseLock();
}
// end thread safe block
return (IStructuredDocumentRegion[]) results.toArray(new IStructuredDocumentRegion[results.size()]);
}
/**
* was made public for easier testing. Normally should never be used by
* client codes.
*/
public IStructuredTextReParser getReParser() {
if (fReParser == null) {
fReParser = new StructuredDocumentReParser();
fReParser.setStructuredDocument(this);
}
return fReParser;
}
private ITextStore getStore() {
Assert.isNotNull(fStore);
return fStore;
}
public String getText() {
String result = get();
return result;
}
/**
* Returns the document's line tracker. Assumes that the document has been
* initialized with a line tracker.
*
* @return the document's line tracker
*/
private ILineTracker getTracker() {
Assert.isNotNull(fTracker);
return fTracker;
}
public IStructuredTextUndoManager getUndoManager() {
if (fUndoManager == null) {
fUndoManager = new StructuredTextUndoManager();
}
return fUndoManager;
}
void initializeFirstAndLastDocumentRegion() {
// cached Node must also be first, at the initial point. Only
// valid
// to call this method right after the first parse.
//
// when starting afresh, our cachedNode should be our firstNode,
// so be sure to initialize the firstNode
firstDocumentRegion = getCachedDocumentRegion();
// be sure to use 'getNext' for this initial finding of the last
// node,
// since the implementation of node.getLastNode may simply call
// structuredDocument.getLastStructuredDocumentRegion!
IStructuredDocumentRegion aNode = firstDocumentRegion;
if (aNode == null) {
// defect 254607: to handle empty documents right, if
// firstnode is
// null, make sure last node is null too
lastDocumentRegion = null;
}
else {
while (aNode != null) {
lastDocumentRegion = aNode;
aNode = aNode.getNext();
}
}
}
/**
* @see IDocument#insertPositionUpdater
*/
public void insertPositionUpdater(IPositionUpdater updater, int index) {
getPositionManager().insertPositionUpdater(updater, index);
}
private void internal_addPositionCategory(String category) {
getPositionManager().addPositionCategory(category);
}
private void internal_addPositionUpdater(IPositionUpdater updater) {
getPositionManager().addPositionUpdater(updater);
}
private void internal_setParser(RegionParser newParser) {
fParser = newParser;
}
String internalGet(int offset, int length) {
String result = null;
// int myLength = getLength();
// if ((0 > offset) || (0 > length) || (offset + length > myLength))
// throw new BadLocationException();
result = getStore().get(offset, length);
return result;
}
/**
* @param requester
* @param start
* @param replacementLength
* @param changes
* @return
*/
private StructuredDocumentEvent internalReplaceText(Object requester, int start, int replacementLength, String changes, boolean ignoreReadOnlySettings) {
StructuredDocumentEvent result = null;
stopPostNotificationProcessing();
if (changes == null)
changes = ""; //$NON-NLS-1$
//
if (Debug.debugStructuredDocument)
System.out.println(getClass().getName() + "::replaceText(" + start + "," + replacementLength + "," + changes + ")"); //$NON-NLS-4$//$NON-NLS-3$//$NON-NLS-2$//$NON-NLS-1$
if (Debug.perfTestStructuredDocumentOnly || Debug.perfTest || Debug.perfTestRawStructuredDocumentOnly) {
startStreamTime = System.currentTimeMillis();
}
try {
// Note: event must be computed before 'fire' method called
fDocumentEvent = new DocumentEvent(this, start, replacementLength, changes);
fireDocumentAboutToChanged();
try {
acquireLock();
if (!ignoreReadOnlySettings && (containsReadOnly(start, replacementLength))) {
NoChangeEvent noChangeEvent = new NoChangeEvent(this, requester, changes, start, replacementLength);
noChangeEvent.reason = NoChangeEvent.READ_ONLY_STATE_CHANGE;
result = noChangeEvent;
}
else {
result = updateModel(requester, start, replacementLength, changes);
}
}
finally {
releaseLock();
}
if (Debug.perfTestRawStructuredDocumentOnly || Debug.perfTest) {
long stopStreamTime = System.currentTimeMillis();
System.out.println("\n\t\t\t\t Time for IStructuredDocument raw replaceText: " + (stopStreamTime - startStreamTime)); //$NON-NLS-1$
}
if (Debug.debugStructuredDocument) {
System.out.println("event type returned by replaceTextWithNoDebuggingThread: " + result); //$NON-NLS-1$
}
}
finally {
// FUTURE_TO_DO: implement callback mechanism? to avoid instanceof
// and casting
// fireStructuredDocumentEvent must be called in order to end
// documentAboutToBeChanged state
// increment modification stamp if modifications were made
if (result != null && !(result instanceof NoChangeEvent))
fModificationStamp++;
if (result == null) {
// result should not be null, but if an exception was thrown,
// it will be
// so send a noChangeEvent and log the problem
NoChangeEvent noChangeEvent = new NoChangeEvent(this, requester, changes, start, replacementLength);
noChangeEvent.reason = NoChangeEvent.NO_EVENT;
fireStructuredDocumentEvent(noChangeEvent);
Logger.log(Logger.ERROR, "Program Error: invalid structured document event"); //$NON-NLS-1$
}
else {
if (result instanceof RegionChangedEvent) {
fireStructuredDocumentEvent((RegionChangedEvent) result);
}
else {
if (result instanceof RegionsReplacedEvent) {
fireStructuredDocumentEvent((RegionsReplacedEvent) result);
}
else {
if (result instanceof StructuredDocumentRegionsReplacedEvent) {
// probably more efficient to mark old regions as
// 'deleted' at the time
// that are determined to be deleted, but I'll do
// here
// in then central spot
// for programming ease.
updateDeletedFields((StructuredDocumentRegionsReplacedEvent) result);
fireStructuredDocumentEvent((StructuredDocumentRegionsReplacedEvent) result);
}
else {
if (result instanceof NoChangeEvent) {
fireStructuredDocumentEvent((NoChangeEvent) result);
}
else {
// if here, this means a new event was created
// and not handled here
// just send a no event until this issue is
// resolved.
NoChangeEvent noChangeEvent = new NoChangeEvent(this, requester, changes, start, replacementLength);
noChangeEvent.reason = NoChangeEvent.NO_EVENT;
fireStructuredDocumentEvent(noChangeEvent);
Logger.log(Logger.INFO, "Program Error: unexpected structured document event: " + result); //$NON-NLS-1$
}
}
}
}
}
if (Debug.perfTest || Debug.perfTestStructuredDocumentOnly) {
long stopStreamTime = System.currentTimeMillis();
System.out.println("\n\t\t\t\t Total Time for IStructuredDocument event signaling/processing in replaceText: " + (stopStreamTime - startStreamTime)); //$NON-NLS-1$
}
resumePostNotificationProcessing();
}
return result;
}
/*
* (non-Javadoc)
*
* @see java.lang.CharSequence#length()
*/
public int length() {
return getLength();
}
public void makeReadOnly(int startOffset, int length) {
makeReadOnly(startOffset, length, false, false);
}
public void makeReadOnly(int startOffset, int length, boolean canInsertBefore, boolean canInsertAfter) {
// doesn't make sense to have a readonly region of 0 length,
// so we'll ignore those requests
if (length <= 0)
return;
String affectedText = this.get(startOffset, length);
// a document event for "read only" change ... must
// be followed by "no change" structuredDocument event
// fDocumentEvent = new DocumentEvent(this, startOffset, length,
// affectedText);
fireReadOnlyAboutToBeChanged();
// if (containsReadOnly(startOffset, length)) {
// adjustReadOnlyRegions(startOffset, length);
// } else {
// we can blindly add category, since no harm done if already
// exists.
addPositionCategory(READ_ONLY_REGIONS_CATEGORY);
Position newPosition = new ReadOnlyPosition(startOffset, length, canInsertBefore);
try {
addPosition(READ_ONLY_REGIONS_CATEGORY, newPosition);
// FIXME: need to change API to pass in requester, so this event
// can be
// created correctly, instead of using null.
NoChangeEvent noChangeEvent = new NoChangeEvent(this, null, affectedText, startOffset, length);
noChangeEvent.reason = NoChangeEvent.READ_ONLY_STATE_CHANGE;
fireReadOnlyStructuredDocumentEvent(noChangeEvent);
}
catch (BadLocationException e) {
// for now, log and ignore. Perhaps later we
// could adjust to handle some cases?
Logger.logException(("could not create readonly region at " + startOffset + " to " + length), e); //$NON-NLS-1$ //$NON-NLS-2$
}
catch (BadPositionCategoryException e) {
// should never occur, since we add category
Logger.logException(e);
}
}
public IStructuredDocument newInstance() {
IStructuredDocument newInstance = StructuredDocumentFactory.getNewStructuredDocumentInstance(getParser().newInstance());
((BasicStructuredDocument) newInstance).setReParser(getReParser().newInstance());
if (getDocumentPartitioner() instanceof StructuredTextPartitioner) {
newInstance.setDocumentPartitioner(((StructuredTextPartitioner) getDocumentPartitioner()).newInstance());
newInstance.getDocumentPartitioner().connect(newInstance);
}
newInstance.setLineDelimiter(getLineDelimiter());
if (getEncodingMemento() != null) {
newInstance.setEncodingMemento((EncodingMemento) getEncodingMemento().clone());
}
return newInstance;
}
/*
* (non-Javadoc)
*
* @see org.eclipse.wst.sse.core.internal.text.IRegionComparible#regionMatches(int,
* int, java.lang.String)
*/
public boolean regionMatches(int offset, int length, String stringToCompare) {
boolean result = false;
ITextStore store = getStore();
if (store instanceof IRegionComparible) {
result = ((IRegionComparible) store).regionMatches(offset, length, stringToCompare);
}
else {
result = get(offset, length).equals(stringToCompare);
}
return result;
}
public boolean regionMatchesIgnoreCase(int offset, int length, String stringToCompare) {
boolean result = false;
ITextStore store = getStore();
if (store instanceof IRegionComparible) {
result = ((IRegionComparible) store).regionMatchesIgnoreCase(offset, length, stringToCompare);
}
else {
result = get(offset, length).equalsIgnoreCase(stringToCompare);
}
return result;
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jface.text.IDocumentExtension#registerPostNotificationReplace(org.eclipse.jface.text.IDocumentListener,
* org.eclipse.jface.text.IDocumentExtension.IReplace)
*/
public void registerPostNotificationReplace(IDocumentListener owner, IDocumentExtension.IReplace replace) {
if (fAcceptPostNotificationReplaces) {
if (fPostNotificationChanges == null)
fPostNotificationChanges = new ArrayList(1);
fPostNotificationChanges.add(new RegisteredReplace(owner, replace));
}
}
protected void releaseLock() {
// do nothing here in super class
}
public void removeDocumentAboutToChangeListener(IModelAboutToBeChangedListener listener) {
synchronized (listenerLock) {
if ((fStructuredDocumentAboutToChangeListeners != null) && (listener != null)) {
// if its not in the listeners, we'll ignore the request
if (Utilities.contains(fStructuredDocumentAboutToChangeListeners, listener)) {
int oldSize = fStructuredDocumentAboutToChangeListeners.length;
int newSize = oldSize - 1;
Object[] newListeners = new Object[newSize];
int index = 0;
for (int i = 0; i < oldSize; i++) {
if (fStructuredDocumentAboutToChangeListeners[i] == listener) { // ignore
}
else {
// copy old to new if its not the one we are
// removing
newListeners[index++] = fStructuredDocumentAboutToChangeListeners[i];
}
}
// now that we have a new array, let's switch it for the
// old
// one
fStructuredDocumentAboutToChangeListeners = newListeners;
}
}
}
}
/**
* removeModelChangedListener method comment.
*/
public void removeDocumentChangedListener(IStructuredDocumentListener listener) {
synchronized (listenerLock) {
if ((fStructuredDocumentChangedListeners != null) && (listener != null)) {
// if its not in the listeners, we'll ignore the request
if (Utilities.contains(fStructuredDocumentChangedListeners, listener)) {
int oldSize = fStructuredDocumentChangedListeners.length;
int newSize = oldSize - 1;
Object[] newListeners = new Object[newSize];
int index = 0;
for (int i = 0; i < oldSize; i++) {
if (fStructuredDocumentChangedListeners[i] == listener) { // ignore
}
else {
// copy old to new if its not the one we are
// removing
newListeners[index++] = fStructuredDocumentChangedListeners[i];
}
}
// now that we have a new array, let's switch it for the
// old
// one
fStructuredDocumentChangedListeners = newListeners;
}
}
}
}
public void removeDocumentChangingListener(IStructuredDocumentListener listener) {
synchronized (listenerLock) {
if ((fStructuredDocumentChangingListeners != null) && (listener != null)) {
// if its not in the listeners, we'll ignore the request
if (Utilities.contains(fStructuredDocumentChangingListeners, listener)) {
int oldSize = fStructuredDocumentChangingListeners.length;
int newSize = oldSize - 1;
Object[] newListeners = new Object[newSize];
int index = 0;
for (int i = 0; i < oldSize; i++) {
if (fStructuredDocumentChangingListeners[i] == listener) { // ignore
}
else {
// copy old to new if its not the one we are
// removing
newListeners[index++] = fStructuredDocumentChangingListeners[i];
}
}
// now that we have a new array, let's switch it for the
// old
// one
fStructuredDocumentChangingListeners = newListeners;
}
}
}
}
public void removeDocumentListener(IDocumentListener listener) {
synchronized (listenerLock) {
if ((fDocumentListeners != null) && (listener != null)) {
// if its not in the listeners, we'll ignore the request
if (Utilities.contains(fDocumentListeners, listener)) {
int oldSize = fDocumentListeners.length;
int newSize = oldSize - 1;
IDocumentListener[] newListeners = new IDocumentListener[newSize];
int index = 0;
for (int i = 0; i < oldSize; i++) {
if (fDocumentListeners[i] == listener) { // ignore
}
else {
// copy old to new if its not the one we are
// removing
newListeners[index++] = fDocumentListeners[i];
}
}
// now that we have a new array, let's switch it for the
// old
// one
fDocumentListeners = newListeners;
}
}
}
}
/*
* @see org.eclipse.jface.text.IDocument#removeDocumentPartitioningListener(org.eclipse.jface.text.IDocumentPartitioningListener)
*/
public void removeDocumentPartitioningListener(IDocumentPartitioningListener listener) {
synchronized (listenerLock) {
Assert.isNotNull(listener);
if (fDocumentPartitioningListeners != null)
fDocumentPartitioningListeners.remove(listener);
}
}
/**
* Removes the given <code>Position</code> from the document's default
* position category. The default position category is to be defined by
* the implementers. If the position is not part of the document's default
* category nothing happens.
*/
public void removePosition(Position position) {
getPositionManager().removePosition(position);
}
/**
* @see IDocument#removePosition
* @exception BadPositionCategoryException
* If the category is not defined for the document
*/
public void removePosition(String category, Position position) throws BadPositionCategoryException {
getPositionManager().removePosition(category, position);
}
/**
* @see IDocument#removePositionCategory
* @exception BadPositionCategoryException
* If the category is not defined for the document
*/
public void removePositionCategory(String category) throws BadPositionCategoryException {
getPositionManager().removePositionCategory(category);
}
/**
* @see IDocument#removePositionUpdater
*/
public void removePositionUpdater(IPositionUpdater updater) {
getPositionManager().removePositionUpdater(updater);
}
/**
* Removes the given document listener from teh document's list of
* prenotified document listeners. If the listener is not registered with
* the document nothing happens.
* <p>
*
* This method is not for public use, it may only be called by
* implementers of <code>IDocumentAdapter</code> and only if those
* implementers need to implement <code>IDocumentListener</code>.
*
* @param documentAdapter
* the listener to be removed
*
* @see #addPrenotifiedDocumentListener(IDocumentListener)
*/
public void removePrenotifiedDocumentListener(org.eclipse.jface.text.IDocumentListener documentAdapter) {
synchronized (listenerLock) {
if (Utilities.contains(fPrenotifiedDocumentListeners, documentAdapter)) {
int previousSize = fPrenotifiedDocumentListeners.length;
if (previousSize > 1) {
IDocumentListener[] listeners = new IDocumentListener[previousSize - 1];
int previousIndex = 0;
int newIndex = 0;
while (previousIndex < previousSize) {
if (fPrenotifiedDocumentListeners[previousIndex] != documentAdapter)
listeners[newIndex++] = fPrenotifiedDocumentListeners[previousIndex];
previousIndex++;
}
fPrenotifiedDocumentListeners = listeners;
}
else {
fPrenotifiedDocumentListeners = null;
}
}
}
}
/**
* This method is for INTERNAL USE ONLY and is NOT API.
*
* Rebuilds the StructuredDocumentRegion chain from the existing text.
* FileBuffer support does not allow clients to know the document's
* location before the text contents are set.
*
* @see set(String)
*/
public void reparse(Object requester) {
stopPostNotificationProcessing();
clearReadOnly();
acquireLock();
try {
CharSequenceReader subSetTextStoreReader = new CharSequenceReader((CharSequence) getStore(), 0, getStore().getLength());
resetParser(subSetTextStoreReader, 0);
//
setCachedDocumentRegion(getParser().getDocumentRegions());
// when starting afresh, our cachedNode should be our firstNode,
// so be sure to initialize the firstNode and lastNode
initializeFirstAndLastDocumentRegion();
StructuredDocumentRegionIterator.setParentDocument(getCachedDocumentRegion(), this);
}
finally {
releaseLock();
}
resumePostNotificationProcessing();
}
/**
* @see IDocument#replace
* @exception BadLocationException
* If position is not a valid range in the document
*/
public void replace(int pos, int length, String string) throws BadLocationException {
if (Debug.displayWarnings) {
System.out.println("Note: IStructuredDocument::replace(int, int, String) .... its better to use replaceText(source, string, int, int) API for structuredDocument updates"); //$NON-NLS-1$
}
replaceText(this, pos, length, string);
}
/**
* Replace the text with "newText" starting at position "start" for a
* length of "replaceLength".
* <p>
*
* @param start
* start offset of text to replace None of the offsets include
* delimiters of preceeding lines. Offset 0 is the first
* character of the document.
* @param replaceLength
* start offset of text to replace
* @param newText
* start offset of text to replace
* <p>
* Implementors have to notify TextChanged listeners after the
* content has been updated. The TextChangedEvent should be set
* as follows:
*
* event.type = SWT.TextReplaced event.start = start of the replaced text
* event.numReplacedLines = number of replaced lines event.numNewLines =
* number of new lines event.replacedLength = length of the replaced text
* event.newLength = length of the new text
*
* NOTE: numNewLines is the number of inserted lines and numReplacedLines
* is the number of deleted lines based on the change that occurs
* visually. For example:
*
* replacedText newText numReplacedLines numNewLines "" "\n" 0 1 "\n\n"
* "a" 2 0 "a" "\n\n" 0 2
*/
/**
* One of the APIs to manipulate the IStructuredDocument in terms of text.
*/
public StructuredDocumentEvent replaceText(Object requester, int start, int replacementLength, String changes) {
return replaceText(requester, start, replacementLength, changes, false);
}
public StructuredDocumentEvent replaceText(Object requester, int start, int replacementLength, String changes, boolean ignoreReadOnlySettings) {
return internalReplaceText(requester, start, replacementLength, changes, ignoreReadOnlySettings);
}
void resetParser(int startOffset, int endOffset) {
RegionParser parser = getParser();
ITextStore textStore = getStore();
if (textStore instanceof CharSequence) {
CharSequenceReader subSetTextStoreReader = new CharSequenceReader((CharSequence) textStore, startOffset, endOffset - startOffset);
parser.reset(subSetTextStoreReader, startOffset);
}
else {
String newNodeText = get(startOffset, endOffset - startOffset);
parser.reset(newNodeText, startOffset);
}
}
void resetParser(Reader reader, int startOffset) {
RegionParser parser = getParser();
parser.reset(reader, startOffset);
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jface.text.IDocumentExtension#resumePostNotificationProcessing()
*/
public void resumePostNotificationProcessing() {
--fStoppedCount;
if (fStoppedCount == 0 && fReentranceCount == 0)
executePostNotificationChanges();
}
/**
* @deprecated in superclass in 3.0 - use a FindReplaceDocumentAdapter
* directly
* @see IDocument#search
*/
public int search(int startPosition, String findString, boolean forwardSearch, boolean caseSensitive, boolean wholeWord) throws BadLocationException {
// (dmw) I added this warning, to know if still being used. I'm not
// sure it
// works as expected any longer.
// but the warning should be removed, once know.
Logger.log(Logger.INFO, "WARNING: using unsupported deprecated method 'search'"); //$NON-NLS-1$
int offset = -1;
IRegion match = new FindReplaceDocumentAdapter(this).find(startPosition, findString, forwardSearch, caseSensitive, wholeWord, false);
if (match != null) {
offset = match.getOffset();
}
return offset;
}
/**
* @see IDocument#setText
*/
public void set(String string) {
if (Debug.displayInfo) {
System.out.println("Note: IStructuredDocument::setText(String) .... its better to use setText(source, string) API for structuredDocument updates"); //$NON-NLS-1$
}
setText(null, string);
}
/**
* This may be marked public, but should be packaged protected, once
* refactoring is complete (in other words, not for client use).
*/
public void setCachedDocumentRegion(IStructuredDocumentRegion structuredRegion) {
if (USE_LOCAL_THREAD) {
fCurrentDocumnetRegionCache.set(structuredRegion);
}
else {
cachedDocumentRegion = structuredRegion;
}
}
/**
* Sets the document's partitioner.
*
* @see IDocumentPartitioner
*/
public void setDocumentPartitioner(IDocumentPartitioner partitioner) {
setDocumentPartitioner(IDocumentExtension3.DEFAULT_PARTITIONING, partitioner);
}
public void setDocumentPartitioner(String partitioning, IDocumentPartitioner 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);
}
public void setEncodingMemento(EncodingMemento encodingMemento) {
this.encodingMemento = encodingMemento;
}
void setFirstDocumentRegion(IStructuredDocumentRegion region) {
firstDocumentRegion = region;
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jface.text.IDocumentExtension4#setInitialLineDelimiter(java.lang.String)
*/
public void setInitialLineDelimiter(String delimiter) {
// make sure our preferred delimiter is
// one of the legal ones
if (Utilities.containsString(getLegalLineDelimiters(), delimiter)) {
preferedDelimiter = delimiter;
}
else {
if (Logger.DEBUG_DOCUMENT)
Logger.log(Logger.INFO, "Attempt to set linedelimiter to non-legal delimiter"); //$NON-NLS-1$ //$NON-NLS-2$
preferedDelimiter = PlatformLineDelimiter;
}
}
void setLastDocumentRegion(IStructuredDocumentRegion region) {
lastDocumentRegion = region;
}
/*
* (non-Javadoc)
*
* @see org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument#setLineDelimiter(java.lang.String)
*/
public void setLineDelimiter(String delimiter) {
setInitialLineDelimiter(delimiter);
}
/**
* Sets the document's line tracker. Must be called at the beginning of
* the constructor.
*
* @param tracker
* the document's line tracker
*/
private void setLineTracker(ILineTracker tracker) {
fTracker = tracker;
}
public void setParser(RegionParser newParser) {
internal_setParser(newParser);
}
/**
* @param positionManager
* The positionManager to set.
*/
// TODO: make private is needed, else remove
void setPositionManager(GenericPositionManager positionManager) {
fPositionManager = positionManager;
}
/**
*
*/
public void setReParser(IStructuredTextReParser newReParser) {
fReParser = newReParser;
if (fReParser != null) {
fReParser.setStructuredDocument(this);
}
}
/**
* One of the APIs to manipulate the IStructuredDocument in terms of text.
*/
public StructuredDocumentEvent setText(Object requester, String theString) {
StructuredDocumentEvent result = null;
result = replaceText(requester, 0, getLength(), theString, true);
return result;
}
/**
* Sets the document's text store. Must be called at the beginning of the
* constructor.
*
* @param store
* the document's text store
*/
private void setTextStore(ITextStore store) {
fStore = store;
}
public void setUndoManager(IStructuredTextUndoManager undoManager) {
// if the undo manager has already been set, then
// fail fast, since changing the undo manager will lead
// to unusual results (or at least loss of undo stack).
if (fUndoManager != null && fUndoManager != undoManager) {
throw new IllegalArgumentException("can not change undo manager once its been set"); //$NON-NLS-1$
}
else {
fUndoManager = undoManager;
}
}
public void startSequentialRewrite(boolean normalized) {
ITextStore store = new SequentialRewriteTextStore(getStore());
setTextStore(store);
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jface.text.IDocumentExtension#stopPostNotificationProcessing()
*/
public void stopPostNotificationProcessing() {
++fStoppedCount;
}
public void stopSequentialRewrite() {
if (getStore() instanceof SequentialRewriteTextStore) {
SequentialRewriteTextStore srws = (SequentialRewriteTextStore) getStore();
ITextStore source = srws.getSourceStore();
setTextStore(source);
srws.dispose();
}
}
/*
* (non-Javadoc)
*
* @see java.lang.CharSequence#subSequence(int, int)
*/
public CharSequence subSequence(int arg0, int arg1) {
return get(arg0, arg1);
}
/**
* @param result
*/
private void updateDeletedFields(StructuredDocumentRegionsReplacedEvent event) {
IStructuredDocumentRegionList oldRegions = event.getOldStructuredDocumentRegions();
for (int i = 0; i < oldRegions.getLength(); i++) {
IStructuredDocumentRegion structuredDocumentRegion = oldRegions.item(i);
structuredDocumentRegion.setDeleted(true);
}
}
/**
* Called by re-parser. Note: this method may be "public" but should only
* be called by re-parsers in the right circumstances.
*/
public void updateDocumentData(int start, int lengthToReplace, String changes) {
stopPostNotificationProcessing();
getStore().replace(start, lengthToReplace, changes);
try {
getTracker().replace(start, lengthToReplace, changes);
}
catch (BadLocationException e) {
// should be impossible here, but will log for now
Logger.logException(e);
}
if (fPositionManager != null) {
fPositionManager.updatePositions(new DocumentEvent(this, start, lengthToReplace, changes));
}
fModificationStamp++;
resumePostNotificationProcessing();
}
private StructuredDocumentEvent updateModel(Object requester, int start, int lengthToReplace, String changes) {
StructuredDocumentEvent result = null;
IStructuredTextReParser reParser = getReParser();
// initialize the IStructuredTextReParser with the standard data
// that's
// always needed
reParser.initialize(requester, start, lengthToReplace, changes);
result = reParser.reparse();
// if result is null at this point, then there must be an error, since
// even if there
// was no change (either disallow due to readonly, or a person pasted
// the same thing
// they had selected) then a "NoChange" event should have been fired.
Assert.isNotNull(result, "no structuredDocument event was created in IStructuredDocument::updateStructuredDocument"); //$NON-NLS-1$
return result;
}
/*
* (non-Javadoc)
*
* @see org.eclipse.wst.sse.core.internal.provisional.document.IEncodedDocument#getPreferredLineDelimiter()
*/
public String getPreferredLineDelimiter() {
return getDefaultLineDelimiter();
}
/*
* (non-Javadoc)
*
* @see org.eclipse.wst.sse.core.internal.provisional.document.IEncodedDocument#setPreferredLineDelimiter(java.lang.String)
*/
public void setPreferredLineDelimiter(String probableLineDelimiter) {
setInitialLineDelimiter(probableLineDelimiter);
}
/**
* Class which implements the rewritable session for the SSE.
*
*/
class StructuredDocumentRewriteSession extends DocumentRewriteSession {
/**
* Creates a new session.
*
* @param sessionType
* the type of this session
*/
protected StructuredDocumentRewriteSession(DocumentRewriteSessionType sessionType) {
super(sessionType);
}
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jface.text.IDocumentExtension4#startRewriteSession(org.eclipse.jface.text.DocumentRewriteSessionType)
*/
public DocumentRewriteSession startRewriteSession(DocumentRewriteSessionType sessionType) throws IllegalStateException {
// delegate to sub-class, so UI threading is handled correctly
return internalStartRewriteSession(sessionType);
}
/**
* NOT-API. Final protected so clients may call this method if needed, but
* cannot override.
*
* @param sessionType
* @return
* @throws IllegalStateException
*/
final protected DocumentRewriteSession internalStartRewriteSession(DocumentRewriteSessionType sessionType) throws IllegalStateException {
if (getActiveRewriteSession() != null)
throw new IllegalStateException();
DocumentRewriteSession session = new StructuredDocumentRewriteSession(sessionType);
DocumentRewriteSessionEvent event = new DocumentRewriteSessionEvent(this, session, DocumentRewriteSessionEvent.SESSION_START);
fireDocumentRewriteSessionEvent(event);
ILineTracker tracker = getTracker();
if (tracker instanceof ILineTrackerExtension) {
ILineTrackerExtension extension = (ILineTrackerExtension) tracker;
extension.startRewriteSession(session);
}
if (DocumentRewriteSessionType.SEQUENTIAL == sessionType)
startSequentialRewrite(false);
else if (DocumentRewriteSessionType.STRICTLY_SEQUENTIAL == sessionType)
startSequentialRewrite(true);
fActiveRewriteSession = session;
return session;
}
public void stopRewriteSession(DocumentRewriteSession session) {
// delegate to sub-class, so UI threading is handled correctly
internalStopRewriteSession(session);
}
/**
* NOT-API. Final protected so clients may call this method if needed, but
* cannot override.
*
* @param session
*/
final protected void internalStopRewriteSession(DocumentRewriteSession session) {
if (fActiveRewriteSession == session) {
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());
}
DocumentRewriteSessionEvent event = new DocumentRewriteSessionEvent(this, session, DocumentRewriteSessionEvent.SESSION_STOP);
fireDocumentRewriteSessionEvent(event);
fActiveRewriteSession = null;
}
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jface.text.IDocumentExtension4#getActiveRewriteSession()
*/
public DocumentRewriteSession getActiveRewriteSession() {
return fActiveRewriteSession;
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jface.text.IDocumentExtension4#addDocumentRewriteSessionListener(org.eclipse.jface.text.IDocumentRewriteSessionListener)
*/
public void addDocumentRewriteSessionListener(IDocumentRewriteSessionListener listener) {
synchronized (listenerLock) {
Assert.isNotNull(listener);
if (fDocumentRewriteSessionListeners == null) {
fDocumentRewriteSessionListeners = new ArrayList(1);
}
if (!fDocumentRewriteSessionListeners.contains(listener))
fDocumentRewriteSessionListeners.add(listener);
}
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jface.text.IDocumentExtension4#removeDocumentRewriteSessionListener(org.eclipse.jface.text.IDocumentRewriteSessionListener)
*/
public void removeDocumentRewriteSessionListener(IDocumentRewriteSessionListener listener) {
synchronized (listenerLock) {
Assert.isNotNull(listener);
if (fDocumentRewriteSessionListeners != null)
fDocumentRewriteSessionListeners.remove(listener);
}
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jface.text.IDocumentExtension4#replace(int, int,
* java.lang.String, long)
*/
public void replace(int offset, int length, String text, long modificationStamp) throws BadLocationException {
replaceText(this, offset, length, text);
fModificationStamp = modificationStamp;
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jface.text.IDocumentExtension4#set(java.lang.String,
* long)
*/
public void set(String text, long modificationStamp) {
// bug 151069 - overwrite read only regions when setting entire document
replaceText(null, 0, getLength(), text, true);
fModificationStamp = modificationStamp;
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jface.text.IDocumentExtension4#getModificationStamp()
*/
public long getModificationStamp() {
return fModificationStamp;
}
/**
* Fires an event, as specified, to the associated listeners.
*
* @param event
* The event to fire, either a start or stop event.
*/
private void fireDocumentRewriteSessionEvent(DocumentRewriteSessionEvent event) {
if (fDocumentRewriteSessionListeners == null || fDocumentRewriteSessionListeners.size() == 0)
return;
List list = new ArrayList(fDocumentRewriteSessionListeners);
Iterator e = list.iterator();
while (e.hasNext()) {
IDocumentRewriteSessionListener l = (IDocumentRewriteSessionListener) e.next();
l.documentRewriteSessionChanged(event);
}
}
}