blob: 1406318b1b2cad2998677b53b1707806e6cff8fc [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 org.eclipse.emf.compare.utils.MatchUtil.getMatchedObject;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import com.google.common.collect.Maps;
import java.util.Map;
import org.eclipse.emf.common.notify.Notification;
import org.eclipse.emf.common.notify.impl.AdapterImpl;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.compare.CompareFactory;
import org.eclipse.emf.compare.ComparePackage;
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.ResourceAttachmentChange;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.util.EcoreUtil;
/**
* An index of relationships between Papyrus resources on each side of a comparison.
*
* @author Christian W. Damus
*/
public final class PapyrusResourceIndex {
/**
* Mapping by side and Papyrusesque extensionless URI of the groups of resources that each together
* comprise a Papyrus model.
*/
private final Map<SidedURI, Map<String, MatchResource>> groups = Maps.newHashMap();
/** Cache of matches by resource. */
private final Map<Resource, MatchResource> matches = Maps.newHashMap();
/** Token stored in the match cache for cache misses. */
private final MatchResource noMatch = CompareFactory.eINSTANCE.createMatchResource();
/** Mapping of refactorings. */
private final BiMap<MatchResource, MatchResource> refactorings;
/** The comparison that I index. */
private final Comparison comparison;
/**
* Index the resource matches in the given {@code comparison}.
*
* @param comparison
* the comparison to index
*/
private PapyrusResourceIndex(Comparison comparison) {
super();
this.comparison = comparison;
this.refactorings = HashBiMap.create(comparison.getMatchedResources().size());
for (MatchResource next : comparison.getMatchedResources()) {
if (next.getLeftURI() != null) {
URI uri = URI.createURI(next.getLeftURI());
SidedURI key = SidedURI.left(uri.trimFileExtension());
getGroup(key).put(uri.fileExtension(), next);
}
if (next.getRightURI() != null) {
URI uri = URI.createURI(next.getRightURI());
SidedURI key = SidedURI.right(uri.trimFileExtension());
getGroup(key).put(uri.fileExtension(), next);
}
}
}
/**
* Get all of the resource matches (including the given {@code match}) that are the resources together
* comprising the Papyrus model containing it.
*
* @param match
* a resource match
* @param side
* the side of the comparison on which the {@code match} is being considered
* @return a mapping of file extension to match for all of the resources of the Papyrus model of which the
* {@code match} is one
*/
public Map<String, MatchResource> getGroup(MatchResource match, DifferenceSource side) {
URI uri = URI.createURI(getURI(match, side));
return getGroup(SidedURI.of(uri.trimFileExtension(), side));
}
/**
* Get the group for the given URI on its side, creating the group if necessary.
*
* @param key
* a URI (without extension) on a side of the comparison
* @return its resource match group
*/
private Map<String, MatchResource> getGroup(SidedURI key) {
Map<String, MatchResource> result = groups.get(key);
if (result == null) {
result = Maps.newHashMap();
groups.put(key, result);
}
return result;
}
/**
* Queries the match for a given {@code resource} in the context of my comparison.
*
* @param resource
* a resource in my comparison
* @return its match
*/
public MatchResource getMatch(Resource resource) {
MatchResource result = matches.get(resource);
if (result == null) {
result = noMatch;
if (resource != null) {
for (MatchResource next : comparison.getMatchedResources()) {
if (next.getLeft() == resource || next.getRight() == resource
|| next.getOrigin() == resource) {
result = next;
break;
}
}
} // else always cache a miss for the null resource
// Cache the result, whether a miss ('noMatch') or not
matches.put(resource, result);
}
return (result == noMatch) ? null : result;
}
/**
* Register a resource refactoring inferred from the given root object match.
*
* @param rootMatch
* a matched resource root apparently "moved" from one resource to another
*/
public void addResourceRefactoring(Match rootMatch) {
EObject leftRoot = rootMatch.getLeft();
EObject rightRoot = rootMatch.getRight();
Resource leftRes = leftRoot.eResource();
Resource rightRes = rightRoot.eResource();
MatchResource leftMatch = getMatch(leftRes);
MatchResource rightMatch = getMatch(rightRes);
refactorings.put(leftMatch, rightMatch);
// And do all of the group
for (Map.Entry<String, MatchResource> next : getGroup(leftMatch, DifferenceSource.LEFT).entrySet()) {
MatchResource nextLeft = next.getValue();
if (nextLeft != leftMatch) {
MatchResource nextRight = getGroup(rightMatch, DifferenceSource.RIGHT).get(next.getKey());
if (nextRight != null) {
refactorings.put(nextLeft, nextRight);
}
}
}
}
/**
* Queries whether the comparison involves any resource refactoring.
*
* @return whether any resource is refactored in my comparison
*/
public boolean hasResourceRefactorings() {
return !refactorings.isEmpty();
}
/**
* Obtains the other resource match, if any, in a refactoring pair.
*
* @param match
* a resource match
* @return the other participant in the refactoring, or {@code null} if the {@code match} is not involved
* in a refactoring
*/
public MatchResource getResourceRefactoring(MatchResource match) {
MatchResource result = refactorings.get(match);
if (result == null) {
result = refactorings.inverse().get(match);
}
return result;
}
/**
* Queries whether a resource attachment change is a {@linkplain DifferenceKind#MOVE move} RAC that is
* only really a pseudo-RAC because, in fact, the source and destination resources are the same resource
* just refactored.
*
* @param diff
* a resource attachment change
* @return the other participant in the refactoring, or {@code null} if the {@code match} is not involved
* in a refactoring
*/
public boolean isInResourceRefactoring(ResourceAttachmentChange diff) {
if (diff.getKind() != DifferenceKind.MOVE) {
// Something that's not a move doesn't indicate refactoring of the resource
return false;
}
Match match = diff.getMatch();
EObject moved = getMatchedObject(match, diff.getSource());
Resource resource = moved.eResource();
MatchResource resourceMatch = getMatch(resource);
MatchResource otherMatch = getResourceRefactoring(resourceMatch);
if (otherMatch == null) {
return false;
}
Resource otherSide = getResource(otherMatch, opposite(diff.getSource()));
// If it is not being moved from the old resource of the refactoring,
// then it's a real move
return otherSide != null && (match.getOrigin() == null
|| match.getOrigin().eResource().getURI().equals(otherSide.getURI()));
}
/**
* Obtains the unique (lazily computed) index of Papyrus resources in the context of a {@code comparison}.
*
* @param comparison
* a comparison
* @return its resource index
*/
public static PapyrusResourceIndex index(Comparison comparison) {
Attachment attachment = (Attachment)EcoreUtil.getExistingAdapter(comparison, Attachment.class);
if (attachment == null) {
attachment = new PapyrusResourceIndex(comparison).new Attachment(comparison);
}
return attachment.getIndex();
}
/**
* Obtains the unique (lazily computed) index of Papyrus resources in the context of the comparison
* containing a {@code match}.
*
* @param match
* a match in some comparison
* @return its resource index
*/
public static PapyrusResourceIndex index(Match match) {
return index(match.getComparison());
}
/**
* Obtains the unique (lazily computed) index of Papyrus resources in the context of the comparison
* containing a {@code diff}.
*
* @param diff
* a difference in some comparison
* @return its resource index
*/
public static PapyrusResourceIndex index(Diff diff) {
return index(diff.getMatch());
}
/**
* Compute the side opposite a given {@code side}.
*
* @param side
* a side of the comparison
* @return the other side
*/
protected static DifferenceSource opposite(DifferenceSource side) {
return DifferenceSource.get(1 - side.ordinal());
}
/**
* Obtains the resource on the given {@code side} of a {@code match}.
*
* @param match
* a resource match
* @param side
* a side
* @return the resource on that {@code side} of the {@code match}
*/
protected static Resource getResource(MatchResource match, DifferenceSource side) {
switch (side) {
case LEFT:
return match.getLeft();
case RIGHT:
return match.getRight();
default:
throw new IllegalArgumentException("side"); //$NON-NLS-1$
}
}
/**
* Obtains the resource URI on the given {@code side} of a {@code match}.
*
* @param match
* a resource match
* @param side
* a side
* @return the URI on that {@code side} of the {@code match}
*/
protected static String getURI(MatchResource match, DifferenceSource side) {
switch (side) {
case LEFT:
return match.getLeftURI();
case RIGHT:
return match.getRightURI();
default:
throw new IllegalArgumentException("side"); //$NON-NLS-1$
}
}
/**
* Assigns the {@code resource} on the given {@code side} of a {@code match}.
*
* @param match
* a resource match
* @param side
* a side
* @param resource
* the resource to assign on that {@code side} of the {@code match}
*/
protected static void setResource(MatchResource match, DifferenceSource side, Resource resource) {
switch (side) {
case LEFT:
match.setLeft(resource);
break;
case RIGHT:
match.setRight(resource);
break;
default:
throw new IllegalArgumentException("side"); //$NON-NLS-1$
}
}
/**
* Assigns the resource {@code URI} on the given {@code side} of a {@code match}.
*
* @param match
* a resource match
* @param side
* a side
* @param URI
* the URI to assign on that {@code side} of the {@code match}
*/
protected static void setURI(MatchResource match, DifferenceSource side, String uri) {
switch (side) {
case LEFT:
match.setLeftURI(uri);
break;
case RIGHT:
match.setRightURI(uri);
break;
default:
throw new IllegalArgumentException("side"); //$NON-NLS-1$
}
}
//
// Nested types
//
/**
* An adapter that attaches the unique instance of the index to a {@link Comparison} for subsequent
* retrieval.
*/
private final class Attachment extends AdapterImpl {
/**
* Initializes me with my target {@code comparison}.
*
* @param comparison
* the comparison to which I attach
*/
Attachment(Comparison comparison) {
super();
comparison.eAdapters().add(this);
}
@Override
public boolean isAdapterForType(Object type) {
return type == PapyrusResourceIndex.class || type == Attachment.class;
}
/**
* Obtains the index that I attach to the comparison.
*
* @return my index
*/
PapyrusResourceIndex getIndex() {
return PapyrusResourceIndex.this;
}
@Override
public void notifyChanged(Notification msg) {
if (msg.isTouch() || msg.getNotifier() != comparison) {
return;
}
switch (msg.getFeatureID(Comparison.class)) {
case ComparePackage.COMPARISON__MATCHED_RESOURCES:
// Purge cache of resource matches
matches.clear();
break;
default:
// Pass
break;
}
}
}
}