/*******************************************************************************
* Copyright (c) 2012, 2015 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
* Stefan Dirix - sort resources before matching
*******************************************************************************/
package org.eclipse.emf.compare.match.resource;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import org.eclipse.emf.compare.CompareFactory;
import org.eclipse.emf.compare.MatchResource;
import org.eclipse.emf.ecore.resource.Resource;
/**
* A {@link StrategyResourceMatcher} will be used to match two or three {@link Resource}s together; depending
* on whether we are doing a two or three way comparison.
* <p>
* Do take note that the match engine expects ResourceMatchers to return matching resources as well as
* resources that do not match.
* </p>
*
* @author <a href="mailto:laurent.goubet@obeo.fr">Laurent Goubet</a>
*/
public class StrategyResourceMatcher implements IResourceMatcher {
/**
* Compares resources according to the string representation of their {@link URI}s.
*/
protected Comparator<Resource> resourceURIComparator = new Comparator<Resource>() {
public int compare(Resource arg0, Resource arg1) {
if (arg0.getURI() == null && arg1.getURI() == null) {
return 0;
}
if (arg0.getURI() == null) {
return -1;
}
if (arg1.getURI() == null) {
return 1;
}
return arg0.getURI().toString().compareTo(arg1.getURI().toString());
}
};
/**
* The known strategies.
*/
private final ImmutableSet<IResourceMatchingStrategy> knownStrategies;
/**
* Default constructor with two strategies: {@link NameMatchingStrategy} & {@link RootIDMatchingStrategy}.
*/
public StrategyResourceMatcher() {
this.knownStrategies = ImmutableSet.of(new NameMatchingStrategy(), new RootIDMatchingStrategy());
}
/**
* Constructor that allows to give your own {@link IResourceMatchingStrategy}.
*
* @param strategies
* the strategies you want to use for the match step. A defensive copy of the provided
* strategies will be done.
*/
public StrategyResourceMatcher(Collection<IResourceMatchingStrategy> strategies) {
this.knownStrategies = ImmutableSet.copyOf(strategies);
}
/**
* {@inheritDoc}
*
* @see org.eclipse.emf.compare.match.resource.IResourceMatcher#createMappings(java.util.Iterator,
* java.util.Iterator, java.util.Iterator)
*/
public Iterable<MatchResource> createMappings(Iterator<? extends Resource> leftResources,
Iterator<? extends Resource> rightResources, Iterator<? extends Resource> originResources) {
final List<MatchResource> mappings;
// Copy the input Resource lists : we'll exhaust them as we go
final List<? extends Resource> leftCopy = Lists.newArrayList(leftResources);
final List<? extends Resource> rightCopy = Lists.newArrayList(rightResources);
final List<? extends Resource> originCopy = Lists.newArrayList(originResources);
Collections.sort(leftCopy, resourceURIComparator);
Collections.sort(rightCopy, resourceURIComparator);
Collections.sort(originCopy, resourceURIComparator);
// Detect matching resources
final IResourceMatchingStrategy[] strategies = getResourceMatchingStrategies();
if (strategies.length == 1) {
mappings = strategies[0].matchResources(leftCopy, rightCopy, originCopy);
// Remove all matched from the copies to leave only unmatched
for (MatchResource newMapping : mappings) {
leftCopy.remove(newMapping.getLeft());
rightCopy.remove(newMapping.getRight());
originCopy.remove(newMapping.getOrigin());
}
} else {
mappings = Lists.newArrayList();
// Break this loop if we exhausted all strategies or if two of the lists are empty (no potential
// match remaining)
for (int i = 0; i < strategies.length
&& !atLeastTwo(leftCopy.isEmpty(), rightCopy.isEmpty(), originCopy.isEmpty()); i++) {
final List<MatchResource> newMappings = strategies[i].matchResources(leftCopy, rightCopy,
originCopy);
for (MatchResource newMapping : newMappings) {
leftCopy.remove(newMapping.getLeft());
rightCopy.remove(newMapping.getRight());
originCopy.remove(newMapping.getOrigin());
}
mappings.addAll(newMappings);
}
}
// Any resource that has not been matched by now is an unmatch. The "copies" list have been updated
// each time we found a
// match, they only contain the remaining unmatch resources now.
for (Resource left : leftCopy) {
mappings.add(createMatchResource(left, null, null));
}
for (Resource right : rightCopy) {
mappings.add(createMatchResource(null, right, null));
}
for (Resource origin : originCopy) {
mappings.add(createMatchResource(null, null, origin));
}
return mappings;
}
/**
* This will check that at least two of the three given booleans are <code>true</code>.
*
* @param condition1
* First of the three booleans.
* @param condition2
* Second of the three booleans.
* @param condition3
* Third of the three booleans.
* @return <code>true</code> if at least two of the three given booleans are <code>true</code>,
* <code>false</code> otherwise.
*/
protected static boolean atLeastTwo(boolean condition1, boolean condition2, boolean condition3) {
// CHECKSTYLE:OFF This expression is alone in its method, and documented.
return condition1 && (condition2 || condition3) || (condition2 && condition3);
// CHECKSTYLE:ON
}
/**
* Returns the matching strategies that are to be used by this resource matcher.
* <p>
* This default implementation will try two ways of matching the resources before giving up : resources
* have equal name, then resources have roots with equal identifiers.
* </p>
* <p>
* Resource Matching Strategies are expected to map resources together, but not to detect resources that
* do not match.
* </p>
*
* @return The resource matching strategies that should be used by this matcher.
*/
protected IResourceMatchingStrategy[] getResourceMatchingStrategies() {
return knownStrategies.toArray(new IResourceMatchingStrategy[] {});
}
/**
* Creates a {@link MatchResource} instance and sets all three resources of the mapping on it.
*
* @param left
* The left resource of this mapping.
* @param right
* The right resource of this mapping.
* @param origin
* The origin resource of this mapping.
* @return The create mapping.
*/
protected static MatchResource createMatchResource(Resource left, Resource right, Resource origin) {
final MatchResource match = CompareFactory.eINSTANCE.createMatchResource();
match.setLeft(left);
match.setRight(right);
match.setOrigin(origin);
if (left != null && left.getURI() != null) {
match.setLeftURI(left.getURI().toString());
}
if (right != null && right.getURI() != null) {
match.setRightURI(right.getURI().toString());
}
if (origin != null && origin.getURI() != null) {
match.setOriginURI(origin.getURI().toString());
}
return match;
}
}