[513931] Fix Deadlock in StructureMergeViewer
[EMFCompare2.git] / plugins / org.eclipse.emf.compare.ide.ui / src / org / eclipse / emf / compare / ide / ui / internal / structuremergeviewer / EMFCompareStructureMergeViewer.java
blob6a90ba46bb96a13cc481c673eb6e2ca115b90a9c
1 /*******************************************************************************
2 * Copyright (c) 2013, 2017 Obeo and others.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the Eclipse Public License v1.0
5 * which accompanies this distribution, and is available at
6 * http://www.eclipse.org/legal/epl-v10.html
7 *
8 * Contributors:
9 * Obeo - initial API and implementation
10 * Michael Borkowski - bug 467191
11 * Philip Langer - bug 462884, 516576
12 * Stefan Dirix - bugs 473985, 474030
13 * Martin Fleck - bug 497066, 483798, 514767, 514415
14 * Alexandra Buzila - bug 513931
15 *******************************************************************************/
16 package org.eclipse.emf.compare.ide.ui.internal.structuremergeviewer;
18 import static com.google.common.base.Predicates.instanceOf;
19 import static com.google.common.collect.Iterables.any;
20 import static org.eclipse.emf.compare.ide.ui.internal.structuremergeviewer.EMFCompareStructureMergeViewerContentProvider.CallbackType.IN_UI_ASYNC;
21 import static org.eclipse.emf.compare.ide.ui.internal.structuremergeviewer.EMFCompareStructureMergeViewerContentProvider.CallbackType.IN_UI_SYNC;
23 import com.google.common.base.Function;
24 import com.google.common.base.Objects;
25 import com.google.common.base.Predicate;
26 import com.google.common.base.Throwables;
27 import com.google.common.collect.Iterables;
28 import com.google.common.collect.Iterators;
29 import com.google.common.collect.Lists;
30 import com.google.common.collect.Maps;
31 import com.google.common.eventbus.Subscribe;
33 import java.util.ArrayList;
34 import java.util.Collection;
35 import java.util.Collections;
36 import java.util.EnumSet;
37 import java.util.EventObject;
38 import java.util.Iterator;
39 import java.util.List;
40 import java.util.Map;
41 import java.util.Set;
43 import org.eclipse.compare.CompareUI;
44 import org.eclipse.compare.CompareViewerPane;
45 import org.eclipse.compare.CompareViewerSwitchingPane;
46 import org.eclipse.compare.ICompareInputLabelProvider;
47 import org.eclipse.compare.INavigatable;
48 import org.eclipse.compare.ITypedElement;
49 import org.eclipse.compare.ResourceNode;
50 import org.eclipse.compare.structuremergeviewer.DiffNode;
51 import org.eclipse.compare.structuremergeviewer.ICompareInput;
52 import org.eclipse.compare.structuremergeviewer.ICompareInputChangeListener;
53 import org.eclipse.core.resources.IStorage;
54 import org.eclipse.core.resources.ResourceAttributes;
55 import org.eclipse.core.runtime.Assert;
56 import org.eclipse.core.runtime.IProgressMonitor;
57 import org.eclipse.core.runtime.IStatus;
58 import org.eclipse.core.runtime.NullProgressMonitor;
59 import org.eclipse.core.runtime.OperationCanceledException;
60 import org.eclipse.core.runtime.Status;
61 import org.eclipse.core.runtime.SubMonitor;
62 import org.eclipse.core.runtime.jobs.Job;
63 import org.eclipse.emf.common.command.Command;
64 import org.eclipse.emf.common.command.CommandStack;
65 import org.eclipse.emf.common.command.CommandStackListener;
66 import org.eclipse.emf.common.notify.Adapter;
67 import org.eclipse.emf.common.notify.Notifier;
68 import org.eclipse.emf.common.ui.CommonUIPlugin;
69 import org.eclipse.emf.common.util.BasicDiagnostic;
70 import org.eclipse.emf.common.util.BasicMonitor;
71 import org.eclipse.emf.common.util.Diagnostic;
72 import org.eclipse.emf.compare.Comparison;
73 import org.eclipse.emf.compare.ConflictKind;
74 import org.eclipse.emf.compare.Diff;
75 import org.eclipse.emf.compare.DifferenceState;
76 import org.eclipse.emf.compare.EMFCompare;
77 import org.eclipse.emf.compare.EMFCompare.Builder;
78 import org.eclipse.emf.compare.Match;
79 import org.eclipse.emf.compare.MatchResource;
80 import org.eclipse.emf.compare.adapterfactory.context.IContextTester;
81 import org.eclipse.emf.compare.command.ICompareCopyCommand;
82 import org.eclipse.emf.compare.domain.ICompareEditingDomain;
83 import org.eclipse.emf.compare.domain.impl.EMFCompareEditingDomain;
84 import org.eclipse.emf.compare.ide.internal.utils.DisposableResourceSet;
85 import org.eclipse.emf.compare.ide.internal.utils.NotLoadingResourceSet;
86 import org.eclipse.emf.compare.ide.ui.internal.EMFCompareIDEUIMessages;
87 import org.eclipse.emf.compare.ide.ui.internal.EMFCompareIDEUIPlugin;
88 import org.eclipse.emf.compare.ide.ui.internal.configuration.EMFCompareConfiguration;
89 import org.eclipse.emf.compare.ide.ui.internal.contentmergeviewer.MirrorUtil;
90 import org.eclipse.emf.compare.ide.ui.internal.contentmergeviewer.label.NoDifferencesCompareInput;
91 import org.eclipse.emf.compare.ide.ui.internal.contentmergeviewer.label.NoSelectedItemCompareInput;
92 import org.eclipse.emf.compare.ide.ui.internal.contentmergeviewer.label.NoVisibleItemCompareInput;
93 import org.eclipse.emf.compare.ide.ui.internal.contentmergeviewer.label.OnlyPseudoConflictsCompareInput;
94 import org.eclipse.emf.compare.ide.ui.internal.contentmergeviewer.util.EMFCompareColor;
95 import org.eclipse.emf.compare.ide.ui.internal.contentmergeviewer.util.RedoAction;
96 import org.eclipse.emf.compare.ide.ui.internal.contentmergeviewer.util.UndoAction;
97 import org.eclipse.emf.compare.ide.ui.internal.editor.ComparisonScopeInput;
98 import org.eclipse.emf.compare.ide.ui.internal.logical.ComparisonScopeBuilder;
99 import org.eclipse.emf.compare.ide.ui.internal.logical.EmptyComparisonScope;
100 import org.eclipse.emf.compare.ide.ui.internal.logical.StreamAccessorStorage;
101 import org.eclipse.emf.compare.ide.ui.internal.preferences.EMFCompareUIPreferences;
102 import org.eclipse.emf.compare.ide.ui.internal.progress.JobProgressInfoComposite;
103 import org.eclipse.emf.compare.ide.ui.internal.progress.JobProgressMonitorWrapper;
104 import org.eclipse.emf.compare.ide.ui.internal.structuremergeviewer.EMFCompareStructureMergeViewerContentProvider.FetchListener;
105 import org.eclipse.emf.compare.ide.ui.internal.structuremergeviewer.actions.MergeAction;
106 import org.eclipse.emf.compare.ide.ui.internal.structuremergeviewer.actions.MergeContainedNonConflictingAction;
107 import org.eclipse.emf.compare.ide.ui.internal.structuremergeviewer.provider.TreeCompareInputAdapterFactory;
108 import org.eclipse.emf.compare.ide.ui.internal.structuremergeviewer.provider.TreeNodeCompareInput;
109 import org.eclipse.emf.compare.ide.ui.internal.util.CompareHandlerService;
110 import org.eclipse.emf.compare.ide.ui.internal.util.JFaceUtil;
111 import org.eclipse.emf.compare.ide.ui.internal.util.PlatformElementUtil;
112 import org.eclipse.emf.compare.internal.merge.MergeDataImpl;
113 import org.eclipse.emf.compare.internal.merge.MergeMode;
114 import org.eclipse.emf.compare.merge.AbstractMerger;
115 import org.eclipse.emf.compare.merge.CachingDiffRelationshipComputer;
116 import org.eclipse.emf.compare.merge.IMergeOptionAware;
117 import org.eclipse.emf.compare.merge.IMerger;
118 import org.eclipse.emf.compare.rcp.EMFCompareRCPPlugin;
119 import org.eclipse.emf.compare.rcp.internal.extension.impl.EMFCompareBuilderConfigurator;
120 import org.eclipse.emf.compare.rcp.ui.internal.configuration.ICompareEditingDomainChange;
121 import org.eclipse.emf.compare.rcp.ui.internal.configuration.IMergePreviewModeChange;
122 import org.eclipse.emf.compare.rcp.ui.internal.configuration.SideLabelProvider;
123 import org.eclipse.emf.compare.rcp.ui.internal.mergeviewer.IColorChangeEvent;
124 import org.eclipse.emf.compare.rcp.ui.internal.structuremergeviewer.filters.StructureMergeViewerFilter;
125 import org.eclipse.emf.compare.rcp.ui.internal.structuremergeviewer.filters.impl.CascadingDifferencesFilter;
126 import org.eclipse.emf.compare.rcp.ui.internal.structuremergeviewer.groups.StructureMergeViewerGrouper;
127 import org.eclipse.emf.compare.rcp.ui.internal.structuremergeviewer.groups.provider.TreeItemProviderAdapterFactorySpec;
128 import org.eclipse.emf.compare.rcp.ui.internal.structuremergeviewer.match.MatchOfContainmentReferenceChangeProcessor;
129 import org.eclipse.emf.compare.rcp.ui.internal.util.SWTUtil;
130 import org.eclipse.emf.compare.rcp.ui.structuremergeviewer.filters.IDifferenceFilterChange;
131 import org.eclipse.emf.compare.rcp.ui.structuremergeviewer.groups.IDifferenceGroup;
132 import org.eclipse.emf.compare.rcp.ui.structuremergeviewer.groups.IDifferenceGroupProvider;
133 import org.eclipse.emf.compare.rcp.ui.structuremergeviewer.groups.IDifferenceGroupProviderChange;
134 import org.eclipse.emf.compare.scope.IComparisonScope;
135 import org.eclipse.emf.compare.utils.EMFComparePredicates;
136 import org.eclipse.emf.compare.utils.IDiagnosable;
137 import org.eclipse.emf.ecore.EObject;
138 import org.eclipse.emf.ecore.resource.Resource;
139 import org.eclipse.emf.ecore.resource.ResourceSet;
140 import org.eclipse.emf.ecore.util.EcoreUtil;
141 import org.eclipse.emf.edit.provider.ComposedAdapterFactory;
142 import org.eclipse.emf.edit.provider.IDisposable;
143 import org.eclipse.emf.edit.provider.ReflectiveItemProviderAdapterFactory;
144 import org.eclipse.emf.edit.provider.resource.ResourceItemProviderAdapterFactory;
145 import org.eclipse.emf.edit.tree.TreeFactory;
146 import org.eclipse.emf.edit.tree.TreeNode;
147 import org.eclipse.jface.action.IMenuListener;
148 import org.eclipse.jface.action.IMenuManager;
149 import org.eclipse.jface.action.MenuManager;
150 import org.eclipse.jface.preference.IPreferenceStore;
151 import org.eclipse.jface.util.IPropertyChangeListener;
152 import org.eclipse.jface.util.PropertyChangeEvent;
153 import org.eclipse.jface.viewers.AbstractTreeViewer;
154 import org.eclipse.jface.viewers.DelegatingStyledCellLabelProvider;
155 import org.eclipse.jface.viewers.ISelection;
156 import org.eclipse.jface.viewers.ISelectionChangedListener;
157 import org.eclipse.jface.viewers.IStructuredSelection;
158 import org.eclipse.jface.viewers.ITreeViewerListener;
159 import org.eclipse.jface.viewers.SelectionChangedEvent;
160 import org.eclipse.jface.viewers.StructuredSelection;
161 import org.eclipse.jface.viewers.TreeExpansionEvent;
162 import org.eclipse.jface.viewers.TreeViewer;
163 import org.eclipse.swt.SWT;
164 import org.eclipse.swt.custom.CTabFolder;
165 import org.eclipse.swt.custom.CTabItem;
166 import org.eclipse.swt.events.ControlAdapter;
167 import org.eclipse.swt.events.ControlEvent;
168 import org.eclipse.swt.events.DisposeEvent;
169 import org.eclipse.swt.events.FocusAdapter;
170 import org.eclipse.swt.events.FocusEvent;
171 import org.eclipse.swt.graphics.Color;
172 import org.eclipse.swt.graphics.GC;
173 import org.eclipse.swt.graphics.Point;
174 import org.eclipse.swt.graphics.Rectangle;
175 import org.eclipse.swt.layout.FillLayout;
176 import org.eclipse.swt.layout.GridData;
177 import org.eclipse.swt.layout.GridLayout;
178 import org.eclipse.swt.widgets.Composite;
179 import org.eclipse.swt.widgets.Control;
180 import org.eclipse.swt.widgets.Display;
181 import org.eclipse.swt.widgets.Event;
182 import org.eclipse.swt.widgets.Listener;
183 import org.eclipse.swt.widgets.Menu;
184 import org.eclipse.swt.widgets.Tree;
185 import org.eclipse.swt.widgets.TreeItem;
186 import org.eclipse.team.internal.ui.mapping.ResourceDiffCompareInput;
187 import org.eclipse.ui.PlatformUI;
188 import org.eclipse.ui.actions.ActionFactory;
189 import org.eclipse.ui.progress.PendingUpdateAdapter;
190 import org.eclipse.ui.themes.ITheme;
191 import org.eclipse.ui.themes.IThemeManager;
194 * Implementation of {@link AbstractStructuredViewerWrapper}.
196 * @author <a href="mailto:axel.richard@obeo.fr">Axel Richard</a>
198 public class EMFCompareStructureMergeViewer extends AbstractStructuredViewerWrapper<CTabFolder, WrappableTreeViewer> implements CommandStackListener {
200 private final class CompareInputChangedJob extends Job {
201 private CompareInputChangedJob(String name) {
202 super(name);
205 @Override
206 public IStatus run(IProgressMonitor monitor) {
207 IProgressMonitor wrapper = new JobProgressMonitorWrapper(monitor, progressInfoItem);
208 SubMonitor subMonitor = SubMonitor.convert(wrapper, EMFCompareIDEUIMessages
209 .getString("EMFCompareStructureMergeViewer.computingModelDifferences"), 100); //$NON-NLS-1$
210 try {
211 compareInputChanged((ICompareInput)getInput(), subMonitor.newChild(100));
212 } catch (final OperationCanceledException e) {
213 return Status.CANCEL_STATUS;
214 } catch (final Exception e) {
215 EMFCompareIDEUIPlugin.getDefault().log(e);
216 } finally {
217 subMonitor.setWorkRemaining(0);
219 return Status.OK_STATUS;
223 private final class TitleBuilderJob extends Job {
225 public TitleBuilderJob() {
226 super("EMF Compare Title Builder"); //$NON-NLS-1$
227 setSystem(true);
230 @Override
231 protected IStatus run(IProgressMonitor monitor) {
232 final String title = new TitleBuilder(getCompareConfiguration()).toString();
233 getContentProvider().runWhenReady(IN_UI_ASYNC, new Runnable() {
234 public void run() {
235 CTabFolder control = getControl();
236 if (!control.isDisposed()) {
237 ((CompareViewerSwitchingPane)control.getParent()).setTitleArgument(title);
241 return Status.OK_STATUS;
245 /** The width of the tree ruler. */
246 private static final int TREE_RULER_WIDTH = 17;
248 private static final Function<TreeNode, Diff> TREE_NODE_AS_DIFF = new Function<TreeNode, Diff>() {
249 public Diff apply(TreeNode input) {
250 if (input.getData() instanceof Diff) {
251 return (Diff)input.getData();
253 return null;
257 /** Preference store holding UI-related settings for this viewer. */
258 protected final IPreferenceStore preferenceStore = EMFCompareIDEUIPlugin.getDefault()
259 .getPreferenceStore();
261 /** The adapter factory. */
262 private ComposedAdapterFactory fAdapterFactory;
264 /** The diff relationship computer. */
265 private CachingDiffRelationshipComputer fDiffRelationshipComputer;
267 /** The tree ruler associated with this viewer. */
268 private EMFCompareDiffTreeRuler treeRuler;
270 private final ICompareInputChangeListener fCompareInputChangeListener;
272 /** The expand/collapse item listener. */
273 private ITreeViewerListener fWrappedTreeListener;
275 /** The tree viewer. */
277 /** The undo action. */
278 private UndoAction undoAction;
280 /** The redo action. */
281 private RedoAction redoAction;
283 /** The compare handler service. */
284 private CompareHandlerService fHandlerService;
287 * When comparing EObjects from a resource, the resource involved doesn't need to be unload by EMF
288 * Compare.
290 private boolean resourceSetShouldBeDisposed;
292 private DependencyData dependencyData;
294 private ISelectionChangedListener selectionChangeListener;
296 /** The current selection. */
297 protected ISelection currentSelection;
299 /** Listener reacting to changes in the {@link #preferenceStore}. */
300 protected IPropertyChangeListener preferenceChangeListener;
302 private final Listener fEraseItemListener;
304 private JobProgressInfoComposite progressInfoItem;
306 private Job inputChangedTask;
308 private final Job titleBuilderJob = new TitleBuilderJob();
310 private CompareToolBar toolBar;
312 private Navigatable navigatable;
314 private EMFCompareColor fColors;
316 private boolean editingDomainNeedsToBeDisposed;
318 private FetchListener toolbarUpdaterContentProviderListener;
320 private boolean cascadingDifferencesFilterEnabled;
322 private IPropertyChangeListener fPreferenceChangeListener;
324 private IPreferenceStore fPreferenceStore;
327 * Constructor.
329 * @param parent
330 * the SWT parent control under which to create the viewer's SWT control.
331 * @param config
332 * a compare configuration the newly created viewer might want to use.
334 public EMFCompareStructureMergeViewer(Composite parent, EMFCompareConfiguration config) {
335 super(parent, config);
337 updateLayout(true, false);
339 StructureMergeViewerFilter structureMergeViewerFilter = getCompareConfiguration()
340 .getStructureMergeViewerFilter();
341 getViewer().addFilter(structureMergeViewerFilter);
343 StructureMergeViewerGrouper structureMergeViewerGrouper = getCompareConfiguration()
344 .getStructureMergeViewerGrouper();
345 structureMergeViewerGrouper.install(getViewer());
347 fCompareInputChangeListener = new ICompareInputChangeListener() {
348 public void compareInputChanged(ICompareInput input) {
349 EMFCompareStructureMergeViewer.this.compareInputChanged(input);
353 setContentProvider(new EMFCompareStructureMergeViewerContentProvider(
354 getCompareConfiguration().getAdapterFactory(), getViewer()));
356 navigatable = new Navigatable(getViewer(), getContentProvider());
358 toolBar = new CompareToolBar(CompareViewerPane.getToolBarManager(parent), structureMergeViewerGrouper,
359 structureMergeViewerFilter, getCompareConfiguration());
360 getViewer().addSelectionChangedListener(toolBar);
362 createContextMenu();
364 selectionChangeListener = new ISelectionChangedListener() {
365 public void selectionChanged(SelectionChangedEvent event) {
366 handleSelectionChangedEvent(event);
369 addSelectionChangedListener(selectionChangeListener);
371 preferenceChangeListener = new IPropertyChangeListener() {
372 public void propertyChange(PropertyChangeEvent event) {
373 EMFCompareStructureMergeViewer.this.handlePreferenceChangedEvent(event);
376 preferenceStore.addPropertyChangeListener(preferenceChangeListener);
378 fWrappedTreeListener = new ITreeViewerListener() {
379 public void treeExpanded(TreeExpansionEvent event) {
380 treeRuler.redraw();
383 public void treeCollapsed(TreeExpansionEvent event) {
384 treeRuler.redraw();
387 getViewer().addTreeListener(fWrappedTreeListener);
389 fEraseItemListener = new Listener() {
390 public void handleEvent(Event event) {
391 handleEraseItemEvent(event);
394 getViewer().getControl().addListener(SWT.EraseItem, fEraseItemListener);
396 fHandlerService = CompareHandlerService.createFor(getCompareConfiguration().getContainer(),
397 getControl().getShell());
399 toolbarUpdaterContentProviderListener = new FetchListener() {
401 @Override
402 public void startFetching() {
403 toolBar.setEnabled(false);
406 @Override
407 public void doneFetching() {
408 toolBar.setEnabled(true);
412 getContentProvider().addFetchingListener(toolbarUpdaterContentProviderListener);
414 setLabelProvider(
415 new DelegatingStyledCellLabelProvider(new EMFCompareStructureMergeViewerLabelProvider(
416 getCompareConfiguration().getAdapterFactory(), this)));
418 undoAction = new UndoAction(getCompareConfiguration().getEditingDomain());
419 redoAction = new RedoAction(getCompareConfiguration().getEditingDomain());
421 editingDomainChange(null, getCompareConfiguration().getEditingDomain());
423 inputChangedTask.setPriority(Job.LONG);
425 config.getEventBus().register(this);
427 final boolean enabled = any(config.getStructureMergeViewerFilter().getSelectedDifferenceFilters(),
428 instanceOf(CascadingDifferencesFilter.class));
429 setCascadingDifferencesFilterEnabled(enabled);
431 fPreferenceChangeListener = new IPropertyChangeListener() {
432 @Override
433 public void propertyChange(PropertyChangeEvent event) {
434 EMFCompareStructureMergeViewer.this.handlePreferenceChangeEvent(event);
438 fPreferenceStore = getCompareConfiguration().getPreferenceStore();
439 if (fPreferenceStore != null) {
440 fPreferenceStore.addPropertyChangeListener(fPreferenceChangeListener);
444 protected void handlePreferenceChangeEvent(PropertyChangeEvent event) {
445 if (MirrorUtil.isMirroredPreference(event.getProperty())) {
446 MirrorUtil.setMirrored(getCompareConfiguration(),
447 Boolean.parseBoolean(event.getNewValue().toString()));
448 // Since the content merge viewer will only be recreated, we simulate a selection change to
449 // re-create the content merge viewer with correct sides
450 ISelection originalSelection = getSelection();
452 setSelection(null);
453 getViewer().handleOpen(null); // parameter not used in super implementation -> null ok
455 setSelection(originalSelection);
456 getViewer().handleOpen(null);
461 * The tool bar must be init after we know the editable state of left and right input.
463 * @see #compareInputChanged(ICompareInput, IProgressMonitor)
465 private void initToolbar(IProgressMonitor monitor) {
466 if (!monitor.isCanceled()) {
467 SWTUtil.safeSyncExec(new Runnable() {
468 public void run() {
469 toolBar.initToolbar(getViewer(), navigatable);
470 toolBar.setEnabled(false);
476 private void enableToolbar(IProgressMonitor monitor) {
477 if (!monitor.isCanceled()) {
478 SWTUtil.safeSyncExec(new Runnable() {
479 public void run() {
480 toolBar.setEnabled(true);
487 * Allow users to merge diffs through context menu.
489 private void createContextMenu() {
490 MenuManager menuMgr = new MenuManager("#PopupMenu"); //$NON-NLS-1$
491 menuMgr.setRemoveAllWhenShown(true);
492 menuMgr.addMenuListener(new IMenuListener() {
493 public void menuAboutToShow(IMenuManager manager) {
494 EMFCompareStructureMergeViewer.this.fillContextMenu(manager);
497 Menu menu = menuMgr.createContextMenu(getViewer().getControl());
498 getViewer().getControl().setMenu(menu);
502 * Fill the context menu with the appropriate actions (ACCEPT/REJECT or LEFT TO RIGHT/RIGHT TO LEFT
503 * depending on the {@link org.eclipse.emf.compare.internal.merge.MergeMode}, and the write access of
504 * models in input).
506 * @param manager
507 * the context menu to fill.
509 private void fillContextMenu(IMenuManager manager) {
510 if (!isOneMergeableItemSelected()) {
511 return;
513 boolean leftEditable = getCompareConfiguration().isLeftEditable();
514 boolean rightEditable = getCompareConfiguration().isRightEditable();
515 final EnumSet<MergeMode> modes;
516 if (rightEditable && leftEditable) {
517 modes = EnumSet.of(MergeMode.RIGHT_TO_LEFT, MergeMode.LEFT_TO_RIGHT);
518 } else {
519 modes = EnumSet.of(MergeMode.ACCEPT, MergeMode.REJECT);
521 if (rightEditable || leftEditable) {
522 for (MergeMode mode : modes) {
523 IMerger.Registry mergerRegistry = EMFCompareRCPPlugin.getDefault().getMergerRegistry();
524 if (isOneDiffSelected()) {
525 MergeAction mergeAction = new MergeAction(getCompareConfiguration(), mergerRegistry, mode,
526 navigatable, (IStructuredSelection)getSelection());
527 manager.add(mergeAction);
528 } else if (isOneMatchOrResourceMatchSelected()) {
529 final Predicate<TreeNode> filterPredicate = new Predicate<TreeNode>() {
530 public boolean apply(TreeNode input) {
531 return input != null
532 && JFaceUtil.isFiltered(getViewer(), input, input.getParent());
535 MergeContainedNonConflictingAction mergeAction = new MergeContainedNonConflictingAction(
536 getCompareConfiguration(), mergerRegistry, mode, navigatable,
537 (IStructuredSelection)getSelection(), filterPredicate);
538 manager.add(mergeAction);
545 * Check if the item selected in this viewer is mergeable; that is, if a {@link Diff}, a {@link Match}, or
546 * {@link MatchResource} is selected.
548 * @return true if the item selected is mergeable, false otherwise.
550 private boolean isOneMergeableItemSelected() {
551 return isOneDiffSelected() || isOneMatchOrResourceMatchSelected();
555 * Specifies whether the a {@link Match} or a {@link MatchResource} is currently selected in this viewer.
557 * @return <code>true</code> if an instance of a {@link Match} or a {@link MatchResource} is selected,
558 * <code>false</code> otherwise.
560 private boolean isOneDiffSelected() {
561 final ISelection selection = getSelection();
562 if (selection instanceof IStructuredSelection && ((IStructuredSelection)selection).size() == 1) {
563 Object element = ((IStructuredSelection)selection).getFirstElement();
564 if (getDataOfTreeNodeOfAdapter(element) instanceof Diff) {
565 return true;
568 return false;
572 * Specifies whether the a {@link Match} or a {@link MatchResource} is currently selected in this viewer.
574 * @return <code>true</code> if an instance of a {@link Match} or a {@link MatchResource} is selected,
575 * <code>false</code> otherwise.
577 private boolean isOneMatchOrResourceMatchSelected() {
578 final ISelection selection = getSelection();
579 if (selection instanceof IStructuredSelection && ((IStructuredSelection)selection).size() == 1) {
580 Object element = ((IStructuredSelection)selection).getFirstElement();
581 if (isMatchOrMatchResource(getDataOfTreeNodeOfAdapter(element))) {
582 return true;
585 return false;
589 * Specifies whether the given {@code eObject} is a {@link Match} or a {@link MatchResource}.
591 * @param eObject
592 * The EObject to check.
593 * @return <code>true</code> if it is an instance a {@link Match} or a {@link MatchResource},
594 * <code>false</code> otherwise.
596 private boolean isMatchOrMatchResource(EObject eObject) {
597 return eObject instanceof Match || eObject instanceof MatchResource;
601 * {@inheritDoc}
603 * @see org.eclipse.emf.compare.ide.ui.internal.structuremergeviewer.AbstractViewerWrapper#preHookCreateControlAndViewer()
605 @Override
606 protected void preHookCreateControlAndViewer() {
607 fAdapterFactory = initAdapterFactory(getCompareConfiguration().getComparison());
608 getCompareConfiguration().setAdapterFactory(fAdapterFactory);
610 fDiffRelationshipComputer = new CachingDiffRelationshipComputer(
611 EMFCompareRCPPlugin.getDefault().getMergerRegistry());
612 getCompareConfiguration().setDiffRelationshipComputer(fDiffRelationshipComputer);
614 inputChangedTask = new CompareInputChangedJob(EMFCompareIDEUIMessages
615 .getString("EMFCompareStructureMergeViewer.computingModelDifferences")); //$NON-NLS-1$
619 * Creates a new adapter factory based on the current compare configuration.
621 * @return adapter factory
623 protected ComposedAdapterFactory initAdapterFactory(Comparison comparison) {
624 Map<Object, Object> context = Maps.newLinkedHashMap();
625 context.put(IContextTester.CTX_COMPARISON, comparison);
627 ComposedAdapterFactory adapterFactory = new ComposedAdapterFactory(
628 EMFCompareRCPPlugin.getDefault().createFilteredAdapterFactoryRegistry(context));
629 adapterFactory.addAdapterFactory(new TreeItemProviderAdapterFactorySpec(
630 getCompareConfiguration().getStructureMergeViewerFilter()));
631 adapterFactory.addAdapterFactory(new ReflectiveItemProviderAdapterFactory());
632 adapterFactory.addAdapterFactory(new ResourceItemProviderAdapterFactory());
633 return adapterFactory;
636 @Subscribe
637 public void colorChanged(
638 @SuppressWarnings("unused") /* necessary for @Subscribe */IColorChangeEvent changeColorEvent) {
639 internalRedraw();
643 * {@inheritDoc}
645 * @see org.eclipse.emf.compare.ide.ui.internal.structuremergeviewer.ViewerWrapper.createControl(
646 * Composite, CompareConfiguration)
648 @Override
649 protected ControlAndViewer<CTabFolder, WrappableTreeViewer> createControlAndViewer(Composite parent) {
650 parent.setLayout(new FillLayout());
651 CTabFolder tabFolder = new CTabFolder(parent, SWT.BOTTOM | SWT.FLAT);
652 tabFolder.setLayout(new FillLayout());
654 // Ensures that this viewer will only display the page's tab
655 // area if there are more than one page
657 tabFolder.addControlListener(new ControlAdapter() {
658 boolean guard = false;
660 @Override
661 public void controlResized(ControlEvent event) {
662 if (!guard) {
663 guard = true;
664 hideTabs();
665 guard = false;
670 updateProblemIndication(Diagnostic.OK_INSTANCE);
672 Composite control = new Composite(tabFolder, SWT.NONE);
673 createItem(0, control);
674 tabFolder.setSelection(0);
676 GridData data = new GridData(SWT.FILL, SWT.FILL, true, true);
677 control.setLayoutData(data);
679 GridLayout layout = new GridLayout(2, false);
680 layout.marginWidth = 0;
681 layout.marginHeight = 0;
682 layout.horizontalSpacing = 0;
683 layout.verticalSpacing = 0;
684 control.setLayout(layout);
686 progressInfoItem = new JobProgressInfoComposite(inputChangedTask, control,
687 SWT.SMOOTH | SWT.HORIZONTAL, SWT.NONE);
688 progressInfoItem.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 2, 1));
689 progressInfoItem.setBackground(Display.getDefault().getSystemColor(SWT.COLOR_LIST_BACKGROUND));
691 final WrappableTreeViewer treeViewer = new WrappableTreeViewer(control,
692 SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL) {
694 * {@inheritDoc}
696 * @see org.eclipse.jface.viewers.TreeViewer#isExpandable(java.lang.Object)
698 @Override
699 public boolean isExpandable(Object element) {
700 if (element instanceof PendingUpdateAdapter) {
701 // Prevents requesting the content provider if the object is a PendingUpdateAdapter
702 return false;
704 if (hasFilters()) {
705 // workaround for 65762
706 return getFilteredChildren(element).length > 0;
708 return super.isExpandable(element);
711 treeViewer.getControl().setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
712 treeViewer.setUseHashlookup(true);
714 treeViewer.getControl().addFocusListener(new FocusAdapter() {
715 @Override
716 public void focusGained(FocusEvent e) {
717 fHandlerService.updatePaneActionHandlers(new Runnable() {
718 public void run() {
719 fHandlerService.setGlobalActionHandler(ActionFactory.UNDO.getId(), undoAction);
720 fHandlerService.setGlobalActionHandler(ActionFactory.REDO.getId(), redoAction);
726 dependencyData = new DependencyData(getCompareConfiguration());
728 tabFolder.setData(CompareUI.COMPARE_VIEWER_TITLE,
729 EMFCompareIDEUIMessages.getString("EMFCompareStructureMergeViewer.title")); //$NON-NLS-1$
731 final ITheme currentTheme = getCurrentTheme();
733 boolean leftIsLocal = getCompareConfiguration().getBooleanProperty("LEFT_IS_LOCAL", false); //$NON-NLS-1$
734 fColors = new EMFCompareColor(control.getDisplay(), leftIsLocal, currentTheme,
735 getCompareConfiguration().getEventBus());
737 treeRuler = new EMFCompareDiffTreeRuler(control, SWT.NONE, treeViewer, dependencyData, fColors);
738 GridData rulerLayoutData = new GridData(SWT.FILL, SWT.FILL, false, true);
739 rulerLayoutData.exclude = true;
740 rulerLayoutData.widthHint = TREE_RULER_WIDTH;
741 rulerLayoutData.minimumWidth = TREE_RULER_WIDTH;
742 treeRuler.setLayoutData(rulerLayoutData);
744 return ControlAndViewer.create(tabFolder, treeViewer);
748 * Determines the current used theme.
750 * @return The currently used theme if available, {@code null} otherwise.
752 private ITheme getCurrentTheme() {
753 if (PlatformUI.isWorkbenchRunning()) {
754 final IThemeManager themeManager = PlatformUI.getWorkbench().getThemeManager();
755 if (themeManager != null) {
756 return themeManager.getCurrentTheme();
759 return null;
763 * {@inheritDoc}
765 * @see org.eclipse.jface.viewers.ContentViewer#getContentProvider()
767 @Override
768 public EMFCompareStructureMergeViewerContentProvider getContentProvider() {
769 return (EMFCompareStructureMergeViewerContentProvider)super.getContentProvider();
772 @Override
773 public DelegatingStyledCellLabelProvider getLabelProvider() {
774 return (DelegatingStyledCellLabelProvider)super.getLabelProvider();
777 private CTabItem createItem(int index, Control control) {
778 CTabItem item = new CTabItem((CTabFolder)control.getParent(), SWT.NONE, index);
779 item.setControl(control);
780 return item;
783 @Subscribe
784 public void handleEditingDomainChange(ICompareEditingDomainChange event) {
785 editingDomainChange(event.getOldValue(), event.getNewValue());
788 private void editingDomainChange(ICompareEditingDomain oldValue, ICompareEditingDomain newValue) {
789 if (newValue != oldValue) {
790 if (oldValue != null) {
791 oldValue.getCommandStack().removeCommandStackListener(this);
794 if (newValue != null) {
795 newValue.getCommandStack().addCommandStackListener(this);
798 undoAction.setEditingDomain(newValue);
799 redoAction.setEditingDomain(newValue);
803 private void refreshTitle() {
804 if (getControl().isDisposed() || !(getControl().getParent() instanceof CompareViewerSwitchingPane)) {
805 return;
808 if (getCompareConfiguration().getComparison() == null) {
809 return;
812 // Schedule with a short delay, because refreshTitle is often called multiple times quickly and this
813 // way, with a short delay, the job is run only once even in that case.
814 titleBuilderJob.schedule(10L);
817 static EObject getDataOfTreeNodeOfAdapter(Object object) {
818 EObject data = null;
819 if (object instanceof Adapter) {
820 Notifier target = ((Adapter)object).getTarget();
821 if (target instanceof TreeNode) {
822 data = ((TreeNode)target).getData();
825 return data;
828 @Subscribe
829 public void mergePreviewModeChange(@SuppressWarnings("unused") IMergePreviewModeChange event) {
830 SWTUtil.safeAsyncExec(new Runnable() {
831 public void run() {
832 updateHighlightRelatedChanges(getSelection());
837 @Subscribe
838 public void handleDifferenceFilterChange(IDifferenceFilterChange event) {
839 final boolean enabled = any(event.getSelectedDifferenceFilters(),
840 instanceOf(CascadingDifferencesFilter.class));
841 setCascadingDifferencesFilterEnabled(enabled);
842 SWTUtil.safeRefresh(this, false, true);
843 getContentProvider().runWhenReady(IN_UI_ASYNC, new Runnable() {
844 public void run() {
845 if (navigatable != null && (navigatable.getViewer().getSelection() == null
846 || navigatable.getViewer().getSelection().isEmpty())) {
847 selectFirstDiffOrDisplayLabelViewer(getCompareConfiguration().getComparison());
854 * Set the state of the cascading filter.
856 * @param enable
857 * true if the filter is enabled, false otherwise.
859 private void setCascadingDifferencesFilterEnabled(boolean enable) {
860 this.cascadingDifferencesFilterEnabled = enable;
861 IMerger.Registry mergerRegistry = EMFCompareRCPPlugin.getDefault().getMergerRegistry();
862 for (IMergeOptionAware merger : Iterables.filter(mergerRegistry.getMergers(null),
863 IMergeOptionAware.class)) {
864 Map<Object, Object> mergeOptions = merger.getMergeOptions();
865 mergeOptions.put(AbstractMerger.SUB_DIFF_AWARE_OPTION, Boolean.valueOf(enable));
870 * Get the state of the cascading filter.
872 * @return true if the filter is enabled, false otherwise.
874 private boolean getCascadingDifferencesFilterEnabled() {
875 return this.cascadingDifferencesFilterEnabled;
878 @Subscribe
879 public void handleDifferenceGroupProviderChange(
880 @SuppressWarnings("unused") IDifferenceGroupProviderChange event) {
881 SWTUtil.safeRefresh(this, false, true);
882 getContentProvider().runWhenReady(IN_UI_ASYNC, new Runnable() {
883 public void run() {
884 selectFirstDiffOrDisplayLabelViewer(getCompareConfiguration().getComparison());
890 * {@inheritDoc}
892 * @see org.eclipse.jface.viewers.Viewer#inputChanged(Object, Object)
894 @Override
895 protected void inputChanged(Object input, Object oldInput) {
896 if (oldInput instanceof ICompareInput) {
897 ICompareInput old = (ICompareInput)oldInput;
898 old.removeCompareInputChangeListener(fCompareInputChangeListener);
899 toolBar.dispose();
901 if (input instanceof ICompareInput) {
902 ICompareInput ci = (ICompareInput)input;
903 ci.addCompareInputChangeListener(fCompareInputChangeListener);
904 compareInputChanged(ci);
909 * {@inheritDoc}
911 * @see org.eclipse.emf.compare.ide.ui.internal.structuremergeviewer.AbstractViewerWrapper#handleDispose(DisposeEvent)
913 @Override
914 protected void handleDispose(DisposeEvent event) {
915 if (fHandlerService != null) {
916 fHandlerService.dispose();
918 getCompareConfiguration().getEventBus().unregister(this);
919 getViewer().removeTreeListener(fWrappedTreeListener);
920 Object input = getInput();
921 if (input instanceof ICompareInput) {
922 ICompareInput ci = (ICompareInput)input;
923 ci.removeCompareInputChangeListener(fCompareInputChangeListener);
925 removeSelectionChangedListener(selectionChangeListener);
926 getViewer().removeSelectionChangedListener(toolBar);
927 getViewer().getTree().removeListener(SWT.EraseItem, fEraseItemListener);
928 if (preferenceChangeListener != null) {
929 preferenceStore.removePropertyChangeListener(preferenceChangeListener);
931 if (editingDomainNeedsToBeDisposed) {
932 ((IDisposable)getCompareConfiguration().getEditingDomain()).dispose();
934 getCompareConfiguration().getStructureMergeViewerGrouper().uninstall(getViewer());
935 compareInputChanged((ICompareInput)null);
936 treeRuler.handleDispose();
937 fAdapterFactory.dispose();
938 fDiffRelationshipComputer.invalidate();
939 toolBar.dispose();
940 fColors.dispose();
942 if (fPreferenceChangeListener != null) {
943 if (fPreferenceStore != null) {
944 fPreferenceStore.removePropertyChangeListener(fPreferenceChangeListener);
946 fPreferenceChangeListener = null;
948 super.handleDispose(event);
952 * {@inheritDoc}
954 * @see org.eclipse.emf.common.command.CommandStackListener#commandStackChanged(java.util.EventObject)
956 public void commandStackChanged(EventObject event) {
957 undoAction.update();
958 redoAction.update();
960 Command mostRecentCommand = ((CommandStack)event.getSource()).getMostRecentCommand();
961 if (mostRecentCommand instanceof ICompareCopyCommand) {
962 // MUST NOT call a setSelection with a list, o.e.compare does not handle it (cf
963 // org.eclipse.compare.CompareEditorInput#getElement(ISelection))
964 Collection<?> affectedObjects = mostRecentCommand.getAffectedObjects();
965 TreeNode unfilteredNode = null;
966 if (!affectedObjects.isEmpty()) {
967 final Iterator<EObject> affectedIterator = Iterables.filter(affectedObjects, EObject.class)
968 .iterator();
969 IDifferenceGroupProvider groupProvider = getCompareConfiguration()
970 .getStructureMergeViewerGrouper().getProvider();
971 while (affectedIterator.hasNext() && unfilteredNode == null) {
972 EObject affected = affectedIterator.next();
973 Iterable<TreeNode> treeNodes = groupProvider.getTreeNodes(affected);
974 for (TreeNode node : treeNodes) {
975 if (!JFaceUtil.isFiltered(getViewer(), node, node.getParent())) {
976 unfilteredNode = node;
977 break;
982 if (unfilteredNode != null) {
983 final Object adaptedAffectedObject = fAdapterFactory.adapt(unfilteredNode,
984 ICompareInput.class);
985 // be sure the affected object has been created in the viewer.
986 for (TreeNode node : getPath(null, unfilteredNode)) {
987 getViewer().expandToLevel(fAdapterFactory.adapt(node, ICompareInput.class), 0);
989 // execute synchronously the set selection to be sure the MergeAction#run() will
990 // select next diff after.
991 SWTUtil.safeSyncExec(new Runnable() {
992 public void run() {
993 refresh();
994 StructuredSelection selection = new StructuredSelection(adaptedAffectedObject);
995 // allows to call CompareToolBar#selectionChanged(SelectionChangedEvent)
996 getViewer().setSelection(selection);
999 // update content viewers with the new selection
1000 SWTUtil.safeAsyncExec(new Runnable() {
1001 public void run() {
1002 navigatable.openSelectedChange();
1006 } else {
1007 // FIXME, should recompute the difference, something happened outside of this compare editor
1012 private Iterable<TreeNode> getPath(TreeNode from, TreeNode to) {
1013 if (to == from) {
1014 return Collections.emptyList();
1017 final List<TreeNode> path = new ArrayList<TreeNode>();
1018 path.add(to);
1019 TreeNode parent = to.getParent();
1020 while (parent != null && parent != from) {
1021 path.add(parent);
1022 parent = parent.getParent();
1024 return Lists.reverse(path);
1028 * Triggered by fCompareInputChangeListener and {@link #inputChanged(Object, Object)}.
1030 void compareInputChanged(ICompareInput input) {
1031 if (input == null) {
1032 // When closing, we don't need a progress monitor to handle the input change
1033 compareInputChanged((ICompareInput)null, new NullProgressMonitor());
1034 return;
1036 // The compare configuration is nulled when the viewer is disposed
1037 if (getCompareConfiguration() != null) {
1038 updateLayout(true, true);
1039 inputChangedTask.schedule();
1043 void compareInputChanged(CompareInputAdapter input, IProgressMonitor monitor) {
1044 compareInputChanged(null, (Comparison)input.getComparisonObject(), monitor);
1047 void compareInputChanged(ComparisonScopeInput input, IProgressMonitor monitor) {
1048 if (monitor.isCanceled()) {
1049 return;
1051 IComparisonScope comparisonScope = input.getComparisonScope();
1052 EMFCompareConfiguration compareConfiguration = getCompareConfiguration();
1054 EMFCompare comparator = compareConfiguration.getEMFComparator();
1055 compareConfiguration.setLeftEditable(input.isLeftEditable());
1056 compareConfiguration.setRightEditable(input.isRightEditable());
1058 if (input.isLeftEditable() && input.isRightEditable()) {
1059 compareConfiguration.setMergePreviewMode(MergeMode.RIGHT_TO_LEFT);
1060 } else {
1061 compareConfiguration.setMergePreviewMode(MergeMode.ACCEPT);
1064 // setup defaults
1065 if (compareConfiguration.getEditingDomain() == null) {
1066 ICompareEditingDomain domain = EMFCompareEditingDomain.create(comparisonScope.getLeft(),
1067 comparisonScope.getRight(), comparisonScope.getOrigin());
1068 compareConfiguration.setEditingDomain(domain);
1070 if (comparator == null) {
1071 Builder builder = EMFCompare.builder();
1072 EMFCompareBuilderConfigurator.createDefault().configure(builder);
1073 comparator = builder.build();
1076 SubMonitor subMonitor = SubMonitor.convert(monitor, 10);
1077 final Comparison comparison = comparator.compare(comparisonScope,
1078 BasicMonitor.toMonitor(subMonitor.newChild(10)));
1080 // Bug 458802: NPE when synchronizing SMV & CMV if comparison is empty
1081 hookAdapters(input, comparison);
1083 compareInputChanged(input.getComparisonScope(), comparison, monitor);
1086 void compareInputChanged(final IComparisonScope scope, final Comparison comparison,
1087 final IProgressMonitor monitor) {
1088 if (!getControl().isDisposed() && !monitor.isCanceled()) { // guard against disposal
1089 final EMFCompareConfiguration config = getCompareConfiguration();
1091 ComposedAdapterFactory oldAdapterFactory = fAdapterFactory;
1092 // re-initialize adapter factory due to new comparison
1093 fAdapterFactory = initAdapterFactory(comparison);
1095 // clear cache for new comparison
1096 if (fDiffRelationshipComputer != null) {
1097 fDiffRelationshipComputer.invalidate();
1100 // propagate new adapter factory
1101 config.setAdapterFactory(fAdapterFactory);
1102 getContentProvider().setAdapterFactory(fAdapterFactory);
1103 ((EMFCompareStructureMergeViewerLabelProvider)getLabelProvider().getStyledStringProvider())
1104 .setAdapterFactory(fAdapterFactory);
1106 final TreeNode treeNode = TreeFactory.eINSTANCE.createTreeNode();
1107 treeNode.setData(comparison);
1108 final Object input = fAdapterFactory.adapt(treeNode, ICompareInput.class);
1110 // this will set to the EMPTY difference group provider, but necessary to avoid NPE while
1111 // setting input.
1112 IDifferenceGroupProvider groupProvider = config.getStructureMergeViewerGrouper().getProvider();
1113 treeNode.eAdapters().add(groupProvider);
1115 // display problem tabs if any
1116 if (!monitor.isCanceled()) {
1117 SWTUtil.safeAsyncExec(new Runnable() {
1118 public void run() {
1119 Diagnostic diagnostic = comparison.getDiagnostic();
1120 if (diagnostic == null) {
1121 updateProblemIndication(Diagnostic.OK_INSTANCE);
1122 } else {
1123 updateProblemIndication(diagnostic);
1129 // must set the input now in a synchronous mean. It will be used in the #setComparisonAndScope
1130 // afterwards during the initialization of StructureMergeViewerFilter and
1131 // StructureMergeViewerGrouper.
1132 if (!monitor.isCanceled()) {
1133 SWTUtil.safeSyncExec(new Runnable() {
1134 public void run() {
1135 getViewer().setInput(input);
1140 config.setComparisonAndScope(comparison, scope);
1142 SWTUtil.safeAsyncExec(new Runnable() {
1144 public void run() {
1145 if (!getControl().isDisposed()) {
1146 updateLayout(false, true);
1151 getContentProvider().runWhenReady(IN_UI_ASYNC, new Runnable() {
1152 public void run() {
1153 if (!getControl().isDisposed()) {
1154 // title is not initialized as the comparison was set in the configuration after
1155 // the refresh caused by the initialization of the viewer filters and the group
1156 // providers.
1157 refreshTitle();
1159 // Expands the tree viewer to the default expansion level
1160 expandTreeToLevel(getDefaultTreeExpansionLevel());
1162 // Selects the first difference once the tree has been filled.
1163 selectFirstDiffOrDisplayLabelViewer(comparison);
1168 SWTUtil.safeAsyncExec(new Runnable() {
1169 public void run() {
1170 fHandlerService.updatePaneActionHandlers(new Runnable() {
1171 public void run() {
1172 fHandlerService.setGlobalActionHandler(ActionFactory.UNDO.getId(), undoAction);
1173 fHandlerService.setGlobalActionHandler(ActionFactory.REDO.getId(), redoAction);
1179 // Disposing this at the start of this method would meet frequent CME
1180 if (oldAdapterFactory != null) {
1181 oldAdapterFactory.dispose();
1186 void compareInputChanged(final ICompareInput input, IProgressMonitor monitor) {
1187 if (input != null && !monitor.isCanceled()) {
1188 if (input instanceof CompareInputAdapter) {
1189 resourceSetShouldBeDisposed = false;
1190 compareInputChanged((CompareInputAdapter)input, monitor);
1191 initToolbar(monitor);
1192 } else if (input instanceof ComparisonScopeInput) {
1193 resourceSetShouldBeDisposed = false;
1194 compareInputChanged((ComparisonScopeInput)input, monitor);
1195 initToolbar(monitor);
1196 } else {
1197 resourceSetShouldBeDisposed = true;
1198 SubMonitor subMonitor = SubMonitor.convert(monitor, 100);
1200 final ITypedElement left = input.getLeft();
1201 final ITypedElement right = input.getRight();
1202 final ITypedElement origin = input.getAncestor();
1204 final boolean leftEditable;
1205 final boolean rightEditable;
1207 EMFCompareConfiguration compareConfiguration = getCompareConfiguration();
1209 * A resource node means that the left ITypedElement is in the workspace, a DiffNode input
1210 * means the comparison has been launched from a Replace With action.
1212 if (left instanceof ResourceNode && !(input instanceof DiffNode)) {
1213 ResourceAttributes attributes = ((ResourceNode)left).getResource()
1214 .getResourceAttributes();
1215 leftEditable = attributes != null && !attributes.isReadOnly();
1216 } else {
1217 leftEditable = compareConfiguration.isLeftEditable();
1220 if (right instanceof ResourceNode) {
1221 ResourceAttributes attributes = ((ResourceNode)right).getResource()
1222 .getResourceAttributes();
1223 rightEditable = attributes != null && !attributes.isReadOnly();
1224 } else {
1225 rightEditable = compareConfiguration.isRightEditable();
1228 compareConfiguration.setLeftEditable(leftEditable);
1229 compareConfiguration.setRightEditable(rightEditable);
1231 if (leftEditable && rightEditable) {
1232 compareConfiguration.setMergePreviewMode(MergeMode.RIGHT_TO_LEFT);
1233 } else {
1234 compareConfiguration.setMergePreviewMode(MergeMode.ACCEPT);
1237 final BasicDiagnostic diagnostic = new BasicDiagnostic(Diagnostic.OK,
1238 EMFCompareIDEUIPlugin.PLUGIN_ID, 0, null, new Object[0]);
1239 IComparisonScope scope = null;
1241 try {
1242 scope = ComparisonScopeBuilder.create(compareConfiguration.getContainer(), left, right,
1243 origin, subMonitor.newChild(85));
1244 } catch (OperationCanceledException e) {
1245 scope = new EmptyComparisonScope();
1246 ((BasicDiagnostic)((EmptyComparisonScope)scope).getDiagnostic())
1247 .merge(new BasicDiagnostic(Diagnostic.CANCEL, EMFCompareIDEUIPlugin.PLUGIN_ID, 0,
1248 EMFCompareIDEUIMessages
1249 .getString("EMFCompareStructureMergeViewer.operationCanceled"), //$NON-NLS-1$
1250 new Object[] {e, }));
1251 } catch (Exception e) {
1252 scope = new EmptyComparisonScope();
1253 ((BasicDiagnostic)((EmptyComparisonScope)scope).getDiagnostic())
1254 .merge(BasicDiagnostic.toDiagnostic(e));
1255 EMFCompareIDEUIPlugin.getDefault().log(e);
1258 if (scope instanceof IDiagnosable && ((IDiagnosable)scope).getDiagnostic() != null) {
1259 diagnostic.merge(((IDiagnosable)scope).getDiagnostic());
1262 final Builder comparisonBuilder = EMFCompare.builder();
1264 EMFCompareBuilderConfigurator.createDefault().configure(comparisonBuilder);
1266 SubMonitor subMonitorChild = SubMonitor.convert(subMonitor.newChild(15), 10);
1267 final Comparison compareResult = comparisonBuilder.build().compare(scope,
1268 BasicMonitor.toMonitor(subMonitorChild));
1270 hookAdapters(input, compareResult);
1272 if (compareResult.getDiagnostic() != null) {
1273 diagnostic.merge(compareResult.getDiagnostic());
1275 // update diagnostic of the comparison with the global one.
1276 compareResult.setDiagnostic(diagnostic);
1278 final ResourceSet leftResourceSet = (ResourceSet)scope.getLeft();
1279 final ResourceSet rightResourceSet = (ResourceSet)scope.getRight();
1280 final ResourceSet originResourceSet = (ResourceSet)scope.getOrigin();
1282 ICompareEditingDomain editingDomain = EMFCompareEditingDomain.create(leftResourceSet,
1283 rightResourceSet, originResourceSet);
1284 editingDomainNeedsToBeDisposed = true;
1285 compareConfiguration.setEditingDomain(editingDomain);
1287 if (leftResourceSet instanceof NotLoadingResourceSet) {
1288 ((NotLoadingResourceSet)leftResourceSet).setAllowResourceLoad(true);
1290 if (rightResourceSet instanceof NotLoadingResourceSet) {
1291 ((NotLoadingResourceSet)rightResourceSet).setAllowResourceLoad(true);
1293 if (originResourceSet instanceof NotLoadingResourceSet) {
1294 ((NotLoadingResourceSet)originResourceSet).setAllowResourceLoad(true);
1297 IStorage leftStorage = PlatformElementUtil.findFile(left);
1298 if (leftStorage == null) {
1299 leftStorage = StreamAccessorStorage.fromTypedElement(left);
1302 initToolbar(monitor);
1303 compareInputChanged(scope, compareResult, monitor);
1305 // Protect compare actions from over-enthusiast users
1306 enableToolbar(monitor);
1307 } else {
1308 compareInputChangedToNull();
1313 * Hooks the adapters required for handling UI properly.
1315 * @param input
1316 * @param compareResult
1318 private void hookAdapters(final ICompareInput input, final Comparison compareResult) {
1319 compareResult.eAdapters().add(new ForwardingCompareInputAdapter(input));
1320 // Thanks to
1321 // org.eclipse.team.internal.ui.mapping.ResourceCompareInputChangeNotifier$CompareInputLabelProvider
1322 // who doesn't check a cast in its getAncestorLabel(), getLeftLabel() and getRightLabel() methods,
1323 // we can't allow to add side label provider in case of an input of type ResourceDiffCompareInput.
1324 if (!(input instanceof ResourceDiffCompareInput)) {
1325 ICompareInputLabelProvider labelProvider = getCompareConfiguration().getLabelProvider();
1326 SideLabelProvider sideLabelProvider = new SideLabelProvider(labelProvider.getAncestorLabel(input),
1327 labelProvider.getLeftLabel(input), labelProvider.getRightLabel(input),
1328 labelProvider.getAncestorImage(input), labelProvider.getLeftImage(input),
1329 labelProvider.getRightImage(input));
1330 compareResult.eAdapters().add(sideLabelProvider);
1332 // Bug 501569: The cascading filter does not hide merged cascading diffs
1333 new MatchOfContainmentReferenceChangeProcessor().execute(compareResult);
1335 // Add a MergeData to handle status decorations on Diffs
1336 MergeDataImpl mergeData = new MergeDataImpl(getCompareConfiguration().isLeftEditable(),
1337 getCompareConfiguration().isRightEditable());
1338 compareResult.eAdapters().add(mergeData);
1342 * Returns whether the first change should be selected automatically after initialization.
1344 * @return true if the first change should be selected automatically, false otherwise.
1345 * @see #selectFirstDiffOrDisplayLabelViewer(Comparison)
1347 protected boolean isSelectFirstChange() {
1348 return preferenceStore.getBoolean(EMFCompareUIPreferences.EDITOR_TREE_AUTO_SELECT_FIRST_CHANGE);
1352 * Returns the default expansion level for the tree viewer.
1354 * @return non-negative level, or {@link AbstractTreeViewer#ALL_LEVELS ALL_LEVELS} to expand all levels of
1355 * the tree
1356 * @see #expandTreeToLevel(int)
1358 protected int getDefaultTreeExpansionLevel() {
1359 return preferenceStore.getInt(EMFCompareUIPreferences.EDITOR_TREE_AUTO_EXPAND_LEVEL);
1363 * Expands the {@link #getViewer() tree viewer} to the given level.
1365 * @param level
1366 * non-negative level, or {@link AbstractTreeViewer#ALL_LEVELS ALL_LEVELS} to expand all levels
1367 * of the tree
1368 * @see TreeViewer#expandToLevel(int)
1370 protected void expandTreeToLevel(int level) {
1371 getViewer().expandToLevel(level);
1375 * Returns whether we highlight changes related to the current selected change.
1377 * @return true if we highlight related changes, false otherwise.
1378 * @see #updateHighlightRelatedChanges(ISelection)
1380 protected boolean isHighlightRelatedChanges() {
1381 return preferenceStore.getBoolean(EMFCompareUIPreferences.EDITOR_TREE_HIGHLIGHT_RELATED_CHANGES);
1385 * Updates the highlighting of related changes for the current selection, if it is
1386 * {@link #isHighlightRelatedChanges() enabled}.
1388 * @param selection
1389 * selection
1391 protected void updateHighlightRelatedChanges(ISelection selection) {
1392 if (!isHighlightRelatedChanges()) {
1393 return;
1395 dependencyData.updateDependencies(selection, EMFCompareRCPPlugin.getDefault().getMergerRegistry());
1396 internalRedraw();
1400 * Clears the highlighting of related changes for the current selection.
1402 protected void clearHighlightRelatedChanges() {
1403 dependencyData.clearDependencies();
1404 internalRedraw();
1408 * Select the first difference...if there are differences, otherwise, display appropriate content viewer
1409 * (no differences or no visible differences)
1411 * @param comparison
1412 * the comparison used to know if there are differences.
1414 private void selectFirstDiffOrDisplayLabelViewer(final Comparison comparison) {
1415 if (comparison != null) {
1416 ICompareInput compareInput = (ICompareInput)EcoreUtil.getAdapter(comparison.eAdapters(),
1417 ICompareInput.class);
1418 if (compareInput == null) {
1419 compareInput = new TreeNodeCompareInput(new TreeCompareInputAdapterFactory());
1421 List<Diff> differences = comparison.getDifferences();
1422 if (differences.isEmpty()) {
1423 navigatable.fireOpen(new NoDifferencesCompareInput(compareInput));
1424 } else if (!navigatable.hasChange(INavigatable.FIRST_CHANGE)) {
1425 if (hasOnlyPseudoConflicts(differences)) {
1426 navigatable.fireOpen(new OnlyPseudoConflictsCompareInput(compareInput));
1427 } else {
1428 navigatable.fireOpen(new NoVisibleItemCompareInput(compareInput));
1430 } else if (!isSelectFirstChange()) {
1431 navigatable.fireOpen(new NoSelectedItemCompareInput(compareInput));
1432 } else {
1433 navigatable.selectChange(INavigatable.FIRST_CHANGE);
1438 private boolean hasOnlyPseudoConflicts(List<Diff> differences) {
1439 return Iterators.all(differences.iterator(), EMFComparePredicates.hasConflict(ConflictKind.PSEUDO));
1442 private void updateLayout(boolean displayProgress, boolean doLayout) {
1443 ((GridData)progressInfoItem.getLayoutData()).exclude = !displayProgress;
1444 progressInfoItem.setVisible(displayProgress);
1446 ((GridData)getViewer().getControl().getLayoutData()).exclude = displayProgress;
1447 getViewer().getControl().setVisible(!displayProgress);
1449 ((GridData)treeRuler.getLayoutData()).exclude = displayProgress;
1450 treeRuler.setVisible(!displayProgress);
1452 if (doLayout) {
1453 getControl().layout(true, true);
1457 private void compareInputChangedToNull() {
1458 if (!inputChangedTask.cancel()) {
1459 try {
1460 inputChangedTask.join();
1461 } catch (InterruptedException e) {
1462 Thread.currentThread().interrupt();
1463 Throwables.propagate(e);
1467 ResourceSet leftResourceSet = null;
1468 ResourceSet rightResourceSet = null;
1469 ResourceSet originResourceSet = null;
1471 final Comparison comparison = getCompareConfiguration().getComparison();
1472 if (comparison != null) {
1473 Iterator<Match> matchIt = comparison.getMatches().iterator();
1474 if (comparison.isThreeWay()) {
1475 while (matchIt.hasNext() && (leftResourceSet == null || rightResourceSet == null
1476 || originResourceSet == null)) {
1477 Match match = matchIt.next();
1478 if (leftResourceSet == null) {
1479 leftResourceSet = getResourceSet(match.getLeft());
1481 if (rightResourceSet == null) {
1482 rightResourceSet = getResourceSet(match.getRight());
1484 if (originResourceSet == null) {
1485 originResourceSet = getResourceSet(match.getOrigin());
1488 } else {
1489 while (matchIt.hasNext() && (leftResourceSet == null || rightResourceSet == null)) {
1490 Match match = matchIt.next();
1491 if (leftResourceSet == null) {
1492 leftResourceSet = getResourceSet(match.getLeft());
1494 if (rightResourceSet == null) {
1495 rightResourceSet = getResourceSet(match.getRight());
1499 comparison.eAdapters().clear();
1502 editingDomainChange(getCompareConfiguration().getEditingDomain(), null);
1504 if (resourceSetShouldBeDisposed) {
1505 final ResourceSet finalLeftResourceSet = leftResourceSet;
1506 final ResourceSet finalRightResourceSet = rightResourceSet;
1507 final ResourceSet finalOriginResourceSet = originResourceSet;
1508 new Job("Resource Disposer") { //$NON-NLS-1$
1509 @Override
1510 protected IStatus run(IProgressMonitor monitor) {
1511 disposeResourceSet(finalLeftResourceSet);
1512 disposeResourceSet(finalRightResourceSet);
1513 disposeResourceSet(finalOriginResourceSet);
1514 return Status.OK_STATUS;
1516 }.schedule();
1519 if (getCompareConfiguration() != null) {
1520 getCompareConfiguration().dispose();
1522 getViewer().setInput(null);
1526 * Disposes the {@link ResourceSet}.
1528 * @param resourceSet
1529 * that need to be disposed.
1531 protected void disposeResourceSet(ResourceSet resourceSet) {
1532 if (resourceSet instanceof DisposableResourceSet) {
1533 ((DisposableResourceSet)resourceSet).dispose();
1534 } else {
1535 unload(resourceSet);
1540 * Handle the erase item event. When select a difference in the structure merge viewer, highlight required
1541 * differences with a specific color, and highlight unmergeable differences with another color.
1543 * @param event
1544 * the erase item event.
1546 private void handleEraseItemEvent(Event event) {
1547 TreeItem item = (TreeItem)event.item;
1548 EObject dataItem = EMFCompareStructureMergeViewer.getDataOfTreeNodeOfAdapter(item.getData());
1549 if (dataItem != null) {
1550 final Set<Diff> requires = dependencyData.getRequires();
1551 final Set<Diff> rejectedDiffs = dependencyData.getRejections();
1552 final GC g = event.gc;
1553 if (requires.contains(dataItem)) {
1554 paintItemBackground(g, item, fColors.getRequiredFillColor());
1555 } else if (rejectedDiffs.contains(dataItem)) {
1556 paintItemBackground(g, item, fColors.getUnmergeableFillColor());
1562 * Paint the background of the given item with the given color.
1564 * @param g
1565 * the GC associated to the item.
1566 * @param item
1567 * the given item.
1568 * @param color
1569 * the given color.
1571 private void paintItemBackground(GC g, TreeItem item, Color color) {
1572 Rectangle itemBounds = item.getBounds();
1573 Tree tree = item.getParent();
1574 Rectangle areaBounds = tree.getClientArea();
1575 g.setClipping(areaBounds.x, itemBounds.y, areaBounds.width, itemBounds.height);
1576 g.setBackground(color);
1577 g.fillRectangle(areaBounds.x, itemBounds.y, areaBounds.width, itemBounds.height);
1581 * Returns a problem indication composite for the given diagnostic. If a problem indication composite
1582 * already exists, the existing one is returned. If no composite exists, a new composite is created if the
1583 * severity of the provided diagnostic is anything besides OK. If no composite exists and the severity
1584 * does not warrant the creation of a new composite, this method returns null.
1586 * @param diagnostic
1587 * comparison diagnostic
1588 * @return the existing or a newly created problem indication composite or null if no indication is
1589 * necessary
1591 private ProblemIndicationComposite getProblemIndication(Diagnostic diagnostic) {
1592 Assert.isNotNull(diagnostic);
1593 int lastEditorPage = getPageCount() - 1;
1594 ProblemIndicationComposite problemIndicationComposite = null;
1595 if (lastEditorPage >= 0 && getItemControl(lastEditorPage) instanceof ProblemIndicationComposite) {
1596 problemIndicationComposite = ((ProblemIndicationComposite)getItemControl(lastEditorPage));
1597 } else if (diagnostic.getSeverity() != Diagnostic.OK && !getControl().isDisposed()) {
1598 problemIndicationComposite = new ProblemIndicationComposite(getControl(), SWT.NONE);
1599 createItem(++lastEditorPage, problemIndicationComposite);
1600 getControl().getItem(lastEditorPage)
1601 .setText(CommonUIPlugin.getPlugin().getString("_UI_Problems_label")); //$NON-NLS-1$
1602 showTabs();
1604 return problemIndicationComposite;
1608 * Updates the problem indication for the provided diagnostic. If everything is {@link Diagnostic#OK} and
1609 * no problem indication is available, this method does nothing. In any other case, the existing or a
1610 * newly created problem indication is updated and automatically revealed if the diagnostics
1611 * {@link Diagnostic#getSeverity() severity} is anything besides {@link Diagnostic#OK} and
1612 * {@link Diagnostic#WARNING}.
1614 * @param diagnostic
1615 * comparison diagnostic
1617 private void updateProblemIndication(Diagnostic diagnostic) {
1618 ProblemIndicationComposite problemIndicationComposite = getProblemIndication(diagnostic);
1619 if (problemIndicationComposite != null) {
1620 problemIndicationComposite.setDiagnostic(diagnostic);
1621 if (diagnostic.getSeverity() != Diagnostic.OK && diagnostic.getSeverity() != Diagnostic.WARNING) {
1622 // reveal problem indication composite (last editor page)
1623 int lastEditorPage = getPageCount() - 1;
1624 setActivePage(lastEditorPage);
1625 updateLayout(false, true);
1630 private void showTabs() {
1631 if (getPageCount() > 1) {
1632 getControl().getItem(0).setText(
1633 EMFCompareIDEUIMessages.getString("EMFCompareStructureMergeViewer.tabItem.0.title")); //$NON-NLS-1$
1634 getControl().setTabHeight(SWT.DEFAULT);
1635 Point point = getControl().getSize();
1636 getControl().setSize(point.x, point.y - 6);
1640 private void hideTabs() {
1641 if (getPageCount() <= 1) {
1642 getControl().getItem(0).setText(""); //$NON-NLS-1$
1643 getControl().setTabHeight(1);
1644 Point point = getControl().getSize();
1645 getControl().setSize(point.x, point.y + 6);
1649 private void setActivePage(int pageIndex) {
1650 Assert.isTrue(pageIndex >= 0 && pageIndex < getPageCount());
1651 getControl().setSelection(pageIndex);
1654 private int getPageCount() {
1655 // May not have been created yet, or may have been disposed.
1656 if (getControl() != null && !getControl().isDisposed()) {
1657 return getControl().getItemCount();
1659 return 0;
1662 private Control getItemControl(int itemIndex) {
1663 CTabItem item = getControl().getItem(itemIndex);
1664 if (item != null) {
1665 return item.getControl();
1667 return null;
1670 private static void unload(ResourceSet resourceSet) {
1671 if (resourceSet != null) {
1672 for (Resource resource : resourceSet.getResources()) {
1673 resource.unload();
1675 resourceSet.getResources().clear();
1679 private static ResourceSet getResourceSet(EObject eObject) {
1680 if (eObject != null) {
1681 Resource eResource = eObject.eResource();
1682 if (eResource != null) {
1683 return eResource.getResourceSet();
1686 return null;
1690 * {@inheritDoc}
1692 * @see org.eclipse.jface.viewers.StructuredViewer#internalRefresh(java.lang.Object)
1694 @Override
1695 protected void internalRefresh(Object element) {
1696 // Postpones the refresh if the content provider is in pending mode
1697 getContentProvider().runWhenReady(IN_UI_SYNC, new Runnable() {
1699 public void run() {
1700 getViewer().refresh();
1703 // Updates dependency data when the viewer has been refreshed and the content provider is ready.
1704 getContentProvider().runWhenReady(IN_UI_SYNC, new Runnable() {
1705 public void run() {
1706 updateHighlightRelatedChanges(getSelection());
1709 // Needs dependency data however do not need to be run in UI thread
1710 getContentProvider().runWhenReady(IN_UI_ASYNC, new Runnable() {
1712 public void run() {
1713 refreshTitle();
1720 * Handles changes to the UI-related preferences in the {@link #preferenceStore}.
1722 * @param event
1723 * change event for a preference property
1725 protected void handlePreferenceChangedEvent(PropertyChangeEvent event) {
1726 if (event.getProperty() == EMFCompareUIPreferences.EDITOR_TREE_HIGHLIGHT_RELATED_CHANGES) {
1727 boolean highlightRelatedChanges = Boolean.parseBoolean(event.getNewValue().toString());
1728 if (highlightRelatedChanges) {
1729 updateHighlightRelatedChanges(getSelection());
1730 } else {
1731 clearHighlightRelatedChanges();
1736 private void handleSelectionChangedEvent(SelectionChangedEvent event) {
1737 if (!Objects.equal(currentSelection, event.getSelection())) {
1738 this.currentSelection = event.getSelection();
1739 updateHighlightRelatedChanges(event.getSelection());
1744 * We need to call redraw() on the tree and the tree ruler because getControl().redraw() doesn't propagate
1745 * the redraw on its sub components under windows platform.
1747 private void internalRedraw() {
1748 Tree tree = getViewer().getTree();
1749 if (!tree.isDisposed()) {
1750 tree.redraw();
1751 if (!treeRuler.isDisposed()) {
1752 treeRuler.redraw();
1757 private static class TitleBuilder {
1759 private final Comparison comparison;
1761 private final IDifferenceGroupProvider groupProvider;
1763 private final Predicate<? super EObject> filterPredicate;
1765 private final Map<Object, Boolean> visited = Maps.newHashMap();
1767 private int diffsCount;
1769 private int visibleDiffsCount;
1771 private int diffsToMergeCount;
1773 public TitleBuilder(EMFCompareConfiguration configuration) {
1774 comparison = configuration.getComparison();
1775 groupProvider = configuration.getStructureMergeViewerGrouper().getProvider();
1776 filterPredicate = configuration.getStructureMergeViewerFilter().getAggregatedPredicate();
1779 void visit(TreeNode node, boolean parentApplies) {
1780 boolean applies = parentApplies && filterPredicate.apply(node);
1781 EObject data = node.getData();
1782 if (data instanceof Diff) {
1783 // If we haven't visited it before...
1784 Boolean visitedApplies = visited.put(data, Boolean.valueOf(applies));
1785 if (visitedApplies == null) {
1786 // Count it.
1787 ++diffsCount;
1790 // If it's visible...
1791 if (applies) {
1792 // If we didn't visit it as visible before...
1793 if (!Boolean.TRUE.equals(visitedApplies)) {
1794 // Count it as visible.
1795 ++visibleDiffsCount;
1797 // And if it's not unresolved, count it has needing to be merged.
1798 Diff diff = (Diff)data;
1799 if (diff.getState() == DifferenceState.UNRESOLVED) {
1800 ++diffsToMergeCount;
1803 } else if (Boolean.TRUE.equals(visitedApplies)) {
1804 // If it was previously counted as visible, but here it's not visible, replace the state
1805 // to indicate it was visible and was counted as visible before.
1806 visited.put(data, Boolean.TRUE);
1810 for (TreeNode childNode : node.getChildren()) {
1811 visit(childNode, applies);
1815 @Override
1816 public String toString() {
1817 for (IDifferenceGroup group : groupProvider.getGroups(comparison)) {
1818 for (TreeNode node : group.getChildren()) {
1819 visit(node, true);
1823 @SuppressWarnings("boxing")
1824 String titleArgument = EMFCompareIDEUIMessages.getString(
1825 "EMFCompareStructureMergeViewer.titleDesc", //$NON-NLS-1$
1826 diffsToMergeCount, visibleDiffsCount, diffsCount - visibleDiffsCount);
1827 return titleArgument;