blob: 3b214d02de2905857422e7b7f352616753f7119f [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2003 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Common Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/cpl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.jface.operation;
import java.lang.reflect.InvocationTargetException;
import org.eclipse.core.runtime.*;
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;
/**
* 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;
}
/* (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 {
// 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) {
if (!display.readAndDispatch())
display.sleep();
}
} else {
try {
join();
} catch (InterruptedException 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.
*
* @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>
*
* @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);
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;
}
}