/******************************************************************************* * Copyright (c) 2000, 2006 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.*; import org.eclipse.rwt.lifecycle.UICallBack; 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 1.0 */ private Thread callingThread; private String callBackId = this.getClass().getName() + this.hashCode(); /** * 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(); } public void run() { UICallBack.runNonUIThreadWithFakeContext( display, new Runnable() { public void run() { doRun(); } } ); } /* (non-Javadoc) * Method declared on Thread. */ public void doRun() { UICallBack.activate( callBackId ); 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 { try { //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); } finally { UICallBack.deactivate( callBackId ); } } } /** * 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 1.0 */ public static void setAllowReadAndDispatch(boolean allowReadAndDispatch) { // use a separate thread if and only if it is OK to spin the event loop runInSeparateThread = allowReadAndDispatch; } }