diff options
9 files changed, 2381 insertions, 2134 deletions
diff --git a/plugins/org.eclipse.emf.compare.diagram.ide.ui/src/org/eclipse/emf/compare/diagram/ide/ui/internal/contentmergeviewer/diagram/DiagramContentMergeViewer.java b/plugins/org.eclipse.emf.compare.diagram.ide.ui/src/org/eclipse/emf/compare/diagram/ide/ui/internal/contentmergeviewer/diagram/DiagramContentMergeViewer.java index 7915f3da5..166aa4ccc 100644 --- a/plugins/org.eclipse.emf.compare.diagram.ide.ui/src/org/eclipse/emf/compare/diagram/ide/ui/internal/contentmergeviewer/diagram/DiagramContentMergeViewer.java +++ b/plugins/org.eclipse.emf.compare.diagram.ide.ui/src/org/eclipse/emf/compare/diagram/ide/ui/internal/contentmergeviewer/diagram/DiagramContentMergeViewer.java @@ -25,6 +25,7 @@ import com.google.common.collect.Lists; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.EventObject; import java.util.HashMap; import java.util.Iterator; @@ -51,6 +52,7 @@ import org.eclipse.emf.compare.diagram.ide.ui.internal.accessor.IDiagramDiffAcce import org.eclipse.emf.compare.diagram.ide.ui.internal.accessor.IDiagramNodeAccessor; import org.eclipse.emf.compare.diagram.internal.extensions.DiagramDiff; import org.eclipse.emf.compare.domain.ICompareEditingDomain; +import org.eclipse.emf.compare.ide.EMFCompareIDEPlugin; import org.eclipse.emf.compare.ide.ui.internal.contentmergeviewer.EMFCompareContentMergeViewer; import org.eclipse.emf.compare.ide.ui.internal.contentmergeviewer.tree.TreeContentMergeViewerContentProvider; import org.eclipse.emf.compare.ide.ui.internal.contentmergeviewer.util.RedoAction; @@ -1627,7 +1629,8 @@ public class DiagramContentMergeViewer extends EMFCompareContentMergeViewer { * instead. This code will break whenever we implement that change. */ if (fCurrentSelectedDiff != null) { - final Command command = getEditingDomain().createCopyCommand(fCurrentSelectedDiff, leftToRight, + final Command command = getEditingDomain().createCopyCommand( + Collections.singletonList(fCurrentSelectedDiff), leftToRight, EMFCompareRCPPlugin.getDefault().getMergerRegistry()); getEditingDomain().getCommandStack().execute(command); diff --git a/plugins/org.eclipse.emf.compare.edit/src/org/eclipse/emf/compare/command/impl/CopyCommand.java b/plugins/org.eclipse.emf.compare.edit/src/org/eclipse/emf/compare/command/impl/CopyCommand.java index a879d7aca..666934a22 100644 --- a/plugins/org.eclipse.emf.compare.edit/src/org/eclipse/emf/compare/command/impl/CopyCommand.java +++ b/plugins/org.eclipse.emf.compare.edit/src/org/eclipse/emf/compare/command/impl/CopyCommand.java @@ -1,69 +1,69 @@ -/*******************************************************************************
- * Copyright (c) 2012, 2013 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.command.impl;
-
-import java.util.Collection;
-import java.util.List;
-
-import org.eclipse.emf.common.notify.Notifier;
-import org.eclipse.emf.common.util.BasicMonitor;
-import org.eclipse.emf.compare.Diff;
-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.ecore.change.util.ChangeRecorder;
-
-/**
- * This command can be used to copy a number of diffs (or a single one) in a given direction.
- * <p>
- * <b>Note</b> that this will merge <i>all</i> differences that are passed to it, whether they're conflicting
- * or not.
- * </p>
- *
- * @author <a href="mailto:laurent.goubet@obeo.fr">Laurent Goubet</a>
- */
-public class CopyCommand extends AbstractCopyCommand {
-
- /**
- * Constructs an instance of this command given the list of differences that it needs to merge.
- *
- * @param changeRecorder
- * The change recorder associated to this command.
- * @param notifiers
- * The collection of notifiers that will be notified of this command's execution.
- * @param differences
- * The list of differences that this command should merge.
- * @param leftToRight
- * The direction in which {@code differences} should be merged.
- * @param mergerRegistry
- * The registry of mergers.
- * @since 3.0
- */
- public CopyCommand(ChangeRecorder changeRecorder, Collection<Notifier> notifiers, List<Diff> differences,
- boolean leftToRight, IMerger.Registry mergerRegistry) {
- super(changeRecorder, notifiers, differences, leftToRight, mergerRegistry);
- }
-
- /**
- * {@inheritDoc}
- *
- * @see org.eclipse.emf.edit.command.ChangeCommand#doExecute()
- */
- @Override
- protected void doExecute() {
- final IBatchMerger merger = new BatchMerger(mergerRegistry);
- if (leftToRight) {
- merger.copyAllLeftToRight(differences, new BasicMonitor());
- } else {
- merger.copyAllRightToLeft(differences, new BasicMonitor());
- }
- }
-}
+/******************************************************************************* + * Copyright (c) 2012, 2013 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.command.impl; + +import java.util.Collection; +import java.util.List; + +import org.eclipse.emf.common.notify.Notifier; +import org.eclipse.emf.common.util.BasicMonitor; +import org.eclipse.emf.compare.Diff; +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.ecore.change.util.ChangeRecorder; + +/** + * This command can be used to copy a number of diffs (or a single one) in a given direction. + * <p> + * <b>Note</b> that this will merge <i>all</i> differences that are passed to it, whether they're conflicting + * or not. + * </p> + * + * @author <a href="mailto:laurent.goubet@obeo.fr">Laurent Goubet</a> + */ +public class CopyCommand extends AbstractCopyCommand { + + /** + * Constructs an instance of this command given the list of differences that it needs to merge. + * + * @param changeRecorder + * The change recorder associated to this command. + * @param notifiers + * The collection of notifiers that will be notified of this command's execution. + * @param differences + * The list of differences that this command should merge. + * @param leftToRight + * The direction in which {@code differences} should be merged. + * @param mergerRegistry + * The registry of mergers. + * @since 3.0 + */ + public CopyCommand(ChangeRecorder changeRecorder, Collection<Notifier> notifiers, + List<? extends Diff> differences, boolean leftToRight, IMerger.Registry mergerRegistry) { + super(changeRecorder, notifiers, differences, leftToRight, mergerRegistry); + } + + /** + * {@inheritDoc} + * + * @see org.eclipse.emf.edit.command.ChangeCommand#doExecute() + */ + @Override + protected void doExecute() { + final IBatchMerger merger = new BatchMerger(mergerRegistry); + if (leftToRight) { + merger.copyAllLeftToRight(differences, new BasicMonitor()); + } else { + merger.copyAllRightToLeft(differences, new BasicMonitor()); + } + } +} diff --git a/plugins/org.eclipse.emf.compare.edit/src/org/eclipse/emf/compare/domain/ICompareEditingDomain.java b/plugins/org.eclipse.emf.compare.edit/src/org/eclipse/emf/compare/domain/ICompareEditingDomain.java index 3a9ed9bee..649d9e833 100644 --- a/plugins/org.eclipse.emf.compare.edit/src/org/eclipse/emf/compare/domain/ICompareEditingDomain.java +++ b/plugins/org.eclipse.emf.compare.edit/src/org/eclipse/emf/compare/domain/ICompareEditingDomain.java @@ -1,46 +1,47 @@ -/*******************************************************************************
- * Copyright (c) 2012, 2013 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;
-
-import java.util.List;
-
-import org.eclipse.emf.common.command.Command;
-import org.eclipse.emf.compare.Diff;
-import org.eclipse.emf.compare.command.ICompareCommandStack;
-import org.eclipse.emf.compare.merge.IMerger;
-import org.eclipse.emf.ecore.change.util.ChangeRecorder;
-
-/**
- * @author <a href="mailto:mikael.barbero@obeo.fr">Mikael Barbero</a>
- */
-public interface ICompareEditingDomain {
-
- void dispose();
-
- ICompareCommandStack getCommandStack();
-
- /**
- * @since 3.0
- */
- ChangeRecorder getChangeRecorder();
-
- /**
- * @since 3.0
- */
- Command createCopyCommand(Diff diff, boolean leftToRight, IMerger.Registry mergerRegistry);
-
- /**
- * @since 3.0
- */
- Command createCopyAllNonConflictingCommand(List<? extends Diff> differences, boolean leftToRight,
- IMerger.Registry mergerRegistry);
-
-}
+/******************************************************************************* + * Copyright (c) 2012, 2013 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; + +import java.util.List; + +import org.eclipse.emf.common.command.Command; +import org.eclipse.emf.compare.Diff; +import org.eclipse.emf.compare.command.ICompareCommandStack; +import org.eclipse.emf.compare.merge.IMerger; +import org.eclipse.emf.ecore.change.util.ChangeRecorder; + +/** + * @author <a href="mailto:mikael.barbero@obeo.fr">Mikael Barbero</a> + */ +public interface ICompareEditingDomain { + + void dispose(); + + ICompareCommandStack getCommandStack(); + + /** + * @since 3.0 + */ + ChangeRecorder getChangeRecorder(); + + /** + * @since 3.0 + */ + Command createCopyCommand(List<? extends Diff> differences, boolean leftToRight, + IMerger.Registry mergerRegistry); + + /** + * @since 3.0 + */ + Command createCopyAllNonConflictingCommand(List<? extends Diff> differences, boolean leftToRight, + IMerger.Registry mergerRegistry); + +} diff --git a/plugins/org.eclipse.emf.compare.edit/src/org/eclipse/emf/compare/domain/impl/EMFCompareEditingDomain.java b/plugins/org.eclipse.emf.compare.edit/src/org/eclipse/emf/compare/domain/impl/EMFCompareEditingDomain.java index d5b5ec89c..8a155391f 100644 --- a/plugins/org.eclipse.emf.compare.edit/src/org/eclipse/emf/compare/domain/impl/EMFCompareEditingDomain.java +++ b/plugins/org.eclipse.emf.compare.edit/src/org/eclipse/emf/compare/domain/impl/EMFCompareEditingDomain.java @@ -1,185 +1,188 @@ -/*******************************************************************************
- * Copyright (c) 2012, 2013 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.Collections;
-import java.util.List;
-
-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.compare.Diff;
-import org.eclipse.emf.compare.command.ICompareCommandStack;
-import org.eclipse.emf.compare.command.impl.CompareCommandStack;
-import org.eclipse.emf.compare.command.impl.CopyAllNonConflictingCommand;
-import org.eclipse.emf.compare.command.impl.CopyCommand;
-import org.eclipse.emf.compare.command.impl.DualCompareCommandStack;
-import org.eclipse.emf.compare.domain.ICompareEditingDomain;
-import org.eclipse.emf.compare.merge.IMerger;
-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.transaction.TransactionalEditingDomain;
-
-/**
- * @author <a href="mailto:mikael.barbero@obeo.fr">Mikael Barbero</a>
- */
-public class EMFCompareEditingDomain implements ICompareEditingDomain {
-
- private final ChangeRecorder fChangeRecorder;
-
- private final ImmutableCollection<Notifier> fNotifiers;
-
- private final ICompareCommandStack fCommandStack;
-
- 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);
- }
-
- final ResourceSet leftRS = getResourceSet(left);
- final ResourceSet rightRS = getResourceSet(right);
-
- if (leftRS != null && rightRS != null) {
- TransactionalEditingDomain leftTED = TransactionalEditingDomain.Factory.INSTANCE
- .getEditingDomain(leftRS);
- TransactionalEditingDomain rightTED = TransactionalEditingDomain.Factory.INSTANCE
- .getEditingDomain(rightRS);
- if (leftTED == null) {
- leftTED = TransactionalEditingDomain.Factory.INSTANCE.createEditingDomain(leftRS);
- }
- if (rightTED == null) {
- rightTED = TransactionalEditingDomain.Factory.INSTANCE.createEditingDomain(rightRS);
- }
- fCommandStack = new DualCompareCommandStack((BasicCommandStack)leftTED.getCommandStack(),
- (BasicCommandStack)rightTED.getCommandStack());
- } else {
- fCommandStack = commandStack;
- }
-
- fChangeRecorder = new ChangeRecorder();
- fChangeRecorder.setResolveProxies(false);
- }
-
- public 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 yet
- }
- return resourceSet;
- }
-
- public static ICompareEditingDomain create(Notifier left, Notifier right, Notifier ancestor) {
- return create(left, right, ancestor, new BasicCommandStack());
- }
-
- public static ICompareEditingDomain create(Notifier left, Notifier right, Notifier ancestor,
- CommandStack commandStack) {
- return create(left, right, ancestor, commandStack, null);
- }
-
- public static ICompareEditingDomain create(Notifier left, Notifier right, Notifier ancestor,
- CommandStack leftCommandStack, CommandStack rightCommandStack) {
-
- final ICompareCommandStack commandStack;
-
- if (leftCommandStack == null && rightCommandStack != null) {
- commandStack = new CompareCommandStack(rightCommandStack);
- } else if (leftCommandStack != null && rightCommandStack == null) {
- 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();
- }
-
- /**
- * {@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(Diff diff, boolean leftToRight, IMerger.Registry mergerRegistry) {
- ImmutableSet<Notifier> notifiers = ImmutableSet.<Notifier> builder().add(
- diff.getMatch().getComparison()).addAll(fNotifiers).build();
- return new CopyCommand(fChangeRecorder, notifiers, Collections.singletonList(diff), leftToRight,
- mergerRegistry);
- }
-
- /**
- * {@inheritDoc}
- *
- * @see org.eclipse.emf.compare.domain.ICompareEditingDomain#createCopyAllNonConflictingCommand(java.util.List,
- * boolean, org.eclipse.emf.compare.merge.IMerger.Registry)
- * @since 3.0
- */
- public Command createCopyAllNonConflictingCommand(List<? extends Diff> differences, boolean leftToRight,
- IMerger.Registry mergerRegistry) {
- ImmutableSet.Builder<Notifier> notifiersBuilder = ImmutableSet.builder();
- for (Diff diff : differences) {
- notifiersBuilder.add(diff.getMatch().getComparison());
- }
- ImmutableSet<Notifier> notifiers = notifiersBuilder.addAll(fNotifiers).build();
-
- return new CopyAllNonConflictingCommand(fChangeRecorder, notifiers, differences, leftToRight,
- mergerRegistry);
- }
-
- /**
- * @since 3.0
- */
- public ChangeRecorder getChangeRecorder() {
- return fChangeRecorder;
- }
-
-}
+/******************************************************************************* + * Copyright (c) 2012, 2013 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.List; + +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.compare.Diff; +import org.eclipse.emf.compare.command.ICompareCommandStack; +import org.eclipse.emf.compare.command.impl.CompareCommandStack; +import org.eclipse.emf.compare.command.impl.CopyAllNonConflictingCommand; +import org.eclipse.emf.compare.command.impl.CopyCommand; +import org.eclipse.emf.compare.command.impl.DualCompareCommandStack; +import org.eclipse.emf.compare.domain.ICompareEditingDomain; +import org.eclipse.emf.compare.merge.IMerger; +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.transaction.TransactionalEditingDomain; + +/** + * @author <a href="mailto:mikael.barbero@obeo.fr">Mikael Barbero</a> + */ +public class EMFCompareEditingDomain implements ICompareEditingDomain { + + private final ChangeRecorder fChangeRecorder; + + private final ImmutableCollection<Notifier> fNotifiers; + + private final ICompareCommandStack fCommandStack; + + 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); + } + + final ResourceSet leftRS = getResourceSet(left); + final ResourceSet rightRS = getResourceSet(right); + + if (leftRS != null && rightRS != null) { + TransactionalEditingDomain leftTED = TransactionalEditingDomain.Factory.INSTANCE + .getEditingDomain(leftRS); + TransactionalEditingDomain rightTED = TransactionalEditingDomain.Factory.INSTANCE + .getEditingDomain(rightRS); + if (leftTED == null) { + leftTED = TransactionalEditingDomain.Factory.INSTANCE.createEditingDomain(leftRS); + } + if (rightTED == null) { + rightTED = TransactionalEditingDomain.Factory.INSTANCE.createEditingDomain(rightRS); + } + fCommandStack = new DualCompareCommandStack((BasicCommandStack)leftTED.getCommandStack(), + (BasicCommandStack)rightTED.getCommandStack()); + } else { + fCommandStack = commandStack; + } + + fChangeRecorder = new ChangeRecorder(); + fChangeRecorder.setResolveProxies(false); + } + + public 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 yet + } + return resourceSet; + } + + public static ICompareEditingDomain create(Notifier left, Notifier right, Notifier ancestor) { + return create(left, right, ancestor, new BasicCommandStack()); + } + + public static ICompareEditingDomain create(Notifier left, Notifier right, Notifier ancestor, + CommandStack commandStack) { + return create(left, right, ancestor, commandStack, null); + } + + public static ICompareEditingDomain create(Notifier left, Notifier right, Notifier ancestor, + CommandStack leftCommandStack, CommandStack rightCommandStack) { + + final ICompareCommandStack commandStack; + + if (leftCommandStack == null && rightCommandStack != null) { + commandStack = new CompareCommandStack(rightCommandStack); + } else if (leftCommandStack != null && rightCommandStack == null) { + 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(); + } + + /** + * {@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(diff.getMatch().getComparison()); + } + ImmutableSet<Notifier> notifiers = notifiersBuilder.addAll(fNotifiers).build(); + + return new CopyCommand(fChangeRecorder, notifiers, differences, leftToRight, mergerRegistry); + } + + /** + * {@inheritDoc} + * + * @see org.eclipse.emf.compare.domain.ICompareEditingDomain#createCopyAllNonConflictingCommand(java.util.List, + * boolean, org.eclipse.emf.compare.merge.IMerger.Registry) + * @since 3.0 + */ + public Command createCopyAllNonConflictingCommand(List<? extends Diff> differences, boolean leftToRight, + IMerger.Registry mergerRegistry) { + ImmutableSet.Builder<Notifier> notifiersBuilder = ImmutableSet.builder(); + for (Diff diff : differences) { + notifiersBuilder.add(diff.getMatch().getComparison()); + } + ImmutableSet<Notifier> notifiers = notifiersBuilder.addAll(fNotifiers).build(); + + return new CopyAllNonConflictingCommand(fChangeRecorder, notifiers, differences, leftToRight, + mergerRegistry); + } + + /** + * @since 3.0 + */ + public ChangeRecorder getChangeRecorder() { + return fChangeRecorder; + } + +} diff --git a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/contentmergeviewer/EMFCompareContentMergeViewer.java b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/contentmergeviewer/EMFCompareContentMergeViewer.java index ea62219ed..d0b336134 100644 --- a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/contentmergeviewer/EMFCompareContentMergeViewer.java +++ b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/contentmergeviewer/EMFCompareContentMergeViewer.java @@ -10,6 +10,7 @@ *******************************************************************************/ package org.eclipse.emf.compare.ide.ui.internal.contentmergeviewer; +import java.util.Collection; import java.util.EventObject; import java.util.Iterator; import java.util.ResourceBundle; @@ -40,6 +41,8 @@ import org.eclipse.emf.compare.rcp.ui.mergeviewer.IMergeViewer; import org.eclipse.emf.compare.rcp.ui.mergeviewer.IMergeViewer.MergeViewerSide; import org.eclipse.emf.compare.rcp.ui.mergeviewer.IMergeViewerItem; import org.eclipse.emf.compare.rcp.ui.mergeviewer.accessor.ICompareAccessor; +import org.eclipse.emf.compare.rcp.ui.structuremergeviewer.filters.IDifferenceFilter; +import org.eclipse.emf.compare.rcp.ui.structuremergeviewer.filters.SubDiffElementsFilter; import org.eclipse.jface.action.Action; import org.eclipse.jface.action.ActionContributionItem; import org.eclipse.jface.action.ToolBarManager; @@ -568,4 +571,26 @@ public abstract class EMFCompareContentMergeViewer extends ContentMergeViewer im } super.handleDispose(event); } + + /** + * Checks the state of the filter sub diff elements. + * + * @return true, if the filter sub diff is active, false otherwise. + */ + @SuppressWarnings("unchecked") + protected boolean isSubDiffFilterActive() { + Object property = getCompareConfiguration().getProperty(EMFCompareConstants.SELECTED_FILTERS); + final Collection<IDifferenceFilter> selectedFilters; + if (property == null) { + return false; + } else { + selectedFilters = (Collection<IDifferenceFilter>)property; + for (IDifferenceFilter iDifferenceFilter : selectedFilters) { + if (iDifferenceFilter instanceof SubDiffElementsFilter) { + return true; + } + } + } + return false; + } } diff --git a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/contentmergeviewer/table/TableContentMergeViewer.java b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/contentmergeviewer/table/TableContentMergeViewer.java index 604124a8c..65e74a097 100644 --- a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/contentmergeviewer/table/TableContentMergeViewer.java +++ b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/contentmergeviewer/table/TableContentMergeViewer.java @@ -10,12 +10,17 @@ *******************************************************************************/ package org.eclipse.emf.compare.ide.ui.internal.contentmergeviewer.table; +import static com.google.common.collect.Iterables.addAll; + +import java.util.ArrayList; +import java.util.List; import java.util.ResourceBundle; import org.eclipse.compare.CompareConfiguration; import org.eclipse.emf.common.command.Command; import org.eclipse.emf.compare.Diff; import org.eclipse.emf.compare.DifferenceState; +import org.eclipse.emf.compare.ide.EMFCompareIDEPlugin; import org.eclipse.emf.compare.ide.ui.internal.contentmergeviewer.EMFCompareContentMergeViewer; import org.eclipse.emf.compare.rcp.EMFCompareRCPPlugin; import org.eclipse.emf.compare.rcp.ui.mergeviewer.IMergeViewer.MergeViewerSide; @@ -24,6 +29,7 @@ import org.eclipse.emf.compare.rcp.ui.mergeviewer.MatchedObject; import org.eclipse.emf.compare.rcp.ui.mergeviewer.MergeViewer; import org.eclipse.emf.compare.rcp.ui.mergeviewer.TableMergeViewer; import org.eclipse.emf.compare.rcp.ui.mergeviewer.accessor.ICompareAccessor; +import org.eclipse.emf.compare.utils.DiffUtil; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.InternalEObject; import org.eclipse.emf.edit.provider.ComposedAdapterFactory; @@ -112,10 +118,15 @@ public class TableContentMergeViewer extends EMFCompareContentMergeViewer { protected void copyDiff(boolean leftToRight) { final Diff diffToCopy = getDiffToCopy(getRightMergeViewer()); if (diffToCopy != null) { - Command copyCommand = getEditingDomain().createCopyCommand(diffToCopy, leftToRight, + List<Diff> diffsToCopy = new ArrayList<Diff>(); + diffsToCopy.add(diffToCopy); + if (isSubDiffFilterActive()) { + addAll(diffsToCopy, DiffUtil.getSubDiffs(leftToRight).apply(diffToCopy)); + } + Command copyCommand = getEditingDomain().createCopyCommand(diffsToCopy, leftToRight, EMFCompareRCPPlugin.getDefault().getMergerRegistry()); - getEditingDomain().getCommandStack().execute(copyCommand); + getEditingDomain().getCommandStack().execute(copyCommand); refresh(); } } diff --git a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/contentmergeviewer/text/EMFCompareTextMergeViewer.java b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/contentmergeviewer/text/EMFCompareTextMergeViewer.java index 4d59b1b6c..02f44b1dc 100644 --- a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/contentmergeviewer/text/EMFCompareTextMergeViewer.java +++ b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/contentmergeviewer/text/EMFCompareTextMergeViewer.java @@ -1,360 +1,362 @@ -/*******************************************************************************
- * Copyright (c) 2012, 2013 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.ide.ui.internal.contentmergeviewer.text;
-
-import com.google.common.collect.ImmutableSet;
-
-import java.util.ResourceBundle;
-import java.util.Set;
-
-import org.eclipse.compare.CompareConfiguration;
-import org.eclipse.compare.CompareNavigator;
-import org.eclipse.compare.ICompareNavigator;
-import org.eclipse.compare.contentmergeviewer.TextMergeViewer;
-import org.eclipse.compare.internal.CompareHandlerService;
-import org.eclipse.compare.internal.MergeSourceViewer;
-import org.eclipse.compare.internal.Utilities;
-import org.eclipse.emf.common.command.Command;
-import org.eclipse.emf.common.notify.Notifier;
-import org.eclipse.emf.common.util.EList;
-import org.eclipse.emf.compare.AttributeChange;
-import org.eclipse.emf.compare.Comparison;
-import org.eclipse.emf.compare.Conflict;
-import org.eclipse.emf.compare.Diff;
-import org.eclipse.emf.compare.DifferenceState;
-import org.eclipse.emf.compare.Match;
-import org.eclipse.emf.compare.command.ICompareCopyCommand;
-import org.eclipse.emf.compare.domain.ICompareEditingDomain;
-import org.eclipse.emf.compare.ide.ui.internal.EMFCompareConstants;
-import org.eclipse.emf.compare.ide.ui.internal.contentmergeviewer.util.DynamicObject;
-import org.eclipse.emf.compare.ide.ui.internal.structuremergeviewer.provider.AttributeChangeNode;
-import org.eclipse.emf.compare.rcp.EMFCompareRCPPlugin;
-import org.eclipse.emf.compare.utils.IEqualityHelper;
-import org.eclipse.emf.compare.utils.ReferenceUtil;
-import org.eclipse.emf.ecore.EAttribute;
-import org.eclipse.emf.ecore.EDataType;
-import org.eclipse.emf.ecore.EObject;
-import org.eclipse.emf.ecore.EStructuralFeature;
-import org.eclipse.emf.ecore.change.util.ChangeRecorder;
-import org.eclipse.emf.ecore.util.EcoreUtil;
-import org.eclipse.emf.edit.command.ChangeCommand;
-import org.eclipse.jface.action.Action;
-import org.eclipse.jface.action.ActionContributionItem;
-import org.eclipse.jface.action.ToolBarManager;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-
-/**
- * @author <a href="mailto:mikael.barbero@obeo.fr">Mikael Barbero</a>
- */
-public class EMFCompareTextMergeViewer extends TextMergeViewer {
-
- private static final String BUNDLE_NAME = EMFCompareTextMergeViewer.class.getName();
-
- private final ICompareEditingDomain fEditingDomain;
-
- private DynamicObject fDynamicObject;
-
- private ActionContributionItem fCopyDiffLeftToRightItem;
-
- private ActionContributionItem fCopyDiffRightToLeftItem;
-
- /**
- * @param parent
- * @param configuration
- */
- public EMFCompareTextMergeViewer(Composite parent, CompareConfiguration configuration) {
- super(parent, configuration);
- fEditingDomain = (ICompareEditingDomain)getCompareConfiguration().getProperty(
- EMFCompareConstants.EDITING_DOMAIN);
- setContentProvider(new EMFCompareTextMergeViewerContentProvider(configuration));
- }
-
- /**
- * {@inheritDoc}
- *
- * @see org.eclipse.compare.contentmergeviewer.TextMergeViewer#copy(boolean)
- */
- @Override
- protected void copy(boolean leftToRight) {
- Object input = getInput();
- if (input instanceof AttributeChangeNode) {
- final AttributeChange attributeChange = ((AttributeChangeNode)input).getTarget();
- final Comparison comparison = attributeChange.getMatch().getComparison();
-
- final Command copyCommand = fEditingDomain.createCopyAllNonConflictingCommand(comparison
- .getDifferences(), leftToRight, EMFCompareRCPPlugin.getDefault().getMergerRegistry());
- fEditingDomain.getCommandStack().execute(copyCommand);
-
- refresh();
- }
- }
-
- protected void copyDiff(boolean leftToRight) {
- Object input = getInput();
- if (input instanceof AttributeChangeNode) {
- final AttributeChange attributeChange = ((AttributeChangeNode)input).getTarget();
-
- final Command copyCommand = fEditingDomain.createCopyCommand(attributeChange, leftToRight,
- EMFCompareRCPPlugin.getDefault().getMergerRegistry());
- fEditingDomain.getCommandStack().execute(copyCommand);
-
- refresh();
- }
- }
-
- /**
- * Inhibits this method to avoid asking to save on each input change!!
- *
- * @see org.eclipse.compare.contentmergeviewer.ContentMergeViewer#doSave(java.lang.Object,
- * java.lang.Object)
- */
- @Override
- protected boolean doSave(Object newInput, Object oldInput) {
- return false;
- }
-
- /**
- * {@inheritDoc}
- *
- * @see org.eclipse.jface.viewers.ContentViewer#setInput(java.lang.Object)
- */
- @Override
- public void setInput(Object newInput) {
- if (newInput == null) {
- // When we leave the current input
- Object oldInput = getInput();
- if (oldInput instanceof AttributeChangeNode) {
- final AttributeChange diff = ((AttributeChangeNode)oldInput).getTarget();
- final EAttribute eAttribute = diff.getAttribute();
- final Match match = diff.getMatch();
- final IEqualityHelper equalityHelper = match.getComparison().getEqualityHelper();
-
- updateModel(diff, eAttribute, equalityHelper, match.getLeft(), true);
- updateModel(diff, eAttribute, equalityHelper, match.getRight(), false);
- }
- }
- super.setInput(newInput);
- }
-
- private void updateModel(final AttributeChange diff, final EAttribute eAttribute,
- final IEqualityHelper equalityHelper, final EObject eObject, boolean isLeft) {
- final String oldValue = getStringValue(eObject, eAttribute);
- final String newValue = new String(getContents(isLeft));
-
- final boolean oldAndNewEquals = equalityHelper.matchingAttributeValues(newValue, oldValue);
- if (eObject != null && !oldAndNewEquals && getCompareConfiguration().isLeftEditable()) {
- // Save the change on left side
- fEditingDomain.getCommandStack().execute(
- new UpdateModelAndRejectDiffCommand(fEditingDomain.getChangeRecorder(), eObject,
- eAttribute, newValue, diff, isLeft));
- }
- }
-
- private String getStringValue(final EObject eObject, final EAttribute eAttribute) {
- final EDataType eAttributeType = eAttribute.getEAttributeType();
- final Object value;
- if (eObject == null) {
- value = null;
- } else {
- value = ReferenceUtil.safeEGet(eObject, eAttribute);
- }
- return EcoreUtil.convertToString(eAttributeType, value);
- }
-
- /**
- * @return the fDynamicObject
- */
- public DynamicObject getDynamicObject() {
- if (fDynamicObject == null) {
- this.fDynamicObject = new DynamicObject(this);
- }
- return fDynamicObject;
- }
-
- @SuppressWarnings("restriction")
- protected final MergeSourceViewer getLeftSourceViewer() {
- return (MergeSourceViewer)getDynamicObject().get("fLeft"); //$NON-NLS-1$
- }
-
- @SuppressWarnings("restriction")
- protected final MergeSourceViewer getRightSourceViewer() {
- return (MergeSourceViewer)getDynamicObject().get("fRight"); //$NON-NLS-1$
- }
-
- protected final void setHandlerService(@SuppressWarnings("restriction") CompareHandlerService service) {
- getDynamicObject().set("fHandlerService", service); //$NON-NLS-1$
- }
-
- /**
- * {@inheritDoc}
- *
- * @see org.eclipse.compare.contentmergeviewer.TextMergeViewer#createToolItems(org.eclipse.jface.action.ToolBarManager)
- */
- @SuppressWarnings("restriction")
- @Override
- protected void createToolItems(ToolBarManager toolBarManager) {
- // avoid super to avoid NPE in org.eclipse.compare.internal.ViewerDescriptor.createViewer
- CompareHandlerService handlerService = CompareHandlerService.createFor(getCompareConfiguration()
- .getContainer(), getLeftSourceViewer().getSourceViewer().getControl().getShell());
- setHandlerService(handlerService);
-
- // Copy actions
- CompareConfiguration cc = getCompareConfiguration();
- if (cc.isRightEditable()) {
- Action copyLeftToRight = new Action() {
- @Override
- public void run() {
- copyDiff(true);
- navigate(true);
- }
- };
- Utilities.initAction(copyLeftToRight, getResourceBundle(), "action.CopyDiffLeftToRight."); //$NON-NLS-1$
- fCopyDiffLeftToRightItem = new ActionContributionItem(copyLeftToRight);
- fCopyDiffLeftToRightItem.setVisible(true);
- toolBarManager.appendToGroup("merge", fCopyDiffLeftToRightItem); //$NON-NLS-1$
- handlerService.registerAction(copyLeftToRight, "org.eclipse.compare.copyLeftToRight"); //$NON-NLS-1$
- }
-
- if (cc.isLeftEditable()) {
- Action copyRightToLeft = new Action() {
- @Override
- public void run() {
- copyDiff(false);
- navigate(true);
- }
- };
- Utilities.initAction(copyRightToLeft, getResourceBundle(), "action.CopyDiffRightToLeft."); //$NON-NLS-1$
- fCopyDiffRightToLeftItem = new ActionContributionItem(copyRightToLeft);
- fCopyDiffRightToLeftItem.setVisible(true);
- toolBarManager.appendToGroup("merge", fCopyDiffRightToLeftItem); //$NON-NLS-1$
- handlerService.registerAction(copyRightToLeft, "org.eclipse.compare.copyRightToLeft"); //$NON-NLS-1$
- }
-
- // Navigation
- final Action nextDiff = new Action() {
- @Override
- public void run() {
- endOfContentReached(true);
- }
- };
- Utilities.initAction(nextDiff, getResourceBundle(), "action.NextDiff.");
- ActionContributionItem contributionNextDiff = new ActionContributionItem(nextDiff);
- contributionNextDiff.setVisible(true);
- toolBarManager.appendToGroup("navigation", contributionNextDiff);
-
- final Action previousDiff = new Action() {
- @Override
- public void run() {
- endOfContentReached(false);
- }
- };
- Utilities.initAction(previousDiff, getResourceBundle(), "action.PrevDiff.");
- ActionContributionItem contributionPreviousDiff = new ActionContributionItem(previousDiff);
- contributionPreviousDiff.setVisible(true);
- toolBarManager.appendToGroup("navigation", contributionPreviousDiff);
- }
-
- /**
- * Called by the framework when the last (or first) diff of the current content viewer has been reached.
- * This will open the content viewer for the next (or previous) diff displayed in the structure viewer.
- *
- * @param next
- * <code>true</code> if we are to open the next structure viewer's diff, <code>false</code> if
- * we should go to the previous instead.
- */
- protected void endOfContentReached(boolean next) {
- final Control control = getControl();
- if (control != null && !control.isDisposed()) {
- final ICompareNavigator navigator = getCompareConfiguration().getContainer().getNavigator();
- if (navigator instanceof CompareNavigator && ((CompareNavigator)navigator).hasChange(next)) {
- navigator.selectChange(next);
- }
- }
- }
-
- /**
- * Called by the framework to navigate to the next (or previous) difference. This will open the content
- * viewer for the next (or previous) diff displayed in the structure viewer.
- *
- * @param next
- * <code>true</code> if we are to open the next structure viewer's diff, <code>false</code> if
- * we should go to the previous instead.
- */
- protected void navigate(boolean next) {
- final Control control = getControl();
- if (control != null && !control.isDisposed()) {
- final ICompareNavigator navigator = getCompareConfiguration().getContainer().getNavigator();
- if (navigator instanceof CompareNavigator && ((CompareNavigator)navigator).hasChange(next)) {
- navigator.selectChange(next);
- }
- }
- }
-
- @Override
- protected ResourceBundle getResourceBundle() {
- return ResourceBundle.getBundle(BUNDLE_NAME);
- }
-
- /**
- * Command to directly modify the semantic model and reject the related difference.
- *
- * @author cnotot
- */
- private static class UpdateModelAndRejectDiffCommand extends ChangeCommand implements ICompareCopyCommand {
-
- private boolean isLeft;
-
- private Diff difference;
-
- private Object value;
-
- private EStructuralFeature feature;
-
- private EObject owner;
-
- public UpdateModelAndRejectDiffCommand(ChangeRecorder changeRecorder, EObject owner,
- EStructuralFeature feature, Object value, Diff difference, boolean isLeft) {
- super(changeRecorder, ImmutableSet.<Notifier> builder().add(owner).addAll(
- getAffectedDiff(difference)).build());
- this.owner = owner;
- this.feature = feature;
- this.value = value;
- this.difference = difference;
- this.isLeft = isLeft;
- }
-
- @Override
- public void doExecute() {
- owner.eSet(feature, value);
- for (Diff affectedDiff : getAffectedDiff(difference)) {
- affectedDiff.setState(DifferenceState.DISCARDED);
- }
- }
-
- private static Set<Diff> getAffectedDiff(Diff diff) {
- EList<Conflict> conflicts = diff.getMatch().getComparison().getConflicts();
- for (Conflict conflict : conflicts) {
- EList<Diff> conflictualDifferences = conflict.getDifferences();
- if (conflictualDifferences.contains(diff)) {
- return ImmutableSet.copyOf(conflictualDifferences);
- }
- }
- return ImmutableSet.of(diff);
- }
-
- public boolean isLeftToRight() {
- return !isLeft;
- }
-
- }
-
-}
+/******************************************************************************* + * Copyright (c) 2012 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.ide.ui.internal.contentmergeviewer.text; + +import com.google.common.collect.ImmutableSet; + +import java.util.Collections; +import java.util.ResourceBundle; +import java.util.Set; + +import org.eclipse.compare.CompareConfiguration; +import org.eclipse.compare.CompareNavigator; +import org.eclipse.compare.ICompareNavigator; +import org.eclipse.compare.contentmergeviewer.TextMergeViewer; +import org.eclipse.compare.internal.CompareHandlerService; +import org.eclipse.compare.internal.MergeSourceViewer; +import org.eclipse.compare.internal.Utilities; +import org.eclipse.emf.common.command.Command; +import org.eclipse.emf.common.notify.Notifier; +import org.eclipse.emf.common.util.EList; +import org.eclipse.emf.compare.AttributeChange; +import org.eclipse.emf.compare.Comparison; +import org.eclipse.emf.compare.Conflict; +import org.eclipse.emf.compare.Diff; +import org.eclipse.emf.compare.DifferenceState; +import org.eclipse.emf.compare.Match; +import org.eclipse.emf.compare.command.ICompareCopyCommand; +import org.eclipse.emf.compare.domain.ICompareEditingDomain; +import org.eclipse.emf.compare.ide.ui.internal.EMFCompareConstants; +import org.eclipse.emf.compare.ide.ui.internal.contentmergeviewer.util.DynamicObject; +import org.eclipse.emf.compare.ide.ui.internal.structuremergeviewer.provider.AttributeChangeNode; +import org.eclipse.emf.compare.rcp.EMFCompareRCPPlugin; +import org.eclipse.emf.compare.utils.IEqualityHelper; +import org.eclipse.emf.compare.utils.ReferenceUtil; +import org.eclipse.emf.ecore.EAttribute; +import org.eclipse.emf.ecore.EDataType; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.EStructuralFeature; +import org.eclipse.emf.ecore.change.util.ChangeRecorder; +import org.eclipse.emf.ecore.util.EcoreUtil; +import org.eclipse.emf.edit.command.ChangeCommand; +import org.eclipse.jface.action.Action; +import org.eclipse.jface.action.ActionContributionItem; +import org.eclipse.jface.action.ToolBarManager; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; + +/** + * @author <a href="mailto:mikael.barbero@obeo.fr">Mikael Barbero</a> + */ +public class EMFCompareTextMergeViewer extends TextMergeViewer { + + private static final String BUNDLE_NAME = EMFCompareTextMergeViewer.class.getName(); + + private final ICompareEditingDomain fEditingDomain; + + private DynamicObject fDynamicObject; + + private ActionContributionItem fCopyDiffLeftToRightItem; + + private ActionContributionItem fCopyDiffRightToLeftItem; + + /** + * @param parent + * @param configuration + */ + public EMFCompareTextMergeViewer(Composite parent, CompareConfiguration configuration) { + super(parent, configuration); + fEditingDomain = (ICompareEditingDomain)getCompareConfiguration().getProperty( + EMFCompareConstants.EDITING_DOMAIN); + setContentProvider(new EMFCompareTextMergeViewerContentProvider(configuration)); + } + + /** + * {@inheritDoc} + * + * @see org.eclipse.compare.contentmergeviewer.TextMergeViewer#copy(boolean) + */ + @Override + protected void copy(boolean leftToRight) { + Object input = getInput(); + if (input instanceof AttributeChangeNode) { + final AttributeChange attributeChange = ((AttributeChangeNode)input).getTarget(); + final Comparison comparison = attributeChange.getMatch().getComparison(); + + final Command copyCommand = fEditingDomain.createCopyAllNonConflictingCommand(comparison + .getDifferences(), leftToRight, EMFCompareRCPPlugin.getDefault().getMergerRegistry()); + fEditingDomain.getCommandStack().execute(copyCommand); + + refresh(); + } + } + + protected void copyDiff(boolean leftToRight) { + Object input = getInput(); + if (input instanceof AttributeChangeNode) { + final AttributeChange attributeChange = ((AttributeChangeNode)input).getTarget(); + + final Command copyCommand = fEditingDomain.createCopyCommand(Collections + .singletonList(attributeChange), leftToRight, EMFCompareRCPPlugin.getDefault() + .getMergerRegistry()); + fEditingDomain.getCommandStack().execute(copyCommand); + + refresh(); + } + } + + /** + * Inhibits this method to avoid asking to save on each input change!! + * + * @see org.eclipse.compare.contentmergeviewer.ContentMergeViewer#doSave(java.lang.Object, + * java.lang.Object) + */ + @Override + protected boolean doSave(Object newInput, Object oldInput) { + return false; + } + + /** + * {@inheritDoc} + * + * @see org.eclipse.jface.viewers.ContentViewer#setInput(java.lang.Object) + */ + @Override + public void setInput(Object newInput) { + if (newInput == null) { + // When we leave the current input + Object oldInput = getInput(); + if (oldInput instanceof AttributeChangeNode) { + final AttributeChange diff = ((AttributeChangeNode)oldInput).getTarget(); + final EAttribute eAttribute = diff.getAttribute(); + final Match match = diff.getMatch(); + final IEqualityHelper equalityHelper = match.getComparison().getEqualityHelper(); + + updateModel(diff, eAttribute, equalityHelper, match.getLeft(), true); + updateModel(diff, eAttribute, equalityHelper, match.getRight(), false); + } + } + super.setInput(newInput); + } + + private void updateModel(final AttributeChange diff, final EAttribute eAttribute, + final IEqualityHelper equalityHelper, final EObject eObject, boolean isLeft) { + final String oldValue = getStringValue(eObject, eAttribute); + final String newValue = new String(getContents(isLeft)); + + final boolean oldAndNewEquals = equalityHelper.matchingAttributeValues(newValue, oldValue); + if (eObject != null && !oldAndNewEquals && getCompareConfiguration().isLeftEditable()) { + // Save the change on left side + fEditingDomain.getCommandStack().execute( + new UpdateModelAndRejectDiffCommand(fEditingDomain.getChangeRecorder(), eObject, + eAttribute, newValue, diff, isLeft)); + } + } + + private String getStringValue(final EObject eObject, final EAttribute eAttribute) { + final EDataType eAttributeType = eAttribute.getEAttributeType(); + final Object value; + if (eObject == null) { + value = null; + } else { + value = ReferenceUtil.safeEGet(eObject, eAttribute); + } + return EcoreUtil.convertToString(eAttributeType, value); + } + + /** + * @return the fDynamicObject + */ + public DynamicObject getDynamicObject() { + if (fDynamicObject == null) { + this.fDynamicObject = new DynamicObject(this); + } + return fDynamicObject; + } + + @SuppressWarnings("restriction") + protected final MergeSourceViewer getLeftSourceViewer() { + return (MergeSourceViewer)getDynamicObject().get("fLeft"); //$NON-NLS-1$ + } + + @SuppressWarnings("restriction") + protected final MergeSourceViewer getRightSourceViewer() { + return (MergeSourceViewer)getDynamicObject().get("fRight"); //$NON-NLS-1$ + } + + protected final void setHandlerService(@SuppressWarnings("restriction") CompareHandlerService service) { + getDynamicObject().set("fHandlerService", service); //$NON-NLS-1$ + } + + /** + * {@inheritDoc} + * + * @see org.eclipse.compare.contentmergeviewer.TextMergeViewer#createToolItems(org.eclipse.jface.action.ToolBarManager) + */ + @SuppressWarnings("restriction") + @Override + protected void createToolItems(ToolBarManager toolBarManager) { + // avoid super to avoid NPE in org.eclipse.compare.internal.ViewerDescriptor.createViewer + CompareHandlerService handlerService = CompareHandlerService.createFor(getCompareConfiguration() + .getContainer(), getLeftSourceViewer().getSourceViewer().getControl().getShell()); + setHandlerService(handlerService); + + // Copy actions + CompareConfiguration cc = getCompareConfiguration(); + if (cc.isRightEditable()) { + Action copyLeftToRight = new Action() { + @Override + public void run() { + copyDiff(true); + navigate(true); + } + }; + Utilities.initAction(copyLeftToRight, getResourceBundle(), "action.CopyDiffLeftToRight."); //$NON-NLS-1$ + fCopyDiffLeftToRightItem = new ActionContributionItem(copyLeftToRight); + fCopyDiffLeftToRightItem.setVisible(true); + toolBarManager.appendToGroup("merge", fCopyDiffLeftToRightItem); //$NON-NLS-1$ + handlerService.registerAction(copyLeftToRight, "org.eclipse.compare.copyLeftToRight"); //$NON-NLS-1$ + } + + if (cc.isLeftEditable()) { + Action copyRightToLeft = new Action() { + @Override + public void run() { + copyDiff(false); + navigate(true); + } + }; + Utilities.initAction(copyRightToLeft, getResourceBundle(), "action.CopyDiffRightToLeft."); //$NON-NLS-1$ + fCopyDiffRightToLeftItem = new ActionContributionItem(copyRightToLeft); + fCopyDiffRightToLeftItem.setVisible(true); + toolBarManager.appendToGroup("merge", fCopyDiffRightToLeftItem); //$NON-NLS-1$ + handlerService.registerAction(copyRightToLeft, "org.eclipse.compare.copyRightToLeft"); //$NON-NLS-1$ + } + + // Navigation + final Action nextDiff = new Action() { + @Override + public void run() { + endOfContentReached(true); + } + }; + Utilities.initAction(nextDiff, getResourceBundle(), "action.NextDiff."); + ActionContributionItem contributionNextDiff = new ActionContributionItem(nextDiff); + contributionNextDiff.setVisible(true); + toolBarManager.appendToGroup("navigation", contributionNextDiff); + + final Action previousDiff = new Action() { + @Override + public void run() { + endOfContentReached(false); + } + }; + Utilities.initAction(previousDiff, getResourceBundle(), "action.PrevDiff."); + ActionContributionItem contributionPreviousDiff = new ActionContributionItem(previousDiff); + contributionPreviousDiff.setVisible(true); + toolBarManager.appendToGroup("navigation", contributionPreviousDiff); + } + + /** + * Called by the framework when the last (or first) diff of the current content viewer has been reached. + * This will open the content viewer for the next (or previous) diff displayed in the structure viewer. + * + * @param next + * <code>true</code> if we are to open the next structure viewer's diff, <code>false</code> if + * we should go to the previous instead. + */ + protected void endOfContentReached(boolean next) { + final Control control = getControl(); + if (control != null && !control.isDisposed()) { + final ICompareNavigator navigator = getCompareConfiguration().getContainer().getNavigator(); + if (navigator instanceof CompareNavigator && ((CompareNavigator)navigator).hasChange(next)) { + navigator.selectChange(next); + } + } + } + + /** + * Called by the framework to navigate to the next (or previous) difference. This will open the content + * viewer for the next (or previous) diff displayed in the structure viewer. + * + * @param next + * <code>true</code> if we are to open the next structure viewer's diff, <code>false</code> if + * we should go to the previous instead. + */ + protected void navigate(boolean next) { + final Control control = getControl(); + if (control != null && !control.isDisposed()) { + final ICompareNavigator navigator = getCompareConfiguration().getContainer().getNavigator(); + if (navigator instanceof CompareNavigator && ((CompareNavigator)navigator).hasChange(next)) { + navigator.selectChange(next); + } + } + } + + @Override + protected ResourceBundle getResourceBundle() { + return ResourceBundle.getBundle(BUNDLE_NAME); + } + + /** + * Command to directly modify the semantic model and reject the related difference. + * + * @author cnotot + */ + private static class UpdateModelAndRejectDiffCommand extends ChangeCommand implements ICompareCopyCommand { + + private boolean isLeft; + + private Diff difference; + + private Object value; + + private EStructuralFeature feature; + + private EObject owner; + + public UpdateModelAndRejectDiffCommand(ChangeRecorder changeRecorder, EObject owner, + EStructuralFeature feature, Object value, Diff difference, boolean isLeft) { + super(changeRecorder, ImmutableSet.<Notifier> builder().add(owner).addAll( + getAffectedDiff(difference)).build()); + this.owner = owner; + this.feature = feature; + this.value = value; + this.difference = difference; + this.isLeft = isLeft; + } + + @Override + public void doExecute() { + owner.eSet(feature, value); + for (Diff affectedDiff : getAffectedDiff(difference)) { + affectedDiff.setState(DifferenceState.DISCARDED); + } + } + + private static Set<Diff> getAffectedDiff(Diff diff) { + EList<Conflict> conflicts = diff.getMatch().getComparison().getConflicts(); + for (Conflict conflict : conflicts) { + EList<Diff> conflictualDifferences = conflict.getDifferences(); + if (conflictualDifferences.contains(diff)) { + return ImmutableSet.copyOf(conflictualDifferences); + } + } + return ImmutableSet.of(diff); + } + + public boolean isLeftToRight() { + return !isLeft; + } + + } + +} diff --git a/plugins/org.eclipse.emf.compare.tests/src/org/eclipse/emf/compare/tests/diff/DiffUtilTest.java b/plugins/org.eclipse.emf.compare.tests/src/org/eclipse/emf/compare/tests/diff/DiffUtilTest.java index a270b0936..117ba16ae 100644 --- a/plugins/org.eclipse.emf.compare.tests/src/org/eclipse/emf/compare/tests/diff/DiffUtilTest.java +++ b/plugins/org.eclipse.emf.compare.tests/src/org/eclipse/emf/compare/tests/diff/DiffUtilTest.java @@ -1,513 +1,609 @@ -/*******************************************************************************
- * Copyright (c) 2012 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.tests.diff;
-
-import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertSame;
-import static junit.framework.Assert.fail;
-
-import com.google.common.collect.Lists;
-
-import java.util.List;
-
-import org.eclipse.emf.compare.CompareFactory;
-import org.eclipse.emf.compare.Comparison;
-import org.eclipse.emf.compare.EMFCompareConfiguration;
-import org.eclipse.emf.compare.utils.DiffUtil;
-import org.junit.Test;
-
-/**
- * We will use this to test the utility methods exposed by the {@link DiffUtil}.
- *
- * @author <a href="mailto:laurent.goubet@obeo.fr">Laurent Goubet</a>
- */
-@SuppressWarnings("all")
-public class DiffUtilTest {
- @Test
- public void lcsTest1() {
- final List<Character> left = Lists.charactersOf("abcde");
- final List<Character> right = Lists.charactersOf("czdab");
-
- final Comparison emptyComparison = createEmptyComparison();
- final List<Character> lcs = DiffUtil.longestCommonSubsequence(emptyComparison, left, right);
-
- /*
- * This is documented in {@link DefaultDiffEngine#longestCommonSubsequence(Comparison, List, List)}.
- * Ensure the documentation stays in sync.
- */
- assertEqualContents(Lists.charactersOf("cd"), lcs);
- }
-
- @Test
- public void lcsTest2() {
- final List<Character> left = Lists.charactersOf("abcde");
- final List<Character> right = Lists.charactersOf("ycdeb");
-
- final Comparison emptyComparison = createEmptyComparison();
- final List<Character> lcs = DiffUtil.longestCommonSubsequence(emptyComparison, left, right);
-
- /*
- * This is documented in {@link DiffUtil#longestCommonSubsequence(Comparison, List, List)}. Ensure the
- * documentation stays in sync.
- */
- assertEqualContents(Lists.charactersOf("cde"), lcs);
- }
-
- @Test
- public void lcsTest3() {
- final List<Integer> left = Lists.newArrayList(1, 2, 3, 4, 5, 6, 7);
- final List<Integer> right = Lists.newArrayList(8, 9, 2, 3, 4, 1, 0);
-
- final Comparison emptyComparison = createEmptyComparison();
- final List<Integer> lcs = DiffUtil.longestCommonSubsequence(emptyComparison, left, right);
-
- // These are the origin and left sides of the "complex" conflict test case.
- assertEqualContents(Lists.newArrayList(2, 3, 4), lcs);
- }
-
- @Test
- public void lcsTest4() {
- final List<Integer> left = Lists.newArrayList(1, 2, 3, 4, 5, 6, 7);
- final List<Integer> right = Lists.newArrayList(6, 2, 9, 3, 0, 4, 1, 7);
-
- final Comparison emptyComparison = createEmptyComparison();
- final List<Integer> lcs = DiffUtil.longestCommonSubsequence(emptyComparison, left, right);
-
- // These are the origin and right sides of the "complex" conflict test case.
- assertEqualContents(Lists.newArrayList(2, 3, 4, 7), lcs);
- }
-
- @Test
- public void insertionIndexTest1() {
- // Assume "left" is {8, 9, 2, 3, 4, 1, 0, 6}
- // Assume "right" is {6, 2, 9, 3, 0, 4, 7}
- // We'll transition "right" into "left" by "merging" the additions one after another.
- // We'll assume the user merges all from left to right, fixing conflicts by "undoing" changes in right
-
- // We'll go through the following changes :
- // add "1" in right = {6, 2, 9, 3, 1, 0, 4, 7}
- // remove 9 from right = {6, 2, 3, 1, 0, 4, 7}
- // add "9" in right = {6, 9, 2, 3, 1, 0, 4, 7}
- // remove "0" from right = {6, 9, 2, 3, 1, 4, 7}
- // add "0" in right = {6, 9, 2, 3, 1, 0, 4, 7}
- // add "8" in right = {6, 8, 9, 2, 3, 1, 0, 4, 7}
- // remove "7" from right = {6, 8, 9, 2, 3, 1, 0, 4}
- // remove "4" from right = {6, 8, 9, 2, 3, 1, 0}
- // add "4" in right = {6, 8, 9, 2, 3, 4, 1, 0}
- // remove "6" from right = {8, 9, 2, 3, 4, 1, 0}
- // add "6" in right = {8, 9, 2, 3, 4, 1, 0, 6}
-
- final List<Integer> left = Lists.newArrayList(8, 9, 2, 3, 4, 1, 0, 6);
- final Comparison emptyComparison = createEmptyComparison();
-
- // Merge the move of "1" (assume 1 already removed from right)
- List<Integer> right = Lists.newArrayList(6, 2, 9, 3, 0, 4, 7);
- int insertionIndex = DiffUtil.findInsertionIndex(emptyComparison, left, right, Integer.valueOf(1));
- // Inserted just before "0"
- assertSame(Integer.valueOf(4), Integer.valueOf(insertionIndex));
-
- // Merge the move of "9" (assume 9 already removed from right)
- right = Lists.newArrayList(6, 2, 3, 1, 0, 4, 7);
- insertionIndex = DiffUtil.findInsertionIndex(emptyComparison, left, right, Integer.valueOf(9));
- // Inserted just before "2"
- assertSame(Integer.valueOf(1), Integer.valueOf(insertionIndex));
-
- // Merge the move of "0" (assume 0 already removed from right)
- right = Lists.newArrayList(6, 9, 2, 3, 1, 4, 7);
- insertionIndex = DiffUtil.findInsertionIndex(emptyComparison, left, right, Integer.valueOf(0));
- // Inserted just before "4"
- assertSame(Integer.valueOf(5), Integer.valueOf(insertionIndex));
-
- // merge the addition of "8"
- right = Lists.newArrayList(6, 9, 2, 3, 1, 0, 4, 7);
- insertionIndex = DiffUtil.findInsertionIndex(emptyComparison, left, right, Integer.valueOf(8));
- // Inserted just before "9"
- assertSame(Integer.valueOf(1), Integer.valueOf(insertionIndex));
-
- // remove "7"... right = {6, 8, 9, 2, 3, 1, 0, 4}
-
- // merge the move of "4" (assume already removed from right)
- right = Lists.newArrayList(6, 8, 9, 2, 3, 1, 0);
- insertionIndex = DiffUtil.findInsertionIndex(emptyComparison, left, right, Integer.valueOf(4));
- // Inserted just after "3"
- assertSame(Integer.valueOf(5), Integer.valueOf(insertionIndex));
-
- // merge the move of "6" (assume already removed from right)
- right = Lists.newArrayList(8, 9, 2, 3, 4, 1, 0);
- insertionIndex = DiffUtil.findInsertionIndex(emptyComparison, left, right, Integer.valueOf(6));
- // Inserted just after "0"
- assertSame(Integer.valueOf(7), Integer.valueOf(insertionIndex));
- }
-
- @Test
- public void insertionIndexTest2() {
- // Try and insert between two lists with no common element
- final List<Integer> right = Lists.newArrayList(4, 5, 6);
- final Comparison emptyComparison = createEmptyComparison();
- // We'll add "0" in right and expect it to be added at the end wherever its location in left
- final Integer expectedIndex = Integer.valueOf(right.size());
-
- List<Integer> left = Lists.newArrayList(0, 1, 2, 3);
- int insertionIndex = DiffUtil.findInsertionIndex(emptyComparison, left, right, Integer.valueOf(0));
- assertSame(expectedIndex, Integer.valueOf(insertionIndex));
-
- left = Lists.newArrayList(1, 0, 2, 3);
- insertionIndex = DiffUtil.findInsertionIndex(emptyComparison, left, right, Integer.valueOf(0));
- assertSame(expectedIndex, Integer.valueOf(insertionIndex));
-
- left = Lists.newArrayList(1, 2, 3, 0);
- insertionIndex = DiffUtil.findInsertionIndex(emptyComparison, left, right, Integer.valueOf(0));
- assertSame(expectedIndex, Integer.valueOf(insertionIndex));
- }
-
- @Test
- public void insertionIndexTest3() {
- // Try and insert an element before the LCS, LCS being the whole second list
- final List<Integer> right = Lists.newArrayList(1, 2, 3);
- final Comparison emptyComparison = createEmptyComparison();
- // We'll add "0" in right and expect it to be added at the beginning
- final Integer expectedIndex = Integer.valueOf(0);
-
- List<Integer> left = Lists.newArrayList(0, 1, 2, 3);
- int insertionIndex = DiffUtil.findInsertionIndex(emptyComparison, left, right, Integer.valueOf(0));
- assertSame(expectedIndex, Integer.valueOf(insertionIndex));
-
- left = Lists.newArrayList(0, 4, 1, 2, 3);
- insertionIndex = DiffUtil.findInsertionIndex(emptyComparison, left, right, Integer.valueOf(0));
- assertSame(expectedIndex, Integer.valueOf(insertionIndex));
-
- left = Lists.newArrayList(4, 0, 1, 2, 3);
- insertionIndex = DiffUtil.findInsertionIndex(emptyComparison, left, right, Integer.valueOf(0));
- assertSame(expectedIndex, Integer.valueOf(insertionIndex));
-
- left = Lists.newArrayList(4, 0, 5, 1, 2, 3);
- insertionIndex = DiffUtil.findInsertionIndex(emptyComparison, left, right, Integer.valueOf(0));
- assertSame(expectedIndex, Integer.valueOf(insertionIndex));
-
- left = Lists.newArrayList(4, 0, 5, 1, 2, 3, 6);
- insertionIndex = DiffUtil.findInsertionIndex(emptyComparison, left, right, Integer.valueOf(0));
- assertSame(expectedIndex, Integer.valueOf(insertionIndex));
-
- left = Lists.newArrayList(4, 0, 5, 1, 6, 2, 3);
- insertionIndex = DiffUtil.findInsertionIndex(emptyComparison, left, right, Integer.valueOf(0));
- assertSame(expectedIndex, Integer.valueOf(insertionIndex));
-
- left = Lists.newArrayList(4, 0, 5, 1, 6, 2, 7, 8, 3, 9);
- insertionIndex = DiffUtil.findInsertionIndex(emptyComparison, left, right, Integer.valueOf(0));
- assertSame(expectedIndex, Integer.valueOf(insertionIndex));
- }
-
- @Test
- public void insertionIndexTest4() {
- // Try and insert an element before the LCS, LCS being part of the second list
- // We'll add "0" in right and expect it to be added just before the LCS
- final Comparison emptyComparison = createEmptyComparison();
-
- List<Integer> left = Lists.newArrayList(0, 1, 2, 3);
- List<Integer> right = Lists.newArrayList(4, 1, 2, 3);
- // Start of LCS is 1
- Integer expectedIndex = Integer.valueOf(right.indexOf(Integer.valueOf(1)));
- int insertionIndex = DiffUtil.findInsertionIndex(emptyComparison, left, right, Integer.valueOf(0));
- assertSame(expectedIndex, Integer.valueOf(insertionIndex));
-
- left = Lists.newArrayList(0, 6, 1, 5, 2, 4, 3);
- right = Lists.newArrayList(7, 4, 1, 2, 3, 8);
- // Start of LCS is 1
- expectedIndex = Integer.valueOf(right.indexOf(Integer.valueOf(1)));
- insertionIndex = DiffUtil.findInsertionIndex(emptyComparison, left, right, Integer.valueOf(0));
- assertSame(expectedIndex, Integer.valueOf(insertionIndex));
-
- left = Lists.newArrayList(5, 0, 6, 7, 1, 2, 4, 3);
- right = Lists.newArrayList(7, 4, 1, 2, 9, 3, 8);
- // Start of LCS is 7
- expectedIndex = Integer.valueOf(right.indexOf(Integer.valueOf(7)));
- insertionIndex = DiffUtil.findInsertionIndex(emptyComparison, left, right, Integer.valueOf(0));
- assertSame(expectedIndex, Integer.valueOf(insertionIndex));
- }
-
- @Test
- public void insertionIndexTest5() {
- // Try and insert an element after the LCS, LCS being the whole second list
- final List<Integer> right = Lists.newArrayList(1, 2, 3);
- final Comparison emptyComparison = createEmptyComparison();
- // We'll add "0" in right and expect it to be added at the end
- final Integer expectedIndex = Integer.valueOf(right.size());
-
- List<Integer> left = Lists.newArrayList(1, 2, 3, 0);
- int insertionIndex = DiffUtil.findInsertionIndex(emptyComparison, left, right, Integer.valueOf(0));
- assertSame(expectedIndex, Integer.valueOf(insertionIndex));
-
- left = Lists.newArrayList(1, 2, 3, 4, 0);
- insertionIndex = DiffUtil.findInsertionIndex(emptyComparison, left, right, Integer.valueOf(0));
- assertSame(expectedIndex, Integer.valueOf(insertionIndex));
-
- left = Lists.newArrayList(1, 2, 3, 0, 4);
- insertionIndex = DiffUtil.findInsertionIndex(emptyComparison, left, right, Integer.valueOf(0));
- assertSame(expectedIndex, Integer.valueOf(insertionIndex));
-
- left = Lists.newArrayList(1, 2, 3, 5, 0, 4);
- insertionIndex = DiffUtil.findInsertionIndex(emptyComparison, left, right, Integer.valueOf(0));
- assertSame(expectedIndex, Integer.valueOf(insertionIndex));
-
- left = Lists.newArrayList(6, 1, 2, 3, 5, 0, 4);
- insertionIndex = DiffUtil.findInsertionIndex(emptyComparison, left, right, Integer.valueOf(0));
- assertSame(expectedIndex, Integer.valueOf(insertionIndex));
-
- left = Lists.newArrayList(1, 6, 2, 3, 5, 0, 4);
- insertionIndex = DiffUtil.findInsertionIndex(emptyComparison, left, right, Integer.valueOf(0));
- assertSame(expectedIndex, Integer.valueOf(insertionIndex));
-
- left = Lists.newArrayList(9, 1, 6, 2, 7, 8, 3, 5, 0, 4);
- insertionIndex = DiffUtil.findInsertionIndex(emptyComparison, left, right, Integer.valueOf(0));
- assertSame(expectedIndex, Integer.valueOf(insertionIndex));
- }
-
- @Test
- public void insertionIndexTest6() {
- // Try and insert an element after the LCS, LCS being part of the second list
- // We'll add "0" in right and expect it to be added just after the LCS
- final Comparison emptyComparison = createEmptyComparison();
-
- List<Integer> left = Lists.newArrayList(1, 2, 3, 0);
- List<Integer> right = Lists.newArrayList(1, 2, 3, 4);
- // End of LCS is 3
- Integer expectedIndex = Integer.valueOf(right.indexOf(Integer.valueOf(3)) + 1);
- int insertionIndex = DiffUtil.findInsertionIndex(emptyComparison, left, right, Integer.valueOf(0));
- assertSame(expectedIndex, Integer.valueOf(insertionIndex));
-
- left = Lists.newArrayList(1, 5, 2, 4, 3, 6, 0);
- right = Lists.newArrayList(8, 1, 2, 3, 4, 7);
- // End of LCS is 3
- expectedIndex = Integer.valueOf(right.indexOf(Integer.valueOf(3)) + 1);
- insertionIndex = DiffUtil.findInsertionIndex(emptyComparison, left, right, Integer.valueOf(0));
- assertSame(expectedIndex, Integer.valueOf(insertionIndex));
-
- left = Lists.newArrayList(1, 2, 4, 3, 7, 6, 0, 5);
- right = Lists.newArrayList(8, 1, 2, 9, 3, 4, 7);
- // End of LCS is 7
- expectedIndex = Integer.valueOf(right.indexOf(Integer.valueOf(7)) + 1);
- insertionIndex = DiffUtil.findInsertionIndex(emptyComparison, left, right, Integer.valueOf(0));
- assertSame(expectedIndex, Integer.valueOf(insertionIndex));
- }
-
- @Test
- public void insertionIndexTest7() {
- // Try and insert an element in the middle of the LCS, LCS being the whole second list
- // We'll add "0" in right and expect it to be added right after the closest LCS element
- final List<Integer> right = Lists.newArrayList(1, 2, 3);
- final Comparison emptyComparison = createEmptyComparison();
-
- List<Integer> left = Lists.newArrayList(1, 0, 2, 3);
- // Closest LCS element "before" is 1
- int expectedIndex = Integer.valueOf(right.indexOf(Integer.valueOf(1)) + 1);
- int insertionIndex = DiffUtil.findInsertionIndex(emptyComparison, left, right, Integer.valueOf(0));
- assertSame(expectedIndex, Integer.valueOf(insertionIndex));
-
- left = Lists.newArrayList(1, 2, 0, 3, 4);
- // Closest LCS element "before" is 2
- expectedIndex = Integer.valueOf(right.indexOf(Integer.valueOf(2)) + 1);
- insertionIndex = DiffUtil.findInsertionIndex(emptyComparison, left, right, Integer.valueOf(0));
- assertSame(expectedIndex, Integer.valueOf(insertionIndex));
-
- left = Lists.newArrayList(1, 0, 4, 2, 3);
- // Closest LCS element "before" is 1
- expectedIndex = Integer.valueOf(right.indexOf(Integer.valueOf(1)) + 1);
- insertionIndex = DiffUtil.findInsertionIndex(emptyComparison, left, right, Integer.valueOf(0));
- assertSame(expectedIndex, Integer.valueOf(insertionIndex));
-
- left = Lists.newArrayList(5, 1, 4, 2, 0, 3);
- // Closest LCS element "before" is 2
- expectedIndex = Integer.valueOf(right.indexOf(Integer.valueOf(2)) + 1);
- insertionIndex = DiffUtil.findInsertionIndex(emptyComparison, left, right, Integer.valueOf(0));
- assertSame(expectedIndex, Integer.valueOf(insertionIndex));
-
- left = Lists.newArrayList(6, 1, 7, 8, 0, 9, 2, 10, 3, 5, 4);
- // Closest LCS element "before" is 1
- expectedIndex = Integer.valueOf(right.indexOf(Integer.valueOf(1)) + 1);
- insertionIndex = DiffUtil.findInsertionIndex(emptyComparison, left, right, Integer.valueOf(0));
- assertSame(expectedIndex, Integer.valueOf(insertionIndex));
- }
-
- @Test
- public void insertionIndexTest8() {
- // Try and insert an element in the middle of the LCS, LCS being part of the second list
- // We'll add "0" in right and expect it to be added right after the closest LCS element
- final Comparison emptyComparison = createEmptyComparison();
-
- List<Integer> left = Lists.newArrayList(1, 2, 0, 3);
- List<Integer> right = Lists.newArrayList(1, 2, 3, 4);
- // Closest LCS element is 2
- Integer expectedIndex = Integer.valueOf(right.indexOf(Integer.valueOf(2)) + 1);
- int insertionIndex = DiffUtil.findInsertionIndex(emptyComparison, left, right, Integer.valueOf(0));
- assertSame(expectedIndex, Integer.valueOf(insertionIndex));
-
- left = Lists.newArrayList(1, 5, 2, 4, 0, 3, 6);
- right = Lists.newArrayList(8, 1, 2, 3, 4, 7);
- // Closest LCS element is 2
- expectedIndex = Integer.valueOf(right.indexOf(Integer.valueOf(2)) + 1);
- insertionIndex = DiffUtil.findInsertionIndex(emptyComparison, left, right, Integer.valueOf(0));
- assertSame(expectedIndex, Integer.valueOf(insertionIndex));
-
- /*
- * This is documented in {@link DefaultDiffEngine#findInsertionIndex(Comparison, List, List, Object)}.
- * Ensure the documentation stays in sync.
- */
- left = Lists.newArrayList(1, 2, 4, 6, 8, 3, 0, 7, 5);
- right = Lists.newArrayList(8, 1, 2, 9, 3, 4, 7);
- // Closest LCS element is 3
- expectedIndex = Integer.valueOf(right.indexOf(Integer.valueOf(3)) + 1);
- insertionIndex = DiffUtil.findInsertionIndex(emptyComparison, left, right, Integer.valueOf(0));
- assertSame(expectedIndex, Integer.valueOf(insertionIndex));
- }
-
- /**
- * Tests {@link NameSimilarity#nameSimilarityMetric(String, String)}.
- * <p>
- * Expected results :
- * <table>
- * <tr>
- * <td>arg1</td>
- * <td>arg2</td>
- * <td>result</td>
- * </tr>
- * <tr>
- * <td>"ceString"</td>
- * <td>"ceString"</td>
- * <td><code>1</code></td>
- * </tr>
- * <tr>
- * <td>"classe"</td>
- * <td>"Classe"</td>
- * <td><code>0.8</code></td>
- * </tr>
- * <tr>
- * <td>"Classe"</td>
- * <td>"UneClasse"</td>
- * <td><code>10/13</code></td>
- * </tr>
- * <tr>
- * <td>"package"</td>
- * <td>"packagedeux"</td>
- * <td><code>12/16</code></td>
- * </tr>
- * <tr>
- * <td>""</td>
- * <td>"MaClasse"</td>
- * <td><code>0</code></td>
- * </tr>
- * <tr>
- * <td>"package"</td>
- * <td>"packageASupprimer"</td>
- * <td><code>12/22</code></td>
- * </tr>
- * <tr>
- * <td>"attribut"</td>
- * <td>"reference"</td>
- * <td><code>0</code></td>
- * </tr>
- * <tr>
- * <td>"aa"</td>
- * <td>"aaaa"</td>
- * <td><code>1/3</code></td>
- * </tr>
- * <tr>
- * <td>"v1"</td>
- * <td>"v2"</td>
- * <td><code>2/4</code></td>
- * </tr>
- * <tr>
- * <td>"v"</td>
- * <td>"v1"</td>
- * <td><code>1/3</code></td>
- * </tr>
- * <tr>
- * <td>"a"</td>
- * <td>"a"</td>
- * <td><code>1</code></td>
- * </tr>
- * <tr>
- * <td>"a"</td>
- * <td>"b"</td>
- * <td><code>0</code></td>
- * </tr>
- * <tr>
- * <td>"a"</td>
- * <td>"A"</td>
- * <td><code>0</code></td>
- * </tr>
- * </table>
- * </p>
- */
- @Test
- public void diceCoefficient() {
- final String[] data = new String[] {"ceString", "ceString", "classe", "Classe", "Classe",
- "UneClasse", "package", "packagedeux", "", "MaClasse", "package", "packageASupprimer",
- "attribut", "reference", "aa", "aaaa", "v1", "v2", "v", "v1", "a", "a", "a", "b", "a", "A" };
- final double[] similarities = new double[] {1d, 0.8d, 10d / 13d, 3d / 4d, 0d, 6d / 11d, 0d, 1d / 3d,
- 1d / 2d, 1d / 3d, 1d, 0d, 0d, };
- for (int i = 0; i < data.length; i += 2) {
- assertEquals("Unexpected result of the dice coefficient for str1 = " + data[i] + " and str2 = "
- + data[i + 1], similarities[i / 2], DiffUtil.diceCoefficient(data[i], data[i + 1]));
- // Make sure that the result is symmetric
- assertEquals("Dice coefficient was not symmetric for str1 = " + data[i] + " and str2 = "
- + data[i + 1], similarities[i / 2], DiffUtil.diceCoefficient(data[i + 1], data[i]));
- }
- }
-
- public void diceCoefficientFailure() {
- try {
- DiffUtil.diceCoefficient(null, null);
- fail("Expected exception has not been thrown");
- } catch (NullPointerException e) {
- // expected
- }
- try {
- DiffUtil.diceCoefficient(null, "aString");
- fail("Expected exception has not been thrown");
- } catch (NullPointerException e) {
- // expected
- }
- try {
- DiffUtil.diceCoefficient("aString", null);
- fail("Expected exception has not been thrown");
- } catch (NullPointerException e) {
- // expected
- }
- }
-
- /**
- * Ensures that the two given lists contain the same elements in the same order. The kind of list does not
- * matter.
- *
- * @param list1
- * First of the two lists to compare.
- * @param list2
- * Second of the two lists to compare.
- */
- private static <T> void assertEqualContents(List<T> list1, List<T> list2) {
- final int size = list1.size();
- assertSame(Integer.valueOf(size), Integer.valueOf(list2.size()));
-
- for (int i = 0; i < size; i++) {
- assertEquals(list1.get(i), list2.get(i));
- }
- }
-
- /**
- * Creates and return a new empty {@link Comparison} object with a defaut {@link EMFCompareConfiguration}.
- *
- * @return the created {@link Comparison}.
- */
- private static Comparison createEmptyComparison() {
- final Comparison emptyComparison = CompareFactory.eINSTANCE.createComparison();
- return emptyComparison;
- }
-}
+/******************************************************************************* + * Copyright (c) 2012 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.tests.diff; + +import static com.google.common.base.Predicates.and; +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertSame; +import static junit.framework.Assert.fail; +import static org.eclipse.emf.compare.utils.EMFComparePredicates.fromSide; +import static org.eclipse.emf.compare.utils.EMFComparePredicates.ofKind; +import static org.eclipse.emf.compare.utils.EMFComparePredicates.referenceValueMatch; + +import com.google.common.base.Predicate; +import com.google.common.collect.Iterables; +import com.google.common.collect.Iterators; +import com.google.common.collect.Lists; + +import java.io.IOException; +import java.util.List; + +import org.eclipse.emf.compare.CompareFactory; +import org.eclipse.emf.compare.Comparison; +import org.eclipse.emf.compare.Diff; +import org.eclipse.emf.compare.DifferenceKind; +import org.eclipse.emf.compare.DifferenceSource; +import org.eclipse.emf.compare.EMFCompare; +import org.eclipse.emf.compare.EMFCompareConfiguration; +import org.eclipse.emf.compare.scope.IComparisonScope; +import org.eclipse.emf.compare.tests.fullcomparison.data.identifier.IdentifierMatchInputData; +import org.eclipse.emf.compare.utils.DiffUtil; +import org.eclipse.emf.ecore.resource.Resource; +import org.junit.Test; + +/** + * We will use this to test the utility methods exposed by the {@link DiffUtil}. + * + * @author <a href="mailto:laurent.goubet@obeo.fr">Laurent Goubet</a> + */ +@SuppressWarnings("all") +public class DiffUtilTest { + @Test + public void lcsTest1() { + final List<Character> left = Lists.charactersOf("abcde"); + final List<Character> right = Lists.charactersOf("czdab"); + + final Comparison emptyComparison = createEmptyComparison(); + final List<Character> lcs = DiffUtil.longestCommonSubsequence(emptyComparison, left, right); + + /* + * This is documented in {@link DefaultDiffEngine#longestCommonSubsequence(Comparison, List, List)}. + * Ensure the documentation stays in sync. + */ + assertEqualContents(Lists.charactersOf("cd"), lcs); + } + + @Test + public void lcsTest2() { + final List<Character> left = Lists.charactersOf("abcde"); + final List<Character> right = Lists.charactersOf("ycdeb"); + + final Comparison emptyComparison = createEmptyComparison(); + final List<Character> lcs = DiffUtil.longestCommonSubsequence(emptyComparison, left, right); + + /* + * This is documented in {@link DiffUtil#longestCommonSubsequence(Comparison, List, List)}. Ensure the + * documentation stays in sync. + */ + assertEqualContents(Lists.charactersOf("cde"), lcs); + } + + @Test + public void lcsTest3() { + final List<Integer> left = Lists.newArrayList(1, 2, 3, 4, 5, 6, 7); + final List<Integer> right = Lists.newArrayList(8, 9, 2, 3, 4, 1, 0); + + final Comparison emptyComparison = createEmptyComparison(); + final List<Integer> lcs = DiffUtil.longestCommonSubsequence(emptyComparison, left, right); + + // These are the origin and left sides of the "complex" conflict test case. + assertEqualContents(Lists.newArrayList(2, 3, 4), lcs); + } + + @Test + public void lcsTest4() { + final List<Integer> left = Lists.newArrayList(1, 2, 3, 4, 5, 6, 7); + final List<Integer> right = Lists.newArrayList(6, 2, 9, 3, 0, 4, 1, 7); + + final Comparison emptyComparison = createEmptyComparison(); + final List<Integer> lcs = DiffUtil.longestCommonSubsequence(emptyComparison, left, right); + + // These are the origin and right sides of the "complex" conflict test case. + assertEqualContents(Lists.newArrayList(2, 3, 4, 7), lcs); + } + + @Test + public void insertionIndexTest1() { + // Assume "left" is {8, 9, 2, 3, 4, 1, 0, 6} + // Assume "right" is {6, 2, 9, 3, 0, 4, 7} + // We'll transition "right" into "left" by "merging" the additions one after another. + // We'll assume the user merges all from left to right, fixing conflicts by "undoing" changes in right + + // We'll go through the following changes : + // add "1" in right = {6, 2, 9, 3, 1, 0, 4, 7} + // remove 9 from right = {6, 2, 3, 1, 0, 4, 7} + // add "9" in right = {6, 9, 2, 3, 1, 0, 4, 7} + // remove "0" from right = {6, 9, 2, 3, 1, 4, 7} + // add "0" in right = {6, 9, 2, 3, 1, 0, 4, 7} + // add "8" in right = {6, 8, 9, 2, 3, 1, 0, 4, 7} + // remove "7" from right = {6, 8, 9, 2, 3, 1, 0, 4} + // remove "4" from right = {6, 8, 9, 2, 3, 1, 0} + // add "4" in right = {6, 8, 9, 2, 3, 4, 1, 0} + // remove "6" from right = {8, 9, 2, 3, 4, 1, 0} + // add "6" in right = {8, 9, 2, 3, 4, 1, 0, 6} + + final List<Integer> left = Lists.newArrayList(8, 9, 2, 3, 4, 1, 0, 6); + final Comparison emptyComparison = createEmptyComparison(); + + // Merge the move of "1" (assume 1 already removed from right) + List<Integer> right = Lists.newArrayList(6, 2, 9, 3, 0, 4, 7); + int insertionIndex = DiffUtil.findInsertionIndex(emptyComparison, left, right, Integer.valueOf(1)); + // Inserted just before "0" + assertSame(Integer.valueOf(4), Integer.valueOf(insertionIndex)); + + // Merge the move of "9" (assume 9 already removed from right) + right = Lists.newArrayList(6, 2, 3, 1, 0, 4, 7); + insertionIndex = DiffUtil.findInsertionIndex(emptyComparison, left, right, Integer.valueOf(9)); + // Inserted just before "2" + assertSame(Integer.valueOf(1), Integer.valueOf(insertionIndex)); + + // Merge the move of "0" (assume 0 already removed from right) + right = Lists.newArrayList(6, 9, 2, 3, 1, 4, 7); + insertionIndex = DiffUtil.findInsertionIndex(emptyComparison, left, right, Integer.valueOf(0)); + // Inserted just before "4" + assertSame(Integer.valueOf(5), Integer.valueOf(insertionIndex)); + + // merge the addition of "8" + right = Lists.newArrayList(6, 9, 2, 3, 1, 0, 4, 7); + insertionIndex = DiffUtil.findInsertionIndex(emptyComparison, left, right, Integer.valueOf(8)); + // Inserted just before "9" + assertSame(Integer.valueOf(1), Integer.valueOf(insertionIndex)); + + // remove "7"... right = {6, 8, 9, 2, 3, 1, 0, 4} + + // merge the move of "4" (assume already removed from right) + right = Lists.newArrayList(6, 8, 9, 2, 3, 1, 0); + insertionIndex = DiffUtil.findInsertionIndex(emptyComparison, left, right, Integer.valueOf(4)); + // Inserted just after "3" + assertSame(Integer.valueOf(5), Integer.valueOf(insertionIndex)); + + // merge the move of "6" (assume already removed from right) + right = Lists.newArrayList(8, 9, 2, 3, 4, 1, 0); + insertionIndex = DiffUtil.findInsertionIndex(emptyComparison, left, right, Integer.valueOf(6)); + // Inserted just after "0" + assertSame(Integer.valueOf(7), Integer.valueOf(insertionIndex)); + } + + @Test + public void insertionIndexTest2() { + // Try and insert between two lists with no common element + final List<Integer> right = Lists.newArrayList(4, 5, 6); + final Comparison emptyComparison = createEmptyComparison(); + // We'll add "0" in right and expect it to be added at the end wherever its location in left + final Integer expectedIndex = Integer.valueOf(right.size()); + + List<Integer> left = Lists.newArrayList(0, 1, 2, 3); + int insertionIndex = DiffUtil.findInsertionIndex(emptyComparison, left, right, Integer.valueOf(0)); + assertSame(expectedIndex, Integer.valueOf(insertionIndex)); + + left = Lists.newArrayList(1, 0, 2, 3); + insertionIndex = DiffUtil.findInsertionIndex(emptyComparison, left, right, Integer.valueOf(0)); + assertSame(expectedIndex, Integer.valueOf(insertionIndex)); + + left = Lists.newArrayList(1, 2, 3, 0); + insertionIndex = DiffUtil.findInsertionIndex(emptyComparison, left, right, Integer.valueOf(0)); + assertSame(expectedIndex, Integer.valueOf(insertionIndex)); + } + + @Test + public void insertionIndexTest3() { + // Try and insert an element before the LCS, LCS being the whole second list + final List<Integer> right = Lists.newArrayList(1, 2, 3); + final Comparison emptyComparison = createEmptyComparison(); + // We'll add "0" in right and expect it to be added at the beginning + final Integer expectedIndex = Integer.valueOf(0); + + List<Integer> left = Lists.newArrayList(0, 1, 2, 3); + int insertionIndex = DiffUtil.findInsertionIndex(emptyComparison, left, right, Integer.valueOf(0)); + assertSame(expectedIndex, Integer.valueOf(insertionIndex)); + + left = Lists.newArrayList(0, 4, 1, 2, 3); + insertionIndex = DiffUtil.findInsertionIndex(emptyComparison, left, right, Integer.valueOf(0)); + assertSame(expectedIndex, Integer.valueOf(insertionIndex)); + + left = Lists.newArrayList(4, 0, 1, 2, 3); + insertionIndex = DiffUtil.findInsertionIndex(emptyComparison, left, right, Integer.valueOf(0)); + assertSame(expectedIndex, Integer.valueOf(insertionIndex)); + + left = Lists.newArrayList(4, 0, 5, 1, 2, 3); + insertionIndex = DiffUtil.findInsertionIndex(emptyComparison, left, right, Integer.valueOf(0)); + assertSame(expectedIndex, Integer.valueOf(insertionIndex)); + + left = Lists.newArrayList(4, 0, 5, 1, 2, 3, 6); + insertionIndex = DiffUtil.findInsertionIndex(emptyComparison, left, right, Integer.valueOf(0)); + assertSame(expectedIndex, Integer.valueOf(insertionIndex)); + + left = Lists.newArrayList(4, 0, 5, 1, 6, 2, 3); + insertionIndex = DiffUtil.findInsertionIndex(emptyComparison, left, right, Integer.valueOf(0)); + assertSame(expectedIndex, Integer.valueOf(insertionIndex)); + + left = Lists.newArrayList(4, 0, 5, 1, 6, 2, 7, 8, 3, 9); + insertionIndex = DiffUtil.findInsertionIndex(emptyComparison, left, right, Integer.valueOf(0)); + assertSame(expectedIndex, Integer.valueOf(insertionIndex)); + } + + @Test + public void insertionIndexTest4() { + // Try and insert an element before the LCS, LCS being part of the second list + // We'll add "0" in right and expect it to be added just before the LCS + final Comparison emptyComparison = createEmptyComparison(); + + List<Integer> left = Lists.newArrayList(0, 1, 2, 3); + List<Integer> right = Lists.newArrayList(4, 1, 2, 3); + // Start of LCS is 1 + Integer expectedIndex = Integer.valueOf(right.indexOf(Integer.valueOf(1))); + int insertionIndex = DiffUtil.findInsertionIndex(emptyComparison, left, right, Integer.valueOf(0)); + assertSame(expectedIndex, Integer.valueOf(insertionIndex)); + + left = Lists.newArrayList(0, 6, 1, 5, 2, 4, 3); + right = Lists.newArrayList(7, 4, 1, 2, 3, 8); + // Start of LCS is 1 + expectedIndex = Integer.valueOf(right.indexOf(Integer.valueOf(1))); + insertionIndex = DiffUtil.findInsertionIndex(emptyComparison, left, right, Integer.valueOf(0)); + assertSame(expectedIndex, Integer.valueOf(insertionIndex)); + + left = Lists.newArrayList(5, 0, 6, 7, 1, 2, 4, 3); + right = Lists.newArrayList(7, 4, 1, 2, 9, 3, 8); + // Start of LCS is 7 + expectedIndex = Integer.valueOf(right.indexOf(Integer.valueOf(7))); + insertionIndex = DiffUtil.findInsertionIndex(emptyComparison, left, right, Integer.valueOf(0)); + assertSame(expectedIndex, Integer.valueOf(insertionIndex)); + } + + @Test + public void insertionIndexTest5() { + // Try and insert an element after the LCS, LCS being the whole second list + final List<Integer> right = Lists.newArrayList(1, 2, 3); + final Comparison emptyComparison = createEmptyComparison(); + // We'll add "0" in right and expect it to be added at the end + final Integer expectedIndex = Integer.valueOf(right.size()); + + List<Integer> left = Lists.newArrayList(1, 2, 3, 0); + int insertionIndex = DiffUtil.findInsertionIndex(emptyComparison, left, right, Integer.valueOf(0)); + assertSame(expectedIndex, Integer.valueOf(insertionIndex)); + + left = Lists.newArrayList(1, 2, 3, 4, 0); + insertionIndex = DiffUtil.findInsertionIndex(emptyComparison, left, right, Integer.valueOf(0)); + assertSame(expectedIndex, Integer.valueOf(insertionIndex)); + + left = Lists.newArrayList(1, 2, 3, 0, 4); + insertionIndex = DiffUtil.findInsertionIndex(emptyComparison, left, right, Integer.valueOf(0)); + assertSame(expectedIndex, Integer.valueOf(insertionIndex)); + + left = Lists.newArrayList(1, 2, 3, 5, 0, 4); + insertionIndex = DiffUtil.findInsertionIndex(emptyComparison, left, right, Integer.valueOf(0)); + assertSame(expectedIndex, Integer.valueOf(insertionIndex)); + + left = Lists.newArrayList(6, 1, 2, 3, 5, 0, 4); + insertionIndex = DiffUtil.findInsertionIndex(emptyComparison, left, right, Integer.valueOf(0)); + assertSame(expectedIndex, Integer.valueOf(insertionIndex)); + + left = Lists.newArrayList(1, 6, 2, 3, 5, 0, 4); + insertionIndex = DiffUtil.findInsertionIndex(emptyComparison, left, right, Integer.valueOf(0)); + assertSame(expectedIndex, Integer.valueOf(insertionIndex)); + + left = Lists.newArrayList(9, 1, 6, 2, 7, 8, 3, 5, 0, 4); + insertionIndex = DiffUtil.findInsertionIndex(emptyComparison, left, right, Integer.valueOf(0)); + assertSame(expectedIndex, Integer.valueOf(insertionIndex)); + } + + @Test + public void insertionIndexTest6() { + // Try and insert an element after the LCS, LCS being part of the second list + // We'll add "0" in right and expect it to be added just after the LCS + final Comparison emptyComparison = createEmptyComparison(); + + List<Integer> left = Lists.newArrayList(1, 2, 3, 0); + List<Integer> right = Lists.newArrayList(1, 2, 3, 4); + // End of LCS is 3 + Integer expectedIndex = Integer.valueOf(right.indexOf(Integer.valueOf(3)) + 1); + int insertionIndex = DiffUtil.findInsertionIndex(emptyComparison, left, right, Integer.valueOf(0)); + assertSame(expectedIndex, Integer.valueOf(insertionIndex)); + + left = Lists.newArrayList(1, 5, 2, 4, 3, 6, 0); + right = Lists.newArrayList(8, 1, 2, 3, 4, 7); + // End of LCS is 3 + expectedIndex = Integer.valueOf(right.indexOf(Integer.valueOf(3)) + 1); + insertionIndex = DiffUtil.findInsertionIndex(emptyComparison, left, right, Integer.valueOf(0)); + assertSame(expectedIndex, Integer.valueOf(insertionIndex)); + + left = Lists.newArrayList(1, 2, 4, 3, 7, 6, 0, 5); + right = Lists.newArrayList(8, 1, 2, 9, 3, 4, 7); + // End of LCS is 7 + expectedIndex = Integer.valueOf(right.indexOf(Integer.valueOf(7)) + 1); + insertionIndex = DiffUtil.findInsertionIndex(emptyComparison, left, right, Integer.valueOf(0)); + assertSame(expectedIndex, Integer.valueOf(insertionIndex)); + } + + @Test + public void insertionIndexTest7() { + // Try and insert an element in the middle of the LCS, LCS being the whole second list + // We'll add "0" in right and expect it to be added right after the closest LCS element + final List<Integer> right = Lists.newArrayList(1, 2, 3); + final Comparison emptyComparison = createEmptyComparison(); + + List<Integer> left = Lists.newArrayList(1, 0, 2, 3); + // Closest LCS element "before" is 1 + int expectedIndex = Integer.valueOf(right.indexOf(Integer.valueOf(1)) + 1); + int insertionIndex = DiffUtil.findInsertionIndex(emptyComparison, left, right, Integer.valueOf(0)); + assertSame(expectedIndex, Integer.valueOf(insertionIndex)); + + left = Lists.newArrayList(1, 2, 0, 3, 4); + // Closest LCS element "before" is 2 + expectedIndex = Integer.valueOf(right.indexOf(Integer.valueOf(2)) + 1); + insertionIndex = DiffUtil.findInsertionIndex(emptyComparison, left, right, Integer.valueOf(0)); + assertSame(expectedIndex, Integer.valueOf(insertionIndex)); + + left = Lists.newArrayList(1, 0, 4, 2, 3); + // Closest LCS element "before" is 1 + expectedIndex = Integer.valueOf(right.indexOf(Integer.valueOf(1)) + 1); + insertionIndex = DiffUtil.findInsertionIndex(emptyComparison, left, right, Integer.valueOf(0)); + assertSame(expectedIndex, Integer.valueOf(insertionIndex)); + + left = Lists.newArrayList(5, 1, 4, 2, 0, 3); + // Closest LCS element "before" is 2 + expectedIndex = Integer.valueOf(right.indexOf(Integer.valueOf(2)) + 1); + insertionIndex = DiffUtil.findInsertionIndex(emptyComparison, left, right, Integer.valueOf(0)); + assertSame(expectedIndex, Integer.valueOf(insertionIndex)); + + left = Lists.newArrayList(6, 1, 7, 8, 0, 9, 2, 10, 3, 5, 4); + // Closest LCS element "before" is 1 + expectedIndex = Integer.valueOf(right.indexOf(Integer.valueOf(1)) + 1); + insertionIndex = DiffUtil.findInsertionIndex(emptyComparison, left, right, Integer.valueOf(0)); + assertSame(expectedIndex, Integer.valueOf(insertionIndex)); + } + + @Test + public void insertionIndexTest8() { + // Try and insert an element in the middle of the LCS, LCS being part of the second list + // We'll add "0" in right and expect it to be added right after the closest LCS element + final Comparison emptyComparison = createEmptyComparison(); + + List<Integer> left = Lists.newArrayList(1, 2, 0, 3); + List<Integer> right = Lists.newArrayList(1, 2, 3, 4); + // Closest LCS element is 2 + Integer expectedIndex = Integer.valueOf(right.indexOf(Integer.valueOf(2)) + 1); + int insertionIndex = DiffUtil.findInsertionIndex(emptyComparison, left, right, Integer.valueOf(0)); + assertSame(expectedIndex, Integer.valueOf(insertionIndex)); + + left = Lists.newArrayList(1, 5, 2, 4, 0, 3, 6); + right = Lists.newArrayList(8, 1, 2, 3, 4, 7); + // Closest LCS element is 2 + expectedIndex = Integer.valueOf(right.indexOf(Integer.valueOf(2)) + 1); + insertionIndex = DiffUtil.findInsertionIndex(emptyComparison, left, right, Integer.valueOf(0)); + assertSame(expectedIndex, Integer.valueOf(insertionIndex)); + + /* + * This is documented in {@link DefaultDiffEngine#findInsertionIndex(Comparison, List, List, Object)}. + * Ensure the documentation stays in sync. + */ + left = Lists.newArrayList(1, 2, 4, 6, 8, 3, 0, 7, 5); + right = Lists.newArrayList(8, 1, 2, 9, 3, 4, 7); + // Closest LCS element is 3 + expectedIndex = Integer.valueOf(right.indexOf(Integer.valueOf(3)) + 1); + insertionIndex = DiffUtil.findInsertionIndex(emptyComparison, left, right, Integer.valueOf(0)); + assertSame(expectedIndex, Integer.valueOf(insertionIndex)); + } + + /** + * Tests {@link NameSimilarity#nameSimilarityMetric(String, String)}. + * <p> + * Expected results : + * <table> + * <tr> + * <td>arg1</td> + * <td>arg2</td> + * <td>result</td> + * </tr> + * <tr> + * <td>"ceString"</td> + * <td>"ceString"</td> + * <td><code>1</code></td> + * </tr> + * <tr> + * <td>"classe"</td> + * <td>"Classe"</td> + * <td><code>0.8</code></td> + * </tr> + * <tr> + * <td>"Classe"</td> + * <td>"UneClasse"</td> + * <td><code>10/13</code></td> + * </tr> + * <tr> + * <td>"package"</td> + * <td>"packagedeux"</td> + * <td><code>12/16</code></td> + * </tr> + * <tr> + * <td>""</td> + * <td>"MaClasse"</td> + * <td><code>0</code></td> + * </tr> + * <tr> + * <td>"package"</td> + * <td>"packageASupprimer"</td> + * <td><code>12/22</code></td> + * </tr> + * <tr> + * <td>"attribut"</td> + * <td>"reference"</td> + * <td><code>0</code></td> + * </tr> + * <tr> + * <td>"aa"</td> + * <td>"aaaa"</td> + * <td><code>1/3</code></td> + * </tr> + * <tr> + * <td>"v1"</td> + * <td>"v2"</td> + * <td><code>2/4</code></td> + * </tr> + * <tr> + * <td>"v"</td> + * <td>"v1"</td> + * <td><code>1/3</code></td> + * </tr> + * <tr> + * <td>"a"</td> + * <td>"a"</td> + * <td><code>1</code></td> + * </tr> + * <tr> + * <td>"a"</td> + * <td>"b"</td> + * <td><code>0</code></td> + * </tr> + * <tr> + * <td>"a"</td> + * <td>"A"</td> + * <td><code>0</code></td> + * </tr> + * </table> + * </p> + */ + @Test + public void diceCoefficient() { + final String[] data = new String[] {"ceString", "ceString", "classe", "Classe", "Classe", + "UneClasse", "package", "packagedeux", "", "MaClasse", "package", "packageASupprimer", + "attribut", "reference", "aa", "aaaa", "v1", "v2", "v", "v1", "a", "a", "a", "b", "a", "A" }; + final double[] similarities = new double[] {1d, 0.8d, 10d / 13d, 3d / 4d, 0d, 6d / 11d, 0d, 1d / 3d, + 1d / 2d, 1d / 3d, 1d, 0d, 0d, }; + for (int i = 0; i < data.length; i += 2) { + assertEquals("Unexpected result of the dice coefficient for str1 = " + data[i] + " and str2 = " + + data[i + 1], similarities[i / 2], DiffUtil.diceCoefficient(data[i], data[i + 1])); + // Make sure that the result is symmetric + assertEquals("Dice coefficient was not symmetric for str1 = " + data[i] + " and str2 = " + + data[i + 1], similarities[i / 2], DiffUtil.diceCoefficient(data[i + 1], data[i])); + } + } + + @Test + public void testSubDiffs() throws IOException { + IdentifierMatchInputData inputData = new IdentifierMatchInputData(); + + final Resource left = inputData.getExtlibraryLeft(); + final Resource origin = inputData.getExtlibraryOrigin(); + final Resource right = inputData.getExtlibraryRight(); + + // 2-way + IComparisonScope scope = EMFCompare.createDefaultScope(left, right); + Comparison comparison = EMFCompare.builder().build().compare(scope); + List<Diff> differences = comparison.getDifferences(); + + // Right to left on a deleted element + final Predicate<? super Diff> leftPeriodical = and(fromSide(DifferenceSource.LEFT), + ofKind(DifferenceKind.DELETE), referenceValueMatch("eClassifiers", "extlibrary.Periodical", + true)); + final Diff leftPeriodicalDiff = Iterators.find(differences.iterator(), leftPeriodical); + boolean leftToRight = false; + Iterable<Diff> subDiffs = DiffUtil.getSubDiffs(leftToRight).apply(leftPeriodicalDiff); + + assertEquals(7, Iterables.size(subDiffs)); + + // Left to right on a deleted element + leftToRight = true; + subDiffs = DiffUtil.getSubDiffs(leftToRight).apply(leftPeriodicalDiff); + + assertEquals(4, Iterables.size(subDiffs)); + + // Right to left on an added element + final Predicate<? super Diff> leftMagazine = and(fromSide(DifferenceSource.LEFT), + ofKind(DifferenceKind.ADD), referenceValueMatch("eClassifiers", "extlibrary.Magazine", true)); + final Diff leftMagazineDiff = Iterators.find(differences.iterator(), leftMagazine); + leftToRight = false; + subDiffs = DiffUtil.getSubDiffs(leftToRight).apply(leftMagazineDiff); + + assertEquals(5, Iterables.size(subDiffs)); + + // Left to right on an added element + leftToRight = true; + subDiffs = DiffUtil.getSubDiffs(leftToRight).apply(leftMagazineDiff); + + assertEquals(5, Iterables.size(subDiffs)); + + // 3-way + scope = EMFCompare.createDefaultScope(left, right, origin); + comparison = EMFCompare.builder().build().compare(scope); + differences = comparison.getDifferences(); + + // Right to left on a deleted element + final Predicate<? super Diff> leftPeriodical3Way = and(fromSide(DifferenceSource.LEFT), + ofKind(DifferenceKind.DELETE), referenceValueMatch("eClassifiers", "extlibrary.Periodical", + true)); + final Diff leftPeriodicalDiff3Way = Iterators.find(differences.iterator(), leftPeriodical3Way); + leftToRight = false; + subDiffs = DiffUtil.getSubDiffs(leftToRight).apply(leftPeriodicalDiff3Way); + + assertEquals(11, Iterables.size(subDiffs)); + + // Left to right on a deleted element + leftToRight = true; + subDiffs = DiffUtil.getSubDiffs(leftToRight).apply(leftPeriodicalDiff3Way); + + assertEquals(8, Iterables.size(subDiffs)); + + // Right to left on a added element + final Predicate<? super Diff> leftMagazine3Way = and(fromSide(DifferenceSource.LEFT), + ofKind(DifferenceKind.ADD), referenceValueMatch("eClassifiers", "extlibrary.Magazine", true)); + final Diff leftMagazineDiff3Way = Iterators.find(differences.iterator(), leftMagazine3Way); + leftToRight = false; + subDiffs = DiffUtil.getSubDiffs(leftToRight).apply(leftMagazineDiff3Way); + + assertEquals(5, Iterables.size(subDiffs)); + + // Left to right on an added element + leftToRight = true; + subDiffs = DiffUtil.getSubDiffs(leftToRight).apply(leftMagazineDiff3Way); + + assertEquals(5, Iterables.size(subDiffs)); + } + + public void diceCoefficientFailure() { + try { + DiffUtil.diceCoefficient(null, null); + fail("Expected exception has not been thrown"); + } catch (NullPointerException e) { + // expected + } + try { + DiffUtil.diceCoefficient(null, "aString"); + fail("Expected exception has not been thrown"); + } catch (NullPointerException e) { + // expected + } + try { + DiffUtil.diceCoefficient("aString", null); + fail("Expected exception has not been thrown"); + } catch (NullPointerException e) { + // expected + } + } + + /** + * Ensures that the two given lists contain the same elements in the same order. The kind of list does not + * matter. + * + * @param list1 + * First of the two lists to compare. + * @param list2 + * Second of the two lists to compare. + */ + private static <T> void assertEqualContents(List<T> list1, List<T> list2) { + final int size = list1.size(); + assertSame(Integer.valueOf(size), Integer.valueOf(list2.size())); + + for (int i = 0; i < size; i++) { + assertEquals(list1.get(i), list2.get(i)); + } + } + + /** + * Creates and return a new empty {@link Comparison} object with a defaut {@link EMFCompareConfiguration}. + * + * @return the created {@link Comparison}. + */ + private static Comparison createEmptyComparison() { + final Comparison emptyComparison = CompareFactory.eINSTANCE.createComparison(); + return emptyComparison; + } +} diff --git a/plugins/org.eclipse.emf.compare/src/org/eclipse/emf/compare/utils/DiffUtil.java b/plugins/org.eclipse.emf.compare/src/org/eclipse/emf/compare/utils/DiffUtil.java index 0b29d1f53..b12eec482 100644 --- a/plugins/org.eclipse.emf.compare/src/org/eclipse/emf/compare/utils/DiffUtil.java +++ b/plugins/org.eclipse.emf.compare/src/org/eclipse/emf/compare/utils/DiffUtil.java @@ -1,958 +1,1064 @@ -/*******************************************************************************
- * Copyright (c) 2012, 2013 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.utils;
-
-import com.google.common.base.Predicate;
-import com.google.common.collect.HashMultiset;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Iterables;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Multiset;
-import com.google.common.collect.Sets;
-
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.Iterator;
-import java.util.List;
-import java.util.ListIterator;
-import java.util.Set;
-
-import org.eclipse.emf.compare.AttributeChange;
-import org.eclipse.emf.compare.Comparison;
-import org.eclipse.emf.compare.Diff;
-import org.eclipse.emf.compare.DifferenceKind;
-import org.eclipse.emf.compare.DifferenceSource;
-import org.eclipse.emf.compare.DifferenceState;
-import org.eclipse.emf.compare.EMFCompareMessages;
-import org.eclipse.emf.compare.Match;
-import org.eclipse.emf.compare.ReferenceChange;
-import org.eclipse.emf.ecore.EObject;
-import org.eclipse.emf.ecore.EReference;
-import org.eclipse.emf.ecore.EStructuralFeature;
-import org.eclipse.emf.ecore.EcorePackage;
-import org.eclipse.emf.ecore.util.InternalEList;
-
-/**
- * This utility class will be used to provide similarity implementations.
- *
- * @author <a href="mailto:laurent.goubet@obeo.fr">Laurent Goubet</a>
- */
-public final class DiffUtil {
- /** This utility class does not need to be instantiated. */
- private DiffUtil() {
- // Hides default constructor
- }
-
- /**
- * Computes the dice coefficient between the two given String's bigrams.
- * <p>
- * This implementation is case sensitive.
- * </p>
- *
- * @param first
- * First of the two Strings to compare.
- * @param second
- * Second of the two Strings to compare.
- * @return The dice coefficient of the two given String's bigrams, ranging from 0 to 1.
- */
- public static double diceCoefficient(String first, String second) {
- final char[] str1 = first.toCharArray();
- final char[] str2 = second.toCharArray();
-
- final double coefficient;
-
- if (Arrays.equals(str1, str2)) {
- coefficient = 1d;
- } else if (str1.length <= 2 || str2.length <= 2) {
- int equalChars = 0;
-
- for (int i = 0; i < Math.min(str1.length, str2.length); i++) {
- if (str1[i] == str2[i]) {
- equalChars++;
- }
- }
-
- int union = str1.length + str2.length;
- if (str1.length != str2.length) {
- coefficient = (double)equalChars / union;
- } else {
- coefficient = ((double)equalChars * 2) / union;
- }
- } else {
- Set<String> s1Bigrams = Sets.newHashSet();
- Set<String> s2Bigrams = Sets.newHashSet();
-
- for (int i = 0; i < str1.length - 1; i++) {
- char[] chars = new char[] {str1[i], str1[i + 1], };
- s1Bigrams.add(String.valueOf(chars));
- }
- for (int i = 0; i < str2.length - 1; i++) {
- char[] chars = new char[] {str2[i], str2[i + 1], };
- s2Bigrams.add(String.valueOf(chars));
- }
-
- Set<String> intersection = Sets.intersection(s1Bigrams, s2Bigrams);
- coefficient = (2d * intersection.size()) / (s1Bigrams.size() + s2Bigrams.size());
- }
-
- return coefficient;
- }
-
- /**
- * This will compute the longest common subsequence between the two given Lists, ignoring any object that
- * is included in {@code ignoredElements}. We will use
- * {@link EqualityHelper#matchingValues(Comparison, Object, Object)} in order to try and match the values
- * from both lists two-by-two. This can thus be used both for reference values or attribute values. If
- * there are two subsequences of the same "longest" length, the first (according to the second argument)
- * will be returned.
- * <p>
- * Take note that this might be slower than
- * {@link #longestCommonSubsequence(Comparison, EqualityHelper, List, List)} and should only be used when
- * elements should be removed from the potential LCS. This is mainly aimed at merge operations during
- * three-way comparisons as some objects might be in conflict and thus shifting the computed insertion
- * indices.
- * </p>
- * <p>
- * Please see {@link #longestCommonSubsequence(Comparison, EqualityHelper, List, List)} for a more
- * complete description.
- * </p>
- *
- * @param comparison
- * This will be used in order to retrieve the Match for EObjects when comparing them.
- * @param ignoredElements
- * Specifies elements that should be excluded from the subsequences.
- * @param sequence1
- * First of the two sequences to consider.
- * @param sequence2
- * Second of the two sequences to consider.
- * @param <E>
- * Type of the sequences content.
- * @return The LCS of the two given sequences. Will never be the same instance as one of the input
- * sequences.
- * @see #longestCommonSubsequence(Comparison, EqualityHelper, List, List).
- */
- public static <E> List<E> longestCommonSubsequence(Comparison comparison, Iterable<E> ignoredElements,
- List<E> sequence1, List<E> sequence2) {
- // FIXME : merge the two "LCS" methods in one (only one line differs...)
- final List<E> copy1 = Lists.newArrayList(sequence1);
- final List<E> copy2 = Lists.newArrayList(sequence2);
-
- // Reduce sets
- final List<E> prefix = trimPrefix(comparison, ignoredElements, copy1, copy2);
- final List<E> suffix = trimSuffix(comparison, ignoredElements, copy1, copy2);
-
- final IEqualityHelper equalityHelper = comparison.getEqualityHelper();
- final int size1 = copy1.size();
- final int size2 = copy2.size();
-
- final int[][] matrix = new int[size1 + 1][size2 + 1];
-
- // Compute the LCS matrix
- for (int i = 1; i <= size1; i++) {
- final E first = copy1.get(i - 1);
- for (int j = 1; j <= size2; j++) {
- final E second = copy2.get(j - 1);
- if (equalityHelper.matchingValues(first, second)
- && !contains(comparison, equalityHelper, ignoredElements, second)) {
- matrix[i][j] = 1 + matrix[i - 1][j - 1];
- } else {
- matrix[i][j] = Math.max(matrix[i - 1][j], matrix[i][j - 1]);
- }
- }
- }
-
- // Traceback the matrix to create the final LCS
- int current1 = size1;
- int current2 = size2;
- final List<E> result = Lists.newArrayList();
-
- while (current1 > 0 && current2 > 0) {
- final int currentLength = matrix[current1][current2];
- final int nextLeft = matrix[current1][current2 - 1];
- final int nextUp = matrix[current1 - 1][current2];
- if (currentLength > nextLeft && currentLength > nextUp) {
- result.add(copy1.get(current1 - 1));
- current1--;
- current2--;
- } else if (nextLeft >= nextUp) {
- current2--;
- } else {
- current1--;
- }
- }
-
- return ImmutableList.copyOf(Iterables.concat(prefix, Lists.reverse(result), suffix));
- }
-
- /**
- * Trims and returns the common prefix of the two given sequences. All ignored elements within or after
- * this common prefix will also be trimmed.
- * <p>
- * Note that the two given sequences will be modified in-place.
- * </p>
- *
- * @param comparison
- * This will be used in order to retrieve the Match for EObjects when comparing them.
- * @param ignoredElements
- * Specifies elements that should be excluded from the subsequences.
- * @param sequence1
- * First of the two sequences to consider.
- * @param sequence2
- * Second of the two sequences to consider.
- * @param <E>
- * Type of the sequences content.
- * @return The common prefix of the two given sequences, less their ignored elements. As a side note, both
- * {@code sequence1} and {@code sequence2} will have been trimmed of their prefix when this
- * returns.
- */
- private static <E> List<E> trimPrefix(Comparison comparison, Iterable<E> ignoredElements,
- List<E> sequence1, List<E> sequence2) {
- final IEqualityHelper equalityHelper = comparison.getEqualityHelper();
- final int size1 = sequence1.size();
- final int size2 = sequence2.size();
-
- final List<E> prefix = Lists.newArrayList();
- int start1 = 1;
- int start2 = 1;
- boolean matching = true;
- while (start1 <= size1 && start2 <= size2 && matching) {
- final E first = sequence1.get(start1 - 1);
- final E second = sequence2.get(start2 - 1);
- if (equalityHelper.matchingValues(first, second)) {
- prefix.add(first);
- start1++;
- start2++;
- } else {
- boolean ignore1 = contains(comparison, equalityHelper, ignoredElements, first);
- boolean ignore2 = contains(comparison, equalityHelper, ignoredElements, second);
- if (ignore1) {
- start1++;
- }
- if (ignore2) {
- start2++;
- }
- if (!ignore1 && !ignore2) {
- matching = false;
- }
- }
- }
- for (int i = 1; i < start1; i++) {
- sequence1.remove(0);
- }
- for (int i = 1; i < start2; i++) {
- sequence2.remove(0);
- }
-
- return prefix;
- }
-
- /**
- * Trims and returns the common suffix of the two given sequences. All ignored elements within or before
- * this common suffix will also be trimmed.
- * <p>
- * Note that the two given sequences will be modified in-place.
- * </p>
- *
- * @param comparison
- * This will be used in order to retrieve the Match for EObjects when comparing them.
- * @param ignoredElements
- * Specifies elements that should be excluded from the subsequences.
- * @param sequence1
- * First of the two sequences to consider.
- * @param sequence2
- * Second of the two sequences to consider.
- * @param <E>
- * Type of the sequences content.
- * @return The common suffix of the two given sequences, less their ignored elements. As a side note, both
- * {@code sequence1} and {@code sequence2} will have been trimmed of their suffix when this
- * returns.
- */
- private static <E> List<E> trimSuffix(Comparison comparison, Iterable<E> ignoredElements,
- List<E> sequence1, List<E> sequence2) {
- final IEqualityHelper equalityHelper = comparison.getEqualityHelper();
- final int size1 = sequence1.size();
- final int size2 = sequence2.size();
-
- final List<E> suffix = Lists.newArrayList();
- int end1 = size1;
- int end2 = size2;
- boolean matching = true;
- while (end1 > 0 && end2 > 0 && matching) {
- final E first = sequence1.get(end1 - 1);
- final E second = sequence2.get(end2 - 1);
- if (equalityHelper.matchingValues(first, second)) {
- suffix.add(first);
- end1--;
- end2--;
- } else {
- boolean ignore1 = contains(comparison, equalityHelper, ignoredElements, first);
- boolean ignore2 = contains(comparison, equalityHelper, ignoredElements, second);
- if (ignore1) {
- end1--;
- }
- if (ignore2) {
- end2--;
- }
- if (!ignore1 && !ignore2) {
- matching = false;
- }
- }
- }
- for (int i = size1; i > end1; i--) {
- sequence1.remove(sequence1.size() - 1);
- }
- for (int i = size2; i > end2; i--) {
- sequence2.remove(sequence2.size() - 1);
- }
-
- return Lists.reverse(suffix);
- }
-
- /**
- * This will compute the longest common subsequence between the two given Lists. We will use
- * {@link EqualityHelper#matchingValues(Comparison, Object, Object)} in order to try and match the values
- * from both lists two-by-two. This can thus be used both for reference values or attribute values. If
- * there are two subsequences of the same "longest" length, the first (according to the second argument)
- * will be returned.
- * <p>
- * For example, it the two given sequence are, in this order, <code>{"a", "b", "c", "d", "e"}</code> and
- * <code>{"c", "z", "d", "a", "b"}</code>, there are two "longest" subsequences : <code>{"a", "b"}</code>
- * and <code>{"c", "d"}</code>. The first of those two subsequences in the second list is
- * <code>{"c", "d"}</code>. On the other hand, the LCS of <code>{"a", "b", "c", "d", "e"}</code> and
- * <code>{"y", "c", "d", "e", "b"}</code> is <code>{"c", "d", "e"}</code>.
- * </p>
- * <p>
- * The following algorithm has been inferred from the wikipedia article on the Longest Common Subsequence,
- * http://en.wikipedia.org/wiki/Longest_common_subsequence_problem at the time of writing. It is
- * decomposed in two : we first compute the LCS matrix, then we backtrack through the input to determine
- * the LCS. Evaluation will be shortcut after the first part if the LCS is one of the two input sequences.
- * </p>
- * <p>
- * Note : we are not using Iterables as input in order to make use of the random access cost of
- * ArrayLists. This might also be converted to directly use arrays. This implementation will not play well
- * with LinkedLists or any List which needs to iterate over the values for each call to
- * {@link List#get(int)}, i.e any list which is not instanceof RandomAccess or does not satisfy its
- * contract.
- * </p>
- *
- * @param comparison
- * This will be used in order to retrieve the Match for EObjects when comparing them.
- * @param sequence1
- * First of the two sequences to consider.
- * @param sequence2
- * Second of the two sequences to consider.
- * @param <E>
- * Type of the sequences content.
- * @return The LCS of the two given sequences. Will never be the same instance as one of the input
- * sequences.
- */
- public static <E> List<E> longestCommonSubsequence(Comparison comparison, List<E> sequence1,
- List<E> sequence2) {
- return longestCommonSubsequence(comparison, Collections.<E> emptyList(), sequence1, sequence2);
- }
-
- /*
- * TODO perf : all "lookups" in source and target could be rewritten by using the lcs elements' matches.
- * This may or may not help, should be profiled.
- */
- /**
- * This will try and determine the index at which a given element from the {@code source} list should be
- * inserted in the {@code target} list. We expect {@code newElement} to be an element from the
- * {@code source} or to have a Match that allows us to map it to one of the {@code source} list's
- * elements.
- * <p>
- * The expected insertion index will always be relative to the Longest Common Subsequence (LCS) between
- * the two given lists, ignoring all elements from that LCS that have changed between the target list and
- * the common origin of the two. If there are more than one "longest" subsequence between the two lists,
- * the insertion index will be relative to the first that comes in the {@code target} list.
- * </p>
- * <p>
- * Note : we are not using Iterables as input in order to make use of the random access cost of
- * ArrayLists. This might also be converted to directly use arrays. This implementation will not play well
- * with LinkedLists or any List which needs to iterate over the values for each call to
- * {@link List#get(int)}, i.e any list which is not instanceof RandomAccess or does not satisfy its
- * contract.
- * </p>
- *
- * @param comparison
- * This will be used in order to retrieve the Match for EObjects when comparing them.
- * @param ignoredElements
- * If there are elements from {@code target} that should be ignored when searching for an
- * insertion index, set them here. Can be {@code null} or an empty list.
- * @param source
- * The List from which one element has to be added to the {@code target} list.
- * @param target
- * The List into which one element from {@code source} has to be added.
- * @param newElement
- * The element from {@code source} that needs to be added into {@code target}.
- * @param <E>
- * Type of the sequences content.
- * @return The index at which {@code newElement} should be inserted in {@code target}.
- * @see #longestCommonSubsequence(Comparison, List, List)
- * @noreference This method is not intended to be referenced by clients.
- */
- public static <E> int findInsertionIndex(Comparison comparison, Iterable<E> ignoredElements,
- List<E> source, List<E> target, E newElement) {
- final IEqualityHelper equalityHelper = comparison.getEqualityHelper();
-
- // We assume that "newElement" is in source but not in the target yet
- final List<E> lcs;
- if (ignoredElements != null) {
- lcs = longestCommonSubsequence(comparison, ignoredElements, source, target);
- } else {
- lcs = longestCommonSubsequence(comparison, source, target);
- }
-
- E firstLCS = null;
- E lastLCS = null;
- if (lcs.size() > 0) {
- firstLCS = lcs.get(0);
- lastLCS = lcs.listIterator(lcs.size()).previous();
- }
-
- final int noLCS = -2;
- int currentIndex = -1;
- int firstLCSIndex = -1;
- int lastLCSIndex = -1;
- if (firstLCS == null) {
- // We have no LCS
- firstLCSIndex = noLCS;
- lastLCSIndex = noLCS;
- }
-
- ListIterator<E> sourceIterator = source.listIterator();
- for (int i = 0; sourceIterator.hasNext() && (currentIndex == -1 || firstLCSIndex == -1); i++) {
- final E sourceElement = sourceIterator.next();
- if (currentIndex == -1 && equalityHelper.matchingValues(sourceElement, newElement)) {
- currentIndex = i;
- }
- if (firstLCSIndex == -1 && equalityHelper.matchingValues(sourceElement, firstLCS)) {
- firstLCSIndex = i;
- }
- }
- // The list may contain duplicates, use a reverse iteration to find the last from LCS.
- final int sourceSize = source.size();
- sourceIterator = source.listIterator(sourceSize);
- for (int i = sourceSize - 1; sourceIterator.hasPrevious() && lastLCSIndex == -1; i--) {
- final E sourceElement = sourceIterator.previous();
- if (lastLCSIndex == -1 && equalityHelper.matchingValues(sourceElement, lastLCS)) {
- lastLCSIndex = i;
- }
- }
-
- int insertionIndex = -1;
- if (firstLCSIndex == noLCS) {
- // We have no LCS. The two lists have no element in common. Insert at the very end of the target.
- insertionIndex = target.size();
- } else if (currentIndex < firstLCSIndex) {
- // The object we are to insert is before the LCS in source.
- insertionIndex = insertBeforeLCS(target, equalityHelper, firstLCS);
- } else if (currentIndex > lastLCSIndex) {
- // The object we are to insert is after the LCS in source.
- insertionIndex = findInsertionIndexAfterLCS(target, equalityHelper, lastLCS);
- } else {
- // Our object is in-between two elements A and B of the LCS in source
- insertionIndex = findInsertionIndexWithinLCS(source, target, equalityHelper, lcs, currentIndex);
- }
-
- // We somehow failed to determine the insertion index. Insert at the very end.
- if (insertionIndex == -1) {
- insertionIndex = target.size();
- }
-
- return insertionIndex;
- }
-
- /**
- * This will be called to try and find the insertion index for an element that is located in-between two
- * elements of the LCS between {@code source} and {@code target}.
- *
- * @param source
- * The List from which one element has to be added to the {@code target} list.
- * @param target
- * The List into which one element from {@code source} has to be added.
- * @param equalityHelper
- * The equality helper to use for this computation.
- * @param lcs
- * The lcs between {@code source} and {@code target}.
- * @param currentIndex
- * Current index (in {@code source} of the element we are to insert into {@code target}.
- * @param <E>
- * Type of the sequences content.
- * @return The index in the target list in which should be inserted that element.
- */
- private static <E> int findInsertionIndexWithinLCS(List<E> source, List<E> target,
- final IEqualityHelper equalityHelper, final List<E> lcs, int currentIndex) {
- int insertionIndex = -1;
- /*
- * If any element of the subsequence {<index of A>, <index of B>} from source had been in the same
- * subsequence in target, it would have been part of the LCS. We thus know none is.
- */
- // The insertion index will be just after A in target
-
- // First, find which element of the LCS is "A"
- int lcsIndexOfSubsequenceStart = -1;
- for (int i = 0; i < currentIndex; i++) {
- final E sourceElement = source.get(i);
-
- boolean isInLCS = false;
- for (int j = lcsIndexOfSubsequenceStart + 1; j < lcs.size() && !isInLCS; j++) {
- final E lcsElement = lcs.get(j);
-
- if (equalityHelper.matchingValues(sourceElement, lcsElement)) {
- isInLCS = true;
- lcsIndexOfSubsequenceStart++;
- }
- }
- }
-
- // Do we have duplicates before A in the lcs?
- final Multiset<E> dupesLCS = HashMultiset.create(lcs.subList(0, lcsIndexOfSubsequenceStart + 1));
- final E subsequenceStart = lcs.get(lcsIndexOfSubsequenceStart);
- int duplicatesToGo = dupesLCS.count(subsequenceStart) - 1;
-
- // Then, find the index of "A" in target
- for (int i = 0; i < target.size() && insertionIndex == -1; i++) {
- final E targetElement = target.get(i);
-
- if (equalityHelper.matchingValues(targetElement, subsequenceStart)) {
- if (duplicatesToGo > 0) {
- duplicatesToGo--;
- } else {
- insertionIndex = i + 1;
- }
- }
- }
-
- return insertionIndex;
- }
-
- /**
- * This will be called when we are to insert an element after the LCS in the {@code target} list.
- *
- * @param target
- * The List into which one element has to be added.
- * @param equalityHelper
- * The equality helper to use for this computation.
- * @param lastLCS
- * The last element of the LCS.
- * @param <E>
- * Type of the sequences content.
- * @return The index to use for insertion into {@code target} in order to add an element just after the
- * LCS.
- */
- private static <E> int findInsertionIndexAfterLCS(List<E> target, IEqualityHelper equalityHelper,
- E lastLCS) {
- int insertionIndex = -1;
- // The insertion index will be inside the subsequence {<LCS end>, <list.size()>} in target.
- /*
- * We'll insert it just after the LCS end : there cannot be any common element between the two lists
- * "after" the LCS since it would be part of the LCS itself.
- */
- for (int i = target.size() - 1; i >= 0 && insertionIndex == -1; i--) {
- final E targetElement = target.get(i);
- if (equalityHelper.matchingValues(targetElement, lastLCS)) {
- // We've reached the last element of the LCS in target. insert after it.
- insertionIndex = i + 1;
- }
- }
- return insertionIndex;
- }
-
- /**
- * This will be called when we are to insert an element before the LCS in the {@code target} list.
- *
- * @param target
- * The List into which one element has to be added.
- * @param equalityHelper
- * The equality helper to use for this computation.
- * @param firstLCS
- * The first element of the LCS.
- * @param <E>
- * Type of the sequences content.
- * @return The index to use for insertion into {@code target} in order to add an element just before the
- * LCS.
- */
- private static <E> int insertBeforeLCS(List<E> target, IEqualityHelper equalityHelper, E firstLCS) {
- int insertionIndex = -1;
- // The insertion index will be inside the subsequence {0, <LCS start>} in target
- /*
- * We'll insert it just before the LCS start : there cannot be any common element between the two
- * lists "before" the LCS since it would be part of the LCS itself.
- */
- for (int i = 0; i < target.size() && insertionIndex == -1; i++) {
- final E targetElement = target.get(i);
-
- if (equalityHelper.matchingValues(targetElement, firstLCS)) {
- // We've reached the first element from the LCS in target. Insert here
- insertionIndex = i;
- }
- }
- return insertionIndex;
- }
-
- /**
- * This will try and determine the index at which a given element from the {@code source} list should be
- * inserted in the {@code target} list. We expect {@code newElement} to be an element from the
- * {@code source} or to have a Match that allows us to map it to one of the {@code source} list's
- * elements.
- * <p>
- * The expected insertion index will always be relative to the Longest Common Subsequence (LCS) between
- * the two given lists. If there are more than one "longest" subsequence between the two lists, the
- * insertion index will be relative to the first that comes in the {@code target} list.
- * </p>
- * <p>
- * For example, assume {@code source} is <code>{"1", "2", "4", "6", "8", "3", "0", "7", "5"}</code> and
- * {@code target} is <code>{"8", "1", "2", "9", "3", "4", "7"}</code>; I try to merge the addition of
- * {@code "0"} in the right list. The returned "insertion index" will be {@code 5} : just after
- * {@code "3"}. There are two subsequence of the same "longest" length 4 :
- * <code>{"1", "2", "3", "7"}</code> and <code>{"1", "2", "4", "7"}</code>. However, the first of those
- * two in {@code target} is <code>{"1", "2", "3", "7"}</code>. The closest element before {@code "0"} in
- * this LCS in {@code source} is {@code "3"}.
- * </p>
- * <p>
- * Note : we are not using Iterables as input in order to make use of the random access cost of
- * ArrayLists. This might also be converted to directly use arrays. This implementation will not play well
- * with LinkedLists or any List which needs to iterate over the values for each call to
- * {@link List#get(int)}, i.e any list which is not instanceof RandomAccess or does not satisfy its
- * contract.
- * </p>
- *
- * @param comparison
- * This will be used in order to retrieve the Match for EObjects when comparing them.
- * @param source
- * The List from which one element has to be added to the {@code target} list.
- * @param target
- * The List into which one element from {@code source} has to be added.
- * @param newElement
- * The element from {@code source} that needs to be added into {@code target}.
- * @param <E>
- * Type of the sequences content.
- * @return The index at which {@code newElement} should be inserted in {@code target}.
- * @see #longestCommonSubsequence(Comparison, List, List)
- */
- public static <E> int findInsertionIndex(Comparison comparison, List<E> source, List<E> target,
- E newElement) {
- return findInsertionIndex(comparison, null, source, target, newElement);
- }
-
- /**
- * This is the main entry point for
- * {@link #findInsertionIndex(Comparison, EqualityHelper, Iterable, List, List, Object)}. It will use
- * default algorithms to determine the source and target lists as well as the list of elements that should
- * be ignored when computing the insertion index.
- *
- * @param comparison
- * This will be used in order to retrieve the Match for EObjects when comparing them.
- * @param diff
- * The diff which merging will trigger the need for an insertion index in its target list.
- * @param rightToLeft
- * {@code true} if the merging will be done into the left list, so that we should consider the
- * right model as the source and the left as the target.
- * @return The index at which this {@code diff}'s value should be inserted into the 'target' list, as
- * inferred from {@code rightToLeft}.
- * @see #findInsertionIndex(Comparison, Iterable, List, List, Object)
- */
- @SuppressWarnings("unchecked")
- public static int findInsertionIndex(Comparison comparison, Diff diff, boolean rightToLeft) {
- final EStructuralFeature feature;
- final Object value;
- if (diff instanceof AttributeChange) {
- feature = ((AttributeChange)diff).getAttribute();
- value = ((AttributeChange)diff).getValue();
- } else if (diff instanceof ReferenceChange) {
- feature = ((ReferenceChange)diff).getReference();
- value = ((ReferenceChange)diff).getValue();
- } else {
- throw new IllegalArgumentException(EMFCompareMessages.getString(
- "DiffUtil.IllegalDiff", diff.eClass().getName())); //$NON-NLS-1$
- }
- if (!feature.isMany()) {
- throw new IllegalArgumentException(EMFCompareMessages.getString(
- "DiffUtil.IllegalFeature", feature.getName())); //$NON-NLS-1$
- }
- final Match match = diff.getMatch();
-
- final EObject expectedContainer;
- if (feature instanceof EReference && ((EReference)feature).isContainment()
- && diff.getKind() == DifferenceKind.MOVE) {
- // The value can only be an EObject, and its match cannot be null.
- // If any of these two assumptions is wrong, something went horribly awry beforehand.
- final Match valueMatch = comparison.getMatch((EObject)value);
-
- final Match targetContainerMatch;
- // If it exists, use the source side's container as reference
- if (rightToLeft && valueMatch.getRight() != null) {
- targetContainerMatch = comparison.getMatch(valueMatch.getRight().eContainer());
- } else if (!rightToLeft && valueMatch.getLeft() != null) {
- targetContainerMatch = comparison.getMatch(valueMatch.getLeft().eContainer());
- } else {
- // Otherwise, the value we're moving on one side has been removed from its source side.
- targetContainerMatch = comparison.getMatch(valueMatch.getOrigin().eContainer());
- }
- if (rightToLeft) {
- expectedContainer = targetContainerMatch.getLeft();
- } else {
- expectedContainer = targetContainerMatch.getRight();
- }
- } else if (rightToLeft) {
- expectedContainer = match.getLeft();
- } else {
- expectedContainer = match.getRight();
- }
-
- final List<Object> sourceList = getSourceList(diff, feature, rightToLeft);
- final List<Object> targetList;
-
- if (expectedContainer != null) {
- final List<Object> temp = (List<Object>)ReferenceUtil.safeEGet(expectedContainer, feature);
- if (feature == EcorePackage.Literals.ECLASS__ESUPER_TYPES
- || feature == EcorePackage.Literals.EOPERATION__EEXCEPTIONS) {
- // workaround 394286
- targetList = temp;
- } else if (temp instanceof InternalEList<?>) {
- // EMF ignores the "resolve" flag for containment lists...
- targetList = ((InternalEList<Object>)temp).basicList();
- } else {
- targetList = temp;
- }
- } else {
- targetList = ImmutableList.of();
- }
-
- Iterable<Object> ignoredElements = Iterables.concat(computeIgnoredElements(targetList, diff),
- Collections.singleton(value));
- // We know we'll have to iterate quite a number of times on this one.
- ignoredElements = Lists.newArrayList(ignoredElements);
-
- return DiffUtil.findInsertionIndex(comparison, ignoredElements, sourceList, targetList, value);
- }
-
- /**
- * Retrieves the "source" list of the given {@code diff}. This will be different according to the kind of
- * change and the direction of the merging.
- *
- * @param diff
- * The diff for which merging we need a 'source'.
- * @param feature
- * The feature on which the merging is actually taking place.
- * @param rightToLeft
- * Direction of the merging. {@code true} if the merge is to be done on the left side, making
- * 'source' the right side, {@code false} otherwise.
- * @return The list that should be used as a source for this merge. May be empty, but never
- * <code>null</code>.
- */
- @SuppressWarnings("unchecked")
- private static List<Object> getSourceList(Diff diff, EStructuralFeature feature, boolean rightToLeft) {
- final Match match = diff.getMatch();
- final List<Object> sourceList;
- final EObject expectedContainer;
-
- if (diff.getKind() == DifferenceKind.MOVE) {
- final boolean undoingLeft = rightToLeft && diff.getSource() == DifferenceSource.LEFT;
- final boolean undoingRight = !rightToLeft && diff.getSource() == DifferenceSource.RIGHT;
-
- if ((undoingLeft || undoingRight) && match.getOrigin() != null) {
- expectedContainer = match.getOrigin();
- } else if (rightToLeft) {
- expectedContainer = match.getRight();
- } else {
- expectedContainer = match.getLeft();
- }
-
- } else {
- if (match.getOrigin() != null && diff.getKind() == DifferenceKind.DELETE) {
- expectedContainer = match.getOrigin();
- } else if (rightToLeft) {
- expectedContainer = match.getRight();
- } else {
- expectedContainer = match.getLeft();
- }
- }
-
- if (expectedContainer != null) {
- final List<Object> temp = (List<Object>)ReferenceUtil.safeEGet(expectedContainer, feature);
- if (feature == EcorePackage.Literals.ECLASS__ESUPER_TYPES
- || feature == EcorePackage.Literals.EOPERATION__EEXCEPTIONS) {
- // workaround 394286
- sourceList = temp;
- } else if (temp instanceof InternalEList<?>) {
- // EMF ignores the "resolve" flag for containment lists...
- sourceList = ((InternalEList<Object>)temp).basicList();
- } else {
- sourceList = temp;
- }
- } else {
- sourceList = ImmutableList.of();
- }
-
- return sourceList;
- }
-
- /**
- * Checks whether the given {@code sequence} contains the given {@code element} according to the semantics
- * of the given {@code equalityHelper}.
- *
- * @param comparison
- * This will be used in order to retrieve the Match for EObjects when comparing them.
- * @param equalityHelper
- * The {@link EqualityHelper} gives us the necessary semantics for Object matching.
- * @param sequence
- * The sequence which elements we need to compare with {@code element}.
- * @param element
- * The element we are seeking in {@code sequence}.
- * @param <E>
- * Type of the sequence content.
- * @return {@code true} if the given {@code sequence} contains an element matching {@code element},
- * {@code false} otherwise.
- * @see EqualityHelper#matchingValues(Comparison, Object, Object)
- */
- private static <E> boolean contains(Comparison comparison, IEqualityHelper equalityHelper,
- Iterable<E> sequence, E element) {
- final Iterator<E> iterator = sequence.iterator();
- while (iterator.hasNext()) {
- E candidate = iterator.next();
- if (equalityHelper.matchingValues(candidate, element)) {
- return true;
- }
- }
- return false;
- }
-
- /**
- * When computing the insertion index of an element in a list, we need to ignore all elements present in
- * that list that feature unresolved Diffs on the same feature.
- *
- * @param candidates
- * The sequence in which we need to compute an insertion index.
- * @param diff
- * The diff we are computing an insertion index for.
- * @param <E>
- * Type of the list's content.
- * @return The list of elements that should be ignored when computing the insertion index for a new
- * element in {@code candidates}.
- */
- private static <E> Iterable<E> computeIgnoredElements(Iterable<E> candidates, final Diff diff) {
- final Match match = diff.getMatch();
- final Iterable<? extends Diff> filteredCandidates = Lists.newArrayList(match.getDifferences());
- final EStructuralFeature feature;
- if (diff instanceof AttributeChange) {
- feature = ((AttributeChange)diff).getAttribute();
- } else if (diff instanceof ReferenceChange) {
- feature = ((ReferenceChange)diff).getReference();
- } else {
- return Collections.emptyList();
- }
-
- final Set<E> ignored = Sets.newLinkedHashSet();
- for (E candidate : candidates) {
- if (candidate instanceof EObject) {
- final Iterable<? extends Diff> differences = match.getComparison().getDifferences(
- (EObject)candidate);
- if (Iterables.any(differences, new UnresolvedDiffMatching(feature, candidate))) {
- ignored.add(candidate);
- }
- } else {
- if (Iterables.any(filteredCandidates, new UnresolvedDiffMatching(feature, candidate))) {
- ignored.add(candidate);
- }
- }
- }
- return ignored;
- }
-
- /**
- * This can be used to check whether a given Diff affects a value for which we can find another,
- * unresolved Diff on a given Feature.
- *
- * @author <a href="mailto:laurent.goubet@obeo.fr">Laurent Goubet</a>
- */
- private static class UnresolvedDiffMatching implements Predicate<Diff> {
- /** Feature on which we expect an unresolved diff. */
- private final EStructuralFeature feature;
-
- /** Element for which we expect an unresolved diff. */
- private final Object element;
-
- /**
- * Constructs a predicate that can be used to retrieve all unresolved diffs that apply to the given
- * {@code feature} and {@code element}.
- *
- * @param feature
- * The feature which our diffs must concern.
- * @param element
- * The element which must be our diffs' target.
- */
- public UnresolvedDiffMatching(EStructuralFeature feature, Object element) {
- this.feature = feature;
- this.element = element;
- }
-
- /**
- * {@inheritDoc}
- *
- * @see com.google.common.base.Predicate#apply(java.lang.Object)
- */
- public boolean apply(Diff input) {
- boolean apply = false;
- if (input instanceof AttributeChange) {
- apply = input.getState() == DifferenceState.UNRESOLVED
- && ((AttributeChange)input).getAttribute() == feature
- && matchingValues((AttributeChange)input, element);
- } else if (input instanceof ReferenceChange) {
- apply = input.getState() == DifferenceState.UNRESOLVED
- && ((ReferenceChange)input).getReference() == feature
- && matchingValues((ReferenceChange)input, element);
- } else {
- apply = false;
- }
- return apply;
- }
-
- /**
- * Checks that the value of the given diff matches <code>value</code>, resorting to the equality
- * helper if needed.
- *
- * @param diff
- * The diff which value we need to check.
- * @param value
- * The expected value of <code>diff</code>
- * @return <code>true</code> if the value matches.
- */
- private boolean matchingValues(AttributeChange diff, Object value) {
- if (diff.getValue() == value) {
- return true;
- }
- // Only resort to the equality helper as "last resort"
- final IEqualityHelper helper = diff.getMatch().getComparison().getEqualityHelper();
- return helper.matchingAttributeValues(diff.getValue(), value);
- }
-
- /**
- * Checks that the value of the given diff matches <code>value</code>, resorting to the equality
- * helper if needed.
- *
- * @param diff
- * The diff which value we need to check.
- * @param value
- * The expected value of <code>diff</code>
- * @return <code>true</code> if the value matches.
- */
- private boolean matchingValues(ReferenceChange diff, Object value) {
- if (diff.getValue() == value) {
- return true;
- }
- // Only resort to the equality helper as "last resort"
- final IEqualityHelper helper = diff.getMatch().getComparison().getEqualityHelper();
- return helper.matchingValues(diff.getValue(), value);
- }
- }
-}
+/******************************************************************************* + * Copyright (c) 2012, 2013 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.utils; + +import static com.google.common.collect.Iterables.addAll; +import static com.google.common.collect.Iterables.concat; + +import com.google.common.base.Function; +import com.google.common.base.Predicate; +import com.google.common.collect.HashMultiset; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; +import com.google.common.collect.Multiset; +import com.google.common.collect.Sets; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; +import java.util.Set; + +import org.eclipse.emf.compare.AttributeChange; +import org.eclipse.emf.compare.Comparison; +import org.eclipse.emf.compare.Diff; +import org.eclipse.emf.compare.DifferenceKind; +import org.eclipse.emf.compare.DifferenceSource; +import org.eclipse.emf.compare.DifferenceState; +import org.eclipse.emf.compare.EMFCompareMessages; +import org.eclipse.emf.compare.Match; +import org.eclipse.emf.compare.ReferenceChange; +import org.eclipse.emf.compare.ResourceAttachmentChange; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.EReference; +import org.eclipse.emf.ecore.EStructuralFeature; +import org.eclipse.emf.ecore.EcorePackage; +import org.eclipse.emf.ecore.util.InternalEList; + +/** + * This utility class will be used to provide similarity implementations. + * + * @author <a href="mailto:laurent.goubet@obeo.fr">Laurent Goubet</a> + */ +public final class DiffUtil { + /** This utility class does not need to be instantiated. */ + private DiffUtil() { + // Hides default constructor + } + + /** + * Computes the dice coefficient between the two given String's bigrams. + * <p> + * This implementation is case sensitive. + * </p> + * + * @param first + * First of the two Strings to compare. + * @param second + * Second of the two Strings to compare. + * @return The dice coefficient of the two given String's bigrams, ranging from 0 to 1. + */ + public static double diceCoefficient(String first, String second) { + final char[] str1 = first.toCharArray(); + final char[] str2 = second.toCharArray(); + + final double coefficient; + + if (Arrays.equals(str1, str2)) { + coefficient = 1d; + } else if (str1.length <= 2 || str2.length <= 2) { + int equalChars = 0; + + for (int i = 0; i < Math.min(str1.length, str2.length); i++) { + if (str1[i] == str2[i]) { + equalChars++; + } + } + + int union = str1.length + str2.length; + if (str1.length != str2.length) { + coefficient = (double)equalChars / union; + } else { + coefficient = ((double)equalChars * 2) / union; + } + } else { + Set<String> s1Bigrams = Sets.newHashSet(); + Set<String> s2Bigrams = Sets.newHashSet(); + + for (int i = 0; i < str1.length - 1; i++) { + char[] chars = new char[] {str1[i], str1[i + 1], }; + s1Bigrams.add(String.valueOf(chars)); + } + for (int i = 0; i < str2.length - 1; i++) { + char[] chars = new char[] {str2[i], str2[i + 1], }; + s2Bigrams.add(String.valueOf(chars)); + } + + Set<String> intersection = Sets.intersection(s1Bigrams, s2Bigrams); + coefficient = (2d * intersection.size()) / (s1Bigrams.size() + s2Bigrams.size()); + } + + return coefficient; + } + + /** + * This will compute the longest common subsequence between the two given Lists, ignoring any object that + * is included in {@code ignoredElements}. We will use + * {@link EqualityHelper#matchingValues(Comparison, Object, Object)} in order to try and match the values + * from both lists two-by-two. This can thus be used both for reference values or attribute values. If + * there are two subsequences of the same "longest" length, the first (according to the second argument) + * will be returned. + * <p> + * Take note that this might be slower than + * {@link #longestCommonSubsequence(Comparison, EqualityHelper, List, List)} and should only be used when + * elements should be removed from the potential LCS. This is mainly aimed at merge operations during + * three-way comparisons as some objects might be in conflict and thus shifting the computed insertion + * indices. + * </p> + * <p> + * Please see {@link #longestCommonSubsequence(Comparison, EqualityHelper, List, List)} for a more + * complete description. + * </p> + * + * @param comparison + * This will be used in order to retrieve the Match for EObjects when comparing them. + * @param ignoredElements + * Specifies elements that should be excluded from the subsequences. + * @param sequence1 + * First of the two sequences to consider. + * @param sequence2 + * Second of the two sequences to consider. + * @param <E> + * Type of the sequences content. + * @return The LCS of the two given sequences. Will never be the same instance as one of the input + * sequences. + * @see #longestCommonSubsequence(Comparison, EqualityHelper, List, List). + */ + public static <E> List<E> longestCommonSubsequence(Comparison comparison, Iterable<E> ignoredElements, + List<E> sequence1, List<E> sequence2) { + // FIXME : merge the two "LCS" methods in one (only one line differs...) + final List<E> copy1 = Lists.newArrayList(sequence1); + final List<E> copy2 = Lists.newArrayList(sequence2); + + // Reduce sets + final List<E> prefix = trimPrefix(comparison, ignoredElements, copy1, copy2); + final List<E> suffix = trimSuffix(comparison, ignoredElements, copy1, copy2); + + final IEqualityHelper equalityHelper = comparison.getEqualityHelper(); + final int size1 = copy1.size(); + final int size2 = copy2.size(); + + final int[][] matrix = new int[size1 + 1][size2 + 1]; + + // Compute the LCS matrix + for (int i = 1; i <= size1; i++) { + final E first = copy1.get(i - 1); + for (int j = 1; j <= size2; j++) { + final E second = copy2.get(j - 1); + if (equalityHelper.matchingValues(first, second) + && !contains(comparison, equalityHelper, ignoredElements, second)) { + matrix[i][j] = 1 + matrix[i - 1][j - 1]; + } else { + matrix[i][j] = Math.max(matrix[i - 1][j], matrix[i][j - 1]); + } + } + } + + // Traceback the matrix to create the final LCS + int current1 = size1; + int current2 = size2; + final List<E> result = Lists.newArrayList(); + + while (current1 > 0 && current2 > 0) { + final int currentLength = matrix[current1][current2]; + final int nextLeft = matrix[current1][current2 - 1]; + final int nextUp = matrix[current1 - 1][current2]; + if (currentLength > nextLeft && currentLength > nextUp) { + result.add(copy1.get(current1 - 1)); + current1--; + current2--; + } else if (nextLeft >= nextUp) { + current2--; + } else { + current1--; + } + } + + return ImmutableList.copyOf(Iterables.concat(prefix, Lists.reverse(result), suffix)); + } + + /** + * Trims and returns the common prefix of the two given sequences. All ignored elements within or after + * this common prefix will also be trimmed. + * <p> + * Note that the two given sequences will be modified in-place. + * </p> + * + * @param comparison + * This will be used in order to retrieve the Match for EObjects when comparing them. + * @param ignoredElements + * Specifies elements that should be excluded from the subsequences. + * @param sequence1 + * First of the two sequences to consider. + * @param sequence2 + * Second of the two sequences to consider. + * @param <E> + * Type of the sequences content. + * @return The common prefix of the two given sequences, less their ignored elements. As a side note, both + * {@code sequence1} and {@code sequence2} will have been trimmed of their prefix when this + * returns. + */ + private static <E> List<E> trimPrefix(Comparison comparison, Iterable<E> ignoredElements, + List<E> sequence1, List<E> sequence2) { + final IEqualityHelper equalityHelper = comparison.getEqualityHelper(); + final int size1 = sequence1.size(); + final int size2 = sequence2.size(); + + final List<E> prefix = Lists.newArrayList(); + int start1 = 1; + int start2 = 1; + boolean matching = true; + while (start1 <= size1 && start2 <= size2 && matching) { + final E first = sequence1.get(start1 - 1); + final E second = sequence2.get(start2 - 1); + if (equalityHelper.matchingValues(first, second)) { + prefix.add(first); + start1++; + start2++; + } else { + boolean ignore1 = contains(comparison, equalityHelper, ignoredElements, first); + boolean ignore2 = contains(comparison, equalityHelper, ignoredElements, second); + if (ignore1) { + start1++; + } + if (ignore2) { + start2++; + } + if (!ignore1 && !ignore2) { + matching = false; + } + } + } + for (int i = 1; i < start1; i++) { + sequence1.remove(0); + } + for (int i = 1; i < start2; i++) { + sequence2.remove(0); + } + + return prefix; + } + + /** + * Trims and returns the common suffix of the two given sequences. All ignored elements within or before + * this common suffix will also be trimmed. + * <p> + * Note that the two given sequences will be modified in-place. + * </p> + * + * @param comparison + * This will be used in order to retrieve the Match for EObjects when comparing them. + * @param ignoredElements + * Specifies elements that should be excluded from the subsequences. + * @param sequence1 + * First of the two sequences to consider. + * @param sequence2 + * Second of the two sequences to consider. + * @param <E> + * Type of the sequences content. + * @return The common suffix of the two given sequences, less their ignored elements. As a side note, both + * {@code sequence1} and {@code sequence2} will have been trimmed of their suffix when this + * returns. + */ + private static <E> List<E> trimSuffix(Comparison comparison, Iterable<E> ignoredElements, + List<E> sequence1, List<E> sequence2) { + final IEqualityHelper equalityHelper = comparison.getEqualityHelper(); + final int size1 = sequence1.size(); + final int size2 = sequence2.size(); + + final List<E> suffix = Lists.newArrayList(); + int end1 = size1; + int end2 = size2; + boolean matching = true; + while (end1 > 0 && end2 > 0 && matching) { + final E first = sequence1.get(end1 - 1); + final E second = sequence2.get(end2 - 1); + if (equalityHelper.matchingValues(first, second)) { + suffix.add(first); + end1--; + end2--; + } else { + boolean ignore1 = contains(comparison, equalityHelper, ignoredElements, first); + boolean ignore2 = contains(comparison, equalityHelper, ignoredElements, second); + if (ignore1) { + end1--; + } + if (ignore2) { + end2--; + } + if (!ignore1 && !ignore2) { + matching = false; + } + } + } + for (int i = size1; i > end1; i--) { + sequence1.remove(sequence1.size() - 1); + } + for (int i = size2; i > end2; i--) { + sequence2.remove(sequence2.size() - 1); + } + + return Lists.reverse(suffix); + } + + /** + * This will compute the longest common subsequence between the two given Lists. We will use + * {@link EqualityHelper#matchingValues(Comparison, Object, Object)} in order to try and match the values + * from both lists two-by-two. This can thus be used both for reference values or attribute values. If + * there are two subsequences of the same "longest" length, the first (according to the second argument) + * will be returned. + * <p> + * For example, it the two given sequence are, in this order, <code>{"a", "b", "c", "d", "e"}</code> and + * <code>{"c", "z", "d", "a", "b"}</code>, there are two "longest" subsequences : <code>{"a", "b"}</code> + * and <code>{"c", "d"}</code>. The first of those two subsequences in the second list is + * <code>{"c", "d"}</code>. On the other hand, the LCS of <code>{"a", "b", "c", "d", "e"}</code> and + * <code>{"y", "c", "d", "e", "b"}</code> is <code>{"c", "d", "e"}</code>. + * </p> + * <p> + * The following algorithm has been inferred from the wikipedia article on the Longest Common Subsequence, + * http://en.wikipedia.org/wiki/Longest_common_subsequence_problem at the time of writing. It is + * decomposed in two : we first compute the LCS matrix, then we backtrack through the input to determine + * the LCS. Evaluation will be shortcut after the first part if the LCS is one of the two input sequences. + * </p> + * <p> + * Note : we are not using Iterables as input in order to make use of the random access cost of + * ArrayLists. This might also be converted to directly use arrays. This implementation will not play well + * with LinkedLists or any List which needs to iterate over the values for each call to + * {@link List#get(int)}, i.e any list which is not instanceof RandomAccess or does not satisfy its + * contract. + * </p> + * + * @param comparison + * This will be used in order to retrieve the Match for EObjects when comparing them. + * @param sequence1 + * First of the two sequences to consider. + * @param sequence2 + * Second of the two sequences to consider. + * @param <E> + * Type of the sequences content. + * @return The LCS of the two given sequences. Will never be the same instance as one of the input + * sequences. + */ + public static <E> List<E> longestCommonSubsequence(Comparison comparison, List<E> sequence1, + List<E> sequence2) { + return longestCommonSubsequence(comparison, Collections.<E> emptyList(), sequence1, sequence2); + } + + /* + * TODO perf : all "lookups" in source and target could be rewritten by using the lcs elements' matches. + * This may or may not help, should be profiled. + */ + /** + * This will try and determine the index at which a given element from the {@code source} list should be + * inserted in the {@code target} list. We expect {@code newElement} to be an element from the + * {@code source} or to have a Match that allows us to map it to one of the {@code source} list's + * elements. + * <p> + * The expected insertion index will always be relative to the Longest Common Subsequence (LCS) between + * the two given lists, ignoring all elements from that LCS that have changed between the target list and + * the common origin of the two. If there are more than one "longest" subsequence between the two lists, + * the insertion index will be relative to the first that comes in the {@code target} list. + * </p> + * <p> + * Note : we are not using Iterables as input in order to make use of the random access cost of + * ArrayLists. This might also be converted to directly use arrays. This implementation will not play well + * with LinkedLists or any List which needs to iterate over the values for each call to + * {@link List#get(int)}, i.e any list which is not instanceof RandomAccess or does not satisfy its + * contract. + * </p> + * + * @param comparison + * This will be used in order to retrieve the Match for EObjects when comparing them. + * @param ignoredElements + * If there are elements from {@code target} that should be ignored when searching for an + * insertion index, set them here. Can be {@code null} or an empty list. + * @param source + * The List from which one element has to be added to the {@code target} list. + * @param target + * The List into which one element from {@code source} has to be added. + * @param newElement + * The element from {@code source} that needs to be added into {@code target}. + * @param <E> + * Type of the sequences content. + * @return The index at which {@code newElement} should be inserted in {@code target}. + * @see #longestCommonSubsequence(Comparison, List, List) + * @noreference This method is not intended to be referenced by clients. + */ + public static <E> int findInsertionIndex(Comparison comparison, Iterable<E> ignoredElements, + List<E> source, List<E> target, E newElement) { + final IEqualityHelper equalityHelper = comparison.getEqualityHelper(); + + // We assume that "newElement" is in source but not in the target yet + final List<E> lcs; + if (ignoredElements != null) { + lcs = longestCommonSubsequence(comparison, ignoredElements, source, target); + } else { + lcs = longestCommonSubsequence(comparison, source, target); + } + + E firstLCS = null; + E lastLCS = null; + if (lcs.size() > 0) { + firstLCS = lcs.get(0); + lastLCS = lcs.listIterator(lcs.size()).previous(); + } + + final int noLCS = -2; + int currentIndex = -1; + int firstLCSIndex = -1; + int lastLCSIndex = -1; + if (firstLCS == null) { + // We have no LCS + firstLCSIndex = noLCS; + lastLCSIndex = noLCS; + } + + ListIterator<E> sourceIterator = source.listIterator(); + for (int i = 0; sourceIterator.hasNext() && (currentIndex == -1 || firstLCSIndex == -1); i++) { + final E sourceElement = sourceIterator.next(); + if (currentIndex == -1 && equalityHelper.matchingValues(sourceElement, newElement)) { + currentIndex = i; + } + if (firstLCSIndex == -1 && equalityHelper.matchingValues(sourceElement, firstLCS)) { + firstLCSIndex = i; + } + } + // The list may contain duplicates, use a reverse iteration to find the last from LCS. + final int sourceSize = source.size(); + sourceIterator = source.listIterator(sourceSize); + for (int i = sourceSize - 1; sourceIterator.hasPrevious() && lastLCSIndex == -1; i--) { + final E sourceElement = sourceIterator.previous(); + if (lastLCSIndex == -1 && equalityHelper.matchingValues(sourceElement, lastLCS)) { + lastLCSIndex = i; + } + } + + int insertionIndex = -1; + if (firstLCSIndex == noLCS) { + // We have no LCS. The two lists have no element in common. Insert at the very end of the target. + insertionIndex = target.size(); + } else if (currentIndex < firstLCSIndex) { + // The object we are to insert is before the LCS in source. + insertionIndex = insertBeforeLCS(target, equalityHelper, firstLCS); + } else if (currentIndex > lastLCSIndex) { + // The object we are to insert is after the LCS in source. + insertionIndex = findInsertionIndexAfterLCS(target, equalityHelper, lastLCS); + } else { + // Our object is in-between two elements A and B of the LCS in source + insertionIndex = findInsertionIndexWithinLCS(source, target, equalityHelper, lcs, currentIndex); + } + + // We somehow failed to determine the insertion index. Insert at the very end. + if (insertionIndex == -1) { + insertionIndex = target.size(); + } + + return insertionIndex; + } + + /** + * This will be called to try and find the insertion index for an element that is located in-between two + * elements of the LCS between {@code source} and {@code target}. + * + * @param source + * The List from which one element has to be added to the {@code target} list. + * @param target + * The List into which one element from {@code source} has to be added. + * @param equalityHelper + * The equality helper to use for this computation. + * @param lcs + * The lcs between {@code source} and {@code target}. + * @param currentIndex + * Current index (in {@code source} of the element we are to insert into {@code target}. + * @param <E> + * Type of the sequences content. + * @return The index in the target list in which should be inserted that element. + */ + private static <E> int findInsertionIndexWithinLCS(List<E> source, List<E> target, + final IEqualityHelper equalityHelper, final List<E> lcs, int currentIndex) { + int insertionIndex = -1; + /* + * If any element of the subsequence {<index of A>, <index of B>} from source had been in the same + * subsequence in target, it would have been part of the LCS. We thus know none is. + */ + // The insertion index will be just after A in target + + // First, find which element of the LCS is "A" + int lcsIndexOfSubsequenceStart = -1; + for (int i = 0; i < currentIndex; i++) { + final E sourceElement = source.get(i); + + boolean isInLCS = false; + for (int j = lcsIndexOfSubsequenceStart + 1; j < lcs.size() && !isInLCS; j++) { + final E lcsElement = lcs.get(j); + + if (equalityHelper.matchingValues(sourceElement, lcsElement)) { + isInLCS = true; + lcsIndexOfSubsequenceStart++; + } + } + } + + // Do we have duplicates before A in the lcs? + final Multiset<E> dupesLCS = HashMultiset.create(lcs.subList(0, lcsIndexOfSubsequenceStart + 1)); + final E subsequenceStart = lcs.get(lcsIndexOfSubsequenceStart); + int duplicatesToGo = dupesLCS.count(subsequenceStart) - 1; + + // Then, find the index of "A" in target + for (int i = 0; i < target.size() && insertionIndex == -1; i++) { + final E targetElement = target.get(i); + + if (equalityHelper.matchingValues(targetElement, subsequenceStart)) { + if (duplicatesToGo > 0) { + duplicatesToGo--; + } else { + insertionIndex = i + 1; + } + } + } + + return insertionIndex; + } + + /** + * This will be called when we are to insert an element after the LCS in the {@code target} list. + * + * @param target + * The List into which one element has to be added. + * @param equalityHelper + * The equality helper to use for this computation. + * @param lastLCS + * The last element of the LCS. + * @param <E> + * Type of the sequences content. + * @return The index to use for insertion into {@code target} in order to add an element just after the + * LCS. + */ + private static <E> int findInsertionIndexAfterLCS(List<E> target, IEqualityHelper equalityHelper, + E lastLCS) { + int insertionIndex = -1; + // The insertion index will be inside the subsequence {<LCS end>, <list.size()>} in target. + /* + * We'll insert it just after the LCS end : there cannot be any common element between the two lists + * "after" the LCS since it would be part of the LCS itself. + */ + for (int i = target.size() - 1; i >= 0 && insertionIndex == -1; i--) { + final E targetElement = target.get(i); + if (equalityHelper.matchingValues(targetElement, lastLCS)) { + // We've reached the last element of the LCS in target. insert after it. + insertionIndex = i + 1; + } + } + return insertionIndex; + } + + /** + * This will be called when we are to insert an element before the LCS in the {@code target} list. + * + * @param target + * The List into which one element has to be added. + * @param equalityHelper + * The equality helper to use for this computation. + * @param firstLCS + * The first element of the LCS. + * @param <E> + * Type of the sequences content. + * @return The index to use for insertion into {@code target} in order to add an element just before the + * LCS. + */ + private static <E> int insertBeforeLCS(List<E> target, IEqualityHelper equalityHelper, E firstLCS) { + int insertionIndex = -1; + // The insertion index will be inside the subsequence {0, <LCS start>} in target + /* + * We'll insert it just before the LCS start : there cannot be any common element between the two + * lists "before" the LCS since it would be part of the LCS itself. + */ + for (int i = 0; i < target.size() && insertionIndex == -1; i++) { + final E targetElement = target.get(i); + + if (equalityHelper.matchingValues(targetElement, firstLCS)) { + // We've reached the first element from the LCS in target. Insert here + insertionIndex = i; + } + } + return insertionIndex; + } + + /** + * This will try and determine the index at which a given element from the {@code source} list should be + * inserted in the {@code target} list. We expect {@code newElement} to be an element from the + * {@code source} or to have a Match that allows us to map it to one of the {@code source} list's + * elements. + * <p> + * The expected insertion index will always be relative to the Longest Common Subsequence (LCS) between + * the two given lists. If there are more than one "longest" subsequence between the two lists, the + * insertion index will be relative to the first that comes in the {@code target} list. + * </p> + * <p> + * For example, assume {@code source} is <code>{"1", "2", "4", "6", "8", "3", "0", "7", "5"}</code> and + * {@code target} is <code>{"8", "1", "2", "9", "3", "4", "7"}</code>; I try to merge the addition of + * {@code "0"} in the right list. The returned "insertion index" will be {@code 5} : just after + * {@code "3"}. There are two subsequence of the same "longest" length 4 : + * <code>{"1", "2", "3", "7"}</code> and <code>{"1", "2", "4", "7"}</code>. However, the first of those + * two in {@code target} is <code>{"1", "2", "3", "7"}</code>. The closest element before {@code "0"} in + * this LCS in {@code source} is {@code "3"}. + * </p> + * <p> + * Note : we are not using Iterables as input in order to make use of the random access cost of + * ArrayLists. This might also be converted to directly use arrays. This implementation will not play well + * with LinkedLists or any List which needs to iterate over the values for each call to + * {@link List#get(int)}, i.e any list which is not instanceof RandomAccess or does not satisfy its + * contract. + * </p> + * + * @param comparison + * This will be used in order to retrieve the Match for EObjects when comparing them. + * @param source + * The List from which one element has to be added to the {@code target} list. + * @param target + * The List into which one element from {@code source} has to be added. + * @param newElement + * The element from {@code source} that needs to be added into {@code target}. + * @param <E> + * Type of the sequences content. + * @return The index at which {@code newElement} should be inserted in {@code target}. + * @see #longestCommonSubsequence(Comparison, List, List) + */ + public static <E> int findInsertionIndex(Comparison comparison, List<E> source, List<E> target, + E newElement) { + return findInsertionIndex(comparison, null, source, target, newElement); + } + + /** + * This is the main entry point for + * {@link #findInsertionIndex(Comparison, EqualityHelper, Iterable, List, List, Object)}. It will use + * default algorithms to determine the source and target lists as well as the list of elements that should + * be ignored when computing the insertion index. + * + * @param comparison + * This will be used in order to retrieve the Match for EObjects when comparing them. + * @param diff + * The diff which merging will trigger the need for an insertion index in its target list. + * @param rightToLeft + * {@code true} if the merging will be done into the left list, so that we should consider the + * right model as the source and the left as the target. + * @return The index at which this {@code diff}'s value should be inserted into the 'target' list, as + * inferred from {@code rightToLeft}. + * @see #findInsertionIndex(Comparison, Iterable, List, List, Object) + */ + @SuppressWarnings("unchecked") + public static int findInsertionIndex(Comparison comparison, Diff diff, boolean rightToLeft) { + final EStructuralFeature feature; + final Object value; + if (diff instanceof AttributeChange) { + feature = ((AttributeChange)diff).getAttribute(); + value = ((AttributeChange)diff).getValue(); + } else if (diff instanceof ReferenceChange) { + feature = ((ReferenceChange)diff).getReference(); + value = ((ReferenceChange)diff).getValue(); + } else { + throw new IllegalArgumentException(EMFCompareMessages.getString( + "DiffUtil.IllegalDiff", diff.eClass().getName())); //$NON-NLS-1$ + } + if (!feature.isMany()) { + throw new IllegalArgumentException(EMFCompareMessages.getString( + "DiffUtil.IllegalFeature", feature.getName())); //$NON-NLS-1$ + } + final Match match = diff.getMatch(); + + final EObject expectedContainer; + if (feature instanceof EReference && ((EReference)feature).isContainment() + && diff.getKind() == DifferenceKind.MOVE) { + // The value can only be an EObject, and its match cannot be null. + // If any of these two assumptions is wrong, something went horribly awry beforehand. + final Match valueMatch = comparison.getMatch((EObject)value); + + final Match targetContainerMatch; + // If it exists, use the source side's container as reference + if (rightToLeft && valueMatch.getRight() != null) { + targetContainerMatch = comparison.getMatch(valueMatch.getRight().eContainer()); + } else if (!rightToLeft && valueMatch.getLeft() != null) { + targetContainerMatch = comparison.getMatch(valueMatch.getLeft().eContainer()); + } else { + // Otherwise, the value we're moving on one side has been removed from its source side. + targetContainerMatch = comparison.getMatch(valueMatch.getOrigin().eContainer()); + } + if (rightToLeft) { + expectedContainer = targetContainerMatch.getLeft(); + } else { + expectedContainer = targetContainerMatch.getRight(); + } + } else if (rightToLeft) { + expectedContainer = match.getLeft(); + } else { + expectedContainer = match.getRight(); + } + + final List<Object> sourceList = getSourceList(diff, feature, rightToLeft); + final List<Object> targetList; + + if (expectedContainer != null) { + final List<Object> temp = (List<Object>)ReferenceUtil.safeEGet(expectedContainer, feature); + if (feature == EcorePackage.Literals.ECLASS__ESUPER_TYPES + || feature == EcorePackage.Literals.EOPERATION__EEXCEPTIONS) { + // workaround 394286 + targetList = temp; + } else if (temp instanceof InternalEList<?>) { + // EMF ignores the "resolve" flag for containment lists... + targetList = ((InternalEList<Object>)temp).basicList(); + } else { + targetList = temp; + } + } else { + targetList = ImmutableList.of(); + } + + Iterable<Object> ignoredElements = Iterables.concat(computeIgnoredElements(targetList, diff), + Collections.singleton(value)); + // We know we'll have to iterate quite a number of times on this one. + ignoredElements = Lists.newArrayList(ignoredElements); + + return DiffUtil.findInsertionIndex(comparison, ignoredElements, sourceList, targetList, value); + } + + /** + * Retrieves the "source" list of the given {@code diff}. This will be different according to the kind of + * change and the direction of the merging. + * + * @param diff + * The diff for which merging we need a 'source'. + * @param feature + * The feature on which the merging is actually taking place. + * @param rightToLeft + * Direction of the merging. {@code true} if the merge is to be done on the left side, making + * 'source' the right side, {@code false} otherwise. + * @return The list that should be used as a source for this merge. May be empty, but never + * <code>null</code>. + */ + @SuppressWarnings("unchecked") + private static List<Object> getSourceList(Diff diff, EStructuralFeature feature, boolean rightToLeft) { + final Match match = diff.getMatch(); + final List<Object> sourceList; + final EObject expectedContainer; + + if (diff.getKind() == DifferenceKind.MOVE) { + final boolean undoingLeft = rightToLeft && diff.getSource() == DifferenceSource.LEFT; + final boolean undoingRight = !rightToLeft && diff.getSource() == DifferenceSource.RIGHT; + + if ((undoingLeft || undoingRight) && match.getOrigin() != null) { + expectedContainer = match.getOrigin(); + } else if (rightToLeft) { + expectedContainer = match.getRight(); + } else { + expectedContainer = match.getLeft(); + } + + } else { + if (match.getOrigin() != null && diff.getKind() == DifferenceKind.DELETE) { + expectedContainer = match.getOrigin(); + } else if (rightToLeft) { + expectedContainer = match.getRight(); + } else { + expectedContainer = match.getLeft(); + } + } + + if (expectedContainer != null) { + final List<Object> temp = (List<Object>)ReferenceUtil.safeEGet(expectedContainer, feature); + if (feature == EcorePackage.Literals.ECLASS__ESUPER_TYPES + || feature == EcorePackage.Literals.EOPERATION__EEXCEPTIONS) { + // workaround 394286 + sourceList = temp; + } else if (temp instanceof InternalEList<?>) { + // EMF ignores the "resolve" flag for containment lists... + sourceList = ((InternalEList<Object>)temp).basicList(); + } else { + sourceList = temp; + } + } else { + sourceList = ImmutableList.of(); + } + + return sourceList; + } + + /** + * Checks whether the given {@code sequence} contains the given {@code element} according to the semantics + * of the given {@code equalityHelper}. + * + * @param comparison + * This will be used in order to retrieve the Match for EObjects when comparing them. + * @param equalityHelper + * The {@link EqualityHelper} gives us the necessary semantics for Object matching. + * @param sequence + * The sequence which elements we need to compare with {@code element}. + * @param element + * The element we are seeking in {@code sequence}. + * @param <E> + * Type of the sequence content. + * @return {@code true} if the given {@code sequence} contains an element matching {@code element}, + * {@code false} otherwise. + * @see EqualityHelper#matchingValues(Comparison, Object, Object) + */ + private static <E> boolean contains(Comparison comparison, IEqualityHelper equalityHelper, + Iterable<E> sequence, E element) { + final Iterator<E> iterator = sequence.iterator(); + while (iterator.hasNext()) { + E candidate = iterator.next(); + if (equalityHelper.matchingValues(candidate, element)) { + return true; + } + } + return false; + } + + /** + * When computing the insertion index of an element in a list, we need to ignore all elements present in + * that list that feature unresolved Diffs on the same feature. + * + * @param candidates + * The sequence in which we need to compute an insertion index. + * @param diff + * The diff we are computing an insertion index for. + * @param <E> + * Type of the list's content. + * @return The list of elements that should be ignored when computing the insertion index for a new + * element in {@code candidates}. + */ + private static <E> Iterable<E> computeIgnoredElements(Iterable<E> candidates, final Diff diff) { + final Match match = diff.getMatch(); + final Iterable<? extends Diff> filteredCandidates = Lists.newArrayList(match.getDifferences()); + final EStructuralFeature feature; + if (diff instanceof AttributeChange) { + feature = ((AttributeChange)diff).getAttribute(); + } else if (diff instanceof ReferenceChange) { + feature = ((ReferenceChange)diff).getReference(); + } else { + return Collections.emptyList(); + } + + final Set<E> ignored = Sets.newLinkedHashSet(); + for (E candidate : candidates) { + if (candidate instanceof EObject) { + final Iterable<? extends Diff> differences = match.getComparison().getDifferences( + (EObject)candidate); + if (Iterables.any(differences, new UnresolvedDiffMatching(feature, candidate))) { + ignored.add(candidate); + } + } else { + if (Iterables.any(filteredCandidates, new UnresolvedDiffMatching(feature, candidate))) { + ignored.add(candidate); + } + } + } + return ignored; + } + + /** + * When merging a {@link Diff}, returns the sub diffs of this given diff, and all associated diffs (see + * {@link DiffUtil#getAssociatedDiffs(Iterable, boolean, Diff)}) of these sub diffs. + * <p> + * If the diff is an {@link AttributeChange} or a {@link ResourceAttachmentChange}, this method will + * return an empty iterable. + * </p> + * <p> + * If the diff is a {@link ReferenceChange} this method will return all differences contained in the match + * that contains the value of the reference change, and all associated diffs of these differences. + * </p> + * + * @param leftToRight + * the direction of merge. + * @return an iterable containing the sub diffs of this given diff, and all associated diffs of these sub + * diffs. + * @since 3.0 + */ + public static Function<Diff, Iterable<Diff>> getSubDiffs(final boolean leftToRight) { + return new Function<Diff, Iterable<Diff>>() { + public Iterable<Diff> apply(Diff diff) { + if (diff instanceof ReferenceChange) { + Match match = diff.getMatch(); + Match matchOfValue = diff.getMatch().getComparison().getMatch( + ((ReferenceChange)diff).getValue()); + if (!match.equals(matchOfValue) && match.getSubmatches().contains(matchOfValue)) { + final Iterable<Diff> subDiffs = matchOfValue.getAllDifferences(); + final Iterable<Diff> associatedDiffs = getAssociatedDiffs(diff, subDiffs, leftToRight); + return ImmutableSet.copyOf(concat(subDiffs, associatedDiffs)); + } + } + return ImmutableSet.of(); + } + }; + } + + /** + * When merging a {@link Diff}, returns the associated diffs of the sub diffs of the diff, and all sub + * diffs (see {@link DiffUtil#getSubDiffs(boolean)}) of these associated diffs. + * <p> + * The associated diffs of a diff are : + * <p> + * - {@link Diff#getRequiredBy()} if the source of the diff is the left side and the direction of the + * merge is right to left. + * </p> + * <p> + * - {@link Diff#getRequiredBy()} if the source of the diff is the right side and the direction of the + * merge is left to right. + * </p> + * <p> + * - {@link Diff#getRequires()} if the source of the diff is the left side and the direction of the merge + * is left to right. + * </p> + * <p> + * - {@link Diff#getRequires()} if the source of the diff is the right side and the direction of the merge + * is right to left. + * </p> + * </p> + * + * @param diffRoot + * the given diff. + * @param subDiffs + * the iterable of sub diffs for which we want the associated diffs. + * @param leftToRight + * the direction of merge. + * @return an iterable containing the associated diffs of these given sub diffs, and all sub diffs of + * these associated diffs. + * @since 3.0 + */ + public static Iterable<Diff> getAssociatedDiffs(final Diff diffRoot, Iterable<Diff> subDiffs, + boolean leftToRight) { + Collection<Diff> associatedDiffs = new HashSet<Diff>(); + for (Diff diff : subDiffs) { + final Collection<Diff> reqs = new HashSet<Diff>(); + if (leftToRight) { + if (diff.getSource() == DifferenceSource.LEFT) { + reqs.addAll(diff.getRequires()); + } else { + reqs.addAll(diff.getRequiredBy()); + } + } else { + if (diff.getSource() == DifferenceSource.LEFT) { + reqs.addAll(diff.getRequiredBy()); + } else { + reqs.addAll(diff.getRequires()); + } + } + reqs.remove(diffRoot); + associatedDiffs.addAll(reqs); + for (Diff req : reqs) { + if (!Iterables.contains(subDiffs, req)) { + addAll(associatedDiffs, getSubDiffs(leftToRight).apply(req)); + } + } + } + return associatedDiffs; + } + + /** + * This can be used to check whether a given Diff affects a value for which we can find another, + * unresolved Diff on a given Feature. + * + * @author <a href="mailto:laurent.goubet@obeo.fr">Laurent Goubet</a> + */ + private static class UnresolvedDiffMatching implements Predicate<Diff> { + /** Feature on which we expect an unresolved diff. */ + private final EStructuralFeature feature; + + /** Element for which we expect an unresolved diff. */ + private final Object element; + + /** + * Constructs a predicate that can be used to retrieve all unresolved diffs that apply to the given + * {@code feature} and {@code element}. + * + * @param feature + * The feature which our diffs must concern. + * @param element + * The element which must be our diffs' target. + */ + public UnresolvedDiffMatching(EStructuralFeature feature, Object element) { + this.feature = feature; + this.element = element; + } + + /** + * {@inheritDoc} + * + * @see com.google.common.base.Predicate#apply(java.lang.Object) + */ + public boolean apply(Diff input) { + boolean apply = false; + if (input instanceof AttributeChange) { + apply = input.getState() == DifferenceState.UNRESOLVED + && ((AttributeChange)input).getAttribute() == feature + && matchingValues((AttributeChange)input, element); + } else if (input instanceof ReferenceChange) { + apply = input.getState() == DifferenceState.UNRESOLVED + && ((ReferenceChange)input).getReference() == feature + && matchingValues((ReferenceChange)input, element); + } else { + apply = false; + } + return apply; + } + + /** + * Checks that the value of the given diff matches <code>value</code>, resorting to the equality + * helper if needed. + * + * @param diff + * The diff which value we need to check. + * @param value + * The expected value of <code>diff</code> + * @return <code>true</code> if the value matches. + */ + private boolean matchingValues(AttributeChange diff, Object value) { + if (diff.getValue() == value) { + return true; + } + // Only resort to the equality helper as "last resort" + final IEqualityHelper helper = diff.getMatch().getComparison().getEqualityHelper(); + return helper.matchingAttributeValues(diff.getValue(), value); + } + + /** + * Checks that the value of the given diff matches <code>value</code>, resorting to the equality + * helper if needed. + * + * @param diff + * The diff which value we need to check. + * @param value + * The expected value of <code>diff</code> + * @return <code>true</code> if the value matches. + */ + private boolean matchingValues(ReferenceChange diff, Object value) { + if (diff.getValue() == value) { + return true; + } + // Only resort to the equality helper as "last resort" + final IEqualityHelper helper = diff.getMatch().getComparison().getEqualityHelper(); + return helper.matchingValues(diff.getValue(), value); + } + } +} |