Skip to main content
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAxel Richard2013-05-21 15:55:05 +0000
committerAxel Richard2013-05-21 15:55:05 +0000
commit85b7a87c67712f20242452353f64c0b562389aa1 (patch)
treefb9f5142c5c3639c3610672e941f95c89b2d580a
parent5f1687db0ac7d46b231ca7e75e44c360e08efdde (diff)
downloadorg.eclipse.emf.compare-axrichard/modelmergeui.tar.gz
org.eclipse.emf.compare-axrichard/modelmergeui.tar.xz
org.eclipse.emf.compare-axrichard/modelmergeui.zip
New model merge UI
-rw-r--r--plugins/org.eclipse.emf.compare.edit/src/org/eclipse/emf/compare/domain/impl/EMFCompareEditingDomain.java16
-rw-r--r--plugins/org.eclipse.emf.compare.edit/src/org/eclipse/emf/compare/provider/spec/OverlayImageProvider.java412
-rw-r--r--plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/EMFCompareStructureMergeViewer.java168
-rw-r--r--plugins/org.eclipse.emf.compare.rcp.ui/icons/full/toolb16/accept_change.gifbin0 -> 76 bytes
-rw-r--r--plugins/org.eclipse.emf.compare.rcp.ui/icons/full/toolb16/reject_change.gifbin0 -> 73 bytes
-rw-r--r--plugins/org.eclipse.emf.compare.rcp.ui/plugin.properties10
-rw-r--r--plugins/org.eclipse.emf.compare.rcp.ui/plugin.xml63
-rw-r--r--plugins/org.eclipse.emf.compare.rcp.ui/src/org/eclipse/emf/compare/rcp/ui/internal/structuremergeviewer/handler/AcceptChange.java35
-rw-r--r--plugins/org.eclipse.emf.compare.rcp.ui/src/org/eclipse/emf/compare/rcp/ui/internal/structuremergeviewer/handler/AcceptRejectChange.java164
-rw-r--r--plugins/org.eclipse.emf.compare.rcp.ui/src/org/eclipse/emf/compare/rcp/ui/internal/structuremergeviewer/handler/AcceptRejectChangePropertyTester.java26
-rw-r--r--plugins/org.eclipse.emf.compare.rcp.ui/src/org/eclipse/emf/compare/rcp/ui/internal/structuremergeviewer/handler/RejectChange.java35
-rw-r--r--plugins/org.eclipse.emf.compare/META-INF/MANIFEST.MF1
-rw-r--r--plugins/org.eclipse.emf.compare/src/org/eclipse/emf/compare/internal/merge/IMergeData.java22
-rw-r--r--plugins/org.eclipse.emf.compare/src/org/eclipse/emf/compare/internal/merge/MergeDataAdapter.java46
-rw-r--r--plugins/org.eclipse.emf.compare/src/org/eclipse/emf/compare/merge/AbstractMerger.java404
-rw-r--r--plugins/org.eclipse.emf.compare/src/org/eclipse/emf/compare/merge/ReferenceChangeMerger.java1434
16 files changed, 1735 insertions, 1101 deletions
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 5ed82f129..439ca85a8 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
@@ -20,7 +20,9 @@ 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.ComparePackage;
import org.eclipse.emf.compare.Diff;
+import org.eclipse.emf.compare.DifferenceState;
import org.eclipse.emf.compare.command.ICompareCommandStack;
import org.eclipse.emf.compare.command.impl.CompareCommandStack;
import org.eclipse.emf.compare.command.impl.CopyAllNonConflictingCommand;
@@ -32,6 +34,7 @@ import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.change.util.ChangeRecorder;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
+import org.eclipse.emf.edit.command.SetCommand;
import org.eclipse.emf.transaction.TransactionalEditingDomain;
/**
@@ -164,6 +167,19 @@ public class EMFCompareEditingDomain implements ICompareEditingDomain {
/**
* {@inheritDoc}
*
+ * @see org.eclipse.emf.compare.domain.ICompareEditingDomain#createChangeStateCommand(org.eclipse.emf.compare.Diff,
+ * boolean)
+ * @since 3.0
+ */
+ public Command createChangeStateCommand(Diff diff, boolean leftToRight) {
+
+ return SetCommand
+ .create(null, diff, ComparePackage.eINSTANCE.getDiff_State(), DifferenceState.MERGED);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
* @see org.eclipse.emf.compare.domain.ICompareEditingDomain#createCopyAllNonConflictingCommand(java.util.List,
* boolean, org.eclipse.emf.compare.merge.IMerger.Registry)
* @since 3.0
diff --git a/plugins/org.eclipse.emf.compare.edit/src/org/eclipse/emf/compare/provider/spec/OverlayImageProvider.java b/plugins/org.eclipse.emf.compare.edit/src/org/eclipse/emf/compare/provider/spec/OverlayImageProvider.java
index 365932911..bdeb757bb 100644
--- a/plugins/org.eclipse.emf.compare.edit/src/org/eclipse/emf/compare/provider/spec/OverlayImageProvider.java
+++ b/plugins/org.eclipse.emf.compare.edit/src/org/eclipse/emf/compare/provider/spec/OverlayImageProvider.java
@@ -1,196 +1,216 @@
-/*******************************************************************************
- * 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.provider.spec;
-
-import static com.google.common.collect.Lists.newArrayList;
-
-import java.util.Collection;
-import java.util.List;
-
-import org.eclipse.emf.common.util.ResourceLocator;
-import org.eclipse.emf.compare.Comparison;
-import org.eclipse.emf.compare.Conflict;
-import org.eclipse.emf.compare.ConflictKind;
-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.Match;
-import org.eclipse.emf.edit.provider.ComposedImage;
-
-/**
- * @author <a href="mailto:mikael.barbero@obeo.fr">Mikael Barbero</a>
- */
-public class OverlayImageProvider {
-
- private final ResourceLocator fResourceLocator;
-
- /**
- *
- */
- public OverlayImageProvider(ResourceLocator resourceLocator) {
- this.fResourceLocator = resourceLocator;
- }
-
- public Object getComposedImage(Diff diff, Object imageToCompose) {
- String overlay = getImageOverlay(diff);
- return getComposedImage(imageToCompose, overlay);
- }
-
- public Object getComposedImage(Match match, Object imageToCompose) {
- String overlay = getImageOverlay(match);
- return getComposedImage(imageToCompose, overlay);
- }
-
- private Object getComposedImage(Object imageToCompose, String overlay) {
- Collection<Object> images = newArrayList();
- images.add(imageToCompose);
- if (overlay != null) {
- Object image = fResourceLocator.getImage(overlay);
- images.add(image);
- }
- return new ComposedImageExtension(images);
- }
-
- // Nothing here has to be externalized
- @SuppressWarnings("nls")
- private String getImageOverlay(Diff diff) {
- final DifferenceSource source = diff.getSource();
- final Match match = diff.getMatch();
- final Conflict conflict = diff.getConflict();
- final DifferenceKind diffKind = diff.getKind();
- final Comparison comparison = match.getComparison();
- String path = "full/ovr16/";
-
- if (diff.getState() == DifferenceState.MERGED) {
- path += "merged_ov";
- } else if (diff.getState() == DifferenceState.DISCARDED) {
- path += "removed_ov";
- } else if (comparison.isThreeWay()) {
- // "png" needs explicit declaration, "gif" does not
- String extension = "";
- if (conflict != null) {
- extension = ".png";
- if (conflict.getKind() == ConflictKind.PSEUDO) {
- path += "p";
- }
- path += "conf";
- if (source == DifferenceSource.RIGHT) {
- path += "r_";
- }
- } else {
- switch (source) {
- case LEFT:
- path += "r_out";
- break;
- case RIGHT:
- path += "r_in";
- break;
- default:
- // Cannot happen ... for now
- break;
- }
- }
-
- switch (diffKind) {
- case ADD:
- path += "add_ov";
- break;
- case DELETE:
- path += "del_ov";
- break;
- case CHANGE:
- // fallthrough
- case MOVE:
- path += "chg_ov";
- break;
- default:
- // Cannot happen ... for now
- break;
- }
- path += extension;
- } else {
- switch (diffKind) {
- case ADD:
- path += "add_ov";
- break;
- case DELETE:
- path += "del_ov";
- break;
- case CHANGE:
- // fallthrough
- case MOVE:
- path += "chg_ov";
- break;
- default:
- break;
- }
- }
- return path;
- }
-
- // Nothing here has to be externalized
- @SuppressWarnings("nls")
- private String getImageOverlay(Match match) {
- return "full/ovr16/match_ov.png";
- }
-
- private final class ComposedImageExtension extends ComposedImage {
-
- /**
- *
- */
- private static final int X_OFFSET = 10;
-
- /**
- * @param images
- */
- ComposedImageExtension(Collection<?> images) {
- super(images);
- }
-
- /**
- * {@inheritDoc}
- *
- * @see org.eclipse.emf.edit.provider.ComposedImage#getDrawPoints(org.eclipse.emf.edit.provider.ComposedImage.Size)
- */
- @Override
- public List<Point> getDrawPoints(Size size) {
- List<ComposedImage.Point> result = super.getDrawPoints(size);
- if (result.size() > 1) {
- result.get(1).x = X_OFFSET;
- result.get(1).y = 2;
- }
- return result;
- }
-
- /**
- * {@inheritDoc}
- *
- * @see org.eclipse.emf.edit.provider.ComposedImage#getSize(java.util.Collection)
- */
- @Override
- public Size getSize(Collection<? extends Size> sizes) {
- this.imageSizes = newArrayList(sizes);
- List<Point> drawPoints = getDrawPoints(null);
-
- Size result = new Size();
- for (int i = 0; i < sizes.size(); i++) {
- Size size = this.imageSizes.get(i);
- Point point = drawPoints.get(i);
-
- result.width = Math.max(result.width, size.width + Math.abs(point.x));
- result.height = Math.max(result.height, size.height + Math.abs(point.y));
- }
- return result;
- }
- }
-}
+/*******************************************************************************
+ * 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.provider.spec;
+
+import static com.google.common.collect.Lists.newArrayList;
+
+import java.util.Collection;
+import java.util.List;
+
+import org.eclipse.emf.common.notify.Adapter;
+import org.eclipse.emf.common.util.ResourceLocator;
+import org.eclipse.emf.compare.Comparison;
+import org.eclipse.emf.compare.Conflict;
+import org.eclipse.emf.compare.ConflictKind;
+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.Match;
+import org.eclipse.emf.compare.internal.merge.IMergeData;
+import org.eclipse.emf.ecore.util.EcoreUtil;
+import org.eclipse.emf.edit.provider.ComposedImage;
+
+/**
+ * @author <a href="mailto:mikael.barbero@obeo.fr">Mikael Barbero</a>
+ */
+public class OverlayImageProvider {
+
+ private final ResourceLocator fResourceLocator;
+
+ /**
+ *
+ */
+ public OverlayImageProvider(ResourceLocator resourceLocator) {
+ this.fResourceLocator = resourceLocator;
+ }
+
+ public Object getComposedImage(Diff diff, Object imageToCompose) {
+ String overlay = getImageOverlay(diff);
+ return getComposedImage(imageToCompose, overlay);
+ }
+
+ public Object getComposedImage(Match match, Object imageToCompose) {
+ String overlay = getImageOverlay(match);
+ return getComposedImage(imageToCompose, overlay);
+ }
+
+ private Object getComposedImage(Object imageToCompose, String overlay) {
+ Collection<Object> images = newArrayList();
+ images.add(imageToCompose);
+ if (overlay != null) {
+ Object image = fResourceLocator.getImage(overlay);
+ images.add(image);
+ }
+ return new ComposedImageExtension(images);
+ }
+
+ // Nothing here has to be externalized
+ @SuppressWarnings("nls")
+ private String getImageOverlay(Diff diff) {
+ final DifferenceSource source = diff.getSource();
+ final Match match = diff.getMatch();
+ final Conflict conflict = diff.getConflict();
+ final DifferenceKind diffKind = diff.getKind();
+ final Comparison comparison = match.getComparison();
+ String path = "full/ovr16/";
+
+ if (diff.getState() == DifferenceState.MERGED) {
+ Adapter adapter = EcoreUtil.getExistingAdapter(diff, IMergeData.class);
+ if (adapter != null) {
+ if (((IMergeData)adapter).hasBeenMergedToLeft()) {
+ if (source == DifferenceSource.LEFT) {
+ path += "removed_ov";
+ } else {
+ path += "merged_ov";
+ }
+ } else {
+ if (source == DifferenceSource.LEFT) {
+ path += "merged_ov";
+ } else {
+ path += "removed_ov";
+ }
+ }
+ } else {
+ path += "merged_ov";
+ }
+ } else if (diff.getState() == DifferenceState.DISCARDED) {
+ path += "removed_ov";
+ } else if (comparison.isThreeWay()) {
+ // "png" needs explicit declaration, "gif" does not
+ String extension = "";
+ if (conflict != null) {
+ extension = ".png";
+ if (conflict.getKind() == ConflictKind.PSEUDO) {
+ path += "p";
+ }
+ path += "conf";
+ if (source == DifferenceSource.RIGHT) {
+ path += "r_";
+ }
+ } else {
+ switch (source) {
+ case LEFT:
+ path += "r_out";
+ break;
+ case RIGHT:
+ path += "r_in";
+ break;
+ default:
+ // Cannot happen ... for now
+ break;
+ }
+ }
+
+ switch (diffKind) {
+ case ADD:
+ path += "add_ov";
+ break;
+ case DELETE:
+ path += "del_ov";
+ break;
+ case CHANGE:
+ // fallthrough
+ case MOVE:
+ path += "chg_ov";
+ break;
+ default:
+ // Cannot happen ... for now
+ break;
+ }
+ path += extension;
+ } else {
+ switch (diffKind) {
+ case ADD:
+ path += "add_ov";
+ break;
+ case DELETE:
+ path += "del_ov";
+ break;
+ case CHANGE:
+ // fallthrough
+ case MOVE:
+ path += "chg_ov";
+ break;
+ default:
+ break;
+ }
+ }
+ return path;
+ }
+
+ // Nothing here has to be externalized
+ @SuppressWarnings("nls")
+ private String getImageOverlay(Match match) {
+ return "full/ovr16/match_ov.png";
+ }
+
+ private final class ComposedImageExtension extends ComposedImage {
+
+ /**
+ *
+ */
+ private static final int X_OFFSET = 10;
+
+ /**
+ * @param images
+ */
+ ComposedImageExtension(Collection<?> images) {
+ super(images);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.eclipse.emf.edit.provider.ComposedImage#getDrawPoints(org.eclipse.emf.edit.provider.ComposedImage.Size)
+ */
+ @Override
+ public List<Point> getDrawPoints(Size size) {
+ List<ComposedImage.Point> result = super.getDrawPoints(size);
+ if (result.size() > 1) {
+ result.get(1).x = X_OFFSET;
+ result.get(1).y = 2;
+ }
+ return result;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.eclipse.emf.edit.provider.ComposedImage#getSize(java.util.Collection)
+ */
+ @Override
+ public Size getSize(Collection<? extends Size> sizes) {
+ this.imageSizes = newArrayList(sizes);
+ List<Point> drawPoints = getDrawPoints(null);
+
+ Size result = new Size();
+ for (int i = 0; i < sizes.size(); i++) {
+ Size size = this.imageSizes.get(i);
+ Point point = drawPoints.get(i);
+
+ result.width = Math.max(result.width, size.width + Math.abs(point.x));
+ result.height = Math.max(result.height, size.height + Math.abs(point.y));
+ }
+ return result;
+ }
+ }
+}
diff --git a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/EMFCompareStructureMergeViewer.java b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/EMFCompareStructureMergeViewer.java
index 9d7d8e63e..d4841b3cd 100644
--- a/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/EMFCompareStructureMergeViewer.java
+++ b/plugins/org.eclipse.emf.compare.ide.ui/src/org/eclipse/emf/compare/ide/ui/internal/structuremergeviewer/EMFCompareStructureMergeViewer.java
@@ -10,8 +10,13 @@
*******************************************************************************/
package org.eclipse.emf.compare.ide.ui.internal.structuremergeviewer;
+import static com.google.common.base.Predicates.and;
+import static com.google.common.base.Predicates.or;
import static com.google.common.collect.Iterables.getFirst;
+import static org.eclipse.emf.compare.utils.EMFComparePredicates.fromSide;
+import static org.eclipse.emf.compare.utils.EMFComparePredicates.ofKind;
+import com.google.common.collect.Lists;
import com.google.common.eventbus.EventBus;
import com.google.common.eventbus.Subscribe;
@@ -25,6 +30,7 @@ import java.util.Comparator;
import java.util.EventObject;
import java.util.HashSet;
import java.util.Iterator;
+import java.util.List;
import org.eclipse.compare.CompareConfiguration;
import org.eclipse.compare.CompareViewerSwitchingPane;
@@ -43,10 +49,13 @@ import org.eclipse.emf.common.command.Command;
import org.eclipse.emf.common.command.CommandStack;
import org.eclipse.emf.common.command.CommandStackListener;
import org.eclipse.emf.common.notify.Adapter;
+import org.eclipse.emf.common.notify.Notifier;
import org.eclipse.emf.common.util.BasicMonitor;
import org.eclipse.emf.compare.Comparison;
import org.eclipse.emf.compare.Conflict;
+import org.eclipse.emf.compare.ConflictKind;
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.Match;
@@ -81,14 +90,26 @@ import org.eclipse.jface.action.IAction;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.action.ToolBarManager;
+import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.viewers.DelegatingStyledCellLabelProvider;
import org.eclipse.jface.viewers.IElementComparer;
+import org.eclipse.jface.viewers.ISelection;
+import org.eclipse.jface.viewers.ISelectionChangedListener;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.ViewerComparator;
import org.eclipse.jface.viewers.ViewerFilter;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.DisposeEvent;
+import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.graphics.GC;
+import org.eclipse.swt.graphics.RGB;
+import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Event;
+import org.eclipse.swt.widgets.Listener;
+import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeItem;
import org.eclipse.team.core.subscribers.Subscriber;
import org.eclipse.team.core.subscribers.SubscriberMergeContext;
@@ -108,6 +129,12 @@ import org.osgi.framework.Version;
*/
public class EMFCompareStructureMergeViewer extends DiffTreeViewer implements CommandStackListener {
+ private static final String REQUIRED_DIFF_COLOR = "RequiredDiffColor";
+
+ private static final String UNMERGEABLE_DIFF_COLOR = "UnmergeableDiffColor";
+
+ private static final String OPTIONAL_DIFF_COLOR = "OptionalDiffColor";
+
private final ICompareInputChangeListener fCompareInputChangeListener;
private final ComposedAdapterFactory fAdapterFactory;
@@ -142,6 +169,8 @@ public class EMFCompareStructureMergeViewer extends DiffTreeViewer implements Co
private EventBus eventBus;
+ private Listener fEraseItemListener;
+
/**
* @param parent
* @param configuration
@@ -172,6 +201,19 @@ public class EMFCompareStructureMergeViewer extends DiffTreeViewer implements Co
}
};
+ addSelectionChangedListener(new ISelectionChangedListener() {
+ public void selectionChanged(SelectionChangedEvent event) {
+ getControl().redraw();
+ }
+ });
+
+ fEraseItemListener = new Listener() {
+ public void handleEvent(Event event) {
+ EMFCompareStructureMergeViewer.this.handleEraseItemEvent(event);
+ }
+ };
+ getControl().addListener(SWT.EraseItem, fEraseItemListener);
+
// Wrap the defined comparer in our own.
setComparer(new DiffNodeComparer(super.getComparer()));
@@ -180,6 +222,10 @@ public class EMFCompareStructureMergeViewer extends DiffTreeViewer implements Co
eventBus.register(this);
}
+ JFaceResources.getColorRegistry().put(REQUIRED_DIFF_COLOR, new RGB(215, 255, 200));
+ JFaceResources.getColorRegistry().put(UNMERGEABLE_DIFF_COLOR, new RGB(255, 254, 200));
+ JFaceResources.getColorRegistry().put(OPTIONAL_DIFF_COLOR, new RGB(193, 210, 230));
+
inputChangedTask.setPriority(Job.LONG);
}
@@ -726,6 +772,7 @@ public class EMFCompareStructureMergeViewer extends DiffTreeViewer implements Co
ci.removeCompareInputChangeListener(fCompareInputChangeListener);
}
compareInputChanged((ICompareInput)null);
+ getControl().removeListener(SWT.EraseItem, fEraseItemListener);
fAdapterFactory.dispose();
super.handleDispose(event);
@@ -794,4 +841,125 @@ public class EMFCompareStructureMergeViewer extends DiffTreeViewer implements Co
}
return result;
}
+
+ /**
+ * Handle the erase item event. When select a difference in the structure merge viewer, highlight required
+ * differences with a specific color, and highlight unmergeable differences with another color.
+ *
+ * @param event
+ * the erase item event.
+ */
+ protected void handleEraseItemEvent(Event event) {
+ ISelection selection = getSelection();
+ Object firstElement = ((IStructuredSelection)selection).getFirstElement();
+ if (firstElement instanceof Adapter) {
+ Notifier target = ((Adapter)firstElement).getTarget();
+ if (target instanceof Diff) {
+ TreeItem item = (TreeItem)event.item;
+ Object dataTreeItem = item.getData();
+ if (dataTreeItem instanceof Adapter) {
+ final List<Diff> requires = getRequires((Diff)target);
+ final List<Diff> unmergeables = getUnmergeables((Diff)target);
+ final List<Diff> optionals = getOptionals(((Diff)target));
+ final GC g = event.gc;
+ if (requires.contains(((Adapter)dataTreeItem).getTarget())) {
+ paintItemBackground(g, item, JFaceResources.getColorRegistry().get(
+ REQUIRED_DIFF_COLOR));
+ } else if (unmergeables.contains(((Adapter)dataTreeItem).getTarget())) {
+ paintItemBackground(g, item, JFaceResources.getColorRegistry().get(
+ UNMERGEABLE_DIFF_COLOR));
+ } else if (optionals.contains(((Adapter)dataTreeItem).getTarget())) {
+ paintItemBackground(g, item, JFaceResources.getColorRegistry().get(
+ OPTIONAL_DIFF_COLOR));
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Paint the background of the given item with the given color.
+ *
+ * @param g
+ * the GC associated to the item.
+ * @param item
+ * the given item.
+ * @param color
+ * the given color.
+ */
+ private void paintItemBackground(GC g, TreeItem item, Color color) {
+ Rectangle itemBounds = item.getBounds();
+ Tree tree = item.getParent();
+ Rectangle areaBounds = tree.getClientArea();
+ g.setClipping(areaBounds.x, itemBounds.y, areaBounds.width, itemBounds.height);
+ g.setBackground(color);
+ g.fillRectangle(areaBounds.x, itemBounds.y, areaBounds.width, itemBounds.height);
+ }
+
+ /**
+ * Get the list of all required differences for merge of the given difference (required, required of
+ * required...).
+ *
+ * @param diff
+ * the given difference.
+ * @return the list of all required differences.
+ */
+ private List<Diff> getRequires(Diff diff) {
+ List<Diff> requires = Lists.newArrayList();
+ for (Diff require : diff.getRequires()) {
+ requires.add(require);
+ requires.addAll(getRequires(require));
+ }
+ return requires;
+ }
+
+ /**
+ * Get the list of all optional differences for merge of the given difference (optional, optional of
+ * optional...).
+ *
+ * @param diff
+ * the given difference.
+ * @return the list of all optional differences.
+ */
+ private List<Diff> getOptionals(Diff diff) {
+ List<Diff> optionals = Lists.newArrayList();
+ for (Diff optional : diff.getRequiredBy()) {
+ optionals.add(optional);
+ optionals.addAll(getOptionals(optional));
+ }
+ return optionals;
+ }
+
+ /**
+ * Get the list of unmergeable differences after the merge of the given difference.
+ *
+ * @param diff
+ * the given difference.
+ * @return the list of unmergeable differences.
+ */
+ private List<Diff> getUnmergeables(Diff diff) {
+ List<Diff> unmergeables = Lists.newArrayList();
+ Conflict conflict = diff.getConflict();
+ if (conflict != null && conflict.getKind() == ConflictKind.REAL) {
+ for (Diff diffConflict : conflict.getDifferences()) {
+ if (and(fromSide(DifferenceSource.LEFT),
+ or(ofKind(DifferenceKind.ADD), ofKind(DifferenceKind.CHANGE))).apply(diff)) {
+ if (and(fromSide(DifferenceSource.RIGHT),
+ or(ofKind(DifferenceKind.DELETE), ofKind(DifferenceKind.CHANGE))).apply(
+ diffConflict)) {
+ unmergeables.add(diffConflict);
+ }
+ } else if (and(fromSide(DifferenceSource.LEFT),
+ or(ofKind(DifferenceKind.DELETE), ofKind(DifferenceKind.CHANGE))).apply(diff)) {
+ if (and(fromSide(DifferenceSource.RIGHT),
+ or(ofKind(DifferenceKind.ADD), ofKind(DifferenceKind.CHANGE)))
+ .apply(diffConflict)) {
+ unmergeables.add(diffConflict);
+ }
+ }
+ }
+ }
+ return unmergeables;
+ }
+
}
diff --git a/plugins/org.eclipse.emf.compare.rcp.ui/icons/full/toolb16/accept_change.gif b/plugins/org.eclipse.emf.compare.rcp.ui/icons/full/toolb16/accept_change.gif
new file mode 100644
index 000000000..23c97f09e
--- /dev/null
+++ b/plugins/org.eclipse.emf.compare.rcp.ui/icons/full/toolb16/accept_change.gif
Binary files differ
diff --git a/plugins/org.eclipse.emf.compare.rcp.ui/icons/full/toolb16/reject_change.gif b/plugins/org.eclipse.emf.compare.rcp.ui/icons/full/toolb16/reject_change.gif
new file mode 100644
index 000000000..1aca259db
--- /dev/null
+++ b/plugins/org.eclipse.emf.compare.rcp.ui/icons/full/toolb16/reject_change.gif
Binary files differ
diff --git a/plugins/org.eclipse.emf.compare.rcp.ui/plugin.properties b/plugins/org.eclipse.emf.compare.rcp.ui/plugin.properties
index f29dc9cca..8dbc9d9dc 100644
--- a/plugins/org.eclipse.emf.compare.rcp.ui/plugin.properties
+++ b/plugins/org.eclipse.emf.compare.rcp.ui/plugin.properties
@@ -13,4 +13,12 @@ providerName = Eclipse Modeling Project
save.model.label = Save Comparison Model
save.model.tooltip = Save Comparison Model
-save.model.command.name = EMF Compare Save Comparison Model \ No newline at end of file
+save.model.command.name = EMF Compare Save Comparison Model
+
+accept.change = Accept Change
+accept.change.tooltip = Accept Change
+accept.change.command.name = EMF Compare Accept Change
+
+reject.change = Reject Change
+reject.change.tooltip = Reject Change
+reject.change.command.name = EMF Compare Reject Change \ No newline at end of file
diff --git a/plugins/org.eclipse.emf.compare.rcp.ui/plugin.xml b/plugins/org.eclipse.emf.compare.rcp.ui/plugin.xml
index 015ea8072..f56288a62 100644
--- a/plugins/org.eclipse.emf.compare.rcp.ui/plugin.xml
+++ b/plugins/org.eclipse.emf.compare.rcp.ui/plugin.xml
@@ -89,6 +89,30 @@
allPopups="false"
locationURI="toolbar:org.eclipse.emf.compare.structuremergeviewer.toolbar">
<command
+ commandId="org.eclipse.emf.compare.rcp.ui.acceptChange"
+ icon="icons/full/toolb16/accept_change.gif"
+ label="%accept.change"
+ style="push"
+ tooltip="%accept.change.tooltip">
+ <visibleWhen
+ checkEnabled="true">
+ </visibleWhen>
+ </command>
+ <command
+ commandId="org.eclipse.emf.compare.rcp.ui.rejectChange"
+ icon="icons/full/toolb16/reject_change.gif"
+ label="%reject.change"
+ style="push"
+ tooltip="%reject.change.tooltip">
+ <visibleWhen
+ checkEnabled="true">
+ </visibleWhen>
+ </command>
+ <separator
+ name="org.eclipse.emf.compare.rcp.ui.separatorSaveModel"
+ visible="true">
+ </separator>
+ <command
commandId="org.eclipse.emf.compare.rcp.ui.saveComparisonModel"
icon="icons/full/toolb16/saveas_edit.gif"
label="%save.model.name"
@@ -100,6 +124,14 @@
<extension
point="org.eclipse.ui.commands">
<command
+ id="org.eclipse.emf.compare.rcp.ui.acceptChange"
+ name="%accept.change.command.name">
+ </command>
+ <command
+ id="org.eclipse.emf.compare.rcp.ui.rejectChange"
+ name="%reject.change.command.name">
+ </command>
+ <command
id="org.eclipse.emf.compare.rcp.ui.saveComparisonModel"
name="%save.model.command.name">
</command>
@@ -107,6 +139,30 @@
<extension
point="org.eclipse.ui.handlers">
<handler
+ class="org.eclipse.emf.compare.rcp.ui.internal.structuremergeviewer.handler.AcceptChange"
+ commandId="org.eclipse.emf.compare.rcp.ui.acceptChange">
+ <activeWhen>
+ <with
+ variable="activeEditor">
+ <test
+ property="emfcompare.hasReadOnlySide">
+ </test>
+ </with>
+ </activeWhen>
+ </handler>
+ <handler
+ class="org.eclipse.emf.compare.rcp.ui.internal.structuremergeviewer.handler.RejectChange"
+ commandId="org.eclipse.emf.compare.rcp.ui.rejectChange">
+ <activeWhen>
+ <with
+ variable="activeEditor">
+ <test
+ property="emfcompare.hasReadOnlySide">
+ </test>
+ </with>
+ </activeWhen>
+ </handler>
+ <handler
class="org.eclipse.emf.compare.rcp.ui.internal.structuremergeviewer.handler.SaveComparisonModel"
commandId="org.eclipse.emf.compare.rcp.ui.saveComparisonModel">
<enabledWhen>
@@ -129,5 +185,12 @@
properties="isSaveable"
type="java.lang.Object">
</propertyTester>
+ <propertyTester
+ class="org.eclipse.emf.compare.rcp.ui.internal.structuremergeviewer.handler.AcceptRejectChangePropertyTester"
+ id="org.eclipse.emf.compare.rcp.ui.hasReadOnlySide"
+ namespace="emfcompare"
+ properties="hasReadOnlySide"
+ type="java.lang.Object">
+ </propertyTester>
</extension>
</plugin>
diff --git a/plugins/org.eclipse.emf.compare.rcp.ui/src/org/eclipse/emf/compare/rcp/ui/internal/structuremergeviewer/handler/AcceptChange.java b/plugins/org.eclipse.emf.compare.rcp.ui/src/org/eclipse/emf/compare/rcp/ui/internal/structuremergeviewer/handler/AcceptChange.java
new file mode 100644
index 000000000..ea87eaa89
--- /dev/null
+++ b/plugins/org.eclipse.emf.compare.rcp.ui/src/org/eclipse/emf/compare/rcp/ui/internal/structuremergeviewer/handler/AcceptChange.java
@@ -0,0 +1,35 @@
+/*******************************************************************************
+ * Copyright (c) 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.rcp.ui.internal.structuremergeviewer.handler;
+
+import static org.eclipse.emf.compare.utils.EMFComparePredicates.fromSide;
+
+import org.eclipse.emf.compare.Diff;
+import org.eclipse.emf.compare.DifferenceSource;
+
+/**
+ * Handler that manages a merge of a difference in case of one side of the comparison is in read-only mode.
+ *
+ * @author <a href="mailto:axel.richard@obeo.fr">Axel Richard</a>
+ * @since 3.0
+ */
+public class AcceptChange extends AcceptRejectChange {
+
+ @Override
+ protected boolean isCopyDiffCase(Diff diff, boolean leftToRight) {
+ if (leftToRight) {
+ return fromSide(DifferenceSource.LEFT).apply(diff);
+ } else {
+ return fromSide(DifferenceSource.RIGHT).apply(diff);
+ }
+ }
+
+}
diff --git a/plugins/org.eclipse.emf.compare.rcp.ui/src/org/eclipse/emf/compare/rcp/ui/internal/structuremergeviewer/handler/AcceptRejectChange.java b/plugins/org.eclipse.emf.compare.rcp.ui/src/org/eclipse/emf/compare/rcp/ui/internal/structuremergeviewer/handler/AcceptRejectChange.java
new file mode 100644
index 000000000..cea793d0b
--- /dev/null
+++ b/plugins/org.eclipse.emf.compare.rcp.ui/src/org/eclipse/emf/compare/rcp/ui/internal/structuremergeviewer/handler/AcceptRejectChange.java
@@ -0,0 +1,164 @@
+package org.eclipse.emf.compare.rcp.ui.internal.structuremergeviewer.handler;
+
+import static com.google.common.collect.Iterables.addAll;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import org.eclipse.compare.CompareConfiguration;
+import org.eclipse.compare.CompareEditorInput;
+import org.eclipse.compare.CompareNavigator;
+import org.eclipse.compare.ICompareNavigator;
+import org.eclipse.core.commands.AbstractHandler;
+import org.eclipse.core.commands.ExecutionEvent;
+import org.eclipse.core.commands.ExecutionException;
+import org.eclipse.emf.common.command.Command;
+import org.eclipse.emf.common.notify.Adapter;
+import org.eclipse.emf.common.notify.Notifier;
+import org.eclipse.emf.compare.Diff;
+import org.eclipse.emf.compare.DifferenceState;
+import org.eclipse.emf.compare.command.ICompareCopyCommand;
+import org.eclipse.emf.compare.domain.ICompareEditingDomain;
+import org.eclipse.emf.compare.internal.merge.IMergeData;
+import org.eclipse.emf.compare.internal.merge.MergeDataAdapter;
+import org.eclipse.emf.compare.rcp.EMFCompareRCPPlugin;
+import org.eclipse.emf.compare.rcp.ui.internal.EMFCompareConstants;
+import org.eclipse.emf.compare.rcp.ui.internal.structuremergeviewer.filters.IDifferenceFilter;
+import org.eclipse.emf.compare.rcp.ui.internal.structuremergeviewer.filters.impl.CascadingDifferencesFilter;
+import org.eclipse.emf.compare.utils.DiffUtil;
+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.ui.ISources;
+import org.eclipse.ui.handlers.HandlerUtil;
+
+public abstract class AcceptRejectChange extends AbstractHandler {
+
+ /** The compare configuration object used to get the compare model. */
+ private CompareConfiguration configuration;
+
+ public Object execute(ExecutionEvent event) throws ExecutionException {
+ Object editorInput = HandlerUtil.getVariable(event, ISources.ACTIVE_EDITOR_INPUT_NAME);
+ if (editorInput instanceof CompareEditorInput) {
+ configuration = ((CompareEditorInput)editorInput).getCompareConfiguration();
+ Object diffNode = ((CompareEditorInput)editorInput).getSelectedEdition();
+ if (diffNode instanceof Adapter) {
+ Notifier diff = ((Adapter)diffNode).getTarget();
+ if (diff instanceof Diff) {
+ boolean leftReadOnly = !configuration.isLeftEditable() && configuration.isRightEditable();
+ boolean rightReadOnly = configuration.isLeftEditable()
+ && !configuration.isRightEditable();
+ if (rightReadOnly) {
+ if (isCopyDiffCase((Diff)diff, leftReadOnly)) {
+ copyDiff((Diff)diff, leftReadOnly);
+ } else {
+ changeState((Diff)diff, rightReadOnly);
+ }
+ // Select next diff
+ navigate(true);
+ }
+ }
+ }
+ }
+ return null;
+ }
+
+ protected abstract boolean isCopyDiffCase(Diff diff, boolean leftToRight);
+
+ private void changeState(Diff diffToChangeState, boolean leftToRight) {
+ if (diffToChangeState != null) {
+ ICompareEditingDomain compareEditingDomain = (ICompareEditingDomain)configuration
+ .getProperty(EMFCompareConstants.EDITING_DOMAIN);
+ Command changeStateCommand = new AcceptRejectChangeCommand(compareEditingDomain
+ .getChangeRecorder(), diffToChangeState, leftToRight);
+ compareEditingDomain.getCommandStack().execute(changeStateCommand);
+ }
+ }
+
+ private void copyDiff(Diff diffToCopy, boolean leftToRight) {
+ if (diffToCopy != null) {
+ List<Diff> diffsToCopy = new ArrayList<Diff>();
+ diffsToCopy.add(diffToCopy);
+ if (isSubDiffFilterActive()) {
+ addAll(diffsToCopy, DiffUtil.getSubDiffs(leftToRight).apply(diffToCopy));
+ }
+ ICompareEditingDomain editingDomain = (ICompareEditingDomain)configuration
+ .getProperty(EMFCompareConstants.EDITING_DOMAIN);
+ Command copyCommand = editingDomain.createCopyCommand(diffsToCopy, leftToRight,
+ EMFCompareRCPPlugin.getDefault().getMergerRegistry());
+
+ editingDomain.getCommandStack().execute(copyCommand);
+ // refresh();
+ }
+ }
+
+ protected boolean isSubDiffFilterActive() {
+ Object property = configuration.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 CascadingDifferencesFilter) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * 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 = configuration.getContainer().getNavigator();
+ if (navigator instanceof CompareNavigator && ((CompareNavigator)navigator).hasChange(next)) {
+ navigator.selectChange(next);
+ }
+ // }
+ }
+
+ public class AcceptRejectChangeCommand extends ChangeCommand implements ICompareCopyCommand {
+
+ private Diff difference;
+
+ private boolean leftToRight;
+
+ public AcceptRejectChangeCommand(ChangeRecorder changeRecorder, Diff difference, boolean leftToRight) {
+ super(changeRecorder, difference);
+ this.difference = difference;
+ this.leftToRight = leftToRight;
+ }
+
+ @Override
+ public void doExecute() {
+ Adapter adapter = EcoreUtil.getExistingAdapter(difference, IMergeData.class);
+ if (adapter != null) {
+ if (leftToRight) {
+ ((IMergeData)adapter).setMergedToRight();
+ } else {
+ ((IMergeData)adapter).setMergedToLeft();
+ }
+ } else {
+ difference.eAdapters().add(new MergeDataAdapter(leftToRight));
+ }
+ difference.setState(DifferenceState.MERGED);
+
+ }
+
+ public boolean isLeftToRight() {
+ return leftToRight;
+ }
+
+ }
+
+}
diff --git a/plugins/org.eclipse.emf.compare.rcp.ui/src/org/eclipse/emf/compare/rcp/ui/internal/structuremergeviewer/handler/AcceptRejectChangePropertyTester.java b/plugins/org.eclipse.emf.compare.rcp.ui/src/org/eclipse/emf/compare/rcp/ui/internal/structuremergeviewer/handler/AcceptRejectChangePropertyTester.java
new file mode 100644
index 000000000..ef704943f
--- /dev/null
+++ b/plugins/org.eclipse.emf.compare.rcp.ui/src/org/eclipse/emf/compare/rcp/ui/internal/structuremergeviewer/handler/AcceptRejectChangePropertyTester.java
@@ -0,0 +1,26 @@
+package org.eclipse.emf.compare.rcp.ui.internal.structuremergeviewer.handler;
+
+import org.eclipse.compare.CompareConfiguration;
+import org.eclipse.compare.CompareEditorInput;
+import org.eclipse.core.expressions.PropertyTester;
+import org.eclipse.ui.IEditorInput;
+import org.eclipse.ui.IEditorPart;
+
+public class AcceptRejectChangePropertyTester extends PropertyTester {
+
+ public boolean test(Object receiver, String property, Object[] args, Object expectedValue) {
+ if (receiver instanceof IEditorPart) {
+ IEditorInput i = ((IEditorPart)receiver).getEditorInput();
+ if (i instanceof CompareEditorInput) {
+ CompareConfiguration configuration = ((CompareEditorInput)i).getCompareConfiguration();
+ if (configuration.isLeftEditable() && !configuration.isRightEditable()) {
+ return true;
+ } else if (!configuration.isLeftEditable() && configuration.isRightEditable()) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+}
diff --git a/plugins/org.eclipse.emf.compare.rcp.ui/src/org/eclipse/emf/compare/rcp/ui/internal/structuremergeviewer/handler/RejectChange.java b/plugins/org.eclipse.emf.compare.rcp.ui/src/org/eclipse/emf/compare/rcp/ui/internal/structuremergeviewer/handler/RejectChange.java
new file mode 100644
index 000000000..0d0380f37
--- /dev/null
+++ b/plugins/org.eclipse.emf.compare.rcp.ui/src/org/eclipse/emf/compare/rcp/ui/internal/structuremergeviewer/handler/RejectChange.java
@@ -0,0 +1,35 @@
+/*******************************************************************************
+ * Copyright (c) 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.rcp.ui.internal.structuremergeviewer.handler;
+
+import static org.eclipse.emf.compare.utils.EMFComparePredicates.fromSide;
+
+import org.eclipse.emf.compare.Diff;
+import org.eclipse.emf.compare.DifferenceSource;
+
+/**
+ * Handler that manages a reject of a difference in case of one side of the comparison is in read-only mode.
+ *
+ * @author <a href="mailto:axel.richard@obeo.fr">Axel Richard</a>
+ * @since 3.0
+ */
+public class RejectChange extends AcceptRejectChange {
+
+ @Override
+ protected boolean isCopyDiffCase(Diff diff, boolean leftToRight) {
+ if (leftToRight) {
+ return fromSide(DifferenceSource.RIGHT).apply(diff);
+ } else {
+ return fromSide(DifferenceSource.LEFT).apply(diff);
+ }
+ }
+
+}
diff --git a/plugins/org.eclipse.emf.compare/META-INF/MANIFEST.MF b/plugins/org.eclipse.emf.compare/META-INF/MANIFEST.MF
index 939f2f939..de8d94943 100644
--- a/plugins/org.eclipse.emf.compare/META-INF/MANIFEST.MF
+++ b/plugins/org.eclipse.emf.compare/META-INF/MANIFEST.MF
@@ -13,6 +13,7 @@ Export-Package: org.eclipse.emf.compare,
org.eclipse.emf.compare.equi,
org.eclipse.emf.compare.impl,
org.eclipse.emf.compare.internal;x-friends:="org.eclipse.emf.compare.logical,org.eclipse.emf.compare.ide",
+ org.eclipse.emf.compare.internal.merge;x-friends:="org.eclipse.emf.compare.rcp.ui,org.eclipse.emf.compare.edit",
org.eclipse.emf.compare.internal.postprocessor.factories;x-internal:=true,
org.eclipse.emf.compare.internal.spec;x-friends:="org.eclipse.emf.compare.tests",
org.eclipse.emf.compare.match,
diff --git a/plugins/org.eclipse.emf.compare/src/org/eclipse/emf/compare/internal/merge/IMergeData.java b/plugins/org.eclipse.emf.compare/src/org/eclipse/emf/compare/internal/merge/IMergeData.java
new file mode 100644
index 000000000..49f9f786d
--- /dev/null
+++ b/plugins/org.eclipse.emf.compare/src/org/eclipse/emf/compare/internal/merge/IMergeData.java
@@ -0,0 +1,22 @@
+/*******************************************************************************
+ * Copyright (c) 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.internal.merge;
+
+public interface IMergeData {
+
+ boolean hasBeenMergedToLeft();
+
+ boolean hasBeenMergedToRight();
+
+ void setMergedToLeft();
+
+ void setMergedToRight();
+}
diff --git a/plugins/org.eclipse.emf.compare/src/org/eclipse/emf/compare/internal/merge/MergeDataAdapter.java b/plugins/org.eclipse.emf.compare/src/org/eclipse/emf/compare/internal/merge/MergeDataAdapter.java
new file mode 100644
index 000000000..ee891d58e
--- /dev/null
+++ b/plugins/org.eclipse.emf.compare/src/org/eclipse/emf/compare/internal/merge/MergeDataAdapter.java
@@ -0,0 +1,46 @@
+/*******************************************************************************
+ * Copyright (c) 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.internal.merge;
+
+import org.eclipse.emf.common.notify.impl.AdapterImpl;
+
+/**
+ * @since 3.0
+ */
+public class MergeDataAdapter extends AdapterImpl implements IMergeData {
+
+ boolean leftToRight;
+
+ public MergeDataAdapter(boolean leftToRight) {
+ this.leftToRight = leftToRight;
+ }
+
+ public boolean hasBeenMergedToLeft() {
+ return leftToRight == false;
+ }
+
+ public boolean hasBeenMergedToRight() {
+ return leftToRight == true;
+ }
+
+ public void setMergedToLeft() {
+ leftToRight = false;
+ }
+
+ public void setMergedToRight() {
+ leftToRight = true;
+ }
+
+ @Override
+ public boolean isAdapterForType(Object type) {
+ return type == IMergeData.class;
+ }
+}
diff --git a/plugins/org.eclipse.emf.compare/src/org/eclipse/emf/compare/merge/AbstractMerger.java b/plugins/org.eclipse.emf.compare/src/org/eclipse/emf/compare/merge/AbstractMerger.java
index 8d852314c..a89bd103e 100644
--- a/plugins/org.eclipse.emf.compare/src/org/eclipse/emf/compare/merge/AbstractMerger.java
+++ b/plugins/org.eclipse.emf.compare/src/org/eclipse/emf/compare/merge/AbstractMerger.java
@@ -1,190 +1,214 @@
-/*******************************************************************************
- * 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.merge;
-
-import java.util.List;
-
-import org.eclipse.emf.common.util.Monitor;
-import org.eclipse.emf.compare.Diff;
-import org.eclipse.emf.compare.utils.EMFCompareCopier;
-import org.eclipse.emf.ecore.EObject;
-import org.eclipse.emf.ecore.util.EcoreUtil;
-import org.eclipse.emf.ecore.util.InternalEList;
-
-/**
- * Abstract implementation of an {@link IMerger}. This can be used as a base implementation to avoid
- * re-implementing the whole contract.
- *
- * @author <a href="mailto:laurent.goubet@obeo.fr">Laurent Goubet</a>
- * @since 3.0
- */
-public abstract class AbstractMerger implements IMerger {
- /** Ranking of this merger. */
- private int ranking;
-
- /** Registry from which this merger has been created.. */
- private Registry registry;
-
- /**
- * {@inheritDoc}
- *
- * @see org.eclipse.emf.compare.merge.IMerger#getRanking()
- */
- public int getRanking() {
- return ranking;
- }
-
- /**
- * {@inheritDoc}
- *
- * @see org.eclipse.emf.compare.merge.IMerger#setRanking(int)
- */
- public void setRanking(int r) {
- ranking = r;
- }
-
- /**
- * {@inheritDoc}
- *
- * @see org.eclipse.emf.compare.merge.IMerger#getRegistry()
- */
- public Registry getRegistry() {
- return registry;
- }
-
- /**
- * {@inheritDoc}
- *
- * @see org.eclipse.emf.compare.merge.IMerger#setRegistry(org.eclipse.emf.compare.merge.IMerger.Registry)
- */
- public void setRegistry(Registry registry) {
- if (this.registry != null && registry != null) {
- throw new IllegalStateException("The registry has to be set only once."); //$NON-NLS-1$
- }
- this.registry = registry;
- }
-
- /**
- * This will merge all {@link Diff#getRequiredBy() differences that require} {@code diff} in the given
- * direction.
- *
- * @param diff
- * We need to merge all differences that require this one (see {@link Diff#getRequiredBy()}.
- * @param rightToLeft
- * If {@code true}, {@link #copyRightToLeft(Diff, Monitor) apply} all differences that require
- * {@code diff}. Otherwise, {@link #copyLeftToRight(Diff, Monitor) revert} them.
- * @param monitor
- * The monitor we should use to report progress.
- */
- protected void mergeRequiredBy(Diff diff, boolean rightToLeft, Monitor monitor) {
- // TODO log back to the user what we will merge along?
- for (Diff dependency : diff.getRequiredBy()) {
- // TODO: what to do when state = Discarded but is required?
- mergeDiff(dependency, rightToLeft, monitor);
- }
- }
-
- /**
- * This will merge all {@link Diff#getRequires() differences required by} {@code diff} in the given
- * direction.
- *
- * @param diff
- * The difference which requirements we need to merge.
- * @param rightToLeft
- * If {@code true}, {@link #copyRightToLeft(Diff, Monitor) apply} all required differences.
- * Otherwise, {@link #copyLeftToRight(Diff, Monitor) revert} them.
- * @param monitor
- * The monitor we should use to report progress.
- */
- protected void mergeRequires(Diff diff, boolean rightToLeft, Monitor monitor) {
- // TODO log back to the user what we will merge along?
- for (Diff dependency : diff.getRequires()) {
- // TODO: what to do when state = Discarded but is required?
- mergeDiff(dependency, rightToLeft, monitor);
- }
- }
-
- /**
- * This can be used by mergers to merge another (required, equivalent...) difference using the right
- * merger for that diff.
- *
- * @param diff
- * The diff we need to merge.
- * @param rightToLeft
- * Direction of that merge.
- * @param monitor
- * The monitor we should use to report progress.
- */
- protected void mergeDiff(Diff diff, boolean rightToLeft, Monitor monitor) {
- if (rightToLeft) {
- final IMerger delegate = getRegistry().getHighestRankingMerger(diff);
- delegate.copyRightToLeft(diff, monitor);
- } else {
- final IMerger delegate = getRegistry().getHighestRankingMerger(diff);
- delegate.copyLeftToRight(diff, monitor);
- }
- }
-
- /**
- * This will create a copy of the given EObject that can be used as the target of an addition (or the
- * reverting of a deletion).
- * <p>
- * The target will be self-contained and will have no reference towards any other EObject set (neither
- * containment nor "classic" references). All of its attributes' values will match the given
- * {@code referenceObject}'s.
- * </p>
- *
- * @param referenceObject
- * The EObject for which we'll create a copy.
- * @return A self-contained copy of {@code referenceObject}.
- * @see EMFCompareCopier#copy(EObject)
- */
- protected EObject createCopy(EObject referenceObject) {
- /*
- * We can't simply use EcoreUtil.copy. References will have their own diffs and will thus be merged
- * later on.
- */
- final EcoreUtil.Copier copier = new EMFCompareCopier();
- return copier.copy(referenceObject);
- }
-
- /**
- * Adds the given {@code value} into the given {@code list} at the given {@code index}. An {@code index}
- * under than zero or above the list's size will mean that the value should be appended at the end of the
- * list.
- *
- * @param list
- * The list into which {@code value} should be added.
- * @param value
- * The value we need to add to {@code list}.
- * @param <E>
- * Type of objects contained in the list.
- * @param insertionIndex
- * The index at which {@code value} should be inserted into {@code list}. {@code -1} if it
- * should be appended at the end of the list.
- */
- @SuppressWarnings("unchecked")
- protected <E> void addAt(List<E> list, E value, int insertionIndex) {
- if (list instanceof InternalEList<?>) {
- if (insertionIndex < 0 || insertionIndex > list.size()) {
- ((InternalEList<Object>)list).addUnique(value);
- } else {
- ((InternalEList<Object>)list).addUnique(insertionIndex, value);
- }
- } else {
- if (insertionIndex < 0 || insertionIndex > list.size()) {
- list.add(value);
- } else {
- list.add(insertionIndex, 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.merge;
+
+import java.util.List;
+
+import org.eclipse.emf.common.notify.Adapter;
+import org.eclipse.emf.common.util.Monitor;
+import org.eclipse.emf.compare.Diff;
+import org.eclipse.emf.compare.internal.merge.IMergeData;
+import org.eclipse.emf.compare.internal.merge.MergeDataAdapter;
+import org.eclipse.emf.compare.utils.EMFCompareCopier;
+import org.eclipse.emf.ecore.EObject;
+import org.eclipse.emf.ecore.util.EcoreUtil;
+import org.eclipse.emf.ecore.util.InternalEList;
+
+/**
+ * Abstract implementation of an {@link IMerger}. This can be used as a base implementation to avoid
+ * re-implementing the whole contract.
+ *
+ * @author <a href="mailto:laurent.goubet@obeo.fr">Laurent Goubet</a>
+ * @since 3.0
+ */
+public abstract class AbstractMerger implements IMerger {
+ /** Ranking of this merger. */
+ private int ranking;
+
+ /** Registry from which this merger has been created.. */
+ private Registry registry;
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.eclipse.emf.compare.merge.IMerger#getRanking()
+ */
+ public int getRanking() {
+ return ranking;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.eclipse.emf.compare.merge.IMerger#setRanking(int)
+ */
+ public void setRanking(int r) {
+ ranking = r;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.eclipse.emf.compare.merge.IMerger#getRegistry()
+ */
+ public Registry getRegistry() {
+ return registry;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.eclipse.emf.compare.merge.IMerger#setRegistry(org.eclipse.emf.compare.merge.IMerger.Registry)
+ */
+ public void setRegistry(Registry registry) {
+ if (this.registry != null && registry != null) {
+ throw new IllegalStateException("The registry has to be set only once."); //$NON-NLS-1$
+ }
+ this.registry = registry;
+ }
+
+ /**
+ * This will merge all {@link Diff#getRequiredBy() differences that require} {@code diff} in the given
+ * direction.
+ *
+ * @param diff
+ * We need to merge all differences that require this one (see {@link Diff#getRequiredBy()}.
+ * @param rightToLeft
+ * If {@code true}, {@link #copyRightToLeft(Diff, Monitor) apply} all differences that require
+ * {@code diff}. Otherwise, {@link #copyLeftToRight(Diff, Monitor) revert} them.
+ * @param monitor
+ * The monitor we should use to report progress.
+ */
+ protected void mergeRequiredBy(Diff diff, boolean rightToLeft, Monitor monitor) {
+ // TODO log back to the user what we will merge along?
+ for (Diff dependency : diff.getRequiredBy()) {
+ // TODO: what to do when state = Discarded but is required?
+ mergeDiff(dependency, rightToLeft, monitor);
+ }
+ }
+
+ /**
+ * This will merge all {@link Diff#getRequires() differences required by} {@code diff} in the given
+ * direction.
+ *
+ * @param diff
+ * The difference which requirements we need to merge.
+ * @param rightToLeft
+ * If {@code true}, {@link #copyRightToLeft(Diff, Monitor) apply} all required differences.
+ * Otherwise, {@link #copyLeftToRight(Diff, Monitor) revert} them.
+ * @param monitor
+ * The monitor we should use to report progress.
+ */
+ protected void mergeRequires(Diff diff, boolean rightToLeft, Monitor monitor) {
+ // TODO log back to the user what we will merge along?
+ for (Diff dependency : diff.getRequires()) {
+ // TODO: what to do when state = Discarded but is required?
+ mergeDiff(dependency, rightToLeft, monitor);
+ }
+ }
+
+ /**
+ * This can be used by mergers to merge another (required, equivalent...) difference using the right
+ * merger for that diff.
+ *
+ * @param diff
+ * The diff we need to merge.
+ * @param rightToLeft
+ * Direction of that merge.
+ * @param monitor
+ * The monitor we should use to report progress.
+ */
+ protected void mergeDiff(Diff diff, boolean rightToLeft, Monitor monitor) {
+ if (rightToLeft) {
+ final IMerger delegate = getRegistry().getHighestRankingMerger(diff);
+ delegate.copyRightToLeft(diff, monitor);
+ } else {
+ final IMerger delegate = getRegistry().getHighestRankingMerger(diff);
+ delegate.copyLeftToRight(diff, monitor);
+ }
+ }
+
+ /**
+ * This will create a copy of the given EObject that can be used as the target of an addition (or the
+ * reverting of a deletion).
+ * <p>
+ * The target will be self-contained and will have no reference towards any other EObject set (neither
+ * containment nor "classic" references). All of its attributes' values will match the given
+ * {@code referenceObject}'s.
+ * </p>
+ *
+ * @param referenceObject
+ * The EObject for which we'll create a copy.
+ * @return A self-contained copy of {@code referenceObject}.
+ * @see EMFCompareCopier#copy(EObject)
+ */
+ protected EObject createCopy(EObject referenceObject) {
+ /*
+ * We can't simply use EcoreUtil.copy. References will have their own diffs and will thus be merged
+ * later on.
+ */
+ final EcoreUtil.Copier copier = new EMFCompareCopier();
+ return copier.copy(referenceObject);
+ }
+
+ /**
+ * Adds the given {@code value} into the given {@code list} at the given {@code index}. An {@code index}
+ * under than zero or above the list's size will mean that the value should be appended at the end of the
+ * list.
+ *
+ * @param list
+ * The list into which {@code value} should be added.
+ * @param value
+ * The value we need to add to {@code list}.
+ * @param <E>
+ * Type of objects contained in the list.
+ * @param insertionIndex
+ * The index at which {@code value} should be inserted into {@code list}. {@code -1} if it
+ * should be appended at the end of the list.
+ */
+ @SuppressWarnings("unchecked")
+ protected <E> void addAt(List<E> list, E value, int insertionIndex) {
+ if (list instanceof InternalEList<?>) {
+ if (insertionIndex < 0 || insertionIndex > list.size()) {
+ ((InternalEList<Object>)list).addUnique(value);
+ } else {
+ ((InternalEList<Object>)list).addUnique(insertionIndex, value);
+ }
+ } else {
+ if (insertionIndex < 0 || insertionIndex > list.size()) {
+ list.add(value);
+ } else {
+ list.add(insertionIndex, value);
+ }
+ }
+ }
+
+ /**
+ * Set the merge way for the given diff. after a merge, it allows to know the way of the merge.
+ *
+ * @param diff
+ * the given diff.
+ * @param leftToRight
+ * the way of the merge.
+ */
+ protected void setMergeDataForDiff(Diff diff, boolean leftToRight) {
+ Adapter adapter = EcoreUtil.getExistingAdapter(diff, IMergeData.class);
+ if (adapter != null) {
+ if (leftToRight) {
+ ((IMergeData)adapter).setMergedToRight();
+ } else {
+ ((IMergeData)adapter).setMergedToLeft();
+ }
+ } else {
+ diff.eAdapters().add(new MergeDataAdapter(leftToRight));
+ }
+ }
+}
diff --git a/plugins/org.eclipse.emf.compare/src/org/eclipse/emf/compare/merge/ReferenceChangeMerger.java b/plugins/org.eclipse.emf.compare/src/org/eclipse/emf/compare/merge/ReferenceChangeMerger.java
index 398c33963..02bb68461 100644
--- a/plugins/org.eclipse.emf.compare/src/org/eclipse/emf/compare/merge/ReferenceChangeMerger.java
+++ b/plugins/org.eclipse.emf.compare/src/org/eclipse/emf/compare/merge/ReferenceChangeMerger.java
@@ -1,714 +1,720 @@
-/*******************************************************************************
- * 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.merge;
-
-import static org.eclipse.emf.compare.utils.ReferenceUtil.safeEIsSet;
-
-import java.util.Iterator;
-import java.util.List;
-
-import org.eclipse.emf.common.util.EList;
-import org.eclipse.emf.common.util.Monitor;
-import org.eclipse.emf.compare.Comparison;
-import org.eclipse.emf.compare.Diff;
-import org.eclipse.emf.compare.DifferenceSource;
-import org.eclipse.emf.compare.DifferenceState;
-import org.eclipse.emf.compare.Match;
-import org.eclipse.emf.compare.ReferenceChange;
-import org.eclipse.emf.compare.utils.DiffUtil;
-import org.eclipse.emf.compare.utils.IEqualityHelper;
-import org.eclipse.emf.ecore.EObject;
-import org.eclipse.emf.ecore.EReference;
-import org.eclipse.emf.ecore.resource.Resource;
-import org.eclipse.emf.ecore.util.EcoreUtil;
-import org.eclipse.emf.ecore.xmi.XMIResource;
-
-/**
- * This specific implementation of {@link AbstractMerger} will be used to merge reference changes.
- *
- * @author <a href="mailto:laurent.goubet@obeo.fr">Laurent Goubet</a>
- */
-public class ReferenceChangeMerger extends AbstractMerger {
- /**
- * {@inheritDoc}
- *
- * @see org.eclipse.emf.compare.merge.IMerger#isMergerFor(org.eclipse.emf.compare.Diff)
- */
- public boolean isMergerFor(Diff target) {
- return target instanceof ReferenceChange;
- }
-
- /**
- * {@inheritDoc}
- *
- * @see org.eclipse.emf.compare.merge.IMerger#copyLeftToRight(org.eclipse.emf.compare.Diff,
- * org.eclipse.emf.common.util.Monitor)
- */
- public void copyLeftToRight(Diff target, Monitor monitor) {
- // Don't merge an already merged (or discarded) diff
- if (target.getState() != DifferenceState.UNRESOLVED) {
- return;
- }
- final ReferenceChange diff = (ReferenceChange)target;
-
- // Change the diff's state before we actually merge it : this allows us to avoid requirement cycles.
- diff.setState(DifferenceState.MERGED);
- if (diff.getEquivalence() != null) {
- boolean continueMerge = handleEquivalences(diff, false, monitor);
- if (!continueMerge) {
- return;
- }
- }
-
- if (diff.getSource() == DifferenceSource.LEFT) {
- // merge all "requires" diffs
- mergeRequires(diff, false, monitor);
-
- switch (diff.getKind()) {
- case ADD:
- // Create the same element in right
- addInTarget(diff, false);
- break;
- case DELETE:
- // Delete that same element from right
- removeFromTarget(diff, false);
- break;
- case MOVE:
- moveElement(diff, false);
- break;
- case CHANGE:
- // Is it an unset?
- if (diff.getMatch().getLeft() != null) {
- final EObject leftValue = (EObject)diff.getMatch().getLeft().eGet(
- diff.getReference(), false);
- if (leftValue == null) {
- removeFromTarget(diff, false);
- } else {
- addInTarget(diff, false);
- }
- } else {
- // we have no left, and the source is on the left. Can only be an unset
- removeFromTarget(diff, false);
- }
- break;
- default:
- break;
- }
- } else {
- // merge all "required by" diffs
- mergeRequiredBy(diff, false, monitor);
-
- switch (diff.getKind()) {
- case ADD:
- // We have a ADD on right. we need to revert this addition
- removeFromTarget(diff, false);
- break;
- case DELETE:
- // DELETE in the right. We need to re-create this element
- addInTarget(diff, false);
- break;
- case MOVE:
- moveElement(diff, false);
- break;
- case CHANGE:
- // Is it an unset?
- if (diff.getMatch().getRight() != null) {
- final EObject rightValue = (EObject)diff.getMatch().getRight().eGet(
- diff.getReference(), false);
- if (rightValue == null) {
- // Value has been unset in the right, and we are merging towards right.
- // We need to re-add this element
- addInTarget(diff, false);
- } else {
- // We'll actually need to "reset" this reference to its original value
- resetInTarget(diff, false);
- }
- } else {
- // we have no right, and the source is on the right. Can only be an unset
- addInTarget(diff, false);
- }
- break;
- default:
- break;
- }
- }
- }
-
- /**
- * {@inheritDoc}
- *
- * @see org.eclipse.emf.compare.merge.IMerger#copyRightToLeft(org.eclipse.emf.compare.Diff,
- * org.eclipse.emf.common.util.Monitor)
- */
- public void copyRightToLeft(Diff target, Monitor monitor) {
- // Don't merge an already merged (or discarded) diff
- if (target.getState() != DifferenceState.UNRESOLVED) {
- return;
- }
- final ReferenceChange diff = (ReferenceChange)target;
-
- // Change the diff's state before we actually merge it : this allows us to avoid requirement cycles.
- diff.setState(DifferenceState.MERGED);
- if (diff.getEquivalence() != null) {
- boolean continueMerge = handleEquivalences(diff, true, monitor);
- if (!continueMerge) {
- return;
- }
- }
-
- if (diff.getSource() == DifferenceSource.LEFT) {
- // merge all "required by" diffs
- mergeRequiredBy(diff, true, monitor);
-
- switch (diff.getKind()) {
- case ADD:
- // We have a ADD on left, thus nothing in right. We need to revert the addition
- removeFromTarget(diff, true);
- break;
- case DELETE:
- // DELETE in the left, thus an element in right. We need to re-create that element
- addInTarget(diff, true);
- break;
- case MOVE:
- moveElement(diff, true);
- break;
- case CHANGE:
- // Is it an unset?
- if (diff.getMatch().getLeft() != null) {
- final EObject leftValue = (EObject)diff.getMatch().getLeft().eGet(
- diff.getReference(), false);
- if (leftValue == null) {
- // Value has been unset in the right, and we are merging towards right.
- // We need to re-add this element
- addInTarget(diff, true);
- } else {
- // We'll actually need to "reset" this reference to its original value
- resetInTarget(diff, true);
- }
- } else {
- // we have no left, and the source is on the left. Can only be an unset
- addInTarget(diff, true);
- }
- break;
- default:
- break;
- }
- } else {
- // merge all "requires" diffs
- mergeRequires(diff, true, monitor);
-
- switch (diff.getKind()) {
- case ADD:
- addInTarget(diff, true);
- break;
- case DELETE:
- removeFromTarget(diff, true);
- break;
- case MOVE:
- moveElement(diff, true);
- break;
- case CHANGE:
- // Is it an unset?
- if (diff.getMatch().getRight() != null) {
- final EObject rightValue = (EObject)diff.getMatch().getRight().eGet(
- diff.getReference(), false);
- if (rightValue == null) {
- removeFromTarget(diff, true);
- } else {
- addInTarget(diff, true);
- }
- } else {
- // we have no right, and the source is on the right. Can only be an unset
- removeFromTarget(diff, true);
- }
- break;
- default:
- break;
- }
- }
- }
-
- /**
- * This will be called when trying to copy a "MOVE" diff.
- *
- * @param diff
- * The diff we are currently merging.
- * @param rightToLeft
- * Whether we should move the value in the left or right side.
- */
- protected void moveElement(ReferenceChange diff, boolean rightToLeft) {
- final Comparison comparison = diff.getMatch().getComparison();
- final Match valueMatch = comparison.getMatch(diff.getValue());
- final EReference reference = diff.getReference();
-
- final EObject expectedContainer;
- if (reference.isContainment()) {
- /*
- * We cannot "trust" the holding match (getMatch) in this case. However, "valueMatch" cannot be
- * null : we cannot have detected a move if the moved element is not matched on both sides. Use
- * that information to retrieve the proper "target" container.
- */
- 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 = diff.getMatch().getLeft();
- } else {
- expectedContainer = diff.getMatch().getRight();
- }
- if (expectedContainer == null) {
- // FIXME throw exception? log? re-try to merge our requirements?
- // one of the "required" diffs should have created our container.
- return;
- }
-
- final EObject expectedValue;
- if (valueMatch == null) {
- // The value being moved is out of the scope
- /*
- * Note : there should not be a way to end up with a "move" for an out of scope value : a move can
- * only be detected if the object is matched on both sides, otherwise all we can see is "add" and
- * "delete"... Is this "fallback" code even reachable? If so, how?
- */
- // We need to look it up
- if (reference.isMany()) {
- @SuppressWarnings("unchecked")
- final List<EObject> targetList = (List<EObject>)expectedContainer.eGet(reference);
- expectedValue = findMatchIn(comparison, targetList, diff.getValue());
- } else {
- expectedValue = (EObject)expectedContainer.eGet(reference);
- }
- } else {
- if (rightToLeft) {
- expectedValue = valueMatch.getLeft();
- } else {
- expectedValue = valueMatch.getRight();
- }
- }
- // We now know the target container, target reference and target value.
- doMove(diff, comparison, expectedContainer, expectedValue, rightToLeft);
- }
-
- /**
- * This will do the actual work of moving the element into its reference. All sanity checks were made in
- * {@link #moveElement(boolean)} and no more verification will be made here.
- *
- * @param diff
- * The diff we are currently merging.
- * @param comparison
- * Comparison holding this Diff.
- * @param expectedContainer
- * The container in which we are reorganizing a reference.
- * @param expectedValue
- * The value that is to be moved within its reference.
- * @param rightToLeft
- * Whether we should move the value in the left or right side.
- */
- @SuppressWarnings("unchecked")
- protected void doMove(ReferenceChange diff, Comparison comparison, EObject expectedContainer,
- EObject expectedValue, boolean rightToLeft) {
- final EReference reference = diff.getReference();
- if (reference.isMany()) {
- // Element to move cannot be part of the LCS... or there would not be a MOVE diff
- int insertionIndex = findInsertionIndex(comparison, diff, rightToLeft);
-
- /*
- * However, it could still have been located "before" its new index, in which case we need to take
- * it into account.
- */
- final List<EObject> targetList = (List<EObject>)expectedContainer.eGet(reference);
- final int currentIndex = targetList.indexOf(expectedValue);
- if (insertionIndex > currentIndex && currentIndex >= 0) {
- insertionIndex--;
- }
-
- if (currentIndex == -1) {
- // happens for container changes for example.
- if (!reference.isContainment()) {
- targetList.remove(expectedValue);
- }
- if (insertionIndex < 0 && insertionIndex > targetList.size()) {
- targetList.add(expectedValue);
- } else {
- targetList.add(insertionIndex, expectedValue);
- }
- } else if (targetList instanceof EList<?>) {
- if (insertionIndex < 0 && insertionIndex > targetList.size()) {
- ((EList<EObject>)targetList).move(targetList.size() - 1, expectedValue);
- } else {
- ((EList<EObject>)targetList).move(insertionIndex, expectedValue);
- }
- } else {
- targetList.remove(expectedValue);
- if (insertionIndex < 0 && insertionIndex > targetList.size()) {
- targetList.add(expectedValue);
- } else {
- targetList.add(insertionIndex, expectedValue);
- }
- }
- } else {
- expectedContainer.eSet(reference, expectedValue);
- }
- }
-
- /**
- * This will be called when we need to create an element in the target side.
- * <p>
- * All necessary sanity checks have been made to ensure that the current operation is one that should
- * create an object in its side or add an objet to a reference. In other words, either :
- * <ul>
- * <li>We are copying from right to left and
- * <ul>
- * <li>we are copying an addition to the right side (we need to create the same object in the left), or</li>
- * <li>we are copying a deletion from the left side (we need to revert the deletion).</li>
- * </ul>
- * </li>
- * <li>We are copying from left to right and
- * <ul>
- * <li>we are copying a deletion from the right side (we need to revert the deletion), or</li>
- * <li>we are copying an addition to the left side (we need to create the same object in the right).</li>
- * </ul>
- * </li>
- * </ul>
- * </p>
- *
- * @param diff
- * The diff we are currently merging.
- * @param rightToLeft
- * Tells us whether we are to add an object on the left or right side.
- */
- @SuppressWarnings("unchecked")
- protected void addInTarget(ReferenceChange diff, boolean rightToLeft) {
- final Match match = diff.getMatch();
- final EObject expectedContainer;
- if (rightToLeft) {
- expectedContainer = match.getLeft();
- } else {
- expectedContainer = match.getRight();
- }
-
- if (expectedContainer == null) {
- // FIXME throw exception? log? re-try to merge our requirements?
- // one of the "required" diffs should have created our container.
- return;
- }
-
- final Comparison comparison = match.getComparison();
- final EReference reference = diff.getReference();
- final EObject expectedValue;
- final Match valueMatch = comparison.getMatch(diff.getValue());
- if (valueMatch == null) {
- // This is an out of scope value.
- if (diff.getValue().eIsProxy()) {
- // Copy the proxy
- expectedValue = EcoreUtil.copy(diff.getValue());
- } else {
- // Use the same value.
- expectedValue = diff.getValue();
- }
- } else if (rightToLeft) {
- if (reference.isContainment()) {
- expectedValue = createCopy(diff.getValue());
- valueMatch.setLeft(expectedValue);
- } else {
- expectedValue = valueMatch.getLeft();
- }
- } else {
- if (reference.isContainment()) {
- expectedValue = createCopy(diff.getValue());
- valueMatch.setRight(expectedValue);
- } else {
- expectedValue = valueMatch.getRight();
- }
- }
-
- // We have the container, reference and value. We need to know the insertion index.
- if (reference.isMany()) {
- final int insertionIndex = findInsertionIndex(comparison, diff, rightToLeft);
-
- final List<EObject> targetList = (List<EObject>)expectedContainer.eGet(reference);
- addAt(targetList, expectedValue, insertionIndex);
- } else {
- expectedContainer.eSet(reference, expectedValue);
- }
-
- if (reference.isContainment()) {
- // Copy XMI ID when applicable.
- final Resource initialResource = diff.getValue().eResource();
- final Resource targetResource = expectedValue.eResource();
- if (initialResource instanceof XMIResource && targetResource instanceof XMIResource) {
- ((XMIResource)targetResource).setID(expectedValue, ((XMIResource)initialResource).getID(diff
- .getValue()));
- }
- }
- }
-
- /**
- * This will be called when we need to remove an element from the target side.
- * <p>
- * All necessary sanity checks have been made to ensure that the current operation is one that should
- * delete an object. In other words, we are :
- * <ul>
- * <li>Copying from right to left and either
- * <ul>
- * <li>we are copying a deletion from the right side (we need to remove the same object in the left) or,</li>
- * <li>we are copying an addition to the left side (we need to revert the addition).</li>
- * </ul>
- * </li>
- * <li>Copying from left to right and either
- * <ul>
- * <li>we are copying an addition to the right side (we need to revert the addition), or.</li>
- * <li>we are copying a deletion from the left side (we need to remove the same object in the right).</li>
- * </ul>
- * </li>
- * </ul>
- * </p>
- *
- * @param diff
- * The diff we are currently merging.
- * @param rightToLeft
- * Tells us whether we are to add an object on the left or right side.
- */
- @SuppressWarnings("unchecked")
- protected void removeFromTarget(ReferenceChange diff, boolean rightToLeft) {
- final Match match = diff.getMatch();
- final EReference reference = diff.getReference();
- final EObject currentContainer;
- if (rightToLeft) {
- currentContainer = match.getLeft();
- } else {
- currentContainer = match.getRight();
- }
- final Comparison comparison = match.getComparison();
- final Match valueMatch = comparison.getMatch(diff.getValue());
-
- if (currentContainer == null) {
- // FIXME throw exception? log? re-try to merge our requirements?
- // one of the "required" diffs should have created our container.
- return;
- }
-
- final EObject expectedValue;
- if (valueMatch == null) {
- // value is out of the scope... we need to look it up
- if (reference.isMany()) {
- final List<EObject> targetList = (List<EObject>)currentContainer.eGet(reference);
- expectedValue = findMatchIn(comparison, targetList, diff.getValue());
- } else {
- // the value will not be needed anyway
- expectedValue = null;
- }
- } else if (rightToLeft) {
- expectedValue = valueMatch.getLeft();
- } else {
- expectedValue = valueMatch.getRight();
- }
-
- // We have the container, reference and value to remove. Expected value can be null when the
- // deletion was made on both side (i.e. a pseudo delete)
- if (reference.isContainment() && expectedValue != null) {
- EcoreUtil.remove(expectedValue);
- if (rightToLeft && valueMatch != null) {
- valueMatch.setLeft(null);
- } else if (valueMatch != null) {
- valueMatch.setRight(null);
- }
- // TODO remove dangling? remove empty Match?
- } else if (reference.isMany()) {
- /*
- * TODO if the same value appears twice, should we try and find the one that has actually been
- * deleted? Can it happen? For now, remove the first occurence we find.
- */
- final List<EObject> targetList = (List<EObject>)currentContainer.eGet(reference);
- targetList.remove(expectedValue);
- } else {
- currentContainer.eUnset(reference);
- }
- }
-
- /**
- * This will be called by the merge operations in order to reset a reference to its original value, be
- * that the left or right side.
- * <p>
- * Should never be called on multi-valued references.
- * </p>
- *
- * @param diff
- * The diff we are currently merging.
- * @param rightToLeft
- * Tells us the direction of this merge operation.
- */
- protected void resetInTarget(ReferenceChange diff, boolean rightToLeft) {
- final Match match = diff.getMatch();
- final EReference reference = diff.getReference();
- final EObject targetContainer;
- if (rightToLeft) {
- targetContainer = match.getLeft();
- } else {
- targetContainer = match.getRight();
- }
-
- final EObject originContainer;
- if (match.getComparison().isThreeWay()) {
- originContainer = match.getOrigin();
- } else if (rightToLeft) {
- originContainer = match.getRight();
- } else {
- originContainer = match.getLeft();
- }
-
- if (originContainer == null || !safeEIsSet(targetContainer, reference)
- || !safeEIsSet(originContainer, reference)) {
- targetContainer.eUnset(reference);
- } else {
- final EObject originalValue = (EObject)originContainer.eGet(reference);
- final Match valueMatch = match.getComparison().getMatch(originalValue);
- final EObject expectedValue;
- if (valueMatch == null) {
- // Value is out of the scope, use it as-is
- expectedValue = originalValue;
- } else if (rightToLeft) {
- expectedValue = valueMatch.getLeft();
- } else {
- expectedValue = valueMatch.getRight();
- }
- targetContainer.eSet(reference, expectedValue);
- }
- }
-
- /**
- * Handles the equivalences of this difference.
- * <p>
- * Note that in certain cases, we'll merge our opposite instead of merging this diff. Specifically, we'll
- * do that for one-to-many eOpposites : we'll merge the 'many' side instead of the 'unique' one. This
- * allows us not to worry about the order of the references on that 'many' side.
- * </p>
- * <p>
- * This is called before the merge of <code>this</code>. In short, if this returns <code>false</code>, we
- * won't carry on merging <code>this</code> after returning.
- * </p>
- *
- * @param diff
- * The diff we are currently merging.
- * @param rightToLeft
- * Direction of the merge.
- * @param monitor
- * The monitor to use in order to report progress information.
- * @return <code>true</code> if the current difference should still be merged after handling its
- * equivalences, <code>false</code> if it should be considered "already merged".
- */
- protected boolean handleEquivalences(ReferenceChange diff, boolean rightToLeft, Monitor monitor) {
- final EReference reference = diff.getReference();
- boolean continueMerge = true;
- for (Diff equivalent : diff.getEquivalence().getDifferences()) {
- if (equivalent instanceof ReferenceChange
- && reference.getEOpposite() == ((ReferenceChange)equivalent).getReference()
- && equivalent.getState() == DifferenceState.UNRESOLVED) {
- // This equivalence is on our eOpposite. Should we merge it instead of 'this'?
- final boolean mergeEquivalence = !reference.isMany()
- && ((ReferenceChange)equivalent).getReference().isMany();
- if (mergeEquivalence) {
- mergeDiff(equivalent, rightToLeft, monitor);
- continueMerge = false;
- }
- } else if (diff.getSource() == DifferenceSource.LEFT) {
- // This can happen when merging subset/supersets... see AddInterfaceTest#testA50UseCase
- /*
- * This should be removed (or we should make sure that we can never be here) when bug 398402
- * is fixed.
- */
- if (rightToLeft && diff.getRequiredBy().contains(equivalent)) {
- mergeDiff(equivalent, rightToLeft, monitor);
- continueMerge = false;
- } else if (!rightToLeft && diff.getRequires().contains(equivalent)) {
- mergeDiff(equivalent, rightToLeft, monitor);
- continueMerge = false;
- }
- } else if (diff.getSource() == DifferenceSource.RIGHT) {
- // This can happen when merging subset/supersets... see AddInterfaceTest#testA50UseCase
- /*
- * This should be removed (or we should make sure that we can never be here) when bug 398402
- * is fixed.
- */
- if (rightToLeft && diff.getRequires().contains(equivalent)) {
- mergeDiff(equivalent, rightToLeft, monitor);
- continueMerge = false;
- } else if (!rightToLeft && diff.getRequiredBy().contains(equivalent)) {
- mergeDiff(equivalent, rightToLeft, monitor);
- continueMerge = false;
- }
- }
- equivalent.setState(DifferenceState.MERGED);
- }
- return continueMerge;
- }
-
- /**
- * Seeks a match of the given {@code element} in the given list, using the equality helper to find it.
- * This is only used when moving or deleting proxies for now.
- *
- * @param comparison
- * The comparison which Diff we are currently merging.
- * @param list
- * The list from which we seek a value.
- * @param element
- * The value for which we need a match in {@code list}.
- * @return The match of {@code element} in {@code list}, {@code null} if none.
- */
- protected EObject findMatchIn(Comparison comparison, List<EObject> list, EObject element) {
- final IEqualityHelper helper = comparison.getEqualityHelper();
- final Iterator<EObject> it = list.iterator();
- while (it.hasNext()) {
- final EObject next = it.next();
- if (helper.matchingValues(next, element)) {
- return next;
- }
- }
- return null;
- }
-
- /**
- * This will be used by the distinct merge actions in order to find the index at which a value should be
- * inserted in its target list. See {@link DiffUtil#findInsertionIndex(Comparison, Diff, boolean)} for
- * more on this.
- * <p>
- * Sub-classes can override this if the insertion order is irrelevant. A return value of {@code -1} will
- * be considered as "no index" and the value will be inserted at the end of its target list.
- * </p>
- *
- * @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}. {@code -1} if the value should be inserted at the end of its
- * target list.
- * @see DiffUtil#findInsertionIndex(Comparison, Diff, boolean)
- */
- protected int findInsertionIndex(Comparison comparison, Diff diff, boolean rightToLeft) {
- return DiffUtil.findInsertionIndex(comparison, diff, rightToLeft);
- }
-}
+/*******************************************************************************
+ * 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.merge;
+
+import static org.eclipse.emf.compare.utils.ReferenceUtil.safeEIsSet;
+
+import java.util.Iterator;
+import java.util.List;
+
+import org.eclipse.emf.common.util.EList;
+import org.eclipse.emf.common.util.Monitor;
+import org.eclipse.emf.compare.Comparison;
+import org.eclipse.emf.compare.Diff;
+import org.eclipse.emf.compare.DifferenceSource;
+import org.eclipse.emf.compare.DifferenceState;
+import org.eclipse.emf.compare.Match;
+import org.eclipse.emf.compare.ReferenceChange;
+import org.eclipse.emf.compare.utils.DiffUtil;
+import org.eclipse.emf.compare.utils.IEqualityHelper;
+import org.eclipse.emf.ecore.EObject;
+import org.eclipse.emf.ecore.EReference;
+import org.eclipse.emf.ecore.resource.Resource;
+import org.eclipse.emf.ecore.util.EcoreUtil;
+import org.eclipse.emf.ecore.xmi.XMIResource;
+
+/**
+ * This specific implementation of {@link AbstractMerger} will be used to merge reference changes.
+ *
+ * @author <a href="mailto:laurent.goubet@obeo.fr">Laurent Goubet</a>
+ */
+public class ReferenceChangeMerger extends AbstractMerger {
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.eclipse.emf.compare.merge.IMerger#isMergerFor(org.eclipse.emf.compare.Diff)
+ */
+ public boolean isMergerFor(Diff target) {
+ return target instanceof ReferenceChange;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.eclipse.emf.compare.merge.IMerger#copyLeftToRight(org.eclipse.emf.compare.Diff,
+ * org.eclipse.emf.common.util.Monitor)
+ */
+ public void copyLeftToRight(Diff target, Monitor monitor) {
+ // Don't merge an already merged (or discarded) diff
+ if (target.getState() != DifferenceState.UNRESOLVED) {
+ return;
+ }
+ final ReferenceChange diff = (ReferenceChange)target;
+
+ // Change the diff's state before we actually merge it : this allows us to avoid requirement cycles.
+ diff.setState(DifferenceState.MERGED);
+ // Set the merge way for the diff
+ setMergeDataForDiff(diff, true);
+
+ if (diff.getEquivalence() != null) {
+ boolean continueMerge = handleEquivalences(diff, false, monitor);
+ if (!continueMerge) {
+ return;
+ }
+ }
+
+ if (diff.getSource() == DifferenceSource.LEFT) {
+ // merge all "requires" diffs
+ mergeRequires(diff, false, monitor);
+
+ switch (diff.getKind()) {
+ case ADD:
+ // Create the same element in right
+ addInTarget(diff, false);
+ break;
+ case DELETE:
+ // Delete that same element from right
+ removeFromTarget(diff, false);
+ break;
+ case MOVE:
+ moveElement(diff, false);
+ break;
+ case CHANGE:
+ // Is it an unset?
+ if (diff.getMatch().getLeft() != null) {
+ final EObject leftValue = (EObject)diff.getMatch().getLeft().eGet(
+ diff.getReference(), false);
+ if (leftValue == null) {
+ removeFromTarget(diff, false);
+ } else {
+ addInTarget(diff, false);
+ }
+ } else {
+ // we have no left, and the source is on the left. Can only be an unset
+ removeFromTarget(diff, false);
+ }
+ break;
+ default:
+ break;
+ }
+ } else {
+ // merge all "required by" diffs
+ mergeRequiredBy(diff, false, monitor);
+
+ switch (diff.getKind()) {
+ case ADD:
+ // We have a ADD on right. we need to revert this addition
+ removeFromTarget(diff, false);
+ break;
+ case DELETE:
+ // DELETE in the right. We need to re-create this element
+ addInTarget(diff, false);
+ break;
+ case MOVE:
+ moveElement(diff, false);
+ break;
+ case CHANGE:
+ // Is it an unset?
+ if (diff.getMatch().getRight() != null) {
+ final EObject rightValue = (EObject)diff.getMatch().getRight().eGet(
+ diff.getReference(), false);
+ if (rightValue == null) {
+ // Value has been unset in the right, and we are merging towards right.
+ // We need to re-add this element
+ addInTarget(diff, false);
+ } else {
+ // We'll actually need to "reset" this reference to its original value
+ resetInTarget(diff, false);
+ }
+ } else {
+ // we have no right, and the source is on the right. Can only be an unset
+ addInTarget(diff, false);
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.eclipse.emf.compare.merge.IMerger#copyRightToLeft(org.eclipse.emf.compare.Diff,
+ * org.eclipse.emf.common.util.Monitor)
+ */
+ public void copyRightToLeft(Diff target, Monitor monitor) {
+ // Don't merge an already merged (or discarded) diff
+ if (target.getState() != DifferenceState.UNRESOLVED) {
+ return;
+ }
+ final ReferenceChange diff = (ReferenceChange)target;
+
+ // Change the diff's state before we actually merge it : this allows us to avoid requirement cycles.
+ diff.setState(DifferenceState.MERGED);
+ // Set the merge way for the diff
+ setMergeDataForDiff(diff, false);
+
+ if (diff.getEquivalence() != null) {
+ boolean continueMerge = handleEquivalences(diff, true, monitor);
+ if (!continueMerge) {
+ return;
+ }
+ }
+
+ if (diff.getSource() == DifferenceSource.LEFT) {
+ // merge all "required by" diffs
+ mergeRequiredBy(diff, true, monitor);
+
+ switch (diff.getKind()) {
+ case ADD:
+ // We have a ADD on left, thus nothing in right. We need to revert the addition
+ removeFromTarget(diff, true);
+ break;
+ case DELETE:
+ // DELETE in the left, thus an element in right. We need to re-create that element
+ addInTarget(diff, true);
+ break;
+ case MOVE:
+ moveElement(diff, true);
+ break;
+ case CHANGE:
+ // Is it an unset?
+ if (diff.getMatch().getLeft() != null) {
+ final EObject leftValue = (EObject)diff.getMatch().getLeft().eGet(
+ diff.getReference(), false);
+ if (leftValue == null) {
+ // Value has been unset in the right, and we are merging towards right.
+ // We need to re-add this element
+ addInTarget(diff, true);
+ } else {
+ // We'll actually need to "reset" this reference to its original value
+ resetInTarget(diff, true);
+ }
+ } else {
+ // we have no left, and the source is on the left. Can only be an unset
+ addInTarget(diff, true);
+ }
+ break;
+ default:
+ break;
+ }
+ } else {
+ // merge all "requires" diffs
+ mergeRequires(diff, true, monitor);
+
+ switch (diff.getKind()) {
+ case ADD:
+ addInTarget(diff, true);
+ break;
+ case DELETE:
+ removeFromTarget(diff, true);
+ break;
+ case MOVE:
+ moveElement(diff, true);
+ break;
+ case CHANGE:
+ // Is it an unset?
+ if (diff.getMatch().getRight() != null) {
+ final EObject rightValue = (EObject)diff.getMatch().getRight().eGet(
+ diff.getReference(), false);
+ if (rightValue == null) {
+ removeFromTarget(diff, true);
+ } else {
+ addInTarget(diff, true);
+ }
+ } else {
+ // we have no right, and the source is on the right. Can only be an unset
+ removeFromTarget(diff, true);
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ /**
+ * This will be called when trying to copy a "MOVE" diff.
+ *
+ * @param diff
+ * The diff we are currently merging.
+ * @param rightToLeft
+ * Whether we should move the value in the left or right side.
+ */
+ protected void moveElement(ReferenceChange diff, boolean rightToLeft) {
+ final Comparison comparison = diff.getMatch().getComparison();
+ final Match valueMatch = comparison.getMatch(diff.getValue());
+ final EReference reference = diff.getReference();
+
+ final EObject expectedContainer;
+ if (reference.isContainment()) {
+ /*
+ * We cannot "trust" the holding match (getMatch) in this case. However, "valueMatch" cannot be
+ * null : we cannot have detected a move if the moved element is not matched on both sides. Use
+ * that information to retrieve the proper "target" container.
+ */
+ 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 = diff.getMatch().getLeft();
+ } else {
+ expectedContainer = diff.getMatch().getRight();
+ }
+ if (expectedContainer == null) {
+ // FIXME throw exception? log? re-try to merge our requirements?
+ // one of the "required" diffs should have created our container.
+ return;
+ }
+
+ final EObject expectedValue;
+ if (valueMatch == null) {
+ // The value being moved is out of the scope
+ /*
+ * Note : there should not be a way to end up with a "move" for an out of scope value : a move can
+ * only be detected if the object is matched on both sides, otherwise all we can see is "add" and
+ * "delete"... Is this "fallback" code even reachable? If so, how?
+ */
+ // We need to look it up
+ if (reference.isMany()) {
+ @SuppressWarnings("unchecked")
+ final List<EObject> targetList = (List<EObject>)expectedContainer.eGet(reference);
+ expectedValue = findMatchIn(comparison, targetList, diff.getValue());
+ } else {
+ expectedValue = (EObject)expectedContainer.eGet(reference);
+ }
+ } else {
+ if (rightToLeft) {
+ expectedValue = valueMatch.getLeft();
+ } else {
+ expectedValue = valueMatch.getRight();
+ }
+ }
+ // We now know the target container, target reference and target value.
+ doMove(diff, comparison, expectedContainer, expectedValue, rightToLeft);
+ }
+
+ /**
+ * This will do the actual work of moving the element into its reference. All sanity checks were made in
+ * {@link #moveElement(boolean)} and no more verification will be made here.
+ *
+ * @param diff
+ * The diff we are currently merging.
+ * @param comparison
+ * Comparison holding this Diff.
+ * @param expectedContainer
+ * The container in which we are reorganizing a reference.
+ * @param expectedValue
+ * The value that is to be moved within its reference.
+ * @param rightToLeft
+ * Whether we should move the value in the left or right side.
+ */
+ @SuppressWarnings("unchecked")
+ protected void doMove(ReferenceChange diff, Comparison comparison, EObject expectedContainer,
+ EObject expectedValue, boolean rightToLeft) {
+ final EReference reference = diff.getReference();
+ if (reference.isMany()) {
+ // Element to move cannot be part of the LCS... or there would not be a MOVE diff
+ int insertionIndex = findInsertionIndex(comparison, diff, rightToLeft);
+
+ /*
+ * However, it could still have been located "before" its new index, in which case we need to take
+ * it into account.
+ */
+ final List<EObject> targetList = (List<EObject>)expectedContainer.eGet(reference);
+ final int currentIndex = targetList.indexOf(expectedValue);
+ if (insertionIndex > currentIndex && currentIndex >= 0) {
+ insertionIndex--;
+ }
+
+ if (currentIndex == -1) {
+ // happens for container changes for example.
+ if (!reference.isContainment()) {
+ targetList.remove(expectedValue);
+ }
+ if (insertionIndex < 0 && insertionIndex > targetList.size()) {
+ targetList.add(expectedValue);
+ } else {
+ targetList.add(insertionIndex, expectedValue);
+ }
+ } else if (targetList instanceof EList<?>) {
+ if (insertionIndex < 0 && insertionIndex > targetList.size()) {
+ ((EList<EObject>)targetList).move(targetList.size() - 1, expectedValue);
+ } else {
+ ((EList<EObject>)targetList).move(insertionIndex, expectedValue);
+ }
+ } else {
+ targetList.remove(expectedValue);
+ if (insertionIndex < 0 && insertionIndex > targetList.size()) {
+ targetList.add(expectedValue);
+ } else {
+ targetList.add(insertionIndex, expectedValue);
+ }
+ }
+ } else {
+ expectedContainer.eSet(reference, expectedValue);
+ }
+ }
+
+ /**
+ * This will be called when we need to create an element in the target side.
+ * <p>
+ * All necessary sanity checks have been made to ensure that the current operation is one that should
+ * create an object in its side or add an objet to a reference. In other words, either :
+ * <ul>
+ * <li>We are copying from right to left and
+ * <ul>
+ * <li>we are copying an addition to the right side (we need to create the same object in the left), or</li>
+ * <li>we are copying a deletion from the left side (we need to revert the deletion).</li>
+ * </ul>
+ * </li>
+ * <li>We are copying from left to right and
+ * <ul>
+ * <li>we are copying a deletion from the right side (we need to revert the deletion), or</li>
+ * <li>we are copying an addition to the left side (we need to create the same object in the right).</li>
+ * </ul>
+ * </li>
+ * </ul>
+ * </p>
+ *
+ * @param diff
+ * The diff we are currently merging.
+ * @param rightToLeft
+ * Tells us whether we are to add an object on the left or right side.
+ */
+ @SuppressWarnings("unchecked")
+ protected void addInTarget(ReferenceChange diff, boolean rightToLeft) {
+ final Match match = diff.getMatch();
+ final EObject expectedContainer;
+ if (rightToLeft) {
+ expectedContainer = match.getLeft();
+ } else {
+ expectedContainer = match.getRight();
+ }
+
+ if (expectedContainer == null) {
+ // FIXME throw exception? log? re-try to merge our requirements?
+ // one of the "required" diffs should have created our container.
+ return;
+ }
+
+ final Comparison comparison = match.getComparison();
+ final EReference reference = diff.getReference();
+ final EObject expectedValue;
+ final Match valueMatch = comparison.getMatch(diff.getValue());
+ if (valueMatch == null) {
+ // This is an out of scope value.
+ if (diff.getValue().eIsProxy()) {
+ // Copy the proxy
+ expectedValue = EcoreUtil.copy(diff.getValue());
+ } else {
+ // Use the same value.
+ expectedValue = diff.getValue();
+ }
+ } else if (rightToLeft) {
+ if (reference.isContainment()) {
+ expectedValue = createCopy(diff.getValue());
+ valueMatch.setLeft(expectedValue);
+ } else {
+ expectedValue = valueMatch.getLeft();
+ }
+ } else {
+ if (reference.isContainment()) {
+ expectedValue = createCopy(diff.getValue());
+ valueMatch.setRight(expectedValue);
+ } else {
+ expectedValue = valueMatch.getRight();
+ }
+ }
+
+ // We have the container, reference and value. We need to know the insertion index.
+ if (reference.isMany()) {
+ final int insertionIndex = findInsertionIndex(comparison, diff, rightToLeft);
+
+ final List<EObject> targetList = (List<EObject>)expectedContainer.eGet(reference);
+ addAt(targetList, expectedValue, insertionIndex);
+ } else {
+ expectedContainer.eSet(reference, expectedValue);
+ }
+
+ if (reference.isContainment()) {
+ // Copy XMI ID when applicable.
+ final Resource initialResource = diff.getValue().eResource();
+ final Resource targetResource = expectedValue.eResource();
+ if (initialResource instanceof XMIResource && targetResource instanceof XMIResource) {
+ ((XMIResource)targetResource).setID(expectedValue, ((XMIResource)initialResource).getID(diff
+ .getValue()));
+ }
+ }
+ }
+
+ /**
+ * This will be called when we need to remove an element from the target side.
+ * <p>
+ * All necessary sanity checks have been made to ensure that the current operation is one that should
+ * delete an object. In other words, we are :
+ * <ul>
+ * <li>Copying from right to left and either
+ * <ul>
+ * <li>we are copying a deletion from the right side (we need to remove the same object in the left) or,</li>
+ * <li>we are copying an addition to the left side (we need to revert the addition).</li>
+ * </ul>
+ * </li>
+ * <li>Copying from left to right and either
+ * <ul>
+ * <li>we are copying an addition to the right side (we need to revert the addition), or.</li>
+ * <li>we are copying a deletion from the left side (we need to remove the same object in the right).</li>
+ * </ul>
+ * </li>
+ * </ul>
+ * </p>
+ *
+ * @param diff
+ * The diff we are currently merging.
+ * @param rightToLeft
+ * Tells us whether we are to add an object on the left or right side.
+ */
+ @SuppressWarnings("unchecked")
+ protected void removeFromTarget(ReferenceChange diff, boolean rightToLeft) {
+ final Match match = diff.getMatch();
+ final EReference reference = diff.getReference();
+ final EObject currentContainer;
+ if (rightToLeft) {
+ currentContainer = match.getLeft();
+ } else {
+ currentContainer = match.getRight();
+ }
+ final Comparison comparison = match.getComparison();
+ final Match valueMatch = comparison.getMatch(diff.getValue());
+
+ if (currentContainer == null) {
+ // FIXME throw exception? log? re-try to merge our requirements?
+ // one of the "required" diffs should have created our container.
+ return;
+ }
+
+ final EObject expectedValue;
+ if (valueMatch == null) {
+ // value is out of the scope... we need to look it up
+ if (reference.isMany()) {
+ final List<EObject> targetList = (List<EObject>)currentContainer.eGet(reference);
+ expectedValue = findMatchIn(comparison, targetList, diff.getValue());
+ } else {
+ // the value will not be needed anyway
+ expectedValue = null;
+ }
+ } else if (rightToLeft) {
+ expectedValue = valueMatch.getLeft();
+ } else {
+ expectedValue = valueMatch.getRight();
+ }
+
+ // We have the container, reference and value to remove. Expected value can be null when the
+ // deletion was made on both side (i.e. a pseudo delete)
+ if (reference.isContainment() && expectedValue != null) {
+ EcoreUtil.remove(expectedValue);
+ if (rightToLeft && valueMatch != null) {
+ valueMatch.setLeft(null);
+ } else if (valueMatch != null) {
+ valueMatch.setRight(null);
+ }
+ // TODO remove dangling? remove empty Match?
+ } else if (reference.isMany()) {
+ /*
+ * TODO if the same value appears twice, should we try and find the one that has actually been
+ * deleted? Can it happen? For now, remove the first occurence we find.
+ */
+ final List<EObject> targetList = (List<EObject>)currentContainer.eGet(reference);
+ targetList.remove(expectedValue);
+ } else {
+ currentContainer.eUnset(reference);
+ }
+ }
+
+ /**
+ * This will be called by the merge operations in order to reset a reference to its original value, be
+ * that the left or right side.
+ * <p>
+ * Should never be called on multi-valued references.
+ * </p>
+ *
+ * @param diff
+ * The diff we are currently merging.
+ * @param rightToLeft
+ * Tells us the direction of this merge operation.
+ */
+ protected void resetInTarget(ReferenceChange diff, boolean rightToLeft) {
+ final Match match = diff.getMatch();
+ final EReference reference = diff.getReference();
+ final EObject targetContainer;
+ if (rightToLeft) {
+ targetContainer = match.getLeft();
+ } else {
+ targetContainer = match.getRight();
+ }
+
+ final EObject originContainer;
+ if (match.getComparison().isThreeWay()) {
+ originContainer = match.getOrigin();
+ } else if (rightToLeft) {
+ originContainer = match.getRight();
+ } else {
+ originContainer = match.getLeft();
+ }
+
+ if (originContainer == null || !safeEIsSet(targetContainer, reference)
+ || !safeEIsSet(originContainer, reference)) {
+ targetContainer.eUnset(reference);
+ } else {
+ final EObject originalValue = (EObject)originContainer.eGet(reference);
+ final Match valueMatch = match.getComparison().getMatch(originalValue);
+ final EObject expectedValue;
+ if (valueMatch == null) {
+ // Value is out of the scope, use it as-is
+ expectedValue = originalValue;
+ } else if (rightToLeft) {
+ expectedValue = valueMatch.getLeft();
+ } else {
+ expectedValue = valueMatch.getRight();
+ }
+ targetContainer.eSet(reference, expectedValue);
+ }
+ }
+
+ /**
+ * Handles the equivalences of this difference.
+ * <p>
+ * Note that in certain cases, we'll merge our opposite instead of merging this diff. Specifically, we'll
+ * do that for one-to-many eOpposites : we'll merge the 'many' side instead of the 'unique' one. This
+ * allows us not to worry about the order of the references on that 'many' side.
+ * </p>
+ * <p>
+ * This is called before the merge of <code>this</code>. In short, if this returns <code>false</code>, we
+ * won't carry on merging <code>this</code> after returning.
+ * </p>
+ *
+ * @param diff
+ * The diff we are currently merging.
+ * @param rightToLeft
+ * Direction of the merge.
+ * @param monitor
+ * The monitor to use in order to report progress information.
+ * @return <code>true</code> if the current difference should still be merged after handling its
+ * equivalences, <code>false</code> if it should be considered "already merged".
+ */
+ protected boolean handleEquivalences(ReferenceChange diff, boolean rightToLeft, Monitor monitor) {
+ final EReference reference = diff.getReference();
+ boolean continueMerge = true;
+ for (Diff equivalent : diff.getEquivalence().getDifferences()) {
+ if (equivalent instanceof ReferenceChange
+ && reference.getEOpposite() == ((ReferenceChange)equivalent).getReference()
+ && equivalent.getState() == DifferenceState.UNRESOLVED) {
+ // This equivalence is on our eOpposite. Should we merge it instead of 'this'?
+ final boolean mergeEquivalence = !reference.isMany()
+ && ((ReferenceChange)equivalent).getReference().isMany();
+ if (mergeEquivalence) {
+ mergeDiff(equivalent, rightToLeft, monitor);
+ continueMerge = false;
+ }
+ } else if (diff.getSource() == DifferenceSource.LEFT) {
+ // This can happen when merging subset/supersets... see AddInterfaceTest#testA50UseCase
+ /*
+ * This should be removed (or we should make sure that we can never be here) when bug 398402
+ * is fixed.
+ */
+ if (rightToLeft && diff.getRequiredBy().contains(equivalent)) {
+ mergeDiff(equivalent, rightToLeft, monitor);
+ continueMerge = false;
+ } else if (!rightToLeft && diff.getRequires().contains(equivalent)) {
+ mergeDiff(equivalent, rightToLeft, monitor);
+ continueMerge = false;
+ }
+ } else if (diff.getSource() == DifferenceSource.RIGHT) {
+ // This can happen when merging subset/supersets... see AddInterfaceTest#testA50UseCase
+ /*
+ * This should be removed (or we should make sure that we can never be here) when bug 398402
+ * is fixed.
+ */
+ if (rightToLeft && diff.getRequires().contains(equivalent)) {
+ mergeDiff(equivalent, rightToLeft, monitor);
+ continueMerge = false;
+ } else if (!rightToLeft && diff.getRequiredBy().contains(equivalent)) {
+ mergeDiff(equivalent, rightToLeft, monitor);
+ continueMerge = false;
+ }
+ }
+ equivalent.setState(DifferenceState.MERGED);
+ }
+ return continueMerge;
+ }
+
+ /**
+ * Seeks a match of the given {@code element} in the given list, using the equality helper to find it.
+ * This is only used when moving or deleting proxies for now.
+ *
+ * @param comparison
+ * The comparison which Diff we are currently merging.
+ * @param list
+ * The list from which we seek a value.
+ * @param element
+ * The value for which we need a match in {@code list}.
+ * @return The match of {@code element} in {@code list}, {@code null} if none.
+ */
+ protected EObject findMatchIn(Comparison comparison, List<EObject> list, EObject element) {
+ final IEqualityHelper helper = comparison.getEqualityHelper();
+ final Iterator<EObject> it = list.iterator();
+ while (it.hasNext()) {
+ final EObject next = it.next();
+ if (helper.matchingValues(next, element)) {
+ return next;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * This will be used by the distinct merge actions in order to find the index at which a value should be
+ * inserted in its target list. See {@link DiffUtil#findInsertionIndex(Comparison, Diff, boolean)} for
+ * more on this.
+ * <p>
+ * Sub-classes can override this if the insertion order is irrelevant. A return value of {@code -1} will
+ * be considered as "no index" and the value will be inserted at the end of its target list.
+ * </p>
+ *
+ * @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}. {@code -1} if the value should be inserted at the end of its
+ * target list.
+ * @see DiffUtil#findInsertionIndex(Comparison, Diff, boolean)
+ */
+ protected int findInsertionIndex(Comparison comparison, Diff diff, boolean rightToLeft) {
+ return DiffUtil.findInsertionIndex(comparison, diff, rightToLeft);
+ }
+}

Back to the top