/******************************************************************************* * Copyright (c) 2001, 2011 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 * (see also #239115) * David Carver (Intalio) - bug 300434 - Make inner classes static where possible * David Carver (Intalio) - bug 300443 - some constants aren't static final * *******************************************************************************/ 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.ISafeRunnable; import org.eclipse.core.runtime.Platform; import org.eclipse.core.runtime.SafeRunner; import org.eclipse.core.runtime.preferences.IScopeContext; import org.eclipse.core.runtime.preferences.InstanceScope; 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.IDocumentPartitionerExtension3; 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.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 static 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; } 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); } } static 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 fCurrentDocumentRegionCache; 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; /** * Theoretically, a document can contain mixed line delimiters, but the * user's preference is usually to be internally consistent. */ private String fInitialLineDelimiter; private static 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; /** * Keeps track of next modification stamp. */ private long fNextModificationStamp= IDocumentExtension4.UNKNOWN_MODIFICATION_STAMP; /** * debug variable only * * @param parser */ private long startStreamTime; /** * debug variable only * * @param parser */ private long startTime; public BasicStructuredDocument() { super(); fCurrentDocumentRegionCache = new CurrentDocumentRegionCache(); setTextStore(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 = fCurrentDocumentRegionCache.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() { String lineDelimiter= null; try { lineDelimiter= getLineDelimiter(0); } catch (BadLocationException x) { } if (lineDelimiter != null) return lineDelimiter; if (fInitialLineDelimiter != null) return fInitialLineDelimiter; String sysLineDelimiter= System.getProperty("line.separator"); //$NON-NLS-1$ String[] delimiters= getLegalLineDelimiters(); Assert.isTrue(delimiters.length > 0); for (int i= 0; i < delimiters.length; i++) { if (delimiters[i].equals(sysLineDelimiter)) { lineDelimiter= sysLineDelimiter; break; } } 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> * <html>[<head></head>]</html> returns <head>,</head> * </pre> * <pre> * <ht[ml><head></he]ad></html> returns <html>,<head>,</head> * </pre> * * <pre> * <html>[<head></head>]</html> returns <head>,</head> * </pre> * <pre> * <ht[ml><head></he]ad></html> returns <html>,<head>,</head> * </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() { 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() { 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 * @param modificationStamp * @param ignoreReadOnlySettings * @return */ private StructuredDocumentEvent internalReplaceText(Object requester, int start, int replacementLength, String changes, long modificationStamp, 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= modificationStamp; fNextModificationStamp= Math.max(fModificationStamp, fNextModificationStamp); fDocumentEvent.fModificationStamp = 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) { // check if we're already making document-wide changes on this thread if (fStoppedCount > 0) return; stopPostNotificationProcessing(); clearReadOnly(); try { acquireLock(); 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 offset, int length, String text) 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, offset, length, text); } /** * Replace the text with "newText" starting at position "start" for a * length of "replaceLength". * <p> * * @param pos * 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 length * start offset of text to replace * @param text * 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 pos, int length, String text) { if (length == 0 && (text == null || text.length() == 0)) return replaceText(requester, pos, length, text, getModificationStamp(), true); else return replaceText(requester, pos, length, text, getNextModificationStamp(), true); } public StructuredDocumentEvent replaceText(Object requester, int start, int replacementLength, String changes, boolean ignoreReadOnlySettings) { long modificationStamp; if (replacementLength == 0 && (changes == null || changes.length() == 0)) modificationStamp = getModificationStamp(); else modificationStamp = getNextModificationStamp(); return replaceText(requester, start, replacementLength, changes, modificationStamp, ignoreReadOnlySettings); } private StructuredDocumentEvent replaceText(Object requester, int start, int replacementLength, String changes, long modificationStamp, boolean ignoreReadOnlySettings) { StructuredDocumentEvent event = internalReplaceText(requester, start, replacementLength, changes, modificationStamp, ignoreReadOnlySettings); return event; } 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) { fCurrentDocumentRegionCache.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 lineDelimiter) { // make sure our preferred delimiter is // one of the legal ones if (Utilities.containsString(getLegalLineDelimiters(), lineDelimiter)) { fInitialLineDelimiter= lineDelimiter; } else { if (Logger.DEBUG_DOCUMENT) Logger.log(Logger.INFO, "Attempt to set linedelimiter to non-legal delimiter"); //$NON-NLS-1$ //$NON-NLS-2$ fInitialLineDelimiter = Platform.getPreferencesService().getString(Platform.PI_RUNTIME, Platform.PREF_LINE_SEPARATOR, System.getProperty("line.separator"), new IScopeContext[] { new InstanceScope() });//$NON-NLS-1$ } } 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) { Assert.isNotNull(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, getNextModificationStamp(), 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) { Assert.isNotNull(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; } } /* * {@inheritDoc} */ public void startSequentialRewrite(boolean normalized) { } /* * (non-Javadoc) * * @see org.eclipse.jface.text.IDocumentExtension#stopPostNotificationProcessing() */ public void stopPostNotificationProcessing() { ++fStoppedCount; } /* * {@inheritDoc} */ public void stopSequentialRewrite() { } /* * (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++; fNextModificationStamp= Math.max(fModificationStamp, fNextModificationStamp); 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. * */ static 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("already in a rewrite session"); 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); } startRewriteSessionOnPartitioners(session); if (DocumentRewriteSessionType.SEQUENTIAL == sessionType) startSequentialRewrite(false); else if (DocumentRewriteSessionType.STRICTLY_SEQUENTIAL == sessionType) startSequentialRewrite(true); fActiveRewriteSession = session; return session; } /** * Starts the given rewrite session. * * @param session the rewrite session * @since 2.0 */ final void startRewriteSessionOnPartitioners(DocumentRewriteSession session) { if (fDocumentPartitioners != null) { Iterator e= fDocumentPartitioners.values().iterator(); while (e.hasNext()) { Object partitioner= e.next(); if (partitioner instanceof IDocumentPartitionerExtension3) { IDocumentPartitionerExtension3 extension= (IDocumentPartitionerExtension3) partitioner; extension.startRewriteSession(session); } } } } 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(); stopRewriteSessionOnPartitioners(session); ILineTracker tracker = getTracker(); if (tracker instanceof ILineTrackerExtension) { ILineTrackerExtension extension = (ILineTrackerExtension) tracker; extension.stopRewriteSession(session, get()); } fActiveRewriteSession = null; DocumentRewriteSessionEvent event = new DocumentRewriteSessionEvent(this, session, DocumentRewriteSessionEvent.SESSION_STOP); fireDocumentRewriteSessionEvent(event); } } /** * Stops the given rewrite session. * * @param session the rewrite session * @since 2.0 */ final void stopRewriteSessionOnPartitioners(DocumentRewriteSession session) { if (fDocumentPartitioners != null) { DocumentPartitioningChangedEvent event= new DocumentPartitioningChangedEvent(this); Iterator e= fDocumentPartitioners.keySet().iterator(); while (e.hasNext()) { String partitioning= (String) e.next(); IDocumentPartitioner partitioner= (IDocumentPartitioner) fDocumentPartitioners.get(partitioning); if (partitioner instanceof IDocumentPartitionerExtension3) { IDocumentPartitionerExtension3 extension= (IDocumentPartitionerExtension3) partitioner; extension.stopRewriteSession(session); event.setPartitionChange(partitioning, 0, getLength()); } } if (!event.isEmpty()) fireDocumentPartitioningChanged(event); } } /* * (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, modificationStamp, true); } /* * (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, modificationStamp, true); } /* * (non-Javadoc) * * @see org.eclipse.jface.text.IDocumentExtension4#getModificationStamp() */ public long getModificationStamp() { return fModificationStamp; } private long getNextModificationStamp() { if (fNextModificationStamp == Long.MAX_VALUE || fNextModificationStamp == IDocumentExtension4.UNKNOWN_MODIFICATION_STAMP) fNextModificationStamp= 0; else fNextModificationStamp= fNextModificationStamp + 1; return fNextModificationStamp; } /** * Fires an event, as specified, to the associated listeners. * * @param event * The event to fire, either a start or stop event. */ private void fireDocumentRewriteSessionEvent(final DocumentRewriteSessionEvent event) { if (fDocumentRewriteSessionListeners == null || fDocumentRewriteSessionListeners.size() == 0) return; Object[] listeners = fDocumentRewriteSessionListeners.toArray(); for (int i = 0; i < listeners.length; i++) { final IDocumentRewriteSessionListener l = (IDocumentRewriteSessionListener) listeners[i]; SafeRunner.run(new ISafeRunnable() { public void run() throws Exception { l.documentRewriteSessionChanged(event); } public void handleException(Throwable exception) { // logged for us } }); } } }