edu.washington.cs.cupid.views.InspectorView.java Source code

Java tutorial

Introduction

Here is the source code for edu.washington.cs.cupid.views.InspectorView.java

Source

/*******************************************************************************
 * Copyright (c) 2013 Todd Schiller.
 * 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:
 *     Todd Schiller - initial API, implementation, and documentation
 ******************************************************************************/
package edu.washington.cs.cupid.views;

import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.math.RoundingMode;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Set;
import java.util.SortedSet;

import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.IJobChangeEvent;
import org.eclipse.core.runtime.jobs.IJobChangeListener;
import org.eclipse.jface.viewers.ColumnWeightData;
import org.eclipse.jface.viewers.ILabelProviderListener;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.ITableLabelProvider;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.ITreeSelection;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.TableLayout;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.SashForm;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Text;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeColumn;
import org.eclipse.ui.IViewSite;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.part.ViewPart;

import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.common.math.IntMath;
import com.google.common.reflect.TypeToken;

import edu.washington.cs.cupid.CapabilityExecutor;
import edu.washington.cs.cupid.CupidPlatform;
import edu.washington.cs.cupid.TypeManager;
import edu.washington.cs.cupid.capability.CapabilityArguments;
import edu.washington.cs.cupid.capability.CapabilityStatus;
import edu.washington.cs.cupid.capability.CapabilityUtil;
import edu.washington.cs.cupid.capability.ICapability;
import edu.washington.cs.cupid.capability.ICapability.IOutput;
import edu.washington.cs.cupid.capability.ICapabilityArguments;
import edu.washington.cs.cupid.internal.CupidActivator;
import edu.washington.cs.cupid.jobs.JobFamily;
import edu.washington.cs.cupid.preferences.PreferenceConstants;
import edu.washington.cs.cupid.preferences.SelectionInspectorPreferencePage;
import edu.washington.cs.cupid.select.CupidSelectionService;
import edu.washington.cs.cupid.select.ICupidSelectionListener;
import edu.washington.cs.cupid.usage.CupidDataCollector;
import edu.washington.cs.cupid.usage.events.CupidEventBuilder;
import edu.washington.cs.cupid.usage.events.EventConstants;

/**
 * View that shows capabilities (and their outputs) that apply to the current workbench selection.
 * @author Todd Schiller (tws@cs.washington.edu)
 */
public class InspectorView extends ViewPart {

    //TODO: support multiple selections for "local" capabilities
    //TODO: localize status messages

    private static final int COLLECTION_PARTITION_SIZE = 10;

    private static final int MAX_LINE_LENGTH = 80;

    private static final boolean INCLUDE_NULL_OUTPUT = false;

    /**
     * The ID of the view as specified by the extension.
     */
    public static final String ID = "edu.washington.cs.cupid.views.InspectorView";

    private SashForm sash;

    private TreeViewer viewer;
    private Text detail;

    private ViewContentProvider contentProvider;

    private SelectionModel selectionModel;

    private Set<JobFamily> oldJobs = Sets.newIdentityHashSet();

    @Override
    public void init(IViewSite site) throws PartInitException {
        CupidDataCollector
                .record(new CupidEventBuilder(EventConstants.LOADED_WHAT, getClass(), CupidActivator.getDefault())
                        .create());

        super.init(site);
    }

    @Override
    public final void createPartControl(final Composite parent) {
        contentProvider = new ViewContentProvider();

        sash = new SashForm(parent, SWT.BORDER | SWT.HORIZONTAL);
        Tree tree = new Tree(sash, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL | SWT.VIRTUAL);
        detail = new Text(sash, SWT.H_SCROLL | SWT.V_SCROLL | SWT.READ_ONLY);

        TreeColumn cCapability = new TreeColumn(tree, SWT.LEFT);
        cCapability.setText("Capability");

        TreeColumn cValue = new TreeColumn(tree, SWT.LEFT);
        cValue.setText("Value");

        TableLayout layout = new TableLayout();
        layout.addColumnData(new ColumnWeightData(1));
        layout.addColumnData(new ColumnWeightData(2));
        tree.setLayout(layout);

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

        viewer = new TreeViewer(tree);
        viewer.setContentProvider(contentProvider);
        viewer.setLabelProvider(new ViewLabelProvider());

        selectionModel = new SelectionModel();
        CupidSelectionService.addListener(selectionModel);

        viewer.addSelectionChangedListener(new DetailContentProvider());
    }

