eu.numberfour.n4js.ui.compare.ProjectCompareTree.java Source code

Java tutorial

Introduction

Here is the source code for eu.numberfour.n4js.ui.compare.ProjectCompareTree.java

Source

/**
 * Copyright (c) 2016 NumberFour AG.
 * 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:
 *   NumberFour AG - Initial API and implementation
 */
package eu.numberfour.n4js.ui.compare;

import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.dialogs.ProgressMonitorDialog;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jface.viewers.ITableColorProvider;
import org.eclipse.jface.viewers.ITableLabelProvider;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeColumn;

import com.google.common.base.Joiner;

import eu.numberfour.n4js.compare.ProjectCompareResult;
import eu.numberfour.n4js.compare.ProjectComparison;
import eu.numberfour.n4js.compare.ProjectComparisonEntry;
import eu.numberfour.n4js.projectModel.IN4JSCore;
import eu.numberfour.n4js.ts.types.TClassifier;
import eu.numberfour.n4js.ts.types.TEnum;

/**
 * UI widget to show comparisons of API and implementation projects.
 */
public class ProjectCompareTree extends TreeViewer {

    /** Maximum number of implementations shown side-by-side if there is no focus implementation. */
    private static final int NUM_OF_IMPLEMENTATION_COLUMNS = 4;
    /** Titles of special columns showing additional information if there is a focus implementation. */
    private static final String[] SPECIAL_COLUMN_TITLES = { "Status", "Description", "Documentation" };
    /** Number of special columns showing additional information if there is a focus implementation. */
    private static final int NUM_OF_SPECIAL_COLUMNS = SPECIAL_COLUMN_TITLES.length;
    /**
     * Number of columns (some may be hidden, depending on the number of implementations and whether there is a focus
     * implementation).
     */
    private static final int NUM_OF_COLUMNS = Math.max(1 + NUM_OF_IMPLEMENTATION_COLUMNS,
            2 + NUM_OF_SPECIAL_COLUMNS);

    /** Color used for non-compliant differences. */
    protected final Color col_diff_error_fg;
    /** Color used for compliant differences. */
    protected final Color col_diff_conformant_fg;
    /** Background color used for highlighting classifiers. */
    protected final Color col_classifier_bg;

    /***/
    protected final ProjectCompareTreeHelper projectCompareTreeHelper;

    /**
     * Project comparison to show or <code>null</code> to show an empty widget.
     */
    protected ProjectComparison comparison = null;
    /**
     * The index of the one implementation to focus on (all other implementations will be hidden; columns of index 2 and
     * above will show additional information for the focus implementation) or -1 to show all available implementations
     * side-by-side.
     */
    protected int focusImplIndex = -1;

    /** Cached documentation for some entries. */
    protected Map<ProjectComparisonEntry, String> cachedDocumentation = null;

    private class MyLabelProvider extends LabelProvider implements ITableLabelProvider, ITableColorProvider {

        @Override
        public String getColumnText(Object element, int columnIndex) {
            final ProjectComparisonEntry entry = (ProjectComparisonEntry) element;
            final int idx = getEntryIndexForColumnIndex(columnIndex);
            if (idx >= -1 && idx < entry.getImplCount()) {
                final StringBuffer sb = new StringBuffer();
                sb.append(columnIndex == 0 ? entry.getTextAPI() : entry.getTextImpl(idx));
                if (columnIndex == 0 ? entry.isInherited() : entry.isInherited(idx))
                    sb.append(" (inherited)");
                if (columnIndex == 0 ? entry.isOverride() : entry.isOverride(idx))
                    sb.append(" (override)");
                return sb.toString();
            } else if (idx == -2) {
                // special columns in focus implementation mode
                return getSpecialColumnText(entry, columnIndex - 2);
            }
            return null;
        }

        private String getSpecialColumnText(ProjectComparisonEntry entry, int specialColumnIndex) {
            if (focusImplIndex < 0 || focusImplIndex >= entry.getImplCount())
                throw new IllegalStateException(); // should never happen
            if (specialColumnIndex == 0) {
                // special column showing the compare status
                final ProjectCompareResult result = projectCompareTreeHelper.compareApiImpl(entry, focusImplIndex);
                switch (result.status) {
                case EQUAL:
                    return null; // empty cell if identical
                case COMPLIANT:
                    return "diff";
                case ERROR:
                    return "*ERROR*";
                }
            } else if (specialColumnIndex == 1) {
                // special column showing description of comparison result
                final ProjectCompareResult result = projectCompareTreeHelper.compareApiImpl(entry, focusImplIndex);
                return result.description;
            } else if (specialColumnIndex == 2) {
                // special column showing @api tag in jsdoc of API and focus implementation
                return cachedDocumentation != null ? cachedDocumentation.get(entry) : null;
            }
            return null;
        }

