Java tutorial
/******************************************************************************* * Copyright (c) 2012, 2013 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.groups; import static com.google.common.base.Predicates.and; import static com.google.common.base.Predicates.instanceOf; import static com.google.common.base.Predicates.not; import static com.google.common.base.Predicates.or; import static com.google.common.collect.Collections2.filter; import static com.google.common.collect.Iterators.any; import static com.google.common.collect.Iterators.concat; import static com.google.common.collect.Iterators.transform; import static com.google.common.collect.Lists.newArrayList; import static com.google.common.collect.Sets.newHashSet; import static org.eclipse.emf.compare.utils.EMFComparePredicates.containmentMoveReferenceChange; import static org.eclipse.emf.compare.utils.EMFComparePredicates.containmentReferenceChange; import static org.eclipse.emf.compare.utils.EMFComparePredicates.hasState; import static org.eclipse.emf.compare.utils.EMFComparePredicates.valueIs; import com.google.common.base.Function; 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.Lists; import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.Set; import org.eclipse.emf.common.notify.Notifier; import org.eclipse.emf.common.notify.impl.AdapterImpl; import org.eclipse.emf.common.util.EList; import org.eclipse.emf.compare.Comparison; import org.eclipse.emf.compare.Diff; import org.eclipse.emf.compare.DifferenceState; import org.eclipse.emf.compare.Match; import org.eclipse.emf.compare.MatchResource; import org.eclipse.emf.compare.ReferenceChange; import org.eclipse.emf.compare.ResourceAttachmentChange; import org.eclipse.emf.compare.provider.utils.ComposedStyledString; import org.eclipse.emf.compare.provider.utils.IStyledString; import org.eclipse.emf.compare.provider.utils.IStyledString.Style; import org.eclipse.emf.compare.rcp.ui.EMFCompareRCPUIPlugin; import org.eclipse.emf.compare.rcp.ui.internal.structuremergeviewer.groups.extender.IDifferenceGroupExtender; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.util.ECrossReferenceAdapter; import org.eclipse.emf.edit.tree.TreeFactory; import org.eclipse.emf.edit.tree.TreeNode; import org.eclipse.swt.graphics.Image; /** * This implementation of a {@link IDifferenceGroup} uses a predicate to filter the whole list of differences. * <p> * This can be subclasses or used directly instead of {@link IDifferenceGroup}. * </p> * * @author <a href="mailto:laurent.goubet@obeo.fr">Laurent Goubet</a> * @since 3.0 */ public class BasicDifferenceGroupImpl extends AdapterImpl implements IDifferenceGroup { /** The filter we'll use in order to filter the differences that are part of this group. */ protected final Predicate<? super Diff> filter; /** The name that the EMF Compare UI will display for this group. */ protected final String name; /** The icon that the EMF Compare UI will display for this group. */ protected final Image image; /** The comparison that is the parent of this group. */ private final Comparison comparison; /** The list of children of this group. */ protected List<TreeNode> children; /** The list of already processed refined diffs. */ protected List<Diff> extensionDiffProcessed; /** The registry of difference group extenders. */ private final IDifferenceGroupExtender.Registry registry = EMFCompareRCPUIPlugin.getDefault() .getDifferenceGroupExtenderRegistry(); /** The cross reference adapter that will be added to this group's children. */ private final ECrossReferenceAdapter crossReferenceAdapter; private final IDifferenceGroupProvider groupProvider; /** * Instantiates this group given the comparison and filter that should be used in order to determine its * list of differences. * <p> * This will use the default name and icon for the group. * </p> * * @param comparison * The comparison that is the parent of this group. * @param unfiltered * The whole unfiltered list of differences. * @param filter * The filter we'll use in order to filter the differences that are part of this group. * @param crossReferenceAdapter * The cross reference adapter that will be added to this group's children. */ public BasicDifferenceGroupImpl(Comparison comparison, IDifferenceGroupProvider groupProvider, Predicate<? super Diff> filter, ECrossReferenceAdapter crossReferenceAdapter) { this(comparison, groupProvider, filter, "Group", EMFCompareRCPUIPlugin.getImage("icons/full/toolb16/group.gif"), //$NON-NLS-1$ crossReferenceAdapter); } /** * @return the comparison */ protected final Comparison getComparison() { return comparison; } /** * Instantiates this group given the comparison and filter that should be used in order 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 unfiltered * The whole unfiltered list of differences. * @param filter * The filter we'll use in order to filter the differences that are part 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 BasicDifferenceGroupImpl(Comparison comparison, IDifferenceGroupProvider groupProvider, Predicate<? super Diff> filter, String name, ECrossReferenceAdapter crossReferenceAdapter) { this(comparison, groupProvider, filter, name, EMFCompareRCPUIPlugin.getImage("icons/full/toolb16/group.gif"), crossReferenceAdapter); //$NON-NLS-1$ } /** * Instantiates this group given the comparison and filter that should be used in order to determine its * list of differences. It will be displayed in the UI with the given icon and name. * * @param comparison * The comparison that is the parent of this group. * @param unfiltered * The whole unfiltered list of differences. * @param filter * The filter we'll use in order to filter the differences that are part of this group. * @param name * The name that the EMF Compare UI will display for this group. * @param image * The icon that the EMF Compare UI will display for this group. * @param crossReferenceAdapter * Updated upstream The cross reference adapter that will be added to this group's children. */ public BasicDifferenceGroupImpl(Comparison comparison, IDifferenceGroupProvider groupProvider, Predicate<? super Diff> filter, String name, Image image, ECrossReferenceAdapter crossReferenceAdapter) { this.comparison = comparison; this.groupProvider = groupProvider; this.filter = filter; this.name = name; this.image = image; this.crossReferenceAdapter = crossReferenceAdapter; } /** * {@inheritDoc} * * @see org.eclipse.emf.common.notify.impl.AdapterImpl#isAdapterForType(java.lang.Object) */ @Override public boolean isAdapterForType(Object type) { return type == IDifferenceGroup.class; } /** * {@inheritDoc} * * @see org.eclipse.emf.compare.rcp.ui.internal.structuremergeviewer.groups.IDifferenceGroup#getName() */ public String getName() { return name; } /** * {@inheritDoc} * * @see org.eclipse.emf.compare.rcp.ui.internal.structuremergeviewer.groups.IDifferenceGroup#getStyledName() */ public IStyledString.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); boolean unresolvedDiffs = any(Iterators.filter(eAllData, Diff.class), hasState(DifferenceState.UNRESOLVED)); if (unresolvedDiffs) { ret.append("> ", Style.DECORATIONS_STYLER); } ret.append(getName()); return ret; } /** * Function that returns all contents of the given EObject. */ protected static final Function<EObject, Iterator<EObject>> E_ALL_CONTENTS = new Function<EObject, Iterator<EObject>>() { public Iterator<EObject> apply(EObject eObject) { return eObject.eAllContents(); } }; /** * {@inheritDoc} * * @see org.eclipse.emf.compare.rcp.ui.internal.structuremergeviewer.groups.IDifferenceGroup#getImage() */ public Image getImage() { return image; } /** * {@inheritDoc} * * @see org.eclipse.emf.compare.rcp.ui.internal.structuremergeviewer.groups.IDifferenceGroup#getChildren() */ public List<? extends TreeNode> getChildren() { if (children == null) { children = newArrayList(); extensionDiffProcessed = newArrayList(); for (Match match : comparison.getMatches()) { List<? extends TreeNode> buildSubTree = buildSubTree((Match) null, match); if (buildSubTree != null) { children.addAll(buildSubTree); } } for (MatchResource matchResource : comparison.getMatchedResources()) { TreeNode buildSubTree = buildSubTree(matchResource); if (buildSubTree != null) { children.add(buildSubTree); } } registerCrossReferenceAdapter(children); } return children; } protected final void registerCrossReferenceAdapter(List<? extends Notifier> notifiers) { for (Notifier notifier : notifiers) { // this cross referencer has to live as long as the objects on which it is installed. notifier.eAdapters().add(crossReferenceAdapter); } } protected final void unregisterCrossReferenceAdapter(List<? extends Notifier> notifiers) { for (Notifier notifier : notifiers) { // this cross referencer has to live as long as the objects on which it is installed. notifier.eAdapters().remove(crossReferenceAdapter); } } /** * Build the sub tree of the given {@link MatchResource}. * * @param matchResource * the given MatchResource. * @return the sub tree of the given MatchResource. */ protected TreeNode buildSubTree(MatchResource matchResource) { TreeNode treeNode = wrap(matchResource); for (Match match : comparison.getMatches()) { treeNode.getChildren().addAll(buildSubTree(matchResource, match)); } return treeNode; } /** * Build the sub tree of the given Match that is a root of the given MatchResource. * * @param matchResource * the given MatchResource. * @param match * the given Match. * @return the sub tree of the given Match that is a root of the given MatchResource. */ protected List<TreeNode> buildSubTree(MatchResource matchResource, Match match) { List<TreeNode> ret = newArrayList(); if (isRootOfResourceURI(match.getLeft(), matchResource.getLeftURI()) || isRootOfResourceURI(match.getRight(), matchResource.getRightURI()) || isRootOfResourceURI(match.getOrigin(), matchResource.getOriginURI())) { Collection<Diff> resourceAttachmentChanges = filter(match.getDifferences(), and(filter, resourceAttachmentChange())); for (Diff diff : resourceAttachmentChanges) { ret.add(wrap(diff)); } } else { for (Match subMatch : match.getSubmatches()) { ret.addAll(buildSubTree(matchResource, subMatch)); } } return ret; } /** * Build the sub tree of the given {@link Match}. * * @param parentMatch * the parent of the given Match. * @param match * the given Match. * @return the sub tree of the given Match. */ public List<TreeNode> buildSubTree(Match parentMatch, Match match) { final List<TreeNode> ret = Lists.newArrayList(); boolean isContainment = false; // Manage containment reference changes if (parentMatch != null) { Collection<Diff> containmentChanges = filter(parentMatch.getDifferences(), containmentReferenceForMatch(match)); if (!containmentChanges.isEmpty()) { isContainment = true; for (Diff diff : containmentChanges) { ret.add(wrap(diff)); EList<Diff> refines = diff.getRefines(); for (Diff refine : refines) { Diff mainDiff = refine.getPrimeRefining(); if (mainDiff != null && mainDiff == diff && !extensionDiffProcessed.contains(refine)) { TreeNode buildSubTree2 = buildSubTree(refine); if (buildSubTree2 != null) { ret.add(buildSubTree2); extensionDiffProcessed.add(refine); } } } } } } if (ret.isEmpty() && !matchWithLeftAndRightInDifferentContainer(match)) { ret.add(wrap(match)); } Collection<TreeNode> toRemove = Lists.newArrayList(); for (TreeNode treeNode : ret) { boolean hasDiff = false; boolean hasNonEmptySubMatch = false; Set<Match> alreadyProcessedSubMatch = newHashSet(); // Manage non-containment changes for (Diff diff : filter(match.getDifferences(), and(filter, not(or(containmentReferenceChange(), resourceAttachmentChange()))))) { if (diff.getPrimeRefining() != null && !extensionDiffProcessed.contains(diff)) { continue; } hasDiff = true; TreeNode buildSubTree = buildSubTree(diff); if (buildSubTree != null) { treeNode.getChildren().add(buildSubTree); } } // Manage sub matches for (Match subMatch : match.getSubmatches()) { if (!alreadyProcessedSubMatch.contains(subMatch)) { List<TreeNode> buildSubTree = buildSubTree(match, subMatch); if (!buildSubTree.isEmpty()) { hasNonEmptySubMatch = true; treeNode.getChildren().addAll(buildSubTree); } } } // Manage move changes for (Diff diff : filter(match.getDifferences(), and(filter, containmentMoveReferenceChange()))) { if (!containsChildrenWithDataEqualsToDiff(treeNode, diff)) { TreeNode buildSubTree = buildSubTree(diff); if (buildSubTree != null) { hasDiff = true; treeNode.getChildren().add(buildSubTree); List<TreeNode> matchSubTree = buildSubTree((Match) null, comparison.getMatch(((ReferenceChange) diff).getValue())); for (TreeNode matchSubTreeNode : matchSubTree) { buildSubTree.getChildren().addAll(matchSubTreeNode.getChildren()); } } } } if (!(isContainment || hasDiff || hasNonEmptySubMatch || filter.equals(Predicates.alwaysTrue()))) { toRemove.add(treeNode); } else if (!isContainment && isMatchWithOnlyResourceAttachmentChanges(match)) { toRemove.add(treeNode); } else { for (IDifferenceGroupExtender ext : registry.getExtenders()) { if (ext.handle(treeNode)) { ext.addChildren(treeNode); } } } } ret.removeAll(toRemove); return ret; } /** * Build the sub tree for the given {@link Diff}. * * @param diff * the given diff. * @return the sub tree of the given diff. */ protected TreeNode buildSubTree(Diff diff) { TreeNode treeNode = wrap(diff); for (IDifferenceGroupExtender ext : registry.getExtenders()) { if (ext.handle(treeNode)) { ext.addChildren(treeNode); } } return treeNode; } /** * Check if the resource of the given object as the same uri as the given uri. * * @param eObject * the given object. * @param uri * the given uri. * @return true if the resource of the given object as the same uri as the given uri, false otherwise. */ protected boolean isRootOfResourceURI(EObject eObject, String uri) { return eObject != null && uri != null && eObject.eResource() != null && uri.equals(eObject.eResource().getURI().toString()); } /** * This can be used to check whether a givan diff is a resource attachment change. * * @return The created predicate. */ protected static Predicate<? super Diff> resourceAttachmentChange() { return Predicates.instanceOf(ResourceAttachmentChange.class); } /** * Checks, for the given Match, if the container of the left part is different from the container of the * right part (Case of a match of a move remote diff). * * @param match * the given Match. * @return true, if the container of the left part is different from the container of the right part, * false otherwise. */ public boolean matchWithLeftAndRightInDifferentContainer(Match match) { EObject left = match.getLeft(); EObject right = match.getRight(); if (left != null && right != null) { return comparison.getMatch(left.eContainer()) != comparison.getMatch(right.eContainer()); } return false; } /** * Checks if the given TreeNode children contain the given diff. * * @param treeNode * the given TreeNode. * @param diff * the given diff. * @return true, if the given TreeNode children contain the given diff, false otherwise. */ protected boolean containsChildrenWithDataEqualsToDiff(TreeNode treeNode, Diff diff) { for (TreeNode child : treeNode.getChildren()) { if (diff == child.getData()) { return true; } } return false; } /** * Predicate to know if the given match contains containment refernce change according to the filter of * the group. * * @param subMatch * the given Match. * @return a predicate to know if the given match contains containment refernce change according to the * filter of the group. */ @SuppressWarnings("unchecked") protected Predicate<Diff> containmentReferenceForMatch(Match subMatch) { return and(filter, containmentReferenceChange(), or(valueIs(subMatch.getLeft()), valueIs(subMatch.getRight()), valueIs(subMatch.getOrigin()))); } /** * Creates a TreeNode form the given EObject. * * @param data * the given EObject. * @return a TreeNode. */ protected TreeNode wrap(EObject data) { TreeNode treeNode = TreeFactory.eINSTANCE.createTreeNode(); treeNode.setData(data); treeNode.eAdapters().add(this); return treeNode; } /** * @param match * @return */ private boolean isMatchWithOnlyResourceAttachmentChanges(Match match) { boolean ret = false; Iterable<Diff> allDifferences = match.getAllDifferences(); if (Iterables.isEmpty(allDifferences)) { ret = false; } else if (Iterables.all(allDifferences, instanceOf(ResourceAttachmentChange.class))) { if (match.getSubmatches() == null || match.getSubmatches().isEmpty()) { ret = true; } } return ret; } /** * {@inheritDoc} * * @see org.eclipse.emf.compare.rcp.ui.internal.structuremergeviewer.groups.IDifferenceGroup#dispose() */ public void dispose() { if (children != null) { unregisterCrossReferenceAdapter(children); children = null; } } }