
org.eclipse.jface.text.reconciler.AbstractReconciler Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of org.eclipse.jface.text Show documentation
Show all versions of org.eclipse.jface.text Show documentation
This is org.eclipse.jface.text jar used by Scout SDK
/*******************************************************************************
* Copyright (c) 2000, 2011 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.jface.text.reconciler;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.jface.text.DocumentEvent;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IDocumentListener;
import org.eclipse.jface.text.ITextInputListener;
import org.eclipse.jface.text.ITextViewer;
/**
* Abstract implementation of {@link IReconciler}. The reconciler
* listens to input document changes as well as changes of
* the input document of the text viewer it is installed on. Depending on
* its configuration it manages the received change notifications in a
* queue folding neighboring or overlapping changes together. The reconciler
* processes the dirty regions as a background activity after having waited for further
* changes for the configured duration of time. A reconciler is started using the
* {@link #install(ITextViewer)} method. As a first step {@link #initialProcess()} is
* executed in the background. Then, the reconciling thread waits for changes that
* need to be reconciled. A reconciler can be resumed by calling {@link #forceReconciling()}
* independent from the existence of actual changes. This mechanism is for subclasses only.
* It is the clients responsibility to stop a reconciler using its {@link #uninstall()}
* method. Unstopped reconcilers do not free their resources.
*
* It is subclass responsibility to specify how dirty regions are processed.
*
*
* @see org.eclipse.jface.text.IDocumentListener
* @see org.eclipse.jface.text.ITextInputListener
* @see org.eclipse.jface.text.reconciler.DirtyRegion
* @since 2.0
*/
abstract public class AbstractReconciler implements IReconciler {
/**
* Background thread for the reconciling activity.
*/
class BackgroundThread extends Thread {
/** Has the reconciler been canceled. */
private boolean fCanceled= false;
/** Has the reconciler been reset. */
private boolean fReset= false;
/** Some changes need to be processed. */
private boolean fIsDirty= false;
/** Is a reconciling strategy active. */
private boolean fIsActive= false;
/**
* Creates a new background thread. The thread
* runs with minimal priority.
*
* @param name the thread's name
*/
public BackgroundThread(String name) {
super(name);
setPriority(Thread.MIN_PRIORITY);
setDaemon(true);
}
/**
* Returns whether a reconciling strategy is active right now.
*
* @return true
if a activity is active
*/
public boolean isActive() {
return fIsActive;
}
/**
* Returns whether some changes need to be processed.
*
* @return true
if changes wait to be processed
* @since 3.0
*/
public synchronized boolean isDirty() {
return fIsDirty;
}
/**
* Cancels the background thread.
*/
public void cancel() {
fCanceled= true;
IProgressMonitor pm= fProgressMonitor;
if (pm != null)
pm.setCanceled(true);
synchronized (fDirtyRegionQueue) {
fDirtyRegionQueue.notifyAll();
}
}
/**
* Suspends the caller of this method until this background thread has
* emptied the dirty region queue.
*/
public void suspendCallerWhileDirty() {
boolean isDirty;
do {
synchronized (fDirtyRegionQueue) {
isDirty= fDirtyRegionQueue.getSize() > 0;
if (isDirty) {
try {
fDirtyRegionQueue.wait();
} catch (InterruptedException x) {
}
}
}
} while (isDirty);
}
/**
* Reset the background thread as the text viewer has been changed,
*/
public void reset() {
if (fDelay > 0) {
synchronized (this) {
fIsDirty= true;
fReset= true;
}
} else {
synchronized (this) {
fIsDirty= true;
}
synchronized (fDirtyRegionQueue) {
fDirtyRegionQueue.notifyAll();
}
}
reconcilerReset();
}
/**
* The background activity. Waits until there is something in the
* queue managing the changes that have been applied to the text viewer.
* Removes the first change from the queue and process it.
*
* Calls {@link AbstractReconciler#initialProcess()} on entrance.
*
*/
public void run() {
synchronized (fDirtyRegionQueue) {
try {
fDirtyRegionQueue.wait(fDelay);
} catch (InterruptedException x) {
}
}
if (fCanceled)
return;
initialProcess();
while (!fCanceled) {
synchronized (fDirtyRegionQueue) {
try {
fDirtyRegionQueue.wait(fDelay);
} catch (InterruptedException x) {
}
}
if (fCanceled)
break;
if (!isDirty())
continue;
synchronized (this) {
if (fReset) {
fReset= false;
continue;
}
}
DirtyRegion r= null;
synchronized (fDirtyRegionQueue) {
r= fDirtyRegionQueue.removeNextDirtyRegion();
}
fIsActive= true;
fProgressMonitor.setCanceled(false);
process(r);
synchronized (fDirtyRegionQueue) {
if (0 == fDirtyRegionQueue.getSize()) {
synchronized (this) {
fIsDirty= fProgressMonitor.isCanceled();
}
fDirtyRegionQueue.notifyAll();
}
}
fIsActive= false;
}
}
}
/**
* Internal document listener and text input listener.
*/
class Listener implements IDocumentListener, ITextInputListener {
/*
* @see IDocumentListener#documentAboutToBeChanged(DocumentEvent)
*/
public void documentAboutToBeChanged(DocumentEvent e) {
}
/*
* @see IDocumentListener#documentChanged(DocumentEvent)
*/
public void documentChanged(DocumentEvent e) {
if (fThread.isActive() || !fThread.isDirty() && fThread.isAlive()) {
if (!fIsAllowedToModifyDocument && Thread.currentThread() == fThread)
throw new UnsupportedOperationException("The reconciler thread is not allowed to modify the document"); //$NON-NLS-1$
aboutToBeReconciled();
}
/*
* The second OR condition handles the case when the document
* gets changed while still inside initialProcess().
*/
if (fThread.isActive() || fThread.isDirty() && fThread.isAlive())
fProgressMonitor.setCanceled(true);
if (fIsIncrementalReconciler)
createDirtyRegion(e);
fThread.reset();
}
/*
* @see ITextInputListener#inputDocumentAboutToBeChanged(IDocument, IDocument)
*/
public void inputDocumentAboutToBeChanged(IDocument oldInput, IDocument newInput) {
if (oldInput == fDocument) {
if (fDocument != null)
fDocument.removeDocumentListener(this);
if (fIsIncrementalReconciler) {
synchronized (fDirtyRegionQueue) {
fDirtyRegionQueue.purgeQueue();
}
if (fDocument != null && fDocument.getLength() > 0 && fThread.isDirty() && fThread.isAlive()) {
DocumentEvent e= new DocumentEvent(fDocument, 0, fDocument.getLength(), ""); //$NON-NLS-1$
createDirtyRegion(e);
fThread.reset();
fThread.suspendCallerWhileDirty();
}
}
fDocument= null;
}
}
/*
* @see ITextInputListener#inputDocumentChanged(IDocument, IDocument)
*/
public void inputDocumentChanged(IDocument oldInput, IDocument newInput) {
fDocument= newInput;
if (fDocument == null)
return;
reconcilerDocumentChanged(fDocument);
fDocument.addDocumentListener(this);
if (!fThread.isDirty())
aboutToBeReconciled();
startReconciling();
}
}
/** Queue to manage the changes applied to the text viewer. */
private DirtyRegionQueue fDirtyRegionQueue;
/** The background thread. */
private BackgroundThread fThread;
/** Internal document and text input listener. */
private Listener fListener;
/** The background thread delay. */
private int fDelay= 500;
/** Are there incremental reconciling strategies? */
private boolean fIsIncrementalReconciler= true;
/** The progress monitor used by this reconciler. */
private IProgressMonitor fProgressMonitor;
/**
* Tells whether this reconciler is allowed to modify the document.
* @since 3.2
*/
private boolean fIsAllowedToModifyDocument= true;
/** The text viewer's document. */
private IDocument fDocument;
/** The text viewer */
private ITextViewer fViewer;
/**
* Processes a dirty region. If the dirty region is null
the whole
* document is consider being dirty. The dirty region is partitioned by the
* document and each partition is handed over to a reconciling strategy registered
* for the partition's content type.
*
* @param dirtyRegion the dirty region to be processed
*/
abstract protected void process(DirtyRegion dirtyRegion);
/**
* Hook called when the document whose contents should be reconciled
* has been changed, i.e., the input document of the text viewer this
* reconciler is installed on. Usually, subclasses use this hook to
* inform all their reconciling strategies about the change.
*
* @param newDocument the new reconciler document
*/
abstract protected void reconcilerDocumentChanged(IDocument newDocument);
/**
* Creates a new reconciler without configuring it.
*/
protected AbstractReconciler() {
fProgressMonitor= new NullProgressMonitor();
}
/**
* Tells the reconciler how long it should wait for further text changes before
* activating the appropriate reconciling strategies.
*
* @param delay the duration in milliseconds of a change collection period.
*/
public void setDelay(int delay) {
fDelay= delay;
}
/**
* Tells the reconciler whether any of the available reconciling strategies
* is interested in getting detailed dirty region information or just in the
* fact that the document has been changed. In the second case, the reconciling
* can not incrementally be pursued.
*
* @param isIncremental indicates whether this reconciler will be configured with
* incremental reconciling strategies
*
* @see DirtyRegion
* @see IReconcilingStrategy
*/
public void setIsIncrementalReconciler(boolean isIncremental) {
fIsIncrementalReconciler= isIncremental;
}
/**
* Tells the reconciler whether it is allowed to change the document
* inside its reconciler thread.
*
* If this is set to false
an {@link UnsupportedOperationException}
* will be thrown when this restriction will be violated.
*
*
* @param isAllowedToModify indicates whether this reconciler is allowed to modify the document
* @since 3.2
*/
public void setIsAllowedToModifyDocument(boolean isAllowedToModify) {
fIsAllowedToModifyDocument= isAllowedToModify;
}
/**
* Sets the progress monitor of this reconciler.
*
* @param monitor the monitor to be used
*/
public void setProgressMonitor(IProgressMonitor monitor) {
Assert.isLegal(monitor != null);
fProgressMonitor= monitor;
}
/**
* Returns whether any of the reconciling strategies is interested in
* detailed dirty region information.
*
* @return whether this reconciler is incremental
*
* @see IReconcilingStrategy
*/
protected boolean isIncrementalReconciler() {
return fIsIncrementalReconciler;
}
/**
* Returns the input document of the text viewer this reconciler is installed on.
*
* @return the reconciler document
*/
protected IDocument getDocument() {
return fDocument;
}
/**
* Returns the text viewer this reconciler is installed on.
*
* @return the text viewer this reconciler is installed on
*/
protected ITextViewer getTextViewer() {
return fViewer;
}
/**
* Returns the progress monitor of this reconciler.
*
* @return the progress monitor of this reconciler
*/
protected IProgressMonitor getProgressMonitor() {
return fProgressMonitor;
}
/*
* @see IReconciler#install(ITextViewer)
*/
public void install(ITextViewer textViewer) {
Assert.isNotNull(textViewer);
fViewer= textViewer;
synchronized (this) {
if (fThread != null)
return;
fThread= new BackgroundThread(getClass().getName());
}
fDirtyRegionQueue= new DirtyRegionQueue();
fListener= new Listener();
fViewer.addTextInputListener(fListener);
// see bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=67046
// if the reconciler gets installed on a viewer that already has a document
// (e.g. when reusing editors), we force the listener to register
// itself as document listener, because there will be no input change
// on the viewer.
// In order to do that, we simulate an input change.
IDocument document= textViewer.getDocument();
if (document != null) {
fListener.inputDocumentAboutToBeChanged(fDocument, document);
fListener.inputDocumentChanged(fDocument, document);
}
}
/*
* @see IReconciler#uninstall()
*/
public void uninstall() {
if (fListener != null) {
fViewer.removeTextInputListener(fListener);
if (fDocument != null) {
fListener.inputDocumentAboutToBeChanged(fDocument, null);
fListener.inputDocumentChanged(fDocument, null);
}
fListener= null;
synchronized (this) {
// http://dev.eclipse.org/bugs/show_bug.cgi?id=19135
BackgroundThread bt= fThread;
fThread= null;
bt.cancel();
}
}
}
/**
* Creates a dirty region for a document event and adds it to the queue.
*
* @param e the document event for which to create a dirty region
*/
private void createDirtyRegion(DocumentEvent e) {
synchronized (fDirtyRegionQueue) {
if (e.getLength() == 0 && e.getText() != null) {
// Insert
fDirtyRegionQueue.addDirtyRegion(new DirtyRegion(e.getOffset(), e.getText().length(), DirtyRegion.INSERT, e.getText()));
} else if (e.getText() == null || e.getText().length() == 0) {
// Remove
fDirtyRegionQueue.addDirtyRegion(new DirtyRegion(e.getOffset(), e.getLength(), DirtyRegion.REMOVE, null));
} else {
// Replace (Remove + Insert)
fDirtyRegionQueue.addDirtyRegion(new DirtyRegion(e.getOffset(), e.getLength(), DirtyRegion.REMOVE, null));
fDirtyRegionQueue.addDirtyRegion(new DirtyRegion(e.getOffset(), e.getText().length(), DirtyRegion.INSERT, e.getText()));
}
}
}
/**
* Hook for subclasses which want to perform some
* action as soon as reconciliation is needed.
*
* Default implementation is to do nothing.
*
*
* @since 3.0
*/
protected void aboutToBeReconciled() {
}
/**
* This method is called on startup of the background activity. It is called only
* once during the life time of the reconciler. Clients may reimplement this method.
*/
protected void initialProcess() {
}
/**
* Forces the reconciler to reconcile the structure of the whole document.
* Clients may extend this method.
*/
protected void forceReconciling() {
if (fDocument != null) {
if (!fThread.isDirty()&& fThread.isAlive())
aboutToBeReconciled();
if (fThread.isActive())
fProgressMonitor.setCanceled(true);
if (fIsIncrementalReconciler) {
DocumentEvent e= new DocumentEvent(fDocument, 0, fDocument.getLength(), fDocument.get());
createDirtyRegion(e);
}
startReconciling();
}
}
/**
* Starts the reconciler to reconcile the queued dirty-regions.
* Clients may extend this method.
*/
protected synchronized void startReconciling() {
if (fThread == null)
return;
if (!fThread.isAlive()) {
try {
fThread.start();
} catch (IllegalThreadStateException e) {
// see https://bugs.eclipse.org/bugs/show_bug.cgi?id=40549
// This is the only instance where the thread is started; since
// we checked that it is not alive, it must be dead already due
// to a run-time exception or error. Exit.
}
} else {
fThread.reset();
}
}
/**
* Hook that is called after the reconciler thread has been reset.
*/
protected void reconcilerReset() {
}
/**
* Tells whether the code is running in this reconciler's
* background thread.
*
* @return true
if running in this reconciler's background thread
* @since 3.4
*/
protected boolean isRunningInReconcilerThread() {
return Thread.currentThread() == fThread;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy