/**
* <copyright>
*
* Copyright (c) 2010-2016 Thales Global Services S.A.S.
* 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:
* Thales Global Services S.A.S. - initial API and implementation
*
* </copyright>
*/
package org.eclipse.emf.diffmerge.ui.util;
import java.lang.reflect.InvocationTargetException;
import java.util.Collections;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.Path;
import org.eclipse.emf.common.command.AbstractCommand;
import org.eclipse.emf.common.command.AbstractCommand.NonDirtying;
import org.eclipse.emf.common.command.CommandStack;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.diffmerge.ui.Messages;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.edit.command.ChangeCommand;
import org.eclipse.emf.edit.domain.EditingDomain;
import org.eclipse.emf.transaction.Transaction;
import org.eclipse.emf.transaction.TransactionalCommandStack;
import org.eclipse.emf.transaction.TransactionalEditingDomain;
import org.eclipse.emf.transaction.impl.InternalTransactionalEditingDomain;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jface.operation.IThreadListener;
import org.eclipse.swt.custom.BusyIndicator;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.progress.IProgressService;
/**
* Utility class for miscellaneous technical concerns.
* @author Olivier Constant
*/
public final class MiscUtil {
/**
* A command that may execute in a transactional context but does not support undo/redo.
* It essentially makes sense together with a consistent usage of CommandStack#flush().
*/
protected static class ForgettingCommand extends AbstractCommand implements NonDirtying {
/** The runnable to execute */
private final Runnable _runnable;
/**
* Constructor
* @param runnable_p a non-null runnable
*/
protected ForgettingCommand(Runnable runnable_p) {
super();
_runnable = runnable_p;
}
/**
* @see org.eclipse.emf.common.command.AbstractCommand#canUndo()
*/
@Override
public boolean canUndo() {
return false;
}
/**
* @see org.eclipse.emf.common.command.AbstractCommand#prepare()
*/
@Override
protected boolean prepare() {
return true;
}
/**
* @see org.eclipse.emf.common.command.Command#execute()
*/
public void execute() {
_runnable.run();
}
/**
* @see org.eclipse.emf.common.command.Command#redo()
*/
public void redo() {
// Nothing
}
}
/**
* An IRunnableWithProgress to be used by an IProgressService, that is capable to modify
* models in a given editing domain regardless of the nature of the editing domain (transactional/
* non-transactional/null) or the execution context (within or outside a transaction).
* The principle is to wrap the behavior in a privileged runnable while executing in the calling
* thread, if relevant, then provide a progress monitor at the time of the execution of the
* behavior by the thread provided by the IProgressService.
*/
protected static class RunnableWithProgressOnModel implements IRunnableWithProgress, IThreadListener {
/** The optional editing domain in which the behavior operates */
protected final EditingDomain _domain;
/** Whether changes done by the behavior must be recorded if possible */
protected final boolean _recordChanges;
/** An non-null runnable that wraps the delegate behavior for the sake of transactional concerns */
protected Runnable _wrappingRunnable;
/** An initially null monitor for the execution of the delegate behavior */
protected IProgressMonitor _monitor;
/**
* Constructor
* @param behavior_p a non-null behavior that ignores transactional aspects
* @param label_p an optional user-friendly name for the behavior
* @param domain_p an optional editing domain in which the behavior operates
* @param recordChanges_p whether changes done by the behavior must be recorded if possible
*/
public RunnableWithProgressOnModel(final IRunnableWithProgress behavior_p,
final String label_p, EditingDomain domain_p, boolean recordChanges_p) {
_domain = domain_p;
_recordChanges = recordChanges_p;
_wrappingRunnable = new Runnable() {
/**
* To execute the behavior in a transaction if necessary
* @see java.lang.Runnable#run()
*/
public void run() {
// Start a transaction if necessary
MiscUtil.execute(_domain, label_p, new Runnable() {
/**
* The adaptation from IRunnableWithProgress to Runnable
* @see java.lang.Runnable#run()
*/
public void run() {
try {
behavior_p.run(_monitor); // Instance variable must be set at that time
} catch (InvocationTargetException e) {
throw new RuntimeException(e); // No choice
} catch (InterruptedException e) {
throw new RuntimeException(e); // No choice
}
}
}, _recordChanges);
}
};
_monitor = null;
}
/**
* @see org.eclipse.jface.operation.IThreadListener#threadChange(java.lang.Thread)
*/
public void threadChange(Thread thread_p) {
// We are still in the calling thread
if (MiscUtil.isRunningInActiveTransaction(_domain)) {
// Create a privileged runnable that can be executed by the thread provided by
// the IProgressService
TransactionalEditingDomain ted = (TransactionalEditingDomain)_domain;
_wrappingRunnable = ted.createPrivilegedRunnable(_wrappingRunnable);
}
}
/**
* @see org.eclipse.jface.operation.IRunnableWithProgress#run(org.eclipse.core.runtime.IProgressMonitor)
*/
public void run(IProgressMonitor monitor_p) throws InvocationTargetException,
InterruptedException {
_monitor = monitor_p; // Becomes accessible by the wrapping runnable
_wrappingRunnable.run();
}
}
/**
* Return the concatenation of the String representation of the given objects
*/
public static String buildString(Object... objects_p) {
StringBuilder builder = new StringBuilder();
for (Object object : objects_p) {
if (null != object)
builder.append(object);
}
return builder.toString();
}
/**
* Execute the given behavior (Runnable) that affects models in the given editing domain
* and ignores transactional aspects
* @param domain_p an optional editing domain
* @param label_p an optional user-friendly name for the behavior
* @param runnable_p a non-null runnable
* @param recordChanges_p whether changes must be recorded if possible
*/
public static void execute(EditingDomain domain_p, String label_p,
final Runnable runnable_p, boolean recordChanges_p) {
if (recordChanges_p && domain_p != null)
executeOnDomain(domain_p, label_p, runnable_p);
else
executeAndForget(domain_p, runnable_p);
}
/**
* Execute the given runnable that affects models in the given editing domain,
* not supporting undo/redo and forgetting all changes. Must be used with caution,
* in contexts where it makes sense, because no rollback is possible.
* If the editing domain is missing, then the runnable is executed as-is.
* @param domain_p an optional editing domain
* @param runnable_p a non-null runnable
*/
public static void executeAndForget(EditingDomain domain_p, Runnable runnable_p) {
if (domain_p != null && !isRunningInActiveTransaction(domain_p)) {
AbstractCommand cmd = new ForgettingCommand(runnable_p);
CommandStack cStack = domain_p.getCommandStack();
if (cStack instanceof TransactionalCommandStack) {
try {
((TransactionalCommandStack)cStack).execute(cmd,
Collections.singletonMap(Transaction.OPTION_NO_UNDO, Boolean.TRUE));
} catch (Exception e) {
// No rollback is possible
}
} else {
cStack.execute(cmd);
}
} else {
runnable_p.run();
}
}
/**
* Execute the given runnable that affects models in the given editing domain
* and ignores transactional aspects
* @param domain_p a non-null editing domain
* @param label_p an optional user-friendly label to appear in the undo/redo menu items
* @param runnable_p a non-null runnable
*/
public static void executeOnDomain(EditingDomain domain_p, String label_p,
final Runnable runnable_p) {
if (isRunningInActiveTransaction(domain_p)) {
// Already in the active transaction
runnable_p.run();
} else {
final String commandLabel = label_p != null? label_p: Messages.MiscUtil_DefaultCommandName;
ChangeCommand cmd = new ChangeCommand(domain_p.getResourceSet()) {
{
label = commandLabel;
}
/**
* @see org.eclipse.emf.edit.command.ChangeCommand#doExecute()
*/
@Override
protected void doExecute() {
runnable_p.run();
}
};
domain_p.getCommandStack().execute(cmd);
}
}
/**
* Execute the given behavior (Runnable), that affects models in the given
* editing domain and ignores transactional aspects, with a busy cursor.
* @param domain_p an optional editing domain
* @param label_p an optional user-friendly name for the behavior
* @param runnable_p a non-null runnable
* @param recordChanges_p whether changes must be recorded if possible
* @param display_p a non-null display for the busy cursor
*/
public static void executeWithBusyCursor(final EditingDomain domain_p,
final String label_p, final Runnable runnable_p, final boolean recordChanges_p,
Display display_p) {
Runnable actualOperation = new Runnable() {
/**
* @see java.lang.Runnable#run()
*/
public void run() {
MiscUtil.execute(domain_p, label_p, runnable_p, recordChanges_p);
}
};
if (MiscUtil.isRunningInActiveTransaction(domain_p))
actualOperation = ((TransactionalEditingDomain)domain_p).createPrivilegedRunnable(actualOperation);
BusyIndicator.showWhile(display_p, actualOperation);
}
/**
* Execute the given behavior (IRunnableWithProgress), that affects models in the given
* editing domain and ignores transactional aspects, in a progress dialog.
* @param domain_p an optional editing domain
* @param label_p an optional user-friendly name for the behavior
* @param behavior_p a non-null runnable with progress
* @param recordChanges_p whether changes must be recorded if possible
*/
public static void executeWithProgress(EditingDomain domain_p, String label_p,
IRunnableWithProgress behavior_p, boolean recordChanges_p)
throws InvocationTargetException, InterruptedException {
IRunnableWithProgress operation = new RunnableWithProgressOnModel(
behavior_p, label_p, domain_p, recordChanges_p);
IProgressService progress = PlatformUI.getWorkbench().getProgressService();
progress.busyCursorWhile(operation);
}
/**
* Return the file that holds the given resource, if any.
* If the file is not local to the platform and allowExternal_p is true,
* then the file is imported (not copied) in the workspace via an ad-hoc
* project.
* @param resource_p a non-null resource
* @param allowExternal_p whether the file is allowed to be external to the workspace
* @return a potentially null Eclipse file
*/
public static IFile getFileFor(Resource resource_p, boolean allowExternal_p) {
IFile result = null;
URI uri = resource_p.getURI();
if (uri != null) {
IWorkspace wk = ResourcesPlugin.getWorkspace();
if (wk != null && wk.getRoot() != null) {
if (uri.isPlatformResource()) {
// Resource in workspace
String platformResourcePath = uri.toPlatformString(true);
result = wk.getRoot().getFile(new Path(platformResourcePath));
} else if (allowExternal_p) {
// Resource from external file
IProject project = wk.getRoot().getProject("ExternalFiles"); //$NON-NLS-1$
try {
if (!project.exists())
project.create(null);
if (!project.isOpen())
project.open(null);
IPath path = new Path(uri.toFileString());
result = project.getFile(path.lastSegment());
result.createLink(path, IResource.NONE, null);
} catch (CoreException e) {
// Just proceed
}
}
}
}
return result;
}
/**
* Return whether the current code executes in the context of the currently
* active transaction on the given editing domain
* @param domain_p a potentially null object
*/
public static boolean isRunningInActiveTransaction(EditingDomain domain_p) {
boolean result = false;
if (domain_p instanceof InternalTransactionalEditingDomain) {
Transaction activeTransaction =
((InternalTransactionalEditingDomain)domain_p).getActiveTransaction();
if (activeTransaction != null)
result = Thread.currentThread() == activeTransaction.getOwner();
}
return result;
}
}