/*******************************************************************************
* Copyright (c) 2013, 2016 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
* Philip Langer - bug 501864
*******************************************************************************/
package org.eclipse.emf.compare.internal.postprocessor.factories;
import com.google.common.base.Predicate;
import com.google.common.collect.Collections2;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import org.eclipse.emf.compare.AttributeChange;
import org.eclipse.emf.compare.Comparison;
import org.eclipse.emf.compare.Diff;
import org.eclipse.emf.compare.DifferenceKind;
import org.eclipse.emf.compare.Match;
import org.eclipse.emf.compare.ReferenceChange;
import org.eclipse.emf.compare.ResourceAttachmentChange;
import org.eclipse.emf.compare.internal.utils.ComparisonUtil;
import org.eclipse.emf.compare.util.CompareSwitch;
import org.eclipse.emf.compare.utils.EMFComparePredicates;
import org.eclipse.emf.compare.utils.MatchUtil;
import org.eclipse.emf.ecore.EObject;
/**
* Factory of difference extensions.
*
* @author <a href="mailto:cedric.notot@obeo.fr">Cedric Notot</a>
*/
public abstract class AbstractChangeFactory implements IChangeFactory {
/**
* Switch which returns the <code>DifferenceKind</code> of the matching diagram extension in relation to
* the given difference.
*/
private CompareSwitch<DifferenceKind> differenceKindCompareSwitch = new DifferenceKindCompareSwitch();
/**
* {@inheritDoc}
*
* @see org.eclipse.emf.compare.internal.postprocessor.factories.IChangeFactory#handles(org.eclipse.emf.compare.Diff)
*/
public boolean handles(Diff input) {
return getRelatedExtensionKind(input) != null;
}
/**
* {@inheritDoc}
*
* @see org.eclipse.emf.compare.internal.postprocessor.factories.IChangeFactory#create(org.eclipse.emf.compare.Diff)
*/
public Diff create(Diff input) {
Diff ret = createExtension();
final DifferenceKind extensionKind = getRelatedExtensionKind(input);
ret.setKind(extensionKind);
// It's important to set the source before calling setRefiningChanges()
// because refines/refinedBy EReferences demand diffs on the same side
ret.setSource(input.getSource());
setRefiningChanges(ret, extensionKind, input);
return ret;
}
/**
* It creates the graphical change extension.
*
* @return The extension.
*/
public abstract Diff createExtension();
/**
* Get the refining differences and set the given extension with them, from the given refining one.
*
* @param extension
* The extension to set.
* @param extensionKind
* The extension kind.
* @param refiningDiff
* The refining difference.
*/
public abstract void setRefiningChanges(Diff extension, DifferenceKind extensionKind, Diff refiningDiff);
/**
* {@inheritDoc}
*
* @see org.eclipse.emf.compare.internal.postprocessor.factories.IChangeFactory#getParentMatch(org.eclipse.emf.compare.Diff)
*/
public Match getParentMatch(Diff input) {
return input.getMatch();
}
/**
* {@inheritDoc}
*
* @see org.eclipse.emf.compare.internal.postprocessor.factories.IChangeFactory#getExtensionKind()
*/
public Class<? extends Diff> getExtensionKind() {
return Diff.class;
}
/**
* {@inheritDoc}
*
* @see org.eclipse.emf.compare.internal.postprocessor.factories.IChangeFactory#fillRequiredDifferences(org.eclipse.emf.compare.Comparison,
* org.eclipse.emf.compare.Diff)
*/
public void fillRequiredDifferences(Comparison comparison, Diff extension) {
fillRequiredDifferencesForMacroToMacro(extension);
fillRequiredDifferencesForUnitToMacro(extension);
}
/**
* Browse required and requiring unit differences to deduce the requirement link between this macroscopic
* change and other ones.
*
* @param macro
* The macroscopic change.
*/
private void fillRequiredDifferencesForMacroToMacro(Diff macro) {
Set<Diff> requiredExtensions = new LinkedHashSet<Diff>();
Set<Diff> requiringExtensions = new LinkedHashSet<Diff>();
for (Diff refiningDiff : macro.getRefinedBy()) {
requiredExtensions.addAll(getDistinctRefinedDifferences(refiningDiff.getRequires()));
requiringExtensions.addAll(getDistinctRefinedDifferences(refiningDiff.getRequiredBy()));
}
// Keep only difference extensions as the given one
requiredExtensions.remove(macro);
requiringExtensions.remove(macro);
macro.getRequires().addAll(
Collections2.filter(requiredExtensions, EMFComparePredicates.fromSide(macro.getSource())));
macro.getRequiredBy().addAll(
Collections2.filter(requiringExtensions, EMFComparePredicates.fromSide(macro.getSource())));
}
/**
* Browse required and requiring unit differences to deduce the requirement link between this macroscopic
* change and other external unit differences.
*
* @param macro
* The macroscopic change.
*/
private void fillRequiredDifferencesForUnitToMacro(Diff macro) {
Set<Diff> requiredExtensions = new LinkedHashSet<Diff>();
Set<Diff> requiringExtensions = new LinkedHashSet<Diff>();
for (Diff refiningDiff : macro.getRefinedBy()) {
for (Diff unit : refiningDiff.getRequires()) {
if (unit.getRefines().isEmpty()) {
requiredExtensions.add(unit);
}
}
for (Diff unit : refiningDiff.getRequiredBy()) {
if (unit.getRefines().isEmpty()) {
requiringExtensions.add(unit);
}
}
}
macro.getRequires().addAll(
Collections2.filter(requiredExtensions, EMFComparePredicates.fromSide(macro.getSource())));
macro.getRequiredBy().addAll(
Collections2.filter(requiringExtensions, EMFComparePredicates.fromSide(macro.getSource())));
}
/**
* Get the <code>DifferenceKind</code> of the matching diagram difference extension in relation to the
* given difference.
*
* @param input
* The given difference.
* @return The kind of the diagram difference extension if this one exists, null otherwise.
*/
protected DifferenceKind getRelatedExtensionKind(Diff input) {
return differenceKindCompareSwitch.doSwitch(input);
}
/**
* Check if the given reference change is related to a graphical add. It may be overridden in the child
* factories in order to precise which kind of graphical add has to be considered.
*
* @param input
* The reference change.
* @return True if the reference change is a good candidate, false otherwise.
*/
protected boolean isRelatedToAnExtensionAdd(ReferenceChange input) {
return false;
}
/**
* Check if the given reference change is related to a graphical delete. It may be overridden in the child
* factories in order to precise which kind of graphical delete has to be considered.
*
* @param input
* The reference change.
* @return True if the reference change is a good candidate, false otherwise.
*/
protected boolean isRelatedToAnExtensionDelete(ReferenceChange input) {
return false;
}
/**
* Check if the given reference change is related to a graphical change. It may be overridden in the child
* factories in order to precise which kind of graphical change has to be considered.
*
* @param input
* The reference change.
* @return True if the reference change is a good candidate, false otherwise.
*/
protected boolean isRelatedToAnExtensionChange(ReferenceChange input) {
return false;
}
/**
* Check if the given reference change is related to a graphical move. It may be overridden in the child
* factories in order to precise which kind of graphical move has to be considered.
*
* @param input
* The reference change.
* @return True if the reference change is a good candidate, false otherwise.
*/
protected boolean isRelatedToAnExtensionMove(ReferenceChange input) {
return false;
}
/**
* Check if the given attribute change is related to a graphical add. It may be overridden in the child
* factories in order to precise which kind of graphical add has to be considered.
*
* @param input
* The attribute change.
* @return True if the attribute change is a good candidate, false otherwise.
*/
protected boolean isRelatedToAnExtensionAdd(AttributeChange input) {
return false;
}
/**
* Check if the given attribute change is related to a graphical delete. It may be overridden in the child
* factories in order to precise which kind of graphical delete has to be considered.
*
* @param input
* The attribute change.
* @return True if the attribute change is a good candidate, false otherwise.
*/
protected boolean isRelatedToAnExtensionDelete(AttributeChange input) {
return false;
}
/**
* Check if the given attribute change is related to a graphical change. It may be overridden in the child
* factories in order to precise which kind of graphical change has to be considered.
*
* @param input
* The attribute change.
* @return True if the attribute change is a good candidate, false otherwise.
*/
protected boolean isRelatedToAnExtensionChange(AttributeChange input) {
return false;
}
/**
* Check if the given attribute change is related to a graphical move. It may be overridden in the child
* factories in order to precise which kind of graphical move has to be considered.
*
* @param input
* The attribute change.
* @return True if the attribute change is a good candidate, false otherwise.
*/
protected boolean isRelatedToAnExtensionMove(AttributeChange input) {
return false;
}
/**
* Check if the given resource attachment change is related to a graphical add. It may be overridden in
* the child factories in order to precise which kind of graphical add has to be considered.
*
* @param input
* The resource attachment change.
* @return True if the resource attachment change is a good candidate, false otherwise.
*/
protected boolean isRelatedToAnExtensionAdd(ResourceAttachmentChange input) {
return false;
}
/**
* Check if the given resource attachment change is related to a graphical delete. It may be overridden in
* the child factories in order to precise which kind of graphical delete has to be considered.
*
* @param input
* The resource attachment change.
* @return True if the resource attachment change is a good candidate, false otherwise.
*/
protected boolean isRelatedToAnExtensionDelete(ResourceAttachmentChange input) {
return false;
}
/**
* Check if the given resource attachment change is related to a graphical change. It may be overridden in
* the child factories in order to precise which kind of graphical change has to be considered.
*
* @param input
* The resource attachment change.
* @return True if the resource attachment change is a good candidate, false otherwise.
*/
protected boolean isRelatedToAnExtensionChange(ResourceAttachmentChange input) {
return false;
}
/**
* Check if the given resource attachment change is related to a graphical ;ove. It may be overridden in
* the child factories in order to precise which kind of graphical ;ove has to be considered.
*
* @param input
* The resource attachment change.
* @return True if the resource attachment change is a good candidate, false otherwise.
*/
protected boolean isRelatedToAnExtensionMove(ResourceAttachmentChange input) {
return false;
}
/**
* Get all the add and delete changes on the objects contained in the one concerned by the given
* difference.
*
* @param input
* The given difference.
* @return The found differences.
*/
protected Set<Diff> getAllContainedDifferences(Diff input) {
final Set<Diff> result = new LinkedHashSet<Diff>();
final Comparison comparison = ComparisonUtil.getComparison(input);
CompareSwitch<EObject> valueGetter = new CompareSwitch<EObject>() {
@Override
public EObject caseReferenceChange(ReferenceChange object) {
return object.getValue();
}
@Override
public EObject caseResourceAttachmentChange(ResourceAttachmentChange object) {
return MatchUtil.getContainer(ComparisonUtil.getComparison(object), object);
}
@Override
public EObject defaultCase(EObject object) {
return null;
}
};
EObject value = valueGetter.doSwitch(input);
if (value != null) {
final Match match = comparison.getMatch(value);
result.addAll(getAllContainedDifferences(comparison, match));
}
return result;
}
/**
* Find the differences, on the given model object, which match with the predicate.
*
* @param comparison
* The comparison.
* @param lookup
* The model object.
* @param p
* The predicate.
* @return The found differences.
*/
protected final List<Diff> findCrossReferences(Comparison comparison, EObject lookup, Predicate<Diff> p) {
return Lists.newArrayList(Iterables.filter(comparison.getDifferences(lookup), p));
}
/**
* Get all the add and delete changes under the given match.
*
* @param comparison
* The comparison.
* @param match
* The match
* @return The found differences.
*/
private Set<Diff> getAllContainedDifferences(Comparison comparison, Match match) {
final Set<Diff> result = Sets.newLinkedHashSet();
final Set<Match> prune = Sets.newLinkedHashSet();
for (Diff candidate : match.getDifferences()) {
// Keep only unit changes...
if (!getExtensionKind().isInstance(candidate)) {
// ... which are not related to an other macroscopic ADD or DELETE or MOVE of a graphical
// object.
if (getRelatedExtensionKind(candidate) == null) {
result.add(candidate);
} else if (candidate instanceof ReferenceChange
&& ((ReferenceChange)candidate).getReference().isContainment()) {
// match of an added or deleted graphical object.
prune.add(comparison.getMatch(((ReferenceChange)candidate).getValue()));
}
}
}
// Re-iterate the research in sub matches of expected objects.
for (Match submatch : match.getSubmatches()) {
if (!prune.contains(submatch)) {
result.addAll(getAllContainedDifferences(comparison, submatch));
}
}
return result;
}
/**
* Get the distinct differences refined by the given differences.
*
* @param refiningDifferences
* The refining differences.
* @return The set of refined differences.
*/
private Set<Diff> getDistinctRefinedDifferences(List<Diff> refiningDifferences) {
Iterator<Diff> unitDiffs = refiningDifferences.iterator();
Set<Diff> extensions = new LinkedHashSet<Diff>();
while (unitDiffs.hasNext()) {
Diff unitDiff = unitDiffs.next();
extensions.addAll(unitDiff.getRefines());
}
return extensions;
}
/**
* This can be used to determine the kind of an extension according to an input Diff.
*
* @author <a href="mailto:cedric.notot@obeo.fr">Cedric Notot</a>
*/
private class DifferenceKindCompareSwitch extends CompareSwitch<DifferenceKind> {
/**
* {@inheritDoc}
*
* @see org.eclipse.emf.compare.util.CompareSwitch#caseAttributeChange(org.eclipse.emf.compare.AttributeChange)
*/
@Override
public DifferenceKind caseAttributeChange(AttributeChange object) {
DifferenceKind result;
if (isRelatedToAnExtensionAdd(object)) {
result = DifferenceKind.ADD;
} else if (isRelatedToAnExtensionDelete(object)) {
result = DifferenceKind.DELETE;
} else if (isRelatedToAnExtensionChange(object)) {
result = DifferenceKind.CHANGE;
} else if (isRelatedToAnExtensionMove(object)) {
result = DifferenceKind.MOVE;
} else {
result = super.caseAttributeChange(object);
}
return result;
}
/**
* {@inheritDoc}
*
* @see org.eclipse.emf.compare.util.CompareSwitch#caseReferenceChange(org.eclipse.emf.compare.ReferenceChange)
*/
@Override
public DifferenceKind caseReferenceChange(ReferenceChange object) {
DifferenceKind result;
if (isRelatedToAnExtensionAdd(object)) {
result = DifferenceKind.ADD;
} else if (isRelatedToAnExtensionDelete(object)) {
result = DifferenceKind.DELETE;
} else if (isRelatedToAnExtensionChange(object)) {
result = DifferenceKind.CHANGE;
} else if (isRelatedToAnExtensionMove(object)) {
result = DifferenceKind.MOVE;
} else {
result = super.caseReferenceChange(object);
}
return result;
}
/**
* {@inheritDoc}
*
* @see org.eclipse.emf.compare.util.CompareSwitch#caseResourceAttachmentChange(org.eclipse.emf.compare.ResourceAttachmentChange)
*/
@Override
public DifferenceKind caseResourceAttachmentChange(ResourceAttachmentChange object) {
DifferenceKind result;
if (isRelatedToAnExtensionAdd(object)) {
result = DifferenceKind.ADD;
} else if (isRelatedToAnExtensionDelete(object)) {
result = DifferenceKind.DELETE;
} else if (isRelatedToAnExtensionChange(object)) {
result = DifferenceKind.CHANGE;
} else if (isRelatedToAnExtensionMove(object)) {
result = DifferenceKind.MOVE;
} else {
result = super.caseResourceAttachmentChange(object);
}
return result;
}
}
}