| /******************************************************************************* |
| * Copyright (c) 2012, 2016 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 |
| * Stefan Dirix - Bug 460902 |
| *******************************************************************************/ |
| package org.eclipse.emf.compare.req; |
| |
| import static com.google.common.base.Predicates.and; |
| import static com.google.common.base.Predicates.instanceOf; |
| import static com.google.common.base.Predicates.or; |
| import static com.google.common.collect.Iterables.filter; |
| import static org.eclipse.emf.compare.DifferenceKind.ADD; |
| import static org.eclipse.emf.compare.DifferenceKind.CHANGE; |
| import static org.eclipse.emf.compare.DifferenceKind.DELETE; |
| import static org.eclipse.emf.compare.DifferenceKind.MOVE; |
| import static org.eclipse.emf.compare.internal.utils.ComparisonUtil.isAddOrSetDiff; |
| import static org.eclipse.emf.compare.internal.utils.ComparisonUtil.isDeleteOrUnsetDiff; |
| import static org.eclipse.emf.compare.internal.utils.ComparisonUtil.isFeatureMapContainment; |
| import static org.eclipse.emf.compare.utils.EMFComparePredicates.ofKind; |
| |
| import com.google.common.base.Predicate; |
| import com.google.common.collect.Collections2; |
| |
| import java.util.LinkedHashSet; |
| import java.util.List; |
| import java.util.Set; |
| |
| import org.apache.log4j.Logger; |
| import org.eclipse.emf.common.util.EList; |
| import org.eclipse.emf.common.util.Monitor; |
| import org.eclipse.emf.compare.Comparison; |
| import org.eclipse.emf.compare.ComparisonCanceledException; |
| import org.eclipse.emf.compare.Diff; |
| import org.eclipse.emf.compare.DifferenceKind; |
| import org.eclipse.emf.compare.DifferenceSource; |
| import org.eclipse.emf.compare.EMFCompareMessages; |
| import org.eclipse.emf.compare.FeatureMapChange; |
| import org.eclipse.emf.compare.Match; |
| import org.eclipse.emf.compare.ReferenceChange; |
| import org.eclipse.emf.compare.ResourceAttachmentChange; |
| 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.util.FeatureMap; |
| |
| /** |
| * The requirements engine is in charge of actually computing the requirements between the differences. |
| * <p> |
| * This default implementation aims at being generic enough to be used for any model, whatever the metamodel. |
| * However, specific requirements might be necessary. |
| * </p> |
| * TODO document available extension possibilities. TODO to test on XSD models for FeatureMaps |
| * |
| * @author <a href="mailto:cedric.notot@obeo.fr">Cedric Notot</a> |
| */ |
| public class DefaultReqEngine implements IReqEngine { |
| |
| /** The logger. */ |
| private static final Logger LOGGER = Logger.getLogger(DefaultReqEngine.class); |
| |
| /** |
| * {@inheritDoc} |
| * |
| * @see org.eclipse.emf.compare.req.IReqEngine#computeRequirements(Comparison, Monitor) |
| */ |
| public void computeRequirements(Comparison comparison, Monitor monitor) { |
| long start = System.currentTimeMillis(); |
| if (LOGGER.isDebugEnabled()) { |
| LOGGER.debug(String.format("detect requirements - START")); //$NON-NLS-1$ |
| } |
| monitor.subTask(EMFCompareMessages.getString("DefaultReqEngine.monitor.req")); //$NON-NLS-1$ |
| for (Diff difference : comparison.getDifferences()) { |
| if (monitor.isCanceled()) { |
| throw new ComparisonCanceledException(); |
| } |
| checkForRequiredDifferences(comparison, difference); |
| } |
| if (LOGGER.isInfoEnabled()) { |
| LOGGER.info(String |
| .format("detect requirement - END - Took %d ms", Long.valueOf(System.currentTimeMillis() - start))); //$NON-NLS-1$ |
| } |
| } |
| |
| /** |
| * Checks the potential required differences from the given <code>difference</code>. |
| * |
| * @param comparison |
| * The comparison this engine is expected to complete. |
| * @param difference |
| * The difference that is to be checked |
| */ |
| protected void checkForRequiredDifferences(Comparison comparison, Diff difference) { |
| |
| Set<Diff> requiredDifferences = new LinkedHashSet<Diff>(); |
| Set<Diff> requiredByDifferences = new LinkedHashSet<Diff>(); |
| |
| Match match = difference.getMatch(); |
| EObject value = getValue(comparison, difference); |
| DifferenceKind kind = difference.getKind(); |
| |
| if (value != null) { |
| boolean isAddition = isAddOrSetDiff(difference); |
| boolean isDeletion = !isAddition && isDeleteOrUnsetDiff(difference); |
| |
| if (isAddition && isDeleteOrAddResourceAttachmentChange(comparison, difference)) { |
| requiredDifferences.addAll(getDiffsThatShouldDependOn((ResourceAttachmentChange)difference)); |
| // ADD object |
| } else if (isAddition && isReferenceContainment(difference)) { |
| // if (isAddition && isReferenceContainment(difference)) { |
| |
| // -> requires ADD on the container of the object |
| requiredDifferences.addAll(getDifferenceOnGivenObject(comparison, value.eContainer(), |
| difference.getSource(), ADD)); |
| |
| // -> requires DELETE of the origin value on the same containment mono-valued reference |
| requiredDifferences.addAll(getDELOriginValueOnContainmentRefSingle(comparison, difference)); |
| |
| // ADD reference |
| } else if (isAddition && !isFeatureMapContainment(difference)) { |
| |
| // -> requires ADD of the value of the reference (target object) |
| requiredDifferences.addAll(getDifferenceOnGivenObject(comparison, value, difference |
| .getSource(), ADD)); |
| |
| // -> requires ADD of the object containing the reference |
| final EObject container = MatchUtil.getContainer(comparison, difference); |
| if (container != null) { |
| requiredDifferences.addAll(getDifferenceOnGivenObject(comparison, container, difference |
| .getSource(), ADD)); |
| } |
| requiredDifferences.addAll(Collections2.filter(match.getDifferences(), and( |
| instanceOf(ResourceAttachmentChange.class), ofKind(ADD)))); |
| |
| } else if (isDeletion && isDeleteOrAddResourceAttachmentChange(comparison, difference)) { |
| requiredByDifferences |
| .addAll(getDiffsThatShouldDependOn((ResourceAttachmentChange)difference)); |
| // DELETE object |
| } else if (isDeletion && isReferenceContainment(difference)) { |
| |
| // -> requires DELETE of the outgoing references and contained objects |
| requiredDifferences.addAll(getDELOutgoingReferences(comparison, difference)); |
| requiredDifferences.addAll(getDifferenceOnGivenObject(comparison, value.eContents(), |
| difference.getSource(), DELETE)); |
| |
| // -> requires MOVE of contained objects |
| requiredDifferences.addAll(getMOVEContainedObjects(comparison, difference)); |
| |
| // The DELETE or CHANGE of incoming references are handled in the DELETE reference and CHANGE |
| // reference cases. |
| |
| // DELETE reference |
| } else if (isDeletion && !isFeatureMapContainment(difference)) { |
| |
| // -> is required by DELETE of the target object |
| requiredByDifferences.addAll(getDifferenceOnGivenObject(comparison, value, difference |
| .getSource(), DELETE)); |
| |
| // MOVE object |
| } else if (kind == MOVE && isReferenceContainment(difference)) { |
| |
| EObject container = value.eContainer(); |
| |
| // -> requires ADD on the container of the object |
| requiredDifferences.addAll(getDifferenceOnGivenObject(comparison, container, difference |
| .getSource(), ADD)); |
| |
| // -> requires MOVE of the container of the object |
| requiredDifferences.addAll(getDifferenceOnGivenObject(comparison, container, difference |
| .getSource(), MOVE)); |
| |
| // CHANGE reference |
| } else if (kind == CHANGE && !isAddition && !isDeletion |
| && !(difference instanceof FeatureMapChange)) { |
| |
| // -> is required by DELETE of the origin target object |
| requiredByDifferences.addAll(getDifferenceOnGivenObject(comparison, MatchUtil.getOriginValue( |
| comparison, (ReferenceChange)difference), difference.getSource(), DELETE)); |
| |
| // -> requires ADD of the value of the reference (target object) if required |
| requiredDifferences.addAll(getDifferenceOnGivenObject(comparison, value, difference |
| .getSource(), ADD)); |
| } |
| |
| difference.getRequires().addAll( |
| Collections2.filter(requiredDifferences, EMFComparePredicates.fromSide(difference |
| .getSource()))); |
| difference.getRequiredBy().addAll( |
| Collections2.filter(requiredByDifferences, EMFComparePredicates.fromSide(difference |
| .getSource()))); |
| } |
| |
| } |
| |
| /** |
| * Checks whether the given diff corresponds to a reference change associated with the addition or the |
| * deletion of an object. |
| * |
| * @param comparison |
| * The comparison |
| * @param diff |
| * The diff to consider |
| * @return <code>true</code> if the given {@code diff} is to be considered a ResourceAttachmentChange with |
| * ADD or DELETE dependencies, <code>false</code> otherwise. |
| */ |
| private boolean isDeleteOrAddResourceAttachmentChange(Comparison comparison, Diff diff) { |
| if (diff instanceof ResourceAttachmentChange && (diff.getKind() == ADD || diff.getKind() == DELETE)) { |
| EObject container = MatchUtil.getContainer(comparison, diff); |
| if (container != null) { |
| EList<Diff> differences = comparison.getDifferences(container); |
| for (Diff containedDiff : differences) { |
| if (containedDiff instanceof ReferenceChange |
| && ((ReferenceChange)containedDiff).getReference().isContainment() |
| && containedDiff.getKind() == diff.getKind()) { |
| return true; |
| } |
| } |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Compute the dependencies specific to ResourceAttachmentChanges DELETE or ADD. (The addition or deletion |
| * of the package controlled/uncontrolled must be a dependency of the RAC) |
| * |
| * @param diff |
| * The given difference |
| * @return a list of dependencies |
| */ |
| private Set<ReferenceChange> getDiffsThatShouldDependOn(ResourceAttachmentChange diff) { |
| Set<ReferenceChange> result = new LinkedHashSet<ReferenceChange>(); |
| Comparison comparison = diff.getMatch().getComparison(); |
| EObject container = MatchUtil.getContainer(comparison, diff); |
| for (ReferenceChange rc : filter(comparison.getDifferences(container), ReferenceChange.class)) { |
| if (diff.getSource() == rc.getSource() && diff.getKind() == rc.getKind()) { |
| result.add(rc); |
| } |
| } |
| return result; |
| } |
| |
| /** |
| * From a <code>sourceDifference</code> (ADD) on a containment mono-valued reference, it retrieves a |
| * potential DELETE difference on the origin value. |
| * |
| * @param comparison |
| * The comparison this engine is expected to complete. |
| * @param sourceDifference |
| * The given difference. |
| * @return The found differences. |
| */ |
| private Set<Diff> getDELOriginValueOnContainmentRefSingle(Comparison comparison, Diff sourceDifference) { |
| Set<Diff> result = new LinkedHashSet<Diff>(); |
| if (!(sourceDifference instanceof ReferenceChange)) { |
| return result; |
| } |
| EReference reference = ((ReferenceChange)sourceDifference).getReference(); |
| if (!reference.isMany()) { |
| EObject originContainer = MatchUtil.getOriginContainer(comparison, sourceDifference); |
| if (originContainer != null) { |
| Object originValue = ReferenceUtil.safeEGet(originContainer, reference); |
| if (originValue instanceof EObject) { |
| result = getDifferenceOnGivenObject(comparison, (EObject)originValue, sourceDifference |
| .getSource(), DELETE); |
| } |
| } |
| } |
| return result; |
| } |
| |
| /** |
| * Retrieve candidate reference changes based on the given <code>object</code> (value) which are from the |
| * given <code>kind</code>. |
| * |
| * @param comparison |
| * the comparison to search in. |
| * @param object |
| * The given object. |
| * @param source |
| * source of the differences. A diff from the left cannot "require" a diff from the right... |
| * @param kind |
| * The given kind. |
| * @return The found differences. |
| */ |
| private Set<Diff> getDifferenceOnGivenObject(Comparison comparison, EObject object, |
| DifferenceSource source, DifferenceKind kind) { |
| final Set<Diff> result = new LinkedHashSet<Diff>(); |
| for (Diff diff : filter(comparison.getDifferences(object), isRequiredContainmentChange(object, |
| source, kind))) { |
| result.add(diff); |
| } |
| return result; |
| } |
| |
| /** |
| * Will be used to filter out of a list of differences all those that relate to containment changes on the |
| * given object (a containment reference change or a resource attachment change if the given object has no |
| * direct container. |
| * |
| * @param object |
| * The object for which we seek containmnent differences. |
| * @param source |
| * source of the differences. A diff from the left cannot "require" a diff from the right... |
| * @param kind |
| * The kind of difference we seek. |
| * @return The created predicate. |
| */ |
| private Predicate<? super Diff> isRequiredContainmentChange(final EObject object, |
| final DifferenceSource source, final DifferenceKind kind) { |
| return new Predicate<Diff>() { |
| public boolean apply(Diff input) { |
| if (input == null || input.getKind() != kind || input.getSource() != source) { |
| return false; |
| } |
| |
| boolean result = false; |
| if (input instanceof ReferenceChange |
| && ((ReferenceChange)input).getReference().isContainment()) { |
| result = true; |
| } else if (input instanceof ResourceAttachmentChange && object.eContainer() == null) { |
| result = true; |
| } |
| return result; |
| } |
| }; |
| } |
| |
| /** |
| * Retrieve candidate reference changes from a list of given <code>objects</code>. |
| * |
| * @see DefaultReqEngine#getDifferenceOnGivenObject(EObject, DifferenceKind). |
| * @param comparison |
| * the comparison to search in. |
| * @param objects |
| * The given objects. |
| * @param source |
| * source of the differences. A diff from the left cannot "require" a diff from the right... |
| * @param kind |
| * The kind of requested differences. |
| * @return The found differences. |
| */ |
| private Set<Diff> getDifferenceOnGivenObject(Comparison comparison, List<EObject> objects, |
| DifferenceSource source, DifferenceKind kind) { |
| Set<Diff> result = new LinkedHashSet<Diff>(); |
| for (EObject object : objects) { |
| result.addAll(getDifferenceOnGivenObject(comparison, object, source, kind)); |
| } |
| return result; |
| } |
| |
| /** |
| * From a <code>sourceDifference</code> (DELETE) on a containment reference, it retrieves potential DELETE |
| * differences on the outgoing references from the value object of the <code>sourceDifference</code>. |
| * |
| * @param comparison |
| * The comparison this engine is expected to complete. |
| * @param sourceDifference |
| * The given difference. |
| * @return The found differences. |
| */ |
| private Set<Diff> getDELOutgoingReferences(Comparison comparison, Diff sourceDifference) { |
| Set<Diff> result = new LinkedHashSet<Diff>(); |
| |
| EObject value = getValue(comparison, sourceDifference); |
| |
| if (value != null) { |
| final Match valueMatch = comparison.getMatch(value); |
| if (valueMatch != null) { |
| for (Diff candidate : filter(valueMatch.getDifferences(), or( |
| instanceOf(ReferenceChange.class), instanceOf(FeatureMapChange.class)))) { |
| if (candidate.getSource() == sourceDifference.getSource() |
| && (candidate.getKind() == DELETE || isDeleteOrUnsetDiff(candidate))) { |
| result.add(candidate); |
| } |
| } |
| } |
| } |
| |
| return result; |
| } |
| |
| /** |
| * From a <code>sourceDifference</code> (DELETE) on a containment reference, it retrieves potential MOVE |
| * differences on the objects contained in the value object of the <code>sourceDifference</code>. |
| * |
| * @param comparison |
| * The comparison this engine is expected to complete. |
| * @param sourceDifference |
| * The given difference. |
| * @return The found differences. |
| */ |
| private Set<ReferenceChange> getMOVEContainedObjects(Comparison comparison, Diff sourceDifference) { |
| Set<ReferenceChange> result = new LinkedHashSet<ReferenceChange>(); |
| EObject value = getValue(comparison, sourceDifference); |
| if (value != null) { |
| List<EObject> contents = value.eContents(); |
| for (EObject content : contents) { |
| EObject originObject = MatchUtil.getOriginObject(comparison, content); |
| if (originObject != null) { |
| for (ReferenceChange difference : filter(comparison.getDifferences(originObject), |
| ReferenceChange.class)) { |
| if (difference.getReference().isContainment() |
| && difference.getSource() == sourceDifference.getSource() |
| && difference.getKind() == MOVE) { |
| result.add(difference); |
| } |
| |
| } |
| } |
| } |
| } |
| return result; |
| } |
| |
| /** |
| * Checks whether the given diff corresponds to a containment change. This holds true for differences on |
| * containment references' values, but also for feature map or resource attachment changes. |
| * |
| * @param diff |
| * The diff to consider. |
| * @return <code>true</code> if the given {@code diff} is to be considered a containment change, |
| * <code>false</code> otherwise. |
| */ |
| private static boolean isReferenceContainment(Diff diff) { |
| return diff instanceof ReferenceChange && ((ReferenceChange)diff).getReference().isContainment() |
| || diff instanceof ResourceAttachmentChange || diff instanceof FeatureMapChange; |
| } |
| |
| /** |
| * Retrieves the "value" of the given containment change. This will be either the "value" field of a |
| * ReferenceChange, or the side of the parent match for a resource attachment change. |
| * |
| * @param comparison |
| * The comparison during which this {@code diff} was detected. |
| * @param diff |
| * The diff which value we are to retrieve. |
| * @return The "value" of the given containment change. |
| */ |
| private static EObject getValue(Comparison comparison, Diff diff) { |
| EObject value = null; |
| if (diff instanceof ReferenceChange) { |
| value = ((ReferenceChange)diff).getValue(); |
| } else if (diff instanceof ResourceAttachmentChange) { |
| value = MatchUtil.getContainer(comparison, diff); |
| } else if (diff instanceof FeatureMapChange) { |
| Object entry = ((FeatureMapChange)diff).getValue(); |
| if (entry instanceof FeatureMap.Entry) { |
| Object entryValue = ((FeatureMap.Entry)entry).getValue(); |
| if (entryValue instanceof EObject) { |
| value = (EObject)entryValue; |
| } |
| } |
| } |
| return value; |
| } |
| |
| } |