blob: 98a0136e8729b1c4d1425717a2068c9fb63c9a78 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2013, 2017 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
* Martin Fleck - bug 507177
*******************************************************************************/
package org.eclipse.emf.compare.uml2.internal.postprocessor;
import static com.google.common.base.Predicates.instanceOf;
import com.google.common.base.Predicate;
import com.google.common.collect.Collections2;
import com.google.common.collect.Iterables;
import com.google.common.collect.Iterators;
import com.google.common.collect.LinkedHashMultimap;
import com.google.common.collect.Multimap;
import com.google.common.collect.SetMultimap;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
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.postprocessor.factories.AbstractChangeFactory;
import org.eclipse.emf.compare.internal.utils.ComparisonUtil;
import org.eclipse.emf.compare.internal.utils.DiffUtil;
import org.eclipse.emf.compare.uml2.internal.StereotypeApplicationChange;
import org.eclipse.emf.compare.uml2.internal.UMLDiff;
import org.eclipse.emf.compare.utils.EMFComparePredicates;
import org.eclipse.emf.compare.utils.MatchUtil;
import org.eclipse.emf.compare.utils.ReferenceUtil;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.EStructuralFeature.Setting;
import org.eclipse.emf.ecore.util.Switch;
import org.eclipse.uml2.common.util.UML2Util;
import org.eclipse.uml2.uml.util.UMLSwitch;
/**
* Factory of UML difference extensions.
*
* @author <a href="mailto:cedric.notot@obeo.fr">Cedric Notot</a>
*/
public abstract class AbstractUMLChangeFactory extends AbstractChangeFactory {
/**
* UML Switch to get the discriminants (if they exist) related to the given business object.
*
* @author <a href="mailto:cedric.notot@obeo.fr">Cedric Notot</a>
*/
protected class DiscriminantsGetter extends UMLSwitch<Set<EObject>> {
@Override
public Set<EObject> defaultCase(EObject object) {
return defaultCaseForDiscriminantsGetter(this, object);
}
}
/**
* Setting to define a candidate to the seeking of refining differences.
*
* @author <a href="mailto:cedric.notot@obeo.fr">Cedric Notot</a>
*/
private class RefiningCandidate implements Setting {
/** The business object containing the structural feature. */
private EObject holdingObject;
/** The structural feature. */
private EStructuralFeature eStructuralFeature;
/**
* Constructor to use if the origin of the target value is unknown (neither holding object nor
* structural feature). This candidate will be considered as a containment reference to the target
* value.
*/
RefiningCandidate() {
}
/**
* Constructor.
*
* @param holdingObject
* The business object containing the structural feature to the target value.
* @param feature
* the structural feature to the target value.
*/
RefiningCandidate(EObject holdingObject, EStructuralFeature feature) {
this.holdingObject = holdingObject;
this.eStructuralFeature = feature;
}
/**
* {@inheritDoc}
*
* @see org.eclipse.emf.ecore.EStructuralFeature.Setting#getEObject()
*/
public EObject getEObject() {
return holdingObject;
}
/**
* {@inheritDoc}
*
* @see org.eclipse.emf.ecore.EStructuralFeature.Setting#getEStructuralFeature()
*/
public EStructuralFeature getEStructuralFeature() {
return eStructuralFeature;
}
/**
* {@inheritDoc}<br>
* No use. Return null.
*
* @see org.eclipse.emf.ecore.EStructuralFeature.Setting#get(boolean)
*/
public Object get(boolean resolve) {
return null;
}
/**
* {@inheritDoc}<br>
* No use. Do nothing.
*
* @see org.eclipse.emf.ecore.EStructuralFeature.Setting#set(java.lang.Object)
*/
public void set(Object newValue) {
}
/**
* {@inheritDoc}<br>
* No use. Return false.
*
* @see org.eclipse.emf.ecore.EStructuralFeature.Setting#isSet()
*/
public boolean isSet() {
return false;
}
/**
* {@inheritDoc}<br>
* No use. Do nothing.
*
* @see org.eclipse.emf.ecore.EStructuralFeature.Setting#unset()
*/
public void unset() {
}
}
/**
* It defines the predicate to keep only the differences which match with the given settings (refining
* candidates).
*
* @author <a href="mailto:cedric.notot@obeo.fr">Cedric Notot</a>
*/
private class DifferencesOnRefiningCandidates implements Predicate<Diff> {
/** The comparison. */
private final Comparison fComparison;
/** The specified settings. */
private final SetMultimap<Object, RefiningCandidate> fRefiningCandidates;
/**
* Constructor.
*
* @param comparison
* The comparison.
* @param refiningCandidates
* The specified settings.
*/
DifferencesOnRefiningCandidates(Comparison comparison,
SetMultimap<Object, RefiningCandidate> refiningCandidates) {
fComparison = comparison;
fRefiningCandidates = refiningCandidates;
}
/**
* {@inheritDoc}
*
* @see com.google.common.base.Predicate#apply(java.lang.Object)
*/
public boolean apply(final Diff input) {
boolean result = false;
Object value = MatchUtil.getValue(input);
Set<RefiningCandidate> settings = fRefiningCandidates.get(value);
if (settings.size() > 0) {
// Keep the current difference if one specified candidate setting match with it at least.
result = Iterables.any(settings, new Predicate<EStructuralFeature.Setting>() {
public boolean apply(EStructuralFeature.Setting setting) {
boolean res = true;
if (setting.getEObject() != null) {
// Keep if match of the current difference is the same as the match of the
// specified
// holding object in the setting...
res = input.getMatch() == fComparison.getMatch(setting.getEObject());
}
if (setting.getEStructuralFeature() != null) {
// ... and the structural feature is the same as the specified one in the
// setting.
res = res && MatchUtil.getStructuralFeature(input) == setting
.getEStructuralFeature();
} else {
// If no structural feature specified, check that the reference of the
// difference is containment.
EStructuralFeature diffFeature = MatchUtil.getStructuralFeature(input);
res = res && DiffUtil.isContainmentReference(diffFeature);
}
return res;
}
});
}
return result;
}
}
/**
* {@inheritDoc}<br>
* It checks that the given difference concerns the creation of an UML macroscopic change. <br>
* It verifies this difference is not a part of a macroscopic ADD or DELETE not to have a macroscopic
* ADD/DELETE plus a macroscopic CHANGE.<br>
* At last, the first matching difference allows to create a complete macroscopic change (with all the
* refining differences. So, the next matching ones will not be held to avoid to create duplicated
* macroscopic changes.
*
* @see org.eclipse.emf.compare.internal.postprocessor.factories.AbstractChangeFactory#handles(org.eclipse.emf.compare.Diff)
*/
@Override
public boolean handles(Diff input) {
return super.handles(input) && !isChangeOnAddOrDelete(input) && input.getRefines().isEmpty();
}
/**
* {@inheritDoc}<br>
* It creates the macroscopic change and builds it. It sets its discriminant (main business object to
* focus). <br>
* For a macroscopic ADD/DELETE, it sets its eReference (the reference of the main refining difference (on
* the discriminant)).
*
* @see org.eclipse.emf.compare.internal.postprocessor.factories.IChangeFactory#create(org.eclipse.emf.compare.Diff)
*/
@Override
public Diff create(Diff input) {
Diff ret = super.create(input);
if (ret instanceof UMLDiff) {
((UMLDiff)ret).setDiscriminant(getDiscriminant(input));
setEReference(input, (UMLDiff)ret);
}
return ret;
}
/**
* {@inheritDoc}<br>
* During the building process, it sets the differences refining the macroscopic one.
*
* @see org.eclipse.emf.compare.internal.postprocessor.factories.AbstractChangeFactory#setRefiningChanges(org.eclipse.emf.compare.Diff,
* org.eclipse.emf.compare.DifferenceKind, org.eclipse.emf.compare.Diff)
*/
@Override
public void setRefiningChanges(Diff extension, DifferenceKind extensionKind, Diff refiningDiff) {
SetMultimap<Object, RefiningCandidate> refiningCandidates = LinkedHashMultimap.create();
Comparison comparison = ComparisonUtil.getComparison(refiningDiff);
// From each discriminant business object, ...
Set<EObject> discriminants = getDiscriminants(refiningDiff);
for (EObject discriminant : discriminants) {
// ... define all the business objects which may be impacted by refining differences specifying
// the
// settings (holding object and structural feature) which link them.
defineRefiningCandidates(discriminant, refiningCandidates);
// For each of these business objects, find the impacted differences, keeping only the ones
// matching the defined settings.
for (Object elt : refiningCandidates.keys()) {
beRefinedByCrossReferences(comparison, elt, (UMLDiff)extension,
new DifferencesOnRefiningCandidates(comparison, refiningCandidates));
}
}
}
/**
* {@inheritDoc}
*
* @see org.eclipse.emf.compare.internal.postprocessor.factories.IChangeFactory#getParentMatch(org.eclipse.emf.compare.Diff)
*/
@Override
public Match getParentMatch(Diff input) {
return getParentMatch(ComparisonUtil.getComparison(input), input);
}
/**
* Get the discriminant business objects concerned by the given difference.<br>
*
* @param input
* The given difference.
* @return The set of discriminant business objects.
*/
protected Set<EObject> getDiscriminants(Diff input) {
EObject value;
// Get the business object to focus as starting point to find all the discriminant objects.
if (input instanceof ReferenceChange && ((ReferenceChange)input).getReference().isContainment()) {
value = ((ReferenceChange)input).getValue();
} else {
value = MatchUtil.getContainer(ComparisonUtil.getComparison(input), input);
}
return getDiscriminants(value);
}
/**
* Get the cross referenced object (setting) matching with given predicate, from the given business
* object.
*
* @param object
* The given object from which we seek a cross referenced object.
* @param predicate
* The predicate.
* @return the setting containing the cross referenced object.
*/
protected Setting getInverseReferences(EObject object, Predicate<EStructuralFeature.Setting> predicate) {
final Iterator<EStructuralFeature.Setting> crossReferences = UML2Util.getInverseReferences(object)
.iterator();
return Iterators.find(crossReferences, predicate, null);
}
/**
* {@inheritDoc}<br>
* The given reference change is related to a macroscopic ADD if... <br>
* - Its reference is a containment one<br>
* - Its value is the specified discriminant<br>
* - Its kind is ADD
*
* @see org.eclipse.emf.compare.internal.postprocessor.factories.AbstractChangeFactory#isRelatedToAnExtensionAdd(org.eclipse.emf.compare.ReferenceChange)
*/
@Override
protected boolean isRelatedToAnExtensionAdd(ReferenceChange input) {
return input.getReference().isContainment() && input.getValue() == getDiscriminant(input)
&& input.getKind() == DifferenceKind.ADD;
}
/**
* {@inheritDoc}<br>
* The given reference change is related to a macroscopic DELETE if... <br>
* - Its reference is a containment one<br>
* - Its value is the specified discriminant<br>
* - Its kind is DELETE
*
* @see org.eclipse.emf.compare.internal.postprocessor.factories.AbstractChangeFactory#isRelatedToAnExtensionDelete(org.eclipse.emf.compare.ReferenceChange)
*/
@Override
protected boolean isRelatedToAnExtensionDelete(ReferenceChange input) {
return input.getReference().isContainment() && input.getValue() == getDiscriminant(input)
&& input.getKind() == DifferenceKind.DELETE;
}
// No UML macroscopic change anymore for any changes on discriminants.
// /**
// * {@inheritDoc}<br>
// * The given reference change is related to a macroscopic CHANGE if discriminants are found from the
// * business impacted object.
// *
// * @see
// org.eclipse.emf.compare.internal.postprocessor.factories.AbstractChangeFactory#isRelatedToAnExtensionChange(org.eclipse.emf.compare.ReferenceChange)
// */
// @Override
// protected boolean isRelatedToAnExtensionChange(ReferenceChange input) {
// return !getDiscriminants(MatchUtil.getContainer(input.getMatch().getComparison(), input)).isEmpty();
// }
// TODO: Add isRelatedToAnExtensionChange(AttributeChange input)
/**
* Get the switch which allows to return the set of discriminants from any business object.<br>
*
* @return The switch to get the discriminants.
*/
protected abstract Switch<Set<EObject>> getDiscriminantsGetter();
/**
* Get the main discriminant business object concerned by the given difference. Usually, it will be one of
* the ones returned by {@link AbstractUMLChangeFactory#getDiscriminants(Diff)}
*
* @param input
* The difference.
* @return The discriminant.
*/
protected abstract EObject getDiscriminant(Diff input);
/**
* Default method in discriminant getters ({@link DiscriminantsGetter}) to find discriminants.
*
* @param discriminantsGetter
* The specific discriminant getter.
* @param object
* The current object as starting point to the seeking of discriminants.
* @return The set of discriminants.
*/
protected static Set<EObject> defaultCaseForDiscriminantsGetter(Switch<Set<EObject>> discriminantsGetter,
EObject object) {
Set<EObject> result = new LinkedHashSet<EObject>();
EObject parent = object.eContainer();
if (parent != null) {
result.addAll(discriminantsGetter.doSwitch(parent));
}
return result;
}
/**
* Get the discriminant business objects from the given one.
*
* @param value
* The given business object.
* @return The set of discriminants.
*/
private Set<EObject> getDiscriminants(EObject value) {
Switch<Set<EObject>> discriminantGetter = getDiscriminantsGetter();
return discriminantGetter.doSwitch(value);
}
/**
* It defines the business objects to scan and their settings to find the matching refining differences,
* from the given discriminant business object.
*
* @param discriminant
* The given discriminant.
* @param refiningCandidates
* Map business object to the list of settings to keep during the research of refining
* differences. This map must not be null.
*/
private void defineRefiningCandidates(EObject discriminant,
Multimap<Object, RefiningCandidate> refiningCandidates) {
// The discriminant itself is a candidate, only on an incoming containment reference.
refiningCandidates.put(discriminant, new RefiningCandidate());
// Delegation to a recursive method to find the other candidates.
defineRefiningCandidatesFrom(discriminant, refiningCandidates);
}
/**
* It defines the business objects to scan and their settings to find the matching refining differences,
* from the given discriminant business object. It ignores the given object and searches candidates in all
* the referenced objects from it and all the attributes. A recursion is made between containment
* references, to scan all the children. Opposite references are taken into account for the candidates.
*
* @param discriminant
* The given discriminant.
* @param refiningCandidates
* Map business object to the list of settings to keep during the research of refining
* differences. This map must not be null.
*/
private void defineRefiningCandidatesFrom(EObject discriminant,
Multimap<Object, RefiningCandidate> refiningCandidates) {
Iterator<EStructuralFeature> outgoingFeatures = discriminant.eClass().getEAllStructuralFeatures()
.iterator();
while (outgoingFeatures.hasNext()) {
EStructuralFeature outgoingFeature = outgoingFeatures.next();
// For each referenced objects and attributes...
Iterator<Object> values = ReferenceUtil.getAsList(discriminant, outgoingFeature).iterator();
while (values.hasNext()) {
Object value = values.next();
// ... register it with its setting.
refiningCandidates.put(value, new RefiningCandidate(discriminant, outgoingFeature));
if (outgoingFeature instanceof EReference && value instanceof EObject) {
if (((EReference)outgoingFeature).isContainment()) {
// Recursion on children
defineRefiningCandidatesFrom((EObject)value, refiningCandidates);
} else if (((EReference)outgoingFeature).getEOpposite() != null) {
// Take opposite references
refiningCandidates.put(discriminant, new RefiningCandidate((EObject)value,
((EReference)outgoingFeature).getEOpposite()));
}
}
}
}
}
/**
* Get the match in which the macroscopic change should be added.<br>
* Take the same match of the given difference. If it is related to a macroscopic CHANGE, take the match
* of the discriminant.
*
* @param comparison
* The current comparison.
* @param input
* The difference to locate.
* @return The containing match.
*/
private Match getParentMatch(Comparison comparison, Diff input) {
if (getRelatedExtensionKind(input) == DifferenceKind.CHANGE) {
return comparison.getMatch(getDiscriminant(input));
} else {
return input.getMatch();
}
}
/**
* Fill the refining link of the given refined extension (macroscopic change) with the found differences
* on the given object (lookup), according to the given predicate.
*
* @param comparison
* The comparison.
* @param lookup
* The object on which differences have to be found.
* @param refinedExtension
* The macroscopic change to set (refinedBy link)
* @param p
* The predicate.
*/
private void beRefinedByCrossReferences(Comparison comparison, Object lookup, UMLDiff refinedExtension,
Predicate<Diff> p) {
if (lookup instanceof EObject) {
List<Diff> crossReferences = findCrossReferences(comparison, (EObject)lookup, p);
refinedExtension.getRefinedBy().addAll(Collections2.filter(crossReferences,
EMFComparePredicates.fromSide(refinedExtension.getSource())));
}
}
/**
* Set the eReference link in the given UML difference, from the given unit difference.
*
* @param input
* The unit difference.
* @param umlDiff
* The UML difference (macroscopic change).
*/
private void setEReference(Diff input, UMLDiff umlDiff) {
if (getRelatedExtensionKind(input) == DifferenceKind.ADD
|| getRelatedExtensionKind(input) == DifferenceKind.DELETE) {
if (input instanceof ReferenceChange) {
umlDiff.setEReference(((ReferenceChange)input).getReference());
} else if (input instanceof ResourceAttachmentChange
&& umlDiff instanceof StereotypeApplicationChange) {
// the resource attachment concerns the stereotype application itself.
// The reference is located "below" that.
final List<Diff> candidates = input.getMatch().getDifferences();
// Little chance that there is more is that the input ... and what we seek.
for (Diff candidate : candidates) {
if (candidate instanceof ReferenceChange) {
umlDiff.setEReference(((ReferenceChange)candidate).getReference());
}
}
}
}
}
/**
* It checks if the given difference concerns is related to a macroscopic CHANGE within a macroscopic
* ADD/DELETE.
*
* @param input
* The difference.
* @return True if it is related to a CHANGE in an ADD/DELETE.
*/
protected boolean isChangeOnAddOrDelete(Diff input) {
if (getRelatedExtensionKind(input) == DifferenceKind.CHANGE) {
final Comparison comparison = ComparisonUtil.getComparison(input);
final EObject discriminant = getDiscriminant(input);
if (discriminant != null) {
return isChangeOnAddOrDelete(input, comparison, discriminant);
}
}
return false;
}
/**
* It checks if the given difference concerns is related to a macroscopic CHANGE within a macroscopic
* ADD/DELETE.
*
* @param input
* The given difference.
* @param comparison
* the related comparison.
* @param discriminant
* The discriminant found for the given difference.
* @return True if it is related to a CHANGE in an ADD/DELETE.
*/
private boolean isChangeOnAddOrDelete(Diff input, final Comparison comparison,
final EObject discriminant) {
boolean result = false;
Match match = comparison.getMatch(discriminant);
if (match != null
&& Iterables.any(match.getDifferences(), instanceOf(ResourceAttachmentChange.class))) {
result = true;
}
if (!result) {
final List<Diff> candidates = comparison.getDifferences(discriminant);
for (Diff diff : candidates) {
if (diff == input) {
// ignore this one
} else {
DifferenceKind relatedExtensionKind = getRelatedExtensionKind(diff);
if ((relatedExtensionKind == DifferenceKind.ADD
|| relatedExtensionKind == DifferenceKind.DELETE)
&& getDiscriminant(diff) == discriminant) {
result = true;
break;
}
}
}
}
return result;
}
}