blob: 1e1201cf1135c00545208db53c49ab146ca50d19 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2006, 2007 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.diff.generic;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import org.eclipse.emf.common.util.TreeIterator;
import org.eclipse.emf.compare.EMFComparePlugin;
import org.eclipse.emf.compare.diff.api.DiffEngine;
import org.eclipse.emf.compare.diff.metamodel.AddAttribute;
import org.eclipse.emf.compare.diff.metamodel.AddModelElement;
import org.eclipse.emf.compare.diff.metamodel.AddReferenceValue;
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.DiffModel;
import org.eclipse.emf.compare.diff.metamodel.MoveModelElement;
import org.eclipse.emf.compare.diff.metamodel.RemoveAttribute;
import org.eclipse.emf.compare.diff.metamodel.RemoveModelElement;
import org.eclipse.emf.compare.diff.metamodel.RemoveReferenceValue;
import org.eclipse.emf.compare.diff.metamodel.UpdateAttribute;
import org.eclipse.emf.compare.diff.metamodel.UpdateUniqueReferenceValue;
import org.eclipse.emf.compare.match.metamodel.Match2Elements;
import org.eclipse.emf.compare.match.metamodel.MatchModel;
import org.eclipse.emf.compare.match.metamodel.UnMatchElement;
import org.eclipse.emf.compare.util.EFactory;
import org.eclipse.emf.compare.util.FactoryException;
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.resource.Resource;
import org.eclipse.emf.ecore.util.EcoreUtil;
/**
* This class is useful when one wants to determine a diff from a matching model.
*
* @author Cedric Brun <a href="mailto:cedric.brun@obeo.fr">cedric.brun@obeo.fr</a>
*/
public class DiffMaker implements DiffEngine {
/**
* This hashmap is useful to find the Match from any EObject instance.
*/
private HashMap<EObject, Match2Elements> eObjectToMatch = new HashMap<EObject, Match2Elements>();
/**
* Return a diffmodel created using the match model. This implementation is a generic and simple one.
*
* @param match
* The matching model
* @return The corresponding diff model
* @throws FactoryException
*/
@SuppressWarnings("unchecked")
public DiffModel doDiff(MatchModel match) {
updateEObjectToMatch(match);
final DiffModel result = DiffFactory.eINSTANCE.createDiffModel();
// we have to browse the model and create the corresponding operations
final Match2Elements matchRoot = (Match2Elements)match.getMatchedElements().get(0);
final Resource leftModel = matchRoot.getLeftElement().eResource();
final Resource rightModel = matchRoot.getRightElement().eResource();
// creating the root modelchange
final DiffGroup diffRoot = DiffFactory.eINSTANCE.createDiffGroup();
// browsing the match model
doDiffDelegate(diffRoot, matchRoot);
// iterate over the unmached elements end determine if they have been
// added or removed.
final Iterator unMatched = match.getUnMatchedElements().iterator();
while (unMatched.hasNext()) {
final UnMatchElement unMatchElement = (UnMatchElement)unMatched.next();
if (unMatchElement.getElement().eResource() == leftModel) {
// add remove model element
final RemoveModelElement operation = DiffFactory.eINSTANCE.createRemoveModelElement();
operation.setLeftElement(unMatchElement.getElement());
operation.setRightParent(getMatchedEObject(unMatchElement.getElement().eContainer()));
addInContainerPackage(diffRoot, operation, unMatchElement.getElement().eContainer());
}
if (unMatchElement.getElement().eResource() == rightModel) {
// add remove model element
final AddModelElement operation = DiffFactory.eINSTANCE.createAddModelElement();
final EObject addedElement = unMatchElement.getElement();
operation.setRightElement(addedElement);
final EObject targetParent = getMatchedEObject(addedElement.eContainer());
operation.setLeftParent(targetParent);
addInContainerPackage(diffRoot, operation, targetParent);
}
}
result.getOwnedElements().add(diffRoot);
// FIXME call diff extensions.
return result;
}
/**
* Fill the <code>eObjectToMatch</code> hashmap to retrieve matchings from left or right EObject.
*/
private void updateEObjectToMatch(MatchModel match) {
final Iterator rootElemIt = match.getMatchedElements().iterator();
while (rootElemIt.hasNext()) {
final Match2Elements matchRoot = (Match2Elements)rootElemIt.next();
eObjectToMatch.put(matchRoot.getLeftElement(), matchRoot);
eObjectToMatch.put(matchRoot.getRightElement(), matchRoot);
final TreeIterator matchElemIt = matchRoot.eAllContents();
while (matchElemIt.hasNext()) {
final Match2Elements matchElem = (Match2Elements)matchElemIt.next();
eObjectToMatch.put(matchElem.getLeftElement(), matchElem);
eObjectToMatch.put(matchElem.getRightElement(), matchElem);
}
}
}
/**
* Return the matched EObject from the one given.
*
* @param from
* The original EObject.
* @return The matched EObject.
*/
private EObject getMatchedEObject(EObject from) {
EObject matchedEObject = null;
final Match2Elements matchElem = eObjectToMatch.get(from);
if (matchElem != null && from.equals(matchElem.getLeftElement()))
matchedEObject = matchElem.getRightElement();
else if (matchElem != null)
matchedEObject = matchElem.getLeftElement();
return matchedEObject;
}
private void doDiffDelegate(DiffGroup root, Match2Elements match) {
DiffGroup current = DiffFactory.eINSTANCE.createDiffGroup();
current.setLeftParent(match.getLeftElement());
try {
checkAttributesUpdates(current, match);
checkReferencesUpdates(current, match);
checkForMove(current, match);
} catch (FactoryException e) {
EMFComparePlugin.log(e, false);
}
// we need to build this list to avoid concurrent modifications
final List<DiffElement> shouldAddToList = new ArrayList<DiffElement>();
// we really have changes
if (current.getSubDiffElements().size() > 0) {
final Iterator it2 = current.getSubDiffElements().iterator();
while (it2.hasNext()) {
final Object eObj = it2.next();
if (!(eObj instanceof DiffGroup)) {
shouldAddToList.add((DiffElement)eObj);
}
}
for (DiffElement diff : shouldAddToList) {
addInContainerPackage(root, diff, current.getLeftParent());
}
} else {
current = root;
}
// taking care of our childs
final Iterator it = match.getSubMatchElements().iterator();
while (it.hasNext()) {
final Match2Elements element = (Match2Elements)it.next();
doDiffDelegate(root, element);
}
}
/**
* Looks for an already created diff group in order to add the operation, if none exists, create one where
* the operation belongs to.
*/
@SuppressWarnings("unchecked")
private void addInContainerPackage(DiffGroup root, DiffElement operation, EObject targetParent) {
if (targetParent == null) {
root.getSubDiffElements().add(operation);
return;
}
final DiffGroup targetGroup = findExistingGroup(root, targetParent);
if (targetGroup == null) {
// we have to create the group
buildHierarchyGroup(targetParent, root).getSubDiffElements().add(operation);
} else {
targetGroup.getSubDiffElements().add(operation);
}
}
@SuppressWarnings("unchecked")
private DiffGroup buildHierarchyGroup(EObject targetParent, DiffGroup root) {
// if targetElement has a parent, we call buildgroup on it, else we add the current group to the root
DiffGroup curGroup = DiffFactory.eINSTANCE.createDiffGroup();
curGroup.setLeftParent(targetParent);
final DiffGroup targetGroup = findExistingGroup(root, targetParent);
if (targetGroup != null)
curGroup = targetGroup;
if (targetParent.eContainer() == null) {
root.getSubDiffElements().add(curGroup);
return curGroup;
}
buildHierarchyGroup(targetParent.eContainer(), root).getSubDiffElements().add(curGroup);
return curGroup;
}
private DiffGroup findExistingGroup(DiffGroup root, EObject targetParent) {
final TreeIterator it = root.eAllContents();
while (it.hasNext()) {
final EObject obj = (EObject)it.next();
if (obj instanceof DiffGroup) {
if (((DiffGroup)obj).getLeftParent() == targetParent) {
return (DiffGroup)obj;
}
}
}
return null;
}
@SuppressWarnings("unchecked")
private void checkForMove(DiffGroup root, Match2Elements matchElement) {
if (matchElement.getLeftElement().eContainer() != null
&& matchElement.getRightElement().eContainer() != null
&& getMatchedEObject(matchElement.getLeftElement().eContainer()) != matchElement
.getRightElement().eContainer()) {
final MoveModelElement operation = DiffFactory.eINSTANCE.createMoveModelElement();
operation.setRightElement(matchElement.getRightElement());
operation.setLeftElement(matchElement.getLeftElement());
operation.setRightTarget(getMatchedEObject(matchElement.getLeftElement().eContainer()));
operation.setLeftTarget(getMatchedEObject(matchElement.getRightElement().eContainer()));
root.getSubDiffElements().add(operation);
}
}
@SuppressWarnings("unchecked")
private void checkAttributesUpdates(DiffGroup root, Match2Elements mapping) throws FactoryException {
final EObject eClass = mapping.getLeftElement().eClass();
List eclassAttributes = new LinkedList();
if (eClass instanceof EClass)
eclassAttributes = ((EClass)eClass).getEAllAttributes();
// for each feature, compare the value
final Iterator it = eclassAttributes.iterator();
while (it.hasNext()) {
final EAttribute next = (EAttribute)it.next();
if (!next.isDerived()) {
final String attributeName = next.getName();
final Object leftValue = EFactory.eGet(mapping.getLeftElement(), attributeName);
final Object rightValue = EFactory.eGet(mapping.getRightElement(), attributeName);
if (leftValue != null && !leftValue.equals(rightValue) && next.isMany()) {
// If an object in the left list isn't contained in the right, it is a remove operation
for (Object aValue : (List)leftValue) {
if (!((List)rightValue).contains(aValue)) {
final RemoveAttribute operation = DiffFactory.eINSTANCE.createRemoveAttribute();
operation.setAttribute(next);
operation.setRightElement(mapping.getRightElement());
operation.setLeftElement(mapping.getLeftElement());
operation.setLeftTarget((EObject)aValue);
root.getSubDiffElements().add(operation);
}
}
for (Object aValue : (List)rightValue) {
if (!((List)leftValue).contains(aValue)) {
final AddAttribute operation = DiffFactory.eINSTANCE.createAddAttribute();
operation.setAttribute(next);
operation.setRightElement(mapping.getRightElement());
operation.setLeftElement(mapping.getLeftElement());
operation.setRightTarget((EObject)aValue);
root.getSubDiffElements().add(operation);
}
}
} else if (leftValue != null && !leftValue.equals(rightValue)) {
final UpdateAttribute operation = DiffFactory.eINSTANCE.createUpdateAttribute();
operation.setRightElement(mapping.getRightElement());
operation.setLeftElement(mapping.getLeftElement());
operation.setAttribute(next);
root.getSubDiffElements().add(operation);
}
}
}
}
@SuppressWarnings("unchecked")
private void checkReferencesUpdates(DiffGroup root, Match2Elements mapping) throws FactoryException {
// for each reference, compare the targets
final Iterator it = mapping.getLeftElement().eClass().getEAllReferences().iterator();
while (it.hasNext()) {
final EReference next = (EReference)it.next();
final String referenceName = next.getName();
if (!next.isContainment() && !next.isDerived() && !next.isTransient()) {
final List leftElementReferences = EFactory.eGetAsList(mapping.getLeftElement(),
referenceName);
final List rightElementReferences = EFactory.eGetAsList(mapping.getRightElement(),
referenceName);
final List<EObject> deletedReferences = new ArrayList<EObject>();
final List<EObject> addedReferences = new ArrayList<EObject>();
if (leftElementReferences != null)
deletedReferences.addAll(leftElementReferences);
if (rightElementReferences != null)
addedReferences.addAll(rightElementReferences);
final List<EObject> matchedOldReferences = getMatchedReferences(deletedReferences);
final List<EObject> matchedNewReferences = getMatchedReferences(addedReferences);
// "Added" references are the references from the left element that can't be mapped
addedReferences.removeAll(matchedOldReferences);
// "deleted" references are the references from the right element that can't be mapped
deletedReferences.removeAll(matchedNewReferences);
// Double check for objects defined in a different model and thus not matched
// We'll use a new list to keep track of theses elements !avoid concurrent modification!
final List<EObject> remoteMatchedElements = new ArrayList<EObject>();
for (EObject deleted : deletedReferences) {
if (addedReferences.contains(deleted)) {
remoteMatchedElements.add(deleted);
}
}
addedReferences.removeAll(remoteMatchedElements);
deletedReferences.removeAll(remoteMatchedElements);
// REFERENCES UPDATES
if (!next.isMany() && addedReferences.size() > 0 && deletedReferences.size() > 0) {
/*
* If neither the left nor the right target are proxies, or if their target URIs are
* distinct, this is a reference update. Otherwise, we are here because we haven't been
* able to resolve the proxy.
*/
if (!addedReferences.get(0).eIsProxy()
|| !deletedReferences.get(0).eIsProxy()
|| !EcoreUtil.getURI(addedReferences.get(0)).equals(
EcoreUtil.getURI(deletedReferences.get(0)))) {
root.getSubDiffElements().add(
createUpdatedReferencesOperation(mapping, next, addedReferences,
deletedReferences));
}
} else {
// REFERENCES ADD
if (addedReferences.size() > 0) {
createNewReferencesOperation(root, mapping, next, addedReferences);
}
// REFERENCES DEL
if (deletedReferences.size() > 0) {
createRemovedReferencesOperation(root, mapping, next, deletedReferences);
}
}
}
}
}
@SuppressWarnings("unchecked")
private UpdateUniqueReferenceValue createUpdatedReferencesOperation(Match2Elements mapping,
EReference newReference, List<EObject> deletedReferences, List<EObject> addedReferences) {
final UpdateUniqueReferenceValue operation = DiffFactory.eINSTANCE.createUpdateUniqueReferenceValue();
operation.setLeftElement(mapping.getLeftElement());
operation.setRightElement(mapping.getRightElement());
operation.setReference(newReference);
EObject leftTarget = getMatchedEObject(addedReferences.get(0));
EObject rightTarget = getMatchedEObject(deletedReferences.get(0));
// checks if target are defined remotely
if (leftTarget == null)
leftTarget = addedReferences.get(0);
if (rightTarget == null)
rightTarget = deletedReferences.get(0);
operation.setLeftTarget(leftTarget);
operation.setRightTarget(rightTarget);
return operation;
}
@SuppressWarnings("unchecked")
private void createNewReferencesOperation(DiffGroup root, Match2Elements mapping, EReference newReference,
List<EObject> addedReferences) {
for (final Iterator<EObject> addedReferenceIterator = addedReferences.iterator(); addedReferenceIterator.hasNext(); ) {
final EObject eobj = addedReferenceIterator.next();
final AddReferenceValue addOperation = DiffFactory.eINSTANCE.createAddReferenceValue();
addOperation.setRightElement(mapping.getRightElement());
addOperation.setLeftElement(mapping.getLeftElement());
addOperation.setReference(newReference);
addOperation.setRightAddedTarget(eobj);
if ((getMatchedEObject(eobj)) != null)
addOperation.setLeftAddedTarget(getMatchedEObject(eobj));
root.getSubDiffElements().add(addOperation);
}
}
@SuppressWarnings("unchecked")
private void createRemovedReferencesOperation(DiffGroup root, Match2Elements mapping,
EReference removedReference, List<EObject> deletedReferences) {
for (final Iterator<EObject> deletedReferenceIterator = deletedReferences.iterator(); deletedReferenceIterator.hasNext(); ) {
final EObject eobj = deletedReferenceIterator.next();
final RemoveReferenceValue delOperation = DiffFactory.eINSTANCE.createRemoveReferenceValue();
delOperation.setRightElement(mapping.getRightElement());
delOperation.setLeftElement(mapping.getLeftElement());
delOperation.setReference(removedReference);
delOperation.setLeftRemovedTarget(eobj);
if ((getMatchedEObject(eobj)) != null)
delOperation.setRightRemovedTarget(getMatchedEObject(eobj));
root.getSubDiffElements().add(delOperation);
}
}
/**
* Returns the list of references from the given list that can be matched.
*
* @param references
* {@link List} of the references to match.
* @return The list of references from the given list that can be matched.
*/
private List<EObject> getMatchedReferences(List<EObject> references) {
final List<EObject> matchedReferences = new ArrayList<EObject>();
for (final Iterator refIterator = references.iterator(); refIterator.hasNext(); ) {
final Object currentReference = refIterator.next();
if (currentReference != null) {
final EObject currentMapped = getMatchedEObject((EObject)currentReference);
if (currentMapped != null)
matchedReferences.add(currentMapped);
}
}
return matchedReferences;
}
}