package org.bndtools.utils.jface; import java.lang.reflect.InvocationTargetException; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.jface.operation.IRunnableContext; import org.eclipse.jface.operation.IRunnableWithProgress; import org.eclipse.swt.widgets.Display; /** * This class aims to improve the bedlam of Eclipse's progress dialogs that don't interrupt the threads, thus requiring * us to constantly poll the {@link IProgressMonitor#isCanceled()} method, which is not generally possible if we are * doing IO or other blocking tasks. */ public class ProgressRunner { /** * <p> * Execute the specified runnable within the runnable context. The work is done on a separate thread, and if the * user cancels the operation then the work thread will be interrupted with {@link Thread#interrupt()}. If custom * cancellation logic is required then the provided runnable can implement {@link CancellableTask} instead of * {@link IProgressMonitor} directly. * </p> * <p> * This method is synchronous and returns when the runnable completes, whether normally or cancellation or error. * </p> * * @param fork * @param runnable * @param context * @param display * @throws InvocationTargetException */ public static void execute(boolean fork, final IRunnableWithProgress runnable, IRunnableContext context, final Display display) throws InvocationTargetException { try { context.run(fork, true, new IRunnableWithProgress() { @Override public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException { OffGuiThreadProgressMonitor delegatingMonitor = new OffGuiThreadProgressMonitor(display, monitor); ProgressRunnerThread thread = new ProgressRunnerThread(runnable, delegatingMonitor); thread.start(); if (display.getThread() == Thread.currentThread()) { // We are running unforked on the display thread, so keep it alive and processing events while we ping the cancellation status. while (thread.isAlive()) { if (!display.readAndDispatch()) { if (monitor.isCanceled()) { thread.interrupt(); } display.sleep(); } } } else { // We are forked on another thread, so just sleep in between pings. while (thread.isAlive()) { if (monitor.isCanceled()) thread.interrupt(); Thread.sleep(500); } } InvocationTargetException exception = thread.exception; if (exception != null) throw exception; } }); } catch (InterruptedException e) { // ignore } } private static class ProgressRunnerThread extends Thread { private final IRunnableWithProgress runnable; private final IProgressMonitor monitor; private volatile InvocationTargetException exception = null; public ProgressRunnerThread(IRunnableWithProgress runnable, IProgressMonitor monitor) { super("Progress Runner"); this.runnable = runnable; this.monitor = monitor; } @Override public void run() { try { runnable.run(monitor); } catch (InvocationTargetException e) { this.exception = e; } catch (InterruptedException e) { // ignore } } @Override public void interrupt() { try { if (runnable instanceof CancellableTask) ((CancellableTask) runnable).cancel(); } finally { super.interrupt(); } } } private static class OffGuiThreadProgressMonitor implements IProgressMonitor { private final Display display; private final IProgressMonitor delegate; public OffGuiThreadProgressMonitor(Display display, IProgressMonitor delegate) { this.display = display; this.delegate = delegate; } @Override public void beginTask(final String name, final int totalWork) { display.asyncExec(new Runnable() { @Override public void run() { delegate.beginTask(name, totalWork); } }); } @Override public void done() { display.asyncExec(new Runnable() { @Override public void run() { delegate.done(); } }); } @Override public void internalWorked(final double work) { display.asyncExec(new Runnable() { @Override public void run() { delegate.internalWorked(work); } }); } @Override public boolean isCanceled() { return Thread.currentThread().isInterrupted(); } @Override public void setCanceled(boolean value) {} @Override public void setTaskName(final String name) { display.asyncExec(new Runnable() { @Override public void run() { delegate.setTaskName(name); } }); } @Override public void subTask(final String name) { display.asyncExec(new Runnable() { @Override public void run() { delegate.subTask(name); } }); } @Override public void worked(final int work) { display.asyncExec(new Runnable() { @Override public void run() { delegate.worked(work); } }); } } }