org.eclipse.emf.compare.ide.ui.internal.structuremergeviewer.EMFCompareStructureMergeViewerContentProvider.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.emf.compare.ide.ui.internal.structuremergeviewer.EMFCompareStructureMergeViewerContentProvider.java

Source

/*******************************************************************************
 * Copyright (c) 2012, 2016 Obeo and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 * 
 * Contributors:
 *     Obeo - initial API and implementation
 *     Michael Borkowski - public CallbackType visibility for testing
 *     Stefan Dirix - bug 473985
 *******************************************************************************/
package org.eclipse.emf.compare.ide.ui.internal.structuremergeviewer;

import static com.google.common.base.Predicates.instanceOf;
import static com.google.common.collect.Iterables.toArray;
import static com.google.common.collect.Iterables.transform;

import com.google.common.base.Function;
import com.google.common.base.Optional;
import com.google.common.base.Predicates;
import com.google.common.collect.Iterables;
import com.google.common.collect.Iterators;
import com.google.common.collect.Lists;

import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.locks.ReentrantLock;

import org.eclipse.compare.structuremergeviewer.ICompareInput;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.jobs.IJobChangeEvent;
import org.eclipse.core.runtime.jobs.IJobChangeListener;
import org.eclipse.core.runtime.jobs.ISchedulingRule;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.emf.common.notify.Adapter;
import org.eclipse.emf.common.notify.AdapterFactory;
import org.eclipse.emf.common.notify.Notifier;
import org.eclipse.emf.compare.Comparison;
import org.eclipse.emf.compare.ide.ui.internal.EMFCompareIDEUIMessages;
import org.eclipse.emf.compare.ide.ui.internal.treecontentmanager.EMFCompareDeferredTreeContentManager;
import org.eclipse.emf.compare.ide.ui.internal.treecontentmanager.EMFCompareDeferredTreeContentManagerUtil;
import org.eclipse.emf.compare.rcp.ui.internal.util.SWTUtil;
import org.eclipse.emf.compare.rcp.ui.structuremergeviewer.groups.IDifferenceGroupProvider2;
import org.eclipse.emf.edit.tree.TreeNode;
import org.eclipse.emf.edit.ui.provider.AdapterFactoryContentProvider;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.viewers.AbstractTreeViewer;
import org.eclipse.jface.viewers.IContentProvider;
import org.eclipse.ui.progress.DeferredTreeContentManager;
import org.eclipse.ui.progress.IDeferredWorkbenchAdapter;
import org.eclipse.ui.progress.IElementCollector;
import org.eclipse.ui.progress.PendingUpdateAdapter;

/**
 * Specialized AdapterFactoryContentProvider for the emf compare structure merge viewer.
 * <p>
 * <i>This class is not intended to be used outside of its package. It has been set to public for testing
 * purpose only.</i>
 * </p>
 * 
 * @author <a href="mailto:mikael.barbero@obeo.fr">Mikael Barbero</a>
 */