    private void cancelOldJobs() {
        CapabilityExecutor.getJobManager().cancel(oldJobs);
        oldJobs.clear();
    }

    private class DetailContentProvider implements ISelectionChangedListener {
        @Override
        public void selectionChanged(final SelectionChangedEvent event) {
            ISelection selection = event.getSelection();

            detail.setText("");

            if (selection instanceof ITreeSelection) {
                Object row = ((ITreeSelection) selection).getFirstElement();
                if (row instanceof ClassRow) {
                    detail.setText(((ClassRow) row).value.toString());
                } else if (row instanceof CapabilityRow) {
                    CapabilityRow x = (CapabilityRow) row;
                    if (x.finished && x.status.getCode() == Status.OK) {

                        if (x.value == null) {
                            throw new NullPointerException(
                                    "Capability '" + x.capability.getName() + "' returned null");
                        }

                        detail.setText(x.value.toString());
                    }
                }
            }
        }
    }

    private interface Row {
        boolean hasChildren();

        Row[] getChildren();

        Row getParent();

        String getColumnText(final Object element, final int columnIndex);
    }

    private final class OutputRow implements Row {
        private final CapabilityRow parent;
        private final ICapability.IOutput<?> output;
        private final Object value;

        public OutputRow(CapabilityRow parent, IOutput<?> output, Object value) {
            super();
            this.parent = parent;
            this.output = output;
            this.value = value;
        }

        @Override
        public boolean hasChildren() {
            return valueHasChildren(value);
        }

        @Override
        public Row[] getChildren() {
            if (value instanceof List) {
                return generateListRows(this, output.getName(), (List<?>) value, 0, ((List<?>) value).size());
            } else if (value.getClass().isArray()) {
                return generateArrayRows(this, output.getName(), value, 0, Array.getLength(value));
            } else {
                return generateRows(this, value);
            }
        }

        @Override
        public Row getParent() {
            return parent;
        }

        @Override
        public String getColumnText(Object element, int columnIndex) {
            switch (columnIndex) {
            case 0:
                return output.getName();
            case 1:
                if (value == null) {
                    return "null";
                } else if (value instanceof Exception) {
                    String msg = ((Exception) value).getLocalizedMessage();
                    return msg == null ? "<error>" : ("<error:" + msg + ">");
                } else {
                    return valueText(value);
                }
            default:
                throw new IllegalArgumentException("Invalid column index");
            }
        }
    }

    private final class ClassRow implements Row {
        private final Row parent;
        private final String name;
        private final Object value;

        public ClassRow(final Row parent, final String name, final Object value) {
            this.parent = parent;
            this.name = name;
            this.value = value;
        }

        @Override
        public boolean hasChildren() {
            return valueHasChildren(value);
        }

        @Override
        public Row[] getChildren() {
            if (value instanceof List) {
                return generateListRows(this, name, (List<?>) value, 0, ((List<?>) value).size());
            } else if (value.getClass().isArray()) {
                return generateArrayRows(this, name, value, 0, Array.getLength(value));
            } else {
                return generateRows(this, value);
            }
        }

        @Override
        public Row getParent() {
            return parent;
        }

        @Override
        public String getColumnText(final Object element, final int columnIndex) {
            switch (columnIndex) {
            case 0:
                return name;
            case 1:
                if (value == null) {
                    return "null";
                } else if (value instanceof Exception) {
                    String msg = ((Exception) value).getLocalizedMessage();
                    return msg == null ? "<error>" : ("<error:" + msg + ">");
                } else {
                    return valueText(value);
                }
            default:
                throw new IllegalArgumentException("Invalid column index");
            }
        }
    }

    private final class ArrayRow implements Row {
        private Row parent;
        private Object array;
        private int offset;
        private int length;
        private String rootName;

        public ArrayRow(final Row parent, final String rootName, final Object array, final int offset,
                final int length) {
            super();
            this.parent = parent;
            this.array = array;
            this.offset = offset;
            this.rootName = rootName;
            this.length = length;
        }

        @Override
        public boolean hasChildren() {
            return true;
        }

        @Override
        public Row[] getChildren() {
            return generateArrayRows(parent, rootName, array, offset, length);
        }

        @Override
        public Row getParent() {
            return parent;
        }

        @Override
        public String getColumnText(final Object element, final int columnIndex) {
            switch (columnIndex) {
            case 0:
                return "[" + offset + " .. " + (offset + length - 1) + "]";
            default:
                return null;
            }
        }
    }

