/** * <copyright> * * Copyright (c) 2010-2016 Thales Global Services S.A.S. * 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: * Thales Global Services S.A.S. - initial API and implementation * * </copyright> */ package org.eclipse.emf.diffmerge.ui.viewers; import java.util.ArrayList; import java.util.Collection; import java.util.List; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.emf.common.util.ECollections; import org.eclipse.emf.common.util.EMap; import org.eclipse.emf.diffmerge.api.IMatch; import org.eclipse.emf.diffmerge.api.Role; import org.eclipse.emf.diffmerge.api.diff.IDifference; import org.eclipse.emf.diffmerge.api.diff.IElementRelativeDifference; import org.eclipse.emf.diffmerge.api.diff.IMergeableDifference; import org.eclipse.emf.diffmerge.api.diff.IPresenceDifference; import org.eclipse.emf.diffmerge.api.diff.IReferenceValuePresence; import org.eclipse.emf.diffmerge.impl.helpers.AbstractExpensiveOperation; import org.eclipse.emf.diffmerge.ui.EMFDiffMergeUIPlugin.DifferenceColorKind; import org.eclipse.emf.diffmerge.ui.EMFDiffMergeUIPlugin.ImageID; import org.eclipse.emf.diffmerge.ui.Messages; import org.eclipse.emf.diffmerge.ui.util.DelegatingLabelProvider; import org.eclipse.emf.diffmerge.ui.util.DiffMergeLabelProvider; import org.eclipse.emf.diffmerge.util.structures.FHashMap; import org.eclipse.emf.diffmerge.util.structures.FHashSet; import org.eclipse.jface.viewers.ILabelProvider; import org.eclipse.jface.viewers.ISelection; import org.eclipse.jface.viewers.ITreeContentProvider; import org.eclipse.jface.viewers.TreeViewer; import org.eclipse.jface.viewers.Viewer; import org.eclipse.jface.viewers.ViewerSorter; import org.eclipse.swt.SWT; import org.eclipse.swt.custom.SashForm; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Group; import org.eclipse.swt.widgets.Shell; /** * A viewer for showing the impact of a merge. * Input: MergeImpactViewer.Input ; Elements: IDifference. * @author Olivier Constant */ public class MergeImpactViewer extends Viewer { /** * The type of accepted inputs. */ public static class ImpactInput { /** Whether modifications occur on the left-hand side or the right-hand side */ private final boolean _sideIsLeft; /** The non-null comparison context */ private final EMFDiffNode _context_p; /** The non-null, unmodifiable, potentially empty set of differences to merge */ private final Collection<IDifference> _toMerge; /** Whether this input has processed its internal data */ private boolean _isComputed; /** The non-null map from matches to differences to merge implicitly */ protected final EMap<IMatch, Collection<IDifference>> _implicitImpact; /** The non-null map from matches to differences to merge explicitly */ protected final EMap<IMatch, Collection<IDifference>> _explicitImpact; /** * The operation that computes the internal data of the input */ protected class ComputationOperation extends AbstractExpensiveOperation { /** * @see AbstractExpensiveOperation#getOperationName() */ public String getOperationName() { return Messages.MergeImpactViewer_ComputationName; } /** * @see AbstractExpensiveOperation#run() */ public IStatus run() { Collection<IDifference> toMerge = getDifferencesToMerge(); for (IDifference anyToMerge : toMerge) { if (anyToMerge instanceof IMergeableDifference) { IMergeableDifference difference = (IMergeableDifference)anyToMerge; registerDifference(difference, true); for (IDifference required : difference.getRequiresDependencies(getDestination())) { if (!required.isMerged() && !toMerge.contains(required)) registerDifference(required, false); } for (IDifference implied : difference.getImpliesDependencies(getDestination())) { if (!implied.isMerged() && !toMerge.contains(implied)) registerDifference(implied, false); } checkProgress(); getMonitor().worked(1); } } return Status.OK_STATUS; } /** * @see AbstractExpensiveOperation#getWorkAmount() */ @Override protected int getWorkAmount() { return getDifferencesToMerge().size(); } /** * Register the given difference * @param difference_p a non-null difference * @param explicit_p whether its merge is explicit or implicit */ protected void registerDifference(IDifference difference_p, boolean explicit_p) { // Getting relevant match IMatch match = null; if (difference_p instanceof IElementRelativeDifference) { IElementRelativeDifference elementDifference = (IElementRelativeDifference)difference_p; if (elementDifference instanceof IReferenceValuePresence) { IReferenceValuePresence presence = (IReferenceValuePresence)elementDifference; if (presence.getFeature() != null && presence.getFeature().isContainment() && !presence.isOrder()) match = presence.getValueMatch(); // Move } if (match == null) match = elementDifference.getElementMatch(); } // Registering difference on match if (match != null) { EMap<IMatch, Collection<IDifference>> map = explicit_p? _explicitImpact: _implicitImpact; Collection<IDifference> differences = map.get(match); if (differences == null) { differences = new FHashSet<IDifference>(); map.put(match, differences); } differences.add(difference_p); } } } /** * Constructor * @param toMerge_p the non-null set of differences to merge * @param toLeft_p whether modifications occur on the left-hand side or the right-hand side * @param context_p the non-null comparison context */ public ImpactInput(Collection<? extends IDifference> toMerge_p, boolean toLeft_p, EMFDiffNode context_p) { _toMerge = new FHashSet<IDifference>(toMerge_p, null); _sideIsLeft = toLeft_p; _context_p = context_p; _isComputed = false; _explicitImpact = new FHashMap<IMatch, Collection<IDifference>>(); _implicitImpact = new FHashMap<IMatch, Collection<IDifference>>(); } /** * Process the internal data of this input * @param monitor_p an optional monitor */ public void compute(IProgressMonitor monitor_p) { ComputationOperation op = new ComputationOperation(); op.run(monitor_p); _isComputed = true; } /** * Return the comparison context for this input * @return a non-null object */ public EMFDiffNode getContext() { return _context_p; } /** * Return the destination of the merge * @return a non-null role which is TARGET or REFERENCE */ public Role getDestination() { return _context_p.getRoleForSide(_sideIsLeft); } /** * Return the set of differences to merge * @return a non-null, unmodifiable, potentially empty set */ public Collection<IDifference> getDifferencesToMerge() { return _toMerge; } /** * Return the map from matches to related differences to merge * @param explicit_p whether the differences are those to merge explicitly or implicitly * @return a non-null, potentially empty, unmodifiable map */ public EMap<IMatch, Collection<IDifference>> getImpact(boolean explicit_p) { return ECollections.unmodifiableEMap(explicit_p? _explicitImpact: _implicitImpact); } /** * Return whether this input has processed its internal data */ public boolean isComputed() { return _isComputed; } /** * Return whether modifications occur on the left-hand side or the right-hand side */ public boolean isOnTheLeft() { return _sideIsLeft; } /** * Return whether the comparison is 3-way */ public boolean isThreeWay() { return _context_p.getActualComparison().isThreeWay(); } } /** The current input (initially null) */ private ImpactInput _input; /** The non-null resource manager for SWT resources in this viewer */ protected final ComparisonResourceManager _resourceManager; /** The main control of the viewer */ protected SashForm _control; /** The upper tree viewer */ protected TreeViewer _upperViewer; /** The lower tree viewer */ protected TreeViewer _lowerViewer; /** * Constructor * @param parent_p a non-null composite * @param resourceManager_p a non-null resource manager for SWT resources */ public MergeImpactViewer(Composite parent_p, ComparisonResourceManager resourceManager_p) { super(); _input = null; _resourceManager = resourceManager_p; createControls(parent_p); } /** * Create the controls for this viewer and return the main control * @param parent_p a non-null composite */ protected void createControls(Composite parent_p) { _control = new SashForm(parent_p, SWT.VERTICAL); _control.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); // Upper section Group upperWrapper = new Group(_control, SWT.NONE); upperWrapper.setText(Messages.MergeImpactViewer_Required); upperWrapper.setLayout(new GridLayout()); _upperViewer = new TreeViewer(upperWrapper); _upperViewer.getControl().setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); // Lower section Group lowerWrapper = new Group(_control, SWT.NONE); lowerWrapper.setText(Messages.MergeImpactViewer_Implied); lowerWrapper.setLayout(new GridLayout()); _lowerViewer = new TreeViewer(lowerWrapper); _lowerViewer.getControl().setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); setupViewers(); // _control.setWeights(new int[] {2, 1}); } /** * @see org.eclipse.jface.viewers.Viewer#getControl() */ @Override public Control getControl() { return _control; } /** * @see org.eclipse.jface.viewers.Viewer#getInput() */ @Override public ImpactInput getInput() { return _input; } /** * @see org.eclipse.jface.viewers.Viewer#getSelection() */ @Override public ISelection getSelection() { return null; } /** * Return the shell of this viewer * @return a non-null shell */ protected Shell getShell() { return getControl().getShell(); } /** * @see org.eclipse.jface.viewers.Viewer#inputChanged(java.lang.Object, java.lang.Object) */ @Override protected void inputChanged(Object input_p, Object oldInput_p) { _upperViewer.setInput(input_p); _lowerViewer.setInput(input_p); _upperViewer.expandAll(); _lowerViewer.expandAll(); } /** * Return whether merging the given difference would conflict with non-merged * opposite changes * @param difference_p a non-null difference */ protected boolean isConflicting(IDifference difference_p) { boolean result = false; if (getInput() != null && getInput().isThreeWay()) { if (difference_p instanceof IPresenceDifference) { IPresenceDifference presence = (IPresenceDifference)difference_p; result = !presence.isAlignedWithAncestor() && presence.getPresenceRole() == getInput().getDestination(); } } return result; } /** * Return whether the given match is concerned with conflicting differences * @param match_p a non-null match */ protected boolean isConflicting(IMatch match_p) { Object[] upperChildren = ((ITreeContentProvider)_upperViewer.getContentProvider()).getChildren(match_p); for (Object child : upperChildren) { if (child instanceof IDifference && isConflicting((IDifference)child)) return true; } Object[] lowerChildren = ((ITreeContentProvider)_lowerViewer.getContentProvider()).getChildren(match_p); for (Object child : lowerChildren) { if (child instanceof IDifference && isConflicting((IDifference)child)) return true; } return false; } /** * @see org.eclipse.jface.viewers.Viewer#refresh() */ @Override public void refresh() { _upperViewer.refresh(); _lowerViewer.refresh(); } /** * Set the "base" label provider for representing model elements * @param labelProvider_p a potentially null label provider, where null stands for default */ public void setDelegateLabelProvider(ILabelProvider labelProvider_p) { ((DelegatingLabelProvider)_upperViewer.getLabelProvider()).setDelegate(labelProvider_p); // Same on both viewers } /** * @see org.eclipse.jface.viewers.Viewer#setInput(java.lang.Object) */ @Override public void setInput(Object input_p) { if (input_p instanceof ImpactInput) { Object oldInput = getInput(); _input = (ImpactInput)input_p; if (!_input.isComputed()) _input.compute(null); inputChanged(_input, oldInput); } } /** * @see org.eclipse.jface.viewers.Viewer#setSelection(org.eclipse.jface.viewers.ISelection, boolean) */ @Override public void setSelection(ISelection selection_p, boolean reveal_p) { // Nothing } /** * Setup the viewers that compose this viewer */ protected void setupViewers() { // Content provider _upperViewer.setContentProvider(new MergeImpactContentProvider(true)); _lowerViewer.setContentProvider(new MergeImpactContentProvider(false)); // Label provider MergeImpactLabelProvider lp = new MergeImpactLabelProvider(); _upperViewer.setLabelProvider(lp); _lowerViewer.setLabelProvider(lp); // Sorter ViewerSorter sorter = new ViewerSorter(); _upperViewer.setSorter(sorter); _lowerViewer.setSorter(sorter); } /** * The content provider for the tree viewers */ protected static class MergeImpactContentProvider implements ITreeContentProvider { /** Whether impact is on explicit or implicit dependencies */ private final boolean _isOnExplicit; /** The current input */ private ImpactInput _input; /** * Constructor * @param explicit_p whether impact is on explicit or implicit dependencies */ public MergeImpactContentProvider(boolean explicit_p) { _isOnExplicit = explicit_p; _input = null; } /** * @see ITreeContentProvider#dispose() */ public void dispose() { _input = null; } /** * @see ITreeContentProvider#getChildren(Object) */ public Object[] getChildren(Object parentElement_p) { Object[] result = new Object[] {}; if (parentElement_p instanceof IMatch) { Collection<IDifference> subDifferences = getImpact().get(parentElement_p); if (subDifferences != null) { List<IDifference> filteredDifferences = new ArrayList<IDifference>( subDifferences.size()); for (IDifference difference : subDifferences) { if (!isMoveOfAdded(difference)) filteredDifferences.add(difference); } result = filteredDifferences.toArray(); } } return result; } /** * Return whether the given difference represents the move of an element * which has also been added * @param difference_p a non-null difference */ private boolean isMoveOfAdded(IDifference difference_p) { boolean result = false; if (difference_p instanceof IReferenceValuePresence) { IReferenceValuePresence valuePresence = (IReferenceValuePresence)difference_p; if (valuePresence.getFeature() != null && valuePresence.getFeature().isContainment()) { IMatch valueMatch = valuePresence.getValueMatch(); result = valueMatch != null && valueMatch.getElementPresenceDifference() != null; } } return result; } /** * @see ITreeContentProvider#getElements(Object) */ public Object[] getElements(Object inputElement_p) { Object[] result; if (inputElement_p instanceof ImpactInput) result = ((ImpactInput)inputElement_p).getImpact(_isOnExplicit).keySet().toArray(); else result = new Object[] {}; return result; } /** * Return the relevant impact map * @return a non-null impact map */ private EMap<IMatch, Collection<IDifference>> getImpact() { EMap<IMatch, Collection<IDifference>> result = ECollections.emptyEMap(); if (_input != null) result = _input.getImpact(_isOnExplicit); return result; } /** * @see ITreeContentProvider#getParent(Object) */ public Object getParent(Object element_p) { return null; // Disables setSelection() } /** * @see ITreeContentProvider#hasChildren(Object) */ public boolean hasChildren(Object element_p) { return getChildren(element_p).length > 0; } /** * @see ITreeContentProvider#inputChanged(Viewer, Object, Object) */ public void inputChanged(Viewer viewer_p, Object oldInput_p, Object newInput_p) { if (newInput_p instanceof ImpactInput) _input = (ImpactInput)newInput_p; } } /** * The label provider for the tree viewers */ protected class MergeImpactLabelProvider extends DelegatingLabelProvider { /** * Constructor */ public MergeImpactLabelProvider() { super(DiffMergeLabelProvider.getInstance()); } /** * @see org.eclipse.emf.diffmerge.ui.util.DelegatingLabelProvider#getDelegate() */ @Override public DiffMergeLabelProvider getDelegate() { return (DiffMergeLabelProvider)super.getDelegate(); } /** * @see org.eclipse.jface.viewers.IColorProvider#getForeground(java.lang.Object) */ @Override public Color getForeground(Object element_p) { DifferenceColorKind result; if (element_p instanceof IDifference && isConflicting((IDifference)element_p)) result = DifferenceColorKind.CONFLICT; else result = getInput().isOnTheLeft()? DifferenceColorKind.LEFT: DifferenceColorKind.RIGHT; return getInput().getContext().getDifferenceColor(result); } /** * @see org.eclipse.emf.diffmerge.ui.util.DelegatingLabelProvider#getImage(java.lang.Object) */ @Override public Image getImage(Object element_p) { Image result = null; if (element_p instanceof IMatch) { IMatch match = (IMatch)element_p; result = getDelegate().getMatchImage(match, getInput().getDestination()); if (result != null && isConflicting(match)) result = _resourceManager.getOverlayVersion(result, ImageID.CONFLICT_STAT); } else if (element_p instanceof IDifference) { IDifference difference = (IDifference)element_p; Role destination = getInput().getDestination(); result = getDelegate().getDifferenceImage(difference, destination); if (isConflicting(difference)) { ImageID overlay = getInput().isOnTheLeft()? ImageID.OUT_STAT: ImageID.INC_STAT; result = _resourceManager.getOverlayVersion(result, overlay); } } return result; } /** * @see org.eclipse.emf.diffmerge.ui.util.DelegatingLabelProvider#getText(java.lang.Object) */ @Override public String getText(Object element_p) { String result = null; if (element_p instanceof IMatch) { IMatch match = (IMatch)element_p; result = getDelegate().getMatchText(match, getInput().getDestination(), null); } else if (element_p instanceof IDifference) { Role destination = getInput().getDestination(); result = getDelegate().getDifferenceText((IDifference)element_p, destination, null); } else { result = getDelegate().getText(element_p); } return result; } } }