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
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
;
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
;
55 * The requirements engine is in charge of actually computing the requirements between the differences.
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.
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
{
67 private static final Logger LOGGER
= Logger
.getLogger(DefaultReqEngine
.class);
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()) {
88 .format("detect requirement - END - Took %d ms", Long
.valueOf(System
.currentTimeMillis() - start
))); //$NON-NLS-1$
93 * Checks the potential required differences from the given <code>difference</code>.
96 * The comparison this engine is expected to complete.
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();
110 boolean isAddition
= isAddOrSetDiff(difference
);
111 boolean isDeletion
= !isAddition
&& isDeleteOrUnsetDiff(difference
);
113 if (isAddition
&& isDeleteOrAddResourceAttachmentChange(comparison
, difference
)) {
114 requiredDifferences
.addAll(getDiffsThatShouldDependOn((ResourceAttachmentChange
)difference
));
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
));
127 } else if (isAddition
&& !isFeatureMapContainment(difference
)) {
129 // -> requires ADD of the value of the reference (target object)
130 requiredDifferences
.addAll(getDifferenceOnGivenObject(comparison
, value
, difference
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
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
));
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
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
));
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
175 // -> requires MOVE of the container of the object
176 requiredDifferences
.addAll(getDifferenceOnGivenObject(comparison
, container
, difference
177 .getSource(), MOVE
));
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
192 difference
.getRequires().addAll(
193 Collections2
.filter(requiredDifferences
, EMFComparePredicates
.fromSide(difference
195 difference
.getRequiredBy().addAll(
196 Collections2
.filter(requiredByDifferences
, EMFComparePredicates
.fromSide(difference
203 * Checks whether the given diff corresponds to a reference change associated with the addition or the
204 * deletion of an object.
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()) {
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)
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()) {
251 * From a <code>sourceDifference</code> (ADD) on a containment mono-valued reference, it retrieves a
252 * potential DELETE difference on the origin value.
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
)) {
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
);
280 * Retrieve candidate reference changes based on the given <code>object</code> (value) which are from the
281 * given <code>kind</code>.
284 * the comparison to search in.
288 * source of the differences. A diff from the left cannot "require" a diff from the right...
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
,
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
309 * The object for which we seek containmnent differences.
311 * source of the differences. A diff from the left cannot "require" a diff from the right...
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
) {
324 boolean result
= false;
325 if (input
instanceof ReferenceChange
326 && ((ReferenceChange
)input
).getReference().isContainment()) {
328 } else if (input
instanceof ResourceAttachmentChange
&& object
.eContainer() == null) {
337 * Retrieve candidate reference changes from a list of given <code>objects</code>.
339 * @see DefaultReqEngine#getDifferenceOnGivenObject(EObject, DifferenceKind).
341 * the comparison to search in.
345 * source of the differences. A diff from the left cannot "require" a diff from the right...
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
));
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>.
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
);
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
);
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>.
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
);
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
);
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.
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.
442 * The comparison during which this {@code diff} was detected.
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
;