diff options
Diffstat (limited to 'plugins/infra/org.eclipse.papyrus.infra.ui/src/org/eclipse/papyrus/infra/ui/util/UIUtil.java')
-rw-r--r-- | plugins/infra/org.eclipse.papyrus.infra.ui/src/org/eclipse/papyrus/infra/ui/util/UIUtil.java | 517 |
1 files changed, 517 insertions, 0 deletions
diff --git a/plugins/infra/org.eclipse.papyrus.infra.ui/src/org/eclipse/papyrus/infra/ui/util/UIUtil.java b/plugins/infra/org.eclipse.papyrus.infra.ui/src/org/eclipse/papyrus/infra/ui/util/UIUtil.java new file mode 100644 index 00000000000..1dd62db4ff5 --- /dev/null +++ b/plugins/infra/org.eclipse.papyrus.infra.ui/src/org/eclipse/papyrus/infra/ui/util/UIUtil.java @@ -0,0 +1,517 @@ +/* + * Copyright (c) 2014, 2016 CEA, Christian W. Damus, 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: + * Christian W. Damus (CEA) - Initial API and implementation + * Christian W. Damus - bug 399859 + * Christian W. Damus - bug 451557 + * Christian W. Damus - bug 485220 + * + */ +package org.eclipse.papyrus.infra.ui.util; + +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.Date; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Queue; +import java.util.concurrent.AbstractExecutorService; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executor; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; +import java.util.concurrent.FutureTask; +import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +import org.eclipse.core.databinding.observable.Realm; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.emf.common.util.AbstractTreeIterator; +import org.eclipse.emf.common.util.TreeIterator; +import org.eclipse.jface.operation.IRunnableContext; +import org.eclipse.jface.operation.IRunnableWithProgress; +import org.eclipse.papyrus.infra.tools.util.IExecutorService; +import org.eclipse.papyrus.infra.tools.util.Iterators2; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Display; +import org.eclipse.ui.IMemento; + +import com.google.common.collect.Iterators; + + +/** + * Miscellaneous general-purpose UI utilities. + */ +public class UIUtil { + + /** + * Not instantiable by clients. + */ + private UIUtil() { + super(); + } + + /** + * Create an executor that runs tasks asynchronously on the UI thread. If you need synchronous execution, schedule {@link Future}s and {@linkplain Future#get() wait} for them. + * + * @param display + * the display on which thread to execute tasks + * + * @return the executor + */ + public static IExecutorService createUIExecutor(Display display) { + return new DisplayExecutorService(display); + } + + /** + * Create an executor that runs tasks asynchronously on an observable {@link Realm}. If you need synchronous execution, schedule {@link Future}s and {@linkplain Future#get() wait} for them. + * + * @param realm + * the observable realm on which thread to execute tasks + * + * @return the executor + */ + public static ExecutorService createObservableExecutor(Realm realm) { + return new RealmExecutorService(realm); + } + + /** + * Creates a local memento that is not persistable and is not based on an XML document. This is useful for capturing the + * state of UI elements locally in cases where persistence of the memento is not required. + * + * @return the memento + */ + public static IMemento createLocalMemento() { + return LocalMemento.createMemento("__anonymous__", null); //$NON-NLS-1$ + } + + /** + * Synchronously invokes a {@code callable} on the given {@code display}'s thread. + * + * @param display + * a display + * @param callable + * a callable to invoke + * @return the callable's result (which, because this method is synchronous, will be ready) + * + * @see #asyncCall(Display, Callable) + * @see #createUIExecutor(Display) + */ + public static <V> Future<V> syncCall(Display display, Callable<V> callable) { + final FutureTask<V> result = new FutureTask<V>(callable); + display.syncExec(result); + return result; + } + + /** + * Synchronously invokes a {@code callable} on the default display thread. + * + * @param callable + * a callable to invoke + * @return the callable's result (which, because this method is synchronous, will be ready) + * + * @see #syncCall(Display, Callable) + * @see #asyncCall(Callable) + * @see #createUIExecutor(Display) + */ + public static <V> Future<V> syncCall(Callable<V> callable) { + return syncCall(Display.getDefault(), callable); + } + + /** + * Asynchronously invokes a {@code callable} on the given {@code display}'s thread. + * + * @param display + * a display + * @param callable + * a callable to invoke + * @return the callable's result + * + * @see #syncCall(Display, Callable) + * @see #createUIExecutor(Display) + */ + public static <V> Future<V> asyncCall(Display display, Callable<V> callable) { + final FutureTask<V> result = new FutureTask<V>(callable); + display.asyncExec(result); + return result; + } + + /** + * Asynchronously invokes a {@code callable} on the default display thread. + * + * @param callable + * a callable to invoke + * @return the callable's result + * + * @see #asyncCall(Display, Callable) + * @see #syncCall(Callable) + * @see #createUIExecutor(Display) + */ + public static <V> Future<V> asyncCall(Callable<V> callable) { + return asyncCall(Display.getDefault(), callable); + } + + /** + * Calls a {@code callable} in the given {@code context}. + * + * @param fork + * {@code true} if the runnable should be run in a separate thread, + * and {@code false} to run in the same thread + * @param cancelable + * {@code true} to enable the cancellation, and {@code false} to make the operation uncancellable + * @param runnable + * the runnable to run + * + * @exception InvocationTargetException + * wraps any exception or error which occurs + * while running the runnable + * @exception InterruptedException + * propagated by the context if the runnable + * acknowledges cancellation by throwing this exception. This should not be thrown + * if {@code cancelable} is {@code false}. + */ + public static <V> V call(IRunnableContext context, boolean fork, boolean cancelable, ICallableWithProgress<V> callable) throws InvocationTargetException, InterruptedException { + class RunnableWrapper implements IRunnableWithProgress { + final ICallableWithProgress<V> delegate; + + V result; + + RunnableWrapper(ICallableWithProgress<V> delegate) { + this.delegate = delegate; + } + + @Override + public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException { + result = delegate.call(monitor); + } + } + + RunnableWrapper wrapper = new RunnableWrapper(callable); + context.run(fork, cancelable, wrapper); + return wrapper.result; + } + + /** + * Obtains a simple executor that asynchronously executes at most one task on the default + * display thread. While any task is still pending execution on this executor, + * all others are silently discarded. This is useful for cases where, for example, UI + * refreshes are posted repeatedly from independent events that aren't aware of each other + * but where each refresh task would repeat the same work. + * + * @param display + * a display on which thread to execute tasks + * + * @return the executor + * + * @see #createAsyncOnceExecutor(Display) + */ + public static Executor createAsyncOnceExecutor() { + return createAsyncOnceExecutor(Display.getDefault()); + } + + /** + * Obtains a simple executor that asynchronously executes at most one task on the given {@code display}'s thread. While any task is still pending execution on this executor, + * all others are silently discarded. This is useful for cases where, for example, UI + * refreshes are posted repeatedly from independent events that aren't aware of each other + * but where each refresh task would repeat the same work. + * + * @param display + * a display on which thread to execute tasks + * + * @return the executor + */ + public static Executor createAsyncOnceExecutor(final Display display) { + return new Executor() { + private final AtomicBoolean pending = new AtomicBoolean(); + + @Override + public void execute(final Runnable task) { + if (pending.compareAndSet(false, true)) { + display.asyncExec(new Runnable() { + + @Override + public void run() { + pending.set(false); + task.run(); + } + }); + } + } + }; + } + + /** + * Obtains a tree iterator over all of the controls contained within a given {@code root} control, not including that {@code root}. + * + * @param root + * a control to iterate + * @return an unmodifiable iterator over all of its nested controls, which naturally will be empty if the {@code root} is not a {@link Composite} + */ + public static TreeIterator<Control> allChildren(Control root) { + return new AbstractTreeIterator<Control>(root, false) { + private static final long serialVersionUID = 1L; + + @Override + protected Iterator<? extends Control> getChildren(Object object) { + return (object instanceof Composite) ? Iterators.forArray(((Composite) object).getChildren()) : Iterators.<Control> emptyIterator(); + } + }; + } + + /** + * Obtains a tree iterator over all of the controls of a particular type contained within a given {@code root} control, not including that {@code root}. + * + * @param root + * a control to iterate + * @param type + * the type of children to include in the iteration + * + * @return an unmodifiable iterator over all of its nested controls, which naturally will be empty if the {@code root} is not a {@link Composite} + */ + public static <C extends Control> TreeIterator<C> allChildren(Control root, final Class<C> type) { + return Iterators2.filter(allChildren(root), type); + } + + // + // Nested types + // + + private static abstract class UIExecutorService extends AbstractExecutorService implements IExecutorService { + + private final Lock lock = new ReentrantLock(); + + private final Condition emptyCond = lock.newCondition(); + + private final Queue<RunnableWrapper> pending = new LinkedList<RunnableWrapper>(); + + private volatile boolean shutdown; + + UIExecutorService() { + super(); + } + + @Override + public void execute(Runnable command) { + if (isShutdown()) { + throw new RejectedExecutionException("Executor service is shut down"); //$NON-NLS-1$ + } + + asyncExec(enqueue(command)); + } + + @Override + public <V> V syncCall(Callable<V> callable) throws InterruptedException, ExecutionException { + class SyncResult implements Runnable { + V result; + ExecutionException fail; + + @Override + public void run() { + try { + result = callable.call(); + } catch (Exception e) { + fail = new ExecutionException(e); + fail.fillInStackTrace(); + } + } + } + + SyncResult result = new SyncResult(); + + syncExec(result); + + if (result.fail != null) { + throw result.fail; + } + + return result.result; + } + + abstract void asyncExec(Runnable runnable); + + @Override + public List<Runnable> shutdownNow() { + List<Runnable> result = new ArrayList<Runnable>(); + + shutdown(); + + for (Runnable dequeued = dequeue(); dequeued != null; dequeued = dequeue()) { + result.add(dequeued); + } + + return result; + } + + private RunnableWrapper enqueue(Runnable task) { + RunnableWrapper result = new RunnableWrapper(task); + + lock.lock(); + try { + boolean wasEmpty = pending.isEmpty(); + pending.offer(result); + if (wasEmpty) { + // Now not empty + emptyCond.signalAll(); + } + } finally { + lock.unlock(); + } + + return result; + } + + private RunnableWrapper dequeue() { + RunnableWrapper result = null; + + lock.lock(); + try { + result = pending.poll(); + if (result == null) { + // Now empty + emptyCond.signalAll(); + } + } finally { + lock.unlock(); + } + + return result; + } + + boolean dequeue(RunnableWrapper task) { + boolean result = false; + + lock.lock(); + try { + result = pending.remove(task); + if (result && pending.isEmpty()) { + // Now empty + emptyCond.signalAll(); + } + } finally { + lock.unlock(); + } + + return result; + } + + @Override + public void shutdown() { + shutdown = true; + } + + @Override + public boolean isTerminated() { + lock.lock(); + try { + return isShutdown() && pending.isEmpty(); + } finally { + lock.unlock(); + } + } + + @Override + public boolean isShutdown() { + return shutdown; + } + + @Override + public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException { + if (timeout < 0L) { + throw new IllegalArgumentException("negative timeout"); //$NON-NLS-1$ + } + + final Date deadline = (timeout == 0L) ? null : new Date(System.currentTimeMillis() + unit.toMillis(timeout)); + boolean result = false; + + lock.lock(); + try { + boolean stillWaiting = true; + for (result = isTerminated(); !result && stillWaiting; result = isTerminated()) { + if (deadline == null) { + emptyCond.await(); + } else { + stillWaiting = emptyCond.awaitUntil(deadline); + } + } + } finally { + lock.unlock(); + } + + return result; + } + + // + // Nested types + // + + private class RunnableWrapper implements Runnable { + + private final Runnable delegate; + + RunnableWrapper(Runnable delegate) { + this.delegate = delegate; + } + + @Override + public void run() { + // Don't run if I was cancelled by shutdown + if (dequeue(this)) { + delegate.run(); + } + } + } + }; + + private static class DisplayExecutorService extends UIExecutorService { + private final Display display; + + DisplayExecutorService(Display display) { + super(); + + this.display = display; + } + + @Override + void asyncExec(Runnable runnable) { + display.asyncExec(runnable); + } + + @Override + public void syncExec(Runnable task) { + display.syncExec(task); + } + } + + private static class RealmExecutorService extends UIExecutorService { + private final Realm realm; + + RealmExecutorService(Realm realm) { + super(); + + this.realm = realm; + } + + @Override + void asyncExec(Runnable runnable) { + realm.asyncExec(runnable); + } + + @Override + public void syncExec(Runnable task) { + realm.exec(task); + } + } +} |