/** * <copyright> * * Copyright (c) 2010-2016 Thales Global Services S.A.S. * 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: * Thales Global Services S.A.S. - initial API and implementation * Jaafar Bouayad (Atos) - Bug #484803 - Conflict detection on deletion * * </copyright> */ package org.eclipse.emf.diffmerge.impl.helpers; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.emf.common.util.URI; import org.eclipse.emf.diffmerge.Messages; import org.eclipse.emf.diffmerge.api.IComparison; import org.eclipse.emf.diffmerge.api.IDiffPolicy; import org.eclipse.emf.diffmerge.api.IMapping; import org.eclipse.emf.diffmerge.api.IMatch; import org.eclipse.emf.diffmerge.api.IMergePolicy; import org.eclipse.emf.diffmerge.api.Role; import org.eclipse.emf.diffmerge.api.diff.IAttributeValuePresence; import org.eclipse.emf.diffmerge.api.diff.IDifference; import org.eclipse.emf.diffmerge.api.diff.IElementPresence; import org.eclipse.emf.diffmerge.api.diff.IMergeableDifference; import org.eclipse.emf.diffmerge.api.diff.IReferenceValuePresence; import org.eclipse.emf.diffmerge.api.diff.IValuePresence; import org.eclipse.emf.diffmerge.api.scopes.IFeaturedModelScope; import org.eclipse.emf.diffmerge.util.structures.FArrayList; import org.eclipse.emf.diffmerge.util.structures.IEqualityTester; import org.eclipse.emf.ecore.EAttribute; import org.eclipse.emf.ecore.EClass; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.EReference; import org.eclipse.emf.ecore.util.EcoreUtil; /** * An operation which builds differences for a comparison. * @author Olivier Constant */ public class DiffOperation extends AbstractExpensiveOperation { /** The non-null diff policy */ private final IDiffPolicy _diffPolicy; /** The non-null merge policy */ private final IMergePolicy _mergePolicy; /** The non-null comparison whose differences are being built */ private final IComparison.Editable _comparison; /** * Constructor based on a comparison with a predefined mapping * @param comparison_p a non-null comparison whose mapping is already built * @param diffPolicy_p a non-null diff policy * @param mergePolicy_p a non-null merge policy */ public DiffOperation(IComparison.Editable comparison_p, IDiffPolicy diffPolicy_p, IMergePolicy mergePolicy_p) { super(); _comparison = comparison_p; _diffPolicy = diffPolicy_p; _mergePolicy = mergePolicy_p; } /** * Create the attribute order difference corresponding to the given link * (holder, reference, value). * @param elementMatch_p a non-null match * @param attribute_p a non-null attribute * @param targetValue_p a non-null object * @param referenceValue_p a non-null object */ protected void createAttributeOrderDifference(IMatch elementMatch_p, EAttribute attribute_p, Object targetValue_p, Object referenceValue_p) { createAttributeValueDifference(elementMatch_p, attribute_p, targetValue_p, Role.TARGET, true); createAttributeValueDifference(elementMatch_p, attribute_p, referenceValue_p, Role.REFERENCE, true); } /** * Create the attribute difference corresponding to the given link (holder, * attribute, value) on the given role. * @param elementMatch_p a non-null, non-partial match * @param attribute_p a non-null attribute * @param value_p a non-null value * @param role_p a non-null role which is TARGET or REFERENCE * @param isOrder_p whether the value presence is solely due to ordering * @return a non-null attribute value presence */ protected IAttributeValuePresence createAttributeValueDifference( IMatch elementMatch_p, EAttribute attribute_p, Object value_p, Role role_p, boolean isOrder_p) { IAttributeValuePresence result = getComparison().newAttributeValuePresence( elementMatch_p, attribute_p, value_p, role_p, isOrder_p); IAttributeValuePresence symmetrical = result.getSymmetrical(); if (symmetrical != null) setSymmetricalValuePresenceDependencies(result, symmetrical); if (getComparison().isThreeWay()) setThreeWayProperties(result); return result; } /** * Create differences based on the mapping between the model scopes compared */ protected void createDifferences() { for (IMatch match : getMapping().getContents()) { checkProgress(); if (getDiffPolicy().coverMatch(match)) createTechnicalDifferences(match); getMonitor().worked(1); } } /** * Create the reference order difference corresponding to the given link * (holder, reference, value). * @param elementMatch_p a non-null match * @param reference_p a non-null reference * @param value_p a value element, which is non-null unless valueMatch_p is not null * @param valueMatch_p an optional match */ protected void createReferenceOrderDifference(IMatch elementMatch_p, EReference reference_p, EObject value_p, IMatch valueMatch_p) { createReferenceValueDifference(elementMatch_p, reference_p, value_p, valueMatch_p, Role.TARGET, true); createReferenceValueDifference(elementMatch_p, reference_p, value_p, valueMatch_p, Role.REFERENCE, true); } /** * Create the reference difference corresponding to the given link (holder, * reference, value) on the given role. * @param elementMatch_p a non-null match * @param reference_p a potentially null reference (null stands for root containment) * @param value_p the value element, which may only be null if valueMatch_p is not null * @param valueMatch_p an optional match, which cannot be null if value_p or reference_p is null * @param role_p a non-null role which is TARGET or REFERENCE * @param isOrder_p whether the value presence is solely due to ordering * @return a non-null reference value presence */ protected IReferenceValuePresence createReferenceValueDifference( IMatch elementMatch_p, EReference reference_p, EObject value_p, IMatch valueMatch_p, Role role_p, boolean isOrder_p) { IReferenceValuePresence result = getComparison().newReferenceValuePresence( elementMatch_p, reference_p, value_p, valueMatch_p, role_p, isOrder_p); setReferencedValueDependencies(result); if (getComparison().isThreeWay()) setThreeWayProperties(result); return result; } /** * Create the technical differences corresponding to the given match * @param match_p a non-null match */ protected void createTechnicalDifferences(IMatch match_p) { assert match_p != null; if (match_p.isPartial()) { getOrCreateElementPresence(match_p); } else { detectContentDifferences(match_p, true); } } /** * Detect the differences related to the attributes for the given match * @param match_p a non-null, non-partial match * @param create_p whether differences must actually be created * @return whether at least one difference was detected */ protected boolean detectAllAttributeDifferences(IMatch match_p, boolean create_p) { assert match_p != null && !match_p.isPartial(); EClass eClass = match_p.get(Role.TARGET).eClass(); boolean result = false; for (EAttribute attribute : eClass.getEAllAttributes()) { if (getDiffPolicy().coverFeature(attribute)) result = detectAttributeDifferences(match_p, attribute, create_p) || result; } return result; } /** * Detect the differences related to the non-container references for the * given match * @param match_p a non-null, non-partial match * @param create_p whether differences must actually be created * @return whether at least one difference was detected */ protected boolean detectAllReferenceDifferences(IMatch match_p, boolean create_p) { assert match_p != null && !match_p.isPartial(); EClass eClass = match_p.get(Role.TARGET).eClass(); boolean result = false; for (EReference reference : eClass.getEAllReferences()) { if (!reference.isContainer() && getDiffPolicy().coverFeature(reference)) result = detectReferenceDifferences(match_p, reference, create_p) || result; } return result; } /** * Detect the differences related to the given attribute for the given match * @param match_p a non-null, non-partial match * @param attribute_p a non-null attribute * @param create_p whether differences must actually be created * @return whether at least one difference was detected */ protected boolean detectAttributeDifferences(IMatch match_p, EAttribute attribute_p, boolean create_p) { assert match_p != null && !match_p.isPartial() && attribute_p != null; boolean result = false; IFeaturedModelScope targetScope = getComparison().getScope(Role.TARGET); IFeaturedModelScope referenceScope = getComparison().getScope(Role.REFERENCE); EObject target = match_p.get(Role.TARGET); EObject reference = match_p.get(Role.REFERENCE); List<Object> targetValues = targetScope.get(target, attribute_p); List<Object> referenceValues = referenceScope.get(reference, attribute_p); List<Object> remainingTargetValues = new ArrayList<Object>(targetValues); List<Object> remainingReferenceValues = new ArrayList<Object>(referenceValues); boolean checkOrder = attribute_p.isMany() && getDiffPolicy().considerOrdered(attribute_p); int maxIndex = -1; for (Object targetValue : targetValues) { ObjectAndIndex matchingReferenceValue = findEqualAttributeValue( attribute_p, targetValue, remainingReferenceValues); if (matchingReferenceValue.getObject() != null) { if (checkOrder) { if (matchingReferenceValue.getIndex() < maxIndex) { // Ordering difference if (!create_p) return true; createAttributeOrderDifference( match_p, attribute_p, targetValue, matchingReferenceValue.getObject()); result = true; checkOrder = false; } else { maxIndex = matchingReferenceValue.getIndex(); } } remainingTargetValues.remove(targetValue); remainingReferenceValues.remove(matchingReferenceValue.getObject()); } } for (Object remainingTargetValue : remainingTargetValues) { if (getDiffPolicy().coverValue(remainingTargetValue, attribute_p)){ if (!create_p) return true; createAttributeValueDifference(match_p, attribute_p, remainingTargetValue, Role.TARGET, false); result = true; } } for (Object remainingReferenceValue : remainingReferenceValues) { if (getDiffPolicy().coverValue(remainingReferenceValue, attribute_p)){ if (!create_p) return true; createAttributeValueDifference(match_p, attribute_p, remainingReferenceValue, Role.REFERENCE, false); result = true; } } return result; } /** * Detect technical differences corresponding to the given non-partial * match, focusing on the content of the elements matched * @param match_p a non-null, non-partial match * @param create_p whether differences must actually be created * @return whether at least one difference was detected */ protected boolean detectContentDifferences(IMatch match_p, boolean create_p) { assert match_p != null && !match_p.isPartial(); boolean result = detectAllAttributeDifferences(match_p, create_p); result = detectAllReferenceDifferences(match_p, create_p) || result; result = detectOwnershipDifferences(match_p, create_p) || result; return result; } /** * Detect the differences related to ownership if needed * @param match_p a non-null, non-partial match * @param create_p whether differences must actually be created * @return whether at least one difference was detected */ protected boolean detectOwnershipDifferences(IMatch match_p, boolean create_p) { assert match_p != null && !match_p.isPartial(); boolean result = false; for (Role role : Arrays.asList(Role.TARGET, Role.REFERENCE)) { IMatch parentMatch = getComparison().getContainerOf(match_p, role); // An ownership difference needs only be created if the container // is unmatched, otherwise it is already handled by container refs if (parentMatch != null && parentMatch.isPartial()) { if (!create_p) return true; EObject element = match_p.get(role); // Non-null because match_p is not partial EReference containment = getComparison().getScope(role).getContainment(element); createReferenceValueDifference(parentMatch, containment, element, match_p, role, false); result = true; } } return result; } /** * Detect the differences related to the given reference for the given match * @param match_p a non-null, non-partial match * @param reference_p a non-null, non-container reference * @param create_p whether differences must actually be created * @return whether at least one difference was detected */ protected boolean detectReferenceDifferences(IMatch match_p, EReference reference_p, boolean create_p) { assert match_p != null && !match_p.isPartial() && reference_p != null; assert !reference_p.isContainer(); boolean result = false; IDiffPolicy diffPolicy = getDiffPolicy(); // Get reference values in different roles IFeaturedModelScope targetScope = getComparison().getScope(Role.TARGET); IFeaturedModelScope referenceScope = getComparison().getScope(Role.REFERENCE); EObject targetElement = match_p.get(Role.TARGET); EObject referenceElement = match_p.get(Role.REFERENCE); List<EObject> targetValues = targetScope.get(targetElement, reference_p); List<EObject> referenceValues = referenceScope.get(referenceElement, reference_p); List<EObject> remainingReferenceValues = new FArrayList<EObject>( referenceValues, IEqualityTester.BY_REFERENCE); boolean checkOrder = reference_p.isMany() && diffPolicy.considerOrdered(reference_p); int maxIndex = -1; // Check which ones match for (EObject targetValue : targetValues) { // For every value in TARGET, get its corresponding match if in scope IMatch targetValueMatch = getMapping().getMatchFor(targetValue, Role.TARGET); // The TARGET value is covered if a match is found or it is a covered out-of-scope value boolean outsideTargetScope = targetValueMatch == null; boolean coverTargetValue = !outsideTargetScope && diffPolicy.coverMatch(targetValueMatch) || outsideTargetScope && diffPolicy.coverOutOfScopeValue(targetValue, reference_p); if (coverTargetValue) { // Check if matching value is present in REFERENCE @SuppressWarnings("null") // OK due to the definition of outsideScope EObject matchReferenceValue = outsideTargetScope? targetValue: targetValueMatch.get(Role.REFERENCE); boolean isIsolated = matchReferenceValue == null; int index = -1; if (!isIsolated) { // Check value presence and ordering index = detectReferenceValueAmong(matchReferenceValue, remainingReferenceValues, outsideTargetScope); isIsolated = index < 0; if (checkOrder && !isIsolated) { if (index < maxIndex) { // Ordering difference if (!create_p) return true; createReferenceOrderDifference( match_p, reference_p, targetValue, targetValueMatch); result = true; checkOrder = false; } else { maxIndex = index; } } } if (isIsolated) { // We have a covered unmatched presence on the TARGET side if (!create_p) return true; createReferenceValueDifference( match_p, reference_p, targetValue, targetValueMatch, Role.TARGET, false); result = true; } else { // Remove from the remaining values on the REFERENCE side if (index > -1) remainingReferenceValues.remove(index); else remainingReferenceValues.remove(matchReferenceValue); } } // Else TARGET value is out of scope and not covered as such } // For every remaining value in REFERENCE, create a difference if covered for (EObject remainingReferenceValue : remainingReferenceValues) { IMatch referenceValueMatch = getMapping().getMatchFor( remainingReferenceValue, Role.REFERENCE); boolean outsideReferenceScope = referenceValueMatch == null; boolean coverReferenceValue = !outsideReferenceScope && diffPolicy.coverMatch(referenceValueMatch) || outsideReferenceScope && diffPolicy.coverOutOfScopeValue( remainingReferenceValue, reference_p); if (coverReferenceValue) { // We have a covered unmatched presence on the REFERENCE side if (!create_p) return true; createReferenceValueDifference(match_p, reference_p, remainingReferenceValue, referenceValueMatch, Role.REFERENCE, false); result = true; } // Else REFERENCE value is out of scope and not covered as such } return result; } /** * Return the position of the given reference value among the given list of values, * given that it should or not be considered as an out-of-scope value * @param value_p a non-null element * @param values_p a non-null, potentially empty list * @param outsideScope_p whether the value is out-of-scope * @return a positive int or -1 if the element is not found */ protected int detectReferenceValueAmong(EObject value_p, List<EObject> values_p, boolean outsideScope_p) { int result = values_p.indexOf(value_p); if (result == -1 && outsideScope_p) { // Outside scope: allow absolute detection URI valueURI = EcoreUtil.getURI(value_p); // TODO Factor out, e.g., in diff policy if (valueURI != null) { int i = -1; for (EObject candidateValue : values_p) { i++; URI candidateURI = EcoreUtil.getURI(candidateValue); if (valueURI.equals(candidateURI)) { result = i; break; } } } } return result; } /** * Return a value in the given collection of values which is considered equal * to the given value for the given attribute and its index in the collection * @param attribute_p a non-null attribute * @param value_p a non-null value which is type-compatible with the attribute * @param candidates_p a non-null collection of candidate values * @return a non-null object */ protected ObjectAndIndex findEqualAttributeValue(EAttribute attribute_p, Object value_p, Collection<? extends Object> candidates_p) { int i = 0; for (Object candidate : candidates_p) { if (getDiffPolicy().considerEqual(value_p, candidate, attribute_p)) return new ObjectAndIndex(candidate, i); i++; } return new ObjectAndIndex(); } /** * Return the comparison which is being built * @return a non-null comparison */ public IComparison.Editable getComparison() { return _comparison; } /** * Return the diff policy * @return a non-null diff policy */ protected IDiffPolicy getDiffPolicy() { return _diffPolicy; } /** * Return the merge policy * @return a non-null merge policy */ protected IMergePolicy getMergePolicy() { return _mergePolicy; } /** * Return the mapping of the comparison which is being built */ protected IMapping getMapping() { return getComparison().getMapping(); } /** * @see org.eclipse.emf.diffmerge.util.IExpensiveOperation#getOperationName() */ public String getOperationName() { return Messages.DiffBuilder_Task_Main; } /** * Create and return the element presence difference corresponding to * the given partial match, if allowed. If it already exists, just return it. * @param match_p a non-null, partial match * @return a potentially null element presence */ protected IElementPresence getOrCreateElementPresence(IMatch match_p) { assert match_p != null && match_p.isPartial(); IElementPresence result = match_p.getElementPresenceDifference(); if (result == null && getDiffPolicy().coverMatch(match_p)) { Role presenceRole = match_p.getUncoveredRole().opposite(); IMatch ownerMatch = getComparison().getContainerOf(match_p, presenceRole); result = getComparison().newElementPresence(match_p, ownerMatch); setElementPresenceDependencies(result); if (getComparison().isThreeWay()) setThreeWayProperties(result); } return result; } /** * @see org.eclipse.emf.diffmerge.impl.helpers.AbstractExpensiveOperation#getWorkAmount() */ @Override protected int getWorkAmount() { return 1 + getMapping().size(); } /** * @see org.eclipse.emf.diffmerge.util.IExpensiveOperation#run() */ public IStatus run() { getMonitor().worked(1); createDifferences(); return Status.OK_STATUS; } /** * Set dependencies on the given presence difference related to cyclic moves, if needed * @param presence_p a non-null containment value presence whose value match is non-null * and not partial */ protected void setCyclicOwnershipDependencies(IReferenceValuePresence presence_p) { // Preconditions assert presence_p.getValueMatch() != null; assert presence_p.getValueMatch().isAMove(); //(1) assert presence_p.getFeature().isContainment(); // Normally implied by (1) assert !presence_p.getValueMatch().isPartial(); // Normally implied by (1) // Behavior final Role orderingRole = getComparison().getMapping().getOrderingRole(); final Role oppositeRole = orderingRole.opposite(); if (presence_p.getPresenceRole() == oppositeRole) { final IComparison comparison = getComparison(); final IMatch valueMatch = presence_p.getValueMatch(); IMatch oppositeAncestorMatch = valueMatch; do { // Going up on the non-ordering side oppositeAncestorMatch = comparison.getContainerOf(oppositeAncestorMatch, oppositeRole); if (oppositeAncestorMatch != null && oppositeAncestorMatch.isAMove()) { IReferenceValuePresence cycleEnd = null; IMatch orderingAncestorMatch = oppositeAncestorMatch; do { // Going up on the ordering side orderingAncestorMatch = comparison.getContainerOf(orderingAncestorMatch, orderingRole); if (orderingAncestorMatch == valueMatch) cycleEnd = oppositeAncestorMatch.getOwnershipDifference(orderingRole); } while (orderingAncestorMatch != null && cycleEnd == null); // Setting cycle dependencies if (cycleEnd != null) { // Breaking cycle on the opposite role, where cycleEnd is the ancestor ((IMergeableDifference.Editable)cycleEnd).markRequires(presence_p, oppositeRole); // Breaking cycle on the ordering role, where presence_p is the ancestor ((IMergeableDifference.Editable)presence_p).markRequires(cycleEnd, orderingRole); } } } while (oppositeAncestorMatch != null); } } /** * Set dependencies between differences of type element presence exclusively * @param presence_p a non-null element presence */ protected void setElementPresenceDependencies(IElementPresence presence_p) { Role presenceRole = presence_p.getPresenceRole(); if (!presence_p.isRoot()) { IMatch ownerMatch = presence_p.getOwnerMatch(); if (getMergePolicy().bindPresenceToOwnership(_comparison.getScope(presenceRole.opposite())) && ownerMatch != null && ownerMatch.isPartial()) { IElementPresence ownerPresence = getOrCreateElementPresence(ownerMatch); if (ownerPresence != null) { // Child addition requires container addition ((IMergeableDifference.Editable)presence_p).markRequires( ownerPresence, presenceRole.opposite()); // Container deletion requires child deletion ((IMergeableDifference.Editable)ownerPresence).markRequires( presence_p, presenceRole); } } } // Grouped addition according to policy Collection<EObject> additionPeers = getMergePolicy().getAdditionGroup( presence_p.getElement(), getComparison().getScope(presenceRole)); for (EObject peer : additionPeers) { IMatch peerMatch = getMapping().getMatchFor(peer, presenceRole); if (peerMatch != null && peerMatch.isPartial()) { IElementPresence peerPresence = getOrCreateElementPresence(peerMatch); if (peerPresence != null) { // Element addition requires group member addition ((IMergeableDifference.Editable)presence_p).markRequires( peerPresence, presenceRole.opposite()); // Group member deletion requires element deletion ((IMergeableDifference.Editable)peerPresence).markRequires( presence_p, presenceRole); } } } // Grouped deletion according to policy Collection<EObject> deletionPeers = getMergePolicy().getDeletionGroup( presence_p.getElement(), getComparison().getScope(presenceRole)); for (EObject peer : deletionPeers) { IMatch peerMatch = getMapping().getMatchFor(peer, presenceRole); if (peerMatch != null && peerMatch.isPartial()) { IElementPresence peerPresence = getOrCreateElementPresence(peerMatch); if (peerPresence != null) { // Element deletion requires group member deletion ((IMergeableDifference.Editable)presence_p).markRequires( peerPresence, presenceRole); } } } } /** * Set dependencies between opposite differences of type reference value presence, * that is, reference differences on the same link * @param first_p a non-null reference value presence * @param second_p a non-null reference value presence which is opposite to first_p */ protected void setOppositeReferenceDependencies( IReferenceValuePresence first_p, IReferenceValuePresence second_p) { assert first_p.isOppositeOf(second_p); IMergeableDifference.Editable first = (IMergeableDifference.Editable)first_p; IMergeableDifference.Editable second = (IMergeableDifference.Editable)second_p; // Opposite diffs are implicitly equivalent except for non-many references // which bring their own constraints and must therefore be merged explicitly Role presenceRole = first_p.getPresenceRole(); if (second_p.getFeature().isMany()) { first.markImplies(second_p, presenceRole); first.markImplies(second_p, presenceRole.opposite()); } else { first.markRequires(second_p, presenceRole); first.markRequires(second_p, presenceRole.opposite()); } if (first_p.getFeature().isMany()) { second.markImplies(first_p, presenceRole); second.markImplies(first_p, presenceRole.opposite()); } else { second.markRequires(first_p, presenceRole); second.markRequires(first_p, presenceRole.opposite()); } } /** * Set the dependencies of an ownership, excluding those of classical * reference value presences * @param presence_p a non-null containment value presence */ protected void setOwnerhipDependencies(IReferenceValuePresence presence_p) { assert presence_p.getFeature() != null && presence_p.getFeature().isContainment(); IMatch valueMatch = presence_p.getValueMatch(); // Handling dependencies: move of value if (valueMatch == null || !valueMatch.isPartial()) { // Implicit global !isMany() to containers which are implicitly symmetric IReferenceValuePresence symmetricalOwnership = presence_p.getSymmetricalOwnership(); if (symmetricalOwnership != null) setSymmetricalOwnershipDependencies(presence_p, symmetricalOwnership); // "Cyclic" moves if (valueMatch != null) setCyclicOwnershipDependencies(presence_p); } } /** * Set dependencies between a reference value presence and the presence of the value * @param referenceDiff_p a non-null reference presence to a partial match */ protected void setPartialReferencedValueDependencies( IReferenceValuePresence referenceDiff_p) { assert referenceDiff_p.getValueMatch() != null; assert referenceDiff_p.getValueMatch().isPartial(); IElementPresence presence = getOrCreateElementPresence( referenceDiff_p.getValueMatch()); if (presence != null) { Role presenceRole = referenceDiff_p.getPresenceRole(); IMergeableDifference.Editable referenceDiff = (IMergeableDifference.Editable)referenceDiff_p; // Ref requires value presence, value absence requires no ref referenceDiff.markRequires(presence, presenceRole.opposite()); ((IMergeableDifference.Editable)presence).markRequires( referenceDiff_p, presenceRole); if (referenceDiff_p.getFeature() != null) { // If containment and presence requires ownership, value presence implies ref // and no ref implies value absence if (referenceDiff_p.getFeature().isContainment() && getMergePolicy().bindPresenceToOwnership( _comparison.getScope(presenceRole.opposite()))) { ((IMergeableDifference.Editable)presence).markImplies( referenceDiff_p, presenceRole.opposite()); referenceDiff.markImplies(presence, presenceRole); } else { // Not a containment or no ownership/presence coupling EReference opposite = referenceDiff_p.getFeature().getEOpposite(); // If reference has an eOpposite which is mandatory for addition, then ... if (opposite != null && getMergePolicy().isMandatoryForAddition(opposite)) { // ... value presence requires ref ((IMergeableDifference.Editable)presence).markRequires( referenceDiff_p, presenceRole.opposite()); // ... and no ref requires value absence referenceDiff.markRequires(presence, presenceRole); } } } } } /** * Set dependencies between a reference value presence and the presence of the holder * @param referenceDiff_p a non-null reference presence to a partial match */ protected void setPartialReferencingElementDependencies( IReferenceValuePresence referenceDiff_p) { assert referenceDiff_p.getElementMatch().isPartial(); IElementPresence presence = getOrCreateElementPresence( referenceDiff_p.getElementMatch()); if (presence != null) { Role presenceRole = referenceDiff_p.getPresenceRole(); // Ref requires element presence, element absence requires no ref ((IMergeableDifference.Editable)referenceDiff_p).markRequires( presence, presenceRole.opposite()); ((IMergeableDifference.Editable)presence).markRequires( referenceDiff_p, presenceRole); } } /** * Set the dependencies of a reference value presence * @param presence_p a non-null reference value presence */ protected void setReferencedValueDependencies(IReferenceValuePresence presence_p) { EReference reference = presence_p.getFeature(); IMatch valueMatch = presence_p.getValueMatch(); // Handling dependencies: links (non-container eOpposite) if (!reference.isContainment()) { IReferenceValuePresence oppositeDiff = presence_p.getOpposite(); if (oppositeDiff != null) setOppositeReferenceDependencies(presence_p, oppositeDiff); } // Handling dependencies: symmetry (!isMany(), ordering) IReferenceValuePresence symmetrical = presence_p.getSymmetrical(); if (symmetrical != null) setSymmetricalValuePresenceDependencies(presence_p, symmetrical); // Handling dependencies: presence of element if (presence_p.getElementMatch().isPartial()) setPartialReferencingElementDependencies(presence_p); // Handling dependencies: presence of value if (valueMatch != null && valueMatch.isPartial()) setPartialReferencedValueDependencies(presence_p); // Handling dependencies: ownership if (reference.isContainment()) setOwnerhipDependencies(presence_p); } /** * Set dependencies between symmetrical ownership differences * @see IReferenceValuePresence#isSymmetricalOwnershipTo(IReferenceValuePresence) * @param first_p a non-null reference value presence * @param second_p a non-null reference value presence which is the * symmetrical ownership to first_p */ protected void setSymmetricalOwnershipDependencies( IReferenceValuePresence first_p, IReferenceValuePresence second_p) { assert first_p.isSymmetricalOwnershipTo(second_p); IMergeableDifference.Editable first = (IMergeableDifference.Editable)first_p; IMergeableDifference.Editable second = (IMergeableDifference.Editable)second_p; // Symmetrical ownership presence is implicit on addition... first.markImplies(second_p, second_p.getPresenceRole()); second.markImplies(first_p, first_p.getPresenceRole()); // ... and explicit on removal first.markRequires(second_p, first_p.getPresenceRole()); second.markRequires(first_p, second_p.getPresenceRole()); } /** * Set dependencies between symmetrical differences of type value presence * @see IValuePresence#isSymmetricalTo(IValuePresence) * @param first_p a non-null value presence * @param second_p a non-null value presence which is symmetrical to first_p */ protected void setSymmetricalValuePresenceDependencies( IValuePresence first_p, IValuePresence second_p) { assert first_p.isSymmetricalTo(second_p); IMergeableDifference.Editable first = (IMergeableDifference.Editable)first_p; IMergeableDifference.Editable second = (IMergeableDifference.Editable)second_p; // Symmetrical diffs are implicitly dependent on addition first.markImplies(second_p, second_p.getPresenceRole()); second.markImplies(first_p, first_p.getPresenceRole()); // Symmetrical diffs are explicitly dependent on removal first.markRequires(second_p, first_p.getPresenceRole()); second.markRequires(first_p, second_p.getPresenceRole()); } /** * Set the properties which are specific to three-way comparisons to the given difference. * Precondition: getComparison().isThreeWay() * @param presence_p a non-null element presence */ protected void setThreeWayProperties(IElementPresence presence_p) { EObject ancestorElement = presence_p.getElementMatch().get(Role.ANCESTOR); if (ancestorElement != null) { // Ancestor element present: try and detect differences between ANCESTOR and TARGET/REFERENCE // by assigning the TARGET role to the ancestor element locally EObject sideElement = presence_p.getElement(); IMatch tempMach = getComparison().newMatch(ancestorElement, sideElement, null); boolean diffWithAncestor = detectContentDifferences(tempMach, false); if (diffWithAncestor) ((IDifference.Editable)presence_p).markAsConflicting(); // Deleted and changed in parallel } else { // Ancestor not present: presence is not aligned with ancestor ((IDifference.Editable)presence_p).markAsDifferentFromAncestor(); } } /** * Set the properties which are specific to three-way comparisons to the given * difference. * Precondition: getComparison().isThreeWay() * @param presence_p a non-null attribute value presence */ protected void setThreeWayProperties(IAttributeValuePresence presence_p) { EObject ancestorHolder = presence_p.getElementMatch().get(Role.ANCESTOR); boolean aligned; if (ancestorHolder == null) { aligned = false; } else { EAttribute attribute = presence_p.getFeature(); IFeaturedModelScope ancestorScope = _comparison.getScope(Role.ANCESTOR); assert ancestorScope != null; // Thanks to call context List<Object> valuesInAncestor = ancestorScope.get(ancestorHolder, attribute); if (presence_p.isOrder()) { Role presenceRole = presence_p.getPresenceRole(); List<Object> values = _comparison.getScope(presenceRole).get( presence_p.getElementMatch().get(presenceRole), presence_p.getFeature()); int maxIndex = -1; aligned = true; for (Object value : values) { ObjectAndIndex matchingAncestorValue = findEqualAttributeValue( attribute, value, valuesInAncestor); if (matchingAncestorValue.getObject() != null) { if (matchingAncestorValue.getIndex() < maxIndex) { // Ordering difference aligned = false; break; } maxIndex = matchingAncestorValue.getIndex(); } } } else { // Not an order ObjectAndIndex equalInAncestor = findEqualAttributeValue( attribute, presence_p.getValue(), valuesInAncestor); aligned = equalInAncestor.getObject() != null; } } if (!aligned) { // Not aligned with ancestor IAttributeValuePresence symmetrical = presence_p.getSymmetrical(); if (symmetrical != null && !symmetrical.isAlignedWithAncestor()) { // Symmetrical is not aligned either: mark both as conflicting ((IDifference.Editable)presence_p).markAsConflicting(); ((IDifference.Editable)symmetrical).markAsConflicting(); } else { // No symmetrical or symmetrical aligned: just mark diff as not aligned ((IDifference.Editable)presence_p).markAsDifferentFromAncestor(); } } } /** * Set the properties which are specific to three-way comparisons to the given difference. * Precondition: getComparison().isThreeWay() * @param presence_p a non-null reference value presence */ protected void setThreeWayProperties(IReferenceValuePresence presence_p) { EObject ancestorHolder = presence_p.getElementMatch().get(Role.ANCESTOR); boolean aligned; // Aligned with ancestor? if (ancestorHolder == null) { aligned = false; } else { IMatch valueMatch = presence_p.getValueMatch(); EObject ancestorValue = valueMatch == null? null: valueMatch.get(Role.ANCESTOR); // May be null IFeaturedModelScope ancestorScope = _comparison.getScope(Role.ANCESTOR); assert ancestorScope != null; // Thanks to call context List<EObject> ancestorValues = new FArrayList<EObject>( ancestorScope.get(ancestorHolder, presence_p.getFeature()), IEqualityTester.BY_REFERENCE); if (presence_p.isOrder()) { // Order Role presenceRole = presence_p.getPresenceRole(); List<EObject> values = _comparison.getScope(presenceRole).get( presence_p.getElementMatch().get(presenceRole), presence_p.getFeature()); int maxIndex = -1; aligned = true; for (EObject value : values) { IMatch currentValueMatch = getMapping().getMatchFor(value, presenceRole); if (currentValueMatch != null) { EObject matchAncestor = currentValueMatch.get(Role.ANCESTOR); //TODO handle ancestor out-of-scope value if (matchAncestor != null) { int index = detectReferenceValueAmong(matchAncestor, ancestorValues, false); if (index >= 0) { if (index < maxIndex) { // Ordering difference aligned = false; break; } maxIndex = index; } } } } } else { // Not an order aligned = ancestorValues.contains(ancestorValue); } } if (!aligned) { // Not aligned with ancestor IReferenceValuePresence symmetrical = presence_p.getSymmetrical(); if (symmetrical != null && !symmetrical.isAlignedWithAncestor()) { // Symmetrical is not aligned either: mark both as conflicting ((IDifference.Editable)presence_p).markAsConflicting(); ((IDifference.Editable)symmetrical).markAsConflicting(); } else { // No symmetrical or symmetrical aligned: just mark diff as not aligned ((IDifference.Editable)presence_p).markAsDifferentFromAncestor(); } } } /** * A trivial data structure that associates an object and an index. * Either the object is not null and the index is greater than or equal to 0, * or the structure is (null, -1). */ protected static class ObjectAndIndex { /** The potentially null object */ private Object _object; /** The index ranging from -1 to max int, inclusive */ private int _index; /** * Full constructor * @param object_p a non-null object * @param index_p a positive int or 0 */ public ObjectAndIndex(Object object_p, int index_p) { assert object_p != null && index_p >= 0; _object = object_p; _index = index_p; } /** * Constructor for no object and -1 as index */ public ObjectAndIndex() { _object = null; _index = -1; } /** * @see java.lang.Object#equals(java.lang.Object) */ @Override public boolean equals(Object peer_p) { boolean result = false; if (peer_p instanceof ObjectAndIndex) { ObjectAndIndex peer = (ObjectAndIndex)peer_p; result = _object == null && peer.getObject() == null || _object != null && _object.equals(peer.getObject()); result = result && _index == peer.getIndex(); } return result; } /** * Return the object * @return a potentially null object */ public Object getObject() { return _object; } /** * Return the index * @return an int ranging from -1 to max int, inclusive */ public int getIndex() { return _index; } /** * @see java.lang.Object#hashCode() */ @Override public int hashCode() { return (_object != null? _object.hashCode(): 0) + Integer.valueOf(_index).hashCode(); } } }