    private final class ListRow implements Row {
        private final Row parent;
        private final List<?> list;
        private final int offset;
        private final int length;
        private final String rootName;

        public ListRow(final Row parent, final String rootName, final List<?> list, final int offset,
                final int length) {
            this.parent = parent;
            this.list = list;
            this.offset = offset;
            this.rootName = rootName;
            this.length = length;
        }

        @Override
        public boolean hasChildren() {
            return true;
        }

        @Override
        public Row[] getChildren() {
            return generateListRows(parent, rootName, list, offset, length);
        }

        @Override
        public Row getParent() {
            return parent;
        }

        @Override
        public String getColumnText(final Object element, final int columnIndex) {
            switch (columnIndex) {
            case 0:
                return "[" + offset + " .. " + (offset + length - 1) + "]";
            default:
                return null;
            }
        }
    }

    private final class CapabilityRow implements Row {
        private final ICapability capability;

        private String statusMsg = "Updating (submitted)...";
        private boolean interrupted = false;
        private boolean finished = false;
        private CapabilityStatus status = null;
        private Object value = null;

        private synchronized void updateStatus(final String msg) {
            statusMsg = msg;
            update(CapabilityRow.this);
        }

        private CapabilityRow(final ICapability capability, final Object input) {

            this.capability = capability;
            JobFamily family = family(input);

            oldJobs.add(family);
            CapabilityExecutor.getJobManager().register(family);

            try {
                ICapabilityArguments args = CapabilityUtil.isGenerator(capability) ? new CapabilityArguments()
                        : CapabilityUtil.packUnaryInput(capability, input);

                CapabilityExecutor.asyncExec(capability, args, family, new IJobChangeListener() {

                    @Override
                    public synchronized void aboutToRun(final IJobChangeEvent event) {
                        updateStatus("Updating (starting)...");
                    }

                    @Override
                    public synchronized void awake(final IJobChangeEvent event) {
                        updateStatus("Updating (awake)...");
                    }

                    @Override
                    public synchronized void done(final IJobChangeEvent event) {
                        synchronized (CapabilityRow.this) {
                            finished = true;
                            status = (CapabilityStatus) event.getResult();

                            if (CapabilityUtil.hasSingleOutput(capability)) {
                                value = CapabilityUtil.singleOutputValue(capability, status);
                            } else {
                                value = status.value();
                            }

                            updateStatus("Done");
                        }
                    }

                    @Override
                    public synchronized void running(final IJobChangeEvent event) {
                        updateStatus("Updating (running)...");
                    }

                    @Override
                    public synchronized void scheduled(final IJobChangeEvent event) {
                        updateStatus("Updating (scheduled)...");
                    }

                    @Override
                    public synchronized void sleeping(final IJobChangeEvent event) {
                        updateStatus("Updating (sleeping)...");
                    }
                });

            } catch (Exception ex) {
                updateStatus(
                        "Error running capability (" + ex.getClass().getSimpleName() + "): " + ex.getMessage());
            }
        }

        @Override
        public synchronized boolean hasChildren() {
            if (finished && status.getCode() == Status.OK) {
                return valueHasChildren(value);
            } else {
                return false;
            }
        }

        @Override
        public synchronized Row[] getChildren() {
            if (finished && status.getCode() == Status.OK) {

                if (CapabilityUtil.hasSingleOutput(capability)) {
                    if (value instanceof List) {
                        return generateListRows(this, capability.getName(), (List<?>) value, 0,
                                ((List<?>) value).size());
                    } else if (value.getClass().isArray()) {
                        return generateArrayRows(this, capability.getName(), value, 0, Array.getLength(value));
                    } else {
                        return generateRows(this, value);
                    }
                } else {
                    return generateOutputRows(this, status);
                }

            } else {
                return new Row[] {};
            }

        }

        @Override
        public Row getParent() {
            return null;
        }

        @Override
        public synchronized String getColumnText(final Object element, final int columnIndex) {

            switch (columnIndex) {
            case 0:
                return capability.getName();
            case 1:
                if (interrupted) {
                    return "Error (interrupted)";
                } else if (finished) {
                    switch (status.getCode()) {
                    case Status.OK:
                        if (value != null) {
                            if (capability.getOutputs().size() > 1) {
                                return "<Multiple Outputs>";
                            } else {
                                return valueText(value);
                            }
                        } else {
                            return null;
                        }
                    case Status.CANCEL:
                        return "Update Cancelled...";
                    case Status.ERROR:
                        return "Exception: " + status.getException().getLocalizedMessage();
                    default:
                        throw new RuntimeException("Unexpected plugin-specific status code: " + status.getCode());
                    }
                } else {
                    return statusMsg;
                }
            default:
                return null;
            }
        }
    }

