blob: fc96df28d0d05d68fdb9e53ec68fa30f284c1052 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2005 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.operation;
import java.lang.reflect.InvocationTargetException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.ProgressMonitorWrapper;
import org.eclipse.jface.util.Assert;
import org.eclipse.swt.widgets.Display;
/**
* Utility class for supporting modal operations.
* The runnable passed to the <code>run</code> method is executed in a
* separate thread, depending on the value of the passed fork argument.
* If the runnable is executed in a separate thread then the current thread
* either waits until the new thread ends or, if the current thread is the
* UI thread, it polls the SWT event queue and dispatches each event.
* <p>
* This class is not intended to be subclassed.
* </p>
*/
public class ModalContext {
/**
* Indicated whether ModalContext is in debug mode;
* <code>false</code> by default.
*/
private static boolean debug = false;
/**
* The number of nested modal runs, or 0 if not inside a modal run.
* This is global state.
*/
private static int modalLevel = 0;
/**
* Indicates whether operations should be run in a separate thread.
* Defaults to true.
* For internal debugging use, set to false to run operations in the calling thread.
*/
private static boolean runInSeparateThread = true;
/**
* Thread which runs the modal context.
*/
private static class ModalContextThread extends Thread {
/**
* The operation to be run.
*/
private IRunnableWithProgress runnable;
/**
* The exception thrown by the operation starter.
*/
private Throwable throwable;
/**
* The progress monitor used for progress and cancelation.
*/
private IProgressMonitor progressMonitor;
/**
* The display used for event dispatching.
*/
private Display display;
/**
* Indicates whether to continue event queue dispatching.
*/
private volatile boolean continueEventDispatching = true;
/**
* The thread that forked this modal context thread.
*
* @since 3.1
*/
private Thread callingThread;
/**
* Creates a new modal context.
*
* @param operation the runnable to run
* @param monitor the progress monitor to use to display progress and receive
* requests for cancelation
* @param display the display to be used to read and dispatch events
*/
private ModalContextThread(IRunnableWithProgress operation,
IProgressMonitor monitor, Display display) {
super("ModalContext"); //$NON-NLS-1$
Assert.isTrue(monitor != null && display != null);
runnable = operation;
progressMonitor = new AccumulatingProgressMonitor(monitor, display);
this.display = display;
this.callingThread = Thread.currentThread();
}
/* (non-Javadoc)
* Method declared on Thread.
*/
public void run() {
try {
if (runnable != null)
runnable.run(progressMonitor);
} catch (InvocationTargetException e) {
throwable = e;
} catch (InterruptedException e) {
throwable = e;
} catch (RuntimeException e) {
throwable = e;
} catch (ThreadDeath e) {
// Make sure to propagate ThreadDeath, or threads will never fully terminate
throw e;
} catch (Error e) {
throwable = e;
} finally {
//notify the operation of change of thread of control
if (runnable instanceof IThreadListener)
((IThreadListener)runnable).threadChange(callingThread);
// Make sure that all events in the asynchronous event queue
// are dispatched.
display.syncExec(new Runnable() {
public void run() {
// do nothing
}
});
// Stop event dispatching
continueEventDispatching = false;
// Force the event loop to return from sleep () so that
// it stops event dispatching.
display.asyncExec(null);
}
}
/**
* Processes events or waits until this modal context thread terminates.
*/
public void block() {
if (display == Display.getCurrent()) {
while (continueEventDispatching) {
// Run the event loop. Handle any uncaught exceptions caused
// by UI events.
try {
if (!display.readAndDispatch())
display.sleep();
}
// ThreadDeath is a normal error when the thread is dying. We must
// propagate it in order for it to properly terminate.
catch (ThreadDeath e) {
throw (e);
}
// For all other exceptions, log the problem.
catch (Throwable e) {
System.err.println("Unhandled event loop exception during blocked modal context."); //$NON-NLS-1$
e.printStackTrace();
}
}
} else {
try {
join();
} catch (InterruptedException e) {
throwable = e;
}
}
}
}
/**
* Returns whether the first progress monitor is the same as, or
* a wrapper around, the second progress monitor.
*
* @param monitor1 the first progress monitor
* @param monitor2 the second progress monitor
* @return <code>true</code> if the first is the same as, or
* a wrapper around, the second
* @see ProgressMonitorWrapper
*/
public static boolean canProgressMonitorBeUsed(IProgressMonitor monitor1,
IProgressMonitor monitor2) {
if (monitor1 == monitor2)
return true;
while (monitor1 instanceof ProgressMonitorWrapper) {
monitor1 = ((ProgressMonitorWrapper) monitor1)
.getWrappedProgressMonitor();
if (monitor1 == monitor2)
return true;
}
return false;
}
/**
* Checks with the given progress monitor and throws
* <code>InterruptedException</code> if it has been canceled.
* <p>
* Code in a long-running operation should call this method
* regularly so that a request to cancel will be honored.
* </p>
* <p>
* Convenience for:
* <pre>
* if (monitor.isCanceled())
* throw new InterruptedException();
* </pre>
* </p>
*
* @param monitor the progress monitor
* @exception InterruptedException if cancelling the operation has been requested
* @see IProgressMonitor#isCanceled()
*/
public static void checkCanceled(IProgressMonitor monitor)
throws InterruptedException {
if (monitor.isCanceled())
throw new InterruptedException();
}
/**
* Returns the currently active modal context thread, or null if no modal context is active.
*/
private static ModalContextThread getCurrentModalContextThread() {
Thread t = Thread.currentThread();
if (t instanceof ModalContextThread)
return (ModalContextThread) t;
return null;
}
/**
* Returns the modal nesting level.
* <p>
* The modal nesting level increases by one each time the
* <code>ModalContext.run</code> method is called within the
* dynamic scope of another call to <code>ModalContext.run</code>.
* </p>
*
* @return the modal nesting level, or <code>0</code> if
* this method is called outside the dynamic scope of any
* invocation of <code>ModalContext.run</code>
*/
public static int getModalLevel() {
return modalLevel;
}
/**
* Returns whether the given thread is running a modal context.
*
* @param thread The thread to be checked
* @return <code>true</code> if the given thread is running a modal context, <code>false</code> if not
*/
public static boolean isModalContextThread(Thread thread) {
return thread instanceof ModalContextThread;
}
/**
* Runs the given runnable in a modal context, passing it a progress monitor.
* <p>
* The modal nesting level is increased by one from the perspective
* of the given runnable.
* </p>
*<p>
* If the supplied operation implements <code>IThreadListener</code>, it
* will be notified of any thread changes required to execute the operation.
* Specifically, the operation will be notified of the thread that will call its
* <code>run</code> method before it is called, and will be notified of the
* change of control back to the thread calling this method when the operation
* completes. These thread change notifications give the operation an
* opportunity to transfer any thread-local state to the execution thread before
* control is transferred to the new thread.
*</p>
* @param operation the runnable to run
* @param fork <code>true</code> if the runnable should run in a separate thread,
* and <code>false</code> if in the same thread
* @param monitor the progress monitor to use to display progress and receive
* requests for cancelation
* @param display the display to be used to read and dispatch events
* @exception InvocationTargetException if the run method must propagate a checked exception,
* it should wrap it inside an <code>InvocationTargetException</code>; runtime exceptions and errors are automatically
* wrapped in an <code>InvocationTargetException</code> by this method
* @exception InterruptedException if the operation detects a request to cancel,
* using <code>IProgressMonitor.isCanceled()</code>, it should exit by throwing
* <code>InterruptedException</code>; this method propagates the exception
*/
public static void run(IRunnableWithProgress operation, boolean fork,
IProgressMonitor monitor, Display display)
throws InvocationTargetException, InterruptedException {
Assert.isTrue(operation != null && monitor != null);
modalLevel++;
try {
if (monitor != null)
monitor.setCanceled(false);
// Is the runnable supposed to be execute in the same thread.
if (!fork || !runInSeparateThread) {
runInCurrentThread(operation, monitor);
} else {
ModalContextThread t = getCurrentModalContextThread();
if (t != null) {
Assert.isTrue(canProgressMonitorBeUsed(monitor,
t.progressMonitor));
runInCurrentThread(operation, monitor);
} else {
t = new ModalContextThread(operation, monitor, display);
if (operation instanceof IThreadListener)
((IThreadListener)operation).threadChange(t);
t.start();
t.block();
Throwable throwable = t.throwable;
if (throwable != null) {
if (debug
&& !(throwable instanceof InterruptedException)
&& !(throwable instanceof OperationCanceledException)) {
System.err
.println("Exception in modal context operation:"); //$NON-NLS-1$
throwable.printStackTrace();
System.err.println("Called from:"); //$NON-NLS-1$
// Don't create the InvocationTargetException on the throwable,
// otherwise it will print its stack trace (from the other thread).
new InvocationTargetException(null)
.printStackTrace();
}
if (throwable instanceof InvocationTargetException) {
throw (InvocationTargetException) throwable;
} else if (throwable instanceof InterruptedException) {
throw (InterruptedException) throwable;
} else if (throwable instanceof OperationCanceledException) {
// See 1GAN3L5: ITPUI:WIN2000 - ModalContext converts OperationCancelException into InvocationTargetException
throw new InterruptedException(throwable
.getMessage());
} else {
throw new InvocationTargetException(throwable);
}
}
}
}
} finally {
modalLevel--;
}
}
/**
* Run a runnable. Convert all thrown exceptions to
* either InterruptedException or InvocationTargetException
*/
private static void runInCurrentThread(IRunnableWithProgress runnable,
IProgressMonitor progressMonitor) throws InterruptedException,
InvocationTargetException {
try {
if (runnable != null)
runnable.run(progressMonitor);
} catch (InvocationTargetException e) {
throw e;
} catch (InterruptedException e) {
throw e;
} catch (OperationCanceledException e) {
throw new InterruptedException();
} catch (ThreadDeath e) {
// Make sure to propagate ThreadDeath, or threads will never fully terminate
throw e;
} catch (RuntimeException e) {
throw new InvocationTargetException(e);
} catch (Error e) {
throw new InvocationTargetException(e);
}
}
/**
* Sets whether ModalContext is running in debug mode.
*
* @param debugMode <code>true</code> for debug mode,
* and <code>false</code> for normal mode (the default)
*/
public static void setDebugMode(boolean debugMode) {
debug = debugMode;
}
/**
* Sets whether ModalContext may process events (by calling <code>Display.readAndDispatch()</code>)
* while running operations. By default, ModalContext will process events while running operations.
* Use this method to disallow event processing temporarily.
* @param allowReadAndDispatch <code>true</code> (the default) if events may be processed while
* running an operation, <code>false</code> if Display.readAndDispatch() should not be called
* from ModalContext.
* @since 3.2
*/
public static void setAllowReadAndDispatch(boolean allowReadAndDispatch) {
// use a separate thread if and only if it is OK to spin the event loop
runInSeparateThread = allowReadAndDispatch;
}
}