/**
* <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
*
* </copyright>
*/
package org.eclipse.emf.diffmerge.impl.helpers;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.emf.diffmerge.Messages;
import org.eclipse.emf.diffmerge.api.IComparison;
import org.eclipse.emf.diffmerge.api.IMapping;
import org.eclipse.emf.diffmerge.api.IMatch;
import org.eclipse.emf.diffmerge.api.IMatchPolicy;
import org.eclipse.emf.diffmerge.api.Role;
import org.eclipse.emf.diffmerge.api.scopes.IModelScope;
import org.eclipse.emf.ecore.EObject;
/**
* An operation which builds a mapping between model scopes for a comparison.
* @author Olivier Constant
*/
public class MatchOperation extends AbstractExpensiveOperation {
/** The non-null match policy */
private final IMatchPolicy _policy;
/** The non-null comparison whose mapping is being built */
private final IComparison.Editable _comparison;
/** The optional map from roles to sets of duplicate match IDs */
protected final Map<Role, Set<Object>> _duplicateIDs;
/** Duplicate IDs in role 1 which may have an impact on the mapping */
protected final Set<Object> _duplicateCandidatesRole1;
/** Duplicate IDs in role 2 which may have an impact on the mapping */
protected final Set<Object> _duplicateCandidatesRole2;
/**
* Constructor
* @param comparison_p a non-null comparison whose mapping is to be built
* @param policy_p a non-null match policy
* @param duplicateIDs_p an optional map that associates each role with an empty,
* modifiable set of duplicate match IDs, to be filled by this operation
*/
public MatchOperation(IComparison.Editable comparison_p, IMatchPolicy policy_p,
Map<Role, Set<Object>> duplicateIDs_p) {
super();
_comparison = comparison_p;
_policy = policy_p;
_duplicateIDs = duplicateIDs_p;
_duplicateCandidatesRole1 = new HashSet<Object>(0);
_duplicateCandidatesRole2 = new HashSet<Object>(0);
}
/**
* Create and return a new (match ID, element) empty map
* @return a non-null map
*/
protected Map<Object, EObject> createMatchIDToElementMap() {
Map<Object, EObject> result;
@SuppressWarnings("unchecked") // No issue if properly defined, see IMatchPolicy
Comparator<Object> comparator =
(Comparator<Object>)getMatchPolicy().getMatchIDComparator();
if (comparator == null)
result = new HashMap<Object, EObject>();
else
result = new TreeMap<Object, EObject>(comparator);
return result;
}
/**
* Explore the scope of the given role and fill the mapping with its elements,
* not attempting to match them
* @param role_p a non-null role
* @param fillIDMap_p whether match IDs must be remembered and returned in a map
* @return an unmodifiable map of (criterion, element) with no null value and which is empty if !rememberIDs_p
*/
protected Map<Object, EObject> explore(Role role_p, boolean fillIDMap_p) {
Map<Object, EObject> result;
if (fillIDMap_p)
result = createMatchIDToElementMap();
else
result = Collections.emptyMap();
IModelScope scope = getComparison().getScope(role_p);
boolean rememberMatchIDs = getMatchPolicy().keepMatchIDs();
if (scope != null) {
// Explore the scope, marking its elements as unmatched
// and registering their match IDs
Iterator<EObject> it = scope.getAllContents();
IMapping.Editable mapping = getComparison().getMapping();
while (it.hasNext()) {
checkProgress();
EObject current = it.next();
IMatch.Editable match = mapping.map(current, role_p);
if (rememberMatchIDs || fillIDMap_p) {
Object matchID = getMatchPolicy().getMatchID(current, scope);
if (matchID != null) {
if (rememberMatchIDs)
match.setMatchID(matchID);
if (fillIDMap_p) {
EObject squatter = result.put(matchID, current);
if (squatter != null && squatter != current && _duplicateIDs != null)
_duplicateCandidatesRole1.add(matchID);
}
}
}
}
}
return result;
}
/**
* Explore the scope of the given role and fill the mapping with its elements,
* attempting to match them by match ID according to the ID registries for the
* given secondary roles
* @param role_p a non-null role
* @param idRegistry1_p a non-null map of (ID, element)
* @param secondaryRole1_p a non-null role which is different from role_p
* @param idRegistry2_p a potentially null map of (ID, element)
* @param secondaryRole2_p a role which is different from role_p and secondaryRole1_p
* and which is null iff idRegistry2_p is null
* @param fillIDMap_p whether match IDs must be remembered and returned in a map
* @return an unmodifiable map of (ID, element) with no null value and which is empty if !rememberIDs_p
*/
protected Map<Object, EObject> exploreAndMatch(Role role_p,
Map<Object, EObject> idRegistry1_p, Role secondaryRole1_p,
Map<Object, EObject> idRegistry2_p, Role secondaryRole2_p,
boolean fillIDMap_p) {
Map<Object, EObject> result;
if (fillIDMap_p)
result = createMatchIDToElementMap();
else
result = Collections.emptyMap();
IModelScope scope = getComparison().getScope(role_p);
boolean rememberMatchIDs = getMatchPolicy().keepMatchIDs();
if (scope != null) {
Iterator<EObject> targetIt = scope.getAllContents();
IMapping.Editable mapping = getComparison().getMapping();
while (targetIt.hasNext()) {
checkProgress();
EObject current = targetIt.next();
EObject counterpart1 = null;
EObject counterpart2 = null;
Object matchID = getMatchPolicy().getMatchID(current, scope);
if (matchID != null) {
if (fillIDMap_p) {
EObject squatter = result.put(matchID, current);
if (squatter != null && squatter != current && _duplicateIDs != null)
_duplicateCandidatesRole2.add(matchID);
}
counterpart1 = idRegistry1_p.get(matchID);
counterpart2 = idRegistry2_p != null? idRegistry2_p.get(matchID): null;
}
if (counterpart1 == null && counterpart2 == null) {
IMatch.Editable match = mapping.map(current, role_p);
if (rememberMatchIDs)
match.setMatchID(matchID);
} else {
boolean contradiction = false;
if (counterpart1 != null) {
if (_duplicateCandidatesRole1.contains(matchID) && _duplicateIDs != null)
_duplicateIDs.get(secondaryRole1_p).add(matchID);
contradiction = mapping.mapIncrementally(current, role_p, counterpart1, secondaryRole1_p);
}
if (counterpart2 != null) {
if (_duplicateCandidatesRole2.contains(matchID) && _duplicateIDs != null)
_duplicateIDs.get(secondaryRole2_p).add(matchID);
contradiction = contradiction ||
mapping.mapIncrementally(current, role_p, counterpart2, secondaryRole2_p);
}
if (contradiction && _duplicateIDs != null)
_duplicateIDs.get(role_p).add(matchID); // matchID cannot be null since current was matched
}
}
}
return result;
}
/**
* Return the match policy
* @return a non-null match policy
*/
protected IMatchPolicy getMatchPolicy() {
return _policy;
}
/**
* Return the comparison which is being built
* @return a non-null comparison
*/
public IComparison.Editable getComparison() {
return _comparison;
}
/**
* @see org.eclipse.emf.diffmerge.util.IExpensiveOperation#getOperationName()
*/
public String getOperationName() {
return Messages.MatchBuilder_Task_Main;
}
/**
* @see org.eclipse.emf.diffmerge.impl.helpers.AbstractExpensiveOperation#getWorkAmount()
*/
@Override
protected int getWorkAmount() {
return _comparison.isThreeWay()? 5: 6; // 1 init, 2|3 for ID-based matching, 2 for cross-refs
}
/**
* Fill the mapping destructively
* Postcondition: getOutput().isCompleteFor(TARGET)
* Postcondition: getOutput().isCompleteFor(REFERENCE)
* Postcondition: !getOutput().isThreeWay() || getOutput().isCompleteFor(ANCESTOR)
*/
protected void match() {
final Role firstSide = getComparison().getMapping().getOrderingRole();
final Role secondSide = firstSide.opposite();
boolean threeWay = _comparison.isThreeWay();
getMonitor().subTask(Messages.MatchBuilder_Task_RegisteringIDs);
Map<Object, EObject> firstSideIDRegistry = explore(firstSide, true);
getMonitor().worked(1);
getMonitor().subTask(Messages.MatchBuilder_Task_MappingIDs);
Map<Object, EObject> secondSideIDRegistry = exploreAndMatch(
secondSide, firstSideIDRegistry, firstSide, null, null, threeWay);
getMonitor().worked(1);
if (threeWay) {
exploreAndMatch(Role.ANCESTOR, firstSideIDRegistry, firstSide,
secondSideIDRegistry, secondSide, false);
getMonitor().worked(1);
}
_duplicateCandidatesRole1.clear();
_duplicateCandidatesRole2.clear();
}
/**
* @see org.eclipse.emf.diffmerge.util.IExpensiveOperation#run()
* Postconditions: see MatchOperation#match()
*/
public IStatus run() {
getMonitor().worked(1);
match();
IMapping.Editable mapping = _comparison.getMapping();
mapping.crossReference(Role.TARGET);
getMonitor().worked(1);
mapping.crossReference(Role.REFERENCE);
getMonitor().worked(1);
return Status.OK_STATUS;
}
}