1 /*******************************************************************************
2 * Copyright (c) 2012, 2016 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
9 * Obeo - initial API and implementation
10 * Michael Borkowski - public CallbackType visibility for testing
11 * Stefan Dirix - bug 473985
12 *******************************************************************************/
13 package org
.eclipse
.emf
.compare
.ide
.ui
.internal
.structuremergeviewer
;
15 import static com
.google
.common
.base
.Predicates
.instanceOf
;
16 import static com
.google
.common
.collect
.Iterables
.toArray
;
17 import static com
.google
.common
.collect
.Iterables
.transform
;
19 import com
.google
.common
.base
.Function
;
20 import com
.google
.common
.base
.Optional
;
21 import com
.google
.common
.base
.Predicates
;
22 import com
.google
.common
.collect
.Iterables
;
23 import com
.google
.common
.collect
.Iterators
;
24 import com
.google
.common
.collect
.Lists
;
26 import java
.util
.Arrays
;
27 import java
.util
.Iterator
;
28 import java
.util
.List
;
29 import java
.util
.concurrent
.CopyOnWriteArrayList
;
30 import java
.util
.concurrent
.locks
.ReentrantLock
;
32 import org
.eclipse
.compare
.structuremergeviewer
.ICompareInput
;
33 import org
.eclipse
.core
.runtime
.IProgressMonitor
;
34 import org
.eclipse
.core
.runtime
.jobs
.IJobChangeEvent
;
35 import org
.eclipse
.core
.runtime
.jobs
.IJobChangeListener
;
36 import org
.eclipse
.core
.runtime
.jobs
.ISchedulingRule
;
37 import org
.eclipse
.core
.runtime
.jobs
.Job
;
38 import org
.eclipse
.emf
.common
.notify
.Adapter
;
39 import org
.eclipse
.emf
.common
.notify
.AdapterFactory
;
40 import org
.eclipse
.emf
.common
.notify
.Notification
;
41 import org
.eclipse
.emf
.common
.notify
.Notifier
;
42 import org
.eclipse
.emf
.compare
.Comparison
;
43 import org
.eclipse
.emf
.compare
.ide
.ui
.internal
.EMFCompareIDEUIMessages
;
44 import org
.eclipse
.emf
.compare
.ide
.ui
.internal
.treecontentmanager
.EMFCompareDeferredTreeContentManager
;
45 import org
.eclipse
.emf
.compare
.ide
.ui
.internal
.treecontentmanager
.EMFCompareDeferredTreeContentManagerUtil
;
46 import org
.eclipse
.emf
.compare
.rcp
.ui
.internal
.util
.SWTUtil
;
47 import org
.eclipse
.emf
.compare
.rcp
.ui
.structuremergeviewer
.groups
.IDifferenceGroupProvider2
;
48 import org
.eclipse
.emf
.edit
.provider
.IViewerNotification
;
49 import org
.eclipse
.emf
.edit
.provider
.ViewerNotification
;
50 import org
.eclipse
.emf
.edit
.tree
.TreeNode
;
51 import org
.eclipse
.emf
.edit
.ui
.provider
.AdapterFactoryContentProvider
;
52 import org
.eclipse
.emf
.edit
.ui
.provider
.NotifyChangedToViewerRefresh
;
53 import org
.eclipse
.jface
.resource
.ImageDescriptor
;
54 import org
.eclipse
.jface
.viewers
.AbstractTreeViewer
;
55 import org
.eclipse
.jface
.viewers
.IContentProvider
;
56 import org
.eclipse
.ui
.progress
.DeferredTreeContentManager
;
57 import org
.eclipse
.ui
.progress
.IDeferredWorkbenchAdapter
;
58 import org
.eclipse
.ui
.progress
.IElementCollector
;
59 import org
.eclipse
.ui
.progress
.PendingUpdateAdapter
;
62 * Specialized AdapterFactoryContentProvider for the emf compare structure merge viewer.
64 * <i>This class is not intended to be used outside of its package. It has been set to public for testing
68 * @author <a href="mailto:mikael.barbero@obeo.fr">Mikael Barbero</a>
70 public class EMFCompareStructureMergeViewerContentProvider
extends AdapterFactoryContentProvider
implements IJobChangeListener
{
73 * Class to listen the state of the content provider.
75 * @author <a href="mailto:arthur.daussy@obeo.fr">Arthur Daussy</a>
77 static class FetchListener
{
80 * This method is called when the content provider starts fetching new elements in an external Job.
82 public void startFetching() {
87 * This method is called when the content provider has finished fetching elements and has removed the
88 * {@link PendingUpdateAdapter} from the tree.
90 public void doneFetching() {
96 * Callback holder used to defer the run of a callback in a specific thread.
98 * @see {@link #callback}
99 * @see EMFCompareStructureMergeViewerContentProvider#runWhenReady(CallbackType, Runnable)
101 private static class CallbackHolder
{
103 private final Runnable callback
;
105 private final CallbackType callbackType
;
107 public CallbackHolder(Runnable callback
, CallbackType callbackType
) {
109 this.callback
= callback
;
110 this.callbackType
= callbackType
;
113 private Runnable
getCallback() {
117 private CallbackType
getType() {
124 * {@link IDeferredWorkbenchAdapter} using this content provider to fetch children.
126 * @author <a href="mailto:arthur.daussy@obeo.fr">Arthur Daussy</a>
128 private static class EMFCompareStructureMergeViewerContentProviderDeferredAdapter
implements IDeferredWorkbenchAdapter
{
130 private final EMFCompareStructureMergeViewerContentProvider contentProvider
;
132 public EMFCompareStructureMergeViewerContentProviderDeferredAdapter(
133 EMFCompareStructureMergeViewerContentProvider contentProvider
) {
135 this.contentProvider
= contentProvider
;
141 * @see {IDeferredWorkbenchAdapter{@link IDeferredWorkbenchAdapter#getChildren(Object)}
143 public Object
[] getChildren(Object o
) {
151 * @see {IDeferredWorkbenchAdapter{@link IDeferredWorkbenchAdapter#getImageDescriptor(Object)}
153 public ImageDescriptor
getImageDescriptor(Object object
) {
161 * @see {IDeferredWorkbenchAdapter{@link IDeferredWorkbenchAdapter#getLabel(Object)}
163 public String
getLabel(Object o
) {
164 return EMFCompareIDEUIMessages
.getString(
165 "EMFCompareStructureMergeViewerContentProvider.deferredWorkbenchAdapter.label"); //$NON-NLS-1$
171 * @see {IDeferredWorkbenchAdapter{@link IDeferredWorkbenchAdapter#getParent(Object)}
173 public Object
getParent(Object o
) {
181 * @see {IDeferredWorkbenchAdapter
182 * {@link IDeferredWorkbenchAdapter#fetchDeferredChildren(Object object, IElementCollector collector, IProgressMonitor monitor)}
184 public void fetchDeferredChildren(Object object
, IElementCollector collector
,
185 IProgressMonitor monitor
) {
186 if (!monitor
.isCanceled()) {
187 if (object
instanceof CompareInputAdapter
) {
188 Notifier target
= ((Adapter
)object
).getTarget();
189 Object
[] children
= contentProvider
.getChildren(target
);
190 collector
.add(children
, monitor
);
199 * @see {IDeferredWorkbenchAdapter{@link IDeferredWorkbenchAdapter#isContainer()}
201 public boolean isContainer() {
209 * @see {IDeferredWorkbenchAdapter{@link #getRule(Object)}
211 public ISchedulingRule
getRule(Object object
) {
218 /** {@link DeferredTreeContentManager} use to fetch groups in a external {@link Job}. */
219 private final EMFCompareDeferredTreeContentManager contentManagerAdapter
;
221 /** Holds true if this content provider is currently fetching children. */
222 private boolean isFetchingGroup
;
224 /** Will protect R/W of {@link #pending} and {@link #isFetchingGroup}. */
225 private final ReentrantLock lock
;
227 /** Object listening the status of this object. */
228 private final List
<FetchListener
> listeners
;
230 /** List of current callbacks. Callbacks are only run once. */
231 private List
<CallbackHolder
> callbacks
;
233 /** Pending object displayed in the tree. */
234 private Object
[] pending
;
237 * Constructs the content provider with the appropriate adapter factory.
239 * @param adapterFactory
240 * The adapter factory used to construct the content provider.
242 public EMFCompareStructureMergeViewerContentProvider(AdapterFactory adapterFactory
,
243 AbstractTreeViewer viewer
) {
244 super(adapterFactory
);
245 contentManagerAdapter
= EMFCompareDeferredTreeContentManagerUtil
246 .createEMFDeferredTreeContentManager(viewer
);
247 contentManagerAdapter
.addUpdateCompleteListener(this);
248 lock
= new ReentrantLock();
249 listeners
= new CopyOnWriteArrayList
<EMFCompareStructureMergeViewerContentProvider
.FetchListener
>();
250 callbacks
= new CopyOnWriteArrayList
<EMFCompareStructureMergeViewerContentProvider
.CallbackHolder
>();
257 * @see org.eclipse.emf.edit.ui.provider.AdapterFactoryContentProvider#getParent(Object object)
260 public Object
getParent(Object element
) {
262 if (element
instanceof CompareInputAdapter
) {
263 Object parentNode
= super.getParent(((Adapter
)element
).getTarget());
264 if (parentNode
instanceof TreeNode
) {
265 final Optional
<Adapter
> cia
= Iterators
.tryFind(((TreeNode
)parentNode
).eAdapters().iterator(),
266 instanceOf(CompareInputAdapter
.class));
267 if (cia
.isPresent()) {
275 } else if (element
instanceof ICompareInput
) {
278 ret
= super.getParent(element
);
284 * Enum used for better readability of the method
285 * {@link EMFCompareStructureMergeViewerContentProvider#runWhenReady(CallbackType, Runnable)}.
287 * @author <a href="mailto:arthur.daussy@obeo.fr">Arthur Daussy</a>
289 // public for testing
290 public static enum CallbackType
{
291 /** Run the runnable in the UI thread synchronously. */
293 /** Run the runnable in the UI thread asynchronously. */
295 /** Run the runnable in the current thread. */
300 * Run the given runnable in the specified thread when then content provider is ready. It can be run
301 * directly if the content provider is not fecthing or during a callback when the content provider is done
305 * of thread to run the {@link Runnable} inside.
309 public void runWhenReady(CallbackType type
, final Runnable runnable
) {
310 // Prevents adding a callback if another thread set this content provider as not fetching.
313 if (isFetchingGroup
) {
314 callbacks
.add(new CallbackHolder(runnable
, type
));
324 * Runs a callback in the related thread.
331 private void run(Runnable callback
, CallbackType type
) {
334 SWTUtil
.safeSyncExec(callback
);
337 SWTUtil
.safeAsyncExec(callback
);
346 * Adds a listener to this content provider.
352 public boolean addFetchingListener(FetchListener listener
) {
353 return listeners
.add(listener
);
357 * Removes a listener to this content provider.
363 public boolean removeFetchingListener(FetchListener listener
) {
364 return listeners
.remove(listener
);
370 * @see org.eclipse.emf.edit.ui.provider.AdapterFactoryContentProvider#hasChildren(Object object)
373 public final boolean hasChildren(Object element
) {
375 if (element
instanceof CompareInputAdapter
) {
376 ret
= super.hasChildren(((Adapter
)element
).getTarget());
377 } else if (element
instanceof ICompareInput
) {
380 ret
= super.hasChildren(element
);
388 * @see org.eclipse.emf.edit.ui.provider.AdapterFactoryContentProvider#getChildren(java.lang.Object)
391 public final Object
[] getChildren(Object element
) {
393 if (element
instanceof CompareInputAdapter
) {
394 children
= getCompareInputAdapterChildren((CompareInputAdapter
)element
);
395 } else if (element
instanceof ICompareInput
) {
396 children
= new Object
[] {};
398 children
= super.getChildren(element
);
401 final Object
[] compareInputChildren
;
403 if (children
== null) {
404 children
= new Object
[] {};
406 // Do not adapt if it's a pending updater
407 if (!Iterables
.all(Arrays
.asList(children
), Predicates
.instanceOf(PendingUpdateAdapter
.class))) {
408 Iterable
<?
> compareInputs
= adapt(children
, getAdapterFactory(), ICompareInput
.class);
409 compareInputChildren
= toArray(compareInputs
, Object
.class);
411 compareInputChildren
= children
;
413 return compareInputChildren
;
417 * Returns a {@link PendingUpdateAdapter} while a Job is fetching the children for this object or the
418 * children if they have already been fetched.
420 * When the job is finished it will autamically replace the {@link PendingUpdateAdapter} by the fetched
421 * children. The fetched children will be stored under the TreeItem holding the input object or at the
422 * root of the tree if the input object match the input of the tree viewer.
425 * @param compareInputAdapter
428 private Object
[] getCompareInputAdapterChildren(CompareInputAdapter compareInputAdapter
) {
429 Notifier target
= compareInputAdapter
.getTarget();
430 if (target
instanceof TreeNode
) {
431 TreeNode treeNode
= (TreeNode
)target
;
432 if (treeNode
.getData() instanceof Comparison
) {
433 IDifferenceGroupProvider2 groupProvider2
= getGroupProvider2(treeNode
);
434 // Handles the first initialisation of the groups.
437 if (groupProvider2
!= null && !groupProvider2
.groupsAreBuilt()) {
438 return deferReturnChildren(compareInputAdapter
);
445 return super.getChildren(compareInputAdapter
.getTarget());
448 private Object
[] deferReturnChildren(CompareInputAdapter compareInputAdapter
) {
449 if (!isFetchingGroup
) {
450 isFetchingGroup
= true;
452 * Notifies listeners that the content provider starts fetching here and not in
453 * EMFCompareStructureMergeViewerContentProvider#aboutToRun() since it is only notified on the
454 * "clear pending updater" job events and not on the "fetching children" job events.
455 * @see org.eclipse.ui.progress.DeferredTreeContentManager.runClearPlaceholderJob(
456 * PendingUpdateAdapter)
458 for (FetchListener callback
: listeners
) {
459 callback
.startFetching();
461 compareInputAdapter
.setDeferredAdapter(
462 new EMFCompareStructureMergeViewerContentProviderDeferredAdapter(this));
463 pending
= contentManagerAdapter
.getChildren(compareInputAdapter
);
468 private IDifferenceGroupProvider2
getGroupProvider2(TreeNode treeNode
) {
469 IDifferenceGroupProvider2 result
= null;
470 Optional
<Adapter
> searchResult
= Iterables
.tryFind(treeNode
.eAdapters(),
471 Predicates
.instanceOf(IDifferenceGroupProvider2
.class));
472 if (searchResult
.isPresent()) {
473 return (IDifferenceGroupProvider2
)searchResult
.get();
481 * @see org.eclipse.emf.edit.ui.provider.AdapterFactoryContentProvider#getElements(Object object)
484 public Object
[] getElements(Object element
) {
485 return getChildren(element
);
491 * @see IContentProvider#dispose()
494 public void dispose() {
496 contentManagerAdapter
.removeUpdateCompleteListener(this);
501 * This implementation specializes the EMF implementation to ensure that if more than 30 notifications
502 * arrive, the viewer is simply refreshed rather than processing each update separately. This is
503 * especially important for when undo is invoked in editor and there are many things to be undone.
507 * @see IContentProvider#dispose()
510 public void notifyChanged(Notification notification
) {
511 if (viewer
!= null && viewer
.getControl() != null && !viewer
.getControl().isDisposed()) {
512 if (notification
instanceof IViewerNotification
) {
513 if (viewerRefresh
== null) {
514 viewerRefresh
= new ViewerRefresh(viewer
) {
518 public synchronized boolean addNotification(IViewerNotification notification
) {
519 if (super.addNotification(notification
)) {
524 // When there are more than 30 notifications, it's probably cheaper to simply
525 // refresh the overall view.
527 super.addNotification(new ViewerNotification(notification
, null, true, true));
536 if (viewerRefresh
.addNotification((IViewerNotification
)notification
)) {
537 viewer
.getControl().getDisplay().asyncExec(viewerRefresh
);
540 NotifyChangedToViewerRefresh
.handleNotifyChanged(viewer
, notification
.getNotifier(),
541 notification
.getEventType(), notification
.getFeature(), notification
.getOldValue(),
542 notification
.getNewValue(), notification
.getPosition());
548 * Adapts each elements of the the given <code>iterable</code> to the given <code>type</code> by using the
549 * given <code>adapterFactory</code>.
552 * the type of returned elements.
554 * the iterable to transform.
555 * @param adapterFactory
556 * the {@link AdapterFactory} used to adapt elements.
558 * the target type of adapted elements.
559 * @return an iterable with element of type <code>type</code>.
561 private static Iterable
<?
> adapt(Iterable
<?
> iterable
, final AdapterFactory adapterFactory
,
562 final Class
<?
> type
) {
563 Function
<Object
, Object
> adaptFunction
= new Function
<Object
, Object
>() {
564 public Object
apply(Object input
) {
565 Object ret
= adapterFactory
.adapt(input
, type
);
572 return transform(iterable
, adaptFunction
);
576 * Adapts each elements of the the given <code>array</code> to the given <code>type</code> by using the
577 * given <code>adapterFactory</code>.
580 * the type of returned elements.
582 * the array to transform.
583 * @param adapterFactory
584 * the {@link AdapterFactory} used to adapt elements.
586 * the target type of adapted elements
587 * @return an iterable with element of type <code>type</code>.
589 private static Iterable
<?
> adapt(Object
[] iterable
, final AdapterFactory adapterFactory
,
590 final Class
<?
> type
) {
591 return adapt(Arrays
.asList(iterable
), adapterFactory
, type
);
597 * @see IJobChangeListener{@link #aboutToRun(IJobChangeEvent)
599 public void aboutToRun(IJobChangeEvent event
) {
601 * Nothing to do here since it has already been done in
602 * EMFCompareStructureMergeViewerContentProvider#getCompareInputAdapterChildren(CompareInputAdapter
603 * compareInputAdapter)
610 * @see IJobChangeListener#awake(IJobChangeEvent)
612 public void awake(IJobChangeEvent event
) {
619 * @see IJobChangeListener#done(IJobChangeEvent)
621 public void done(IJobChangeEvent event
) {
622 if (event
.getResult().isOK()) {
623 // Prevents running callbacks while another thread add a new callback or another thread launch a
627 if (isFetchingGroup
) {
628 isFetchingGroup
= false;
630 for (FetchListener listener
: listeners
) {
631 listener
.doneFetching();
632 // If the listener starts to fetch again then stop notifying listeners and wait for
633 // the content provider to be ready before re-starting.
634 if (isFetchingGroup
) {
639 final Iterator
<CallbackHolder
> callbacksIterator
= callbacks
.iterator();
641 while (callbacksIterator
.hasNext()) {
642 CallbackHolder callbackHolder
= callbacksIterator
.next();
643 run(callbackHolder
.getCallback(), callbackHolder
.getType());
644 // If the callback has started to fetch again the stop running callbacks and wait for
645 // the content provider to be ready.
646 if (isFetchingGroup
) {
647 List
<CallbackHolder
> remainingCallBack
= Lists
.newArrayList(callbacksIterator
);
648 callbacks
= new CopyOnWriteArrayList
<EMFCompareStructureMergeViewerContentProvider
.CallbackHolder
>(
665 * @see IJobChangeListener#running(IJobChangeEvent)
667 public void running(IJobChangeEvent event
) {
674 * @see IJobChangeListener#scheduled(IJobChangeEvent)
676 public void scheduled(IJobChangeEvent event
) {
683 * @see IJobChangeListener#sleeping(IJobChangeEvent)
685 public void sleeping(IJobChangeEvent event
) {