/******************************************************************************* * Copyright (c) 2012 Google, Inc. * 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: * Google, Inc. - initial API and implementation *******************************************************************************/ package com.windowtester.runtime.swt.internal.widgets; import java.util.concurrent.Callable; import org.eclipse.swt.widgets.Display; import com.windowtester.internal.debug.ThreadUtil; import com.windowtester.runtime.WaitTimedOutException; import com.windowtester.runtime.internal.concurrent.SafeCallable; /** * The object responsible for executing {@link Callable} on the SWT UI thread. Rather than * calling this class directly, it is preferred to call * {@link DisplayReference#execute(Callable, long)} * @param <T> */ class SWTUIExecutor<T> { /** * Internal lock used when accessing {@link #executing} and {@link #exception} */ private static final Object LOCK = new Object(); /** * The callable that the receiver executes on the SWT UI thread. */ private final Callable<T> callable; /** * Indicates whether the callable is executing */ private boolean executing = false; /** * The value returned by the {@link Callable#call()} method. */ private T result; /** * An exception that occurred when executing the {@link Callable#call()} method or * <code>null</code> if none */ private Throwable exception; /** * Construct a new instance for executing the specified {@link Callable} on the SWT UI * thread. * * @param callable the callable (not <code>null</code>) */ SWTUIExecutor(Callable<T> callable) { if (callable == null) throw new IllegalArgumentException(); this.callable = callable; } /** * Called on any thread to execute the callable's {@link #runInUI()} method on the SWT * UI thread. Do not call this method directly, but rather * {@link SWTUI#execute(Callable, Display, long)}. * * @param maxWaitTime the maximum number of milliseconds to wait for the SWT UI thread * to execute the callable. This value is ignored if this method is called * on the SWT UI thread. * @param interval the number of milliseconds to sleep before again checking to see if * the callable has finished executing. * @throws SWTUIException if there is an exception when executing the callable on * the SWT UI thread * @throws WaitTimedOutException if the SWT UI thread does not execute the callable * with specified number of milliseconds, but never thrown if this method * is called on the SWT UI thread. */ T run(Display display, long maxWaitTime, int interval) { // Initialize the execution fields synchronized (LOCK) { if (executing) throw new IllegalStateException("Callable is already executing"); executing = true; exception = null; } // Execute the callable on the UI thread if (Thread.currentThread() == display.getThread()) execute(); else display.asyncExec(new Runnable() { public void run() { execute(); } }); // Wait for the result, exception, or timeout long startTime = System.currentTimeMillis(); while (true) { synchronized (LOCK) { if (exception != null) throw new SWTUIException(exception); if (!executing) return result; } long deltaTime = System.currentTimeMillis() - startTime; if (deltaTime > maxWaitTime) { String errMsg = "Quit waiting for UI thread to execute callable " + System.currentTimeMillis() + "\nElapse time: " + deltaTime + "\nMax wait time: " + maxWaitTime; System.err.println(errMsg); System.err.println("UI Thread: " + display.getThread()); System.err.println("This Thread: " + Thread.currentThread()); ThreadUtil.printStackTraces(); throw new WaitTimedOutException(errMsg); } try { Thread.sleep(interval); } catch (InterruptedException e) { // ignored... fall through } } } /** * Call the callable's {@link Callable#call()} and capture the return value. If an * exception occurs, and the callable implements {@link SafeCallable} then call the * callable's {@link SafeCallable#handleException(Exception)} with the exception that * occurred. */ private void execute() { try { result = callable.call(); } catch (Exception ex) { if (callable instanceof SafeCallable<?>) { try { result = ((SafeCallable<T>) callable).handleException(ex); } catch (Throwable ex2) { synchronized (LOCK) { exception = ex2; } } } else { synchronized (LOCK) { exception = ex; } } } finally { synchronized (LOCK) { executing = false; } } } }