at.bitandart.zoubek.mervin.review.ReviewExplorer.java Source code

Java tutorial

Introduction

Here is the source code for at.bitandart.zoubek.mervin.review.ReviewExplorer.java

Source

/*******************************************************************************
 * Copyright (c) 2015, 2016 Florian Zoubek.
 * 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:
 *    Florian Zoubek - initial API and implementation
 *******************************************************************************/
package at.bitandart.zoubek.mervin.review;

import java.text.MessageFormat;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import javax.annotation.PostConstruct;
import javax.inject.Inject;

import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.e4.ui.model.application.ui.basic.MPart;
import org.eclipse.e4.ui.model.application.ui.menu.MHandledMenuItem;
import org.eclipse.e4.ui.workbench.modeling.EModelService;
import org.eclipse.e4.ui.workbench.modeling.ESelectionService;
import org.eclipse.e4.ui.workbench.modeling.ElementMatcher;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.compare.Diff;
import org.eclipse.emf.compare.Match;
import org.eclipse.emf.compare.utils.MatchUtil;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.edit.provider.ComposedAdapterFactory;
import org.eclipse.emf.edit.ui.provider.AdapterFactoryContentProvider;
import org.eclipse.emf.edit.ui.provider.AdapterFactoryLabelProvider;
import org.eclipse.gmf.runtime.notation.View;
import org.eclipse.jface.util.Policy;
import org.eclipse.jface.viewers.ColumnLabelProvider;
import org.eclipse.jface.viewers.ContentViewer;
import org.eclipse.jface.viewers.ILabelProviderListener;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StyledCellLabelProvider;
import org.eclipse.jface.viewers.StyledString;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.TreeViewerColumn;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerCell;
import org.eclipse.jface.viewers.ViewerComparator;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeColumn;

import at.bitandart.zoubek.mervin.IReviewHighlightService;
import at.bitandart.zoubek.mervin.IReviewHighlightServiceListener;
import at.bitandart.zoubek.mervin.model.modelreview.DiagramPatch;
import at.bitandart.zoubek.mervin.model.modelreview.DiagramResource;
import at.bitandart.zoubek.mervin.model.modelreview.ModelPatch;
import at.bitandart.zoubek.mervin.model.modelreview.ModelResource;
import at.bitandart.zoubek.mervin.model.modelreview.ModelReview;
import at.bitandart.zoubek.mervin.model.modelreview.Patch;
import at.bitandart.zoubek.mervin.model.modelreview.PatchSet;
import at.bitandart.zoubek.mervin.patchset.history.HighlightStyler;
import at.bitandart.zoubek.mervin.patchset.history.IPatchSetHistoryEntry;
import at.bitandart.zoubek.mervin.patchset.history.ISimilarityHistoryService.DiffWithSimilarity;
import at.bitandart.zoubek.mervin.util.vis.HSB;
import at.bitandart.zoubek.mervin.util.vis.NumericColoredColumnLabelProvider;
import at.bitandart.zoubek.mervin.util.vis.ThreeWayLabelTreeViewerComparator;
import at.bitandart.zoubek.mervin.util.vis.ThreeWayObjectTreeViewerComparator;

/**
 * Review exploration view - shows an overview of the currently loaded model
 * review provided by the last active editor.
 * 
 * <p>
 * This class adapts to the following classes:
 * <ul>
 * <li>{@link ModelReview} - the current model review instance</li>
 * </ul>
 * </p>
 * 
 * @author Florian Zoubek
 * 
 * @see ModelReviewEditorTrackingView
 *
 */
