blob: f6007816396e233761bb098204b92a1ff53bb6da [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2015 Obeo.
* 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
*******************************************************************************/
package org.eclipse.emf.compare.diagram.ide.ui.papyrus.internal.merge;
import static org.eclipse.emf.compare.DifferenceSource.LEFT;
import static org.eclipse.emf.compare.DifferenceSource.RIGHT;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.apache.log4j.Logger;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.compare.Comparison;
import org.eclipse.emf.compare.Diff;
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.compare.diagram.ide.ui.papyrus.internal.CompareDiagramIDEUIPapyrusPlugin;
import org.eclipse.emf.compare.diagram.ide.ui.papyrus.internal.CompareUIPapyrusMessages;
import org.eclipse.emf.compare.internal.utils.ComparisonUtil;
import org.eclipse.emf.compare.merge.IMergeCriterion;
import org.eclipse.emf.compare.merge.ResourceAttachmentChangeMerger;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.papyrus.infra.core.resource.sasheditor.DiModel;
import org.eclipse.papyrus.infra.core.resource.sasheditor.SashModel;
import org.eclipse.papyrus.infra.gmfdiag.common.model.NotationModel;
import org.eclipse.papyrus.uml.tools.model.UmlModel;
/**
* This Merger deals with ResourceAttachmentChanges as soon as there is Papyrus involved. Its purpose it to
* make sure additions, deletions, and renames of papyrus resources are made synchronously (*.notation, *.uml
* and *.di need to be synchronously created/deleted).
*
* @author <a href="mailto:laurent.delaigue@obeo.fr">Laurent Delaigue</a>
*/
@SuppressWarnings("restriction")
public class PapyrusResourceAttachmentChangeMerger extends ResourceAttachmentChangeMerger {
/** List of file extensions managed by this post-processor. */
public static final List<String> FILE_EXTENSIONS = Arrays.asList(UmlModel.UML_FILE_EXTENSION,
DiModel.DI_FILE_EXTENSION, NotationModel.NOTATION_FILE_EXTENSION,
SashModel.SASH_MODEL_FILE_EXTENSION);
/** The logger. */
private static final Logger LOGGER = Logger.getLogger(PapyrusResourceAttachmentChangeMerger.class);
/**
* {@inheritDoc}
*
* @see org.eclipse.emf.compare.merge.IMerger#isMergerFor(org.eclipse.emf.compare.Diff)
*/
@Override
public boolean isMergerFor(Diff target) {
try {
return target instanceof ResourceAttachmentChange
&& isPapyrusChange((ResourceAttachmentChange)target);
// CHECKSTYLE:OFF
} catch (Exception e) {
// The change resource has no URI or other strange issue
}
// CHECKSTYLE:ON
return false;
}
@Override
public boolean apply(IMergeCriterion criterion) {
return criterion == null;
}
/**
* {@inheritDoc}
* <p>
* Manages move of empty resources related to the model element move from one resource to another. Then
* delegates to the super implementation to actually perform the move.
* </p>
*/
@Override
protected void move(final ResourceAttachmentChange diff, boolean rightToLeft) {
dealWithAssociatedEmptyResources(diff, rightToLeft);
super.move(diff, rightToLeft);
}
@Override
protected void addInTarget(ResourceAttachmentChange diff, boolean rightToLeft) {
dealWithAssociatedEmptyResources(diff, rightToLeft);
super.addInTarget(diff, rightToLeft);
}
@Override
protected void removeFromTarget(ResourceAttachmentChange diff, boolean rightToLeft) {
dealWithAssociatedEmptyResources(diff, rightToLeft);
super.removeFromTarget(diff, rightToLeft);
}
/**
* Manages the creation and deletion of associated empty resources that cannot be handled by model diffs
* since they contain no model element to which attach any diff.
*
* @param diff
* The change to apply
* @param rightToLeft
* The direction in which the change must be applied
*/
private void dealWithAssociatedEmptyResources(final ResourceAttachmentChange diff, boolean rightToLeft) {
Comparison comp = ComparisonUtil.getComparison(diff);
Iterable<MatchResource> relatedMatchResource = Iterables.filter(comp.getMatchedResources(),
concernsTheSamePapyrusVirtualNodeAs(diff));
final ResourceSet targetRS;
if (rightToLeft) {
targetRS = getResourceSet(comp, LEFT);
} else {
targetRS = getResourceSet(comp, RIGHT);
}
for (MatchResource mr : relatedMatchResource) {
if (rightToLeft) {
if (mr.getLeft() != null && mr.getLeft().getContents().isEmpty()) {
// Delete only if former file doesn't exist on the other side
if (mr.getRight() == null) {
delete(targetRS, mr.getLeft().getURI());
if (LOGGER.isInfoEnabled()) {
LOGGER.info("Deleted empty resource " + mr.getLeft().getURI()); //$NON-NLS-1$
}
}
} else if (mr.getRight() != null && mr.getRight().getContents().isEmpty()) {
URI targetURI = mr.getRight().getURI();
if (!targetRS.getURIConverter().exists(targetURI, Collections.emptyMap())) {
targetRS.createResource(targetURI);
if (LOGGER.isInfoEnabled()) {
LOGGER.info("Created empty resource " + targetURI); //$NON-NLS-1$
}
}
}
} else {
if (mr.getRight() != null && mr.getRight().getContents().isEmpty()) {
// Delete only if former file doesn't exist on the other side
if (mr.getLeft() == null) {
delete(targetRS, mr.getRight().getURI());
if (LOGGER.isInfoEnabled()) {
LOGGER.info("Deleted empty resource " + mr.getRight().getURI()); //$NON-NLS-1$
}
}
} else if (mr.getLeft() != null && mr.getLeft().getContents().isEmpty()) {
URI targetURI = mr.getLeft().getURI();
if (!targetRS.getURIConverter().exists(targetURI, Collections.emptyMap())) {
targetRS.createResource(targetURI);
if (LOGGER.isInfoEnabled()) {
LOGGER.info("Created empty resource " + targetURI); //$NON-NLS-1$
}
}
}
}
}
}
/**
* Obtain the resource set to use for a side.
*
* @param comp
* Comparison
* @param side
* side
* @return The resource set to use, never null.
*/
private ResourceSet getResourceSet(Comparison comp, DifferenceSource side) {
// CHECKSTYLE:OFF
switch (side) {
case LEFT:
for (MatchResource mr : comp.getMatchedResources()) {
if (mr.getLeft() != null && mr.getLeft().getResourceSet() != null) {
return mr.getLeft().getResourceSet();
}
}
case RIGHT:
for (MatchResource mr : comp.getMatchedResources()) {
if (mr.getRight() != null && mr.getRight().getResourceSet() != null) {
return mr.getRight().getResourceSet();
}
}
}
// CHECKSTYLE:ON
throw new IllegalStateException();
}
/**
* Delete a resource.
*
* @param rs
* Resource set from where to delete the resource
* @param uri
* URI of the resource to delete
*/
private void delete(ResourceSet rs, URI uri) {
Resource toDelete = rs.getResource(uri, false);
if (toDelete == null) {
return;
}
try {
toDelete.delete(Collections.EMPTY_MAP);
} catch (IOException e) {
CompareDiagramIDEUIPapyrusPlugin.getDefault().getLog()
.log(new Status(IStatus.ERROR, CompareDiagramIDEUIPapyrusPlugin.PLUGIN_ID,
CompareUIPapyrusMessages
.getString("PapyrusResourceAttachmentChangeMerge.deleteFailure"), //$NON-NLS-1$
e));
}
}
/**
* Indicates whether the given change concerns a papyrus resource.
*
* @param change
* The change to test
* @return <code>true</code> if the change concerns a papyrus resource.
*/
private boolean isPapyrusChange(ResourceAttachmentChange change) {
Match match = change.getMatch();
EObject o = match.getLeft();
if (o == null) {
o = match.getRight();
if (o == null) {
o = match.getOrigin();
}
}
return FILE_EXTENSIONS.contains(o.eResource().getURI().fileExtension());
}
/**
* Provide a predicate to filter MatchResources.
*
* @param change
* The reference change
* @return A predicate that will filter the MatchResources that will concerne resources that are part of
* the same virtual node as the given change's resource(s).
*/
private SameVirtualNode concernsTheSamePapyrusVirtualNodeAs(ResourceAttachmentChange change) {
return new SameVirtualNode(change);
}
/**
* Predicate that matches all {@link MatchResource}s that are related to a papyrus related resource of its
* reference change (the change that is passed to the constructor).
*
* @author <a href="mailto:laurent.delaigue@obeo.fr">Laurent Delaigue</a>
*/
private static final class SameVirtualNode implements Predicate<MatchResource> {
/** The trimmed left URI, can be null. */
private final URI leftURI;
/** The trimmed right URI, can be null. */
private final URI rightURI;
/** The trimmed ancestor URI, can be null. */
private final URI originURI;
/**
* Constructor.
*
* @param change
* The change
*/
private SameVirtualNode(ResourceAttachmentChange change) {
Match match = change.getMatch();
leftURI = getResourceURI(match.getLeft());
rightURI = getResourceURI(match.getRight());
originURI = getResourceURI(match.getOrigin());
}
/**
* Compute the URI of a given EObject's resource, avoiding NPEs.
*
* @param o
* The EObject
* @return The given EObject's resource URI, may be null.
*/
private URI getResourceURI(EObject o) {
if (o == null || o.eResource() == null) {
return null;
}
return o.eResource().getURI();
}
/**
* Predicate implementation.
*
* @param input
* the MatchResource to filter.
* @return true if the given matchResource is part of the same papyrus virtual node as this
* predicate's reference change, but is not related to the same resource.
*/
public boolean apply(MatchResource input) {
if (input.getLeft() != null && leftURI != null) {
URI uri = input.getLeft().getURI();
return !leftURI.equals(uri) && leftURI.trimFileExtension().equals(uri.trimFileExtension());
}
if (input.getRight() != null && rightURI != null) {
URI uri = input.getRight().getURI();
return !rightURI.equals(uri) && rightURI.trimFileExtension().equals(uri.trimFileExtension());
}
if (input.getOrigin() != null && originURI != null && originURI.trimFileExtension() != null) {
URI uri = input.getOrigin().getURI();
return !originURI.equals(uri)
&& originURI.trimFileExtension().equals(uri.trimFileExtension());
}
return false;
}
}
}