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