public class EMFCompareStructureMergeViewerContentProvider extends AdapterFactoryContentProvider
        implements IJobChangeListener {

    /**
     * Class to listen the state of the content provider.
     * 
     * @author <a href="mailto:arthur.daussy@obeo.fr">Arthur Daussy</a>
     */
    static class FetchListener {

        /**
         * This method is called when the content provider starts fetching new elements in an external Job.
         */
        public void startFetching() {

        }

        /**
         * This method is called when the content provider has finished fetching elements and has removed the
         * {@link PendingUpdateAdapter} from the tree.
         */
        public void doneFetching() {

        }
    }

    /**
     * Callback holder used to defer the run of a callback in a specific thread.
     * 
     * @see {@link #callback}
     * @see EMFCompareStructureMergeViewerContentProvider#runWhenReady(CallbackType, Runnable)
     */
    private static class CallbackHolder {

        private final Runnable callback;

        private final CallbackType callbackType;

        public CallbackHolder(Runnable callback, CallbackType callbackType) {
            super();
            this.callback = callback;
            this.callbackType = callbackType;
        }

        private Runnable getCallback() {
            return callback;
        }

        private CallbackType getType() {
            return callbackType;
        }

    }

    /**
     * {@link IDeferredWorkbenchAdapter} using this content provider to fetch children.
     * 
     * @author <a href="mailto:arthur.daussy@obeo.fr">Arthur Daussy</a>
     */
    private static class EMFCompareStructureMergeViewerContentProviderDeferredAdapter
            implements IDeferredWorkbenchAdapter {

        private final EMFCompareStructureMergeViewerContentProvider contentProvider;

        public EMFCompareStructureMergeViewerContentProviderDeferredAdapter(
                EMFCompareStructureMergeViewerContentProvider contentProvider) {
            super();
            this.contentProvider = contentProvider;
        }

        /**
         * {@inheritDoc}
         * 
         * @see {IDeferredWorkbenchAdapter{@link IDeferredWorkbenchAdapter#getChildren(Object)}
         */
        public Object[] getChildren(Object o) {
            // Not used
            return null;
        }

        /**
         * {@inheritDoc}
         * 
         * @see {IDeferredWorkbenchAdapter{@link IDeferredWorkbenchAdapter#getImageDescriptor(Object)}
         */
        public ImageDescriptor getImageDescriptor(Object object) {
            // Not used
            return null;
        }

        /**
         * {@inheritDoc}
         * 
         * @see {IDeferredWorkbenchAdapter{@link IDeferredWorkbenchAdapter#getLabel(Object)}
         */
        public String getLabel(Object o) {
            return EMFCompareIDEUIMessages
                    .getString("EMFCompareStructureMergeViewerContentProvider.deferredWorkbenchAdapter.label"); //$NON-NLS-1$
        }

        /**
         * {@inheritDoc}
         * 
         * @see {IDeferredWorkbenchAdapter{@link IDeferredWorkbenchAdapter#getParent(Object)}
         */
        public Object getParent(Object o) {
            // Not used
            return null;
        }

        /**
         * {@inheritDoc}
         * 
         * @see {IDeferredWorkbenchAdapter
         *      {@link IDeferredWorkbenchAdapter#fetchDeferredChildren(Object object, IElementCollector collector, IProgressMonitor monitor)}
         */
        public void fetchDeferredChildren(Object object, IElementCollector collector, IProgressMonitor monitor) {
            if (!monitor.isCanceled()) {
                if (object instanceof CompareInputAdapter) {
                    Notifier target = ((Adapter) object).getTarget();
                    Object[] children = contentProvider.getChildren(target);
                    collector.add(children, monitor);
                }
            }

        }

        /**
         * {@inheritDoc}
         * 
         * @see {IDeferredWorkbenchAdapter{@link IDeferredWorkbenchAdapter#isContainer()}
         */
        public boolean isContainer() {
            // Not used
            return true;
        }

        /**
         * {@inheritDoc}
         * 
         * @see {IDeferredWorkbenchAdapter{@link #getRule(Object)}
         */
        public ISchedulingRule getRule(Object object) {
            // Not used
            return null;
        }

    }

    /** {@link DeferredTreeContentManager} use to fetch groups in a external {@link Job}. */
    private final EMFCompareDeferredTreeContentManager contentManagerAdapter;

    /** Holds true if this content provider is currently fetching children. */
    private boolean isFetchingGroup;

    /** Will protect R/W of {@link #pending} and {@link #isFetchingGroup}. */
    private final ReentrantLock lock;

    /** Object listening the status of this object. */
    private final List<FetchListener> listeners;

    /** List of current callbacks. Callbacks are only run once. */
    private List<CallbackHolder> callbacks;

    /** Pending object displayed in the tree. */
    private Object[] pending;

    /**
     * Constructs the content provider with the appropriate adapter factory.
     * 
     * @param adapterFactory
     *            The adapter factory used to construct the content provider.
     */
    public EMFCompareStructureMergeViewerContentProvider(AdapterFactory adapterFactory, AbstractTreeViewer viewer) {
        super(adapterFactory);
        contentManagerAdapter = EMFCompareDeferredTreeContentManagerUtil
                .createEMFDeferredTreeContentManager(viewer);
        contentManagerAdapter.addUpdateCompleteListener(this);
        lock = new ReentrantLock();
        listeners = new CopyOnWriteArrayList<EMFCompareStructureMergeViewerContentProvider.FetchListener>();
        callbacks = new CopyOnWriteArrayList<EMFCompareStructureMergeViewerContentProvider.CallbackHolder>();

    }

    /**
     * {@inheritDoc}
     * 
     * @see org.eclipse.emf.edit.ui.provider.AdapterFactoryContentProvider#getParent(Object object)
     */
    @Override
    public Object getParent(Object element) {
        final Object ret;
        if (element instanceof CompareInputAdapter) {
            Object parentNode = super.getParent(((Adapter) element).getTarget());
            if (parentNode instanceof TreeNode) {
                final Optional<Adapter> cia = Iterators.tryFind(((TreeNode) parentNode).eAdapters().iterator(),
                        instanceOf(CompareInputAdapter.class));
                if (cia.isPresent()) {
                    ret = cia.get();
                } else {
                    ret = parentNode;
                }
            } else {
                ret = parentNode;
            }
        } else if (element instanceof ICompareInput) {
            ret = null;
        } else {
            ret = super.getParent(element);
        }
        return ret;
    }

    /**
     * Enum used for better readability of the method
     * {@link EMFCompareStructureMergeViewerContentProvider#runWhenReady(CallbackType, Runnable)}.
     * 
     * @author <a href="mailto:arthur.daussy@obeo.fr">Arthur Daussy</a>
     */
    // public for testing
    public static enum CallbackType {
        /** Run the runnable in the UI thread synchronously. */
        IN_UI_SYNC,
        /** Run the runnable in the UI thread asynchronously. */
        IN_UI_ASYNC,
        /** Run the runnable in the current thread. */
        IN_CURRENT_THREAD
    }

    /**
     * Run the given runnable in the specified thread when then content provider is ready. It can be run
     * directly if the content provider is not fecthing or during a callback when the content provider is done
     * fetching.
     * 
     * @param type
     *            of thread to run the {@link Runnable} inside.
     * @param runnable
     *            to run
     */
    public void runWhenReady(CallbackType type, final Runnable runnable) {
        // Prevents adding a callback if another thread set this content provider as not fetching.
        lock.lock();
        try {
            if (isFetchingGroup) {
                callbacks.add(new CallbackHolder(runnable, type));
            } else {
                run(runnable, type);
            }
        } finally {
            lock.unlock();
        }
    }

    /**
     * Runs a callback in the related thread.
     * 
     * @param callback
     *            to run.
     * @param type
     *            of thread.
     */
    private void run(Runnable callback, CallbackType type) {
        switch (type) {
        case IN_UI_SYNC:
            SWTUtil.safeSyncExec(callback);
            break;
        case IN_UI_ASYNC:
            SWTUtil.safeAsyncExec(callback);
            break;
        default:
            callback.run();
            break;
        }
    }

    /**
     * Adds a listener to this content provider.
     * 
     * @param listener
     *            to add
     * @return
     */
    public boolean addFetchingListener(FetchListener listener) {
        return listeners.add(listener);
    }

    /**
     * Removes a listener to this content provider.
     * 
     * @param listener
     *            to remove
     * @return
     */
    public boolean removeFetchingListener(FetchListener listener) {
        return listeners.remove(listener);
    }

    /**
     * {@inheritDoc}
     * 
     * @see org.eclipse.emf.edit.ui.provider.AdapterFactoryContentProvider#hasChildren(Object object)
     */
    @Override
    public final boolean hasChildren(Object element) {
        final boolean ret;
        if (element instanceof CompareInputAdapter) {
            ret = super.hasChildren(((Adapter) element).getTarget());
        } else if (element instanceof ICompareInput) {
            ret = false;
        } else {
            ret = super.hasChildren(element);
        }
        return ret;
    }

    /**
     * {@inheritDoc}
     * 
     * @see org.eclipse.emf.edit.ui.provider.AdapterFactoryContentProvider#getChildren(java.lang.Object)
     */
    @Override
    public final Object[] getChildren(Object element) {
        Object[] children;
        if (element instanceof CompareInputAdapter) {
            children = getCompareInputAdapterChildren((CompareInputAdapter) element);
        } else if (element instanceof ICompareInput) {
            children = new Object[] {};
        } else {
            children = super.getChildren(element);
        }

        final Object[] compareInputChildren;
        // Avoid NPE.
        if (children == null) {
            children = new Object[] {};
        }
        // Do not adapt if it's a pending updater
        if (!Iterables.all(Arrays.asList(children), Predicates.instanceOf(PendingUpdateAdapter.class))) {
            Iterable<?> compareInputs = adapt(children, getAdapterFactory(), ICompareInput.class);
            compareInputChildren = toArray(compareInputs, Object.class);
        } else {
            compareInputChildren = children;
        }
        return compareInputChildren;
    }

    /**
     * Returns a {@link PendingUpdateAdapter} while a Job is fetching the children for this object or the
     * children if they have already been fetched.
     * <p>
     * When the job is finished it will autamically replace the {@link PendingUpdateAdapter} by the fetched
     * children. The fetched children will be stored under the TreeItem holding the input object or at the
     * root of the tree if the input object match the input of the tree viewer.
     * </p>
     * 
     * @param compareInputAdapter
     * @return
     */
    private Object[] getCompareInputAdapterChildren(CompareInputAdapter compareInputAdapter) {
        Notifier target = compareInputAdapter.getTarget();
        if (target instanceof TreeNode) {
            TreeNode treeNode = (TreeNode) target;
            if (treeNode.getData() instanceof Comparison) {
                IDifferenceGroupProvider2 groupProvider2 = getGroupProvider2(treeNode);
                // Handles the first initialisation of the groups.
                lock.lock();
                try {
                    if (groupProvider2 != null && !groupProvider2.groupsAreBuilt()) {
                        return deferReturnChildren(compareInputAdapter);
                    }
                } finally {
                    lock.unlock();
                }
            }
        }
        return super.getChildren(compareInputAdapter.getTarget());
    }

    private Object[] deferReturnChildren(CompareInputAdapter compareInputAdapter) {
        if (!isFetchingGroup) {
            isFetchingGroup = true;
            /*
             * Notifies listeners that the content provider starts fetching here and not in
             * EMFCompareStructureMergeViewerContentProvider#aboutToRun() since it is only notified on the
             * "clear pending updater" job events and not on the "fetching children" job events.
             * @see org.eclipse.ui.progress.DeferredTreeContentManager.runClearPlaceholderJob(
             * PendingUpdateAdapter)
             */
            for (FetchListener callback : listeners) {
                callback.startFetching();
            }
            compareInputAdapter
                    .setDeferredAdapter(new EMFCompareStructureMergeViewerContentProviderDeferredAdapter(this));
            pending = contentManagerAdapter.getChildren(compareInputAdapter);
        }
        return pending;
    }

    private IDifferenceGroupProvider2 getGroupProvider2(TreeNode treeNode) {
        IDifferenceGroupProvider2 result = null;
        Optional<Adapter> searchResult = Iterables.tryFind(treeNode.eAdapters(),
                Predicates.instanceOf(IDifferenceGroupProvider2.class));
        if (searchResult.isPresent()) {
            return (IDifferenceGroupProvider2) searchResult.get();
        }
        return result;
    }

    /**
     * {@inheritDoc}
     * 
     * @see org.eclipse.emf.edit.ui.provider.AdapterFactoryContentProvider#getElements(Object object)
     */
    @Override
    public Object[] getElements(Object element) {
        return getChildren(element);
    }

    /**
     * {@inheritDoc}
     * 
     * @see IContentProvider#dispose()
     */
    @Override
    public void dispose() {
        super.dispose();
        contentManagerAdapter.removeUpdateCompleteListener(this);
        listeners.clear();
    }

    /**
     * Adapts each elements of the the given <code>iterable</code> to the given <code>type</code> by using the
     * given <code>adapterFactory</code>.
     * 
     * @param <T>
     *            the type of returned elements.
     * @param iterable
     *            the iterable to transform.
     * @param adapterFactory
     *            the {@link AdapterFactory} used to adapt elements.
     * @param type
     *            the target type of adapted elements.
     * @return an iterable with element of type <code>type</code>.
     */
    private static Iterable<?> adapt(Iterable<?> iterable, final AdapterFactory adapterFactory,
            final Class<?> type) {
        Function<Object, Object> adaptFunction = new Function<Object, Object>() {
            public Object apply(Object input) {
                Object ret = adapterFactory.adapt(input, type);
                if (ret == null) {
                    return input;
                }
                return ret;
            }
        };
        return transform(iterable, adaptFunction);
    }

    /**
     * Adapts each elements of the the given <code>array</code> to the given <code>type</code> by using the
     * given <code>adapterFactory</code>.
     * 
     * @param <T>
     *            the type of returned elements.
     * @param iterable
     *            the array to transform.
     * @param adapterFactory
     *            the {@link AdapterFactory} used to adapt elements.
     * @param type
     *            the target type of adapted elements
     * @return an iterable with element of type <code>type</code>.
     */
    private static Iterable<?> adapt(Object[] iterable, final AdapterFactory adapterFactory, final Class<?> type) {
        return adapt(Arrays.asList(iterable), adapterFactory, type);
    }

    /**
     * {@inheritDoc}
     * 
     * @see IJobChangeListener{@link #aboutToRun(IJobChangeEvent)
     */
    public void aboutToRun(IJobChangeEvent event) {
        /*
         * Nothing to do here since it has already been done in
         * EMFCompareStructureMergeViewerContentProvider#getCompareInputAdapterChildren(CompareInputAdapter
         * compareInputAdapter)
         */
    }

    /**
     * {@inheritDoc}
     * 
     * @see IJobChangeListener#awake(IJobChangeEvent)
     */
    public void awake(IJobChangeEvent event) {
        // Nothing to do
    }

    /**
     * {@inheritDoc}
     * 
     * @see IJobChangeListener#done(IJobChangeEvent)
     */
    public void done(IJobChangeEvent event) {
        if (event.getResult().isOK()) {
            // Prevents running callbacks while another thread add a new callback or another thread launch a
            // fetching job.
            lock.lock();
            try {
                if (isFetchingGroup) {
                    isFetchingGroup = false;
                    pending = null;
                    for (FetchListener listener : listeners) {
                        listener.doneFetching();
                        // If the listener starts to fetch again then stop notifying listeners and wait for
                        // the content provider to be ready before re-starting.
                        if (isFetchingGroup) {
                            return;
                        }
                    }

                    final Iterator<CallbackHolder> callbacksIterator = callbacks.iterator();

                    while (callbacksIterator.hasNext()) {
                        CallbackHolder callbackHolder = callbacksIterator.next();
                        run(callbackHolder.getCallback(), callbackHolder.getType());
                        // If the callback has started to fetch again the stop running callbacks and wait for
                        // the content provider to be ready.
                        if (isFetchingGroup) {
                            List<CallbackHolder> remainingCallBack = Lists.newArrayList(callbacksIterator);
                            callbacks = new CopyOnWriteArrayList<EMFCompareStructureMergeViewerContentProvider.CallbackHolder>(
                                    remainingCallBack);
                            return;
                        }
                    }

                    callbacks.clear();
                }
            } finally {
                lock.unlock();
            }
        }
    }

    /**
     * {@inheritDoc}
     * 
     * @see IJobChangeListener#running(IJobChangeEvent)
     */
    public void running(IJobChangeEvent event) {
        // Nothing to do
    }

    /**
     * {@inheritDoc}
     * 
     * @see IJobChangeListener#scheduled(IJobChangeEvent)
     */
    public void scheduled(IJobChangeEvent event) {
        // Nothing to do
    }

    /**
     * {@inheritDoc}
     * 
     * @see IJobChangeListener#sleeping(IJobChangeEvent)
     */
    public void sleeping(IJobChangeEvent event) {
        // Nothing to do
    }

}