/*******************************************************************************
* Copyright (c) 2006, 2011 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
* 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.common.util.URI;
import org.eclipse.emf.compare.FactoryException;
import org.eclipse.emf.compare.diff.engine.IMatchManager;
import org.eclipse.emf.compare.diff.engine.IMatchManager.MatchSide;
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.ReferenceChangeLeftTarget;
import org.eclipse.emf.compare.diff.metamodel.ReferenceChangeRightTarget;
import org.eclipse.emf.compare.diff.metamodel.ReferenceOrderChange;
import org.eclipse.emf.compare.diff.metamodel.UpdateReference;
import org.eclipse.emf.compare.match.internal.statistic.ResourceSimilarity;
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.EClass;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.EcorePackage;
import org.eclipse.emf.ecore.util.EcoreUtil;
/**
* This will implement the reference checks : order of reference values, changes between two versions, ...
*
* @author Laurent Goubet laurent.goubet@obeo.fr
* @since 1.0
*/
public class ReferencesCheck 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 CrossReferencer mechanism is now hidden behind the {@link IMatchManager} interface.
*/
@Deprecated
public ReferencesCheck(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 ReferencesCheck(IMatchManager manager) {
super(manager);
}
/**
* Checks if there's been references updates in the model.
*
* A reference is considered updated if its value(s) has been changed (either removal or addition of an
* element if the reference is multi-valued or update of a single-valued reference) between the left and
* the right model.
*
*
* @param root
* {@link DiffGroup root} of the {@link DiffElement} to create.
* @param mapping
* Contains informations about the left and right model elements we have to compare.
* @throws FactoryException
* Thrown if we cannot fetch the references' values.
*/
public void checkReferencesUpdates(DiffGroup root, Match2Elements mapping) throws FactoryException {
final EClass eClass = mapping.getLeftElement().eClass();
final List eclassReferences = eClass.getEAllReferences();
final Iterator it = eclassReferences.iterator();
while (it.hasNext()) {
final EReference next = it.next();
if (!shouldBeIgnored(next)) {
checkReferenceUpdates(root, mapping, next);
} else if (next.isContainment() && next.isOrdered()) {
checkContainmentReferenceOrderChange(root, mapping, next);
}
}
}
/**
* Checks if there's been references updates in the model.
*
* A reference is considered updated if its value(s) has been changed (either removal or addition of an
* element if the reference is multi-valued or update of a single-valued reference) between the left and
* the ancestor model, the right and the ancestor or between the left and the right model.
*
*
* @param root
* {@link DiffGroup root} of the {@link DiffElement} to create.
* @param mapping
* Contains informations about the left, right and origin model elements we have to compare.
* @throws FactoryException
* Thrown if we cannot fetch the references' values.
*/
public void checkReferencesUpdates(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 eclassReferences = eClass.getEAllReferences();
final Iterator it = eclassReferences.iterator();
while (it.hasNext()) {
final EReference next = it.next();
if (!shouldBeIgnored(next)) {
checkReferenceUpdates(root, mapping, next);
} else if (next.isContainment() && next.isOrdered()) {
checkContainmentReferenceOrderChange(root, mapping, next);
}
}
}
/**
* This will be called to check for ordering changes on a given containment reference values.
*
* @param root
* {@link DiffGroup Root} of the {@link DiffElement}s to create.
* @param mapping
* Contains informations about the left and right model elements we have to compare.
* @param reference
* {@link EReference} to check for modifications.
* @throws FactoryException
* Thrown if we cannot fetch the references' values.
*/
@SuppressWarnings("unchecked")
protected void checkContainmentReferenceOrderChange(DiffGroup root, Match2Elements mapping,
EReference reference) throws FactoryException {
/*
* We'll need to compute the added and removed reference values from the cross referencing of
* unmatched elements as they haven't been processed yet.
*/
final List leftElementReferences = new ArrayList(
(List)EFactory.eGetAsList(mapping.getLeftElement(), reference.getName()));
final List rightElementReferences = new ArrayList(
(List)EFactory.eGetAsList(mapping.getRightElement(), reference.getName()));
final List removedIndices = new ArrayList();
// Purge "left" list of all reference values that have been added to it
for (EObject leftValue : new ArrayList(leftElementReferences)) {
if (!getMatchManager().isInScope(leftValue)
|| getMatchManager().isUnmatched(leftValue)
|| getMatchManager().getMatchedEObject(leftValue.eContainer()) != getMatchManager()
.getMatchedEObject(leftValue).eContainer())
leftElementReferences.remove(leftValue);
}
for (EObject rightValue : new ArrayList(rightElementReferences)) {
if (!getMatchManager().isInScope(rightValue)
|| getMatchManager().isUnmatched(rightValue)
|| getMatchManager().getMatchedEObject(rightValue.eContainer()) != getMatchManager()
.getMatchedEObject(rightValue).eContainer()) {
removedIndices.add(Integer.valueOf(rightElementReferences.indexOf(rightValue)));
}
}
int expectedIndex = 0;
for (int i = 0; i < leftElementReferences.size(); i++) {
final EObject matched = getMatchManager().getMatchedEObject(leftElementReferences.get(i));
for (final Integer removedIndex : new ArrayList(removedIndices)) {
if (expectedIndex == removedIndex) {
expectedIndex += 1;
removedIndices.remove(removedIndex);
}
}
if (rightElementReferences.indexOf(matched) != expectedIndex++) {
final ReferenceOrderChange refChange = DiffFactory.eINSTANCE.createReferenceOrderChange();
refChange.setReference(reference);
refChange.setLeftElement(mapping.getLeftElement());
refChange.setRightElement(mapping.getRightElement());
// The loop will be broken here. Initialize left and right "target" lists for the diff
for (int j = removedIndices.size() - 1; j >= 0; j--) {
rightElementReferences.remove(removedIndices.get(j).intValue());
}
final List leftTarget = new ArrayList(
getMatchedReferences(rightElementReferences));
final List rightTarget = new ArrayList(
getMatchedReferences(leftElementReferences));
refChange.getLeftTarget().addAll(leftTarget);
refChange.getRightTarget().addAll(rightTarget);
if (mapping instanceof Match3Elements) {
furtherCheckContainmentReferenceOrderChange(root, (Match3Elements)mapping, reference, refChange);
} else {
root.getSubDiffElements().add(refChange);
}
break;
}
}
}
/**
* Perform further checks on containment reference reordering to determine if the left or right changed and if a conflict occurred.
*
* @param root
* {@link DiffGroup Root} of the {@link DiffElement}s to create.
* @param mapping
* Contains informations about the left and right model elements we have to compare.
* @param reference
* {@link EReference} to check for modifications.
* @throws FactoryException
* Thrown if we cannot fetch the references' values.
*/
@SuppressWarnings("unchecked")
private void furtherCheckContainmentReferenceOrderChange(DiffGroup root, Match3Elements mapping,
EReference reference, ReferenceOrderChange refChange) throws FactoryException {
final List originElementReferences = new ArrayList(
(List)EFactory.eGetAsList(mapping.getOriginElement(), reference.getName()));
final List leftElementReferences = new ArrayList(
(List)EFactory.eGetAsList(mapping.getLeftElement(), reference.getName()));
final List rightElementReferences = new ArrayList(
(List)EFactory.eGetAsList(mapping.getRightElement(), reference.getName()));
// Look for change between origin and left
boolean leftChanged = containsChange(originElementReferences, leftElementReferences, IMatchManager.MatchSide.LEFT);
// Look for change between origin and right
boolean rightChanged = containsChange(originElementReferences, rightElementReferences, IMatchManager.MatchSide.RIGHT);
// Look for change between left and right
if (leftChanged && rightChanged) {
boolean leftAndRightDiffer = containsChange(leftElementReferences, rightElementReferences, IMatchManager.MatchSide.RIGHT);
if (!leftAndRightDiffer) {
// No difference between left and right. No change entry should be created.
return;
}
}
if (leftChanged || rightChanged) {
DiffElement group = root;
if (leftChanged && rightChanged) {
// Left change and right change are different. Conflict.
final ConflictingDiffElement conflictingDiff = DiffFactory.eINSTANCE.createConflictingDiffElement();
conflictingDiff.setLeftParent(mapping.getLeftElement());
conflictingDiff.setRightParent(mapping.getRightElement());
conflictingDiff.setOriginElement(mapping.getOriginElement());
group.getSubDiffElements().add(conflictingDiff);
group = conflictingDiff;
} else if (rightChanged) {
refChange.setRemote(true);
}
group.getSubDiffElements().add(refChange);
}
}
/**
* Check for changes in order between 2 lists
* @param list1 List 1
* @param list2 List 2
* @param list2Side Side of list 2.
* @return {@code true} only if there is a difference in order.
*/
private boolean containsChange(List list1, List list2, MatchSide list2Side) {
int previousIndex = -1;
for (EObject originElementReference : list1) {
final EObject matched = getMatchManager().getMatchedEObject(originElementReference, list2Side);
if (matched != null) {
int currentIndex = list2.indexOf(matched);
if (currentIndex >= 0) {
if (currentIndex <= previousIndex) {
return true;
}
previousIndex = currentIndex;
}
}
}
return false;
}
/**
* This will be called to check for changes on a given reference values. Note that we know
* reference.isMany() and reference.isOrdered() always return true here for the
* generic diff engine and the tests won't be made.
*
* @param root
* {@link DiffGroup Root} of the {@link DiffElement}s to create.
* @param reference
* {@link EReference} to check for modifications.
* @param leftElement
* Element corresponding to the final holder of the given reference.
* @param rightElement
* Element corresponding to the initial holder of the given reference.
* @param addedReferences
* Contains the created differences for added reference values.
* @param removedReferences
* Contains the created differences for removed reference values.
* @throws FactoryException
* Thrown if we cannot fetch the references' values.
*/
@SuppressWarnings("unchecked")
protected void checkReferenceOrderChange(DiffGroup root, EReference reference, Match2Elements mapping, EObject leftElement,
EObject rightElement, List addedReferences,
List removedReferences) throws FactoryException {
final List leftElementReferences = new ArrayList(
(List)EFactory.eGetAsList(leftElement, reference.getName()));
final List rightElementReferences = new ArrayList(
(List)EFactory.eGetAsList(rightElement, reference.getName()));
final List removedIndices = new ArrayList(removedReferences.size());
final List leftElementReferencesCopy = new ArrayList(leftElementReferences);
final List rightElementReferencesCopy = new ArrayList(rightElementReferences);
removeAll(rightElementReferences, leftElementReferencesCopy);
removeAll(leftElementReferences, rightElementReferencesCopy);
// Purge "left" list of all reference values that have been added to it
for (final ReferenceChangeLeftTarget added : addedReferences) {
leftElementReferences.remove(added.getLeftTarget());
}
for (final ReferenceChangeRightTarget removed : removedReferences) {
removedIndices.add(Integer.valueOf(rightElementReferences.indexOf(removed.getRightTarget())));
}
int expectedIndex = 0;
for (int i = 0; i < leftElementReferences.size(); i++) {
final EObject matched = getMatchManager().getMatchedEObject(leftElementReferences.get(i));
for (final Integer removedIndex : new ArrayList(removedIndices)) {
if (i == removedIndex.intValue()) {
expectedIndex += 1;
removedIndices.remove(removedIndex);
}
}
if (rightElementReferences.indexOf(matched) != expectedIndex++) {
final ReferenceOrderChange refChange = DiffFactory.eINSTANCE.createReferenceOrderChange();
refChange.setReference(reference);
refChange.setLeftElement(leftElement);
refChange.setRightElement(rightElement);
// The loop will be broken here. Initialize left and right "target" lists for the diff
for (int j = removedIndices.size() - 1; j >= 0; j--) {
rightElementReferences.remove(removedIndices.get(j).intValue());
}
final List leftTarget = new ArrayList(
getMatchedReferences(rightElementReferences));
final List rightTarget = new ArrayList(
getMatchedReferences(leftElementReferences));
refChange.getLeftTarget().addAll(leftTarget);
refChange.getRightTarget().addAll(rightTarget);
if (mapping != null && mapping instanceof Match3Elements) {
furtherCheckContainmentReferenceOrderChange(root, (Match3Elements)mapping, reference, refChange);
} else {
root.getSubDiffElements().add(refChange);
}
// root.getSubDiffElements().add(refChange);
break;
}
}
}
/**
* This will check that the values of the given reference from the objects contained by mapping has been
* modified.
*
* @param root
* {@link DiffGroup root} of the {@link DiffElement} to create.
* @param mapping
* Contains informations about the left and right model elements we have to compare.
* @param reference
* The reference we need to check for differences.
* @throws FactoryException
* Thrown if one of the checks fails.
*/
protected void checkReferenceUpdates(DiffGroup root, Match2Elements mapping, EReference reference)
throws FactoryException {
createNonConflictingReferencesUpdate(root, reference, mapping, mapping.getLeftElement(),
mapping.getRightElement());
}
/**
* This will check that the values of the given reference from the objects contained by mapping has been
* modified.
*
* @param root
* {@link DiffGroup root} of the {@link DiffElement} to create.
* @param mapping
* Contains informations about the left and right model elements we have to compare.
* @param reference
* The reference we need to check for differences.
* @throws FactoryException
* Thrown if one of the checks fails.
*/
protected void checkReferenceUpdates(DiffGroup root, Match3Elements mapping, EReference reference)
throws FactoryException {
final String referenceName = reference.getName();
final List