Fix some of the warnings
[EMFCompare2.git] / plugins / org.eclipse.emf.compare / src / org / eclipse / emf / compare / merge / AttributeChangeMerger.java
blob4b424a2f29bbf22caf2e2249eb9fcc43f82e5ae7
1 /*******************************************************************************
2 * Copyright (c) 2012, 2015 Obeo and others.
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 * Philip Langer - [446947, 458147] Support for three-way text merging
11 *******************************************************************************/
12 package org.eclipse.emf.compare.merge;
14 import static org.eclipse.emf.compare.utils.ReferenceUtil.safeEGet;
15 import static org.eclipse.emf.compare.utils.ReferenceUtil.safeEIsSet;
16 import static org.eclipse.emf.compare.utils.ReferenceUtil.safeESet;
18 import java.util.List;
20 import org.eclipse.emf.common.util.EList;
21 import org.eclipse.emf.compare.AttributeChange;
22 import org.eclipse.emf.compare.Comparison;
23 import org.eclipse.emf.compare.Diff;
24 import org.eclipse.emf.compare.DifferenceSource;
25 import org.eclipse.emf.compare.Match;
26 import org.eclipse.emf.compare.internal.ThreeWayTextDiff;
27 import org.eclipse.emf.compare.internal.utils.DiffUtil;
28 import org.eclipse.emf.ecore.EAttribute;
29 import org.eclipse.emf.ecore.EEnum;
30 import org.eclipse.emf.ecore.EEnumLiteral;
31 import org.eclipse.emf.ecore.ENamedElement;
32 import org.eclipse.emf.ecore.EObject;
33 import org.eclipse.emf.ecore.EStructuralFeature;
34 import org.eclipse.emf.ecore.impl.DynamicEObjectImpl;
36 /**
37 * This specific implementation of {@link AbstractMerger} will be used to merge attribute changes.
39 * @author <a href="mailto:laurent.goubet@obeo.fr">Laurent Goubet</a>
41 public class AttributeChangeMerger extends AbstractMerger {
42 /**
43 * {@inheritDoc}
45 * @see org.eclipse.emf.compare.merge.IMerger#isMergerFor(org.eclipse.emf.compare.Diff)
47 public boolean isMergerFor(Diff target) {
48 return target instanceof AttributeChange;
51 /**
52 * {@inheritDoc}
54 * @see org.eclipse.emf.compare.merge.AbstractMerger#accept(org.eclipse.emf.compare.Diff, boolean)
56 @Override
57 protected void accept(final Diff diff, boolean rightToLeft) {
58 AttributeChange attributeChange = (AttributeChange)diff;
59 switch (diff.getKind()) {
60 case ADD:
61 // Create the same element in right
62 addInTarget(attributeChange, rightToLeft);
63 break;
64 case DELETE:
65 // Delete that same element from right
66 removeFromTarget(attributeChange, rightToLeft);
67 break;
68 case MOVE:
69 moveElement(attributeChange, rightToLeft);
70 break;
71 case CHANGE:
72 changeValue(attributeChange, rightToLeft);
73 break;
74 default:
75 break;
79 /**
80 * {@inheritDoc}
82 * @see org.eclipse.emf.compare.merge.AbstractMerger#reject(org.eclipse.emf.compare.Diff, boolean)
84 @Override
85 protected void reject(Diff diff, boolean rightToLeft) {
86 AttributeChange attributeChange = (AttributeChange)diff;
87 switch (diff.getKind()) {
88 case ADD:
89 // We have a ADD on right. we need to revert this addition
90 removeFromTarget(attributeChange, rightToLeft);
91 break;
92 case DELETE:
93 // DELETE in the right. We need to re-create this element
94 addInTarget(attributeChange, rightToLeft);
95 break;
96 case MOVE:
97 moveElement(attributeChange, rightToLeft);
98 break;
99 case CHANGE:
100 changeValue(attributeChange, rightToLeft);
101 break;
102 default:
103 break;
108 * This will be called when we need to create an element in the target side.
109 * <p>
110 * All necessary sanity checks have been made to ensure that the current operation is one that should
111 * create an object in its side or add an objet to an attribute. In other words, either :
112 * <ul>
113 * <li>We are copying from right to left and
114 * <ul>
115 * <li>we are copying an addition to the right side (we need to create the same object in the left), or</li>
116 * <li>we are copying a deletion from the left side (we need to revert the deletion).</li>
117 * </ul>
118 * </li>
119 * <li>We are copying from left to right and
120 * <ul>
121 * <li>we are copying a deletion from the right side (we need to revert the deletion), or</li>
122 * <li>we are copying an addition to the left side (we need to create the same object in the right).</li>
123 * </ul>
124 * </li>
125 * </ul>
126 * </p>
128 * @param diff
129 * The diff we are currently merging.
130 * @param rightToLeft
131 * Tells us whether we are to add an object on the left or right side.
133 @SuppressWarnings("unchecked")
134 protected void addInTarget(AttributeChange diff, boolean rightToLeft) {
135 final Match match = diff.getMatch();
136 final EObject targetContainer = getTargetContainer(diff, rightToLeft);
138 if (targetContainer == null) {
139 // FIXME throw exception? log? re-try to merge our requirements?
140 // one of the "required" diffs should have created our container.
141 } else {
142 final Comparison comparison = match.getComparison();
143 final EStructuralFeature attribute = diff.getAttribute();
144 final Object expectedValue = diff.getValue();
145 // We have the container, attribute and value. We need to know the insertion index.
146 if (attribute.isMany()) {
147 final int insertionIndex = findInsertionIndex(comparison, diff, rightToLeft);
149 final List<Object> targetList = (List<Object>)safeEGet(targetContainer, attribute);
150 addAt(targetList, expectedValue, insertionIndex);
151 } else {
152 safeESet(targetContainer, attribute, expectedValue);
158 * This will be called when we need to remove an element from the target side.
159 * <p>
160 * All necessary sanity checks have been made to ensure that the current operation is one that should
161 * delete an object. In other words, we are :
162 * <ul>
163 * <li>Copying from right to left and either
164 * <ul>
165 * <li>we are copying a deletion from the right side (we need to remove the same object in the left) or,</li>
166 * <li>we are copying an addition to the left side (we need to revert the addition).</li>
167 * </ul>
168 * </li>
169 * <li>Copying from left to right and either
170 * <ul>
171 * <li>we are copying an addition to the right side (we need to revert the addition), or.</li>
172 * <li>we are copying a deletion from the left side (we need to remove the same object in the right).</li>
173 * </ul>
174 * </li>
175 * </ul>
176 * </p>
178 * @param diff
179 * The diff we are currently merging.
180 * @param rightToLeft
181 * Tells us whether we are to add an object on the left or right side.
183 @SuppressWarnings("unchecked")
184 protected void removeFromTarget(AttributeChange diff, boolean rightToLeft) {
185 final EObject currentContainer = getTargetContainer(diff, rightToLeft);
187 if (currentContainer == null) {
188 // FIXME throw exception? log? re-try to merge our requirements?
189 } else {
190 final Object expectedValue = diff.getValue();
191 final EStructuralFeature attribute = diff.getAttribute();
192 // We have the container, attribute and value to remove.
193 if (attribute.isMany()) {
195 * TODO if the same value appears twice, should we try and find the one that has actually been
196 * deleted? Will it happen that often? For now, remove the first occurence we find.
198 final List<Object> targetList = (List<Object>)safeEGet(currentContainer, attribute);
199 targetList.remove(expectedValue);
200 } else {
201 currentContainer.eUnset(attribute);
207 * This will be called when trying to copy a "MOVE" diff.
209 * @param diff
210 * The diff we are currently merging.
211 * @param rightToLeft
212 * Whether we should move the value in the left or right side.
214 protected void moveElement(AttributeChange diff, boolean rightToLeft) {
215 final EObject expectedContainer = getTargetContainer(diff, rightToLeft);
217 if (expectedContainer == null) {
218 // TODO throws exception?
219 } else {
220 final Comparison comparison = diff.getMatch().getComparison();
221 final Object expectedValue = diff.getValue();
223 // We now know the target container, target attribute and target value.
224 doMove(diff, comparison, expectedContainer, expectedValue, rightToLeft);
229 * This will do the actual work of moving the element into its attribute. All sanity checks were made in
230 * {@link #moveElement(boolean)} and no more verification will be made here.
232 * @param diff
233 * The diff we are currently merging.
234 * @param comparison
235 * Comparison holding this Diff.
236 * @param expectedContainer
237 * The container in which we are reorganizing an attribute.
238 * @param expectedValue
239 * The value that is to be moved within its attribute.
240 * @param rightToLeft
241 * Whether we should move the value in the left or right side.
243 @SuppressWarnings("unchecked")
244 protected void doMove(AttributeChange diff, Comparison comparison, EObject expectedContainer,
245 Object expectedValue, boolean rightToLeft) {
246 final EStructuralFeature attribute = diff.getAttribute();
247 if (attribute.isMany()) {
248 // Element to move cannot be part of the LCS... or there would not be a MOVE diff
249 int insertionIndex = findInsertionIndex(comparison, diff, rightToLeft);
251 * However, it could still have been located "before" its new index, in which case we need to take
252 * it into account.
254 final List<Object> targetList = (List<Object>)safeEGet(expectedContainer, attribute);
255 final int currentIndex = targetList.indexOf(expectedValue);
256 if (insertionIndex > currentIndex) {
257 insertionIndex--;
260 if (targetList instanceof EList<?>) {
261 if (insertionIndex < 0 || insertionIndex > targetList.size()) {
262 ((EList<Object>)targetList).move(targetList.size() - 1, expectedValue);
263 } else {
264 ((EList<Object>)targetList).move(insertionIndex, expectedValue);
266 } else {
267 targetList.remove(expectedValue);
268 if (insertionIndex < 0 || insertionIndex > targetList.size()) {
269 targetList.add(expectedValue);
270 } else {
271 targetList.add(insertionIndex, expectedValue);
274 } else {
275 // This will never happen with the default diff engine, but may still be done from extenders
276 safeESet(expectedContainer, attribute, expectedValue);
281 * This will be called by the merge operations in order to reset an attribute to its original value, be
282 * that the left or right side.
283 * <p>
284 * Should never be called on multi-valued attributes.
285 * </p>
287 * @param diff
288 * The diff we are currently merging.
289 * @param rightToLeft
290 * Tells us the direction of this merge operation.
291 * @deprecated this has been refactored into {@link #changeValue(AttributeChange, boolean)}.
293 @Deprecated
294 protected void resetInTarget(AttributeChange diff, boolean rightToLeft) {
295 final EStructuralFeature attribute = diff.getAttribute();
296 final EObject targetContainer = getTargetContainer(diff, rightToLeft);
297 final EObject sourceContainer = getSourceContainer(diff, rightToLeft);
299 if (sourceContainer == null || !safeEIsSet(targetContainer, attribute)
300 || !safeEIsSet(sourceContainer, attribute)) {
301 targetContainer.eUnset(attribute);
302 } else {
303 final Object expectedValue = safeEGet(sourceContainer, attribute);
304 safeESet(targetContainer, attribute, expectedValue);
309 * This will be called by the merge operations in order to change single-valued attributes.
311 * @param diff
312 * The diff we are currently merging.
313 * @param rightToLeft
314 * Direction of the merge.
316 protected void changeValue(AttributeChange diff, boolean rightToLeft) {
317 final EStructuralFeature attribute = diff.getAttribute();
318 final Object targetValue = getTargetValue(diff, rightToLeft);
319 final EObject targetContainer = getTargetContainer(diff, rightToLeft);
321 if (isUnset(diff, targetValue)) {
322 targetContainer.eUnset(attribute);
323 } else {
324 safeESet(targetContainer, attribute, targetValue);
329 * Returns the target value, that is, the value to be set when merging the given {@code diff} in the
330 * direction indicated by {@code rightToLeft}.
332 * @param diff
333 * The diff we are currently merging.
334 * @param rightToLeft
335 * Direction of the merge.
336 * @return The target value to be set when merging.
338 private Object getTargetValue(AttributeChange diff, boolean rightToLeft) {
339 final EStructuralFeature attribute = diff.getAttribute();
340 final EObject targetContainer = getTargetContainer(diff, rightToLeft);
341 final EObject sourceContainer = getSourceContainer(diff, rightToLeft);
342 final Object sourceValue = safeEGet(sourceContainer, attribute);
344 final Object targetValue;
345 if (isEnumChangeOfDynamicEObject(diff, targetContainer)) {
346 final EEnum eEnum = getAttributeTypeEnumFromDynamicObject(targetContainer, attribute);
347 targetValue = eEnum.getEEnumLiteral(((ENamedElement)sourceValue).getName());
348 } else if (requireThreeWayTextMerge(diff)) {
349 targetValue = performThreeWayTextMerge(diff, rightToLeft);
350 } else {
351 targetValue = sourceValue;
354 return targetValue;
358 * Specifies whether the given {@code diff} concerns a change of a dynamic EObject at an attribute with an
359 * EEnum type.
361 * @param diff
362 * The diff to check.
363 * @param targetContainer
364 * The target container.
365 * @return <code>true</code> if is a change of a dynamic EObject with an EEnum-typed attribute,
366 * <code>false</code> otherwise.
368 private boolean isEnumChangeOfDynamicEObject(AttributeChange diff, EObject targetContainer) {
369 return diff.getAttribute().getEType() instanceof EEnum
370 && targetContainer instanceof DynamicEObjectImpl;
374 * Returns the EEnum that is the attribute type of the given {@code attribute} used in the given container
375 * object {@code dynamicObject}.
377 * @param dynamicObject
378 * The container object.
379 * @param attribute
380 * The attribute.
381 * @return The EEnum acting as type of {@code attribute}.
383 private EEnum getAttributeTypeEnumFromDynamicObject(EObject dynamicObject, EStructuralFeature attribute) {
384 return (EEnum)((EEnumLiteral)safeEGet(dynamicObject, attribute)).eContainer();
388 * Returns the source container, that is the original container holding the original value before the
389 * given {@code diff} is applied. This is different depending on direction indicated by
390 * {@code rightToLeft}.
392 * @param diff
393 * The diff we are currently merging.
394 * @param rightToLeft
395 * Direction of the merge.
396 * @return The source container.
398 private EObject getSourceContainer(AttributeChange diff, boolean rightToLeft) {
399 final EObject sourceContainer;
400 final Match match = diff.getMatch();
401 if (match.getComparison().isThreeWay() && isRejectingChange(diff, rightToLeft)) {
402 sourceContainer = match.getOrigin();
403 } else if (rightToLeft) {
404 sourceContainer = match.getRight();
405 } else {
406 sourceContainer = match.getLeft();
408 return sourceContainer;
412 * Returns the target container, that is the container holding the value to be updated when merging the
413 * given {@code diff} in the direction indicated by {@code rightToLeft}.
415 * @param diff
416 * The diff we are currently merging.
417 * @param rightToLeft
418 * Direction of the merge.
419 * @return The target container to be updated when merging.
421 private EObject getTargetContainer(AttributeChange diff, boolean rightToLeft) {
422 final Match match = diff.getMatch();
423 if (rightToLeft) {
424 return match.getLeft();
425 } else {
426 return match.getRight();
431 * Specifies whether the given {@code diff} unsets the attribute value when updating the attribute with
432 * the given {@code targetValue}.
434 * @param diff
435 * The difference to check.
436 * @param targetValue
437 * The value to be set.
438 * @return <code>true</code> if setting {@code targetValue} is an unset, <code>false</code> otherwise.
440 private boolean isUnset(AttributeChange diff, Object targetValue) {
441 final Object defaultValue = diff.getAttribute().getDefaultValue();
442 return targetValue == null || targetValue.equals(defaultValue)
443 || (defaultValue == null && "".equals(targetValue)); //$NON-NLS-1$
447 * Specifies whether a three-way text merge is required for applying the given {@code diff} in the
448 * direction indicated in {@code rightToLeft}.
449 * <p>
450 * Three-way text merging is required when accepting or rejecting the changes of a
451 * {@link #isStringAttribute(EAttribute) String attributes} in a three-way merge scenario.
452 * </p>
454 * @param diff
455 * The diff to be applied.
456 * @return <code>true</code> if three-way text merging is required, <code>false</code> otherwise.
458 private boolean requireThreeWayTextMerge(AttributeChange diff) {
459 return diff.getMatch().getComparison().isThreeWay() && isStringAttribute(diff.getAttribute());
463 * Specifies whether the given {@code attribute} is an attribute of type String.
465 * @param attribute
466 * The attribute to be checked.
467 * @return <code>true</code> if it is a String attribute, <code>false</code> otherwise.
469 private boolean isStringAttribute(EAttribute attribute) {
470 return attribute.getEAttributeType().getInstanceClass() == String.class;
474 * Specifies whether applying the given {@code diff} in the direction indicated in {@code rightToLeft}
475 * means accepting the change as opposed to rejecting the change.
477 * @param diff
478 * The diff to be checked.
479 * @param rightToLeft
480 * The direction of applying {@code diff}.
481 * @return <code>true</code> if it means accepting the change, <code>false</code> otherwise.
483 private boolean isAcceptingChange(AttributeChange diff, boolean rightToLeft) {
484 return (DifferenceSource.LEFT.equals(diff.getSource()) && !rightToLeft)
485 || (DifferenceSource.RIGHT.equals(diff.getSource()) && rightToLeft);
489 * Specifies whether applying the given {@code diff} in the direction indicated in {@code rightToLeft}
490 * means rejecting the change as opposed to accepting the change.
492 * @param diff
493 * The diff to be checked.
494 * @param rightToLeft
495 * The direction of applying {@code diff}.
496 * @return <code>true</code> if it means rejecting the change, <code>false</code> otherwise.
498 private boolean isRejectingChange(AttributeChange diff, boolean rightToLeft) {
499 return !isAcceptingChange(diff, rightToLeft);
503 * Performs a three-way text merge for the given {@code diff} and returns the merged text.
504 * <p>
505 * Depending on whether the given {@code diff} is an accept or reject in the context of the merge
506 * direction indicated by {@code rightToLeft}, this method will perform different strategies of merging.
507 * </p>
509 * @param diff
510 * The diff for which a three-way text diff is to be performed.
511 * @param rightToLeft
512 * The direction of applying the {@code diff}.
513 * @return The merged text.
515 private String performThreeWayTextMerge(AttributeChange diff, boolean rightToLeft) {
516 if (isAcceptingChange(diff, rightToLeft)) {
517 return performAcceptingThreeWayTextMerge(diff);
518 } else {
519 return performRejectingThreeWayTextMerge(diff, rightToLeft);
524 * Performs a three-way text merge accepting the given {@code diff} and returns the merged text.
525 * <p>
526 * This method must only be called for {@link #isStringAttribute(EAttribute) String attributes}. As the
527 * three-way text merging is symmetric, the result is equal irrespectively of the direction of merging as
528 * long as applying the change {@link #isAcceptingChange(AttributeChange, boolean) means accepting it} as
529 * opposed to rejecting it.
530 * </p>
532 * @param diff
533 * The diff for which a three-way text diff is to be performed.
534 * @return The merged text.
536 private String performAcceptingThreeWayTextMerge(AttributeChange diff) {
537 final Match match = diff.getMatch();
538 final EAttribute attribute = diff.getAttribute();
539 final String originValue = (String)safeEGet(match.getOrigin(), attribute);
540 final String leftValue = (String)safeEGet(match.getLeft(), attribute);
541 final String rightValue = (String)safeEGet(match.getRight(), attribute);
543 return performThreeWayTextMerge(leftValue, rightValue, originValue);
547 * Performs a three-way text merge rejecting the given {@code diff} and returns the merged text.
548 * <p>
549 * When rejecting an attribute change, we need to undo its effects on the attribute value, which is in
550 * most of the cases done by setting the value to the original value. However, if there is a concurrent
551 * attribute change of the same attribute value at the opposite side, it might have been merged to the
552 * current side already. Therefore, we need to undo only those differences in the attribute value that
553 * come from the current to-be-rejected diff.
554 * </p>
555 * <p>
556 * This is done by applying a normal three-way merge, but instead of the origin value, left value, and
557 * right value, we compute the three-way merge as follows: as origin value, we use the value of the
558 * {@code diff}, which is the value as it was set on the respective side. As a left value, the value of
559 * the current side as it is currently stored in the model; thus, a potential merging of opposite diffs
560 * may have changed this value already. And as a right value, we use the actual origin value from the
561 * origin model.
562 * </p>
563 * <p>
564 * Since we consider the current value as it is in the model right now (including potential previous
565 * merges) and the origin value as the two changed sides, a three-way merge will apply the changes we did
566 * through merging and reset all other to the origin value.
567 * </p>
568 * <p>
569 * Note that, if {@code diff} is an unset (that is, the current value is empty or null or default), we
570 * just use the original value.
571 * </p>
573 * @param diff
574 * The diff for which a three-way text diff is to be performed.
575 * @param rightToLeft
576 * The direction of applying the {@code diff}.
577 * @return The merged text.
579 private String performRejectingThreeWayTextMerge(AttributeChange diff, boolean rightToLeft) {
580 final EAttribute attribute = diff.getAttribute();
581 final EObject originContainer = diff.getMatch().getOrigin();
582 final String originValue = (String)safeEGet(originContainer, attribute);
583 final String changedValueFromModel = getChangedValueFromModel(diff);
584 final String changedValue = (String)diff.getValue();
586 if (isUnset(diff, changedValueFromModel)) {
587 return originValue;
588 } else {
589 return performThreeWayTextMerge(changedValueFromModel, originValue, changedValue);
594 * Returns the changed value, as it is right now stored in the model, of the attribute that is affected by
595 * the given {@code diff}.
597 * @param diff
598 * The diff to get the changed value for.
599 * @return The changed value.
601 private String getChangedValueFromModel(AttributeChange diff) {
602 final EAttribute attribute = diff.getAttribute();
603 final EObject changedContainer;
604 switch (diff.getSource()) {
605 case LEFT:
606 changedContainer = diff.getMatch().getLeft();
607 break;
608 case RIGHT:
609 changedContainer = diff.getMatch().getRight();
610 break;
611 default:
612 throw new IllegalArgumentException();
614 return (String)safeEGet(changedContainer, attribute);
618 * Performs a three-way text merge for the given {@code origin}, {@code left}, and {@code right} text
619 * versions.
621 * @param left
622 * The left version of the String.
623 * @param right
624 * The right version of the String.
625 * @param origin
626 * The original version of the String.
627 * @return The merged version.
628 * @since 3.2
630 protected String performThreeWayTextMerge(final String left, final String right, final String origin) {
631 ThreeWayTextDiff textDiff = new ThreeWayTextDiff(origin, left, right);
632 return textDiff.getMerged();
636 * This will be used by the distinct merge actions in order to find the index at which a value should be
637 * inserted in its target list. See {@link DiffUtil#findInsertionIndex(Comparison, Diff, boolean)} for
638 * more on this.
639 * <p>
640 * Sub-classes can override this if the insertion order is irrelevant. A return value of {@code -1} will
641 * be considered as "no index" and the value will be inserted at the end of its target list.
642 * </p>
644 * @param comparison
645 * This will be used in order to retrieve the Match for EObjects when comparing them.
646 * @param diff
647 * The diff which merging will trigger the need for an insertion index in its target list.
648 * @param rightToLeft
649 * {@code true} if the merging will be done into the left list, so that we should consider the
650 * right model as the source and the left as the target.
651 * @return The index at which this {@code diff}'s value should be inserted into the 'target' list, as
652 * inferred from {@code rightToLeft}. {@code -1} if the value should be inserted at the end of its
653 * target list.
654 * @see DiffUtil#findInsertionIndex(Comparison, Diff, boolean)
656 protected int findInsertionIndex(Comparison comparison, Diff diff, boolean rightToLeft) {
657 return DiffUtil.findInsertionIndex(comparison, diff, rightToLeft);