blob: 14b15232706142cfd577db29f6dc191d773ae9df [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2012, 2018 Obeo and others.
* 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
* Philip Langer - add caching
*******************************************************************************/
package org.eclipse.emf.compare.rcp.ui.internal.contentmergeviewer.accessor.impl;
import static com.google.common.collect.Iterables.filter;
import static com.google.common.collect.Iterables.size;
import static com.google.common.collect.Lists.newArrayList;
import static com.google.common.collect.Lists.newArrayListWithCapacity;
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.collect.Maps;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.eclipse.emf.common.notify.AdapterFactory;
import org.eclipse.emf.compare.AttributeChange;
import org.eclipse.emf.compare.Diff;
import org.eclipse.emf.compare.DifferenceKind;
import org.eclipse.emf.compare.FeatureMapChange;
import org.eclipse.emf.compare.Match;
import org.eclipse.emf.compare.ReferenceChange;
import org.eclipse.emf.compare.internal.utils.DiffUtil;
import org.eclipse.emf.compare.rcp.ui.internal.EMFCompareConstants;
import org.eclipse.emf.compare.rcp.ui.internal.mergeviewer.item.impl.MergeViewerItem;
import org.eclipse.emf.compare.rcp.ui.mergeviewer.IMergeViewer.MergeViewerSide;
import org.eclipse.emf.compare.rcp.ui.mergeviewer.item.IMergeViewerItem;
import org.eclipse.emf.compare.utils.IEqualityHelper;
import org.eclipse.emf.compare.utils.ReferenceUtil;
import org.eclipse.emf.ecore.EObject;
/**
* A specific {@link AbstractStructuralFeatureAccessor} for multi-valued structural feature objects.
*
* @author <a href="mailto:mikael.barbero@obeo.fr">Mikael Barbero</a>
* @since 4.0
*/
public class ManyStructuralFeatureAccessorImpl extends AbstractStructuralFeatureAccessor {
/**
* A cache used by {@link #getDiffWithValue(Object)}.
*/
Map<Object, Diff> valueDiffs;
/**
* Default constructor.
*
* @param adapterFactory
* the adapater factory used to create the accessor.
* @param diff
* the diff associated with this accessor.
* @param side
* the side of the accessor.
*/
public ManyStructuralFeatureAccessorImpl(AdapterFactory adapterFactory, Diff diff, MergeViewerSide side) {
super(adapterFactory, diff, side);
}
/**
* {@inheritDoc}
*
* @see org.eclipse.emf.compare.rcp.ui.contentmergeviewer.accessor.ICompareAccessor#getItems()
*/
public ImmutableList<? extends IMergeViewerItem> getItems() {
List<? extends IMergeViewerItem> ret;
List<?> list = getFeatureValues(getSide());
ret = createMergeViewerItemFrom(list);
if (getSide() != MergeViewerSide.ANCESTOR
&& list.size() < EMFCompareConstants.LIST_SIZE_INSERTION_POINT_THRESHOLD) {
ret = createInsertionPoints(ret);
}
return ImmutableList.copyOf(ret);
}
/**
* Create IMergeViewerItems for the given values.
*
* @param values
* the given values.
* @return the list of newly created IMergeViewerItems.
*/
private List<? extends IMergeViewerItem> createMergeViewerItemFrom(List<?> values) {
List<IMergeViewerItem> ret = newArrayListWithCapacity(values.size());
for (Object value : values) {
IMergeViewerItem valueToAdd = createMergeViewerItemFrom(value);
ret.add(valueToAdd);
}
return ret;
}
/**
* Create a IMergeViewerItem for the given object.
*
* @param object
* the given object.
* @return an IMergeViewerItem.
*/
private IMergeViewerItem createMergeViewerItemFrom(Object object) {
Diff diff = getDiffWithValue(object);
Object left = matchingValue(object, MergeViewerSide.LEFT);
Object right = matchingValue(object, MergeViewerSide.RIGHT);
Object ancestor = matchingValue(object, MergeViewerSide.ANCESTOR);
final boolean leftEmptyBox = !getFeatureValues(MergeViewerSide.LEFT).contains(left);
final boolean rightEmptyBox = !getFeatureValues(MergeViewerSide.RIGHT).contains(right);
if (leftEmptyBox || rightEmptyBox) {
if (leftEmptyBox) {
left = null;
}
if (rightEmptyBox) {
right = null;
}
}
return new MergeViewerItem(getComparison(), diff, left, right, ancestor, getSide(),
getRootAdapterFactory());
}
/**
* Create insertion points for the given values.
*
* @param values
* the given values.
* @return a list of newly created insertion points that implement {@link IMergeViewerItem}.
*/
private List<? extends IMergeViewerItem> createInsertionPoints(
final List<? extends IMergeViewerItem> values) {
List<IMergeViewerItem> ret = newArrayList(values);
for (Diff diff : getDifferences().reverse()) {
boolean isLeft = getSide() == MergeViewerSide.LEFT;
Object left = getValueFromDiff(diff, MergeViewerSide.LEFT);
Object right = getValueFromDiff(diff, MergeViewerSide.RIGHT);
final boolean leftEmptyBox = isLeft
&& (left == null || !getFeatureValues(getSide()).contains(left));
final boolean rightEmptyBox = !isLeft
&& (right == null || !getFeatureValues(getSide()).contains(right));
if (leftEmptyBox || rightEmptyBox) {
// Bug 458818: Don't display a delete+moved element twice
// If the diff is part of a conflict,
// we don't want to display both the MOVE and the DELETE on the same side
if (diff.getKind() == DifferenceKind.MOVE && isPartOfConflictWithDelete(diff)) {
continue;
}
Object ancestor = getValueFromDiff(diff, MergeViewerSide.ANCESTOR);
if (leftEmptyBox) {
left = null;
}
if (rightEmptyBox) {
right = null;
}
IMergeViewerItem insertionPoint = new MergeViewerItem(getComparison(), diff, left, right,
ancestor, getSide(), getRootAdapterFactory());
final int insertionIndex = Math.min(findInsertionIndex(diff, isLeft), ret.size());
List<IMergeViewerItem> subList = ret.subList(0, insertionIndex);
final int nbInsertionPointBefore = size(filter(subList, IMergeViewerItem.IS_INSERTION_POINT));
int index = Math.min(insertionIndex + nbInsertionPointBefore, ret.size());
ret.add(index, insertionPoint);
}
}
return ret;
}
private boolean isPartOfConflictWithDelete(final Diff diff) {
if (diff.getConflict() == null) {
return false;
}
return Iterables.any(diff.getConflict().getDifferences(), new Predicate<Diff>() {
public boolean apply(Diff aDiff) {
return diff != aDiff && aDiff.getKind() == DifferenceKind.DELETE;
}
});
}
/**
* Find the insertion index for the given diff.
*
* @param diff
* the given diff.
* @param rightToLeft
* the way of merge.
* @return the insertion index.
*/
protected int findInsertionIndex(Diff diff, boolean rightToLeft) {
return DiffUtil.findInsertionIndex(getComparison(), diff, rightToLeft);
}
/**
* Get the Diff corresponding to the given object.
*
* @param value
* the given object.
* @return the Diff corresponding to the given object.
*/
private Diff getDiffWithValue(Object value) {
if (valueDiffs == null) {
valueDiffs = Maps.newIdentityHashMap();
for (Diff diff : getDifferences()) {
Object valueOfDiff = getValueFromDiff(diff, getSide());
valueDiffs.put(valueOfDiff, diff);
}
}
return valueDiffs.get(value);
}
/**
* Get the value of the given side associated to the given Diff.
*
* @param diff
* the given Diff.
* @param side
* the side of the Diff.
* @return the value associated to the given Diff.
*/
protected Object getValueFromDiff(final Diff diff, MergeViewerSide side) {
Object diffValue = getDiffValue(diff);
Object ret = matchingValue(diffValue, side);
return ret;
}
/**
* Get the value of the given side of the Match corresponding to the given object.
*
* @param object
* the given object.
* @param side
* the given side.
* @return the value of the given side of the Match corresponding to the given object.
*/
private Object matchingValue(Object object, MergeViewerSide side) {
final Object ret;
if (object instanceof EObject) {
final Match matchOfValue = getComparison().getMatch((EObject)object);
if (matchOfValue != null) {
switch (side) {
case ANCESTOR:
ret = matchOfValue.getOrigin();
break;
case LEFT:
ret = matchOfValue.getLeft();
break;
case RIGHT:
ret = matchOfValue.getRight();
break;
default:
throw new IllegalStateException();
}
} else {
ret = matchingValue(object, getFeatureValues(side));
}
} else {
ret = matchingValue(object, getFeatureValues(side));
}
return ret;
}
/**
* Get the value in the given list that match to the given value.
*
* @param value
* the given value.
* @param in
* the list of values.
* @return the value in the given list that match to the given value.
*/
private Object matchingValue(Object value, List<?> in) {
Object ret = null;
IEqualityHelper equalityHelper = getComparison().getEqualityHelper();
Iterator<?> valuesIterator = in.iterator();
while (valuesIterator.hasNext() && ret == null) {
Object object = valuesIterator.next();
if (equalityHelper.matchingValues(object, value)) {
ret = object;
}
}
return ret;
}
/**
* Returns the values of the current feature on the given side.
*
* @param side
* the given side.
* @return the values of the current feature on the given side.
*/
protected List<?> getFeatureValues(MergeViewerSide side) {
final EObject eObject = getEObject(side);
return ReferenceUtil.getAsListResolving(eObject, getStructuralFeature());
}
/**
* Returns either {@link ReferenceChange#getValue()}, {@link AttributeChange#getValue()} or a
* {@link FeatureMapChange#getValue()} depending on the runtime type of the give, {@code diff} or null
* otherwise.
*
* @param diff
* the given Diff.
* @return the value of the given Diff.
*/
protected Object getDiffValue(Diff diff) {
final Object ret;
if (diff instanceof ReferenceChange) {
ret = ((ReferenceChange)diff).getValue();
} else if (diff instanceof AttributeChange) {
ret = ((AttributeChange)diff).getValue();
} else if (diff instanceof FeatureMapChange) {
ret = ((FeatureMapChange)diff).getValue();
} else if (diff.getPrimeRefining() instanceof ReferenceChange) {
ret = ((ReferenceChange)diff.getPrimeRefining()).getValue();
} else {
ret = null;
}
return ret;
}
}