blob: 2ddb4b231850fe2affc20b40040329d14788dcaa [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2016, 2017 EclipseSource Muenchen GmbH 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:
* Alexandra Buzila, Stefan Dirix - initial API and implementation
* Philip Langer - bug 527864
*******************************************************************************/
package org.eclipse.emf.compare.ide.ui.internal.contentmergeviewer.tree.provider;
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 com.google.common.collect.Iterables.getFirst;
import static com.google.common.collect.Iterables.isEmpty;
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 static java.util.Collections.emptyList;
import static org.eclipse.emf.compare.merge.AbstractMerger.isInTerminalState;
import static org.eclipse.emf.compare.utils.EMFComparePredicates.CONTAINMENT_REFERENCE_CHANGE;
import static org.eclipse.emf.compare.utils.EMFComparePredicates.anyRefining;
import static org.eclipse.emf.compare.utils.EMFComparePredicates.fromSide;
import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
import org.eclipse.emf.common.notify.AdapterFactory;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.compare.Comparison;
import org.eclipse.emf.compare.Conflict;
import org.eclipse.emf.compare.ConflictKind;
import org.eclipse.emf.compare.Diff;
import org.eclipse.emf.compare.DifferenceKind;
import org.eclipse.emf.compare.DifferenceSource;
import org.eclipse.emf.compare.DifferenceState;
import org.eclipse.emf.compare.Match;
import org.eclipse.emf.compare.ResourceAttachmentChange;
import org.eclipse.emf.compare.graph.IGraphView;
import org.eclipse.emf.compare.internal.utils.DiffUtil;
import org.eclipse.emf.compare.match.impl.NotLoadedFragmentMatch;
import org.eclipse.emf.compare.rcp.ui.internal.mergeviewer.item.impl.MergeViewerItem;
import org.eclipse.emf.compare.rcp.ui.internal.mergeviewer.item.impl.ResourceAttachmentChangeMergeViewerItem;
import org.eclipse.emf.compare.rcp.ui.internal.util.MergeViewerUtil;
import org.eclipse.emf.compare.rcp.ui.internal.util.ResourceUIUtil;
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.rcp.ui.mergeviewer.item.provider.IMergeViewerItemContentProvider;
import org.eclipse.emf.compare.rcp.ui.mergeviewer.item.provider.IMergeViewerItemProviderConfiguration;
import org.eclipse.emf.compare.rcp.ui.structuremergeviewer.groups.IDifferenceGroupProvider;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.ecore.util.FeatureMap;
import org.eclipse.emf.edit.provider.FeatureMapEntryWrapperItemProvider;
import org.eclipse.emf.edit.provider.ITreeItemContentProvider;
/**
* ContentProvider for {@link IMergeViewerItem}s which uses their 'left', 'right' and 'ancestor' sides in
* combination with the given {@link AdapterFactory} to create the children and parent
* {@link IMergeViewerItem}s.
*
* @author Alexandra Buzila
* @author Stefan Dirix
*/
public class TreeMergeViewerItemContentProvider implements IMergeViewerItemContentProvider {
/**
* If the list on any one of the sides contains more elements than the given threshold, don't try and
* compute the insertion index for differences merging. On the one hand, showing insertion points in lists
* with so many elements wouldn't reallybe human readable, on the other hand, trying to compute insertion
* indices for too large lists will easily result in OutOfMemoryErrors. For example, if the left and right
* sides contain 60000 elements, we'll end up trying to instantiate an array with the following signature:
* "int[60000][60000]" to compute the LCS (see DiffUtils). Such an array would cost 13GB of memory as a
* conservative estimate.
*/
private static final short LIST_SIZE_INSERTION_POINT_THRESHOLD = 10000;
/**
* {@inheritDoc}
*/
public boolean canHandle(Object object) {
return object instanceof IMergeViewerItem;
}
/**
* {@inheritDoc}
*/
public Object getParent(Object object, IMergeViewerItemProviderConfiguration configuration) {
IMergeViewerItem mergeViewerItem = (IMergeViewerItem)object;
IMergeViewerItem ret = null;
if (mergeViewerItem.getDiff() instanceof ResourceAttachmentChange) {
ret = createBasicContainer((ResourceAttachmentChange)mergeViewerItem.getDiff(), mergeViewerItem,
configuration.getAdapterFactory());
} else {
Object sideValue = getBestSideValue(mergeViewerItem, configuration.getSide());
Object parent = null;
ITreeItemContentProvider treeItemContentProvider = (ITreeItemContentProvider)configuration
.getAdapterFactory().adapt(sideValue, ITreeItemContentProvider.class);
if (treeItemContentProvider != null) {
parent = treeItemContentProvider.getParent(sideValue);
}
if (parent instanceof EObject) {
ret = createBasicMergeViewerItem((EObject)parent, mergeViewerItem.getSide(), configuration);
} else if (parent instanceof Resource) {
ret = createParent(mergeViewerItem, (Resource)parent, configuration);
} else if (sideValue instanceof NotLoadedFragmentMatch) {
ret = createParent(mergeViewerItem, (NotLoadedFragmentMatch)sideValue, configuration);
}
}
return ret;
}
/**
* {@inheritDoc}
*/
public boolean hasChildren(Object object, IMergeViewerItemProviderConfiguration configuration) {
IMergeViewerItem mergeViewerItem = (IMergeViewerItem)object;
if (mergeViewerItem.getLeft() instanceof NotLoadedFragmentMatch) {
final NotLoadedFragmentMatch notLoadedFragmentMatch = (NotLoadedFragmentMatch)mergeViewerItem
.getLeft();
return !notLoadedFragmentMatch.getChildren().isEmpty();
}
final Object sideValue = getSideValue(mergeViewerItem, mergeViewerItem.getSide());
final List<Object> children = getChildrenFromContentProvider(sideValue,
configuration.getAdapterFactory());
final List<Object> otherSideChildren = getChildrenFromContentProvider(
getSideValue(mergeViewerItem, mergeViewerItem.getSide().opposite()),
configuration.getAdapterFactory());
final List<? extends Diff> differences = collectAndFilterDifferences(otherSideChildren,
configuration);
if (hasChildren(mergeViewerItem, differences, children, configuration)) {
return true;
}
final EObject bestSideValue = (EObject)getBestSideValue(mergeViewerItem, configuration.getSide());
final Match match = configuration.getComparison().getMatch(bestSideValue);
return hasNotLoadedFragmentsItems(match, mergeViewerItem.getSide());
}
/**
* {@inheritDoc}
*/
public Object[] getChildren(Object object, IMergeViewerItemProviderConfiguration configuration) {
ArrayList<Object> ret = Lists.newArrayList();
IMergeViewerItem mergeViewerItem = (IMergeViewerItem)object;
if (mergeViewerItem.getLeft() instanceof NotLoadedFragmentMatch) {
ret.addAll(createChildren(mergeViewerItem, (NotLoadedFragmentMatch)mergeViewerItem.getLeft(),
configuration));
} else {
Object sideValue = getSideValue(mergeViewerItem, mergeViewerItem.getSide());
Object otherSideValue = getSideValue(mergeViewerItem, mergeViewerItem.getSide().opposite());
final List<Object> children = getChildrenFromContentProvider(sideValue,
configuration.getAdapterFactory());
final List<Object> otherSideChildren = getChildrenFromContentProvider(otherSideValue,
configuration.getAdapterFactory());
List<? extends Diff> differences = collectAndFilterDifferences(otherSideChildren, configuration);
ret.addAll(createChildren(children, mergeViewerItem, differences, configuration));
// Add not loaded fragment match if needed
final EObject bestSideValue = (EObject)getBestSideValue(mergeViewerItem, configuration.getSide());
final Match match = configuration.getComparison().getMatch(bestSideValue);
ret.addAll(getNotLoadedFragmentsItems(configuration.getComparison(), match,
mergeViewerItem.getSide(), configuration.getAdapterFactory()));
}
return ret.toArray();
}
/**
* Determines the differences related to the given objects and filters them according to the
* {@link #visibleContainmentDiffPredicate(IMergeViewerItemProviderConfiguration)}.
*
* @param objects
* the objects.
* @param configuration
* the {@link IMergeViewerItemProviderConfiguration}.
* @return the determined differences.
*/
protected List<? extends Diff> collectAndFilterDifferences(Iterable<Object> objects,
IMergeViewerItemProviderConfiguration configuration) {
final List<? extends Diff> differences = collectDifferences(configuration.getComparison(), objects);
return Lists.newArrayList(filter(differences, visibleContainmentDiffPredicate(configuration)));
}
/**
* Indicates whether the given {@link IMergeViewerItem} parent has real or insertion point children.
*
* @param parent
* the {@link IMergeViewerItem} for which the children are to be determined.
* @param differences
* the list of relevant diffs.
* @param children
* the list of possible children candidates.
* @param configuration
* the {@link IMergeViewerItemConfiguration}.
* @return {@code true} if the given {@code parent} should have children {@link IMergeViewerItem}s,
* {@code false} otherwise.
*/
private boolean hasChildren(IMergeViewerItem parent, Iterable<? extends Diff> differences,
List<Object> children, IMergeViewerItemProviderConfiguration configuration) {
if (yieldsMergeViewerItem(configuration.getComparison(), parent.getDiff(), children)) {
return true;
}
if (parent.getSide() != MergeViewerSide.ANCESTOR) {
return yieldsInsertionPoint(parent, differences, configuration);
}
return true;
}
/**
* Get the children of the MergeViewerItem in case the MergeViewerItem is a
* {@link org.eclipse.emf.compare.match.impl.NotLoadedFragmentMatch}.
*
* @param parent
* the parent {@link IMergeViewerItem}
* @param nlfm
* the NotLoadedFragmentMatch for which we want the children (as MergeViewerItems)
* @param configuration
* the {@link IMergeViewerItemProviderConfiguration}.
* @return the children (a list of MergeViewerItems) of the given element.
*/
private List<IMergeViewerItem> createChildren(IMergeViewerItem parent, NotLoadedFragmentMatch nlfm,
IMergeViewerItemProviderConfiguration configuration) {
final List<IMergeViewerItem> ret = newArrayList();
final Collection<Match> matches = nlfm.getChildren();
for (Match match : matches) {
final IMergeViewerItem container;
if (match instanceof NotLoadedFragmentMatch) {
container = createMergeViewerItem(configuration.getComparison(), parent.getDiff(), match,
match, match, parent.getSide(), configuration.getAdapterFactory());
} else {
container = createMergeViewerItem(configuration.getComparison(), parent.getDiff(),
match.getLeft(), match.getRight(), match.getOrigin(), parent.getSide(),
configuration.getAdapterFactory());
}
ret.add(container);
}
return ret;
}
/**
* Creates the {@link IMergeViewerItem}s and insertion points for the given children.
*
* @param children
* the children for which {@link IMergeViewerItem}s shall be created.
* @param parent
* the parent of the children to create.
* @param differences
* list of relevant differences.
* @param configuration
* the {@link IMergeViewerItemProviderConfiguration}.
* @return the list of created {@link IMergeViewerItem}s.
*/
private List<IMergeViewerItem> createChildren(Collection<Object> children, IMergeViewerItem parent,
final List<? extends Diff> differences, IMergeViewerItemProviderConfiguration configuration) {
final List<IMergeViewerItem> ret = Lists.newArrayList();
final List<IMergeViewerItem> realChildren = createMergeViewerItemsFrom(children, parent,
configuration);
if (parent.getSide() != MergeViewerSide.ANCESTOR) {
ret.addAll(createInsertionPoints(parent, realChildren, differences, configuration));
} else {
ret.addAll(realChildren);
}
return ret;
}
/**
* Creates and inserts the insertion points for the given {@code values}.
*
* @param parent
* the {@link IMergeViewerItem} parent.
* @param values
* the list of {@link IMergeViewerItem} children for which insertion points are to be created.
* @param differences
* the list of relevant differences.
* @param configuration
* the {@link IMergeViewerItemProviderConfiguration}.
* @return the list of {@code values} containing the created insertion points.
*/
protected List<IMergeViewerItem> createInsertionPoints(IMergeViewerItem parent,
final List<IMergeViewerItem> values, List<? extends Diff> differences,
IMergeViewerItemProviderConfiguration configuration) {
MergeViewerSide side = parent.getSide();
AdapterFactory adapterFactory = configuration.getAdapterFactory();
List<Object> sideContent = getChildrenFromContentProvider(getSideValue(parent, side), adapterFactory);
List<Object> oppositeContent = getChildrenFromContentProvider(getSideValue(parent, side.opposite()),
adapterFactory);
List<Object> ancestorContent = getChildrenFromContentProvider(
getSideValue(parent, MergeViewerSide.ANCESTOR), adapterFactory);
if (sideContent.size() > LIST_SIZE_INSERTION_POINT_THRESHOLD
&& oppositeContent.size() > LIST_SIZE_INSERTION_POINT_THRESHOLD) {
return new ArrayList<IMergeViewerItem>(values);
}
return createInsertionPoints(parent, sideContent, oppositeContent, ancestorContent, values,
differences, configuration);
}
/**
* Creates the insertion points for the given {@code values} based on the children of each side.
*
* @param parent
* the {@link IMergeViewerItem} parent.
* @param sideContent
* the object for 'this' side.
* @param oppositeContent
* the object for the 'other' side.
* @param ancestorContent
* the objects for the 'origin' side-
* @param values
* the {@link IMergeViewerItem}s for which the insertion points are to be created.
* @param differences
* the list of relevant differences.
* @param configuration
* the {@link IMergeViewerItemProviderConfiguration}.
* @return the list of {@code values} containing the created insertion points.
*/
protected List<IMergeViewerItem> createInsertionPoints(IMergeViewerItem parent,
final List<Object> sideContent, final List<Object> oppositeContent,
final List<Object> ancestorContent, final List<? extends IMergeViewerItem> values,
List<? extends Diff> differences, IMergeViewerItemProviderConfiguration configuration) {
final List<IMergeViewerItem> ret = newArrayList(values);
if (differences.isEmpty()) {
return ret;
}
if (sideContent.isEmpty() && oppositeContent.isEmpty()) {
return ret;
}
Comparison comparison = configuration.getComparison();
AdapterFactory adapterFactory = configuration.getAdapterFactory();
MergeViewerSide side = parent.getSide();
for (Diff diff : Lists.reverse(differences)) {
EObject value = (EObject)getDiffValue(diff);
Match match = comparison.getMatch(value);
if (!isPseudoAddConflict(diff) && (isAddOnOppositeSide(diff, side)
|| isDeleteOnSameSide(diff, side) || isInsertOnBothSides(diff, match))) {
if (match == null && isInTerminalState(diff)) {
EObject bestSideValue = (EObject)getBestSideValue(parent, configuration.getSide());
match = comparison.getMatch(bestSideValue);
match = getMatchWithNullValues(match);
}
if (match != null && !isRealAddConflict(diff, match, side)) {
IMergeViewerItem insertionPoint = createMergeViewerItem(comparison, diff, match.getLeft(),
match.getRight(), match.getOrigin(), side, adapterFactory);
final int insertionIndex;
if (match.getLeft() == null && match.getRight() == null && diff.getConflict() != null
&& diff.getConflict().getKind() == ConflictKind.PSEUDO) {
// pseudo conflict delete...
insertionIndex = ancestorContent.indexOf(value);
} else {
insertionIndex = Math.min(
DiffUtil.findInsertionIndex(comparison, oppositeContent, sideContent, value),
ret.size());
}
// offset the insertion by the number of previous insertion points in the list
// Cannot be improved by keeping the number of created insertion points because the given
// "values" parameter may already contains some insertion points.
int realIndex = 0;
for (int index = 0; index < insertionIndex && realIndex < ret.size(); realIndex++) {
if (!ret.get(realIndex).isInsertionPoint()) {
index++;
}
}
ret.add(realIndex, insertionPoint);
}
}
}
return ret;
}
/**
* Determines the value of the given diff. If the diff has no "value", its prime refining or refined-by
* counterparts are also checked.
*
* @param diff
* the {@link Diff} to check.
* @return the value of the given diff. If no value exists {@code null} will be returned.
*/
protected Object getDiffValue(Diff diff) {
final Object diffValue = MergeViewerUtil.getDiffValue(diff);
if (diffValue != null) {
return diffValue;
}
final Set<Diff> allRefiningDiffs = DiffUtil.getAllRefiningDiffs(diff);
final Iterable<Diff> containmentRefs = filter(allRefiningDiffs, CONTAINMENT_REFERENCE_CHANGE);
return getFirstValue(containmentRefs);
}
/**
* Determines the first non-null value of the given diffs.
*
* @param diffs
* the {@link Diff}s to check.
* @return the first non-null value if it exists, {@code null} otherwise.
*/
protected Object getFirstValue(Iterable<Diff> diffs) {
final Iterable<Object> values = filter(Iterables.transform(diffs, new Function<Diff, Object>() {
public Object apply(Diff input) {
return MergeViewerUtil.getDiffValue(input);
}
}), Predicates.notNull());
if (values.iterator().hasNext()) {
return values.iterator().next();
}
return null;
}
/**
* Collect the differences of the given objects.
*
* @param objects
* The objects whose differences will be returned
* @param comparison
* the Comparison
* @return An {@link Iterable} over all collected Differences.
*/
private List<Diff> collectDifferences(Comparison comparison, Iterable<Object> objects) {
List<Diff> differences = StreamSupport.stream(objects.spliterator(), false)
.filter(EObject.class::isInstance).map(EObject.class::cast).distinct()
.flatMap(eobject -> comparison.getDifferences(eobject).stream()).collect(Collectors.toList());
return differences;
}
/**
* Adapts to {@link ITreeItemMergeViewerContentProvider} or {@link ITreeItemContentProvider} and calls
* getChildren. Also unwraps {@link FeatureMap.Entry}.
*
* @param object
* The object for which the children are to be determined.
* @return A list of all children of the given {@code object}.
*/
protected List<Object> getChildrenFromContentProvider(Object object, AdapterFactory adapterFactory) {
if (object == null) {
return emptyList();
}
ITreeItemContentProvider treeItemContentProvider = (ITreeItemContentProvider)adapterFactory
.adapt(object, ITreeItemContentProvider.class);
if (treeItemContentProvider != null) {
final List<Object> children = new ArrayList<Object>(treeItemContentProvider.getChildren(object));
return unwrapFeatureMapEntryProviders(children);
}
return emptyList();
}
/**
* Unwraps {@link FeatureMapEntryWrapperItemProvider} from the given list of {@code values}.
*
* @param values
* the values possibly containing {@link FeatureMapEntryWrapperItemProvider}s.
* @return list of objects containing the original non-FeatureMapEntryWrapperItemProvider and the
* unwrapped objects.
*/
private List<Object> unwrapFeatureMapEntryProviders(List<Object> values) {
final List<Object> result = new ArrayList<Object>(values.size());
for (Object value : values) {
if (FeatureMapEntryWrapperItemProvider.class.isInstance(value)) {
final FeatureMapEntryWrapperItemProvider featureMapEntryProvider = FeatureMapEntryWrapperItemProvider.class
.cast(value);
final Object featureMapEntry = featureMapEntryProvider.getValue();
if (FeatureMap.Entry.class.isInstance(featureMapEntry)) {
final Object featureMapValue = FeatureMap.Entry.class.cast(featureMapEntry).getValue();
result.add(featureMapValue);
}
} else {
result.add(value);
}
}
return result;
}
/**
* Indicates whether for the given diffs at least one {@link IMergeViewerItem} insertion point should be
* created.
*
* @param comparison
* the {@link Comparison}.
* @param diffs
* the {@link Diff}s.
* @param configuration
* the {@link IMergeViewerItemProviderConfiguration}.
* @return {@code true} if at least one {@link IMergeViewerItem} should be created, {@code false}
* otherwise.
*/
protected boolean yieldsInsertionPoint(final IMergeViewerItem parent, Iterable<? extends Diff> diffs,
final IMergeViewerItemProviderConfiguration configuration) {
final MergeViewerSide side = parent.getSide();
final Comparison comparison = configuration.getComparison();
final AdapterFactory adapterFactory = configuration.getAdapterFactory();
List<Object> sideContent = getChildrenFromContentProvider(getSideValue(parent, side), adapterFactory);
List<Object> oppositeContent = getChildrenFromContentProvider(getSideValue(parent, side.opposite()),
adapterFactory);
if (sideContent.isEmpty() && oppositeContent.isEmpty()) {
return false;
}
return Iterables.any(diffs, new Predicate<Diff>() {
public boolean apply(Diff diff) {
if (isPseudoAddConflict(diff)) {
return false;
}
EObject value = (EObject)MergeViewerUtil.getDiffValue(diff);
Match match = comparison.getMatch(value);
if (isAddOnOppositeSide(diff, side) || isDeleteOnSameSide(diff, side)
|| isInsertOnBothSides(diff, match)) {
if (match == null && isInTerminalState(diff)) {
EObject bestSideValue = (EObject)getBestSideValue(parent, configuration.getSide());
match = comparison.getMatch(bestSideValue);
match = getMatchWithNullValues(match);
}
return match != null && !isRealAddConflict(diff, match, side);
}
return false;
}
});
}
/**
* Indicates whether for the given values at least one {@link IMergeViewerItem} should be created.
*
* @param comparison
* the {@link Comparison}.
* @param diff
* the {@link Diff}.
* @param values
* the values.
* @return {@code true} if at least one {@link IMergeViewerItem} should be created, {@code false}
* otherwise.
*/
protected boolean yieldsMergeViewerItem(Comparison comparison, Diff diff, Collection<?> values) {
Iterable<EObject> elements = filter(values, EObject.class);
if (diff != null && !Iterables.isEmpty(elements)) {
return true;
}
for (EObject element : elements) {
Match match = comparison.getMatch(element);
if (match != null && !isMatchWithAllProxyData(match)) {
return true;
}
}
return false;
}
/**
* Indicates whether the match has not loaded fragments.
*
* @param match
* the {@link Match} to check, can be <code>null</code>.
* @param side
* the {@link MergeViewerSide} to check.
* @return {@code true} if there are not loaded fragments, {@code false} otherwise.
*/
private boolean hasNotLoadedFragmentsItems(Match match, MergeViewerSide side) {
if (match == null) {
return false;
}
Collection<Match> childrenMatches = ResourceUIUtil
.getChildrenMatchWithNotLoadedParent(match.getComparison(), match, side);
return !childrenMatches.isEmpty();
}
/**
* Get the parent (as MergeViewerItem) of the MergeViewerItem in case the MergeViewerItem is a
* {@link org.eclipse.emf.ecore.resource.Resource}.
*
* @param mergeViewerItem
* the {@link IMergeViewerItem} for which the parent is created.
* @param resource
* the Resource for which we want the parent (as MergeViewerItem).
* @param configuration
* the {@link IMergeViewerItemProviderConfiguration}.
* @return the parent (a MergeViewerItem) of the given element.
*/
private IMergeViewerItem createParent(IMergeViewerItem mergeViewerItem, Resource resource,
IMergeViewerItemProviderConfiguration configuration) {
final IMergeViewerItem parent;
URI uri = resource.getURI();
if (ResourceUIUtil.isFragment(uri)) {
final Object object = getBestSideValue(mergeViewerItem, configuration.getSide());
final Match matchOfValue = configuration.getComparison().getMatch((EObject)object);
if (matchOfValue != null) {
final NotLoadedFragmentMatch notLoadedFragmentMatch = new NotLoadedFragmentMatch(
matchOfValue);
parent = createMergeViewerItem(configuration.getComparison(), mergeViewerItem.getDiff(),
notLoadedFragmentMatch, notLoadedFragmentMatch, notLoadedFragmentMatch,
mergeViewerItem.getSide(), configuration.getAdapterFactory());
} else {
parent = null;
}
} else {
parent = null;
}
return parent;
}
/**
* Get the parent of the MergeViewerItem in case the MergeViewerItem is a
* {@link org.eclipse.emf.compare.match.impl.NotLoadedFragmentMatch}.
*
* @param mergeViewerItem
* the {@link IMergeViewerItem}.
* @param nlfm
* the NotLoadedFragmentMatch for which we want the parent (as MergeViewerItem)
* @param configuration
* the {@link IMergeViewerItemProviderConfiguration}.l
* @return the parent (@link IMergeViewerItem) of the given element.
*/
private IMergeViewerItem createParent(IMergeViewerItem mergeViewerItem, NotLoadedFragmentMatch nlfm,
IMergeViewerItemProviderConfiguration configuration) {
IMergeViewerItem parent = null;
MergeViewerSide side = mergeViewerItem.getSide();
final Collection<? extends Match> children = nlfm.getChildren();
for (Match match : children) {
URI uri = ResourceUIUtil.getDataURI(match, side);
if (uri != null) {
IGraphView<URI> graph = ResourceUIUtil.getResourcesURIGraph();
URI parentData = graph.getParentData(uri);
ResourceSet rs = ResourceUIUtil.getDataResourceSet(match, side);
Resource resourceParent = ResourceUIUtil.getParent(rs, uri);
while (resourceParent == null && parentData != null) {
resourceParent = ResourceUIUtil.getParent(rs, parentData.trimFragment());
parentData = graph.getParentData(parentData.trimFragment());
}
if (resourceParent != null && parentData != null) {
EObject eObjectParent = resourceParent.getEObject(parentData.fragment());
if (eObjectParent != null) {
parent = createBasicMergeViewerItem(eObjectParent, side, configuration);
break;
}
} else {
parent = createNotLoadedFragmentContainer(rs, uri, configuration.getComparison(),
mergeViewerItem.getDiff(), side, configuration.getAdapterFactory());
if (parent != null) {
break;
}
}
}
}
return parent;
}
/**
* Create an IMergeViewerItem.Container that holds NotLoadedFragmentMatches.
*
* @param uri
* the URI of the Match element for which we want to create a container.
* @param side
* @param diff
* @return an IMergeViewerItem.Container.
*/
private IMergeViewerItem createNotLoadedFragmentContainer(ResourceSet rs, URI uri, Comparison comparison,
Diff diff, MergeViewerSide side, AdapterFactory adapterFactory) {
final IMergeViewerItem parent;
URI parentURI = ResourceUIUtil.getParentResourceURI(rs, uri);
URI rootResourceURI = ResourceUIUtil.getRootResourceURI(uri);
if (parentURI == null) {
parentURI = rootResourceURI;
}
Collection<Match> notLoadedFragmentMatches = Lists.newArrayList();
Collection<Match> rootMatches = comparison.getMatches();
Collection<URI> uris = ResourceUIUtil.getDataURIs(rootMatches, side);
for (Match rootMatch : rootMatches) {
URI rootMatchDataURI = ResourceUIUtil.getDataURI(rootMatch, side);
if (!rootResourceURI.equals(rootMatchDataURI) && !parentURI.equals(rootMatchDataURI)
&& ResourceUIUtil.isChildOf(rootMatchDataURI, ImmutableSet.of(parentURI))
&& !ResourceUIUtil.isChildOf(rootMatchDataURI, uris)) {
notLoadedFragmentMatches.add(new NotLoadedFragmentMatch(rootMatch));
}
}
if (notLoadedFragmentMatches.size() > 1) {
final NotLoadedFragmentMatch notLoadedFragmentMatch = new NotLoadedFragmentMatch(
notLoadedFragmentMatches);
parent = createMergeViewerItem(comparison, diff, notLoadedFragmentMatch, notLoadedFragmentMatch,
notLoadedFragmentMatch, side, adapterFactory);
} else {
parent = null;
}
return parent;
}
/**
* Creates a 'basic' {@link IMergeViewerItem}.
*
* @param eObject
* the {@link EObject} for which to create an {@link IMergeViewerItem}.
* @param side
* the {@link MergeViewerSide}.
* @param configuration
* the {@link IMergeViewerItemProviderConfiguration}.
* @return the created {@link IMergeViewerItem}.
*/
private IMergeViewerItem createBasicMergeViewerItem(EObject eObject, MergeViewerSide side,
IMergeViewerItemProviderConfiguration configuration) {
IMergeViewerItem ret = null;
Comparison comparison = configuration.getComparison();
Match match = comparison.getMatch(eObject);
if (match == null) {
return null;
}
EObject expectedValue = MergeViewerUtil.getEObject(match, side);
if (expectedValue != null) {
Iterable<? extends Diff> diffs = getDiffsWithValue(expectedValue, side, match, configuration);
Diff diff = getFirst(diffs, null);
ret = createMergeViewerItem(comparison, diff, match, side, configuration.getAdapterFactory());
} else {
expectedValue = MergeViewerUtil.getEObject(match, side.opposite());
Iterable<? extends Diff> diffs = Lists.newArrayList();
if (expectedValue != null) {
diffs = getDiffsWithValue(expectedValue, side, match, configuration);
}
if (isEmpty(diffs)) {
expectedValue = MergeViewerUtil.getEObject(match, MergeViewerSide.ANCESTOR);
if (expectedValue != null) {
diffs = getDiffsWithValue(expectedValue, side, match, configuration);
}
}
if (!isEmpty(diffs)) {
Diff diff = getFirst(diffs, null);
if (diff instanceof ResourceAttachmentChange) {
ret = createMergeViewerItem(comparison, diff, match, side,
configuration.getAdapterFactory());
} else {
ret = createInsertionPoint(comparison, diff, side, configuration.getAdapterFactory());
}
}
}
return ret;
}
/**
* Creates an insertion point.
*
* @param comparison
* the {@link Comparison}.
* @param diff
* the {@link Diff}.
* @param side
* the {@link MergeViewerSide}.
* @param adapterFactory
* the {@link AdapterFactory}.
* @return the newly created insertion point.
*/
private IMergeViewerItem createInsertionPoint(Comparison comparison, Diff diff, MergeViewerSide side,
AdapterFactory adapterFactory) {
Object left = MergeViewerUtil.getValueFromDiff(diff, MergeViewerSide.LEFT);
Object right = MergeViewerUtil.getValueFromDiff(diff, MergeViewerSide.RIGHT);
IMergeViewerItem insertionPoint = null;
if (left == null && right == null) {
// Do not display anything
} else {
final boolean leftEmptyBox = side == MergeViewerSide.LEFT
&& (left == null || !MergeViewerUtil.getValues(diff, side).contains(left));
final boolean rightEmptyBox = side == MergeViewerSide.RIGHT
&& (right == null || !MergeViewerUtil.getValues(diff, side).contains(right));
if (leftEmptyBox || rightEmptyBox) {
Object ancestor = MergeViewerUtil.getValueFromDiff(diff, MergeViewerSide.ANCESTOR);
insertionPoint = createMergeViewerItem(comparison, diff, left, right, ancestor, side,
adapterFactory);
}
}
return insertionPoint;
}
/**
* After merging a diff which will lead to have an insertion point on both sides, the match associated
* with this diff will be unreacheable because its left and right sides will be null. This method will
* find this match.
*
* @param match
* the given match, can be <code>null</code>
* @return the match associated with the given merged diff.
*/
private Match getMatchWithNullValues(Match match) {
if (match != null) {
for (Match subMatch : match.getSubmatches()) {
if (subMatch.getLeft() == null && subMatch.getRight() == null) {
return subMatch;
}
}
}
return null;
}
/**
* Indicates whether the diff is merged and the match does not match anymore.
*
* @param diff
* the {@link Diff} to check.
* @param match
* {@link Match} to check.
* @return {@code true} if the diff is merged and the match does not exist or has no more left and right,
* {@code false} otherwise.
*/
private boolean isInsertOnBothSides(Diff diff, Match match) {
return diff.getState() == DifferenceState.MERGED
&& (match == null || (match.getLeft() == null && match.getRight() == null));
}
/**
* Indicates whether the diff is of kind ADD in a pseudo conflict.
*
* @param diff
* the {@link Diff} to check.
* @return {@code true} if the diff is of kind ADD in a pseudo conflict, {@code false} otherwise.
*/
private boolean isPseudoAddConflict(Diff diff) {
Conflict conflict = diff.getConflict();
return conflict != null && conflict.getKind() == ConflictKind.PSEUDO
&& diff.getKind() == DifferenceKind.ADD;
}
/**
* Indicates whether the given diff is in real conflict.
*
* @param diff
* the {@link Diff} to check.
* @param match
* the {@link Match} the diff operates on.
* @param side
* the {@link MergeViewerSide}.
* @return {@code true} if the diff is a real conflict on the match, {@code false} otherwise.
*/
private boolean isRealAddConflict(Diff diff, Match match, MergeViewerSide side) {
// Real add conflict (see https://bugs.eclipse.org/bugs/show_bug.cgi?id=442898)
return match.getRight() != null && match.getLeft() != null && isAddOnOppositeSide(diff, side);
}
/**
* Indicates whether the diff is an add on the opposite side as the given side.
*
* @param diff
* the {@link Diff}.
* @param side
* the {@link MergeViewerSide}.
* @return {@code true} if the diff is an add on the opposite side as the given side, {@code false}
* otherwise.
*/
private boolean isAddOnOppositeSide(Diff diff, MergeViewerSide side) {
if (diff.getState() != DifferenceState.MERGED && diff.getKind() == DifferenceKind.ADD) {
DifferenceSource source = diff.getSource();
return (source == DifferenceSource.LEFT && side == MergeViewerSide.RIGHT)
|| (source == DifferenceSource.RIGHT && side == MergeViewerSide.LEFT);
}
return false;
}
/**
* Indicates whether the diff is a delete on the same side as the given one.
*
* @param diff
* the {@link Diff}.
* @param side
* the {@link MergeViewerSide}.
* @return {@code true} if the diff is a delete on the same side as the given side, {@code false}
* otherwise.
*/
private boolean isDeleteOnSameSide(Diff diff, MergeViewerSide side) {
if (diff.getState() != DifferenceState.DISCARDED && diff.getKind() == DifferenceKind.DELETE) {
DifferenceSource source = diff.getSource();
return (source == DifferenceSource.LEFT && side == MergeViewerSide.LEFT)
|| (source == DifferenceSource.RIGHT && side == MergeViewerSide.RIGHT);
}
return false;
}
/**
* Return an Iterable of {@link Diff} which are visible and linked to the given expectedValue. Try to get
* containment reference changes first, then if empty, try to get resource attachment changes.
*
* @param expectedValue
* the expected value
* @param side
* {@link MergeViewerSide}.
* @param parentMatch
* the {@link Match} parent.
* @param configuration
* the {@link IMergeViewerItemProviderConfiguration}.
* @return an iterable of diffs.
*/
private Iterable<? extends Diff> getDiffsWithValue(EObject expectedValue, MergeViewerSide side,
Match parentMatch, IMergeViewerItemProviderConfiguration configuration) {
Iterable<? extends Diff> diffs = getVisibleContainmentDiffs(expectedValue, configuration);
if (size(diffs) > 1 && side != MergeViewerSide.ANCESTOR) {
diffs = filter(diffs, fromSide(side.convertToDifferenceSource()));
}
Diff diff = getFirst(diffs, null);
if (diff == null) {
diffs = filter(parentMatch.getDifferences(), instanceOf(ResourceAttachmentChange.class));
}
return diffs;
}
/**
* Determines all differences regarding the given {@code object} which are related to a containment
* reference change and are visible in the viewer.
*
* @param object
* the {@link EObject}.
* @param configuration
* the {@link IMergeViewerItemProviderConfiguration}.
* @return all visible diffs regarding {@code object} related to containment differences.
*/
protected Iterable<? extends Diff> getVisibleContainmentDiffs(EObject object,
IMergeViewerItemProviderConfiguration configuration) {
List<Diff> differences = configuration.getComparison().getDifferences(object);
return filter(differences, visibleContainmentDiffPredicate(configuration));
}
/**
* Get a non-null side of the the given {@link IMergeViewerItem}, preferring but not limited to the given
* side.
*
* @param mergeViewerItem
* the {@link IMergeViewerItem}.
* @param side
* {@link MergeViewerSide}
* @return the side object. May be null if all sides are null.
*/
protected Object getBestSideValue(IMergeViewerItem mergeViewerItem, MergeViewerSide side) {
Object sideValue;
if (side != MergeViewerSide.ANCESTOR) {
sideValue = getSideValue(mergeViewerItem, side);
if (sideValue == null) {
sideValue = getSideValue(mergeViewerItem, side.opposite());
if (sideValue == null) {
sideValue = getSideValue(mergeViewerItem, MergeViewerSide.ANCESTOR);
}
}
} else {
sideValue = getSideValue(mergeViewerItem, MergeViewerSide.ANCESTOR);
if (sideValue == null) {
sideValue = getSideValue(mergeViewerItem, MergeViewerSide.LEFT);
if (sideValue == null) {
sideValue = getSideValue(mergeViewerItem, MergeViewerSide.RIGHT);
}
}
}
return sideValue;
}
/**
* {@inheritDoc}
*
* @param mergeViewerItem
* @see org.eclipse.emf.compare.rcp.ui.mergeviewer.item.ide.ui.internal.contentmergeviewer.IMergeViewerItem#getSideValue(org.eclipse.emf.compare.rcp.ui.mergeviewer.ide.ui.internal.contentmergeviewer.IMergeViewer.MergeViewerSide)
*/
protected Object getSideValue(IMergeViewerItem mergeViewerItem, MergeViewerSide side) {
switch (side) {
case LEFT:
return mergeViewerItem.getLeft();
case RIGHT:
return mergeViewerItem.getRight();
case ANCESTOR:
return mergeViewerItem.getAncestor();
default:
throw new IllegalStateException(); // happy compiler :)
}
}
/**
* Create an IMergeViewerItem for the parent of the given {@link ResourceAttachmentChange}.
*
* @param diff
* the given {@link ResourceAttachmentChange}.
* @return an IMergeViewerItem.
*/
protected IMergeViewerItem createBasicContainer(ResourceAttachmentChange diff,
IMergeViewerItem mergeViewerItem, AdapterFactory adapterFactory) {
final Comparison comparison = diff.getMatch().getComparison();
MergeViewerSide side = mergeViewerItem.getSide();
Resource left = MergeViewerUtil.getResource(comparison, MergeViewerSide.LEFT, diff);
Resource right = MergeViewerUtil.getResource(comparison, MergeViewerSide.RIGHT, diff);
Resource ancestor = MergeViewerUtil.getResource(comparison, MergeViewerSide.ANCESTOR, diff);
IMergeViewerItem ret = new ResourceAttachmentChangeMergeViewerItem(comparison, null, left, right,
ancestor, side, adapterFactory);
return ret;
}
/**
* Return potential NotLoadedFragment children items (as MergeViewerItem) of the MergeViewerItem in case
* the MergeViewerItem is a {@link org.eclipse.emf.compare.match.Match}.
*
* @param match
* the Match for which we want the potential NotLoadedFragment children items (as
* MergeViewerItems), can be <code>null</code>
* @return the NotLoadedFragment children items (a list of MergeViewerItems) of the given element.
*/
private List<IMergeViewerItem> getNotLoadedFragmentsItems(Comparison comparison, Match match,
MergeViewerSide side, AdapterFactory adapterFactory) {
if (match == null) {
return emptyList();
}
final List<IMergeViewerItem> ret = newArrayList();
final Collection<Match> childrenMatches = ResourceUIUtil
.getChildrenMatchWithNotLoadedParent(comparison, match, side);
if (!childrenMatches.isEmpty()) {
boolean setNames = childrenMatches.size() > 1;
for (Match child : childrenMatches) {
NotLoadedFragmentMatch notLoadedFragmentMatch = new NotLoadedFragmentMatch(child);
if (setNames) {
notLoadedFragmentMatch.setName(ResourceUIUtil.getResourceName(notLoadedFragmentMatch));
}
IMergeViewerItem notLoadedFragmentItem = createMergeViewerItem(comparison, null,
notLoadedFragmentMatch, notLoadedFragmentMatch, notLoadedFragmentMatch, side,
adapterFactory);
ret.add(notLoadedFragmentItem);
}
}
return ret;
}
/**
* Predicate for checking if the given {@code diff} is visible and itself (or any of its 'refining' diffs)
* is a containment reference change.
*
* @param configuration
* the {@link IMergeViewerItemProviderConfiguration}.
* @return the {@link Predicate}.
*/
protected Predicate<Diff> visibleContainmentDiffPredicate(
IMergeViewerItemProviderConfiguration configuration) {
return and(or(CONTAINMENT_REFERENCE_CHANGE, anyRefining(CONTAINMENT_REFERENCE_CHANGE)),
visibleInMergeViewerPredicate(configuration.getDifferenceFilterPredicate(),
configuration.getDifferenceGroupProvider()));
}
/**
* Predicate for checking if the given {@code diff} is visible in the merge viewer.
*
* @param predicate
* the filtering {@link Predicate}.
* @param groupProvider
* the active {@link IDifferenceGroupProvider}.
* @return the {@link Predicate}.
*/
protected Predicate<Diff> visibleInMergeViewerPredicate(final Predicate<? super EObject> predicate,
final IDifferenceGroupProvider groupProvider) {
if (predicate == null) {
return Predicates.alwaysTrue();
}
// checks whether the diff itself or at least one of its refinements is visible
return new Predicate<Diff>() {
public boolean apply(Diff diff) {
return MergeViewerUtil.isVisibleInMergeViewer(diff, groupProvider, predicate);
}
};
}
/**
* Creates the Merge Viewer Items for the given {@code values}.
*
* @param values
* the object for which {@link IMergeViewerItem}s shall be created.
* @param parent
* the {@link IMergeViewerItem} parent of the children to create
* @param configuration
* the {@link IMergeViewerItemProviderConfiguration}.
* @return
*/
protected List<IMergeViewerItem> createMergeViewerItemsFrom(Collection<?> values, IMergeViewerItem parent,
IMergeViewerItemProviderConfiguration configuration) {
List<IMergeViewerItem> ret = newArrayListWithCapacity(values.size());
for (EObject value : filter(values, EObject.class)) {
Match match = configuration.getComparison().getMatch(value);
if ((match != null && !isMatchWithAllProxyData(match))) {
IMergeViewerItem valueToAdd = createMergeViewerItemFrom(value, parent, configuration);
if (valueToAdd != null) {
ret.add(valueToAdd);
}
}
}
return ret;
}
/**
* Check if the given match holds a proxy on each side.
*
* @param match
* the given match.
* @return true if the given match holds a proxy on each side, false otherwise.
*/
private boolean isMatchWithAllProxyData(Match match) {
boolean proxy = false;
EObject left = match.getLeft();
EObject right = match.getRight();
EObject origin = match.getOrigin();
if (left != null && right != null) {
if (left.eIsProxy() && right.eIsProxy()) {
proxy = true;
}
}
if (proxy == true && match.getComparison().isThreeWay() && (origin == null || !origin.eIsProxy())) {
proxy = false;
}
return proxy;
}
/**
* Creates an IMergeViewerItem from an EObject.
*
* @param eObject
* the given eObject.
* @param parent
* the {@link IMergeViewerItem} parent of the child to create.
* @param configuration
* the {@link IMergeViewerItemProviderConfiguration}.
* @return an IMergeViewerItem.
*/
protected IMergeViewerItem createMergeViewerItemFrom(EObject eObject, IMergeViewerItem parent,
IMergeViewerItemProviderConfiguration configuration) {
final Comparison comparison = configuration.getComparison();
final AdapterFactory adapterFactory = configuration.getAdapterFactory();
final Match match = comparison.getMatch(eObject);
final MergeViewerSide side = parent.getSide();
final Diff diff = getFirst(getVisibleContainmentDiffs(eObject, configuration), null);
if (match != null) {
return createMergeViewerItem(comparison, diff, match, side, adapterFactory);
} else {
switch (side) {
case LEFT:
return createMergeViewerItem(comparison, diff, eObject, null, null, side, adapterFactory);
case RIGHT:
return createMergeViewerItem(comparison, diff, null, eObject, null, side, adapterFactory);
case ANCESTOR:
return createMergeViewerItem(comparison, diff, null, null, eObject, side, adapterFactory);
default:
throw new IllegalStateException();
}
}
}
/**
* Creates the {@link IMergeViewerItem} from the given data.
*
* @param comparison
* the {@link Comparison}.
* @param diff
* the {@link Diff}. May be null.
* @param left
* the left object. May be null. (One of left, right, ancestor should be non-null).
* @param right
* the right object. May be null. (One of left, right, ancestor should be non-null).
* @param ancestor
* the ancestor object. May be null. (One of left, right, ancestor should be non-null).
* @param side
* the {@link MergeViewerSide}.
* @param adapterFactory
* the {@link AdapterFactory}.
* @return the created {@link IMergeViewerItem}.
*/
protected IMergeViewerItem createMergeViewerItem(Comparison comparison, Diff diff, Object left,
Object right, Object ancestor, MergeViewerSide side, AdapterFactory adapterFactory) {
return new MergeViewerItem(comparison, diff, left, right, ancestor, side, adapterFactory);
}
/**
* Creates the {@link IMergeViewerItem} from the given data.
*
* @param comparison
* the {@link Comparison}.
* @param diff
* the {@link Diff}. May be null.
* @param match
* the {@link Match}.
* @param side
* the {@link MergeViewerSide}.
* @param adapterFactory
* the {@link AdapterFactory}.
* @return the created {@link IMergeViewerItem}.
*/
protected IMergeViewerItem createMergeViewerItem(Comparison comparison, Diff diff, Match match,
MergeViewerSide side, AdapterFactory adapterFactory) {
return createMergeViewerItem(comparison, diff, match.getLeft(), match.getRight(), match.getOrigin(),
side, adapterFactory);
}
}