    private final class ViewLabelProvider implements ITableLabelProvider {

        @Override
        public void dispose() {
            // NO OP
        }

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

        @Override
        public void addListener(final ILabelProviderListener listener) {
            // NO OP
        }

        @Override
        public void removeListener(final ILabelProviderListener listener) {
            // NO OP
        }

        @Override
        public Image getColumnImage(final Object element, final int columnIndex) {
            return null;
        }

        @Override
        public String getColumnText(final Object element, final int columnIndex) {
            if (element instanceof Row) {
                return ((Row) element).getColumnText(element, columnIndex);
            } else {
                throw new IllegalArgumentException("Illegal table entry");
            }
        }

    }

    private final class SelectionModel implements ICupidSelectionListener {

        @Override
        public void selectionChanged(final IWorkbenchPart part, final Object[] data) {
            if (data != null && data.length > 0) {
                synchronized (viewer) {
                    if (!part.equals(InspectorView.this)) {
                        // TODO handle multiple data case
                        cancelOldJobs();

                        CupidDataCollector.record(CupidEventBuilder
                                .contextEvent(getClass(), part, data, CupidActivator.getDefault()).create());

                        viewer.setInput(data[0]);
                    }
                }
            }
        }
    }

    @Override
    public final void dispose() {
        super.dispose();
        CupidSelectionService.removeListener(selectionModel);
    }

    private final class ViewContentProvider implements ITreeContentProvider {
        @Override
        public void inputChanged(final Viewer v, final Object oldSelection, final Object newSelection) {
            // NO OP
        }

        @Override
        public void dispose() {
            // NO OP
        }

        @Override
        public Object[] getElements(final Object argument) {
            Set<String> hidden = Sets.newHashSet(
                    CupidActivator.getDefault().getPreferenceStore().getString(PreferenceConstants.P_INSPECTOR_HIDE)
                            .split(SelectionInspectorPreferencePage.SEPARATOR));

            if (argument == null) {
                return new Object[] {};
            }

            List<Object> rows = Lists.newArrayList();

            rows.add(new ClassRow(null, "Selected Object", argument));

            SortedSet<ICapability> capabilities = CupidPlatform.getCapabilityRegistry()
                    .getCapabilities(TypeToken.of(argument.getClass()));

            for (ICapability capability : capabilities) {
                if (!hidden.contains(capability.getName())) {

                    if (CapabilityUtil.isGenerator(capability)) {
                        rows.add(new CapabilityRow(capability, null));

                    } else if (CapabilityUtil.isUnary(capability)
                            && TypeManager.isCompatible(CapabilityUtil.unaryParameter(capability), argument)) {

                        Object adapted = TypeManager.getCompatible(CapabilityUtil.unaryParameter(capability),
                                argument);
                        rows.add(new CapabilityRow(capability, adapted));
                    }
                }
            }

            return rows.toArray(new Object[] {});
        }

        @Override
        public boolean hasChildren(final Object element) {
            if (element instanceof Row) {
                return ((Row) element).hasChildren();
            }
            return false;
        }

        @Override
        public Object[] getChildren(final Object parentElement) {
            if (parentElement instanceof Row) {
                return ((Row) parentElement).getChildren();
            } else {
                throw new IllegalArgumentException("Expected row");
            }
        }

        @Override
        public Object getParent(final Object element) {
            if (element instanceof Row) {
                return ((Row) element).getParent();
            } else {
                return null;
            }
        }
    }

    private boolean valueHasChildren(final Object value) {
        return !(value == null || value.getClass().isPrimitive() || value instanceof String
                || value instanceof Number);
    }

    private Row[] generateOutputRows(final CapabilityRow parent, final CapabilityStatus status) {

        List<IOutput<?>> outputs = Lists.newArrayList(status.value().getOutputs().keySet());

        Collections.sort(outputs, new Comparator<IOutput<?>>() {
            @Override
            public int compare(IOutput<?> o1, IOutput<?> o2) {
                return o1.getName().compareTo(o2.getName());
            }
        });

        List<Row> result = Lists.newArrayList();

        for (IOutput<?> output : outputs) {
            result.add(new OutputRow(parent, output, status.value().getOutput(output)));
        }

        return result.toArray(new Row[] {});
    }

