/*******************************************************************************
* Copyright (c) 2012, 2015 Obeo.
* 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:
* Obeo - initial API and implementation
*******************************************************************************/
package org.eclipse.emf.compare.domain.impl;
import com.google.common.collect.ImmutableCollection;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.emf.common.command.BasicCommandStack;
import org.eclipse.emf.common.command.Command;
import org.eclipse.emf.common.command.CommandStack;
import org.eclipse.emf.common.notify.Notifier;
import org.eclipse.emf.common.util.BasicMonitor;
import org.eclipse.emf.compare.Comparison;
import org.eclipse.emf.compare.Diff;
import org.eclipse.emf.compare.command.ICompareCommandStack;
import org.eclipse.emf.compare.command.ICompareCopyCommand;
import org.eclipse.emf.compare.command.impl.CompareCommandStack;
import org.eclipse.emf.compare.command.impl.DualCompareCommandStack;
import org.eclipse.emf.compare.command.impl.MergeAllNonConflictingCommand;
import org.eclipse.emf.compare.command.impl.MergeCommand;
import org.eclipse.emf.compare.command.impl.TransactionalDualCompareCommandStack;
import org.eclipse.emf.compare.domain.ICompareEditingDomain;
import org.eclipse.emf.compare.domain.IMergeRunnable;
import org.eclipse.emf.compare.internal.domain.IMergeAllNonConflictingRunnable;
import org.eclipse.emf.compare.internal.utils.ComparisonUtil;
import org.eclipse.emf.compare.merge.BatchMerger;
import org.eclipse.emf.compare.merge.IBatchMerger;
import org.eclipse.emf.compare.merge.IMerger;
import org.eclipse.emf.compare.merge.IMerger.Registry;
import org.eclipse.emf.compare.provider.EMFCompareEditPlugin;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.change.util.ChangeRecorder;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.edit.domain.AdapterFactoryEditingDomain;
import org.eclipse.emf.edit.domain.EditingDomain;
import org.eclipse.emf.edit.provider.IDisposable;
import org.eclipse.emf.transaction.TransactionalEditingDomain;
import org.eclipse.emf.transaction.impl.AbstractTransactionalCommandStack;
import org.eclipse.emf.transaction.util.TransactionUtil;
/**
* Default implementation that use a change recorder in the background to record the changes made by executed
* commands.
*
* @author <a href="mailto:mikael.barbero@obeo.fr">Mikael Barbero</a>
*/
public class EMFCompareEditingDomain implements ICompareEditingDomain, IDisposable {
/** The change recorder instance. */
private final ChangeRecorder fChangeRecorder;
/** The notifiers on which the change recorder will be installed. */
private final ImmutableCollection<Notifier> fNotifiers;
/** The command stack on which the merge commands will be executed. */
private final ICompareCommandStack fCommandStack;
/** List of domains we've created ourselves and should thus cleanup ourselves. */
private final List<TransactionalEditingDomain> disposableDomains;
/**
* Creates a new instance with the given notifiers to be listen to when something will be changed.
*
* @param left
* the left root notifier of the comparison (i.e. the
* {@link org.eclipse.emf.compare.scope.IComparisonScope#getLeft()}
* @param right
* the right root notifier of the comparison (i.e. the
* {@link org.eclipse.emf.compare.scope.IComparisonScope#getRight()}
* @param ancestor
* the ancestor root notifier of the comparison (i.e. the
* {@link org.eclipse.emf.compare.scope.IComparisonScope#getOrigin()}
* @param commandStack
* the command stack to be used to track execution of commands.
*/
public EMFCompareEditingDomain(Notifier left, Notifier right, Notifier ancestor,
ICompareCommandStack commandStack) {
if (ancestor == null) {
fNotifiers = ImmutableList.of(left, right);
} else {
fNotifiers = ImmutableList.of(left, right, ancestor);
}
fCommandStack = commandStack;
fChangeRecorder = new ChangeRecorder();
fChangeRecorder.setResolveProxies(false);
disposableDomains = new ArrayList<TransactionalEditingDomain>();
}
/**
* Returns the resource set containing the given notifier, the given {@code notifier} if it is a
* {@link ResourceSet}, <code>null</code> otherwise.
*
* @param notifier
* the notifier from which we have to look for a {@link ResourceSet}.
* @return the resource set containing the given notifier, the given {@code notifier} if it is a
* {@link ResourceSet}, <code>null</code> otherwise
*/
private static ResourceSet getResourceSet(Notifier notifier) {
ResourceSet resourceSet = null;
if (notifier instanceof ResourceSet) {
resourceSet = (ResourceSet)notifier;
} else if (notifier instanceof Resource) {
resourceSet = ((Resource)notifier).getResourceSet();
} else if (notifier instanceof EObject) {
Resource eResource = ((EObject)notifier).eResource();
if (eResource != null) {
resourceSet = eResource.getResourceSet();
}
} else {
// impossible as of today
}
return resourceSet;
}
/**
* Creates a new compare editing domain on the given notifier with an appropriate
* {@link ICompareCommandStack} set up on it.
*
* @param left
* the left notifier. Should not be <code>null</code>.
* @param right
* the right notifier. Should not be <code>null</code>.
* @param ancestor
* the ancestor notifier. May be <code>null</code>.
* @return a new compare editing domain on the given notifier.
*/
public static ICompareEditingDomain create(Notifier left, Notifier right, Notifier ancestor) {
boolean hadLeftED = getExistingEditingDomain(left) != null;
boolean hadRightED = getExistingEditingDomain(right) != null;
EditingDomain leftED = getOrCreateEditingDomain(left);
EditingDomain rightED = getOrCreateEditingDomain(right);
final ICompareEditingDomain domain;
if (leftED != null && rightED != null) {
CommandStack leftCommandStack = leftED.getCommandStack();
CommandStack rightCommandStack = rightED.getCommandStack();
ICompareCommandStack commandStack;
if (leftCommandStack instanceof AbstractTransactionalCommandStack
&& rightCommandStack instanceof AbstractTransactionalCommandStack) {
commandStack = new TransactionalDualCompareCommandStack(
(AbstractTransactionalCommandStack)leftCommandStack,
(AbstractTransactionalCommandStack)rightCommandStack);
} else if (leftCommandStack instanceof BasicCommandStack
&& rightCommandStack instanceof BasicCommandStack) {
commandStack = new DualCompareCommandStack((BasicCommandStack)leftCommandStack,
(BasicCommandStack)rightCommandStack);
} else {
EMFCompareEditPlugin.getPlugin().getLog().log(new Status(IStatus.WARNING,
EMFCompareEditPlugin.PLUGIN_ID,
"Command stacks of the editing domain of " //$NON-NLS-1$
+ left + " and " //$NON-NLS-1$
+ right
+ " are not instances of BasicCommandStack, nor AbstractTransactionalCommandStack, therefore, they will not be used as backing command stacks for the current merge session.")); //$NON-NLS-1$
commandStack = new CompareCommandStack(new BasicCommandStack());
}
domain = new EMFCompareEditingDomain(left, right, ancestor, commandStack);
} else {
domain = create(left, right, ancestor, new BasicCommandStack());
}
if (!hadLeftED && leftED instanceof TransactionalEditingDomain) {
((EMFCompareEditingDomain)domain).addDomainToDispose((TransactionalEditingDomain)leftED);
}
if (!hadRightED && rightED instanceof TransactionalEditingDomain) {
((EMFCompareEditingDomain)domain).addDomainToDispose((TransactionalEditingDomain)rightED);
}
return domain;
}
/**
* Return an existing editing domain associated with the given {@link Notifier}.
*
* @param notifier
* the notifier from which the editing domain has to be linked.
* @return an editing domain associated with the given {@link Notifier} if any.
*/
private static EditingDomain getExistingEditingDomain(Notifier notifier) {
EditingDomain editingDomain = TransactionUtil.getEditingDomain(notifier);
if (editingDomain == null) {
editingDomain = AdapterFactoryEditingDomain.getEditingDomainFor(notifier);
}
return editingDomain;
}
/**
* Returns an editing domain associated with the given {@link Notifier}. It will first look for a
* {@link TransactionalEditingDomain} then for an {@link AdapterFactoryEditingDomain}. It neither is found
* a new {@link TransactionalEditingDomain} is created.
*
* @param notifier
* the notifier from which the editing domain has to be linked.
* @return an editing domain associated with the given {@link Notifier}
*/
private static EditingDomain getOrCreateEditingDomain(Notifier notifier) {
EditingDomain editingDomain = getExistingEditingDomain(notifier);
if (editingDomain == null) {
ResourceSet resourceSet = getResourceSet(notifier);
if (resourceSet != null) {
editingDomain = TransactionalEditingDomain.Factory.INSTANCE.createEditingDomain(resourceSet);
}
}
return editingDomain;
}
/**
* Equivalent to {@code create(left, right, ancestor, commandStack, null)}.
*
* @param left
* the left notifier. Should not be <code>null</code>.
* @param right
* the right notifier. Should not be <code>null</code>.
* @param ancestor
* the ancestor notifier. May be <code>null</code>.
* @param commandStack
* a command stack to which merge command will be delegated to.
* @return a newly created compare editing domain.
*/
public static ICompareEditingDomain create(Notifier left, Notifier right, Notifier ancestor,
CommandStack commandStack) {
return create(left, right, ancestor, commandStack, null);
}
/**
* Creates a new compare editing domain on the given notifier with an appropriate
* {@link ICompareCommandStack} set up on it.
*
* @param left
* the left notifier. Should not be <code>null</code>.
* @param right
* the right notifier. Should not be <code>null</code>.
* @param ancestor
* the ancestor notifier. May be <code>null</code>.
* @param leftCommandStack
* a command stack to which merge to left command will be delegated to.
* @param rightCommandStack
* a command stack to which merge to irght command will be delegated to.
* @return a newly created compare editing domain.
*/
public static ICompareEditingDomain create(Notifier left, Notifier right, Notifier ancestor,
CommandStack leftCommandStack, CommandStack rightCommandStack) {
final ICompareCommandStack commandStack;
if (leftCommandStack == null && rightCommandStack != null) {
if (rightCommandStack instanceof ICompareCommandStack) {
commandStack = (ICompareCommandStack)rightCommandStack;
} else {
commandStack = new CompareCommandStack(rightCommandStack);
}
} else if (leftCommandStack != null && rightCommandStack == null) {
if (leftCommandStack instanceof ICompareCommandStack) {
commandStack = (ICompareCommandStack)leftCommandStack;
} else {
commandStack = new CompareCommandStack(leftCommandStack);
}
} else if (leftCommandStack instanceof BasicCommandStack
&& rightCommandStack instanceof BasicCommandStack) {
commandStack = new DualCompareCommandStack((BasicCommandStack)leftCommandStack,
(BasicCommandStack)rightCommandStack);
} else {
commandStack = new CompareCommandStack(new BasicCommandStack());
}
return new EMFCompareEditingDomain(left, right, ancestor, commandStack);
}
/**
* {@inheritDoc}
*
* @see org.eclipse.emf.compare.domain.ICompareEditingDomain#dispose()
*/
public void dispose() {
fChangeRecorder.dispose();
if (fCommandStack instanceof IDisposable) {
((IDisposable)fCommandStack).dispose();
}
for (TransactionalEditingDomain domain : disposableDomains) {
domain.dispose();
}
}
/**
* {@inheritDoc}
*
* @see org.eclipse.emf.compare.domain.ICompareEditingDomain#getCommandStack()
*/
public ICompareCommandStack getCommandStack() {
return fCommandStack;
}
/**
* {@inheritDoc}
*
* @see org.eclipse.emf.compare.domain.ICompareEditingDomain#createCopyCommand(org.eclipse.emf.compare.Diff,
* boolean, org.eclipse.emf.compare.merge.IMerger.Registry)
* @since 3.0
*/
public Command createCopyCommand(List<? extends Diff> differences, boolean leftToRight,
IMerger.Registry mergerRegistry) {
ImmutableSet.Builder<Notifier> notifiersBuilder = ImmutableSet.builder();
for (Diff diff : differences) {
notifiersBuilder.add(ComparisonUtil.getComparison(diff));
}
ImmutableSet<Notifier> notifiers = notifiersBuilder.addAll(fNotifiers).build();
IMergeRunnable runnable = new IMergeRunnable() {
public void merge(List<? extends Diff> diffs, boolean lTR, Registry registry) {
final IBatchMerger merger = new BatchMerger(registry);
if (lTR) {
merger.copyAllLeftToRight(diffs, new BasicMonitor());
} else {
merger.copyAllRightToLeft(diffs, new BasicMonitor());
}
}
};
return new MergeCommand(fChangeRecorder, notifiers, differences, leftToRight, mergerRegistry,
runnable);
}
/**
* {@inheritDoc}
*
* @see org.eclipse.emf.compare.domain.ICompareEditingDomain#createCopyCommand(java.util.List, boolean,
* org.eclipse.emf.compare.merge.IMerger.Registry,
* org.eclipse.emf.compare.command.ICompareCopyCommand.IMergeRunnable)
*/
public ICompareCopyCommand createCopyCommand(List<? extends Diff> differences, boolean leftToRight,
Registry mergerRegistry, IMergeRunnable runnable) {
ImmutableSet.Builder<Notifier> notifiersBuilder = ImmutableSet.builder();
for (Diff diff : differences) {
notifiersBuilder.add(ComparisonUtil.getComparison(diff));
}
ImmutableSet<Notifier> notifiers = notifiersBuilder.addAll(fNotifiers).build();
return new MergeCommand(fChangeRecorder, notifiers, differences, leftToRight, mergerRegistry,
runnable);
}
/**
* Creates a command that will merge all non-conflicting differences in the given direction.
* <p>
* A "non-conflicting" difference is any difference that is not in a real conflict with another <u>and</u>
* that does not, directly or indirectly, depend on the merge of a difference that is in conflict itself.
* </p>
* <p>
* Note that only the differences originating from the "source" side of the chosen merge direction will be
* considered.
* </p>
*
* @param comparison
* The comparison which differences to merge.
* @param leftToRight
* The direction in which we should merge the differences.
* @param mergerRegistry
* The registry to query for specific mergers for each difference.
* @param runnable
* the runnable to execute for the actual merge operation.
* @return The copy command, ready for use.
* @since 4.1
*/
public ICompareCopyCommand createCopyAllNonConflictingCommand(Comparison comparison, boolean leftToRight,
IMerger.Registry mergerRegistry, IMergeAllNonConflictingRunnable runnable) {
ImmutableSet.Builder<Notifier> notifiersBuilder = ImmutableSet.builder();
notifiersBuilder.add(comparison);
ImmutableSet<Notifier> notifiers = notifiersBuilder.addAll(fNotifiers).build();
return new MergeAllNonConflictingCommand(fChangeRecorder, notifiers, comparison, leftToRight,
mergerRegistry, runnable);
}
/**
* {@inheritDoc}
*
* @see org.eclipse.emf.compare.domain.ICompareEditingDomain#getChangeRecorder()
*/
public ChangeRecorder getChangeRecorder() {
return fChangeRecorder;
}
/**
* Sets up a {@link TransactionalEditingDomain} for disposal along with {@code this}.
*
* @param domain
* The domain that should be disposed when we are.
*/
private void addDomainToDispose(TransactionalEditingDomain domain) {
disposableDomains.add(domain);
}
}