/*******************************************************************************
* Copyright (c) 2006, 2011 Obeo and others.
* 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
* Martin Taal - [299641] Compare arrays by their content instead of instance equality - Note : moved to DiffCollectionsHelper
* Victor Roldan Betancort - [352002] introduce IMatchManager
*******************************************************************************/
package org.eclipse.emf.compare.diff.engine.check;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.eclipse.emf.compare.FactoryException;
import org.eclipse.emf.compare.diff.engine.IMatchManager;
import org.eclipse.emf.compare.diff.metamodel.AttributeChangeLeftTarget;
import org.eclipse.emf.compare.diff.metamodel.AttributeChangeRightTarget;
import org.eclipse.emf.compare.diff.metamodel.AttributeOrderChange;
import org.eclipse.emf.compare.diff.metamodel.ConflictingDiffElement;
import org.eclipse.emf.compare.diff.metamodel.DiffElement;
import org.eclipse.emf.compare.diff.metamodel.DiffFactory;
import org.eclipse.emf.compare.diff.metamodel.DiffGroup;
import org.eclipse.emf.compare.diff.metamodel.UpdateAttribute;
import org.eclipse.emf.compare.match.metamodel.Match2Elements;
import org.eclipse.emf.compare.match.metamodel.Match3Elements;
import org.eclipse.emf.compare.util.EFactory;
import org.eclipse.emf.ecore.EAttribute;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.util.EcoreUtil;
/**
* This will implement the attribute checks : order of attribute values, changes between two versions, ...
*
* @author Laurent Goubet laurent.goubet@obeo.fr
* @since 1.0
*/
public class AttributesCheck extends AbstractCheck {
/**
* Simply delegates to the super constructor.
*
* @param referencer
* CrossReferencer instantiated with the match model or match resource set.
* @see {@link AbstractCheck#DefaultCheck(org.eclipse.emf.ecore.util.EcoreUtil.CrossReferencer)}
* @deprecated The CrossReferencer mechanism is now hidden behind the {@link IMatchManager} interface.
*/
@Deprecated
public AttributesCheck(EcoreUtil.CrossReferencer referencer) {
super(referencer);
}
/**
* Simply delegates to the super constructor.
*
* @see IMatchManager
* @param manager
* the IMatchManager instance to determine matches for certain EObject
* @since 1.3
*/
public AttributesCheck(IMatchManager manager) {
super(manager);
}
/**
* This will iterate through all the attributes of the mapping
's two elements to check if any
* of them has been modified.
*
* @param root
* {@link DiffGroup root} of the {@link DiffElement} to create if one of the attributes has
* actually been changed.
* @param mapping
* This contains the mapping information about the elements we need to check.
* @throws FactoryException
* Thrown if one of the checks fails.
*/
public void checkAttributesUpdates(DiffGroup root, Match2Elements mapping) throws FactoryException {
final EClass eClass = mapping.getLeftElement().eClass();
final List eclassAttributes = eClass.getEAllAttributes();
// for each feature, compare the value
final Iterator it = eclassAttributes.iterator();
while (it.hasNext()) {
final EAttribute next = it.next();
if (!shouldBeIgnored(next)) {
checkAttributeUpdates(root, mapping, next);
}
}
}
/**
* This will iterate through all the attributes of the mapping
's three elements to check if
* any of them has been modified.
*
* @param root
* {@link DiffGroup root} of the {@link DiffElement} to create if one of the attribute has
* actually been changed.
* @param mapping
* This contains the mapping information about the elements we need to check for a move.
* @throws FactoryException
* Thrown if one of the checks fails.
*/
public void checkAttributesUpdates(DiffGroup root, Match3Elements mapping) throws FactoryException {
// Ignores matchElements when they don't have origin (no updates on
// these)
if (mapping.getOriginElement() == null)
return;
final EClass eClass = mapping.getOriginElement().eClass();
final List eclassAttributes = eClass.getEAllAttributes();
// for each feature, compare the value
final Iterator it = eclassAttributes.iterator();
while (it.hasNext()) {
final EAttribute next = it.next();
if (!shouldBeIgnored(next)) {
checkAttributeUpdates(root, mapping, next);
}
}
}
/**
* This will be used internaly to check that an attribute's values have changed from one version to the
* other.
*
* Specifically, this will check for :
*
* Enumeration literals : if they have the same literal and integer values.
* Feature Map Entries : if their respective values have been matched.
* Arrays : compare the content of the two arrays.
* Other : if the left value is equal to the right value or both are null
.
*
*
*
* @param left
* The value of the attribute from the left compare resource.
* @param right
* The value of the attribute from the right compare resource.
* @return true
if the left
value is distinct from the right
value.
*/
protected boolean areDistinctValues(Object left, Object right) {
return matcherHelper.areDistinctValues(left, right);
}
/**
* This will check that the values of the given attribute from the objects contained by mapping has been
* modified.
*
* @param root
* {@link DiffGroup root} of the {@link DiffElement} to create if one of the attributes has
* actually been changed.
* @param mapping
* This contains the mapping information about the elements we need to check.
* @param attribute
* The attribute we need to check for differences.
* @throws FactoryException
* Thrown if one of the checks fails.
* @since 1.0
*/
protected void checkAttributeUpdates(DiffGroup root, Match2Elements mapping, EAttribute attribute)
throws FactoryException {
final String attributeName = attribute.getName();
boolean distinct = false;
if (attribute.isMany()) {
final List leftValue = convertFeatureMapList(EFactory.eGetAsList(
mapping.getLeftElement(), attributeName));
final List rightValue = convertFeatureMapList(EFactory.eGetAsList(
mapping.getRightElement(), attributeName));
if (leftValue.size() != rightValue.size()) {
distinct = true;
} else {
for (int i = 0; !distinct && i < leftValue.size(); i++) {
distinct = areDistinctValues(leftValue.get(i), rightValue.get(i));
}
}
} else {
final Object leftValue = EFactory.eGet(mapping.getLeftElement(), attributeName);
final Object rightValue = EFactory.eGet(mapping.getRightElement(), attributeName);
distinct = areDistinctValues(leftValue, rightValue);
}
if (distinct) {
createNonConflictingAttributeChange(root, attribute, mapping.getLeftElement(),
mapping.getRightElement());
}
}
/**
* This will check that the values of the given attribute from the objects contained by mapping has been
* modified.
*
* @param root
* {@link DiffGroup root} of the {@link DiffElement} to create if one of the attributes has
* actually been changed.
* @param mapping
* This contains the mapping information about the elements we need to check.
* @param attribute
* The attribute we need to check for differences.
* @throws FactoryException
* Thrown if one of the checks fails.
* @since 1.0
*/
protected void checkAttributeUpdates(DiffGroup root, Match3Elements mapping, EAttribute attribute)
throws FactoryException {
final String attributeName = attribute.getName();
boolean rightDistinctFromOrigin = false;
boolean rightDistinctFromLeft = false;
boolean leftDistinctFromOrigin = false;
if (attribute.isMany()) {
final List leftValue = convertFeatureMapList(EFactory.eGetAsList(
mapping.getLeftElement(), attributeName));
final List rightValue = convertFeatureMapList(EFactory.eGetAsList(
mapping.getRightElement(), attributeName));
final List ancestorValue = convertFeatureMapList(EFactory.eGetAsList(
mapping.getOriginElement(), attributeName));
if (rightValue.size() != ancestorValue.size()) {
rightDistinctFromOrigin = true;
} else {
for (int i = 0; !rightDistinctFromOrigin && i < rightValue.size(); i++) {
rightDistinctFromOrigin = areDistinctValues(rightValue.get(i), ancestorValue.get(i));
}
}
if (leftValue.size() != ancestorValue.size()) {
leftDistinctFromOrigin = true;
} else {
for (int i = 0; !leftDistinctFromOrigin && i < leftValue.size(); i++) {
leftDistinctFromOrigin = areDistinctValues(leftValue.get(i), ancestorValue.get(i));
}
}
if (leftValue.size() != rightValue.size()) {
rightDistinctFromLeft = true;
} else {
for (int i = 0; !rightDistinctFromLeft && i < leftValue.size(); i++) {
rightDistinctFromLeft = areDistinctValues(leftValue.get(i), rightValue.get(i));
}
}
} else {
final Object leftValue = EFactory.eGet(mapping.getLeftElement(), attributeName);
final Object rightValue = EFactory.eGet(mapping.getRightElement(), attributeName);
final Object ancestorValue = EFactory.eGet(mapping.getOriginElement(), attributeName);
rightDistinctFromOrigin = areDistinctValues(rightValue, ancestorValue);
rightDistinctFromLeft = areDistinctValues(rightValue, leftValue);
leftDistinctFromOrigin = areDistinctValues(leftValue, ancestorValue);
}
// non conflicting change
if (leftDistinctFromOrigin && !rightDistinctFromOrigin) {
createNonConflictingAttributeChange(root, attribute, mapping.getLeftElement(),
mapping.getRightElement());
// only latest from head has changed
} else if (rightDistinctFromOrigin && !leftDistinctFromOrigin) {
createRemoteAttributeChange(root, attribute, mapping);
// conflicting
} else if (rightDistinctFromOrigin && leftDistinctFromOrigin && rightDistinctFromLeft) {
checkConflictingAttributesUpdate(root, attribute, mapping);
}
}
/**
* Determines if we should ignore an attribute for diff detection.
*
* Default is to ignore attributes marked either
*
*
*
* Clients should override this if they wish to ignore other attributes.
*
*
* @param attribute
* Attribute to determine whether it should be ignored.
* @return True
if attribute has to be ignored, False
otherwise.
*/
protected boolean shouldBeIgnored(EAttribute attribute) {
boolean ignore = attribute.isTransient();
ignore = ignore || attribute.isDerived();
return ignore;
}
/**
* Checks if there are conflictual changes between the values of the given {@link EAttribute}.
*
* An attribute update is considered "conflictual" if it isn't multi-valued and its left value
* differs from the right value.
*
*
* @param root
* {@link DiffGroup root} of the {@link DiffElement} to create if there actually are
* conflictual changes in the mapped elements attribute
values.
* @param attribute
* Target {@link EAttribute} to check.
* @param mapping
* Contains the three (ancestor, left, right) elements' mapping.
* @throws FactoryException
* Thrown if we cannot fetch attribute
's values for either one of the mapped
* elements.
*/
private void checkConflictingAttributesUpdate(DiffGroup root, EAttribute attribute, Match3Elements mapping)
throws FactoryException {
if (!attribute.isMany()) {
createConflictingAttributeChange(root, attribute, mapping);
} else {
final List remoteDeletedValues = new ArrayList();
final List remoteAddedValues = new ArrayList();
final List deletedValues = new ArrayList();
final List addedValues = new ArrayList();
populateThreeWayAttributeChanges(mapping, attribute, addedValues, deletedValues,
remoteAddedValues, remoteDeletedValues);
createRemoteAttributeDiffs(root, attribute, mapping.getLeftElement(), mapping.getRightElement(),
remoteAddedValues, remoteDeletedValues);
createLocalAttributeDiffs(root, attribute, mapping.getLeftElement(), mapping.getRightElement(),
addedValues, deletedValues);
}
}
/**
* Creates "local" Attribute diffs according to the given information.
*
* @param root
* {@link DiffGroup root} of the {@link DiffElement} to create.
* @param attribute
* Attribute which value has been changed.
* @param leftElement
* Left element of the attribute change.
* @param rightElement
* Right element of the attribute change.
* @param addedValues
* Values that have been added to the left element.
* @param deletedValues
* Values that have been deleted from the left element.
* @throws FactoryException
* Thrown if we cannot compute the ordering diffs for this attribute.
*/
private void createLocalAttributeDiffs(DiffGroup root, EAttribute attribute, EObject leftElement,
EObject rightElement, List addedValues, List deletedValues)
throws FactoryException {
final List addedValuesDiffs = new ArrayList(
addedValues.size());
final List deletedValuesDiffs = new ArrayList(
deletedValues.size());
// ADD Attribute values
for (final Object aValue : addedValues) {
final AttributeChangeLeftTarget operation = DiffFactory.eINSTANCE
.createAttributeChangeLeftTarget();
operation.setAttribute(attribute);
operation.setRightElement(rightElement);
operation.setLeftElement(leftElement);
operation.setLeftTarget(aValue);
root.getSubDiffElements().add(operation);
addedValuesDiffs.add(operation);
}
// REMOVE Attribute values
for (final Object aValue : deletedValues) {
final AttributeChangeRightTarget operation = DiffFactory.eINSTANCE
.createAttributeChangeRightTarget();
operation.setAttribute(attribute);
operation.setRightElement(rightElement);
operation.setLeftElement(leftElement);
operation.setRightTarget(aValue);
root.getSubDiffElements().add(operation);
deletedValuesDiffs.add(operation);
}
// ORDER CHANGE
if (attribute.isOrdered()) {
checkAttributeOrderChange(root, attribute, leftElement, rightElement, addedValuesDiffs,
deletedValuesDiffs);
}
}
/**
* Creates "remote" Attribute diffs according to the given information.
*
* @param root
* {@link DiffGroup root} of the {@link DiffElement} to create.
* @param attribute
* Attribute which value has been changed.
* @param leftElement
* Left element of the attribute change.
* @param rightElement
* Right element of the attribute change.
* @param remoteDeletedValues
* Values that have been deleted from the remote (right) element.
* @param remoteAddedValues
* Values that have been added to the remote (right) element.
* @throws FactoryException
* Thrown if we cannot compute the ordering diffs for this attribute.
*/
private void createRemoteAttributeDiffs(DiffGroup root, EAttribute attribute, EObject leftElement,
EObject rightElement, List remoteDeletedValues, List remoteAddedValues)
throws FactoryException {
final List remoteDeletedValuesDiffs = new ArrayList(
remoteDeletedValues.size());
final List remoteAddedValuesDiffs = new ArrayList(
remoteAddedValues.size());
// REMOTE REMOVE Attribute values
for (final Object aValue : remoteDeletedValues) {
final AttributeChangeLeftTarget operation = DiffFactory.eINSTANCE
.createAttributeChangeLeftTarget();
operation.setAttribute(attribute);
operation.setRightElement(rightElement);
operation.setLeftElement(leftElement);
operation.setLeftTarget(aValue);
operation.setRemote(true);
root.getSubDiffElements().add(operation);
remoteDeletedValuesDiffs.add(operation);
}
// REMOTE ADD Attribute values
for (final Object aValue : remoteAddedValues) {
final AttributeChangeRightTarget operation = DiffFactory.eINSTANCE
.createAttributeChangeRightTarget();
operation.setAttribute(attribute);
operation.setRightElement(rightElement);
operation.setLeftElement(leftElement);
operation.setRightTarget(aValue);
operation.setRemote(true);
root.getSubDiffElements().add(operation);
remoteAddedValuesDiffs.add(operation);
}
// REMOTE ORDER CHANGE
if (attribute.isOrdered()) {
checkAttributeRemoteOrderChange(root, attribute, leftElement, rightElement,
remoteDeletedValuesDiffs, remoteAddedValuesDiffs);
}
}
/**
* Checks a given {@link EAttribute attribute} for changes related to a given mapping
and
* populates the given {@link List}s with the attribute values belonging to them.
*
*
* "Added" values are the values that have been added in the left element since the origin
* and that haven't been added in the right element.
* "Deleted" values are the values that have been removed from the left element since the
* origin but are still present in the right element.
* "Remotely added" values are the values that have been added in the right element since
* the origin but haven't been added in the left element.
* "Remotely deleted" values are the values that have been removed from the right element
* since the origin but are still present in the left element.
*
*
*
* @param mapping
* Contains informations about the left, right and origin elements.
* @param attribute
* {@link EAttribute} we're checking for changes.
* @param addedValues
* {@link List} that will be populated with the values that have been added in the left element
* since the origin.
* @param deletedValues
* {@link List} that will be populated with the values that have been removed from the left
* element since the origin.
* @param remoteAddedValues
* {@link List} that will be populated with the values that have been added in the right
* element since the origin.
* @param remoteDeletedValues
* {@link List} that will be populated with the values that have been removed from the right
* element since the origin.
* @throws FactoryException
* Thrown if we cannot fetch the attribute's values in either the left, right or origin
* element.
*/
private void populateThreeWayAttributeChanges(Match3Elements mapping, EAttribute attribute,
List addedValues, List deletedValues, List remoteAddedValues,
List remoteDeletedValues) throws FactoryException {
final String attributeName = attribute.getName();
final List leftValues = convertFeatureMapList(EFactory.eGetAsList(mapping.getLeftElement(),
attributeName));
final List rightValues = convertFeatureMapList(EFactory.eGetAsList(mapping.getRightElement(),
attributeName));
final List ancestorValues = convertFeatureMapList(EFactory.eGetAsList(
mapping.getOriginElement(), attributeName));
// populates remotely added and locally deleted lists
final List leftCopy = new ArrayList(leftValues);
List ancestorCopy = new ArrayList(ancestorValues);
for (Object right : rightValues) {
Object leftMatched = null;
final Iterator leftIterator = leftCopy.iterator();
while (leftMatched == null && leftIterator.hasNext()) {
final Object next = leftIterator.next();
if (!areDistinctValues(right, next)) {
leftMatched = next;
}
}
Object ancestorMatched = null;
final Iterator ancestorIterator = ancestorCopy.iterator();
while (ancestorMatched == null && ancestorIterator.hasNext()) {
final Object next = ancestorIterator.next();
if (!areDistinctValues(right, next)) {
ancestorMatched = next;
}
}
if (leftMatched == null && ancestorMatched == null) {
remoteAddedValues.add(right);
} else if (leftMatched == null) {
deletedValues.add(right);
}
if (leftMatched != null) {
leftCopy.remove(leftMatched);
}
if (ancestorMatched != null) {
ancestorCopy.remove(ancestorMatched);
}
}
// populates remotely deleted and locally added lists
final List rightCopy = new ArrayList(rightValues);
ancestorCopy = new ArrayList(ancestorValues);
for (Object left : leftValues) {
Object rightMatched = null;
final Iterator rightIterator = rightCopy.iterator();
while (rightMatched == null && rightIterator.hasNext()) {
final Object next = rightIterator.next();
if (!areDistinctValues(left, next)) {
rightMatched = next;
}
}
Object ancestorMatched = null;
final Iterator ancestorIterator = ancestorCopy.iterator();
while (ancestorMatched == null && ancestorIterator.hasNext()) {
final Object next = ancestorIterator.next();
if (!areDistinctValues(left, next)) {
ancestorMatched = next;
}
}
if (rightMatched == null && ancestorMatched == null) {
addedValues.add(left);
} else if (rightMatched == null) {
remoteDeletedValues.add(left);
}
if (rightMatched != null) {
rightCopy.remove(rightMatched);
}
if (ancestorMatched != null) {
ancestorCopy.remove(ancestorMatched);
}
}
}
/**
* This will create the {@link ConflictingDiffGroup} and its children for a conflictual AttributeChange.
*
* @param root
* {@link DiffGroup root} of the {@link DiffElement} to create.
* @param attribute
* Attribute which has been changed to conflictual values.
* @param mapping
* Contains informations about the left, right and origin element.
* @throws FactoryException
* Thrown if we cannot create the {@link ConflictingDiffGroup}'s children.
*/
private void createConflictingAttributeChange(DiffGroup root, EAttribute attribute, Match3Elements mapping)
throws FactoryException {
// We'll use this diffGroup to make use of #createNonConflictingAttributeChange(DiffGroup, EAttribute,
// EObject, EObject)
final DiffGroup dummyGroup = DiffFactory.eINSTANCE.createDiffGroup();
createNonConflictingAttributeChange(dummyGroup, attribute, mapping.getLeftElement(),
mapping.getRightElement());
if (dummyGroup.getSubDiffElements().size() > 0) {
final ConflictingDiffElement conflictingDiff = DiffFactory.eINSTANCE
.createConflictingDiffElement();
conflictingDiff.setLeftParent(mapping.getLeftElement());
conflictingDiff.setRightParent(mapping.getRightElement());
conflictingDiff.setOriginElement(mapping.getOriginElement());
for (final DiffElement subDiff : new ArrayList(dummyGroup.getSubDiffElements())) {
conflictingDiff.getSubDiffElements().add(subDiff);
}
root.getSubDiffElements().add(conflictingDiff);
}
}
/**
* Creates and add the {@link DiffGroup} corresponding to an AttributeChange operation to the given
* {@link DiffGroup root}.
*
* @param root
* {@link DiffGroup root} of the {@link DiffElement} to create.
* @param attribute
* Attribute which value has been changed.
* @param leftElement
* Left element of the attribute change.
* @param rightElement
* Right element of the attribute change.
* @throws FactoryException
* Thrown if we cannot fetch the attribute's value for either one of the elements.
*/
private void createNonConflictingAttributeChange(DiffGroup root, EAttribute attribute,
EObject leftElement, EObject rightElement) throws FactoryException {
if (attribute.isMany()) {
final List rightValues = convertFeatureMapList(EFactory.eGetAsList(rightElement,
attribute.getName()));
final List leftValues = convertFeatureMapList(EFactory.eGetAsList(leftElement,
attribute.getName()));
final List addedValues = computeAddedValues(leftValues, rightValues);
final List deletedValues = computeDeletedValues(leftValues, rightValues);
createLocalAttributeDiffs(root, attribute, leftElement, rightElement, addedValues, deletedValues);
} else {
final UpdateAttribute operation = DiffFactory.eINSTANCE.createUpdateAttribute();
operation.setRightElement(rightElement);
operation.setLeftElement(leftElement);
operation.setAttribute(attribute);
root.getSubDiffElements().add(operation);
}
}
/**
* This will be called to check for changes on a given attribute's values. Note that we know that
* attribute.isMany()
and attribute.isOrdered()
always return true
* here.
*
* @param root
* {@link DiffGroup Root} of the {@link DiffElement}s to create.
* @param attribute
* {@link EAttribute} to check for modifications.
* @param leftElement
* Element corresponding to the final holder of the given attribute.
* @param rightElement
* Element corresponding to the initial holder of the given attribute.
* @param addedValues
* Contains the created differences for added attribute values.
* @param deletedValues
* Contains the created differences for removed attribute values.
* @throws FactoryException
* Thrown if we cannot fetch the attribute's values.
* @since 1.3
*/
protected void checkAttributeOrderChange(DiffGroup root, EAttribute attribute, EObject leftElement,
EObject rightElement, List addedValues,
List deletedValues) throws FactoryException {
final List rightValues = convertFeatureMapList(EFactory.eGetAsList(rightElement,
attribute.getName()));
final List leftValues = convertFeatureMapList(EFactory.eGetAsList(leftElement,
attribute.getName()));
final List removedIndices = new ArrayList(deletedValues.size());
// Purge "left" list of all attribute values that have been added to it
for (AttributeChangeLeftTarget added : addedValues) {
leftValues.remove(added.getLeftTarget());
}
// Compute the list of indices that have been removed from the list
for (AttributeChangeRightTarget removed : deletedValues) {
final int removedIndex = rightValues.indexOf(removed.getRightTarget());
removedIndices.add(Integer.valueOf(removedIndex));
}
// Iterate over the list to detect values that actually changed order.
int expectedIndex = 0;
for (int i = 0; i < leftValues.size(); i++) {
for (Integer removedIndex : new ArrayList(removedIndices)) {
if (i == removedIndex.intValue()) {
expectedIndex++;
removedIndices.remove(removedIndex);
}
}
if (areDistinctValues(leftValues.get(i), rightValues.get(expectedIndex++))) {
final AttributeOrderChange attributeChange = DiffFactory.eINSTANCE
.createAttributeOrderChange();
attributeChange.setAttribute(attribute);
attributeChange.setLeftElement(leftElement);
attributeChange.setRightElement(rightElement);
root.getSubDiffElements().add(attributeChange);
break;
}
}
}
/**
* This will be called to check for changes on a given attribute's values. Note that we know that
* attribute.isMany()
and attribute.isOrdered()
always return true
* here.
*
* @param root
* {@link DiffGroup Root} of the {@link DiffElement}s to create.
* @param attribute
* {@link EAttribute} to check for modifications.
* @param leftElement
* Element corresponding to the final holder of the given attribute.
* @param rightElement
* Element corresponding to the initial holder of the given attribute.
* @param remoteDeletedValues
* Contains the created differences for remotely removed attribute values.
* @param remoteAddedValues
* Contains the created differences for remotely added attribute values.
* @throws FactoryException
* Thrown if we cannot fetch the attribute's values.
* @since 1.3
*/
protected void checkAttributeRemoteOrderChange(DiffGroup root, EAttribute attribute, EObject leftElement,
EObject rightElement, List remoteDeletedValues,
List remoteAddedValues) throws FactoryException {
final List rightValues = convertFeatureMapList(EFactory.eGetAsList(rightElement,
attribute.getName()));
final List leftValues = convertFeatureMapList(EFactory.eGetAsList(leftElement,
attribute.getName()));
final List removedIndices = new ArrayList(remoteAddedValues.size());
// Purge "left" list of all attribute values that have been added to it
for (AttributeChangeLeftTarget remoteDeleted : remoteDeletedValues) {
leftValues.remove(remoteDeleted.getLeftTarget());
}
// Compute the list of indices that have been removed from the list
for (AttributeChangeRightTarget remoteAdded : remoteAddedValues) {
final int removedIndex = rightValues.indexOf(remoteAdded.getRightTarget());
removedIndices.add(Integer.valueOf(removedIndex));
}
// Iterate over the list to detect values that actually changed order.
int expectedIndex = 0;
for (int i = 0; i < leftValues.size(); i++) {
for (Integer removedIndex : new ArrayList(removedIndices)) {
if (i == removedIndex.intValue()) {
expectedIndex++;
removedIndices.remove(removedIndex);
}
}
if (areDistinctValues(leftValues.get(i), rightValues.get(expectedIndex))) {
final AttributeOrderChange attributeChange = DiffFactory.eINSTANCE
.createAttributeOrderChange();
attributeChange.setAttribute(attribute);
attributeChange.setLeftElement(leftElement);
attributeChange.setRightElement(rightElement);
attributeChange.setRemote(true);
root.getSubDiffElements().add(attributeChange);
break;
}
}
}
/**
* This will create and populate a {@link List} with all the values from the leftValues
list
* that are not present the the rightValues
list.
*
* @param leftValues
* List of the left element attribute values.
* @param rightValues
* List of the right element attribute values.
* @return The list of all values that are contained by leftValues
but not by
* rightValues
.
*/
private List computeAddedValues(List leftValues, List rightValues) {
final List result = new ArrayList(leftValues);
final List rightCopy = new ArrayList(rightValues);
for (Object left : leftValues) {
Object matched = null;
final Iterator rightIterator = rightCopy.iterator();
while (matched == null && rightIterator.hasNext()) {
final Object next = rightIterator.next();
if (!areDistinctValues(left, next)) {
matched = next;
}
}
if (matched != null) {
rightCopy.remove(matched);
result.remove(left);
}
}
return result;
}
/**
* This will create and populate a {@link List} with all the values from the rightValues
list
* that are not present the the leftValues
list.
*
* @param leftValues
* List of the left element attribute values.
* @param rightValues
* List of the right element attribute values.
* @return The list of all values that are contained by rightValues
but not by
* leftValues
.
*/
private List computeDeletedValues(List leftValues, List rightValues) {
final List result = new ArrayList(rightValues);
final List leftCopy = new ArrayList(leftValues);
for (Object right : rightValues) {
Object matched = null;
final Iterator leftIterator = leftCopy.iterator();
while (matched == null && leftIterator.hasNext()) {
final Object next = leftIterator.next();
if (!areDistinctValues(right, next)) {
matched = next;
}
}
if (matched != null) {
leftCopy.remove(matched);
result.remove(right);
}
}
return result;
}
/**
* This will create the needed remote attribute change {@link DiffElement} under the given
* {@link DiffGroup root}.
* An attribute is "remotely changed" if it has been added, updated or deleted in the right
* (latest from head) version but it has kept its former value in the left (working copy) version.
*
* @param root
* {@link DiffGroup root} of the {@link DiffElement} to create.
* @param attribute
* Target {@link EAttribute} of the update.
* @param mapping
* Contains the three (ancestor, left, right) elements' mapping.
* @throws FactoryException
* Thrown if we cannot fetch attribute
's left and right values.
*/
private void createRemoteAttributeChange(DiffGroup root, EAttribute attribute, Match3Elements mapping)
throws FactoryException {
if (attribute.isMany()) {
final List remoteDeletedValues = new ArrayList();
final List remoteAddedValues = new ArrayList();
// We know that there is no "local" diff, thus addedValues and deletedValues are not used.
populateThreeWayAttributeChanges(mapping, attribute, new ArrayList(),
new ArrayList(), remoteAddedValues, remoteDeletedValues);
createRemoteAttributeDiffs(root, attribute, mapping.getLeftElement(), mapping.getRightElement(),
remoteDeletedValues, remoteAddedValues);
} else {
final UpdateAttribute operation = DiffFactory.eINSTANCE.createUpdateAttribute();
operation.setRemote(true);
operation.setRightElement(mapping.getRightElement());
operation.setLeftElement(mapping.getLeftElement());
operation.setAttribute(attribute);
root.getSubDiffElements().add(operation);
}
}
/**
* This can be used to check that the given list contains the given value. This will use the checks
* described in {@link #areDistinctValues(Object, Object)}.
*
* @param values
* The list we need to check for a value equivalent to value
.
* @param value
* The value we need to know if it's contained by values
.
* @return true
if {@link #areDistinctValues(Object, Object)} returned true for one of the
* objects contained by values
when compared with value
.
* @deprecated no longer in use.
*/
@Deprecated
protected final boolean attributeListContains(List values, Object value) {
for (Object aValue : values) {
if (!areDistinctValues(aValue, value)) {
return true;
}
}
return false;
}
}