public class ReviewExplorer extends ModelReviewEditorTrackingView
        implements IReviewHighlightProvidingPart, IAdaptable {

    public static final String PART_DESCRIPTOR_ID = "at.bitandart.zoubek.mervin.partdescriptor.review";

    public static final String VIEW_MENU_ID = "at.bitandart.zoubek.mervin.menu.view.review.explorer";

    public static final String VIEW_MENU_ITEM_HIGHLIGHT_SWITCH_MODE = "at.bitandart.zoubek.mervin.menu.view.review.explorer.highlight.switchmode";

    @Inject
    private ESelectionService selectionService;

    @Inject
    private IReviewHighlightService highlightService;

    @Inject
    private Display display;

    private HighlightStyler highlightStyler;

    /**
     * the complete list of filtered and derived elements to highlight in this
     * view.
     */
    private List<Object> objectsToHighlight = new LinkedList<>();

    /**
     * the current highlight mode, never null
     */
    private HighlightMode highlightMode = HighlightMode.SELECTION;

    // JFace Viewers

    /**
     * tree viewer that shows the overview of the model review
     */
    private TreeViewer reviewTreeViewer;

    // SWT Controls

    /**
     * the main panel of this view, that contains all controls for this view
     */
    private Composite mainPanel;

    // Data

    /**
     * indicates if all SWT controls and viewers have been correctly set up
     */
    private boolean viewInitialized = false;

    @PostConstruct
    public void postConstruct(Composite parent, EModelService modelService, MPart part) {

        syncMenuAndToolbarItemState(modelService, part);

        highlightStyler = new HighlightStyler(display);

        mainPanel = new Composite(parent, SWT.NONE);
        mainPanel.setLayout(new GridLayout());

        // initialize tree viewer

        reviewTreeViewer = new TreeViewer(mainPanel, SWT.SINGLE | SWT.H_SCROLL | SWT.V_SCROLL);
        reviewTreeViewer.setComparator(new ViewerComparator());
        reviewTreeViewer.setContentProvider(new ModelReviewContentProvider());
        reviewTreeViewer.addSelectionChangedListener(new HighlightSelectionListener(this) {

            @Override
            public void selectionChanged(SelectionChangedEvent event) {

                super.selectionChanged(event);
                ISelection selection = event.getSelection();
                selectionService.setSelection(selection);
            }
        });

        Tree reviewTree = reviewTreeViewer.getTree();
        reviewTree.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
        reviewTree.setLinesVisible(false);
        reviewTree.setHeaderVisible(true);
        reviewTree.addMouseTrackListener(new HighlightHoveredTreeItemMouseTracker(this));

        // set up all columns of the tree

        // main label column
        TreeViewerColumn labelColumn = new TreeViewerColumn(reviewTreeViewer, SWT.NONE);
        labelColumn.getColumn().setResizable(true);
        labelColumn.getColumn().setMoveable(true);
        labelColumn.getColumn().setText("Element");
        labelColumn.getColumn().setWidth(200);
        ModelReviewExplorerMainColumnLabelProvider labelColumnLabelProvider = new ModelReviewExplorerMainColumnLabelProvider();
        labelColumn.setLabelProvider(labelColumnLabelProvider);
        labelColumn.getColumn().addSelectionListener(
                new ThreeWayObjectTreeViewerComparator(reviewTreeViewer, labelColumn, labelColumnLabelProvider));

        // change count column
        TreeViewerColumn changeCountColumn = new TreeViewerColumn(reviewTreeViewer, SWT.NONE);
        changeCountColumn.getColumn().setResizable(true);
        changeCountColumn.getColumn().setMoveable(false);
        changeCountColumn.getColumn().setText("#C");
        changeCountColumn.getColumn().setAlignment(SWT.CENTER);
        changeCountColumn.getColumn().setToolTipText("Number of changed elements");
        ChangeCountColumnLabelProvider changeCountColumnLabelProvider = new ChangeCountColumnLabelProvider(
                reviewTreeViewer, Display.getCurrent().getSystemColor(SWT.COLOR_WHITE),
                Display.getCurrent().getSystemColor(SWT.COLOR_BLACK));
        changeCountColumn.setLabelProvider(changeCountColumnLabelProvider);
        changeCountColumn.getColumn().addSelectionListener(new ThreeWayLabelTreeViewerComparator(reviewTreeViewer,
                changeCountColumn, changeCountColumnLabelProvider));

        // reference count column
        TreeViewerColumn refCountColumn = new TreeViewerColumn(reviewTreeViewer, SWT.NONE);
        refCountColumn.getColumn().setResizable(true);
        refCountColumn.getColumn().setMoveable(false);
        refCountColumn.getColumn().setText("#RC");
        refCountColumn.getColumn().setAlignment(SWT.CENTER);
        refCountColumn.getColumn().setToolTipText("Number of references to the tgiven elements");
        ReferencedChangeCountColumnLabelProvider refChangeCountColumnlabelProvider = new ReferencedChangeCountColumnLabelProvider(
                reviewTreeViewer, Display.getCurrent().getSystemColor(SWT.COLOR_WHITE),
                Display.getCurrent().getSystemColor(SWT.COLOR_BLACK));
        refCountColumn.setLabelProvider(refChangeCountColumnlabelProvider);
        refCountColumn.getColumn().addSelectionListener(new ThreeWayLabelTreeViewerComparator(reviewTreeViewer,
                refCountColumn, refChangeCountColumnlabelProvider));

        // the resource column
        TreeViewerColumn resourceColumn = new TreeViewerColumn(reviewTreeViewer, SWT.NONE);
        resourceColumn.getColumn().setResizable(true);
        resourceColumn.getColumn().setMoveable(true);
        resourceColumn.getColumn().setText("Resource");
        resourceColumn.getColumn().setWidth(200);
        ModelReviewExplorerResourceColumnLabelProvider resourceColumnLabelProvider = new ModelReviewExplorerResourceColumnLabelProvider();
        resourceColumn.setLabelProvider(resourceColumnLabelProvider);
        resourceColumn.getColumn().addSelectionListener(new ThreeWayLabelTreeViewerComparator(reviewTreeViewer,
                resourceColumn, resourceColumnLabelProvider));

        // all controls updated, now update them with the given values
        viewInitialized = true;

        // refresh the viewer highlights if highlighting is requested
        highlightService.addHighlightServiceListener(new IReviewHighlightServiceListener() {

            @Override
            public void elementRemoved(ModelReview review, Object element) {

                updatesObjectToHighlight();
                reviewTreeViewer.refresh();

            }

            @Override
            public void elementAdded(ModelReview review, Object element) {

                updatesObjectToHighlight();
                reviewTreeViewer.refresh();

            }
        });

        updateValues();
    }

    /**
     * updates the list of filtered and derived highlighted elements from the
     * current highlight service for the current model review.
     */
    private void updatesObjectToHighlight() {

        objectsToHighlight.clear();
        ModelReview currentModelReview = getCurrentModelReview();

        if (currentModelReview != null) {

            List<Object> highlightedElements = highlightService.getHighlightedElements(getCurrentModelReview());
            // TODO apply filter
            objectsToHighlight.addAll(highlightedElements);

            addDerivedElementsToHighlight(currentModelReview, highlightedElements, objectsToHighlight);
        }

    }

    /**
     * adds the derived objects to highlight for the given {@link ModelReview}
     * {@link Diff} to the given list of highlighted objects.
     * 
     * @param modelReview
     *            the model review to highlight elements for.
     * @param highlightedElements
     *            the highlighted elements as reported by the highlight service.
     * @param objectsToHighlight
     *            the list of elements to add the derived highlighted elements
     *            to.
     */
    protected void addDerivedElementsToHighlight(ModelReview modelReview, List<Object> highlightedElements,
            List<Object> objectsToHighlight) {

        for (Object highlightedElement : highlightedElements) {

            if (highlightedElement instanceof IPatchSetHistoryEntry<?, ?>) {

                IPatchSetHistoryEntry<?, ?> historyEntry = (IPatchSetHistoryEntry<?, ?>) highlightedElement;
                Object entryObject = historyEntry.getEntryObject();

                /* check the entry object first */
                if (entryObject instanceof Diff) {

                    // TODO apply filter
                    Diff diff = (Diff) entryObject;
                    addDerivedElementsToHighlight(diff, objectsToHighlight);
                }

                EList<PatchSet> patchSets = modelReview.getPatchSets();
                for (PatchSet patchSet : patchSets) {

                    Object value = historyEntry.getValue(patchSet);
                    if (value instanceof DiffWithSimilarity) {

                        // TODO apply filter
                        Diff diff = ((DiffWithSimilarity) value).getDiff();
                        // TODO apply filter
                        addDerivedElementsToHighlight(diff, objectsToHighlight);
                    }
                }
            }
        }
    }

    /**
     * adds the derived objects to highlight for the given highlighted
     * {@link Diff} to the given list of highlighted objects.
     * 
     * @param diff
     *            the highlighted diff to add derived highlighted objects to the
     *            given list of highlighted objects
     * @param objectsToHighlight
     */
    protected void addDerivedElementsToHighlight(Diff diff, List<Object> objectsToHighlight) {

        Match match = diff.getMatch();
        EObject left = match.getLeft();
        EObject right = match.getRight();
        Object value = MatchUtil.getValue(diff);
        // TODO apply filter
        if (value != null) {
            objectsToHighlight.add(value);
        }
        if (left != null) {
            objectsToHighlight.add(left);
        }
        if (right != null) {
            objectsToHighlight.add(right);
        }
    }

    /**
     * synchronizes the menu and toolbar item state of radio and check items
     * with this view.
     * 
     * @param modelService
     *            the service used to find the menu items
     * @param part
     *            the part containing the menu items
     */
    private void syncMenuAndToolbarItemState(EModelService modelService, MPart part) {

        /* for now, just enforce the default state */
        ElementMatcher matcher = new ElementMatcher(VIEW_MENU_ITEM_HIGHLIGHT_SWITCH_MODE, MHandledMenuItem.class,
                (List<String>) null);
        /*
         * IN_PART is not part of ANYWHERE, so use this variant of findElements
         * and pass IN_PART as search flag
         */
        List<MHandledMenuItem> items = modelService.findElements(part, MHandledMenuItem.class,
                EModelService.IN_PART, matcher);
        for (MHandledMenuItem item : items) {
            item.setSelected(false);
        }
    }

    @Override
    protected void updateValues() {

        // we cannot update the controls if they are not initialized yet
        if (viewInitialized) {

            updatesObjectToHighlight();
            ModelReview currentModelReview = getCurrentModelReview();

            // update the tree viewer
            reviewTreeViewer.setInput(currentModelReview);
            reviewTreeViewer.refresh();

            for (TreeColumn treeColumn : reviewTreeViewer.getTree().getColumns()) {
                treeColumn.pack();
            }

            reviewTreeViewer.getTree().layout();
            mainPanel.layout();
        }
    }

    @Override
    public void setHighlightMode(HighlightMode highlightMode) {

        if (highlightMode != null) {
            this.highlightMode = highlightMode;
            highlightService.clearHighlights(getCurrentModelReview());
        }

    }

    @Override
    public HighlightMode getHighlightMode() {
        return highlightMode;
    }

    @Override
    public IReviewHighlightService getReviewHighlightService() {
        return highlightService;
    }

    @Override
    public ModelReview getHighlightedModelReview() {
        return getCurrentModelReview();
    }

    @Override
    public <T> T getAdapter(Class<T> adapterType) {

        if (adapterType == ModelReview.class) {
            return adapterType.cast(getCurrentModelReview());
        }
        return null;
    }

    /**
     * {@link ColumnLabelProvider} for the main column in the model review tree.
     * 
     * @author Florian Zoubek
     *
     */
    private class ModelReviewExplorerMainColumnLabelProvider extends StyledCellLabelProvider
            implements Comparator<Object> {

        /**
         * label provider for model and diagram elements
         */
        private AdapterFactoryLabelProvider adapterFactoryLabelProvider;

        public ModelReviewExplorerMainColumnLabelProvider() {
            /*
             * Obtain the registered label providers for model and diagram
             * elements
             */
            adapterFactoryLabelProvider = new AdapterFactoryLabelProvider(
                    new ComposedAdapterFactory(ComposedAdapterFactory.Descriptor.Registry.INSTANCE));
        }

        @Override
        public void update(ViewerCell cell) {

            Object element = cell.getElement();
            StyledString text = new StyledString();
            if (isHighlighted(element, objectsToHighlight)) {
                text.append(getText(element), highlightStyler);
            } else {
                text.append(getText(element));
            }

            cell.setText(text.getString());
            cell.setStyleRanges(text.getStyleRanges());
            cell.setImage(getImage(element));

            super.update(cell);

        }

        /**
         * checks if the given element should be highlighted or not.
         * 
         * @param element
         *            the element to check.
         * @param highlightedElements
         *            the set of elements to highlight.
         * @return true if the given element should be highlighted, false
         *         otherwise.
         */
        private boolean isHighlighted(Object element, List<Object> highlightedElements) {

            if (highlightedElements.contains(element)) {
                return true;
            }

            if (element instanceof View) {
                if (isHighlighted(((View) element).getElement(), highlightedElements)) {
                    return true;
                }
            }

            if (element instanceof TreeItemContainer) {
                return isHighlighted(((TreeItemContainer) element).getChildren(), highlightedElements);
            }

            if (element instanceof PatchSet) {
                return isHighlighted(((PatchSet) element).getPatches(), highlightedElements);
            }

            if (element instanceof ModelPatch) {
                return isHighlighted(((ModelPatch) element).getNewModelResource(), highlightedElements)
                        || isHighlighted(((ModelPatch) element).getOldModelResource(), highlightedElements);
            }

            if (element instanceof DiagramPatch) {
                return isHighlighted(((DiagramPatch) element).getNewDiagramResource(), highlightedElements)
                        || isHighlighted(((DiagramPatch) element).getOldDiagramResource(), highlightedElements);
            }

            if (element instanceof ModelResource) {
                return isHighlighted(((ModelResource) element).getObjects(), highlightedElements);
            }

            if (element instanceof EObject) {
                return isHighlighted(((EObject) element).eContents(), highlightedElements);
            }

            return false;
        }

        /**
         * checks if one of the elements provided by the given iterable is
         * highlighted or not.
         * 
         * @param iterable
         *            the iterable that provides the elements.
         * @param highlightedElements
         *            the set of elements to highlight.
         * @return true if one of the elements provided by the given iterable
         *         should be highlighted, false otherwise.
         */
        public boolean isHighlighted(Iterable<?> iterable, List<Object> highlightedElements) {
            for (Object element : iterable) {
                if (isHighlighted(element, highlightedElements)) {
                    return true;
                }
            }
            return false;
        }

        /**
         * checks if one of the elements provided by the given array is
         * highlighted or not.
         * 
         * @param array
         *            the array that provides the elements.
         * @param highlightedElements
         *            the set of elements to highlight.
         * @return true if one of the elements provided by the given array
         *         should be highlighted, false otherwise.
         */
        public boolean isHighlighted(Object[] array, List<Object> highlightedElements) {
            for (Object element : array) {
                if (isHighlighted(element, highlightedElements)) {
                    return true;
                }
            }
            return false;
        }

        /**
         * @param element
         *            the element to retrieve the label text for.
         * @return the label text for the given element.
         */
        public String getText(Object element) {

            if (element instanceof PatchSet) {
                return MessageFormat.format("PatchSet #{0}", ((PatchSet) element).getId());
            }

            if (element instanceof TreeItemContainer) {
                return ((TreeItemContainer) element).getText();
            }

            if (element instanceof Patch) {
                return ((Patch) element).getNewPath();
            }

            if (element instanceof ModelResource) {
                return ((ModelResource) element).getRootPackages().get(0).getName();
            }

            if (element instanceof EObject) {
                return adapterFactoryLabelProvider.getText(element);
            }

            return element.toString();
        }

        /**
         * @param element
         *            the element to retrieve the label image for.
         * @return the label icon for the given element
         */
        public Image getImage(Object element) {

            if (element instanceof EObject) {
                return adapterFactoryLabelProvider.getImage(element);
            }
            return null;

        }

        @Override
        public void addListener(ILabelProviderListener listener) {
            adapterFactoryLabelProvider.addListener(listener);
            super.addListener(listener);
        }

        @Override
        public void dispose() {
            adapterFactoryLabelProvider.dispose();
            super.dispose();
        }

        @Override
        public boolean isLabelProperty(Object element, String property) {
            return adapterFactoryLabelProvider.isLabelProperty(element, property);
        }

        @Override
        public void removeListener(ILabelProviderListener listener) {
            adapterFactoryLabelProvider.removeListener(listener);
            super.removeListener(listener);
        }

        @Override
        public int compare(Object object1, Object object2) {
            String text1 = getText(object1);
            String text2 = getText(object2);
            return Policy.getComparator().compare(text1, text2);
        }
    }

    /**
     * {@link ColumnLabelProvider} for the resource column in the model review
     * tree.
     * 
     * @author Florian Zoubek
     *
     */
    private class ModelReviewExplorerResourceColumnLabelProvider extends ColumnLabelProvider {

        @Override
        public String getText(Object element) {

            if (element instanceof EObject) {

                Resource resource = ((EObject) element).eResource();
                if (resource != null) {
                    return resource.getURI().toString();
                }

            }
            return null;
        }

    }

    /**
     * Base class for all {@link NumericColoredColumnLabelProvider}s that count
     * elements of the model review and need access to the model review.
     * 
     * @author Florian Zoubek
     *
     */
    private abstract class ModelReviewCounterColumnLabelProvider extends NumericColoredColumnLabelProvider {

        ContentViewer viewer;

        public ModelReviewCounterColumnLabelProvider(ContentViewer viewer, HSB minHSB, HSB maxHSB, Color fgColor1,
                Color fgColor2) {
            super(minHSB, maxHSB, fgColor1, fgColor2);
            this.viewer = viewer;
        }

        /**
         * finds the parent {@link PatchSet} of the given element, if it exists.
         * 
         * @param element
         * @return the parent {@link PatchSet} or null if it cannot be found
         */
        protected PatchSet findPatchSet(Object element) {

            ITreeContentProvider contentProvider = (ITreeContentProvider) viewer.getContentProvider();
            Object currentElement = element;

            while (currentElement != null && !(currentElement instanceof PatchSet)) {
                currentElement = contentProvider.getParent(currentElement);
            }

            if (currentElement instanceof PatchSet) {
                return (PatchSet) currentElement;
            }

            return null;
        }
    }

    /**
     * {@link NumericColoredColumnLabelProvider} for the change count column of
     * the model review tree.
     * 
     * @author Florian Zoubek
     *
     */
    private class ChangeCountColumnLabelProvider extends ModelReviewCounterColumnLabelProvider {

        public ChangeCountColumnLabelProvider(ContentViewer viewer, Color fgBright, Color fgDark) {
            super(viewer, new HSB(205.0f, 0.f, 1.0f), new HSB(205.0f, 0.59f, 0.32f), fgBright, fgDark);
        }

        @Override
        public float getMaxValue(Object element) {

            PatchSet patchSet = findPatchSet(element);
            if (patchSet != null) {
                return patchSet.getMaxObjectChangeCount();
            }

            return 0;
        }

        @Override
        public float getMinValue(Object element) {
            return 0;
        }

        @Override
        public float getValue(Object element) {

            PatchSet patchSet = findPatchSet(element);
            if (patchSet != null) {

                Map<EObject, Integer> objectChangeCount = patchSet.getObjectChangeCount();
                if (objectChangeCount.containsKey(element)) {
                    return objectChangeCount.get(element);
                }

            }

            return 0;
        }

        @Override
        public boolean hasValue(Object element) {

            PatchSet patchSet = findPatchSet(element);
            if (patchSet != null) {

                Map<EObject, Integer> objectChangeCount = patchSet.getObjectChangeCount();
                return objectChangeCount.containsKey(element);
            }

            return false;
        }

    }

    /**
     * {@link NumericColoredColumnLabelProvider} for the reference count column
     * of the model review tree.
     * 
     * @author Florian Zoubek
     *
     */
    private class ReferencedChangeCountColumnLabelProvider extends ModelReviewCounterColumnLabelProvider {

        public ReferencedChangeCountColumnLabelProvider(ContentViewer viewer, Color fgBright, Color fgDark) {
            super(viewer, new HSB(205.0f, 0.f, 1.0f), new HSB(205.0f, 0.59f, 0.32f), fgBright, fgDark);
        }

        @Override
        public float getMaxValue(Object element) {

            PatchSet patchSet = findPatchSet(element);
            if (patchSet != null) {
                return patchSet.getMaxObjectChangeRefCount();
            }

            return 0;
        }

        @Override
        public float getMinValue(Object element) {
            return 0;
        }

        @Override
        public float getValue(Object element) {

            PatchSet patchSet = findPatchSet(element);
            if (patchSet != null) {

                Map<EObject, Integer> objectChangeRefCount = patchSet.getObjectChangeRefCount();
                if (objectChangeRefCount.containsKey(element)) {
                    return objectChangeRefCount.get(element);
                }

            }

            return 0;
        }

        @Override
        public boolean hasValue(Object element) {

            PatchSet patchSet = findPatchSet(element);
            if (patchSet != null) {

                Map<EObject, Integer> objectChangeRefCount = patchSet.getObjectChangeRefCount();
                return objectChangeRefCount.containsKey(element);
            }

            return false;
        }

    }

    /**
     * An {@link ITreeContentProvider} for the model review tree. Shows
     * currently the contents of all {@link PatchSet}s in a {@link ModelReview}
     * as well as the involved models and diagrams.
     * 
     * @author Florian Zoubek
     *
     */
    private class ModelReviewContentProvider implements ITreeContentProvider {

        private AdapterFactoryContentProvider adapterFactoryContentProvider;

        private ModelReview modelReview;
        private Map<PatchSet, Collection<Object>> cachedPatchSetChildren = new HashMap<>();

        public ModelReviewContentProvider() {
            adapterFactoryContentProvider = new AdapterFactoryContentProvider(
                    new ComposedAdapterFactory(ComposedAdapterFactory.Descriptor.Registry.INSTANCE));
        }

        @Override
        public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
            adapterFactoryContentProvider.inputChanged(viewer, oldInput, newInput);
            // we need the root model review to find the parent of some children
            if (newInput instanceof ModelReview) {
                modelReview = (ModelReview) newInput;
            }
            // clear the cache
            cachedPatchSetChildren.clear();
        }

        @Override
        public void dispose() {
            adapterFactoryContentProvider.dispose();
        }

        @Override
        public boolean hasChildren(Object element) {
            if (element instanceof ModelReview) {
                return true;
            }
            if (element instanceof PatchSet) {
                return true;
            }
            if (element instanceof TreeItemContainer) {
                return ((TreeItemContainer) element).hasChildren();
            }
            if (element instanceof ModelResource) {
                return !((ModelResource) element).getObjects().isEmpty();
            }
            if (element instanceof EObject) {
                return adapterFactoryContentProvider.hasChildren(element);
            }
            return false;
        }

        @Override
        public Object getParent(Object element) {
            if (element instanceof ModelReview) {
                return null;
            }
            if (element instanceof PatchSet) {
                return ((PatchSet) element).getReview();
            }
            if (element instanceof DiagramResource) {
                DiagramResource diagramResource = (DiagramResource) element;
                for (PatchSet patchSet : modelReview.getPatchSets()) {
                    if (patchSet.getNewInvolvedDiagrams().contains(diagramResource)
                            || patchSet.getNewInvolvedDiagrams().contains(diagramResource)) {
                        return patchSet;
                    }
                }
                return null;
            }
            if (element instanceof ModelResource) {
                ModelResource modelResource = (ModelResource) element;
                for (PatchSet patchSet : modelReview.getPatchSets()) {
                    if (patchSet.getNewInvolvedModels().contains(modelResource)
                            || patchSet.getOldInvolvedModels().contains(modelResource)) {
                        return patchSet;
                    }
                }
                return null;
            }
            if (element instanceof EObject) {
                EObject eObject = (EObject) element;
                Object parent = adapterFactoryContentProvider.getParent(element);

                /*
                 * FIXME It might be better to use an own implementation of
                 * EcoreUtil.UsageCrossReferencer to detect references to the
                 * model resource
                 */
                if (parent == null || parent instanceof Resource) {
                    // search for an containing model or diagram resource
                    for (PatchSet patchSet : modelReview.getPatchSets()) {
                        ModelResource modelResource = findContainingModelResource(patchSet.getNewInvolvedDiagrams(),
                                eObject);
                        if (modelResource != null)
                            return modelResource;
                        modelResource = findContainingModelResource(patchSet.getOldInvolvedDiagrams(), eObject);
                        if (modelResource != null)
                            return modelResource;
                        modelResource = findContainingModelResource(patchSet.getNewInvolvedModels(), eObject);
                        if (modelResource != null)
                            return modelResource;
                        modelResource = findContainingModelResource(patchSet.getOldInvolvedModels(), eObject);
                        if (modelResource != null)
                            return modelResource;
                    }
                }
                return parent;
            }
            return null;
        }

        /**
         * finds the {@link ModelResource} that contains the given object.
         * 
         * @param modelResources
         *            a {@link Collection} of {@link ModelResource}s to check
         * @param object
         * @return the {@link ModelResource} or null if no {@link ModelResource}
         *         contains the object
         */
        private ModelResource findContainingModelResource(Collection<? extends ModelResource> modelResources,
                EObject object) {
            for (ModelResource modelResource : modelResources) {
                if (modelResource.getObjects().contains(object)) {
                    return modelResource;
                }
            }
            return null;
        }

        @Override
        public Object[] getElements(Object inputElement) {
            if (inputElement instanceof ModelReview) {
                return ((ModelReview) inputElement).getPatchSets().toArray();
            }
            return new Object[0];
        }

        @Override
        public Object[] getChildren(Object parentElement) {
            if (parentElement instanceof PatchSet) {

                PatchSet patchSet = (PatchSet) parentElement;
                /*
                 * These categories do not exist in the model, so we have to
                 * create temporary containers. We cache them to make sure that
                 * these categories stay the same even if the tree is refreshed.
                 */
                if (!cachedPatchSetChildren.containsKey(patchSet)) {
                    List<Object> children = new LinkedList<>();
                    children.add(new InvolvedModelsTreeItem(patchSet));
                    children.add(new InvolvedDiagramsTreeItem(patchSet));
                    children.add(new PatchSetTreeItem(patchSet));
                    cachedPatchSetChildren.put(patchSet, children);
                }
                return cachedPatchSetChildren.get(patchSet).toArray();
            }
            if (parentElement instanceof TreeItemContainer) {
                return ((TreeItemContainer) parentElement).getChildren();
            }
            if (parentElement instanceof ModelResource) {
                return ((ModelResource) parentElement).getObjects().toArray();
            }
            if (parentElement instanceof EObject) {
                return adapterFactoryContentProvider.getChildren(parentElement);
            }
            return new Object[0];
        }
    }

    /**
     * Base interface for a temporary container of elements in an
     * {@link TreeViewer}.
     * 
     * @author Florian Zoubek
     *
     */
    private interface TreeItemContainer {

        /**
         * @return true if the container has children, false otherwise
         */
        public boolean hasChildren();

        /**
         * @return an array of all children of this container
         */
        public Object[] getChildren();

        /**
         * @return the label text for this container
         */
        public String getText();
    }

    /**
     * A temporary container for all patches of an {@link PatchSet}.
     * 
     * @author Florian Zoubek
     *
     */
    private class PatchSetTreeItem implements TreeItemContainer {

        private PatchSet patchSet;

        public PatchSetTreeItem(PatchSet patchSet) {
            super();
            this.patchSet = patchSet;
        }

        @Override
        public boolean hasChildren() {
            return !patchSet.getPatches().isEmpty();
        }

        @Override
        public Object[] getChildren() {
            return patchSet.getPatches().toArray();
        }

        @Override
        public String getText() {
            return "Patches";
        }
    }

    /**
     * A temporary container for the involved models of an {@link PatchSet}.
     * 
     * @author Florian Zoubek
     *
     */
    private class InvolvedModelsTreeItem implements TreeItemContainer {

        private PatchSet patchSet;

        public InvolvedModelsTreeItem(PatchSet patchSet) {
            super();
            this.patchSet = patchSet;
        }

        @Override
        public boolean hasChildren() {
            return !patchSet.getNewInvolvedModels().isEmpty();
        }

        @Override
        public Object[] getChildren() {
            return patchSet.getNewInvolvedModels().toArray();
        }

        @Override
        public String getText() {
            return "Involved models";
        }
    }

    /**
     * A temporary container for the involved diagrams of an {@link PatchSet}.
     * 
     * @author Florian Zoubek
     *
     */
    private class InvolvedDiagramsTreeItem implements TreeItemContainer {

        private PatchSet patchSet;

        public InvolvedDiagramsTreeItem(PatchSet patchSet) {
            super();
            this.patchSet = patchSet;
        }

        @Override
        public boolean hasChildren() {
            return !patchSet.getNewInvolvedDiagrams().isEmpty();
        }

        @Override
        public Object[] getChildren() {
            return patchSet.getNewInvolvedDiagrams().toArray();
        }

        @Override
        public String getText() {
            return "Involved diagrams";
        }
    }

}