blob: 3bf1782d3e5dcb262e81367002cb4e804e27cf14 [file] [log] [blame]
package org.eclipse.bpel.compare.ui.gef.bpel;
/*******************************************************************************
* Copyright (c) 2006, 2012, 2008 Obeo.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Obeo - initial API and implementation
*******************************************************************************/
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.emf.common.EMFPlugin;
import org.eclipse.emf.common.util.BasicMonitor;
import org.eclipse.emf.common.util.Monitor;
import org.eclipse.emf.common.util.TreeIterator;
import org.eclipse.emf.compare.EMFComparePlugin;
import org.eclipse.emf.compare.FactoryException;
import org.eclipse.emf.compare.match.MatchOptions;
import org.eclipse.emf.compare.match.engine.IMatchEngine;
import org.eclipse.emf.compare.match.internal.statistic.NameSimilarity;
import org.eclipse.emf.compare.match.internal.statistic.StructureSimilarity;
import org.eclipse.emf.compare.match.metamodel.Match2Elements;
import org.eclipse.emf.compare.match.metamodel.Match3Elements;
import org.eclipse.emf.compare.match.metamodel.MatchElement;
import org.eclipse.emf.compare.match.metamodel.MatchFactory;
import org.eclipse.emf.compare.match.metamodel.MatchModel;
import org.eclipse.emf.compare.match.metamodel.Side;
import org.eclipse.emf.compare.match.metamodel.UnmatchElement;
import org.eclipse.emf.compare.match.statistic.MetamodelFilter;
import org.eclipse.emf.compare.util.EFactory;
import org.eclipse.emf.compare.util.EMFCompareMap;
import org.eclipse.emf.compare.util.EMFComparePreferenceConstants;
import org.eclipse.emf.compare.util.EclipseModelUtils;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.emf.ecore.xmi.XMIResource;
/**
* These services are useful when one wants to compare models more precisely using the method modelDiff.
*
* @author <a href="mailto:cedric.brun@obeo.fr">Cedric Brun</a>
*/
public class BPELMatchEngine implements IMatchEngine {
//public class BPELMatchEngine extends GenericMatchEngine {
/** Used while computing similarity, this defines the general threshold. */
private static final double GENERAL_THRESHOLD = 0.96d;
/** Containmnent reference for the matched elements root. */
private static final String MATCH_ELEMENT_NAME = "matchedElements"; //$NON-NLS-1$
/**
* Minimal number of attributes an element must have for content comparison.
*/
private static final int MIN_ATTRIBUTES_COUNT = 5;
/** This constant is used as key for the buffering of name similarity. */
private static final char NAME_SIMILARITY = 'n';
/** This constant is used as key for the buffering of relations similarity. */
private static final char RELATION_SIMILARITY = 'r';
/** Containmnent reference for {@link MatchElement}s' submatches. */
private static final String SUBMATCH_ELEMENT_NAME = "subMatchElements"; //$NON-NLS-1$
/** This constant is used as key for the buffering of type similarity. */
private static final char TYPE_SIMILARITY = 't';
/** Containmnent reference for the {@link MatchModel}'s unmatched elements. */
private static final String UNMATCH_ELEMENT_NAME = "unmatchedElements"; //$NON-NLS-1$
/** This constant is used as key for the buffering of value similarity. */
private static final char VALUE_SIMILARITY = 'v';
/**
* {@link MetamodelFilter} used for filtering unused features of the objects we're computing the
* similarity for.
*/
protected final MetamodelFilter filter = new MetamodelFilter();
/** Contains the options given to the match procedure. */
protected final Map<String, Object> options = new EMFCompareMap<String, Object>();
/**
* This map allows us memorize the {@link EObject} we've been able to match thanks to their functional ID.
*/
private final Map<String, EObject> matchedByID = new EMFCompareMap<String, EObject>();
/**
* This map allows us memorize the {@link EObject} we've been able to match thanks to their XMI ID.
*/
private final Map<String, EObject> matchedByXMIID = new EMFCompareMap<String, EObject>();
/**
* This map is used to cache the comparison results Pair(Element1, Element2) => [nameSimilarity,
* valueSimilarity, relationSimilarity, TypeSimilarity].
*/
private final Map<String, Double> metricsCache = new EMFCompareMap<String, Double>();
/**
* This list allows us to memorize the unMatched elements for a three-way comparison.<br/>
* <p>
* More specifically, we will populate this list with the {@link UnmatchElement}s created by the
* comparison between the left and the ancestor model, followed by the {@link UnmatchElement} created by
* the comparison between the right and the ancestor model.<br/> Those {@link UnmatchElement} will then
* be filtered to retain only those that actually cannot be matched.
* </p>
*/
private final Set<EObject> remainingUnMatchedElements = new HashSet<EObject>();
/**
* This list will be intensively used while matching elements to keep track of the unmatched ones from the
* left model.
*/
private final List<EObject> stillToFindFromModel1 = new ArrayList<EObject>();
/**
* This list will be intensively used while matching elements to keep track of the unmatched ones from the
* right model.
*/
private final List<EObject> stillToFindFromModel2 = new ArrayList<EObject>();
/**
* The options map must be initialized to avoid potential NPEs. This initializer will take care of this
* issue.
*/
{
options.putAll(loadPreferenceOptionMap());
}
/**
* {@inheritDoc}
*
* @see org.eclipse.emf.compare.match.api.IMatchEngine#contentMatch(org.eclipse.emf.ecore.EObject,
* org.eclipse.emf.ecore.EObject, org.eclipse.emf.ecore.EObject, java.util.Map)
*/
public MatchModel contentMatch(EObject leftObject, EObject rightObject, EObject ancestor,
Map<String, Object> optionMap) {
final MatchModel root = MatchFactory.eINSTANCE.createMatchModel();
setModelRoots(root, leftObject.eResource(), rightObject.eResource(), ancestor.eResource());
final Monitor monitor = createProgressMonitor();
final MatchModel leftObjectAncestorMatch = contentMatch(leftObject, ancestor, optionMap);
final MatchModel rightObjectAncestorMatch = contentMatch(rightObject, ancestor, optionMap);
final List<MatchElement> leftObjectMatchedElements = new ArrayList<MatchElement>(
leftObjectAncestorMatch.getMatchedElements());
final List<MatchElement> rightObjectMatchedElements = new ArrayList<MatchElement>(
rightObjectAncestorMatch.getMatchedElements());
// populates the unmatched elements list for later use
for (Object unMatch : leftObjectAncestorMatch.getUnmatchedElements())
remainingUnMatchedElements.add(((UnmatchElement)unMatch).getElement());
for (Object unMatch : rightObjectAncestorMatch.getUnmatchedElements())
remainingUnMatchedElements.add(((UnmatchElement)unMatch).getElement());
try {
Match3Elements subMatchRoot = null;
if (leftObjectMatchedElements.size() > 0 && rightObjectMatchedElements.size() > 0) {
final Match2Elements leftObjectMatchRoot = (Match2Elements)leftObjectMatchedElements.get(0);
final Match2Elements rightObjectMatchRoot = (Match2Elements)rightObjectMatchedElements.get(0);
subMatchRoot = MatchFactory.eINSTANCE.createMatch3Elements();
subMatchRoot.setSimilarity(absoluteMetric(leftObjectMatchRoot.getLeftElement(),
rightObjectMatchRoot.getLeftElement(), rightObjectMatchRoot.getRightElement()));
subMatchRoot.setLeftElement(leftObjectMatchRoot.getLeftElement());
subMatchRoot.setRightElement(rightObjectMatchRoot.getLeftElement());
subMatchRoot.setOriginElement(rightObjectMatchRoot.getRightElement());
redirectedAdd(root, MATCH_ELEMENT_NAME, subMatchRoot);
createSub3Match(root, subMatchRoot, leftObjectMatchRoot, rightObjectMatchRoot);
} else {
for (EObject left : leftObjectMatchedElements)
stillToFindFromModel1.add(left);
for (EObject right : rightObjectMatchedElements)
stillToFindFromModel2.add(right);
}
// We will now check through the unmatched object for matches.
processUnmatchedElements(root, subMatchRoot);
// #createSub3Match(MatchModel, Match3Element, Match2Elements,
// Match2Elements) will have updated "remainingUnMatchedElements"
final Set<EObject> remainingLeft = new HashSet<EObject>();
final Set<EObject> remainingRight = new HashSet<EObject>();
for (EObject unMatched : remainingUnMatchedElements) {
if (unMatched.eResource() == leftObject.eResource()) {
remainingLeft.add(unMatched);
final TreeIterator<EObject> iterator = unMatched.eAllContents();
while (iterator.hasNext())
remainingLeft.add(iterator.next());
} else if (unMatched.eResource() == rightObject.eResource()) {
remainingRight.add(unMatched);
final TreeIterator<EObject> iterator = unMatched.eAllContents();
while (iterator.hasNext())
remainingRight.add(iterator.next());
}
}
stillToFindFromModel1.clear();
stillToFindFromModel2.clear();
final List<Match2Elements> mappings = mapLists(new ArrayList<EObject>(remainingLeft),
new ArrayList<EObject>(remainingRight), this
.<Integer> getOption(MatchOptions.OPTION_SEARCH_WINDOW), monitor);
for (Match2Elements map : mappings) {
final Match3Elements subMatch = MatchFactory.eINSTANCE.createMatch3Elements();
subMatch.setLeftElement(map.getLeftElement());
subMatch.setRightElement(map.getRightElement());
if (subMatchRoot == null)
redirectedAdd(root, MATCH_ELEMENT_NAME, subMatch);
else
redirectedAdd(subMatchRoot, SUBMATCH_ELEMENT_NAME, subMatch);
}
final Map<EObject, Boolean> unMatchedElements = new EMFCompareMap<EObject, Boolean>();
for (EObject remoteUnMatch : stillToFindFromModel1) {
unMatchedElements.put(remoteUnMatch, true);
}
for (EObject unMatch : stillToFindFromModel2) {
unMatchedElements.put(unMatch, false);
}
createThreeWayUnmatchElements(root, unMatchedElements);
} catch (FactoryException e) {
EMFComparePlugin.log(e, false);
} catch (InterruptedException e) {
// Cannot be thrown since we have no monitor
}
return root;
}
/**
* {@inheritDoc}
*
* @see org.eclipse.emf.compare.match.api.IMatchEngine#contentMatch(org.eclipse.emf.ecore.EObject,
* org.eclipse.emf.ecore.EObject, java.util.Map)
*/
public MatchModel contentMatch(EObject leftObject, EObject rightObject, Map<String, Object> optionMap) {
if (optionMap != null && optionMap.size() > 0)
loadOptionMap(optionMap);
final Monitor monitor = createProgressMonitor();
final MatchModel root = MatchFactory.eINSTANCE.createMatchModel();
setModelRoots(root, leftObject.eResource(), rightObject.eResource());
/*
* As we could very well be passed two EClasses (as opposed to modelMatch which compares all roots of
* a resource), we cannot filter the model.
*/
final Set<EObject> still1 = new HashSet<EObject>();
final Set<EObject> still2 = new HashSet<EObject>();
// navigate through both objects at the same time and realize mappings..
try {
if (!this.<Boolean> getOption(MatchOptions.OPTION_IGNORE_XMI_ID))
matchByXMIID(leftObject, rightObject);
if (!this.<Boolean> getOption(MatchOptions.OPTION_IGNORE_ID))
matchByID(leftObject, rightObject);
if (isSimilar(leftObject, rightObject)) {
stillToFindFromModel1.clear();
stillToFindFromModel2.clear();
final Match2Elements matchModelRoot = recursiveMappings(leftObject, rightObject, monitor);
redirectedAdd(root, MATCH_ELEMENT_NAME, matchModelRoot);
createSubMatchElements(matchModelRoot, new ArrayList<EObject>(stillToFindFromModel1),
new ArrayList<EObject>(stillToFindFromModel2), monitor);
still1.addAll(stillToFindFromModel1);
still2.addAll(stillToFindFromModel2);
createUnmatchElements(root, still1, Side.LEFT);
createUnmatchElements(root, still2, Side.RIGHT);
} else {
// The two objects passed as this method's parameters are not
// similar. Creates unmatch root.
still1.add(leftObject);
still1.add(rightObject);
createUnmatchElements(root, still1, null);
}
} catch (FactoryException e) {
EMFComparePlugin.log(e, false);
} catch (InterruptedException e) {
// Cannot be thrown since we have no monitor
}
return root;
}
/**
* {@inheritDoc}
*
* @see org.eclipse.emf.compare.match.api.IMatchEngine#modelMatch(org.eclipse.emf.ecore.EObject,
* org.eclipse.emf.ecore.EObject, org.eclipse.emf.ecore.EObject, java.util.Map)
*/
public MatchModel modelMatch(EObject leftRoot, EObject rightRoot, EObject ancestor,
Map<String, Object> optionMap) throws InterruptedException {
if (optionMap != null && optionMap.size() > 0)
loadOptionMap(optionMap);
MatchModel result = null;
// Creates and sizes progress monitor
final Monitor monitor = createProgressMonitor();
int size = 1;
for (EObject root : leftRoot.eResource().getContents()) {
final Iterator<EObject> rootContent = root.eAllContents();
while (rootContent.hasNext()) {
rootContent.next();
size++;
}
}
startMonitor(monitor, size * 2);
result = doMatch(leftRoot.eResource(), rightRoot.eResource(), ancestor.eResource(), monitor);
return result;
}
/**
* {@inheritDoc}
*
* @see org.eclipse.emf.compare.match.api.IMatchEngine#modelMatch(org.eclipse.emf.ecore.EObject,
* org.eclipse.emf.ecore.EObject, java.util.Map)
*/
public MatchModel modelMatch(EObject leftRoot, EObject rightRoot, Map<String, Object> optionMap)
throws InterruptedException {
if (optionMap != null && optionMap.size() > 0)
loadOptionMap(optionMap);
MatchModel result = null;
// Creates and sizes progress monitor
final Monitor monitor = createProgressMonitor();
int size = 1;
for (EObject root : leftRoot.eResource().getContents()) {
final Iterator<EObject> rootContent = root.eAllContents();
while (rootContent.hasNext()) {
rootContent.next();
size++;
}
}
startMonitor(monitor, size);
result = doMatch(leftRoot.eResource(), rightRoot.eResource(), monitor);
return result;
}
/**
* {@inheritDoc}
*
* @see org.eclipse.emf.compare.match.api.IMatchEngine#reset()
*/
public void reset() {
filter.clear();
options.clear();
matchedByID.clear();
matchedByXMIID.clear();
metricsCache.clear();
remainingUnMatchedElements.clear();
stillToFindFromModel1.clear();
stillToFindFromModel2.clear();
options.putAll(loadPreferenceOptionMap());
}
/**
* {@inheritDoc}
*
* @see org.eclipse.emf.compare.match.api.IMatchEngine#resourceMatch(org.eclipse.emf.ecore.resource.Resource,
* org.eclipse.emf.ecore.resource.Resource, java.util.Map)
*/
public MatchModel resourceMatch(Resource leftResource, Resource rightResource,
Map<String, Object> optionMap) throws InterruptedException {
if (optionMap != null && optionMap.size() > 0)
loadOptionMap(optionMap);
MatchModel result = null;
// Creates and sizes progress monitor
final Monitor monitor = createProgressMonitor();
int size = 1;
for (EObject root : leftResource.getContents()) {
final Iterator<EObject> rootContent = root.eAllContents();
while (rootContent.hasNext()) {
rootContent.next();
size++;
}
}
startMonitor(monitor, size);
result = doMatch(leftResource, rightResource, monitor);
return result;
}
/**
* {@inheritDoc}
*
* @see org.eclipse.emf.compare.match.api.IMatchEngine#resourceMatch(org.eclipse.emf.ecore.resource.Resource,
* org.eclipse.emf.ecore.resource.Resource, org.eclipse.emf.ecore.resource.Resource, java.util.Map)
*/
public MatchModel resourceMatch(Resource leftResource, Resource rightResource, Resource ancestorResource,
Map<String, Object> optionMap) throws InterruptedException {
if (optionMap != null && optionMap.size() > 0)
loadOptionMap(optionMap);
MatchModel result = null;
// Creates and sizes progress monitor
final Monitor monitor = createProgressMonitor();
int size = 1;
for (EObject root : leftResource.getContents()) {
final Iterator<EObject> rootContent = root.eAllContents();
while (rootContent.hasNext()) {
rootContent.next();
size++;
}
}
startMonitor(monitor, size * 2);
result = doMatch(leftResource, rightResource, ancestorResource, monitor);
return result;
}
/**
* {@inheritDoc}
*
* @see org.eclipse.emf.compare.match.api.IMatchEngine#resourceSetMatch(org.eclipse.emf.ecore.resource.ResourceSet,
* org.eclipse.emf.ecore.resource.ResourceSet, java.util.Map)
*/
public MatchModel resourceSetMatch(ResourceSet leftResourceSet, ResourceSet rightResourceSet,
Map<String, Object> optionMap) {
// TODO this should be implemented. It will break both match and diff
// MMs so wait till 0.9/1.0.
throw new UnsupportedOperationException("Not implemented yet."); //$NON-NLS-1$
}
/**
* {@inheritDoc}
*
* @see org.eclipse.emf.compare.match.api.IMatchEngine#resourceSetMatch(org.eclipse.emf.ecore.resource.ResourceSet,
* org.eclipse.emf.ecore.resource.ResourceSet, org.eclipse.emf.ecore.resource.ResourceSet,
* java.util.Map)
*/
public MatchModel resourceSetMatch(ResourceSet leftResourceSet, ResourceSet rightResourceSet,
ResourceSet ancestorResourceSet, Map<String, Object> optionMap) {
// TODO this should be implemented. It will break both match and diff
// MMs so wait till 0.9/1.0.
throw new UnsupportedOperationException("Not implemented yet."); //$NON-NLS-1$
}
/**
* This will compute the similarity between two {@link EObject}s' contents.
*
* @param obj1
* First of the two {@link EObject}s.
* @param obj2
* Second of the two {@link EObject}s.
* @return <code>double</code> representing the similarity between the two {@link EObject}s' contents.
* 0 &lt; value &lt; 1.
* @throws FactoryException
* Thrown if we cannot compute the {@link EObject}s' contents similarity metrics.
* @see NameSimilarity#contentValue(EObject, MetamodelFilter)
*/
protected double contentSimilarity(EObject obj1, EObject obj2) throws FactoryException {
double similarity = 0d;
final Double value = getSimilarityFromCache(obj1, obj2, VALUE_SIMILARITY);
if (value != null) {
similarity = value;
} else {
similarity = NameSimilarity.nameSimilarityMetric(NameSimilarity.contentValue(obj1, filter),
NameSimilarity.contentValue(obj2, filter));
setSimilarityInCache(obj1, obj2, VALUE_SIMILARITY, similarity);
}
return similarity;
}
/**
* This will iterate through the given {@link List} and return its element which is most similar (as given
* by {@link #absoluteMetric(EObject, EObject)}) to the given {@link EObject}.
*
* @param eObj
* {@link EObject} we're searching a similar item for in the list.
* @param list
* {@link List} in which we are to find an object similar to <code>eObj</code>.
* @return The element from <code>list</code> which is the most similar to <code>eObj</code>.
* @throws FactoryException
* Thrown if we cannot compute the {@link #absoluteMetric(EObject, EObject) absolute metric}
* between <code>eObj</code> and one of the list's objects.
*/
protected EObject findMostSimilar(EObject eObj, List<EObject> list) throws FactoryException {
double max = 0d;
EObject resultObject = null;
final Iterator<EObject> it = list.iterator();
while (it.hasNext() && max != 1.0d) {
final EObject next = it.next();
if (this.<Boolean> getOption(MatchOptions.OPTION_DISTINCT_METAMODELS)
|| eObj.eClass() == next.eClass()) {
final double similarity = absoluteMetric(eObj, next);
if (similarity > max) {
max = similarity;
resultObject = next;
}
}
}
return resultObject;
}
/**
* This will return the value associated to the given key in the options map.
* <p>
* NOTE : Misuses of this method will easily throw {@link ClassCastException}s.
* </p>
*
* @param <T>
* Expected type of the value associated to <code>key</code>.
* @param key
* Key of the value to retrieve.
* @return Value associated to the given key in the options map.
* @throws ClassCastException
* If the value isn't assignment compatible with the expected type.
*/
@SuppressWarnings("unchecked")
protected <T> T getOption(String key) throws ClassCastException {
return (T)options.get(key);
}
/**
* This will lookup in the {@link #matchedByID} map and check if the two given objects have indeed been
* matched by their ID.
*
* @param left
* Left of the two objects to check.
* @param right
* right of the two objects to check.
* @return <code>True</code> these objects haven't been matched by their ID, <code>False</code>
* otherwise.
* @throws FactoryException
* Thrown if we cannot compute the key for the object to match.
*/
protected boolean haveDistinctID(EObject left, EObject right) throws FactoryException {
boolean result = false;
final StringBuilder leftKey = new StringBuilder();
leftKey.append(NameSimilarity.findName(left));
leftKey.append(left.hashCode());
final EObject matched = matchedByID.get(leftKey.toString());
// must be the same instance
if (matched != null)
result = matched != right;
else
// we didn't match a single element with this ID.
// This could be because no IDs are defined.
result = EcoreUtil.getID(left) != null;
return result;
}
/**
* This will lookup in the {@link #matchedByXMIID} map and check if the two given objects have indeed been
* matched by their XMI ID.
*
* @param left
* Left of the two objects to check.
* @param right
* right of the two objects to check.
* @return <code>True</code> these objects haven't been matched by their XMI ID, <code>False</code>
* otherwise.
* @throws FactoryException
* Thrown if we cannot compute the key for the object to match.
*/
protected boolean haveDistinctXMIID(EObject left, EObject right) throws FactoryException {
boolean result = false;
final StringBuilder leftKey = new StringBuilder();
leftKey.append(NameSimilarity.findName(left));
leftKey.append(left.hashCode());
final EObject matched = matchedByXMIID.get(leftKey.toString());
// must be the same instance
if (matched != null)
result = matched != right;
else
// we didn't match a single element with this ID.
// This could be because no IDs are defined.
result = left.eResource() instanceof XMIResource
&& ((XMIResource)left.eResource()).getID(left) != null;
return result;
}
/**
* Returns <code>True</code> if the 2 given {@link EObject}s are considered similar.
*
* @param obj1
* The first {@link EObject} to compare.
* @param obj2
* Second of the {@link EObject}s to compare.
* @return <code>True</code> if both elements have the same serialization ID, <code>False</code>
* otherwise.
* @throws FactoryException
* Thrown if we cannot compute one of the needed similarity.
*/
protected boolean isSimilar(EObject obj1, EObject obj2) throws FactoryException {
boolean similar = false;
// Defines threshold constants to assume objects' similarity
final double nameOnlyMetricThreshold = 0.7d;
final double fewerAttributesNameThreshold = 0.8d;
final double relationsThreshold = 0.3d;
final double nameThreshold = 0.2d;
final double contentThreshold = 0.9d;
final double triWayThreshold = 0.9d;
final double generalThreshold = GENERAL_THRESHOLD;
// Computes some of the required metrics
final double nameSimilarity = nameSimilarity(obj1, obj2);
final boolean hasSameUri = hasSameUri(obj1, obj2);
final int obj1NonNullFeatures = nonNullFeaturesCount(obj1);
final int obj2NonNullFeatures = nonNullFeaturesCount(obj2);
if (!this.<Boolean> getOption(MatchOptions.OPTION_DISTINCT_METAMODELS)
&& obj1.eClass() != obj2.eClass()) {
similar = false;
} else if (!this.<Boolean> getOption(MatchOptions.OPTION_IGNORE_ID) && haveDistinctID(obj1, obj2)) {
similar = false;
} else if (!this.<Boolean> getOption(MatchOptions.OPTION_IGNORE_XMI_ID)
&& haveDistinctXMIID(obj1, obj2)) {
similar = false;
} else if (nameSimilarity == 1) {
similar = true;
// softer tests if we don't have enough attributes to compare the
// objects
} else if (obj1NonNullFeatures == 1 && obj2NonNullFeatures == 1) {
similar = nameSimilarity > nameOnlyMetricThreshold;
} else if (nameSimilarity > fewerAttributesNameThreshold
&& obj1NonNullFeatures <= MIN_ATTRIBUTES_COUNT && obj2NonNullFeatures <= MIN_ATTRIBUTES_COUNT
&& typeSimilarity(obj1, obj2) > generalThreshold) {
similar = true;
} else {
final double contentSimilarity = contentSimilarity(obj1, obj2);
final double relationsSimilarity = relationsSimilarity(obj1, obj2);
if (relationsSimilarity == 1 && hasSameUri && nameSimilarity > nameThreshold) {
similar = true;
} else if (contentSimilarity == 1 && relationsSimilarity == 1) {
similar = true;
} else if (contentSimilarity > generalThreshold && relationsSimilarity > relationsThreshold
&& nameSimilarity > nameThreshold) {
similar = true;
} else if (relationsSimilarity > generalThreshold && contentSimilarity > contentThreshold) {
similar = true;
} else if (contentSimilarity > triWayThreshold && nameSimilarity > triWayThreshold
&& relationsSimilarity > triWayThreshold) {
similar = true;
} else if (contentSimilarity > generalThreshold && nameSimilarity > generalThreshold
&& typeSimilarity(obj1, obj2) > generalThreshold) {
similar = true;
}
}
return similar;
}
/**
* This will compute the similarity between two {@link EObject}s' names.
*
* @param obj1
* First of the two {@link EObject}s.
* @param obj2
* Second of the two {@link EObject}s.
* @return <code>double</code> representing the similarity between the two {@link EObject}s' names. 0
* &lt; value &lt; 1.
* @see NameSimilarity#nameSimilarityMetric(String, String)
*/
protected double nameSimilarity(EObject obj1, EObject obj2) {
double similarity = 0d;
try {
final Double value = getSimilarityFromCache(obj1, obj2, NAME_SIMILARITY);
if (value != null) {
similarity = value;
} else {
similarity = NameSimilarity.nameSimilarityMetric(NameSimilarity.findName(obj1),
NameSimilarity.findName(obj2));
setSimilarityInCache(obj1, obj2, NAME_SIMILARITY, similarity);
}
} catch (FactoryException e) {
// fails silently, will return a similarity of 0d
}
return similarity;
}
/*
* created as package visibility method to allow access from initializer's listener. Shouldn't be further
* opened.
*/
/**
* This will load all the needed options with their default values.
*
* @return Map containing all the needed options with their default values.
*/
/* package */Map<String, Object> loadPreferenceOptionMap() {
final Map<String, Object> optionMap = new EMFCompareMap<String, Object>(17);
optionMap.put(MatchOptions.OPTION_SEARCH_WINDOW, getPreferenceSearchWindow());
optionMap.put(MatchOptions.OPTION_IGNORE_ID, getPreferenceIgnoreID());
optionMap.put(MatchOptions.OPTION_IGNORE_XMI_ID, getPreferenceIgnoreXMIID());
optionMap.put(MatchOptions.OPTION_DISTINCT_METAMODELS, getPreferenceDistinctMetaModel());
optionMap.put(MatchOptions.OPTION_PROGRESS_MONITOR, null);
return optionMap;
}
/**
* Returns an absolute comparison metric between the two given {@link EObject}s.
*
* @param obj1
* The first {@link EObject} to compare.
* @param obj2
* Second of the {@link EObject}s to compare.
* @return An absolute comparison metric. 0 &lt; value &lt; 1.
* @throws FactoryException
* Thrown if we cannot compute the content similarity.
*/
private double absoluteMetric(EObject obj1, EObject obj2) throws FactoryException {
final double nameSimilarity = nameSimilarity(obj1, obj2);
if (nameSimilarity == 1.0d) {
return 1.0;
}
final double relationsSimilarity = relationsSimilarity(obj1, obj2);
double sameUri = 0d;
if (hasSameUri(obj1, obj2))
sameUri = 1;
final double positionSimilarity = relationsSimilarity / 2d + sameUri / 2d;
final double contentWeight = 0.5d;
if (nonNullFeaturesCount(obj1) > MIN_ATTRIBUTES_COUNT
&& nonNullFeaturesCount(obj2) > MIN_ATTRIBUTES_COUNT) {
final double nameWeight = 0.8d;
final double positionWeight = 0.4d;
final double contentSimilarity = contentSimilarity(obj1, obj2);
// Computing type similarity really is time expensive
// double typeSimilarity = typeSimilarity(obj1, obj2);
return (contentSimilarity * contentWeight + nameSimilarity * nameWeight + positionSimilarity
* positionWeight)
/ (contentWeight + nameWeight + positionWeight);
}
// we didn't have enough features to compute an accurate metric
final double nameWeight = 0.4d;
final double positionWeight = 0.2d;
return (nameSimilarity * nameWeight + positionSimilarity * positionWeight)
/ (nameWeight + positionWeight);
}
/**
* Returns an absolute comparison metric between the three given {@link EObject}s.
*
* @param obj1
* The first {@link EObject} to compare.
* @param obj2
* Second of the {@link EObject}s to compare.
* @param obj3
* Second of the {@link EObject}s to compare.
* @return An absolute comparison metric
* @throws FactoryException
* Thrown if we cannot compute the content similarity.
*/
private double absoluteMetric(EObject obj1, EObject obj2, EObject obj3) throws FactoryException {
final double metric1 = absoluteMetric(obj1, obj2);
final double metric2 = absoluteMetric(obj1, obj3);
final double metric3 = absoluteMetric(obj2, obj3);
return (metric1 + metric2 + metric3) / 3;
}
/**
* This will recursively create three-way submatches and add them under the given {@link MatchModel}. The
* two {@link Match2Elements} we consider as parameters are the result of the two-way comparisons between :
* <ul>
* <li>The left and origin model.</li>
* <li>The right and origin model.</li>
* </ul>
* <br/><br/>We can then consider that a {@link Match3Element} would be :
*
* <pre>
* match.leftElement = left.getLeftElement();
* match.originElement = left.getRightElement() = right.getRightElement();
* match.rightElement = right.getLeftElement();
* </pre>
*
* @param root
* {@link MatchModel} under which to add our {@link Match3Element}s.
* @param matchElementRoot
* Root of the {@link Match3Element}s' hierarchy for the current element to be created.
* @param left
* Left {@link Match2Elements} to consider.
* @param right
* Right {@link Match2Elements} to consider.
* @throws FactoryException
* Thrown if we cannot compute the {@link #absoluteMetric(EObject, EObject, EObject) absolute
* metric} between the three elements or if we cannot add a {@link Match3Element} under the
* given <code>matchElementRoot</code>.
*/
private void createSub3Match(MatchModel root, Match3Elements matchElementRoot, Match2Elements left,
Match2Elements right) throws FactoryException {
final List<MatchElement> leftSubMatches = left.getSubMatchElements();
final List<MatchElement> rightSubMatches = right.getSubMatchElements();
final List<MatchElement> leftNotFound = new ArrayList<MatchElement>(leftSubMatches);
final List<MatchElement> rightNotFound = new ArrayList<MatchElement>(rightSubMatches);
for (MatchElement nextLeft : leftSubMatches) {
final Match2Elements nextLeftMatch = (Match2Elements)nextLeft;
Match2Elements correspondingMatch = null;
for (MatchElement nextRight : rightNotFound) {
final Match2Elements nextRightMatch = (Match2Elements)nextRight;
if (nextRightMatch.getRightElement().equals(nextLeftMatch.getRightElement())) {
correspondingMatch = nextRightMatch;
break;
}
}
if (correspondingMatch != null) {
final Match3Elements match = MatchFactory.eINSTANCE.createMatch3Elements();
match.setSimilarity(absoluteMetric(nextLeftMatch.getLeftElement(), correspondingMatch
.getLeftElement(), correspondingMatch.getRightElement()));
match.setLeftElement(nextLeftMatch.getLeftElement());
match.setRightElement(correspondingMatch.getLeftElement());
match.setOriginElement(correspondingMatch.getRightElement());
redirectedAdd(matchElementRoot, SUBMATCH_ELEMENT_NAME, match);
createSub3Match(root, matchElementRoot, nextLeftMatch, correspondingMatch);
leftNotFound.remove(nextLeftMatch);
rightNotFound.remove(correspondingMatch);
}
}
for (MatchElement nextLeftNotFound : leftNotFound) {
stillToFindFromModel1.add(nextLeftNotFound);
}
for (MatchElement nextRightNotFound : rightNotFound) {
stillToFindFromModel2.add(nextRightNotFound);
}
}
/**
* Creates the {@link Match2Elements submatch elements} corresponding to the mapping of objects from the
* two given {@link List}s.
*
* @param root
* Root of the {@link MatchModel} where to insert all these mappings.
* @param list1
* First of the lists used to compute mapping.
* @param list2
* Second of the lists used to compute mapping.
* @param monitor
* {@link CompareProgressMonitor progress monitor} to display while the comparison lasts. Might
* be <code>null</code>, in which case we won't monitor progress.
* @throws FactoryException
* Thrown if we cannot match the elements of the two lists or add submatch elements to
* <code>root</code>.
* @throws InterruptedException
* Thrown if the operation is cancelled or fails somehow.
*/
private void createSubMatchElements(EObject root, List<EObject> list1, List<EObject> list2,
Monitor monitor) throws FactoryException, InterruptedException {
stillToFindFromModel1.clear();
stillToFindFromModel2.clear();
final List<Match2Elements> mappings = mapLists(list1, list2, this
.<Integer> getOption(MatchOptions.OPTION_SEARCH_WINDOW), monitor);
final Iterator<Match2Elements> it = mappings.iterator();
while (it.hasNext()) {
final Match2Elements map = it.next();
final Match2Elements match = recursiveMappings(map.getLeftElement(), map.getRightElement(),
monitor);
redirectedAdd(root, SUBMATCH_ELEMENT_NAME, match);
}
}
/**
* Creates {@link UnmatchElement}s and {@link RemoteUnmatchElement}s wrapped around all the elements of
* the given {@link List}.
*
* @param root
* Root of the {@link MatchModel} under which to insert all these elements.
* @param unMatchedElements
* {@link List} containing all the elements we haven't been able to match.
* @throws FactoryException
* Thrown if we cannot add elements under the given {@link MatchModel root}.
*/
private void createThreeWayUnmatchElements(MatchModel root, Map<EObject, Boolean> unMatchedElements)
throws FactoryException {
for( Map.Entry<EObject, Boolean> entry : unMatchedElements.entrySet()) {
// We will only consider the highest level of an unmatched element
// hierarchy
if (!unMatchedElements.containsKey(entry.getKey().eContainer())) {
final UnmatchElement unMap;
if( entry.getValue()) {
unMap = MatchFactory.eINSTANCE.createUnmatchElement();
unMap.setRemote(true);
}
else
unMap = MatchFactory.eINSTANCE.createUnmatchElement();
unMap.setElement( entry.getKey());
redirectedAdd(root, UNMATCH_ELEMENT_NAME, unMap);
}
}
unMatchedElements.clear();
}
/**
* Creates {@link UnmatchElement}s wrapped around all the elements of the given {@link List}.
*
* @param root
* Root of the {@link MatchModel} under which to insert all these {@link UnmatchElement}s.
* @param unMatchedElements
* {@link Set} containing all the elements we haven't been able to match.
* @throws FactoryException
* Thrown if we cannot add elements under the given {@link MatchModel root}.
*/
private void createUnmatchElements(MatchModel root, Set<EObject> unMatchedElements, Side side)
throws FactoryException {
for (EObject element : unMatchedElements) {
final UnmatchElement unMap = MatchFactory.eINSTANCE.createUnmatchElement();
unMap.setElement(element);
if (side != null)
unMap.setSide(side);
redirectedAdd(root, UNMATCH_ELEMENT_NAME, unMap);
}
unMatchedElements.clear();
}
/**
* This method handles the creation and returning of a two way model match.
*
* @param leftResource
* Left model for the comparison.
* @param rightResource
* Right model for the comparison.
* @param monitor
* Progress monitor to display while the comparison lasts.
* @return The corresponding {@link MatchModel}.
* @throws InterruptedException
* Thrown if the comparison is interrupted somehow.
*/
private MatchModel doMatch(Resource leftResource, Resource rightResource, Monitor monitor)
throws InterruptedException {
final MatchModel root = MatchFactory.eINSTANCE.createMatchModel();
setModelRoots(root, leftResource, rightResource);
// filters unused features
filterUnused(leftResource);
filterUnused(rightResource);
// navigate through both models at the same time and realize mappings..
try {
if (!this.<Boolean> getOption(MatchOptions.OPTION_IGNORE_XMI_ID))
if (leftResource instanceof XMIResource && rightResource instanceof XMIResource)
matchByXMIID((XMIResource)leftResource, (XMIResource)rightResource);
if (!this.<Boolean> getOption(MatchOptions.OPTION_IGNORE_ID))
matchByID(leftResource, rightResource);
monitor.subTask("Matching roots"); //$NON-NLS-1$
final List<Match2Elements> matchedRoots = mapLists(leftResource.getContents(), rightResource
.getContents(), this.<Integer> getOption(MatchOptions.OPTION_SEARCH_WINDOW), monitor);
stillToFindFromModel1.clear();
stillToFindFromModel2.clear();
final List<EObject> unMatchedLeftRoots = new ArrayList<EObject>(leftResource.getContents());
final List<EObject> unMatchedRightRoots = new ArrayList<EObject>(rightResource.getContents());
// These sets will help us in keeping track of the yet to be found
// elements
final Set<EObject> still1 = new HashSet<EObject>();
final Set<EObject> still2 = new HashSet<EObject>();
// If one of the resources has no roots, considers it as deleted
if (leftResource.getContents().size() > 0 && rightResource.getContents().size() > 0) {
Match2Elements matchModelRoot = MatchFactory.eINSTANCE.createMatch2Elements();
// We haven't found any similar roots, we then consider the
// firsts to be similar.
if (matchedRoots.size() == 0) {
final Match2Elements rootMapping = MatchFactory.eINSTANCE.createMatch2Elements();
rootMapping.setLeftElement(leftResource.getContents().get(0));
EObject rightElement = findMostSimilar(leftResource.getContents().get(0),
unMatchedRightRoots);
if (rightElement == null)
rightElement = unMatchedRightRoots.get(0);
rootMapping.setRightElement(rightElement);
matchedRoots.add(rootMapping);
}
monitor.subTask("Processing matched roots' contents"); //$NON-NLS-1$
for (Match2Elements matchedRoot : matchedRoots) {
List<EObject> rightContentsWithChildren = getContentsWithChildren(matchedRoot.getRightElement());
final Match2Elements rootMapping = myRecursiveMappings(matchedRoot.getLeftElement(),
matchedRoot.getRightElement(), rightContentsWithChildren, monitor);
stillToFindFromModel2.addAll(rightContentsWithChildren);
// final Match2Elements rootMapping = recursiveMappings(matchedRoot.getLeftElement(),
// matchedRoot.getRightElement(), monitor);
// this is the first passage
if (matchModelRoot.getLeftElement() == null) {
matchModelRoot = rootMapping;
redirectedAdd(root, MATCH_ELEMENT_NAME, matchModelRoot);
} else {
redirectedAdd(matchModelRoot, SUBMATCH_ELEMENT_NAME, rootMapping);
}
// Synchronizes the two lists to avoid multiple elements
still1.removeAll(stillToFindFromModel1);
still2.removeAll(stillToFindFromModel2);
// checks for matches within the yet to found elements lists
createSubMatchElements(rootMapping, new ArrayList<EObject>(stillToFindFromModel1),
new ArrayList<EObject>(stillToFindFromModel2), monitor);
// Adds all unfound elements to the sets
still1.addAll(stillToFindFromModel1);
still2.addAll(stillToFindFromModel2);
unMatchedLeftRoots.remove(matchedRoot.getLeftElement());
unMatchedRightRoots.remove(matchedRoot.getRightElement());
}
// We'll iterate through the unMatchedRoots all contents
monitor.subTask("Processing unmatched roots"); //$NON-NLS-1$
createSubMatchElements(matchModelRoot, unMatchedLeftRoots, unMatchedRightRoots, monitor);
} else {
// Roots are unmatched, this is either a file addition or
// deletion
still1.addAll(unMatchedLeftRoots);
still2.addAll(unMatchedRightRoots);
}
// Now takes care of remaining unfound elements
still1.addAll(stillToFindFromModel1);
still2.addAll(stillToFindFromModel2);
createUnmatchElements(root, still1, Side.LEFT);
createUnmatchElements(root, still2, Side.RIGHT);
} catch (FactoryException e) {
EMFComparePlugin.log(e, false);
}
return root;
}
/**
* This method handles the creation and returning of a three way model match.
*
* @param leftResource
* Left model for the comparison.
* @param rightResource
* Right model for the comparison.
* @param ancestorResource
* Common ancestor of the right and left models.
* @param monitor
* Progress monitor to display while the comparison lasts.
* @return The corresponding {@link MatchModel}.
* @throws InterruptedException
* Thrown if the comparison is interrupted somehow.
*/
private MatchModel doMatch(Resource leftResource, Resource rightResource, Resource ancestorResource,
Monitor monitor) throws InterruptedException {
final MatchModel root = MatchFactory.eINSTANCE.createMatchModel();
setModelRoots(root, leftResource, rightResource, ancestorResource);
final MatchModel root1AncestorMatch = doMatch(leftResource, ancestorResource, monitor);
final MatchModel root2AncestorMatch = doMatch(rightResource, ancestorResource, monitor);
final List<MatchElement> root1MatchedElements = new ArrayList<MatchElement>(root1AncestorMatch
.getMatchedElements());
final List<MatchElement> root2MatchedElements = new ArrayList<MatchElement>(root2AncestorMatch
.getMatchedElements());
// populates the unmatched elements list for later use
for (Object unMatch : root1AncestorMatch.getUnmatchedElements())
remainingUnMatchedElements.add(((UnmatchElement)unMatch).getElement());
for (Object unMatch : root2AncestorMatch.getUnmatchedElements())
remainingUnMatchedElements.add(((UnmatchElement)unMatch).getElement());
try {
final Match3Elements subMatchRoot = MatchFactory.eINSTANCE.createMatch3Elements();
if (root2MatchedElements.size() > 0) {
final Match2Elements root1Match = (Match2Elements)root1MatchedElements.get(0);
final Match2Elements root2Match = (Match2Elements)root2MatchedElements.get(0);
subMatchRoot.setSimilarity(absoluteMetric(root1Match.getLeftElement(), root2Match
.getLeftElement(), root2Match.getRightElement()));
subMatchRoot.setLeftElement(root1Match.getLeftElement());
subMatchRoot.setRightElement(root2Match.getLeftElement());
subMatchRoot.setOriginElement(root2Match.getRightElement());
redirectedAdd(root, MATCH_ELEMENT_NAME, subMatchRoot);
createSub3Match(root, subMatchRoot, root1Match, root2Match);
} else if (root1MatchedElements.size() > 0) {
stillToFindFromModel1.add(root1MatchedElements.get(0));
}
// We will now check through the unmatched object for matches. This
// will allow for a more accurate detection
// for models with multiple roots.
processUnmatchedElements(root, subMatchRoot);
// #processUnmatchedElements(MatchModel, Match3Element)
// will have updated "remainingUnMatchedElements"
final Set<EObject> remainingLeft = new HashSet<EObject>();
final Set<EObject> remainingRight = new HashSet<EObject>();
for (EObject unMatched : remainingUnMatchedElements) {
if (unMatched.eResource() == leftResource) {
remainingLeft.add(unMatched);
final TreeIterator<EObject> iterator = unMatched.eAllContents();
while (iterator.hasNext())
remainingLeft.add(iterator.next());
} else if (unMatched.eResource() == rightResource) {
remainingRight.add(unMatched);
final TreeIterator<EObject> iterator = unMatched.eAllContents();
while (iterator.hasNext())
remainingRight.add(iterator.next());
}
}
stillToFindFromModel1.clear();
stillToFindFromModel2.clear();
final List<Match2Elements> mappings = mapLists(new ArrayList<EObject>(remainingLeft),
new ArrayList<EObject>(remainingRight), this
.<Integer> getOption(MatchOptions.OPTION_SEARCH_WINDOW), monitor);
for (Match2Elements map : mappings) {
final Match3Elements subMatch = MatchFactory.eINSTANCE.createMatch3Elements();
subMatch.setLeftElement(map.getLeftElement());
subMatch.setRightElement(map.getRightElement());
redirectedAdd(subMatchRoot, SUBMATCH_ELEMENT_NAME, subMatch);
}
final Map<EObject, Boolean> unMatchedElements = new EMFCompareMap<EObject, Boolean>();
for (EObject remoteUnMatch : stillToFindFromModel1) {
unMatchedElements.put(remoteUnMatch, true);
}
for (EObject unMatch : stillToFindFromModel2) {
unMatchedElements.put(unMatch, false);
}
createThreeWayUnmatchElements(root, unMatchedElements);
} catch (FactoryException e) {
EMFComparePlugin.log(e, false);
}
return root;
}
/**
* Filters unused features of the resource.
*
* @param resource
* Resource to be apply filter on.
*/
private void filterUnused(Resource resource) {
for (EObject root : resource.getContents())
filter.analyseModel(root);
}
/**
* Workaround for bug #235606 : elements held by a reference with containment=true and derived=true are
* not matched since not returned by {@link EObject#eContents()}. This allows us to return the list of all
* contents from an EObject <u>including</u> those references.
*
* @param eObject
* The EObject we seek the content of.
* @return The list of all the content of a given EObject, derived containmnent references included.
*/
@SuppressWarnings("unchecked")
private List<EObject> getContents(EObject eObject) {
// TODO can this be cached (Map<EClass, List<EReference>>)?
final List<EObject> result = new ArrayList(eObject.eContents());
for (EReference reference : eObject.eClass().getEAllReferences()) {
if (reference.isContainment() && reference.isDerived()) {
final Object value = eObject.eGet(reference);
if (value instanceof Collection)
result.addAll((Collection)value);
else if (value instanceof EObject)
result.add((EObject)value);
}
}
return result;
}
/**
* Returns whether we should assume the metamodels of the compared models are distinct.
*
* @return <code>true</code> if the metamodels are to be assumed distinct, <code>false</code>
* otherwise.
*/
private boolean getPreferenceDistinctMetaModel() {
if (EMFPlugin.IS_ECLIPSE_RUNNING && EMFComparePlugin.getDefault() != null)
return EMFComparePlugin.getDefault().getBoolean(
EMFComparePreferenceConstants.PREFERENCES_KEY_DISTINCT_METAMODEL);
return MatchOptions.DEFAULT_DISTINCT_METAMODEL;
}
/**
* Returns whether we should ignore the IDs or compare using them.
*
* @return <code>True</code> if we should ignore ID, <code>False</code> otherwise.
*/
private boolean getPreferenceIgnoreID() {
if (EMFPlugin.IS_ECLIPSE_RUNNING && EMFComparePlugin.getDefault() != null)
return EMFComparePlugin.getDefault().getBoolean(
EMFComparePreferenceConstants.PREFERENCES_KEY_IGNORE_ID);
return MatchOptions.DEFAULT_IGNORE_ID;
}
/**
* Returns whether we should ignore the XMI IDs or compare with them.
*
* @return <code>True</code> if we should ignore XMI ID, <code>False</code> otherwise.
*/
private boolean getPreferenceIgnoreXMIID() {
if (EMFPlugin.IS_ECLIPSE_RUNNING && EMFComparePlugin.getDefault() != null)
return EMFComparePlugin.getDefault().getBoolean(
EMFComparePreferenceConstants.PREFERENCES_KEY_IGNORE_XMIID);
return MatchOptions.DEFAULT_IGNORE_XMI_ID;
}
/**
* Returns the search window corresponding to the number of siblings to consider while matching. Reducing
* this number (on the preferences page) considerably improve performances while reducing precision.
*
* @return An <code>int</code> representing the number of siblings to consider for matching.
*/
private int getPreferenceSearchWindow() {
int searchWindow = MatchOptions.DEFAULT_SEARCH_WINDOW;
if (EMFPlugin.IS_ECLIPSE_RUNNING
&& EMFComparePlugin.getDefault() != null
&& EMFComparePlugin.getDefault().getInt(
EMFComparePreferenceConstants.PREFERENCES_KEY_SEARCH_WINDOW) > 0)
searchWindow = EMFComparePlugin.getDefault().getInt(
EMFComparePreferenceConstants.PREFERENCES_KEY_SEARCH_WINDOW);
if (searchWindow < 0)
searchWindow = 0;
return searchWindow;
}
/**
* Returns the given similarity between the two given {@link EObject}s as it is stored in cache.<br/>
* <p>
* <code>similarityKind</code> must be one of
* <ul>
* <li>{@link #NAME_SIMILARITY}</li>
* <li>{@link #TYPE_SIMILARITY}</li>
* <li>{@link #VALUE_SIMILARITY}</li>
* <li>{@link #RELATION_SIMILARITY}</li>
* </ul>
* </p>
*
* @param obj1
* First of the two {@link EObject}s we seek the similarity for.
* @param obj2
* Second of the two {@link EObject}s we seek the similarity for.
* @param similarityKind
* Kind of similarity to get.
* @return The similarity as described by <code>similarityKind</code> as it is stored in cache for the
* two given {@link EObject}s.
*/
private Double getSimilarityFromCache(EObject obj1, EObject obj2, char similarityKind) {
return metricsCache.get(pairHashCode(obj1, obj2, similarityKind));
}
/**
* Checks wether the two given {@link EObject} have the same URI.
*
* @param obj1
* First of the two {@link EObject} we're comparing.
* @param obj2
* Second {@link EObject} we're comparing.
* @return <code>True</code> if the {@link EObject}s have the same URI, <code>False</code> otherwise.
*/
private boolean hasSameUri(EObject obj1, EObject obj2) {
if (obj1.eResource() != null && obj2.eResource() != null)
return obj1.eResource().getURIFragment(obj1).equals(obj2.eResource().getURIFragment(obj2));
return false;
}
/**
* This replaces the contents of the defaults options map with the options overridden by the given map.
*
* @param map
* Map containing the option given to the match procedure. cannot be <code>null</code>.
*/
private void loadOptionMap(Map<String, Object> map) {
options.putAll(map);
if (this.<Integer> getOption(MatchOptions.OPTION_SEARCH_WINDOW) < 0)
options.put(MatchOptions.OPTION_SEARCH_WINDOW, getPreferenceSearchWindow());
}
/**
* Returns a list containing mappings of the nodes of both given {@link List}s.
*
* @param list1
* First of the lists from which we need to map the elements
* @param list2
* Second list to map the elements from.
* @param window
* Number of siblings to consider for the matching.
* @param monitor
* {@link CompareProgressMonitor Progress monitor} to display while the comparison lasts. Might
* be <code>null</code>, in which case we won't monitor progress.
* @return A {@link List} containing mappings of the nodes of both given {@link List}s.
* @throws FactoryException
* Thrown if the metrics cannot be computed.
* @throws InterruptedException
* Thrown if the matching process is interrupted somehow.
*/
private List<Match2Elements> mapLists(List<EObject> list1, List<EObject> list2, int window,
Monitor monitor) throws FactoryException, InterruptedException {
final List<Match2Elements> result = new ArrayList<Match2Elements>();
int curIndex = 0 - window / 2;
final List<EObject> notFoundList1 = new ArrayList<EObject>(list1);
final List<EObject> notFoundList2 = new ArrayList<EObject>(list2);
final Iterator<EObject> it1 = list1.iterator();
// then iterate over the 2 lists and compare the elements
while (it1.hasNext() && notFoundList2.size() > 0) {
final EObject obj1 = it1.next();
final StringBuilder obj1Key = new StringBuilder();
obj1Key.append(NameSimilarity.findName(obj1));
obj1Key.append(obj1.hashCode());
EObject obj2 = matchedByID.get(obj1Key.toString());
if (obj2 == null) {
// subtracts the difference between the notfound and the
// original list to avoid ArrayOutOfBounds
final int end = Math.min(curIndex + window - (list2.size() - notFoundList2.size()),
notFoundList2.size());
final int index = Math
.min(Math.max(curIndex - (list2.size() - notFoundList2.size()), 0), end);
obj2 = findMostSimilar(obj1, notFoundList2.subList(index, end));
if (obj2 != null) {
// checks if the most similar to obj2 is obj1
final EObject obj1Check = findMostSimilar(obj2, notFoundList1);
if (obj1Check != obj1 && obj1Check != null && isSimilar(obj1Check, obj2)) {
continue;
}
}
}
if (notFoundList1.contains(obj1) && notFoundList2.contains(obj2) && isSimilar(obj1, obj2)) {
final Match2Elements mapping = MatchFactory.eINSTANCE.createMatch2Elements();
final double metric = absoluteMetric(obj1, obj2);
mapping.setLeftElement(obj1);
mapping.setRightElement(obj2);
mapping.setSimilarity(metric);
result.add(mapping);
notFoundList2.remove(obj2);
notFoundList1.remove(obj1);
}
curIndex += 1;
monitor.worked(1);
if (monitor.isCanceled())
throw new InterruptedException();
}
// now putting the not found elements aside for later
stillToFindFromModel2.addAll(notFoundList2);
stillToFindFromModel1.addAll(notFoundList1);
return result;
}
/**
* Returns a list containing mappings of the nodes of both given {@link List}s.
*
* @param list1
* First of the lists from which we need to map the elements
* @param list2
* Second list to map the elements from.
* @param window
* Number of siblings to consider for the matching.
* @param monitor
* {@link CompareProgressMonitor Progress monitor} to display while the comparison lasts. Might
* be <code>null</code>, in which case we won't monitor progress.
* @return A {@link List} containing mappings of the nodes of both given {@link List}s.
* @throws FactoryException
* Thrown if the metrics cannot be computed.
* @throws InterruptedException
* Thrown if the matching process is interrupted somehow.
*/
private List<Match2Elements> myMapLists(List<EObject> list1, List<EObject> list2, int window,
Monitor monitor) throws FactoryException, InterruptedException {
final List<Match2Elements> result = new ArrayList<Match2Elements>();
int curIndex = 0 - window / 2;
final List<EObject> notFoundList1 = new ArrayList<EObject>(list1);
final List<EObject> notFoundList2 = new ArrayList<EObject>(list2);
final Iterator<EObject> it1 = list1.iterator();
// then iterate over the 2 lists and compare the elements
while (it1.hasNext() && notFoundList2.size() > 0) {
final EObject obj1 = it1.next();
final StringBuilder obj1Key = new StringBuilder();
obj1Key.append(NameSimilarity.findName(obj1));
obj1Key.append(obj1.hashCode());
EObject obj2 = matchedByID.get(obj1Key.toString());
if (obj2 == null) {
// subtracts the difference between the notfound and the
// original list to avoid ArrayOutOfBounds
final int end = Math.min(curIndex + window - (list2.size() - notFoundList2.size()),
notFoundList2.size());
final int index = Math
.min(Math.max(curIndex - (list2.size() - notFoundList2.size()), 0), end);
obj2 = findMostSimilar(obj1, notFoundList2.subList(index, end));
if (obj2 != null) {
// checks if the most similar to obj2 is obj1
final EObject obj1Check = findMostSimilar(obj2, notFoundList1);
if (obj1Check != obj1 && obj1Check != null && isSimilar(obj1Check, obj2)) {
continue;
}
}
}
if (notFoundList1.contains(obj1) && notFoundList2.contains(obj2) && isSimilar(obj1, obj2)) {
final Match2Elements mapping = MatchFactory.eINSTANCE.createMatch2Elements();
final double metric = absoluteMetric(obj1, obj2);
mapping.setLeftElement(obj1);
mapping.setRightElement(obj2);
mapping.setSimilarity(metric);
result.add(mapping);
notFoundList2.remove(obj2);
notFoundList1.remove(obj1);
list2.remove(obj2);
}
curIndex += 1;
monitor.worked(1);
if (monitor.isCanceled())
throw new InterruptedException();
}
// now putting the not found elements aside for later
// stillToFindFromModel2.addAll(notFoundList2);
stillToFindFromModel1.addAll(notFoundList1);
return result;
}
/**
* Iterates through both of the given EObjects to find all of their children that can be matched by their
* functional ID, then populates {@link #matchedByID} with those mappings.
* <p>
* Note that this method will perform a check to ensure the two objects' resources are indeed
* XMIResources.
* </p>
*
* @param obj1
* First of the two EObjects to visit.
* @param obj2
* Second of the EObjects to visit.
* @throws FactoryException
* Thrown if we couldn't compute a key to store the items in cache.
*/
private void matchByID(EObject obj1, EObject obj2) throws FactoryException {
matchedByID.clear();
final Iterator<EObject> iterator1 = obj1.eAllContents();
while (iterator1.hasNext()) {
final EObject item1 = iterator1.next();
final String item1ID = EcoreUtil.getID(item1);
if (item1ID != null) {
final Iterator<EObject> iterator2 = obj2.eAllContents();
while (iterator2.hasNext()) {
final EObject item2 = iterator2.next();
final String item2ID = EcoreUtil.getID(item2);
if (item2 != null && item1ID.equals(item2ID)) {
final StringBuilder item1Key = new StringBuilder();
item1Key.append(NameSimilarity.findName(item1));
item1Key.append(item1.hashCode());
matchedByID.put(item1Key.toString(), item2);
break;
}
}
}
}
}
/**
* Iterates through both of the given {@link XMIResource resources} to find all the elements that can be
* matched by their XMI ID, then populates {@link #matchedByID} with those mappings.
*
* @param left
* First of the two {@link XMIResource resources} to visit.
* @param right
* Second of the {@link XMIResource resources} to visit.
* @throws FactoryException
* Thrown if we couldn't compute a key to store the items in cache.
*/
private void matchByID(Resource left, Resource right) throws FactoryException {
matchedByID.clear();
final Iterator<EObject> leftIterator = left.getAllContents();
while (leftIterator.hasNext()) {
final EObject item1 = leftIterator.next();
final String item1ID = EcoreUtil.getID(item1);
if (item1ID != null) {
final Iterator<EObject> rightIterator = right.getAllContents();
while (rightIterator.hasNext()) {
final EObject item2 = rightIterator.next();
final String item2ID = EcoreUtil.getID(item2);
if (item2 != null && item1ID.equals(item2ID)) {
final StringBuilder item1Key = new StringBuilder();
item1Key.append(NameSimilarity.findName(item1));
item1Key.append(item1.hashCode());
matchedByID.put(item1Key.toString(), item2);
break;
}
}
}
}
}
/**
* Iterates through both of the given EObjects to find all of their children that can be matched by their
* XMI ID, then populates {@link #matchedByXMIID} with those mappings.
* <p>
* Note that this method will perform a check to ensure the two objects' resources are indeed
* XMIResources.
* </p>
*
* @param obj1
* First of the two EObjects to visit.
* @param obj2
* Second of the EObjects to visit.
* @throws FactoryException
* Thrown if we couldn't compute a key to store the items in cache.
*/
private void matchByXMIID(EObject obj1, EObject obj2) throws FactoryException {
matchedByXMIID.clear();
if (obj1 != null && obj2 != null && obj1.eResource() instanceof XMIResource
&& obj2.eResource() instanceof XMIResource) {
final XMIResource left = (XMIResource)obj1.eResource();
final XMIResource right = (XMIResource)obj2.eResource();
final Iterator<EObject> iterator = obj1.eAllContents();
while (iterator.hasNext()) {
final EObject item1 = iterator.next();
final String item1ID = left.getID(item1);
if (item1ID != null) {
final EObject item2 = right.getEObject(item1ID);
if (item2 != null) {
final StringBuilder item1Key = new StringBuilder();
item1Key.append(NameSimilarity.findName(item1));
item1Key.append(item1.hashCode());
matchedByXMIID.put(item1Key.toString(), item2);
}
}
}
}
}
/**
* Iterates through both of the given {@link XMIResource resources} to find all the elements that can be
* matched by their XMI ID, then populates {@link #matchedByXMIID} with those mappings.
*
* @param left
* First of the two {@link XMIResource resources} to visit.
* @param right
* Second of the {@link XMIResource resources} to visit.
* @throws FactoryException
* Thrown if we couldn't compute a key to store the items in cache.
*/
private void matchByXMIID(XMIResource left, XMIResource right) throws FactoryException {
matchedByXMIID.clear();
final Iterator<EObject> leftIterator = left.getAllContents();
while (leftIterator.hasNext()) {
final EObject item1 = leftIterator.next();
final String item1ID = left.getID(item1);
if (item1ID != null) {
final EObject item2 = right.getEObject(item1ID);
if (item2 != null) {
final StringBuilder item1Key = new StringBuilder();
item1Key.append(NameSimilarity.findName(item1));
item1Key.append(item1.hashCode());
matchedByXMIID.put(item1Key.toString(), item2);
}
}
}
}
/**
* Counts all the {@link EStructuralFeature features} of the given {@link EObject} that are
* <code>null</code> or initialized to the empty {@link String} &quot;&quot;.
*
* @param eobj
* {@link EObject} we need to count the empty features of.
* @return The number of features initialized to <code>null</code> or the empty String.
*/
private int nonNullFeaturesCount(EObject eobj) {
// TODO should probably cache result here
int nonNullFeatures = 0;
final Iterator<EStructuralFeature> features = eobj.eClass().getEAllStructuralFeatures().iterator();
while (features.hasNext()) {
final EStructuralFeature feature = features.next();
if (eobj.eGet(feature) != null && !"".equals(eobj.eGet(feature).toString())) //$NON-NLS-1$
nonNullFeatures++;
}
return nonNullFeatures;
}
/**
* Computes an unique key between to {@link EObject}s to store their similarity in cache.
* <p>
* <code>similarityKind</code> must be one of
* <ul>
* <li>{@link #NAME_SIMILARITY}</li>
* <li>{@link #TYPE_SIMILARITY}</li>
* <li>{@link #VALUE_SIMILARITY}</li>
* <li>{@link #RELATION_SIMILARITY}</li>
* </ul>
* </p>
*
* @param obj1
* First of the two {@link EObject}s.
* @param obj2
* Second of the two {@link EObject}s.
* @param similarityKind
* Kind of similarity this key will represent in cache.
* @return Unique key for the similarity cache.
*/
private String pairHashCode(EObject obj1, EObject obj2, char similarityKind) {
if (similarityKind == NAME_SIMILARITY || similarityKind == TYPE_SIMILARITY
|| similarityKind == VALUE_SIMILARITY || similarityKind == RELATION_SIMILARITY) {
final StringBuilder hash = new StringBuilder();
hash.append(similarityKind).append(obj1.hashCode()).append(obj2.hashCode());
return hash.toString();
}
throw new IllegalArgumentException("DifferencesServices.illegalSimilarityKind"); //$NON-NLS-1$
}
/**
* Allows for a more accurate modifications detection for three way comparison with multiple roots models.
*
* @param root
* Root of the {@link MatchModel}.
* @param subMatchRoot
* Root of the {@link Match3Element}s' hierarchy for the current element to be created.
* @throws FactoryException
* Thrown if we cannot compute {@link EObject}s similarity or if adding elements to either
* <code>root</code> or <code>subMatchRoot</code> fails somehow.
*/
private void processUnmatchedElements(MatchModel root, Match3Elements subMatchRoot)
throws FactoryException {
for (EObject obj1 : new ArrayList<EObject>(stillToFindFromModel1)) {
boolean matchFound = false;
if (obj1 instanceof Match2Elements) {
final Match2Elements match1 = (Match2Elements)obj1;
for (EObject obj2 : new ArrayList<EObject>(stillToFindFromModel2)) {
if (obj2 instanceof Match2Elements) {
final Match2Elements match2 = (Match2Elements)obj2;
if (match1.getRightElement() == match2.getRightElement()) {
matchFound = true;
final Match3Elements match = MatchFactory.eINSTANCE.createMatch3Elements();
match.setSimilarity(absoluteMetric(match1.getLeftElement(), match2
.getLeftElement(), match2.getRightElement()));
match.setLeftElement(match1.getLeftElement());
match.setRightElement(match2.getLeftElement());
match.setOriginElement(match2.getRightElement());
// This will happen if we couldn't match previously
if (subMatchRoot == null) {
redirectedAdd(root, MATCH_ELEMENT_NAME, match);
createSub3Match(root, match, match1, match2);
} else {
redirectedAdd(subMatchRoot, SUBMATCH_ELEMENT_NAME, match);
createSub3Match(root, subMatchRoot, match1, match2);
}
stillToFindFromModel1.remove(match1);
stillToFindFromModel2.remove(match2);
}
}
}
if (!matchFound) {
remainingUnMatchedElements.add(match1.getLeftElement());
}
}
}
for (EObject eObj : new ArrayList<EObject>(stillToFindFromModel1)) {
if (eObj instanceof Match2Elements) {
final Match2Elements nextLeftNotFound = (Match2Elements)eObj;
final UnmatchElement unMatch = MatchFactory.eINSTANCE.createUnmatchElement();
unMatch.setElement(nextLeftNotFound.getLeftElement());
remainingUnMatchedElements.remove(nextLeftNotFound.getLeftElement());
remainingUnMatchedElements.remove(nextLeftNotFound.getRightElement());
redirectedAdd(root, UNMATCH_ELEMENT_NAME, unMatch);
}
}
for (EObject eObj : new ArrayList<EObject>(stillToFindFromModel2)) {
if (eObj instanceof Match2Elements) {
final Match2Elements nextRightNotFound = (Match2Elements)eObj;
final UnmatchElement unMatch = MatchFactory.eINSTANCE.createUnmatchElement();
unMatch.setRemote(true);
unMatch.setElement(nextRightNotFound.getLeftElement());
remainingUnMatchedElements.remove(nextRightNotFound.getLeftElement());
remainingUnMatchedElements.remove(nextRightNotFound.getRightElement());
redirectedAdd(root, UNMATCH_ELEMENT_NAME, unMatch);
}
}
}
/**
* We consider here <code>current1</code> and <code>current2</code> are similar. This method creates
* the mapping for the objects <code>current1</code> and <code>current2</code>, Then submappings for
* these two elements' contents.
*
* @param current1
* First element of the two elements mapping.
* @param current2
* Second of the two elements mapping.
* @param monitor
* {@link CompareProgressMonitor Progress monitor} to display while the comparison lasts. Might
* be <code>null</code>, in which case we won't monitor progress.
* @return The mapping for <code>current1</code> and <code>current2</code> and their content.
* @throws FactoryException
* Thrown when the metrics cannot be computed for <code>current1</code> and
* <code>current2</code>.
* @throws InterruptedException
* Thrown if the matching process is interrupted somehow.
*/
private Match2Elements recursiveMappings(EObject current1, EObject current2,
Monitor monitor) throws FactoryException, InterruptedException {
Match2Elements mapping = null;
mapping = MatchFactory.eINSTANCE.createMatch2Elements();
mapping.setLeftElement(current1);
mapping.setRightElement(current2);
mapping.setSimilarity(absoluteMetric(current1, current2));
final List<Match2Elements> mapList = mapLists(getContents(current1), getContents(current2), this
.<Integer> getOption(MatchOptions.OPTION_SEARCH_WINDOW), monitor);
// We can map other elements with mapLists; we iterate through them.
final Iterator<Match2Elements> it = mapList.iterator();
while (it.hasNext()) {
final Match2Elements subMapping = it.next();
// As we know source and target are similars, we call recursive
// mappings onto these objects
EFactory.eAdd(mapping, SUBMATCH_ELEMENT_NAME, recursiveMappings(subMapping.getLeftElement(),
subMapping.getRightElement(), monitor));
}
return mapping;
}
/**
* We consider here <code>current1</code> and <code>current2</code> are similar. This method creates
* the mapping for the objects <code>current1</code> and <code>current2</code>, Then submappings for
* these two elements' contents.
*
* @param current1
* First element of the two elements mapping.
* @param current2
* Second of the two elements mapping.
* @param monitor
* {@link CompareProgressMonitor Progress monitor} to display while the comparison lasts. Might
* be <code>null</code>, in which case we won't monitor progress.
* @return The mapping for <code>current1</code> and <code>current2</code> and their content.
* @throws FactoryException
* Thrown when the metrics cannot be computed for <code>current1</code> and
* <code>current2</code>.
* @throws InterruptedException
* Thrown if the matching process is interrupted somehow.
*/
private Match2Elements myRecursiveMappings(EObject current1, EObject current2, List<EObject> rightChildren,
Monitor monitor) throws FactoryException, InterruptedException {
Match2Elements mapping = null;
mapping = MatchFactory.eINSTANCE.createMatch2Elements();
mapping.setLeftElement(current1);
mapping.setRightElement(current2);
mapping.setSimilarity(absoluteMetric(current1, current2));
final List<Match2Elements> mapList = myMapLists(getContents(current1), rightChildren, this
.<Integer> getOption(MatchOptions.OPTION_SEARCH_WINDOW), monitor);
// We can map other elements with mapLists; we iterate through them.
final Iterator<Match2Elements> it = mapList.iterator();
while (it.hasNext()) {
final Match2Elements subMapping = it.next();
// As we know source and target are similars, we call recursive
// mappings onto these objects
EFactory.eAdd(mapping, SUBMATCH_ELEMENT_NAME, myRecursiveMappings(subMapping.getLeftElement(),
subMapping.getRightElement(), rightChildren, monitor));
}
return mapping;
}
private List<EObject> getContentsWithChildren(EObject eObject) {
List<EObject> result = new ArrayList<EObject>();
List<EObject> children = getContents(eObject);
result.addAll(children);
for (EObject child : children) {
result.addAll(getContentsWithChildren(child));
}
return result;
}
/**
* This method is an indirection for adding Mappings in the current MappingGroup.
*
* @param object
* {@link EObject} to add a feature value to.
* @param name
* Name of the feature to consider.
* @param value
* Value to add to the feature <code>name</code> of <code>object</code>.
* @throws FactoryException
* Thrown if the value's affectation fails.
*/
private void redirectedAdd(EObject object, String name, Object value) throws FactoryException {
EFactory.eAdd(object, name, value);
}
/**
* This will compute the similarity between two {@link EObject}s' relations.
*
* @param obj1
* First of the two {@link EObject}s.
* @param obj2
* Second of the two {@link EObject}s.
* @return <code>double</code> representing the similarity between the two {@link EObject}s' relations.
* 0 &lt; value &lt; 1.
* @throws FactoryException
* Thrown if we cannot compute the relations' similarity metrics.
* @see StructureSimilarity#relationsSimilarityMetric(EObject, EObject, MetamodelFilter)
*/
private double relationsSimilarity(EObject obj1, EObject obj2) throws FactoryException {
double similarity = 0d;
final Double value = getSimilarityFromCache(obj1, obj2, RELATION_SIMILARITY);
if (value != null) {
similarity = value;
} else {
similarity = StructureSimilarity.relationsSimilarityMetric(obj1, obj2, filter);
setSimilarityInCache(obj1, obj2, RELATION_SIMILARITY, similarity);
}
return similarity;
}
/**
* Sets the values of the {@link MatchModel}'s left and right models.
*
* @param modelRoot
* Root of the {@link MatchModel}.
* @param left
* Element from which to resolve the left model URI.
* @param right
* Element from which to resolve the right model URI.
*/
private void setModelRoots(MatchModel modelRoot, Resource left, Resource right) {
setModelRoots(modelRoot, left, right, null);
}
/**
* Sets the values of the {@link MatchModel}'s left, right and ancestor models.
*
* @param modelRoot
* Root of the {@link MatchModel}.
* @param left
* Element from which to resolve the left model URI.
* @param right
* Element from which to resolve the right model URI.
* @param ancestor
* Element from which to resolve the ancestor model URI. Can be <code>null</code>.
*/
private void setModelRoots(MatchModel modelRoot, Resource left, Resource right, Resource ancestor) {
// Sets values of left, right and ancestor model URIs
if (left != null) {
modelRoot.getLeftRoots().addAll(left.getContents());
}
if (right != null) {
modelRoot.getRightRoots().addAll(right.getContents());
}
if (ancestor != null) {
modelRoot.getAncestorRoots().addAll(ancestor.getContents());
}
}
/**
* Stores in cache the given similarity between the two given {@link EObject}s.<br/>
* <p>
* <code>similarityKind</code> must be one of
* <ul>
* <li>{@link #NAME_SIMILARITY}</li>
* <li>{@link #TYPE_SIMILARITY}</li>
* <li>{@link #VALUE_SIMILARITY}</li>
* <li>{@link #RELATION_SIMILARITY}</li>
* </ul>
* </p>
*
* @param obj1
* First of the two {@link EObject}s we're setting the similarity for.
* @param obj2
* Second of the two {@link EObject}s we're setting the similarity for.
* @param similarityKind
* Kind of similarity to set.
* @param similarity
* Value of the similarity between the two {@link EObject}s.
*/
private void setSimilarityInCache(EObject obj1, EObject obj2, char similarityKind, double similarity) {
metricsCache.put(pairHashCode(obj1, obj2, similarityKind), new Double(similarity));
}
/**
* Starts the monitor for comparison progress. Externalized here to avoid multiple usage of the Strings.
*
* @param monitor
* The monitor that need be started
* @param size
* Size of the monitor
*/
private void startMonitor(Monitor monitor, int size) {
monitor.beginTask("Comparing model", size); //$NON-NLS-1$
monitor.subTask("Browsing model"); //$NON-NLS-1$
}
/**
* This will compute the similarity between two {@link EObject}s' types.
*
* @param obj1
* First of the two {@link EObject}s.
* @param obj2
* Second of the two {@link EObject}s.
* @return <code>double</code> representing the similarity between the two {@link EObject}s' types. 0
* &lt; value &lt; 1.
* @throws FactoryException
* Thrown if we cannot compute the type similarity metrics.
* @see StructureSimilarity#typeSimilarityMetric(EObject, EObject)
*/
private double typeSimilarity(EObject obj1, EObject obj2) throws FactoryException {
double similarity = 0d;
final Double value = getSimilarityFromCache(obj1, obj2, TYPE_SIMILARITY);
if (value != null) {
similarity = value;
} else {
similarity = StructureSimilarity.typeSimilarityMetric(obj1, obj2);
setSimilarityInCache(obj1, obj2, TYPE_SIMILARITY, similarity);
}
return similarity;
}
/**
* Creates the progress monitor that will be displayed to the user while the comparison lasts.
*
* @return The progress monitor that will be displayed to the user while the comparison lasts.
*/
private Monitor createProgressMonitor() {
Monitor monitor = new BasicMonitor();
final Object delegateMonitor = getOption(MatchOptions.OPTION_PROGRESS_MONITOR);
if (delegateMonitor != null && EMFPlugin.IS_ECLIPSE_RUNNING) {
monitor = EclipseModelUtils.createProgressMonitor(delegateMonitor);
}
return monitor;
}
}