blob: a1118e479bf332d85fbbebbdcc3629a1b8f7594a [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2012, 2013 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
*******************************************************************************/
package org.eclipse.emf.compare.match.eobject;
import com.google.common.base.Function;
import com.google.common.base.Optional;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.emf.common.util.Monitor;
import org.eclipse.emf.compare.CompareFactory;
import org.eclipse.emf.compare.Comparison;
import org.eclipse.emf.compare.Match;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.InternalEObject;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.emf.ecore.xmi.XMIResource;
/**
* This implementation of an {@link IEObjectMatcher} will create {@link Match}es based on the input EObjects
* identifiers (either XMI:ID or attribute ID) alone.
*
* @author <a href="mailto:laurent.goubet@obeo.fr">Laurent Goubet</a>
*/
public class IdentifierEObjectMatcher implements IEObjectMatcher {
/**
* This instance might have a delegate matcher. The delegate matcher will be called when no ID is found
* and its results are aggregated with the current matcher.
*/
private Optional<IEObjectMatcher> delegate;
/**
* This will be used to determine what represents the "identifier" of an EObject. By default, we will use
* the following logic, in order (i.e. if condition 1 is fulfilled, we will not try conditions 2 and 3) :
* <ol>
* <li>If the given eObject is a proxy, it is uniquely identified by its URI fragment.</li>
* <li>If the eObject's EClass has an eIdAttribute set, use this attribute's value.</li>
* <li>If the eObject is located in an XMI resource and has an XMI ID, use this as its unique identifier.</li>
* </ol>
*/
private Function<EObject, String> idComputation = new DefaultIDFunction();
/**
* Creates an ID based matcher without any delegate.
*/
public IdentifierEObjectMatcher() {
this(null, new DefaultIDFunction());
}
/**
* Creates an ID based matcher with the given delegate when no ID can be found.
*
* @param delegateWhenNoID
* the matcher to delegate to when no ID is found.
*/
public IdentifierEObjectMatcher(IEObjectMatcher delegateWhenNoID) {
this(delegateWhenNoID, new DefaultIDFunction());
}
/**
* Creates an ID based matcher computing the ID with the given function.
*
* @param idComputation
* the function used to compute the ID.
*/
public IdentifierEObjectMatcher(Function<EObject, String> idComputation) {
this(null, idComputation);
}
/**
* Create an ID based matcher with a delegate which is going to be called when no ID is found for a given
* EObject. It is computing the ID with the given function
*
* @param delegateWhenNoID
* the delegate matcher to use when no ID is found.
* @param idComputation
* the function used to compute the ID.
*/
public IdentifierEObjectMatcher(IEObjectMatcher delegateWhenNoID, Function<EObject, String> idComputation) {
this.delegate = Optional.fromNullable(delegateWhenNoID);
this.idComputation = idComputation;
}
/**
* {@inheritDoc}
*/
public void createMatches(Comparison comparison, Iterator<? extends EObject> leftEObjects,
Iterator<? extends EObject> rightEObjects, Iterator<? extends EObject> originEObjects,
Monitor monitor) {
final List<EObject> leftEObjectsNoID = Lists.newArrayList();
final List<EObject> rightEObjectsNoID = Lists.newArrayList();
final List<EObject> originEObjectsNoID = Lists.newArrayList();
final Set<Match> matches = matchPerId(leftEObjects, rightEObjects, originEObjects, leftEObjectsNoID,
rightEObjectsNoID, originEObjectsNoID);
Iterables.addAll(comparison.getMatches(), matches);
if (delegate.isPresent()) {
doDelegation(comparison, leftEObjectsNoID, rightEObjectsNoID, originEObjectsNoID, monitor);
} else {
for (EObject eObject : leftEObjectsNoID) {
Match match = CompareFactory.eINSTANCE.createMatch();
match.setLeft(eObject);
matches.add(match);
}
for (EObject eObject : rightEObjectsNoID) {
Match match = CompareFactory.eINSTANCE.createMatch();
match.setRight(eObject);
matches.add(match);
}
for (EObject eObject : originEObjectsNoID) {
Match match = CompareFactory.eINSTANCE.createMatch();
match.setOrigin(eObject);
matches.add(match);
}
}
}
/**
* Execute matching process for the delegated IEObjectMatcher.
*
* @param comparison
* the comparison object that contains the matches
* @param monitor
* the monitor instance to track the matching progress
* @param leftEObjectsNoID
* remaining left objects after matching
* @param rightEObjectsNoID
* remaining right objects after matching
* @param originEObjectsNoID
* remaining origin objects after matching
*/
protected void doDelegation(Comparison comparison, final List<EObject> leftEObjectsNoID,
final List<EObject> rightEObjectsNoID, final List<EObject> originEObjectsNoID, Monitor monitor) {
delegate.get().createMatches(comparison, leftEObjectsNoID.iterator(), rightEObjectsNoID.iterator(),
originEObjectsNoID.iterator(), monitor);
}
/**
* Matches the EObject per ID.
*
* @param leftEObjects
* the objects to match (left side).
* @param rightEObjects
* the objects to match (right side).
* @param originEObjects
* the objects to match (origin side).
* @param leftEObjectsNoID
* remaining left objects after matching
* @param rightEObjectsNoID
* remaining right objects after matching
* @param originEObjectsNoID
* remaining origin objects after matching
* @return the match built in the process.
*/
protected Set<Match> matchPerId(Iterator<? extends EObject> leftEObjects,
Iterator<? extends EObject> rightEObjects, Iterator<? extends EObject> originEObjects,
final List<EObject> leftEObjectsNoID, final List<EObject> rightEObjectsNoID,
final List<EObject> originEObjectsNoID) {
final Set<Match> matches = Sets.newLinkedHashSet();
// This lookup map will be used by iterations on right and origin to find the match in which they
// should add themselves
final Map<String, Match> idToMatch = Maps.newHashMap();
// We will try and mimic the structure of the input model.
// These map do not need to be ordered, we only need fast lookup.
final Map<EObject, Match> leftEObjectsToMatch = Maps.newHashMap();
final Map<EObject, Match> rightEObjectsToMatch = Maps.newHashMap();
final Map<EObject, Match> originEObjectsToMatch = Maps.newHashMap();
// We'll only iterate once on each of the three sides, building the matches as we go
while (leftEObjects.hasNext()) {
final EObject left = leftEObjects.next();
final String identifier = idComputation.apply(left);
if (identifier != null) {
final Match match = CompareFactory.eINSTANCE.createMatch();
match.setLeft(left);
// Can we find a parent? Assume we're iterating in containment order
final EObject parentEObject = left.eContainer();
final Match parent = leftEObjectsToMatch.get(parentEObject);
if (parent != null) {
parent.getSubmatches().add(match);
} else {
matches.add(match);
}
idToMatch.put(identifier, match);
leftEObjectsToMatch.put(left, match);
} else {
leftEObjectsNoID.add(left);
}
}
while (rightEObjects.hasNext()) {
final EObject right = rightEObjects.next();
// Do we have an existing match?
final String identifier = idComputation.apply(right);
if (identifier != null) {
Match match = idToMatch.get(identifier);
if (match != null) {
match.setRight(right);
rightEObjectsToMatch.put(right, match);
} else {
// Otherwise, create and place it.
match = CompareFactory.eINSTANCE.createMatch();
match.setRight(right);
// Can we find a parent?
final EObject parentEObject = right.eContainer();
final Match parent = rightEObjectsToMatch.get(parentEObject);
if (parent != null) {
parent.getSubmatches().add(match);
} else {
matches.add(match);
}
rightEObjectsToMatch.put(right, match);
idToMatch.put(identifier, match);
}
} else {
rightEObjectsNoID.add(right);
}
}
while (originEObjects.hasNext()) {
final EObject origin = originEObjects.next();
// Do we have an existing match?
final String identifier = idComputation.apply(origin);
if (identifier != null) {
Match match = idToMatch.get(identifier);
if (match != null) {
match.setOrigin(origin);
originEObjectsToMatch.put(origin, match);
} else {
// Otherwise, create and place it.
match = CompareFactory.eINSTANCE.createMatch();
match.setOrigin(origin);
// Can we find a parent?
final EObject parentEObject = origin.eContainer();
final Match parent = originEObjectsToMatch.get(parentEObject);
if (parent != null) {
parent.getSubmatches().add(match);
} else {
matches.add(match);
}
idToMatch.put(identifier, match);
originEObjectsToMatch.put(origin, match);
}
} else {
originEObjectsNoID.add(origin);
}
}
return matches;
}
/**
* The default function used to retrieve IDs from EObject. You might want to extend or compose with it if
* you want to reuse its behavior.
*/
public static class DefaultIDFunction implements Function<EObject, String> {
/**
* Return an ID for an EObject, null if not found.
*
* @param eObject
* The EObject for which we need an identifier.
* @return The identifier for that EObject if we could determine one. <code>null</code> if no
* condition (see description above) is fulfilled for the given eObject.
*/
public String apply(EObject eObject) {
final String identifier;
if (eObject == null) {
identifier = null;
} else if (eObject.eIsProxy()) {
identifier = ((InternalEObject)eObject).eProxyURI().fragment();
} else {
String functionalId = EcoreUtil.getID(eObject);
if (functionalId != null) {
identifier = functionalId;
} else {
final Resource eObjectResource = eObject.eResource();
if (eObjectResource instanceof XMIResource) {
identifier = ((XMIResource)eObjectResource).getID(eObject);
} else {
identifier = null;
}
}
}
return identifier;
}
}
}