Package org.eclipse.jface.operation

Source Code of org.eclipse.jface.operation.ModalContext

/*******************************************************************************
* Copyright (c) 2000, 2009 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>
* @noinstantiate This class is not intended to be instantiated by clients.
* @noextend This class is not intended to be subclassed by clients.
*/
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) {
          Throwable exception =
            invokeThreadListener(((IThreadListener) runnable), callingThread);
         
          //Forward it if we don't already have one
          if(exception != null && throwable == null)
            throwable = exception;
        }

        // 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()) {
        int exceptionCount = 0;
        while (continueEventDispatching) {
          // Run the event loop. Handle any uncaught exceptions caused
          // by UI events.
          try {
            if (!display.readAndDispatch()) {
              display.sleep();
            }
            exceptionCount = 0;
          }
          // 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 t) {
            if (t instanceof VirtualMachineError) {
              throw (VirtualMachineError) t;
            }
            exceptionCount++;
            // We're counting exceptions in client code, such as asyncExecs,
            // so be generous about how many may fail consecutively before we
            // give up.
            if (exceptionCount > 50 || display.isDisposed()) {
                      if (t instanceof RuntimeException) {
                throw (RuntimeException) t;
              } else if (t instanceof Error) {
                throw (Error) t;
              } else {
                throw new RuntimeException(t);
              }
            }
            Policy
                .getLog()
                .log(
                    new Status(
                        IStatus.ERROR,
                        Policy.JFACE,
                        "Unhandled event loop exception during blocked modal context.",//$NON-NLS-1$
                        t));
          }
        }
      } 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);
          Throwable listenerException = null;
          if (operation instanceof IThreadListener) {
            listenerException = invokeThreadListener((IThreadListener) operation, t);
          }
         
          if(listenerException == null){
            t.start();
            t.block();
          }
          else {
            if(t.throwable == null)
              t.throwable = listenerException;
          }
          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--;
    }
  }

  /**
   * Invoke the ThreadListener if there are any errors or RuntimeExceptions
   * return them.
   *
   * @param listener
   * @param switchingThread
   *            the {@link Thread} being switched to
   */
  static Throwable invokeThreadListener(IThreadListener listener,
      Thread switchingThread) {
    try {
      listener.threadChange(switchingThread);
    } catch (ThreadDeath e) {
      // Make sure to propagate ThreadDeath, or threads will never
      // fully terminate
      throw e;
    } catch (Error e) {
      return e;
    }catch (RuntimeException e) {
      return e;
    }
    return null;
  }

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

Related Classes of org.eclipse.jface.operation.ModalContext

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.