package org.marketcetera.photon.commons.ui;
import java.lang.reflect.InvocationTargetException;
import java.util.List;
import java.util.concurrent.Callable;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.MultiStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.SubMonitor;
import org.eclipse.jface.dialogs.ErrorDialog;
import org.eclipse.jface.operation.IRunnableContext;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jface.window.IShellProvider;
import org.eclipse.jface.window.SameShellProvider;
import org.eclipse.jface.wizard.IWizardContainer;
import org.marketcetera.photon.commons.Validate;
import org.marketcetera.util.log.I18NBoundMessage;
import org.marketcetera.util.misc.ClassVersion;
import com.google.common.collect.Lists;
/* $License$ */
/**
* JFace utility methods.
*
* @author <a href="mailto:will@marketcetera.com">Will Horn</a>
* @version $Id: JFaceUtils.java 16154 2012-07-14 16:34:05Z colin $
* @since 2.0.0
*/
@ClassVersion("$Id: JFaceUtils.java 16154 2012-07-14 16:34:05Z colin $")
public final class JFaceUtils {
/**
* Runs the operation in a wizard container. This is equivalent to:
*
* <pre>
* runModalWithErrorDialog(container, new SameShellProvider(container.getShell()),
* operation, cancelable, failureMessage)
* </pre>
*
* @param container
* the wizard container that provides the context and shell
* @param operation
* the operation to run
* @param cancelable
* whether the operation should be cancelable
* @param failureMessage
* the localized message for logging failures
* @return true if the operation succeeded, false if it was canceled or an
* error occurred
* @throws IllegalArgumentException
* if any parameter is null
*/
public static boolean runModalWithErrorDialog(IWizardContainer container,
IRunnableWithProgress operation, boolean cancelable,
I18NBoundMessage failureMessage) {
Validate.notNull(container, "container"); //$NON-NLS-1$
return runModalWithErrorDialog(container, new SameShellProvider(
container.getShell()), operation, cancelable, failureMessage);
}
/**
* Runs the operation on the given context. There are three possible
* outcomes:
* <ol>
* <li>The operation is successful - this method will return true.</li>
* <li>The operation was canceled - failureMessage will be logged at the
* info level and this method will return false.</li>
* <li>The operation threw an exception - failureMessage will be logged at
* the error level, a dialog will be presented to the user with the details
* of the failure, and this method will return false.</li>
* </ol>
*
* @param context
* the context in which to run the operation
* @param shellProvider
* provides the shell for the error dialog
* @param operation
* the operation to run
* @param cancelable
* whether the operation should be cancelable
* @param failureMessage
* the localized message for logging failures
* @return true if the operation succeeded, false if it was canceled or an
* error occurred
* @throws IllegalArgumentException
* if any parameter is null
*/
public static boolean runModalWithErrorDialog(
final IRunnableContext context, IShellProvider shellProvider,
final IRunnableWithProgress operation, final boolean cancelable,
final I18NBoundMessage failureMessage) {
Validate.notNull(context, "context", //$NON-NLS-1$
shellProvider, "shellProvider", //$NON-NLS-1$
operation, "operation", //$NON-NLS-1$
failureMessage, "failureMessage"); //$NON-NLS-1$
return runWithErrorDialog(shellProvider, new Callable<Boolean>() {
@Override
public Boolean call() throws Exception {
try {
context.run(true, cancelable, operation);
return true;
} catch (InterruptedException e) {
/*
* The runnable responded to cancellation. This should only
* happen if cancelable is true. Since the exception
* occurred on a separate thread where task was forked,
* there is no need to do anything here.
*/
failureMessage.info(JFaceUtils.class, e);
return false;
} catch (InvocationTargetException e) {
Throwable realException = e.getCause();
if (realException instanceof Error) {
throw (Error) realException;
} else if (realException instanceof Exception) {
throw (Exception) realException;
} else {
throw e;
}
}
}
}, failureMessage);
}
/**
* Runs the operation. There are two possible outcomes:
* <ol>
* <li>The operation is successful - this method will return true.</li>
* <li>The operation threw an exception - failureMessage will be logged at
* the error level, a dialog will be presented to the user with the details
* of the failure, and this method will return false.</li>
* </ol>
*
* @param shellProvider
* provides the shell for the error dialog
* @param operation
* the operation to run
* @param failureMessage
* the localized message for logging failures
* @return true if the operation succeeded, false if an error occurred
* @throws IllegalArgumentException
* if any parameter is null
*/
public static boolean runWithErrorDialog(IShellProvider shellProvider,
Callable<Boolean> operation, I18NBoundMessage failureMessage) {
Validate.notNull(shellProvider, "shellProvider", //$NON-NLS-1$
operation, "operation", //$NON-NLS-1$
failureMessage, "failureMessage"); //$NON-NLS-1$
try {
return operation.call();
} catch (Exception e) {
failureMessage.error(JFaceUtils.class, e);
List<IStatus> nestedDetails = Lists.newArrayList();
Throwable current = e;
while (current != null) {
String message = current.getLocalizedMessage();
if (message != null) {
nestedDetails.add(new Status(Status.ERROR,
CommonsUI.PLUGIN_ID, message));
}
current = current.getCause();
}
int size = nestedDetails.size();
IStatus status;
if (size == 0) {
status = new Status(Status.ERROR, CommonsUI.PLUGIN_ID,
Messages.JFACE_UTILS_SEE_LOG.getText(e.getClass()
.getSimpleName()));
} else if (size == 1) {
status = nestedDetails.get(0);
} else {
status = new MultiStatus(CommonsUI.PLUGIN_ID, IStatus.ERROR,
nestedDetails.subList(1, size).toArray(
new IStatus[size - 1]), nestedDetails.get(0)
.getMessage(), null);
}
ErrorDialog.openError(shellProvider.getShell(),
Messages.JFACE_UTILS_OPERATION_FAILED__DIALOG_TITLE
.getText(), null, status);
return false;
}
}
/**
* Utility method to wrap a Callable in an {@link IRunnableWithProgress}.
* Use this when you have a top level task that does not support the
* progress monitor.
*
* @param callable
* the task to run
* @param taskName
* the name for the task, may be null
* @return an {@link IRunnableWithProgress} wrapping the task
* @throws IllegalArgumentException
* if callable is null
*/
public static IRunnableWithProgress wrap(final Callable<Void> callable,
final String taskName) {
Validate.notNull(callable, "callable"); //$NON-NLS-1$
return new IRunnableWithProgress() {
@Override
public void run(IProgressMonitor monitor)
throws InvocationTargetException, InterruptedException {
monitor.beginTask(taskName, IProgressMonitor.UNKNOWN);
try {
callable.call();
} catch (Exception e) {
throw new InvocationTargetException(e);
} finally {
monitor.done();
}
}
};
}
/**
* Convenience utility that creates a {@link SubMonitor}, handles checked
* exceptions, and calls {@link IProgressMonitor#done()} on the parent
* progress monitor.
*
* @param runnable
* the code that does the actual work
* @return a runnable that can safely be used
*/
public static IRunnableWithProgress safeRunnableWithProgress(
final IUnsafeRunnableWithProgress runnable) {
Validate.notNull(runnable, "runnable"); //$NON-NLS-1$
return new IRunnableWithProgress() {
@Override
public void run(IProgressMonitor monitor)
throws InvocationTargetException, InterruptedException {
try {
runnable.run(monitor);
} catch (InterruptedException e) {
// propagate InterruptedException since it has special
// meaning, i.e. cancellation
throw e;
} catch (Exception e) {
throw new InvocationTargetException(e);
} finally {
if (monitor != null) {
monitor.done();
}
}
}
};
}
/**
* Interface to be used with
* {@link JFaceUtils#safeRunnableWithProgress(IUnsafeRunnableWithProgress)}.
*/
@ClassVersion("$Id: JFaceUtils.java 16154 2012-07-14 16:34:05Z colin $")
public interface IUnsafeRunnableWithProgress {
/**
* Runs this operation. Progress should be reported to the given
* progress sub monitor. A request to cancel the operation should be
* honored and acknowledged by throwing
* <code>InterruptedException</code>.
*
* @param monitor
* the progress monitor to use for reporting progress to the
* user. It is the caller's responsibility to call done() on
* the given monitor. Accepts null, indicating that no
* progress should be reported and that the operation cannot
* be canceled.
* @throws InterruptedException
* if the operation detects a request to cancel, using
* <code>IProgressMonitor.isCanceled()</code>, it should
* exit by throwing <code>InterruptedException</code>
* @throws Exception
* if an exception occurs
*/
void run(IProgressMonitor monitor) throws Exception;
}
private JFaceUtils() {
throw new AssertionError("non-instantiable"); //$NON-NLS-1$
}
}