001: /*******************************************************************************
002: * Copyright (c) 2000, 2006 IBM Corporation and others.
003: * All rights reserved. This program and the accompanying materials
004: * are made available under the terms of the Eclipse Public License v1.0
005: * which accompanies this distribution, and is available at
006: * http://www.eclipse.org/legal/epl-v10.html
007: *
008: * Contributors:
009: * IBM Corporation - initial API and implementation
010: *******************************************************************************/package org.eclipse.jface.operation;
011:
012: import java.lang.reflect.InvocationTargetException;
013:
014: import org.eclipse.core.runtime.IProgressMonitor;
015: import org.eclipse.core.runtime.OperationCanceledException;
016: import org.eclipse.core.runtime.ProgressMonitorWrapper;
017: import org.eclipse.core.runtime.Assert;
018: import org.eclipse.swt.widgets.Display;
019:
020: /**
021: * Utility class for supporting modal operations.
022: * The runnable passed to the <code>run</code> method is executed in a
023: * separate thread, depending on the value of the passed fork argument.
024: * If the runnable is executed in a separate thread then the current thread
025: * either waits until the new thread ends or, if the current thread is the
026: * UI thread, it polls the SWT event queue and dispatches each event.
027: * <p>
028: * This class is not intended to be subclassed.
029: * </p>
030: */
031: public class ModalContext {
032:
033: /**
034: * Indicated whether ModalContext is in debug mode;
035: * <code>false</code> by default.
036: */
037: private static boolean debug = false;
038:
039: /**
040: * The number of nested modal runs, or 0 if not inside a modal run.
041: * This is global state.
042: */
043: private static int modalLevel = 0;
044:
045: /**
046: * Indicates whether operations should be run in a separate thread.
047: * Defaults to true.
048: * For internal debugging use, set to false to run operations in the calling thread.
049: */
050: private static boolean runInSeparateThread = true;
051:
052: /**
053: * Thread which runs the modal context.
054: */
055: private static class ModalContextThread extends Thread {
056: /**
057: * The operation to be run.
058: */
059: private IRunnableWithProgress runnable;
060:
061: /**
062: * The exception thrown by the operation starter.
063: */
064: private Throwable throwable;
065:
066: /**
067: * The progress monitor used for progress and cancelation.
068: */
069: private IProgressMonitor progressMonitor;
070:
071: /**
072: * The display used for event dispatching.
073: */
074: private Display display;
075:
076: /**
077: * Indicates whether to continue event queue dispatching.
078: */
079: private volatile boolean continueEventDispatching = true;
080:
081: /**
082: * The thread that forked this modal context thread.
083: *
084: * @since 3.1
085: */
086: private Thread callingThread;
087:
088: /**
089: * Creates a new modal context.
090: *
091: * @param operation the runnable to run
092: * @param monitor the progress monitor to use to display progress and receive
093: * requests for cancelation
094: * @param display the display to be used to read and dispatch events
095: */
096: private ModalContextThread(IRunnableWithProgress operation,
097: IProgressMonitor monitor, Display display) {
098: super ("ModalContext"); //$NON-NLS-1$
099: Assert.isTrue(monitor != null && display != null);
100: runnable = operation;
101: progressMonitor = new AccumulatingProgressMonitor(monitor,
102: display);
103: this .display = display;
104: this .callingThread = Thread.currentThread();
105: }
106:
107: /* (non-Javadoc)
108: * Method declared on Thread.
109: */
110: public void run() {
111: try {
112: if (runnable != null) {
113: runnable.run(progressMonitor);
114: }
115: } catch (InvocationTargetException e) {
116: throwable = e;
117: } catch (InterruptedException e) {
118: throwable = e;
119: } catch (RuntimeException e) {
120: throwable = e;
121: } catch (ThreadDeath e) {
122: // Make sure to propagate ThreadDeath, or threads will never fully terminate
123: throw e;
124: } catch (Error e) {
125: throwable = e;
126: } finally {
127: //notify the operation of change of thread of control
128: if (runnable instanceof IThreadListener) {
129: ((IThreadListener) runnable)
130: .threadChange(callingThread);
131: }
132:
133: // Make sure that all events in the asynchronous event queue
134: // are dispatched.
135: display.syncExec(new Runnable() {
136: public void run() {
137: // do nothing
138: }
139: });
140:
141: // Stop event dispatching
142: continueEventDispatching = false;
143:
144: // Force the event loop to return from sleep () so that
145: // it stops event dispatching.
146: display.asyncExec(null);
147: }
148: }
149:
150: /**
151: * Processes events or waits until this modal context thread terminates.
152: */
153: public void block() {
154: if (display == Display.getCurrent()) {
155: while (continueEventDispatching) {
156: // Run the event loop. Handle any uncaught exceptions caused
157: // by UI events.
158: try {
159: if (!display.readAndDispatch()) {
160: display.sleep();
161: }
162: }
163: // ThreadDeath is a normal error when the thread is dying. We must
164: // propagate it in order for it to properly terminate.
165: catch (ThreadDeath e) {
166: throw (e);
167: }
168: // For all other exceptions, log the problem.
169: catch (Throwable e) {
170: System.err
171: .println("Unhandled event loop exception during blocked modal context."); //$NON-NLS-1$
172: e.printStackTrace();
173: }
174: }
175: } else {
176: try {
177: join();
178: } catch (InterruptedException e) {
179: throwable = e;
180: }
181: }
182: }
183: }
184:
185: /**
186: * Returns whether the first progress monitor is the same as, or
187: * a wrapper around, the second progress monitor.
188: *
189: * @param monitor1 the first progress monitor
190: * @param monitor2 the second progress monitor
191: * @return <code>true</code> if the first is the same as, or
192: * a wrapper around, the second
193: * @see ProgressMonitorWrapper
194: */
195: public static boolean canProgressMonitorBeUsed(
196: IProgressMonitor monitor1, IProgressMonitor monitor2) {
197: if (monitor1 == monitor2) {
198: return true;
199: }
200:
201: while (monitor1 instanceof ProgressMonitorWrapper) {
202: monitor1 = ((ProgressMonitorWrapper) monitor1)
203: .getWrappedProgressMonitor();
204: if (monitor1 == monitor2) {
205: return true;
206: }
207: }
208: return false;
209: }
210:
211: /**
212: * Checks with the given progress monitor and throws
213: * <code>InterruptedException</code> if it has been canceled.
214: * <p>
215: * Code in a long-running operation should call this method
216: * regularly so that a request to cancel will be honored.
217: * </p>
218: * <p>
219: * Convenience for:
220: * <pre>
221: * if (monitor.isCanceled())
222: * throw new InterruptedException();
223: * </pre>
224: * </p>
225: *
226: * @param monitor the progress monitor
227: * @exception InterruptedException if cancelling the operation has been requested
228: * @see IProgressMonitor#isCanceled()
229: */
230: public static void checkCanceled(IProgressMonitor monitor)
231: throws InterruptedException {
232: if (monitor.isCanceled()) {
233: throw new InterruptedException();
234: }
235: }
236:
237: /**
238: * Returns the currently active modal context thread, or null if no modal context is active.
239: */
240: private static ModalContextThread getCurrentModalContextThread() {
241: Thread t = Thread.currentThread();
242: if (t instanceof ModalContextThread) {
243: return (ModalContextThread) t;
244: }
245: return null;
246: }
247:
248: /**
249: * Returns the modal nesting level.
250: * <p>
251: * The modal nesting level increases by one each time the
252: * <code>ModalContext.run</code> method is called within the
253: * dynamic scope of another call to <code>ModalContext.run</code>.
254: * </p>
255: *
256: * @return the modal nesting level, or <code>0</code> if
257: * this method is called outside the dynamic scope of any
258: * invocation of <code>ModalContext.run</code>
259: */
260: public static int getModalLevel() {
261: return modalLevel;
262: }
263:
264: /**
265: * Returns whether the given thread is running a modal context.
266: *
267: * @param thread The thread to be checked
268: * @return <code>true</code> if the given thread is running a modal context, <code>false</code> if not
269: */
270: public static boolean isModalContextThread(Thread thread) {
271: return thread instanceof ModalContextThread;
272: }
273:
274: /**
275: * Runs the given runnable in a modal context, passing it a progress monitor.
276: * <p>
277: * The modal nesting level is increased by one from the perspective
278: * of the given runnable.
279: * </p>
280: *<p>
281: * If the supplied operation implements <code>IThreadListener</code>, it
282: * will be notified of any thread changes required to execute the operation.
283: * Specifically, the operation will be notified of the thread that will call its
284: * <code>run</code> method before it is called, and will be notified of the
285: * change of control back to the thread calling this method when the operation
286: * completes. These thread change notifications give the operation an
287: * opportunity to transfer any thread-local state to the execution thread before
288: * control is transferred to the new thread.
289: *</p>
290: * @param operation the runnable to run
291: * @param fork <code>true</code> if the runnable should run in a separate thread,
292: * and <code>false</code> if in the same thread
293: * @param monitor the progress monitor to use to display progress and receive
294: * requests for cancelation
295: * @param display the display to be used to read and dispatch events
296: * @exception InvocationTargetException if the run method must propagate a checked exception,
297: * it should wrap it inside an <code>InvocationTargetException</code>; runtime exceptions and errors are automatically
298: * wrapped in an <code>InvocationTargetException</code> by this method
299: * @exception InterruptedException if the operation detects a request to cancel,
300: * using <code>IProgressMonitor.isCanceled()</code>, it should exit by throwing
301: * <code>InterruptedException</code>; this method propagates the exception
302: */
303: public static void run(IRunnableWithProgress operation,
304: boolean fork, IProgressMonitor monitor, Display display)
305: throws InvocationTargetException, InterruptedException {
306: Assert.isTrue(operation != null && monitor != null);
307:
308: modalLevel++;
309: try {
310: if (monitor != null) {
311: monitor.setCanceled(false);
312: }
313: // Is the runnable supposed to be execute in the same thread.
314: if (!fork || !runInSeparateThread) {
315: runInCurrentThread(operation, monitor);
316: } else {
317: ModalContextThread t = getCurrentModalContextThread();
318: if (t != null) {
319: Assert.isTrue(canProgressMonitorBeUsed(monitor,
320: t.progressMonitor));
321: runInCurrentThread(operation, monitor);
322: } else {
323: t = new ModalContextThread(operation, monitor,
324: display);
325: if (operation instanceof IThreadListener) {
326: ((IThreadListener) operation).threadChange(t);
327: }
328: t.start();
329: t.block();
330: Throwable throwable = t.throwable;
331: if (throwable != null) {
332: if (debug
333: && !(throwable instanceof InterruptedException)
334: && !(throwable instanceof OperationCanceledException)) {
335: System.err
336: .println("Exception in modal context operation:"); //$NON-NLS-1$
337: throwable.printStackTrace();
338: System.err.println("Called from:"); //$NON-NLS-1$
339: // Don't create the InvocationTargetException on the throwable,
340: // otherwise it will print its stack trace (from the other thread).
341: new InvocationTargetException(null)
342: .printStackTrace();
343: }
344: if (throwable instanceof InvocationTargetException) {
345: throw (InvocationTargetException) throwable;
346: } else if (throwable instanceof InterruptedException) {
347: throw (InterruptedException) throwable;
348: } else if (throwable instanceof OperationCanceledException) {
349: // See 1GAN3L5: ITPUI:WIN2000 - ModalContext converts OperationCancelException into InvocationTargetException
350: throw new InterruptedException(throwable
351: .getMessage());
352: } else {
353: throw new InvocationTargetException(
354: throwable);
355: }
356: }
357: }
358: }
359: } finally {
360: modalLevel--;
361: }
362: }
363:
364: /**
365: * Run a runnable. Convert all thrown exceptions to
366: * either InterruptedException or InvocationTargetException
367: */
368: private static void runInCurrentThread(
369: IRunnableWithProgress runnable,
370: IProgressMonitor progressMonitor)
371: throws InterruptedException, InvocationTargetException {
372: try {
373: if (runnable != null) {
374: runnable.run(progressMonitor);
375: }
376: } catch (InvocationTargetException e) {
377: throw e;
378: } catch (InterruptedException e) {
379: throw e;
380: } catch (OperationCanceledException e) {
381: throw new InterruptedException();
382: } catch (ThreadDeath e) {
383: // Make sure to propagate ThreadDeath, or threads will never fully terminate
384: throw e;
385: } catch (RuntimeException e) {
386: throw new InvocationTargetException(e);
387: } catch (Error e) {
388: throw new InvocationTargetException(e);
389: }
390: }
391:
392: /**
393: * Sets whether ModalContext is running in debug mode.
394: *
395: * @param debugMode <code>true</code> for debug mode,
396: * and <code>false</code> for normal mode (the default)
397: */
398: public static void setDebugMode(boolean debugMode) {
399: debug = debugMode;
400: }
401:
402: /**
403: * Sets whether ModalContext may process events (by calling <code>Display.readAndDispatch()</code>)
404: * while running operations. By default, ModalContext will process events while running operations.
405: * Use this method to disallow event processing temporarily.
406: * @param allowReadAndDispatch <code>true</code> (the default) if events may be processed while
407: * running an operation, <code>false</code> if Display.readAndDispatch() should not be called
408: * from ModalContext.
409: * @since 3.2
410: */
411: public static void setAllowReadAndDispatch(
412: boolean allowReadAndDispatch) {
413: // use a separate thread if and only if it is OK to spin the event loop
414: runInSeparateThread = allowReadAndDispatch;
415: }
416: }
|