| /******************************************************************************* |
| * Copyright (c) 2016 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.conflict; |
| |
| import static com.google.common.base.Predicates.instanceOf; |
| import static org.eclipse.emf.compare.ConflictKind.PSEUDO; |
| import static org.eclipse.emf.compare.ConflictKind.REAL; |
| import static org.eclipse.emf.compare.DifferenceKind.ADD; |
| import static org.eclipse.emf.compare.DifferenceKind.DELETE; |
| import static org.eclipse.emf.compare.DifferenceKind.MOVE; |
| import static org.eclipse.emf.compare.internal.utils.ComparisonUtil.isDeleteOrUnsetDiff; |
| import static org.eclipse.emf.compare.utils.EMFComparePredicates.possiblyConflictingWith; |
| |
| import java.util.Collection; |
| |
| import org.eclipse.emf.common.util.EList; |
| import org.eclipse.emf.common.util.Monitor; |
| 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.Match; |
| import org.eclipse.emf.compare.ReferenceChange; |
| import org.eclipse.emf.compare.ResourceAttachmentChange; |
| import org.eclipse.emf.ecore.EObject; |
| import org.eclipse.emf.ecore.resource.Resource; |
| |
| /** |
| * Search for {@link ResourceAttachmentChange} conflicts. |
| * |
| * @author <a href="mailto:laurent.delaigue@obeo.fr">Laurent Delaigue</a> |
| */ |
| public class ResourceAttachmentChangeConflictSearch { |
| |
| /** |
| * Search conflicts for {@link ResourceAttachmentChange} of kind {@link DifferenceKind#ADD}. |
| * |
| * @author <a href="mailto:laurent.delaigue@obeo.fr">Laurent Delaigue</a> |
| */ |
| public static class Add extends AbstractConflictSearch<ResourceAttachmentChange> { |
| |
| /** |
| * Constructor. |
| * |
| * @param diff |
| * The diff to search for conflicts |
| * @param index |
| * Comparison index, must not be null |
| * @param monitor |
| * the monitor to report progress to, must not be null |
| */ |
| public Add(ResourceAttachmentChange diff, ComparisonIndex index, Monitor monitor) { |
| super(diff, index, monitor); |
| } |
| |
| @Override |
| public void detectConflicts() { |
| // Can conflict with any containment reference change that concerns the same object |
| Match match = diff.getMatch(); |
| EObject value = getValue(diff); |
| |
| // First let's see if ReferenceChanges point to the EObject moved |
| Collection<ReferenceChange> refChanges = index.getReferenceChangesByValue(value); |
| Iterable<ReferenceChange> candidates = refChanges.stream() |
| .filter(possiblyConflictingWith(diff))::iterator; |
| for (ReferenceChange candidate : candidates) { |
| if (candidate.getReference().isContainment()) { |
| if (candidate.getValue().eContainer() == null) { |
| // The element is a new root on one side, but it has been moved to an EObject |
| // container on the other |
| conflict(candidate, REAL); |
| } |
| } else { |
| // [477607] DELETE does not necessarily mean that the element is removed from the |
| // model |
| if (value.eContainer() == null) { |
| // The root has been deleted. |
| // Anything other than a delete of this value in a reference is a conflict. |
| if (candidate.getKind() == DELETE) { |
| // No conflict here |
| } else { |
| conflict(candidate, REAL); |
| } |
| } |
| } |
| } |
| |
| // Then let's see if there's a conflict with another ResourceAttachmentChange |
| EList<Diff> diffsInSameMatch = diff.getMatch().getDifferences(); |
| Iterable<Diff> racCandidates = diffsInSameMatch.stream().filter( |
| possiblyConflictingWith(diff).and(instanceOf(ResourceAttachmentChange.class)))::iterator; |
| for (Diff candidate : racCandidates) { |
| ConflictKind kind = REAL; |
| if (candidate.getKind() == ADD) { |
| final Resource diffRes; |
| final Resource candidateRes; |
| if (diff.getSource() == DifferenceSource.LEFT) { |
| diffRes = match.getLeft().eResource(); |
| candidateRes = match.getRight().eResource(); |
| } else { |
| diffRes = match.getRight().eResource(); |
| candidateRes = match.getLeft().eResource(); |
| } |
| if (getMatchResource(diffRes) == getMatchResource(candidateRes)) { |
| kind = PSEUDO; |
| } |
| } |
| conflict(candidate, kind); |
| } |
| } |
| } |
| |
| /** |
| * Search conflicts for {@link ResourceAttachmentChange} of kind {@link DifferenceKind#CHANGE}. |
| * |
| * @author <a href="mailto:laurent.delaigue@obeo.fr">Laurent Delaigue</a> |
| */ |
| public static class Change extends AbstractConflictSearch<ResourceAttachmentChange> { |
| |
| /** |
| * Constructor. |
| * |
| * @param diff |
| * The diff to search for conflicts |
| * @param index |
| * Comparison index, must not be null |
| * @param monitor |
| * the monitor to report progress to, must not be null |
| */ |
| public Change(ResourceAttachmentChange diff, ComparisonIndex index, Monitor monitor) { |
| super(diff, index, monitor); |
| } |
| |
| @Override |
| public void detectConflicts() { |
| throw new IllegalStateException("ResourceAttachmentChanges of type CHANGE should not exist."); //$NON-NLS-1$ |
| } |
| } |
| |
| /** |
| * Search conflicts for {@link ResourceAttachmentChange} of kind {@link DifferenceKind#DELETE}. |
| * |
| * @author <a href="mailto:laurent.delaigue@obeo.fr">Laurent Delaigue</a> |
| */ |
| public static class Delete extends AbstractConflictSearch<ResourceAttachmentChange> { |
| |
| /** |
| * Constructor. |
| * |
| * @param diff |
| * The diff to search for conflicts |
| * @param index |
| * Comparison index, must not be null |
| * @param monitor |
| * the monitor to report progress to, must not be null |
| */ |
| public Delete(ResourceAttachmentChange diff, ComparisonIndex index, Monitor monitor) { |
| super(diff, index, monitor); |
| } |
| |
| @Override |
| public void detectConflicts() { |
| Match match = diff.getMatch(); |
| EObject value = getRelatedModelElement(diff); |
| |
| // First let's see if ReferenceChanges point to the EObject moved |
| if (value != null) { |
| Collection<ReferenceChange> refChanges = index.getReferenceChangesByValue(value); |
| Iterable<ReferenceChange> candidates = refChanges.stream() |
| .filter(possiblyConflictingWith(diff))::iterator; |
| for (ReferenceChange candidate : candidates) { |
| if (candidate.getReference().isContainment()) { |
| // The element is a new root on one side, but it has been moved to an EObject |
| // container on the other |
| conflict(candidate, REAL); |
| } else { |
| // [477607] DELETE does not necessarily mean that the element is removed from the |
| // model |
| if (value.eContainer() == null) { |
| // The root has been deleted. |
| // Anything other than a delete of this value in a reference is a conflict. |
| if (candidate.getKind() != DELETE) { |
| conflict(candidate, REAL); |
| } |
| } |
| } |
| } |
| } |
| |
| // Then let's see if there's a conflict with another ResourceAttachmentChange |
| EList<Diff> diffsInSameMatch = diff.getMatch().getDifferences(); |
| Iterable<Diff> racCandidates = diffsInSameMatch.stream().filter( |
| possiblyConflictingWith(diff).and(instanceOf(ResourceAttachmentChange.class)))::iterator; |
| for (Diff candidate : racCandidates) { |
| ConflictKind kind = REAL; |
| if (candidate.getKind() == DELETE) { |
| final Resource diffRes; |
| final Resource candidateRes; |
| diffRes = match.getOrigin().eResource(); |
| candidateRes = match.getOrigin().eResource(); |
| if (getMatchResource(diffRes) == getMatchResource(candidateRes)) { |
| kind = PSEUDO; |
| } |
| } |
| conflict(candidate, kind); |
| } |
| |
| // [381143] Every Diff "under" a root deletion conflicts with it. |
| // [477607] DELETE does not necessarily mean that the element is removed from the model |
| // Each element under a pseudo-conflicting diff should have its own conflict and not be just a |
| // dependence of the existing conflict |
| if (isDanglingRootDeletion() |
| && (diff.getConflict() == null || diff.getConflict().getKind() != PSEUDO)) { |
| // We do not want to create a pseudo conflict between a deleted container and its |
| // deleted content, since that would prevent us from merging the container deletion |
| // altogether (since pseudo conflicts usually mean that no action is needed). |
| // conflict(extendedCandidate, PSEUDO); |
| match.getDifferences().stream().filter(possiblyConflictingWith(diff)) |
| .filter(d -> !isDeleteOrUnsetDiff(d)).forEach(candidate -> conflict(candidate, REAL)); |
| } |
| } |
| |
| /** |
| * Indicate whether the current diff represents the deletion of a 'dangling' root of a resource. A |
| * dangling root is an EObject that had no parent in the model. |
| * |
| * @return <code>true</code> if the current diff is a deletion of a dangling root EObject. |
| */ |
| protected boolean isDanglingRootDeletion() { |
| EObject o = getRelatedModelElement(diff); |
| if (o != null) { |
| return false; |
| } |
| EObject ancestorRoot = diff.getMatch().getOrigin(); |
| if (ancestorRoot.eContainer() == null) { |
| return true; |
| } |
| return false; |
| } |
| } |
| |
| /** |
| * Search conflicts for {@link ResourceAttachmentChange} of kind {@link DifferenceKind#MOVE}. |
| * |
| * @author <a href="mailto:laurent.delaigue@obeo.fr">Laurent Delaigue</a> |
| */ |
| public static class Move extends AbstractConflictSearch<ResourceAttachmentChange> { |
| |
| /** |
| * Constructor. |
| * |
| * @param diff |
| * The diff to search for conflicts |
| * @param index |
| * Comparison index, must not be null |
| * @param monitor |
| * the monitor to report progress to, must not be null |
| */ |
| public Move(ResourceAttachmentChange diff, ComparisonIndex index, Monitor monitor) { |
| super(diff, index, monitor); |
| } |
| |
| @Override |
| public void detectConflicts() { |
| EObject value = getRelatedModelElement(diff); |
| |
| // First let's see if ReferenceChanges point to the EObject moved |
| Collection<ReferenceChange> refChanges = index.getReferenceChangesByValue(value); |
| |
| // The element is a new root on one side, but it has been moved to an EObject container on |
| // the other |
| refChanges.stream().filter(possiblyConflictingWith(diff)) |
| .filter(candidate -> candidate.getReference().isContainment()) |
| .forEach(candidate -> conflict(candidate, REAL)); |
| |
| EList<Diff> diffsInSameMatch = diff.getMatch().getDifferences(); |
| Iterable<Diff> candidates = diffsInSameMatch.stream().filter( |
| possiblyConflictingWith(diff).and(instanceOf(ResourceAttachmentChange.class)))::iterator; |
| for (Diff candidate : candidates) { |
| ConflictKind kind = REAL; |
| if (candidate.getKind() == MOVE) { |
| String lhsURI = diff.getResourceURI(); |
| String rhsURI = ((ResourceAttachmentChange)candidate).getResourceURI(); |
| if (lhsURI.equals(rhsURI)) { |
| kind = ConflictKind.PSEUDO; |
| } |
| } |
| conflict(candidate, kind); |
| } |
| } |
| } |
| } |