/******************************************************************************* * 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); } }