| /******************************************************************************* |
| * 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; |
| } |
| } |