blob: 60664fa94a1c40b75019e32fc053ce1237e6019a [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2013, 2016 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
* Stefan Dirix - bug 488941
* Simon Delisle, Edgar Mueller - bug 486923
* Tanja Mayerhofer - bug 501864
*******************************************************************************/
package org.eclipse.emf.compare.rcp.ui.internal.structuremergeviewer.groups.impl;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Predicates.and;
import static com.google.common.base.Predicates.not;
import static com.google.common.base.Predicates.or;
import static com.google.common.collect.Iterables.any;
import static com.google.common.collect.Iterators.concat;
import static com.google.common.collect.Iterators.filter;
import static com.google.common.collect.Iterators.transform;
import static com.google.common.collect.Lists.newArrayList;
import static com.google.common.collect.Maps.newHashMap;
import static java.util.Collections.unmodifiableSet;
import static org.eclipse.emf.compare.ConflictKind.PSEUDO;
import static org.eclipse.emf.compare.ConflictKind.REAL;
import static org.eclipse.emf.compare.internal.utils.DiffUtil.getRootRefinedDiffs;
import static org.eclipse.emf.compare.utils.EMFComparePredicates.allAtomicRefining;
import static org.eclipse.emf.compare.utils.EMFComparePredicates.anyRefining;
import static org.eclipse.emf.compare.utils.EMFComparePredicates.containsConflictOfTypes;
import static org.eclipse.emf.compare.utils.EMFComparePredicates.fromSide;
import static org.eclipse.emf.compare.utils.EMFComparePredicates.hasConflict;
import static org.eclipse.emf.compare.utils.EMFComparePredicates.hasState;
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Sets;
import com.google.common.collect.Sets.SetView;
import com.google.common.collect.UnmodifiableIterator;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.emf.common.notify.Adapter;
import org.eclipse.emf.common.util.BasicEList;
import org.eclipse.emf.common.util.EList;
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.DifferenceSource;
import org.eclipse.emf.compare.DifferenceState;
import org.eclipse.emf.compare.impl.ConflictImpl;
import org.eclipse.emf.compare.provider.utils.ComposedStyledString;
import org.eclipse.emf.compare.provider.utils.IStyledString;
import org.eclipse.emf.compare.provider.utils.IStyledString.IComposedStyledString;
import org.eclipse.emf.compare.provider.utils.IStyledString.Style;
import org.eclipse.emf.compare.rcp.ui.internal.EMFCompareRCPUIMessages;
import org.eclipse.emf.compare.rcp.ui.internal.configuration.SideLabelProvider;
import org.eclipse.emf.compare.rcp.ui.internal.structuremergeviewer.nodes.ConflictNode;
import org.eclipse.emf.compare.rcp.ui.structuremergeviewer.groups.AbstractDifferenceGroupProvider;
import org.eclipse.emf.compare.rcp.ui.structuremergeviewer.groups.IDifferenceGroup;
import org.eclipse.emf.compare.scope.IComparisonScope;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.util.ECrossReferenceAdapter;
import org.eclipse.emf.ecore.util.EcoreUtil;
/**
* This implementation of a
* {@link org.eclipse.emf.compare.rcp.ui.structuremergeviewer.groups.IDifferenceGroupProvider} will be used to
* group the differences by their {@link DifferenceSource side} : left, right and conflicts.
* <p>
* The table below describes the location of a diff depending on its status and that of its refining diffs
* (whether all or some of them are in a real/pseudo conflict). <br/>
* <br/>
* <table style="border-collapse:collapse;">
* <thead>
* <tr>
* <th rowspan="2"></th>
* <th colspan="2" style="border:1px solid;">Real Conflicts</th>
* <th colspan="2" style="border:1px solid;">Pseudo-Conflicts</th>
* <th style="border:1px solid;">No Conflict</th>
* </tr>
* <tr>
* <th style="border:1px solid;">All</th>
* <th style="border:1px solid;">Some</th>
* <th style="border:1px solid;">All</th>
* <th style="border:1px solid;">Some</th>
* <th style="border:1px solid;">All</th>
* </tr>
* </thead> <tbody>
* <tr>
* <td style="border:1px solid; padding:0 2px;">Tech. filter ON</td>
* <td style="border:1px solid; padding:0 2px;">Conflict</td>
* <td style="border:1px solid; padding:0 2px;">Conflict</td>
* <td style="border:1px solid; padding:0 2px;">Conflict (hidden)</td>
* <td style="border:1px solid; padding:0 2px;">Side</td>
* <td style="border:1px solid; padding:0 2px;">Side</td>
* </tr>
* <tr>
* <td style="border:1px solid; padding:0 2px;">Tech. filter OFF</td>
* <td style="border:1px solid; padding:0 2px;">Conflict</td>
* <td style="border:1px solid; padding:0 2px;">Conflict</td>
* <td style="border:1px solid; padding:0 2px;">Conflict</td>
* <td style="border:1px solid; padding:0 2px;">Side</td>
* <td style="border:1px solid; padding:0 2px;">Side</td>
* </tr>
* </tbody>
* </table>
* </p>
*
* @author <a href="mailto:axel.richard@obeo.fr">Axel Richard</a>
* @since 4.0
*/
public class ThreeWayComparisonGroupProvider extends AbstractDifferenceGroupProvider {
/**
* The default predicate used to filter differences in difference groups. The predicate returns true for
* diffs that do not have a direct or indirect real conflict and that do not have only pseudo conflicts.
*/
public static final Predicate<? super Diff> DEFAULT_DIFF_GROUP_FILTER_PREDICATE = and(
not(hasConflict(REAL, PSEUDO)),
not(or(anyRefining(hasConflict(REAL)), allAtomicRefining(hasConflict(PSEUDO)))));
/**
* {@inheritDoc}
*
* @see org.eclipse.emf.compare.rcp.ui.structuremergeviewer.groups.IDifferenceGroupProvider#isEnabled(org
* .eclipse.emf.compare.scope.IComparisonScope, org.eclipse.emf.compare.Comparison)
*/
@Override
public boolean isEnabled(IComparisonScope scope, Comparison comparison) {
if (comparison != null && comparison.isThreeWay()) {
return true;
}
return false;
}
/**
* Specialized {@link BasicDifferenceGroupImpl} for Conflicts.
*
* @author <a href="mailto:axel.richard@obeo.fr">Axel Richard</a>
*/
public static class ConflictsGroupImpl extends BasicDifferenceGroupImpl {
/**
* The default predicate used to filter differences.
*/
private static final Predicate<? super Diff> DEFAULT_CONFLICT_GROUP_FILTER_PREDICATE = hasConflict(
ConflictKind.REAL, ConflictKind.PSEUDO);
/**
* Conflict groups to show in SMV.
*/
private final List<CompositeConflict> compositeConflicts = newArrayList();
/**
* Maps conflicting differences to their composite conflicts.
*/
private final Map<Diff, CompositeConflict> diffToCompositeConflictMap = newHashMap();
/**
* {@inheritDoc}.
*
* @see org.eclipse.emf.compare.rcp.ui.internal.structuremergeviewer.groups.impl.BasicDifferenceGroupImpl#BasicDifferenceGroupImpl(org.eclipse.emf.compare.Comparison,
* java.lang.Iterable, com.google.common.base.Predicate, java.lang.String)
*/
public ConflictsGroupImpl(Comparison comparison, Predicate<? super Diff> filter, String name,
ECrossReferenceAdapter crossReferenceAdapter) {
super(comparison, filter, name, crossReferenceAdapter);
}
/**
* Instantiates this group given the comparison. It will use the default filter to determine its list
* of differences. It will be displayed in the UI with the default icon and the given name.
*
* @param comparison
* The comparison that is the parent of this group.
* @param name
* The name that the EMF Compare UI will display for this group.
* @param crossReferenceAdapter
* The cross reference adapter that will be added to this group's children.
*/
public ConflictsGroupImpl(Comparison comparison, String name,
ECrossReferenceAdapter crossReferenceAdapter) {
super(comparison, DEFAULT_CONFLICT_GROUP_FILTER_PREDICATE, name, crossReferenceAdapter);
}
/**
* In conflicts, a special case must be handled for refining diffs: If they are not part of the same
* conflict then they should not be in the same group as the refined diff.
*
* @param diff
* The difference
* @return <code>true</code> if the diff refines nothing or if its conflict does not contain all the
* diffs it refines.
*/
@Override
protected boolean mustDisplayAsDirectChildOfMatch(Diff diff) {
return diff.getRefines().isEmpty() || (diff.getConflict() != null
&& !diff.getConflict().getDifferences().containsAll(diff.getRefines()));
}
@Override
protected void doBuildSubTrees() {
for (Conflict conflict : getComparison().getConflicts()) {
final CompositeConflict compositeConflict = new CompositeConflict(conflict);
compositeConflicts.add(compositeConflict);
joinOverlappingCompositeConflicts(compositeConflict);
updateDiffToCompositeConflictMap(compositeConflict);
}
for (CompositeConflict conflict : compositeConflicts) {
final ConflictNodeBuilder builder = new ConflictNodeBuilder(conflict, this);
final ConflictNode conflictNode = builder.buildNode();
children.add(conflictNode);
}
}
/**
* Joins the given composite conflict with existing overlapping composite conflicts.
*
* @param compositeConflict
* The composite conflict into which overlapping composite conflicts should be joined
*/
private void joinOverlappingCompositeConflicts(final CompositeConflict compositeConflict) {
// determine composite conflicts to join
final Set<CompositeConflict> compositeConflictsToJoin = new HashSet<CompositeConflict>();
for (Diff diff : compositeConflict.getDifferences()) {
if (diffToCompositeConflictMap.containsKey(diff)) {
compositeConflictsToJoin.add(diffToCompositeConflictMap.get(diff));
}
}
// join conflict groups
for (CompositeConflict conflictGroupToJoin : compositeConflictsToJoin) {
compositeConflict.join(conflictGroupToJoin);
compositeConflicts.remove(conflictGroupToJoin);
}
}
/**
* Updates the diff to composite conflict map with the given composite conflict.
*
* @param compositeConflict
* The composite conflict that should be added to the diff to composite conflict map
*/
private void updateDiffToCompositeConflictMap(final CompositeConflict compositeConflict) {
for (Diff diff : compositeConflict.getDifferences()) {
diffToCompositeConflictMap.put(diff, compositeConflict);
}
}
/**
* {@inheritDoc}
*
* @see org.eclipse.emf.compare.rcp.ui.internal.structuremergeviewer.groups.impl.BasicDifferenceGroupImpl#getStyledName()
*/
@Override
public IComposedStyledString getStyledName() {
final IStyledString.IComposedStyledString ret = new ComposedStyledString();
Iterator<EObject> eAllContents = concat(transform(getChildren().iterator(), E_ALL_CONTENTS));
Iterator<EObject> eAllData = transform(eAllContents, TREE_NODE_DATA);
UnmodifiableIterator<Diff> eAllDiffData = filter(eAllData, Diff.class);
Collection<Diff> diffs = Sets.newHashSet(eAllDiffData);
boolean unresolvedDiffs = any(diffs, and(hasState(DifferenceState.UNRESOLVED),
hasConflict(ConflictKind.REAL, ConflictKind.PSEUDO)));
if (unresolvedDiffs) {
ret.append("> ", Style.DECORATIONS_STYLER); //$NON-NLS-1$
}
ret.append(getName());
return ret;
}
}
/**
* This extension of {@link Conflict} is used to handle {@link Diff#getRefinedBy() refined} diffs and to
* join conflicts for the SMV. If refining diffs are part of a conflict, we show their refined diffs
* instead. As we show refined diffs instead of the refining diffs, multiple conflicts may consequently
* include the same refined diffs. To avoid that, this extension of a conflict also joins such overlapping
* conflicts.
*
* @author <a href="mailto:tmayerhofer@eclipsesource.com">Tanja Mayerhofer</a>
*/
public static class CompositeConflict extends ConflictImpl {
/** The joined conflicts. */
private Set<Conflict> conflicts = new LinkedHashSet<Conflict>();
/** The diffs of all composed conflicts. */
private EList<Diff> diffs = new BasicEList<Diff>();
/** The conflict kind of this composite conflict. */
private ConflictKind conflictKind = ConflictKind.REAL;
/**
* Creates a new composite conflict for the given conflict.
*
* @param conflict
* The conflict to create a composite conflict for, must not be <code>null</code> and must
* have a non-<code>null</code> {@link Conflict#getKind() kind}.
*/
public CompositeConflict(Conflict conflict) {
conflicts.add(checkNotNull(conflict));
conflictKind = checkNotNull(conflict.getKind());
diffs.addAll(computeRefinedDiffs(conflict));
}
/**
* Computes the refined diffs of the conflict. In particular, refining diffs are replaced by refined
* diffs.
*
* @param conflict
* The conflict to compute its refined diffs for.
* @return The set of refined diffs of the conflict
*/
private Set<Diff> computeRefinedDiffs(Conflict conflict) {
Set<Diff> computedDiffs = new LinkedHashSet<Diff>();
for (Diff diff : conflict.getDifferences()) {
if (diff.getRefines().isEmpty()) {
computedDiffs.add(diff);
} else {
computedDiffs.addAll(getRootRefinedDiffs(diff));
}
}
return computedDiffs;
}
@Override
public ConflictKind getKind() {
return conflictKind;
}
/**
* Returns an EList built by aggregating the diffs of all the aggregated conflicts.
* <p>
* <b>This list is not supposed to be used for update, since modifying this list will not modify the
* underlying conflicts.</b>
* </p>
*/
@Override
public EList<Diff> getDifferences() {
return diffs;
}
/**
* Returns an unmodifiable view of the joined conflicts.
*
* @return An unmodifiable view of the joined conflicts, never <code>null</code> nor empty.
*/
public Set<Conflict> getConflicts() {
return unmodifiableSet(conflicts);
}
/**
* Joins the provided composite conflict with this composite conflict.
*
* @param conflict
* The conflict to be joined with this composite conflict
*/
public void join(CompositeConflict conflict) {
Set<Diff> currentDiffSet = new LinkedHashSet<Diff>(diffs);
Set<Diff> otherDiffSet = new LinkedHashSet<Diff>(conflict.getDifferences());
SetView<Diff> newDiffs = Sets.difference(otherDiffSet, currentDiffSet);
diffs.addAll(newDiffs);
if (conflicts.addAll(conflict.getConflicts()) && conflictKind != REAL) {
if (any(conflicts, containsConflictOfTypes(REAL))) {
conflictKind = REAL;
} else {
conflictKind = PSEUDO;
}
}
}
}
@Override
protected Collection<? extends IDifferenceGroup> buildGroups(Comparison comparison2) {
Adapter adapter = EcoreUtil.getAdapter(getComparison().eAdapters(), SideLabelProvider.class);
final String leftLabel;
final String rightLabel;
if (adapter instanceof SideLabelProvider) {
SideLabelProvider labelProvider = (SideLabelProvider)adapter;
leftLabel = labelProvider.getLeftLabel();
rightLabel = labelProvider.getRightLabel();
} else {
leftLabel = EMFCompareRCPUIMessages.getString("ThreeWayComparisonGroupProvider.left.label"); //$NON-NLS-1$
rightLabel = EMFCompareRCPUIMessages.getString("ThreeWayComparisonGroupProvider.right.label"); //$NON-NLS-1$
}
final ConflictsGroupImpl conflicts = new ConflictsGroupImpl(getComparison(),
EMFCompareRCPUIMessages.getString("ThreeWayComparisonGroupProvider.conflicts.label"), //$NON-NLS-1$
getCrossReferenceAdapter());
conflicts.buildSubTree();
final BasicDifferenceGroupImpl leftSide = new BasicDifferenceGroupImpl(getComparison(),
and(fromSide(DifferenceSource.LEFT), DEFAULT_DIFF_GROUP_FILTER_PREDICATE), leftLabel,
getCrossReferenceAdapter());
leftSide.buildSubTree();
final BasicDifferenceGroupImpl rightSide = new BasicDifferenceGroupImpl(getComparison(),
and(fromSide(DifferenceSource.RIGHT), DEFAULT_DIFF_GROUP_FILTER_PREDICATE), rightLabel,
getCrossReferenceAdapter());
rightSide.buildSubTree();
return ImmutableList.of(conflicts, leftSide, rightSide);
}
}