blob: 2e7f03f195a9adcec9fa5ec34fb4fa6483b8b1e8 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2018 Christian W. Damus 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:
* Christian W. Damus - initial API and implementation
*******************************************************************************/
package org.eclipse.papyrus.compare.uml2.internal.postprocessor;
import static com.google.common.base.Predicates.and;
import static com.google.common.collect.Iterables.filter;
import static org.eclipse.emf.compare.utils.EMFComparePredicates.ofKind;
import static org.eclipse.papyrus.compare.uml2.internal.postprocessor.PapyrusResourceIndex.getResource;
import static org.eclipse.papyrus.compare.uml2.internal.postprocessor.PapyrusResourceIndex.index;
import static org.eclipse.papyrus.compare.uml2.internal.postprocessor.PapyrusResourceIndex.opposite;
import static org.eclipse.papyrus.compare.uml2.internal.postprocessor.PapyrusResourceIndex.setResource;
import static org.eclipse.papyrus.compare.uml2.internal.postprocessor.PapyrusResourceIndex.setURI;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterators;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import java.util.Iterator;
import java.util.Map;
import org.eclipse.emf.common.util.Monitor;
import org.eclipse.emf.compare.Comparison;
import org.eclipse.emf.compare.Diff;
import org.eclipse.emf.compare.DifferenceKind;
import org.eclipse.emf.compare.DifferenceSource;
import org.eclipse.emf.compare.Match;
import org.eclipse.emf.compare.MatchResource;
import org.eclipse.emf.compare.ReferenceChange;
import org.eclipse.emf.compare.ResourceAttachmentChange;
import org.eclipse.emf.compare.postprocessor.IPostProcessor;
import org.eclipse.emf.compare.uml2.internal.postprocessor.util.UMLCompareUtil;
import org.eclipse.emf.compare.utils.MatchUtil;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.InternalEObject;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.uml2.uml.Element;
/**
* Post-processor for comparisons of Papyrus models.
*
* @author Christian W. Damus
*/
public class PapyrusPostProcessor implements IPostProcessor {
/**
* A predicate matching diffs that are {@link ResourceAttachmentChange}s that move a stereotype
* application.
*/
static final Predicate<Diff> IS_STEREOTYPE_APPLICATION_RESOURCE_MOVE = and(isStereotypeApplicationRAC(),
ofKind(DifferenceKind.MOVE));
/**
* A predicate matching diffs that are {@link ResourceAttachmentChange}s that move the principal UML
* element of a resource.
*/
static final Predicate<Diff> IS_RESOURCE_REFACTORING_MOVE = and(isResourceRefactoringRAC(),
ofKind(DifferenceKind.MOVE));
/**
* Initializes me.
*/
public PapyrusPostProcessor() {
super();
}
public void postMatch(Comparison comparison, Monitor monitor) {
// Handle refactoring of model resources, esp. sub-units
rematchRefactoredUMLResources(comparison, monitor);
}
public void postDiff(Comparison comparison, Monitor monitor) {
// Pass
}
public void postRequirements(Comparison comparison, Monitor monitor) {
// Ensure that certain diffs of stereotype applications are merged
// only after certain other diffs of their base UML elements
findStereotypeApplicationDependencies(comparison);
}
public void postEquivalences(Comparison comparison, Monitor monitor) {
// Pass
}
public void postConflicts(Comparison comparison, Monitor monitor) {
// Pass
}
public void postComparison(Comparison comparison, Monitor monitor) {
// Pass
}
/**
* Find diffs for stereotype applications moved from one resource to another and add the moves (if any) of
* their base elements as pre-requisites.
*
* @param comparison
* the contextual comparison
*/
protected void findStereotypeApplicationDependencies(Comparison comparison) {
for (ResourceAttachmentChange rac : getStereotypeApplicationResourceMoves(comparison)) {
// Also the diff that moves the base element, if any
Match match = rac.getMatch();
EObject stereotypeApplication = MatchUtil.getMatchedObject(match, rac.getSource());
Element baseElement = UMLCompareUtil.getBaseElement(stereotypeApplication);
if (baseElement != null) {
Match baseMatch = comparison.getMatch(baseElement);
Diff moveDiff = findMoveDiff(baseMatch, comparison, rac.getSource());
if (moveDiff != null) {
rac.getRequires().add(moveDiff);
}
}
}
}
/**
* Obtain a predicate matching diffs that are {@link ResourceAttachmentChange}s pertaining to a stereotype
* application.
*
* @return the is-stereotype-application-RAC predicate
*/
protected static Predicate<Diff> isStereotypeApplicationRAC() {
return new Predicate<Diff>() {
public boolean apply(Diff input) {
if (!(input instanceof ResourceAttachmentChange)) {
return false;
}
Match match = input.getMatch();
EObject object = match.getLeft();
if (object == null) {
object = match.getRight();
}
return object != null && !(object instanceof Element)
&& UMLCompareUtil.getBaseElement(object) != null;
}
};
}
/**
* Selects the resource attachment changes that indicate moves of stereotype applications from a resource
* to another.
*
* @param comparison
* the comparison from which to select the diffs
* @return the stereotype application moves between resources
*/
protected Iterable<ResourceAttachmentChange> getStereotypeApplicationResourceMoves(
Comparison comparison) {
return filter(filter(comparison.getDifferences(), ResourceAttachmentChange.class),
IS_STEREOTYPE_APPLICATION_RESOURCE_MOVE);
}
/**
* Obtains a predicate matching {@link ResourceAttachmentChange}s of UML elements that signify the
* refactoring (rename/move) of a resource.
*
* @return the is-resource-refactoring-RAC predicate
*/
protected static Predicate<Diff> isResourceRefactoringRAC() {
return new Predicate<Diff>() {
public boolean apply(Diff input) {
if (!(input instanceof ResourceAttachmentChange)) {
return false;
}
Match match = input.getMatch();
EObject object = match.getLeft();
if (object == null) {
object = match.getRight();
}
return object instanceof Element && index(match).getResourceRefactoring(match) != null;
}
};
}
/**
* Find the nearest diff to a given {@code match} that moves it (perhaps indirectly via the content tree)
* from a resource to another resource.
*
* @param match
* a match for which to look for a move diff
* @param comparison
* the contextual comparison
* @param side
* the side of the comparison on which to look for move diffs
* @return a diff that moves that {@code match}ed object from one resource to another, or {@code null} if
* it is not moved between resources
*/
protected Diff findMoveDiff(Match match, Comparison comparison, DifferenceSource side) {
Diff result = null;
if (match != null) {
EObject object = MatchUtil.getMatchedObject(match, side);
if (object != null) {
for (Diff next : comparison.getDifferences(object)) {
if (next instanceof ReferenceChange
&& ((ReferenceChange)next).getReference().isContainment()) {
result = next;
break;
}
}
}
if (result == null) {
// Maybe the object is moved to a resource root?
Iterator<ResourceAttachmentChange> iter = Iterators.filter(match.getDifferences().iterator(),
ResourceAttachmentChange.class);
if (iter.hasNext()) {
result = iter.next();
}
}
if (result == null && match.eContainer() instanceof Match) {
// Look up the tree
result = findMoveDiff((Match)match.eContainer(), comparison, side);
}
}
return result;
}
/**
* Analyze the matching of resources to re-match any that have different URIs but that by dint of their
* primary UML element having moved are actually a refactoring (rename or move) of a single logical UML
* resource.
*
* @param comparison
* the contextual comparison
* @param monitor
* progress monitor
*/
protected void rematchRefactoredUMLResources(Comparison comparison, Monitor monitor) {
// Look for renamed resources, which are matched by their root UML elements
Map<Resource, MatchResource> updates = Maps.newHashMap();
PapyrusResourceIndex resourceGroups = index(comparison);
for (MatchResource mres : comparison.getMatchedResources()) {
if (!rematch(comparison, mres, DifferenceSource.LEFT, resourceGroups, updates)) {
// Try the other way around
rematch(comparison, mres, DifferenceSource.RIGHT, resourceGroups, updates);
}
}
if (!updates.isEmpty()) {
// Remove incomplete matches
for (Iterator<MatchResource> iter = Lists.reverse(comparison.getMatchedResources())
.iterator(); iter.hasNext() && !updates.isEmpty();) {
MatchResource next = iter.next();
MatchResource update = updates.remove(next.getLeft());
if (update == null || update == next) {
update = updates.remove(next.getRight());
if (update == next) {
update = null;
}
}
if (update != null) {
// MatchResource::locationChanges is no longer used,
// so don't worry about updating that
// Capture the origin from the match we're deleting
if (update.getOrigin() == null) {
update.setOrigin(next.getOrigin());
update.setOriginURI(next.getOriginURI());
}
// Remove the redundant match
iter.remove();
}
}
}
}
/**
* Attempt to re-match a UML resource that appears to be unmatched with a UML resource on the other side,
* also unmatched, that contains the same root {@link Element}, on the assumption that such element
* constitutes logical content of the resource.
*
* @param comparison
* the contextual comparison
* @param mres
* a resource to re-match
* @param side
* the side from which to try to find a new match on the other side
* @param index
* an index of relationships between resources in the context of the the {@code comparison}
* @param updates
* collects the incomplete matches of resources that were merged into incomplete matches on the
* other side, which need to be removed from the {@code comparison} in a subsequent step
* (because it isn't safe to do so in-line)
* @return {@code true} if a new match was found for the resource from this {@code side}; {@code false},
* otherwise
*/
protected boolean rematch(Comparison comparison, MatchResource mres, DifferenceSource side,
PapyrusResourceIndex index, Map<Resource, MatchResource> updates) {
final DifferenceSource opposite = opposite(side);
final Resource oneSide = getResource(mres, side);
final Resource otherSide = getResource(mres, opposite);
if (oneSide != null && otherSide == null) {
// Don't process a resource match that we've already melded into another
if (!updates.containsKey(oneSide) && !oneSide.getContents().isEmpty()) {
EObject root = oneSide.getContents().get(0);
if (!(root instanceof Element)) {
// Only need this for UML resources that reliably contain
// only one real element, to handle stereotype applications
// that are additional non-UML roots
return false;
}
Match rootMatch = comparison.getMatch(root);
if (rootMatch != null) {
EObject otherRoot = MatchUtil.getMatchedObject(rootMatch, opposite);
if (otherRoot == null) {
return false;
}
Resource other = ((InternalEObject)otherRoot).eDirectResource();
if (other != null) {
combine(mres, other, opposite, index, updates);
return true;
}
}
}
}
return false;
}
/**
* Combine a resource match with an{@code other} resource.
*
* @param uml
* an unmatched UML resource to complete with the {@code other}
* @param other
* a resource with which to complete the match
* @param otherSide
* the side on which the {@code other} resource is
* @param index
* an index of relationships between resources in the context of the the {@code comparison}
* @param updates
* collects the incomplete matches of resources that were merged into incomplete matches on the
* other side, which need to be removed from the {@code comparison} in a subsequent step
* (because it isn't safe to do so in-line)
*/
protected void combine(MatchResource uml, Resource other, DifferenceSource otherSide,
PapyrusResourceIndex index, Map<Resource, MatchResource> updates) {
// Meld the matches for the UML resource
setResource(uml, otherSide, other);
setURI(uml, otherSide, other.getURI().toString());
updates.put(other, uml);
// And do the same for other Papyrus resources in the same group
DifferenceSource thisSide = opposite(otherSide);
Map<String, MatchResource> thisGroup = index.getGroup(uml, thisSide);
Map<String, MatchResource> otherGroup = index.getGroup(uml, otherSide);
for (Map.Entry<String, MatchResource> next : thisGroup.entrySet()) {
MatchResource combine = next.getValue();
if (combine != uml && getResource(combine, otherSide) == null) {
MatchResource combineWith = otherGroup.get(next.getKey());
if (combineWith != null && getResource(combineWith, thisSide) == null) {
Resource combineWithResource = getResource(combineWith, otherSide);
setResource(combine, otherSide, combineWithResource);
setURI(combine, otherSide, combineWithResource.getURI().toString());
updates.put(combineWithResource, combine);
}
}
}
}
}