        @Override
        public Image getColumnImage(Object element, int columnIndex) {
            if (columnIndex == 0) {
                final ProjectComparisonEntry entry = (ProjectComparisonEntry) element;
                final EObject api = entry.getElementAPI();
                if (api != null)
                    return projectCompareTreeHelper.getImage(api);
            }
            return null;
        }

        @Override
        public Color getForeground(Object element, int columnIndex) {
            if (columnIndex == 0)
                return null;
            final int idx = getEntryIndexForColumnIndex(columnIndex);
            if (idx >= 0) {
                final ProjectCompareResult result = projectCompareTreeHelper
                        .compareApiImpl((ProjectComparisonEntry) element, idx);
                switch (result.status) {
                case EQUAL:
                    return null; // default color if identical
                case COMPLIANT:
                    return col_diff_conformant_fg;
                case ERROR:
                    return col_diff_error_fg;
                }
            }
            return null;
        }

        @Override
        public Color getBackground(Object element, int columnIndex) {
            for (EObject currElem : ((ProjectComparisonEntry) element).getAllElements())
                if (currElem instanceof TClassifier || currElem instanceof TEnum)
                    return col_classifier_bg;
            return null;
        }

        private int getEntryIndexForColumnIndex(int columnIndex) {
            if (focusImplIndex >= 0) {
                // we have a focus implementation
                if (columnIndex == 0)
                    return -1; // show API
                else if (columnIndex == 1)
                    return focusImplIndex; // show focus implementation
                else {
                    return -2; // show special columns
                }
            } else {
                // no focus implementation
                return columnIndex - 1; // show API or implementation
            }
        }
    }

    private class MyContentProvider implements ITreeContentProvider {

        @Override
        public Object[] getElements(Object inputElement) {
            return ((ProjectComparison) inputElement).getEntries();
        }

        @Override
        public boolean hasChildren(Object element) {
            return ((ProjectComparisonEntry) element).hasChildren();
        }

        @Override
        public Object[] getChildren(Object parentElement) {
            return ((ProjectComparisonEntry) parentElement).getChildren();
        }

        @Override
        public Object getParent(Object element) {
            return ((ProjectComparisonEntry) element).getParent();
        }

        @Override
        public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
            // ignore
        }

