blob: 42ba19d34b4755f7047c51765d4094a03ae5f862 [file] [log] [blame]
/*****************************************************************************
* Copyright (c) 2013, 2017 CEA LIST.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* CEA LIST - Initial API and implementation
*****************************************************************************/
package org.eclipse.papyrus.cdo.internal.ui.util;
import java.lang.reflect.Method;
import java.util.concurrent.Callable;
import java.util.concurrent.Executor;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
import org.eclipse.gmf.runtime.common.ui.util.DisplayUtils;
import org.eclipse.papyrus.cdo.ui.Activator;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.PlatformUI;
/**
* This is the UIUtil type. Enjoy.
*/
public class UIUtil {
/**
* Not instantiable by clients.
*/
private UIUtil() {
super();
}
public static Executor uiSafeExecutor() {
return UISafeExecutor.INSTANCE;
}
/**
* Posts a runnable to run later on the UI thread, whether the current
* thread is the UI or not. In fact, the name "later" suggests that usually
* this method is useful in code that is on the UI thread but needs to
* ensure that some block runs later, after other events have been
* processed.
*
* @param runnable
* a block of code to run later
*/
public static void later(Runnable runnable) {
DisplayUtils.getDisplay().asyncExec(runnable);
}
/**
* Posts a callable that needs access to the UI to run asynchronously on the
* specified {@code display} thread.
*
* @param display
* the display on which to post a computation asynchronously
* @param callable
* a computation to run asynchronously on the UI thread
* @return the future result of the {@code callable}
*/
public static <T> Future<T> asyncCall(Display display, Callable<T> callable) {
final FutureTask<T> result = new FutureTask<T>(callable);
display.asyncExec(result);
return result;
}
/**
* A UI-safe execution of a {@code callable} that needs access to the UI. If
* the current thread is the UI thread, then the result will be available
* immediately. Otherwise, the {@code callable} will be invoked
* asynchronously and the result will be available some time later.
*
* @param callable
* a computation to run on the UI thread
* @return the future result of the {@code callable}
*/
public static <T> Future<T> call(Callable<T> callable) {
final FutureTask<T> result = new FutureTask<T>(callable);
Display display = Display.getCurrent();
if (display != null) {
result.run();
} else {
DisplayUtils.getDisplay().asyncExec(result);
}
return result;
}
/**
* <p>
* Queries whether the current thread is the UI thread and, if not, posts an runnable to re-dispatch the calling method asynchronously on the UI thread.
* </p>
* <p>
* The intended usage pattern is thus:
* </p>
*
* <pre>
* public void doSomethingToTheUI(Object arg1, String arg2) {
* if (UIUtil.ensureUIThread(this, arg1, arg2) {
* // do stuff here with arg1 and arg2 that requires
* // the UI thread
* }
* }
* </pre>
*
* @param receiver
* the receiver of the method to be (potentially) re-dispatched.
* This is the object calling the {@code ensureUIThread()} utility
* @param arguments
* the arguments passed in the method invocation to be
* re-dispatched
*
* @return {@code true} if the current thread is the UI thread and the
* calling method may proceed; {@code false} if the method
* invocation was re-dispatched asynchronously and the calling
* method must not proceed
*/
public static boolean ensureUIThread(final Object receiver,
final Object... arguments) {
final Display display = DisplayUtils.getDisplay();
boolean result = Display.getCurrent() == display;
if (!result) {
// find the calling method and post its invocation asynchronously
String callingMethodName = null;
StackTraceElement[] stack = Thread.currentThread().getStackTrace();
for (int i = 0; i < stack.length; i++) {
StackTraceElement next = stack[i];
if (UIUtil.class.getName().equals(next.getClassName())
&& "ensureUIThread".equals(next.getMethodName())) { //$NON-NLS-1$
callingMethodName = stack[i + 1].getMethodName();
break;
}
}
if (callingMethodName == null) {
throw new IllegalStateException("Invalid stack trace"); //$NON-NLS-1$
}
final Method method = findMethod(receiver.getClass(),
callingMethodName, arguments);
if (method == null) {
throw new IllegalStateException("Could not find calling method"); //$NON-NLS-1$
}
method.setAccessible(true);
display.asyncExec(new Runnable() {
@Override
public void run() {
try {
method.invoke(receiver, arguments);
} catch (Exception e) {
Activator.log.error(e);
}
}
});
}
return result;
}
private static Method findMethod(Class<?> owner, String name,
Object[] arguments) {
Method result = null;
Class<?>[] actual = new Class<?>[arguments.length];
for (int i = 0; i < arguments.length; i++) {
actual[i] = (arguments[i] == null)
? null
: arguments[i].getClass();
}
Method[] declared = owner.getDeclaredMethods();
out: for (int i = 0; i < declared.length; i++) {
Method next = declared[i];
if (name.equals(next.getName())) {
Class<?>[] parameters = next.getParameterTypes();
for (int j = 0; j < parameters.length; j++) {
if ((actual[j] != null)
&& !parameters[j].isAssignableFrom(actual[j])) {
continue out;
}
}
result = next;
break out;
}
}
return result;
}
//
// Nested types
//
private static final class UISafeExecutor
implements Executor {
static final UISafeExecutor INSTANCE = new UISafeExecutor();
@Override
public void execute(Runnable command) {
Display workbenchDisplay = PlatformUI.getWorkbench().getDisplay();
if (Display.getCurrent() == workbenchDisplay) {
command.run();
} else {
workbenchDisplay.asyncExec(command);
}
}
}
}