/**
* <copyright>
*
* Copyright (c) 2010-2016 Thales Global Services S.A.S.
* 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:
* Thales Global Services S.A.S. - initial API and implementation
*
* </copyright>
*/
package org.eclipse.emf.diffmerge.impl.helpers;
import java.util.Collection;
import java.util.List;
import org.eclipse.emf.diffmerge.api.IComparison;
import org.eclipse.emf.diffmerge.api.IDiffPolicy;
import org.eclipse.emf.diffmerge.api.IMapping;
import org.eclipse.emf.diffmerge.api.IMatch;
import org.eclipse.emf.diffmerge.api.IMergePolicy;
import org.eclipse.emf.diffmerge.api.Role;
import org.eclipse.emf.diffmerge.api.scopes.IEditableModelScope;
import org.eclipse.emf.diffmerge.api.scopes.IFeaturedModelScope;
import org.eclipse.emf.ecore.EAttribute;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.util.EcoreUtil;
/**
* A unidirectional copier from a given scope to another one.
* The state as defined in the superclass is never modified, only behavior is reused.
* @author Olivier Constant
*/
public class UnidirectionalComparisonCopier extends EcoreUtil.Copier {
/** The serial version ID */
private static final long serialVersionUID = 1L;
/** The non-null role of the source for this copier */
protected final Role _sourceRole;
/** The initially null mapping this copier relies upon */
protected IMapping.Editable _mapping;
/** The initially null source scope for this copier */
protected IFeaturedModelScope _sourceScope;
/** The initially null target scope for this copier */
protected IEditableModelScope _destinationScope;
/** The potentially null diff policy */
protected IDiffPolicy _diffPolicy;
/** The potentially null merge policy to apply */
protected IMergePolicy _mergePolicy;
/**
* Constructor
* @param sourceRole_p the non-null role being the source role of this copier
* w.r.t. the comparison
*/
public UnidirectionalComparisonCopier(Role sourceRole_p) {
super(false, true);
_sourceRole = sourceRole_p;
_mapping = null;
_sourceScope = null;
_destinationScope = null;
}
/**
* Return a (shallow) copy of the given element.
* @see org.eclipse.emf.ecore.util.EcoreUtil.Copier#copy(EObject)
* Precondition: this method has never been called on the same element before,
* except if clear() was called in the meantime.
* @param element_p an element belonging to the source scope
* @return a non-null copy
*/
@Override
public EObject copy(EObject element_p) {
EObject result = copyAsProxy(element_p);
for (EAttribute attribute : element_p.eClass().getEAllAttributes()) {
if (coverFeature(attribute))
copyAttribute(attribute, element_p, result);
}
// No call to method put, so the state never changes
return result;
}
/**
* Complete the given partial match by copying its unmatched element and
* updating this mapping accordingly.
* The references of the element are not completed.
* Postcondition: !partialMatch_p.isPartial()
* @param partialMatch_p a non-null partial match
* @param comparison_p a non-null comparison
* @return a non-null element which is a clone of the element in partialMatch_p
*/
public EObject completeMatch(IMatch partialMatch_p, IComparison.Editable comparison_p) {
setComparison(comparison_p);
assert partialMatch_p.getUncoveredRole() == _sourceRole.opposite() &&
!getCompletedMatches().contains(partialMatch_p);
EObject element = partialMatch_p.get(_sourceRole);
EObject result = copy(element);
assert result != null;
_mapping.mapIncrementally(
element, _sourceRole, result, _sourceRole.opposite());
getCompletedMatches().add(_mapping.getMatchFor(element, _sourceRole));
return result;
}
/**
* Complete the references between all completed elements
* @param comparison_p a non-null comparison defining a behavioral context
*/
public void completeReferences(IComparison.Editable comparison_p) {
setComparison(comparison_p);
copyReferences();
}
/**
* Return a raw copy of the given element with only the proxy URI being set
* @param element_p a non-null element
* @return a non-null element of the same EClass and proxy URI
*/
protected EObject copyAsProxy(EObject element_p) {
EObject result = createCopy(element_p);
copyProxyURI(element_p, result);
return result;
}
/**
* @see org.eclipse.emf.ecore.util.EcoreUtil.Copier#copyAttribute(EAttribute, EObject, EObject)
*/
@Override
protected void copyAttribute(EAttribute attribute_p, EObject element_p,
EObject copy_p) {
for (Object value : _sourceScope.get(element_p, attribute_p))
_destinationScope.add(copy_p, attribute_p, value);
}
/**
* @see org.eclipse.emf.ecore.util.EcoreUtil.Copier#copyReferences()
*/
@Override
public void copyReferences() {
for (IMatch updatedMatch : getCompletedMatches())
copyReferences(updatedMatch);
// Update of containments may have changed resources, which may have an impact on IDs
if (_mergePolicy != null) {
for (IMatch updatedMatch : getCompletedMatches()) {
EObject source = updatedMatch.get(_sourceRole);
EObject target = updatedMatch.get(_sourceRole.opposite());
BidirectionalComparisonCopier.handleIDCopy(
source, _sourceScope, target, _destinationScope, _mergePolicy);
}
}
}
/**
* Copy the cross-references of the destination element of the given match
* @param match_p a non-null, non-partial match
*/
protected void copyReferences(IMatch match_p) {
EObject source = match_p.get(_sourceRole);
EObject destination = match_p.get(_sourceRole.opposite());
assert source != null && destination != null;
for (EReference reference : source.eClass().getEAllReferences()) {
if (!reference.isContainer() && coverFeature(reference))
copyReference(reference, source, destination);
}
}
/**
* @see org.eclipse.emf.ecore.util.EcoreUtil.Copier#copyReference(EReference, EObject, EObject)
*/
@Override
protected void copyReference(EReference reference_p, EObject source_p,
EObject destination_p) {
// This implementation assumes that values need only be added
List<EObject> sourceValues = _sourceScope.get(source_p, reference_p);
for (EObject sourceValue : sourceValues) {
IMatch valueMatch = _mapping.getMatchFor(sourceValue, _sourceRole);
if (valueMatch != null) {
// Value in scope
// If value is in copier or ref is unidirectional, it is not handled
// by a ref presence diff so it must be copied
boolean mustCopy = getCompletedMatches().contains(valueMatch) ||
// Being a containment means there is an implicit opposite
(reference_p.getEOpposite() == null && !reference_p.isContainment());
if (!mustCopy) {
// Otherwise, check if it is actually handled by a ref presence diff
// (it may not be because the opposite ref may not be covered by the diff policy)
IMatch holderMatch = _mapping.getMatchFor(source_p, _sourceRole);
if (holderMatch != null)
mustCopy = holderMatch.getReferenceValueDifference(
reference_p, sourceValue) == null;
}
if (mustCopy) {
EObject destinationValue = valueMatch.get(_sourceRole.opposite());
if (destinationValue != null)
_destinationScope.add(destination_p, reference_p, destinationValue);
} // Else handled by a ref presence diff
} else {
// Value out of scope: keep as is if no side effect due to bidirectionality or containment
if (useOriginalReferences && reference_p.getEOpposite() == null &&
!reference_p.isContainment() && !reference_p.isContainer() ||
_diffPolicy != null && _diffPolicy.coverOutOfScopeValue(sourceValue, reference_p)) {
_destinationScope.add(destination_p, reference_p, sourceValue);
}
}
}
}
/**
* Return whether the given feature must be copied
* @param feature_p a non-null feature
*/
protected boolean coverFeature(EStructuralFeature feature_p) {
return _mergePolicy != null && _mergePolicy.copyFeature(feature_p, _destinationScope);
}
/**
* @see java.util.LinkedHashMap#get(java.lang.Object)
*/
@Override
public EObject get(Object key_p) {
return get(key_p, true);
}
/**
* Return the element from the destination role which matches with the given element
* @param key_p a potentially null object, which for relevance should be a
* non-null element in the source role
* @param inCopierOnly_p whether the scope should be restricted to the matches
* updated by this copier
* @return a potentially null element
*/
public EObject get(Object key_p, boolean inCopierOnly_p) {
EObject result = null;
if (key_p instanceof EObject) {
IMatch match = _mapping.getMatchFor((EObject)key_p, _sourceRole);
if (match != null && (!inCopierOnly_p || getCompletedMatches().contains(match)))
result = match.get(_sourceRole.opposite());
}
return result;
}
/**
* Return the modifiable set of completed matches from the source role
* to its opposite
* @return a non-null, modifiable collection
*/
protected Collection<IMatch> getCompletedMatches() {
return _mapping.getModifiableCompletedMatches(_sourceRole.opposite());
}
/**
* Set the comparison which defines the behavioral context of this copier
* @param comparison_p a non-null comparison
*/
protected void setComparison(IComparison.Editable comparison_p) {
_mapping = comparison_p.getMapping();
_sourceScope = comparison_p.getScope(_sourceRole);
_destinationScope = comparison_p.getScope(_sourceRole.opposite());
_diffPolicy = comparison_p.getLastDiffPolicy();
_mergePolicy = comparison_p.getLastMergePolicy();
if (_mergePolicy != null)
useOriginalReferences = _mergePolicy.copyOutOfScopeCrossReferences(
_sourceScope, _destinationScope);
}
}