    private Row[] generateArrayRows(final Row parent, final String rootName, final Object array, final int offset,
            final int length) {

        List<Row> result = Lists.newArrayList();

        if (length <= COLLECTION_PARTITION_SIZE) {
            for (int i = 0; i < length; i++) {
                result.add(new ClassRow(parent, rootName + "[" + (i + offset) + "]", Array.get(array, offset + i)));
            }
        } else {
            int depth = IntMath.log10(length - 1, RoundingMode.FLOOR);
            int size = IntMath.pow(COLLECTION_PARTITION_SIZE, depth);
            int cnt = IntMath.divide(length, size, RoundingMode.CEILING);

            for (int i = 0; i < cnt; i++) {
                int start = i * size;
                int end = Math.min(length - 1, start + size - 1);

                result.add(new ArrayRow(parent, rootName, array, offset + start, end - start + 1));
            }
        }

        return result.toArray(new Row[] {});
    }

    private Row[] generateListRows(final Row parent, final String rootName, final List<?> value, final int offset,
            final int length) {

        List<Row> result = Lists.newArrayList();

        if (length <= COLLECTION_PARTITION_SIZE) {
            for (int i = 0; i < length; i++) {
                result.add(new ClassRow(parent, rootName + "[" + (i + offset) + "]", value.get(i + offset)));
            }
        } else {
            int depth = IntMath.log10(length - 1, RoundingMode.FLOOR);
            int size = IntMath.pow(COLLECTION_PARTITION_SIZE, depth);
            int cnt = IntMath.divide(length, size, RoundingMode.CEILING);

            for (int i = 0; i < cnt; i++) {
                int start = i * size;
                int end = Math.min(length - 1, start + size - 1);

                result.add(new ListRow(parent, rootName, value, offset + start, end - start + 1));
            }
        }

        return result.toArray(new Row[] {});
    }

    private Row[] generateRows(final Row parent, final Object value) {
        if (!valueHasChildren(value)) {
            return new Row[] {};
        }

        List<Row> children = Lists.newArrayList();

        for (Field field : value.getClass().getFields()) {
            if (!Modifier.isStatic(field.getModifiers())) {
                try {
                    Object child = field.get(value);
                    children.add(new ClassRow(parent, field.getName(), child));
                } catch (Exception e) {
                    children.add(new ClassRow(parent, field.getName(), e));
                }
            }
        }

        for (Method method : value.getClass().getMethods()) {
            if (!Modifier.isStatic(method.getModifiers())) {
                if (method.getParameterTypes().length == 0 && method.getName().startsWith("get")
                //&& !method.getName().equalsIgnoreCase("getClass")
                ) {

                    try {
                        if (!method.isAccessible()) {
                            method.setAccessible(true);
                        }

                        Object child = method.invoke(value);

                        if (child != null || INCLUDE_NULL_OUTPUT) {
                            children.add(new ClassRow(parent, method.getName(), child));
                        }
                    } catch (Exception e) {
                        children.add(new ClassRow(parent, method.getName(), e));
                    }
                }
            }
        }

        return children.toArray(new Row[] {});
    }

    private JobFamily family(final Object input) {
        return new JobFamily(input, this);
    }

    @Override
    public final void setFocus() {
        synchronized (viewer) {
            viewer.getControl().setFocus();
        }
    }

    /**
     * Safely (in the UI thread) updates the given row in the table.
     * @param row the row to update
     */
    private void update(final CapabilityRow row) {
        Display display = PlatformUI.getWorkbench().getDisplay();

        // does this need synchronization?
        if (!display.isDisposed()) {
            display.asyncExec(new Runnable() {
                @Override
                public void run() {
                    if (!viewer.getTree().isDisposed()) {
                        if (row.finished) {
                            viewer.refresh(row);
                        } else {
                            viewer.update(row, null);
                        }
                    }
                }
            });
        }
    }

    public static String valueText(Object obj) {
        String lines[] = obj.toString().split("\\r?\\n", 2);

        if (lines[0].length() > MAX_LINE_LENGTH) {
            return lines[0].substring(0, MAX_LINE_LENGTH) + " ...";
        } else {
            return lines.length == 1 ? lines[0] : (lines[0] + " ...");
        }
    }
}