        @Override
        public void dispose() {
            // ignore
        }
    }

    /**
     * Create an instance.
     */
    public ProjectCompareTree(Composite parent, int style, ProjectCompareTreeHelper projectCompareTreeHelper) {
        super(parent, style);
        this.projectCompareTreeHelper = projectCompareTreeHelper;

        parent.addDisposeListener(new DisposeListener() {
            @Override
            public void widgetDisposed(DisposeEvent e) {
                onDispose();
            }
        });

        col_diff_error_fg = new Color(parent.getDisplay(), 255, 40, 40);
        col_diff_conformant_fg = new Color(parent.getDisplay(), 60, 127, 95);
        col_classifier_bg = new Color(parent.getDisplay(), 200, 220, 250);

        final Tree tree = getTree();
        for (int n = 0; n < NUM_OF_COLUMNS; n++) {
            final TreeColumn colN = new TreeColumn(tree, SWT.LEFT);
            if (n == 0)
                colN.setWidth(300); // make API column a bit wider
            else
                colN.setWidth(200);
        }

        tree.setHeaderVisible(true);
        tree.setLinesVisible(true);

        setLabelProvider(new MyLabelProvider());
        setContentProvider(new MyContentProvider());

        setComparison(null, null, null);
    }

    /**
     * Creates a new default comparison of all API / implementation projects in the default workspace (i.e. the one
     * accessed via {@link IN4JSCore}) and shows this comparison in the widget.
     */
    public void setComparison() {
        final ProgressMonitorDialog dlg = new ProgressMonitorDialog(getTree().getShell());
        try {
            dlg.run(false, false, new IRunnableWithProgress() {
                @Override
                public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
                    setComparison(monitor);
                }
            });
        } catch (InvocationTargetException | InterruptedException e) {
            // ignore
        }
    }

    /**
     * Like {@link #setComparison()}, but a custom progress monitor can be provided (may be <code>null</code> if no
     * progress monitor should be used).
     */
    public void setComparison(IProgressMonitor monitor) {
        if (monitor != null) {
            monitor.beginTask("Building comparison for API / implementation projects in workspace ...",
                    IProgressMonitor.UNKNOWN);
            monitor.worked(1);
        }
        final List<String> errorMessages = new ArrayList<>();
        if (monitor != null) {
            monitor.subTask("Comparing projects in workspace ...");
            monitor.worked(1);
        }
        final ProjectComparison newComparison = projectCompareTreeHelper.createComparison(true, errorMessages);
        if (errorMessages.isEmpty()) {
            // if there is exactly one implementation, then focus on that; otherwise do not focus on an
            // implementation (i.e. show all implementations side-by-side)
            final String newFocusImplId;
            if (newComparison != null && newComparison.getImplCount() == 1)
                newFocusImplId = newComparison.getImplId(0);
            else
                newFocusImplId = null;
            setComparison(newComparison, newFocusImplId, monitor);
        } else {
            setComparison(null, null, null);
            MessageDialog.openError(getTree().getShell(), "Setup Invalid",
                    "The API / implementation setup is invalid:\n" + Joiner.on('\n').join(errorMessages));
        }
        if (monitor != null)
            monitor.done();
    }

    /**
     * Show the given comparison in the widget. All arguments may be <code>null</code>.
     *
     * @param comparison
     *            the comparison to show in the widget or <code>null</code> to clear the widget.
     * @param focusImplId
     *            the ID of a single implementation to focus on (all other implementations will be hidden; columns of
     *            index 2 and above will show additional information for this focus implementation) or <code>null</code>
     *            to show all available implementations side-by-side (but without additional information in separate
     *            columns).
     * @param monitor
     *            the progress monitor to use or <code>null</code>.
     */
    public void setComparison(ProjectComparison comparison, String focusImplId, IProgressMonitor monitor) {
        this.comparison = comparison;
        this.focusImplIndex = comparison != null && focusImplId != null ? comparison.getImplIndex(focusImplId) : -1;
        this.cachedDocumentation = null;

        // in case of a focus implementation, read documentation for API and the focus implementation into cache
        if (comparison != null && focusImplId != null) {
            if (monitor != null) {
                monitor.subTask("Scanning jsdoc ...");
                monitor.worked(1);
            }
            final int focusImplIdx = comparison.getImplIndex(focusImplId);
            cachedDocumentation = projectCompareTreeHelper.readDocumentation(comparison,
                    new int[] { focusImplIdx });
        }

        if (monitor != null) {
            monitor.subTask("Updating UI ...");
            monitor.worked(1);
        }
        setInput(comparison);
        refreshColumnHeaders();
        expandAll();
    }

    /**
     * Refresh titles of columns.
     */
    protected void refreshColumnHeaders() {
        setColumnHeader(0, "API"); // column 0 is always the API column
        // clear old titles
        for (int idx = 1; idx < NUM_OF_COLUMNS; idx++)
            setColumnHeader(idx, "");
        // set new column titles
        if (comparison != null) {
            if (focusImplIndex >= 0) {
                setColumnHeader(1, comparison.getImplId(focusImplIndex));
                for (int specialColumnIdx = 0; specialColumnIdx < NUM_OF_SPECIAL_COLUMNS; specialColumnIdx++)
                    setColumnHeader(2 + specialColumnIdx, SPECIAL_COLUMN_TITLES[specialColumnIdx]);
            } else {
                for (int implColumnIdx = 0; implColumnIdx < NUM_OF_IMPLEMENTATION_COLUMNS; implColumnIdx++)
                    setColumnHeader(1 + implColumnIdx, comparison.getImplId(implColumnIdx));
            }
        }
    }

    private void setColumnHeader(int idx, String label) {
        getTree().getColumn(idx).setText(label != null ? label : "");
    }

    /**
     * Dispose of colors.
     */
    protected void onDispose() {
        if (col_diff_error_fg != null)
            col_diff_error_fg.dispose();
        if (col_diff_conformant_fg != null)
            col_diff_conformant_fg.dispose();
        if (col_classifier_bg != null)
            col_classifier_bg.dispose();
    }
}