[492341] Res Att Change correctly implies diffs
[EMFCompare2.git] / plugins / org.eclipse.emf.compare / src / org / eclipse / emf / compare / req / DefaultReqEngine.java
bloba01e6d61116aa9ce615d8688c789c35fb879ae0e
1 /*******************************************************************************
2 * Copyright (c) 2012, 2016 Obeo.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the Eclipse Public License v1.0
5 * which accompanies this distribution, and is available at
6 * http://www.eclipse.org/legal/epl-v10.html
7 *
8 * Contributors:
9 * Obeo - initial API and implementation
10 * Stefan Dirix - Bug 460902
11 *******************************************************************************/
12 package org.eclipse.emf.compare.req;
14 import static com.google.common.base.Predicates.and;
15 import static com.google.common.base.Predicates.instanceOf;
16 import static com.google.common.base.Predicates.or;
17 import static com.google.common.collect.Iterables.filter;
18 import static org.eclipse.emf.compare.DifferenceKind.ADD;
19 import static org.eclipse.emf.compare.DifferenceKind.CHANGE;
20 import static org.eclipse.emf.compare.DifferenceKind.DELETE;
21 import static org.eclipse.emf.compare.DifferenceKind.MOVE;
22 import static org.eclipse.emf.compare.internal.utils.ComparisonUtil.isAddOrSetDiff;
23 import static org.eclipse.emf.compare.internal.utils.ComparisonUtil.isDeleteOrUnsetDiff;
24 import static org.eclipse.emf.compare.internal.utils.ComparisonUtil.isFeatureMapContainment;
25 import static org.eclipse.emf.compare.utils.EMFComparePredicates.ofKind;
27 import com.google.common.base.Predicate;
28 import com.google.common.collect.Collections2;
30 import java.util.LinkedHashSet;
31 import java.util.List;
32 import java.util.Set;
34 import org.apache.log4j.Logger;
35 import org.eclipse.emf.common.util.EList;
36 import org.eclipse.emf.common.util.Monitor;
37 import org.eclipse.emf.compare.Comparison;
38 import org.eclipse.emf.compare.ComparisonCanceledException;
39 import org.eclipse.emf.compare.Diff;
40 import org.eclipse.emf.compare.DifferenceKind;
41 import org.eclipse.emf.compare.DifferenceSource;
42 import org.eclipse.emf.compare.EMFCompareMessages;
43 import org.eclipse.emf.compare.FeatureMapChange;
44 import org.eclipse.emf.compare.Match;
45 import org.eclipse.emf.compare.ReferenceChange;
46 import org.eclipse.emf.compare.ResourceAttachmentChange;
47 import org.eclipse.emf.compare.utils.EMFComparePredicates;
48 import org.eclipse.emf.compare.utils.MatchUtil;
49 import org.eclipse.emf.compare.utils.ReferenceUtil;
50 import org.eclipse.emf.ecore.EObject;
51 import org.eclipse.emf.ecore.EReference;
52 import org.eclipse.emf.ecore.util.FeatureMap;
54 /**
55 * The requirements engine is in charge of actually computing the requirements between the differences.
56 * <p>
57 * This default implementation aims at being generic enough to be used for any model, whatever the metamodel.
58 * However, specific requirements might be necessary.
59 * </p>
60 * TODO document available extension possibilities. TODO to test on XSD models for FeatureMaps
62 * @author <a href="mailto:cedric.notot@obeo.fr">Cedric Notot</a>
64 public class DefaultReqEngine implements IReqEngine {
66 /** The logger. */
67 private static final Logger LOGGER = Logger.getLogger(DefaultReqEngine.class);
69 /**
70 * {@inheritDoc}
72 * @see org.eclipse.emf.compare.req.IReqEngine#computeRequirements(Comparison, Monitor)
74 public void computeRequirements(Comparison comparison, Monitor monitor) {
75 long start = System.currentTimeMillis();
76 if (LOGGER.isDebugEnabled()) {
77 LOGGER.debug(String.format("detect requirements - START")); //$NON-NLS-1$
79 monitor.subTask(EMFCompareMessages.getString("DefaultReqEngine.monitor.req")); //$NON-NLS-1$
80 for (Diff difference : comparison.getDifferences()) {
81 if (monitor.isCanceled()) {
82 throw new ComparisonCanceledException();
84 checkForRequiredDifferences(comparison, difference);
86 if (LOGGER.isInfoEnabled()) {
87 LOGGER.info(String
88 .format("detect requirement - END - Took %d ms", Long.valueOf(System.currentTimeMillis() - start))); //$NON-NLS-1$
92 /**
93 * Checks the potential required differences from the given <code>difference</code>.
95 * @param comparison
96 * The comparison this engine is expected to complete.
97 * @param difference
98 * The difference that is to be checked
100 protected void checkForRequiredDifferences(Comparison comparison, Diff difference) {
102 Set<Diff> requiredDifferences = new LinkedHashSet<Diff>();
103 Set<Diff> requiredByDifferences = new LinkedHashSet<Diff>();
105 Match match = difference.getMatch();
106 EObject value = getValue(comparison, difference);
107 DifferenceKind kind = difference.getKind();
109 if (value != null) {
110 boolean isAddition = isAddOrSetDiff(difference);
111 boolean isDeletion = !isAddition && isDeleteOrUnsetDiff(difference);
113 if (isAddition && isDeleteOrAddResourceAttachmentChange(comparison, difference)) {
114 requiredDifferences.addAll(getDiffsThatShouldDependOn((ResourceAttachmentChange)difference));
115 // ADD object
116 } else if (isAddition && isReferenceContainment(difference)) {
117 // if (isAddition && isReferenceContainment(difference)) {
119 // -> requires ADD on the container of the object
120 requiredDifferences.addAll(getDifferenceOnGivenObject(comparison, value.eContainer(),
121 difference.getSource(), ADD));
123 // -> requires DELETE of the origin value on the same containment mono-valued reference
124 requiredDifferences.addAll(getDELOriginValueOnContainmentRefSingle(comparison, difference));
126 // ADD reference
127 } else if (isAddition && !isFeatureMapContainment(difference)) {
129 // -> requires ADD of the value of the reference (target object)
130 requiredDifferences.addAll(getDifferenceOnGivenObject(comparison, value, difference
131 .getSource(), ADD));
133 // -> requires ADD of the object containing the reference
134 final EObject container = MatchUtil.getContainer(comparison, difference);
135 if (container != null) {
136 requiredDifferences.addAll(getDifferenceOnGivenObject(comparison, container, difference
137 .getSource(), ADD));
139 requiredDifferences.addAll(Collections2.filter(match.getDifferences(), and(
140 instanceOf(ResourceAttachmentChange.class), ofKind(ADD))));
142 } else if (isDeletion && isDeleteOrAddResourceAttachmentChange(comparison, difference)) {
143 requiredByDifferences
144 .addAll(getDiffsThatShouldDependOn((ResourceAttachmentChange)difference));
145 // DELETE object
146 } else if (isDeletion && isReferenceContainment(difference)) {
148 // -> requires DELETE of the outgoing references and contained objects
149 requiredDifferences.addAll(getDELOutgoingReferences(comparison, difference));
150 requiredDifferences.addAll(getDifferenceOnGivenObject(comparison, value.eContents(),
151 difference.getSource(), DELETE));
153 // -> requires MOVE of contained objects
154 requiredDifferences.addAll(getMOVEContainedObjects(comparison, difference));
156 // The DELETE or CHANGE of incoming references are handled in the DELETE reference and CHANGE
157 // reference cases.
159 // DELETE reference
160 } else if (isDeletion && !isFeatureMapContainment(difference)) {
162 // -> is required by DELETE of the target object
163 requiredByDifferences.addAll(getDifferenceOnGivenObject(comparison, value, difference
164 .getSource(), DELETE));
166 // MOVE object
167 } else if (kind == MOVE && isReferenceContainment(difference)) {
169 EObject container = value.eContainer();
171 // -> requires ADD on the container of the object
172 requiredDifferences.addAll(getDifferenceOnGivenObject(comparison, container, difference
173 .getSource(), ADD));
175 // -> requires MOVE of the container of the object
176 requiredDifferences.addAll(getDifferenceOnGivenObject(comparison, container, difference
177 .getSource(), MOVE));
179 // CHANGE reference
180 } else if (kind == CHANGE && !isAddition && !isDeletion
181 && !(difference instanceof FeatureMapChange)) {
183 // -> is required by DELETE of the origin target object
184 requiredByDifferences.addAll(getDifferenceOnGivenObject(comparison, MatchUtil.getOriginValue(
185 comparison, (ReferenceChange)difference), difference.getSource(), DELETE));
187 // -> requires ADD of the value of the reference (target object) if required
188 requiredDifferences.addAll(getDifferenceOnGivenObject(comparison, value, difference
189 .getSource(), ADD));
192 difference.getRequires().addAll(
193 Collections2.filter(requiredDifferences, EMFComparePredicates.fromSide(difference
194 .getSource())));
195 difference.getRequiredBy().addAll(
196 Collections2.filter(requiredByDifferences, EMFComparePredicates.fromSide(difference
197 .getSource())));
203 * Checks whether the given diff corresponds to a reference change associated with the addition or the
204 * deletion of an object.
206 * @param comparison
207 * The comparison
208 * @param diff
209 * The diff to consider
210 * @return <code>true</code> if the given {@code diff} is to be considered a ResourceAttachmentChange with
211 * ADD or DELETE dependencies, <code>false</code> otherwise.
213 private boolean isDeleteOrAddResourceAttachmentChange(Comparison comparison, Diff diff) {
214 if (diff instanceof ResourceAttachmentChange && (diff.getKind() == ADD || diff.getKind() == DELETE)) {
215 EObject container = MatchUtil.getContainer(comparison, diff);
216 if (container != null) {
217 EList<Diff> differences = comparison.getDifferences(container);
218 for (Diff containedDiff : differences) {
219 if (containedDiff instanceof ReferenceChange
220 && ((ReferenceChange)containedDiff).getReference().isContainment()
221 && containedDiff.getKind() == diff.getKind()) {
222 return true;
227 return false;
231 * Compute the dependencies specific to ResourceAttachmentChanges DELETE or ADD. (The addition or deletion
232 * of the package controlled/uncontrolled must be a dependency of the RAC)
234 * @param diff
235 * The given difference
236 * @return a list of dependencies
238 private Set<ReferenceChange> getDiffsThatShouldDependOn(ResourceAttachmentChange diff) {
239 Set<ReferenceChange> result = new LinkedHashSet<ReferenceChange>();
240 Comparison comparison = diff.getMatch().getComparison();
241 EObject container = MatchUtil.getContainer(comparison, diff);
242 for (ReferenceChange rc : filter(comparison.getDifferences(container), ReferenceChange.class)) {
243 if (diff.getSource() == rc.getSource() && diff.getKind() == rc.getKind()) {
244 result.add(rc);
247 return result;
251 * From a <code>sourceDifference</code> (ADD) on a containment mono-valued reference, it retrieves a
252 * potential DELETE difference on the origin value.
254 * @param comparison
255 * The comparison this engine is expected to complete.
256 * @param sourceDifference
257 * The given difference.
258 * @return The found differences.
260 private Set<Diff> getDELOriginValueOnContainmentRefSingle(Comparison comparison, Diff sourceDifference) {
261 Set<Diff> result = new LinkedHashSet<Diff>();
262 if (!(sourceDifference instanceof ReferenceChange)) {
263 return result;
265 EReference reference = ((ReferenceChange)sourceDifference).getReference();
266 if (!reference.isMany()) {
267 EObject originContainer = MatchUtil.getOriginContainer(comparison, sourceDifference);
268 if (originContainer != null) {
269 Object originValue = ReferenceUtil.safeEGet(originContainer, reference);
270 if (originValue instanceof EObject) {
271 result = getDifferenceOnGivenObject(comparison, (EObject)originValue, sourceDifference
272 .getSource(), DELETE);
276 return result;
280 * Retrieve candidate reference changes based on the given <code>object</code> (value) which are from the
281 * given <code>kind</code>.
283 * @param comparison
284 * the comparison to search in.
285 * @param object
286 * The given object.
287 * @param source
288 * source of the differences. A diff from the left cannot "require" a diff from the right...
289 * @param kind
290 * The given kind.
291 * @return The found differences.
293 private Set<Diff> getDifferenceOnGivenObject(Comparison comparison, EObject object,
294 DifferenceSource source, DifferenceKind kind) {
295 final Set<Diff> result = new LinkedHashSet<Diff>();
296 for (Diff diff : filter(comparison.getDifferences(object), isRequiredContainmentChange(object,
297 source, kind))) {
298 result.add(diff);
300 return result;
304 * Will be used to filter out of a list of differences all those that relate to containment changes on the
305 * given object (a containment reference change or a resource attachment change if the given object has no
306 * direct container.
308 * @param object
309 * The object for which we seek containmnent differences.
310 * @param source
311 * source of the differences. A diff from the left cannot "require" a diff from the right...
312 * @param kind
313 * The kind of difference we seek.
314 * @return The created predicate.
316 private Predicate<? super Diff> isRequiredContainmentChange(final EObject object,
317 final DifferenceSource source, final DifferenceKind kind) {
318 return new Predicate<Diff>() {
319 public boolean apply(Diff input) {
320 if (input == null || input.getKind() != kind || input.getSource() != source) {
321 return false;
324 boolean result = false;
325 if (input instanceof ReferenceChange
326 && ((ReferenceChange)input).getReference().isContainment()) {
327 result = true;
328 } else if (input instanceof ResourceAttachmentChange && object.eContainer() == null) {
329 result = true;
331 return result;
337 * Retrieve candidate reference changes from a list of given <code>objects</code>.
339 * @see DefaultReqEngine#getDifferenceOnGivenObject(EObject, DifferenceKind).
340 * @param comparison
341 * the comparison to search in.
342 * @param objects
343 * The given objects.
344 * @param source
345 * source of the differences. A diff from the left cannot "require" a diff from the right...
346 * @param kind
347 * The kind of requested differences.
348 * @return The found differences.
350 private Set<Diff> getDifferenceOnGivenObject(Comparison comparison, List<EObject> objects,
351 DifferenceSource source, DifferenceKind kind) {
352 Set<Diff> result = new LinkedHashSet<Diff>();
353 for (EObject object : objects) {
354 result.addAll(getDifferenceOnGivenObject(comparison, object, source, kind));
356 return result;
360 * From a <code>sourceDifference</code> (DELETE) on a containment reference, it retrieves potential DELETE
361 * differences on the outgoing references from the value object of the <code>sourceDifference</code>.
363 * @param comparison
364 * The comparison this engine is expected to complete.
365 * @param sourceDifference
366 * The given difference.
367 * @return The found differences.
369 private Set<Diff> getDELOutgoingReferences(Comparison comparison, Diff sourceDifference) {
370 Set<Diff> result = new LinkedHashSet<Diff>();
372 EObject value = getValue(comparison, sourceDifference);
374 if (value != null) {
375 final Match valueMatch = comparison.getMatch(value);
376 if (valueMatch != null) {
377 for (Diff candidate : filter(valueMatch.getDifferences(), or(
378 instanceOf(ReferenceChange.class), instanceOf(FeatureMapChange.class)))) {
379 if (candidate.getSource() == sourceDifference.getSource()
380 && (candidate.getKind() == DELETE || isDeleteOrUnsetDiff(candidate))) {
381 result.add(candidate);
387 return result;
391 * From a <code>sourceDifference</code> (DELETE) on a containment reference, it retrieves potential MOVE
392 * differences on the objects contained in the value object of the <code>sourceDifference</code>.
394 * @param comparison
395 * The comparison this engine is expected to complete.
396 * @param sourceDifference
397 * The given difference.
398 * @return The found differences.
400 private Set<ReferenceChange> getMOVEContainedObjects(Comparison comparison, Diff sourceDifference) {
401 Set<ReferenceChange> result = new LinkedHashSet<ReferenceChange>();
402 EObject value = getValue(comparison, sourceDifference);
403 if (value != null) {
404 List<EObject> contents = value.eContents();
405 for (EObject content : contents) {
406 EObject originObject = MatchUtil.getOriginObject(comparison, content);
407 if (originObject != null) {
408 for (ReferenceChange difference : filter(comparison.getDifferences(originObject),
409 ReferenceChange.class)) {
410 if (difference.getReference().isContainment()
411 && difference.getSource() == sourceDifference.getSource()
412 && difference.getKind() == MOVE) {
413 result.add(difference);
420 return result;
424 * Checks whether the given diff corresponds to a containment change. This holds true for differences on
425 * containment references' values, but also for feature map or resource attachment changes.
427 * @param diff
428 * The diff to consider.
429 * @return <code>true</code> if the given {@code diff} is to be considered a containment change,
430 * <code>false</code> otherwise.
432 private static boolean isReferenceContainment(Diff diff) {
433 return diff instanceof ReferenceChange && ((ReferenceChange)diff).getReference().isContainment()
434 || diff instanceof ResourceAttachmentChange || diff instanceof FeatureMapChange;
438 * Retrieves the "value" of the given containment change. This will be either the "value" field of a
439 * ReferenceChange, or the side of the parent match for a resource attachment change.
441 * @param comparison
442 * The comparison during which this {@code diff} was detected.
443 * @param diff
444 * The diff which value we are to retrieve.
445 * @return The "value" of the given containment change.
447 private static EObject getValue(Comparison comparison, Diff diff) {
448 EObject value = null;
449 if (diff instanceof ReferenceChange) {
450 value = ((ReferenceChange)diff).getValue();
451 } else if (diff instanceof ResourceAttachmentChange) {
452 value = MatchUtil.getContainer(comparison, diff);
453 } else if (diff instanceof FeatureMapChange) {
454 Object entry = ((FeatureMapChange)diff).getValue();
455 if (entry instanceof FeatureMap.Entry) {
456 Object entryValue = ((FeatureMap.Entry)entry).getValue();
457 if (entryValue instanceof EObject) {
458 value = (EObject)entryValue;
462 return value;