blob: ea7140d79b92a1d50672fcd18bbcc55e2ddab4ea [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2013, 2016 Obeo.
* 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
*******************************************************************************/
package org.eclipse.emf.compare.rcp.ui.internal.structuremergeviewer.filters.impl;
import static org.eclipse.emf.compare.ConflictKind.REAL;
import static org.eclipse.emf.compare.DifferenceKind.ADD;
import static org.eclipse.emf.compare.DifferenceKind.DELETE;
import static org.eclipse.emf.compare.DifferenceKind.MOVE;
import static org.eclipse.emf.compare.utils.EMFComparePredicates.CONTAINMENT_REFERENCE_CHANGE;
import static org.eclipse.emf.compare.utils.EMFComparePredicates.fromSide;
import static org.eclipse.emf.compare.utils.EMFComparePredicates.hasNoDirectOrIndirectConflict;
import static org.eclipse.emf.compare.utils.EMFComparePredicates.ofKind;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.Iterables;
import com.google.common.collect.Iterators;
import com.google.common.collect.UnmodifiableIterator;
import org.eclipse.emf.common.notify.Adapter;
import org.eclipse.emf.compare.Diff;
import org.eclipse.emf.compare.DifferenceSource;
import org.eclipse.emf.compare.Match;
import org.eclipse.emf.compare.ResourceAttachmentChange;
import org.eclipse.emf.compare.match.MatchOfContainmentReferenceChangeAdapter;
import org.eclipse.emf.compare.rcp.ui.structuremergeviewer.filters.AbstractDifferenceFilter;
import org.eclipse.emf.compare.utils.MatchUtil;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.emf.edit.tree.TreeNode;
/**
* A filter used by default that filters out cascading differences (differences located under a Match that is
* either ADDed or DELETEd on the diff's side). The MOVE differences are not hidden by this filter.
* Differences hidden are all those that match the following criteria:
* <ul>
* <li>this.kind != MOVE</li>
* <li>this.conflict == null && this.'indirect real conflicts' is empty</li>
* <li>this.refines is empty</li>
* <li>this is located inside a TreeNode that represents a Match that is either ADDed or DELETEd, and for
* which the diff that represents this addition or deletion is not refined by this.</li>
* </ul>
*
* @author <a href="mailto:axel.richard@obeo.fr">Axel Richard</a>
* @since 4.0
*/
public class CascadingDifferencesFilter extends AbstractDifferenceFilter {
/**
* The predicate used by this filter when it is selected.
*/
private static final Predicate<? super EObject> PREDICATE_WHEN_SELECTED = new Predicate<EObject>() {
public boolean apply(EObject input) {
boolean ret = false;
if (input instanceof TreeNode) {
TreeNode treeNode = (TreeNode)input;
EObject data = treeNode.getData();
if (data instanceof Diff && !(data instanceof ResourceAttachmentChange)) {
Diff diff = (Diff)data;
if (diff.getKind() != MOVE && hasNoDirectOrIndirectConflict(REAL).apply(diff)
&& diff.getRefines().isEmpty()) {
TreeNode parent = treeNode.getParent();
if (parent != null && parent.getData() instanceof Match) {
Match parentMatch = (Match)parent.getData();
ret = isInsideAddOrDeleteTreeNode(diff, parent);
if (!ret && isAddOrDeleteMatch(parentMatch, diff.getSource())) {
ret = !Predicates
.and(Predicates.or(CONTAINMENT_REFERENCE_CHANGE,
REFINED_BY_CONTAINMENT_REF_CHANGE), ofKind(ADD, DELETE))
.apply(diff);
}
}
}
}
}
return ret;
}
private boolean isInsideAddOrDeleteTreeNode(Diff diff, TreeNode parent) {
boolean ret = false;
DifferenceSource side = diff.getSource();
TreeNode grandParent = parent.getParent();
Match grandParentMatch = null;
if (grandParent != null && grandParent.getData() instanceof Match) {
grandParentMatch = (Match)grandParent.getData();
}
if (isAddOrDeleteMatch(grandParentMatch, side)) {
// The ancestor has been added/deleted, we must filter the current diff
// _unless_ it is refined by the diff that represents the grand-parent
// add/delete
Diff addOrDeleteDiff = findAddOrDeleteDiff(grandParentMatch, side);
if (addOrDeleteDiff != null) {
if (diff.getRefinedBy().contains(addOrDeleteDiff)) {
// recurse
ret = isInsideAddOrDeleteTreeNode(diff, grandParent);
} else {
ret = true;
}
}
}
return ret;
}
private Diff findAddOrDeleteDiff(Match match, DifferenceSource side) {
final Iterable<Diff> addOrDeleteContainmentDiffs = MatchUtil
.findAddOrDeleteContainmentDiffs(match);
if (addOrDeleteContainmentDiffs != null) {
final UnmodifiableIterator<Diff> sideChanges = Iterators
.filter(addOrDeleteContainmentDiffs.iterator(), fromSide(side));
if (sideChanges.hasNext()) {
return sideChanges.next();
}
}
return null;
}
/**
* Indicate whether a Match is that of an object that was either added or deleted on the given side.
*
* @param match
* The match
* @param side
* The side
* @return <code>true</code> if the matched object is present on side but not on origin or vice-versa.
* <code>false</code> if match is <code>null</code>.
*/
protected boolean isAddOrDeleteMatch(Match match, DifferenceSource side) {
if (match == null) {
return false;
}
Adapter adapter = EcoreUtil.getAdapter(match.eAdapters(),
MatchOfContainmentReferenceChangeAdapter.class);
return adapter != null;
}
};
private static final Predicate<Diff> REFINED_BY_CONTAINMENT_REF_CHANGE = new Predicate<Diff>() {
public boolean apply(Diff input) {
return Iterables.any(input.getRefinedBy(), CONTAINMENT_REFERENCE_CHANGE);
}
};
@Override
public Predicate<? super EObject> getPredicateWhenSelected() {
return PREDICATE_WHEN_SELECTED;
}
}