| /******************************************************************************* |
| * 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.Assert; |
| import org.eclipse.core.runtime.IProgressMonitor; |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.core.runtime.OperationCanceledException; |
| import org.eclipse.core.runtime.ProgressMonitorWrapper; |
| import org.eclipse.core.runtime.Status; |
| import org.eclipse.jface.util.Policy; |
| 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) { |
| Policy |
| .getLog() |
| .log( |
| new Status( |
| IStatus.ERROR, |
| Policy.JFACE, |
| "Unhandled event loop exception during blocked modal context.",//$NON-NLS-1$ |
| e)); |
| } |
| } |
| } 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; |
| } |
| } |