edu.washington.cs.cupid.wizards.ui.MappingPage.java Source code

Java tutorial

Introduction

Here is the source code for edu.washington.cs.cupid.wizards.ui.MappingPage.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.wizards.ui;

import java.io.Serializable;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.SortedSet;

import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.IStructuredSelection;
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.SelectionChangedEvent;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.wizard.WizardPage;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
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.layout.RowLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Group;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Text;
import org.eclipse.swt.widgets.TreeColumn;

import com.google.common.base.Predicate;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.common.reflect.TypeToken;

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.CapabilityUtil;
import edu.washington.cs.cupid.capability.ICapability;
import edu.washington.cs.cupid.capability.ICapabilityArguments;
import edu.washington.cs.cupid.capability.ISerializableCapability;
import edu.washington.cs.cupid.capability.dynamic.DynamicSerializablePipeline;
import edu.washington.cs.cupid.wizards.TypeComboUpdater;
import edu.washington.cs.cupid.wizards.TypeUtil;
import edu.washington.cs.cupid.wizards.internal.Activator;
import edu.washington.cs.cupid.wizards.internal.CapabilityMapping;
import edu.washington.cs.cupid.wizards.internal.DerivedCapability;
import edu.washington.cs.cupid.wizards.internal.ValueMapping;

public class MappingPage extends WizardPage {

    private static final String DEFAULT_NAME = "My Mapping";
    private static final String DEFAULT_DESCRIPTION = "A mapping between values";
    private static final String VALUE_SENTINAL = "<Value>";
    private static final String DEFAULT_KEY_TYPE = "java.lang.Object";

    //
    // Views
    //

    private Text nameEntry;
    private Text descriptionEntry;

    private Combo objectType;
    private TreeViewer keyTree;
    private Combo keyLinkCombo;

    private TreeViewer valueTree;
    private Combo valueLinkCombo;

    private Button objectSelect;

    private Group typeGroup;
    private Group capabilityGroup;

    //
    // Model
    //

    private boolean keyAsType = true;
    private TypeToken<?> keyType;

    private ISerializableCapability keySet;
    private ISerializableCapability valueSet;

    private ArrayList<Method> valueLinks;
    private ArrayList<Method> keyLinks;

    protected MappingPage() {
        super("Mapping");
    }

    protected MappingPage(TypeToken<?> startingType) {
        super("Mapping");
        this.keyType = startingType;
    }

    @Override
    public void createControl(Composite parent) {
        setTitle("New Mapping");
        setMessage("Select a key and value collection followed by the fields to link");

        Composite dialog = new Composite(parent, SWT.NONE);
        GridData data = new GridData(GridData.FILL_BOTH);
        dialog.setLayoutData(data);

        GridLayout layout = new GridLayout();
        layout.numColumns = 2;
        layout.verticalSpacing = 10;
        layout.horizontalSpacing = 10;
        layout.marginWidth = 10;
        layout.marginHeight = 10;
        dialog.setLayout(layout);

        createMetaGroup(dialog);

        Composite selectRow = new Composite(dialog, SWT.NONE);

        GridData selectRowData = new GridData();
        selectRowData.horizontalAlignment = SWT.CENTER;
        selectRow.setLayoutData(selectRowData);

        RowLayout selectRowLayout = new RowLayout();
        selectRowLayout.center = true;
        selectRow.setLayout(selectRowLayout);

        Label useLabel = new Label(selectRow, SWT.LEFT);
        useLabel.setText("Key Type:");

        Button useType = new Button(selectRow, SWT.RADIO);
        useType.setSelection(true);
        useType.setText("Object");
        useType.addSelectionListener(new SelectionListener() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                keyAsType = true;
                updateKey();
                enableKeys();
            }

            @Override
            public void widgetDefaultSelected(SelectionEvent e) {
                // NO OP
            }
        });

        Button useCapability = new Button(selectRow, SWT.RADIO);
        useCapability.setText("Capability Output");
        useCapability.addSelectionListener(new SelectionListener() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                keyAsType = false;
                updateKey();
                enableKeys();
            }

            @Override
            public void widgetDefaultSelected(SelectionEvent e) {
                // NO OP
            }
        });

        createValueGroup(dialog);
        createKeyGroup(dialog);

        keyTree.addSelectionChangedListener(new ISelectionChangedListener() {
            @Override
            public void selectionChanged(SelectionChangedEvent event) {
                updateKey();
            }
        });

        valueTree.addSelectionChangedListener(new ISelectionChangedListener() {
            @Override
            public void selectionChanged(SelectionChangedEvent event) {
                valueSet = selectedCapability(valueTree);

                valueLinkCombo.removeAll();
                valueLinkCombo.add(VALUE_SENTINAL);

                if (valueSet != null) {
                    valueLinks = links(getElementType(valueSet));

                    for (Method m : valueLinks) {
                        valueLinkCombo.add(m.getName());
                    }
                }

                valueLinkCombo.setText(VALUE_SENTINAL);
            }
        });

        createInjectionGroup(dialog);
        enableKeys();
        updateKey();
        setControl(dialog);
    }

    private void enableKeys() {
        objectType.setEnabled(keyAsType);
        objectSelect.setEnabled(keyAsType);
        typeGroup.setEnabled(keyAsType);

        keyTree.getTree().setEnabled(!keyAsType);
        capabilityGroup.setEnabled(!keyAsType);
    }

    private void updateKey() {
        if (keyAsType) {
            keyType = typeFromString(objectType.getText());
        } else {
            keySet = selectedCapability(keyTree);
            keyType = keySet == null ? null : getElementType(keySet);
        }

        keyLinkCombo.removeAll();
        keyLinks = Lists.newArrayList();

        keyLinkCombo.add(VALUE_SENTINAL);

        if (keyType != null) {
            keyLinks = links(keyType);

            for (Method m : keyLinks) {
                keyLinkCombo.add(m.getName());
            }
        }

        keyLinkCombo.setText(VALUE_SENTINAL);

        valueTree.refresh();
    }

    /**
     * Returns the capability selected from the capability listing; returns <tt>null</tt>
     * if no element is selected.
     * @param viewer the capability viewer
     * @return the capability selected from the capability listing, or <tt>null</tt> if no element is selected
     */
    private ISerializableCapability selectedCapability(TreeViewer viewer) {
        Object selected = ((IStructuredSelection) viewer.getSelection()).getFirstElement();

        if (selected == null) {
            return null;
        } else if (selected instanceof ISerializableCapability) {
            return (ISerializableCapability) selected;
        } else if (selected instanceof ICapability) {
            return new DynamicSerializablePipeline(null, null,
                    Lists.<Serializable>newArrayList(((ICapability) selected).getName()),
                    Lists.<ICapabilityArguments>newArrayList(CapabilityArguments.NONE));
        } else if (selected instanceof DerivedCapability) {
            return ((DerivedCapability) selected).toPipeline();
        } else {
            throw new RuntimeException("Unexpected tree entry");
        }
    }

    private TypeToken<?> typeFromString(String data) {
        Class<?> clazz;
        try {
            clazz = Activator.getDefault().getBundle().loadClass(data);
            return TypeToken.of(clazz);
        } catch (ClassNotFoundException e) {
            return null;
        }
    }

    private TypeToken<?> getElementType(ICapability capability) {
        if (capability == null) {
            throw new NullPointerException("capability cannot be null");
        }
        ParameterizedType type = (ParameterizedType) CapabilityUtil.singleOutput(capability).getType().getType();
        Class<?> param = (Class<?>) type.getActualTypeArguments()[0];
        return TypeToken.of(param);
    }

    private void createTypeKeyGroup(Composite composite) {
        typeGroup = new Group(composite, SWT.SHADOW_ETCHED_IN);
        GridData data = new GridData(GridData.FILL_HORIZONTAL);
        typeGroup.setLayoutData(data);
        typeGroup.setText("Map Key");

        GridLayout layout = new GridLayout();
        layout.numColumns = 3;
        typeGroup.setLayout(layout);

        Label objectLabel = new Label(typeGroup, SWT.LEFT);
        objectLabel.setText("Object Type:");

        objectType = new Combo(typeGroup, SWT.LEFT | SWT.BORDER);
        objectType.setText(keyType != null ? keyType.toString() : DEFAULT_KEY_TYPE);
        objectType.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));

        final TypeComboUpdater updater = new TypeComboUpdater(objectType);

        objectType.addModifyListener(new ModifyListener() {
            @Override
            public void modifyText(ModifyEvent e) {
                updateKey();
            }
        });

        objectSelect = new Button(typeGroup, SWT.PUSH);
        objectSelect.setText("Search");
        objectSelect.addSelectionListener(new SelectionListener() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                try {
                    IType selected = TypeUtil.showTypeDialog(getShell());
                    if (selected != null) {
                        String newType = selected.getFullyQualifiedName();
                        objectType.setText(newType);
                        updater.updateSuperTypeList(newType);
                    }
                } catch (JavaModelException ex) {
                    throw new RuntimeException("Error opening type search dialog", ex);
                }
            }

            @Override
            public void widgetDefaultSelected(SelectionEvent e) {
                // NO OP
            }
        });

    }

    private void createCapabilityKeyGroup(Composite composite) {
        capabilityGroup = new Group(composite, SWT.SHADOW_ETCHED_IN);
        GridData data = new GridData(GridData.FILL_BOTH);
        data.minimumHeight = 200;
        capabilityGroup.setLayoutData(data);
        capabilityGroup.setText("Map Key");

        GridLayout layout = new GridLayout();
        layout.numColumns = 1;
        capabilityGroup.setLayout(layout);

        keyTree = new TreeViewer(capabilityGroup, SWT.BORDER | SWT.V_SCROLL | SWT.H_SCROLL);
        keyTree.setContentProvider(new TreeContentProvider());
        keyTree.setLabelProvider(new KeyTreeLabelProvider());

        TreeColumn column = new TreeColumn(keyTree.getTree(), SWT.LEFT);
        column.setWidth(300);
        column.setText("Capability");
        keyTree.getTree().setLayoutData(new GridData(GridData.FILL_BOTH));

        keyTree.setInput(validCapabilities());

    }

    private void createKeyGroup(Composite composite) {
        createTypeKeyGroup(composite);
        createCapabilityKeyGroup(composite);
    }

    private static ArrayList<Method> links(TypeToken<?> type) {
        ArrayList<Method> result = Lists.newArrayList();

        for (Method method : type.getRawType().getMethods()) {
            if (DerivedCapability.isGetter(method)) {
                result.add(method);
            }
        }

        return result;
    }

    private LinkedHashSet<ICapability> validCapabilities() {
        SortedSet<ICapability> consistent = CupidPlatform.getCapabilityRegistry()
                .getCapabilitiesForOutput(TypeToken.of(Collection.class));

        SortedSet<ICapability> linear = CupidPlatform.getCapabilityRegistry()
                .getCapabilities(new Predicate<ICapability>() {
                    @Override
                    public boolean apply(ICapability capability) {
                        return CapabilityUtil.isLinear(capability);
                    }
                });

        return Sets.newLinkedHashSet(Sets.intersection(consistent, linear));
    }

    private void createValueGroup(Composite composite) {
        Group group = new Group(composite, SWT.SHADOW_ETCHED_IN);
        GridData data = new GridData(GridData.FILL_BOTH);
        data.minimumHeight = 300;
        data.verticalSpan = 3;
        group.setText("Map Values");
        group.setLayoutData(data);

        GridLayout layout = new GridLayout();
        layout.numColumns = 1;
        group.setLayout(layout);

        valueTree = new TreeViewer(group, SWT.BORDER | SWT.V_SCROLL | SWT.H_SCROLL);
        valueTree.setContentProvider(new TreeContentProvider());
        valueTree.setLabelProvider(new ValuesLabelProvider());

        TreeColumn column = new TreeColumn(valueTree.getTree(), SWT.LEFT);
        column.setWidth(300);
        column.setText("Capability");
        valueTree.getTree().setLayoutData(new GridData(GridData.FILL_BOTH));

        valueTree.setInput(validCapabilities());
    }

    private void createInjectionGroup(Composite composite) {
        Group group = new Group(composite, SWT.SHADOW_ETCHED_IN);
        GridData data = new GridData(GridData.FILL_BOTH);
        data.horizontalSpan = 2;
        group.setText("Mapping Function");
        group.setLayoutData(data);

        GridLayout layout = new GridLayout();
        layout.numColumns = 5;
        group.setLayout(layout);

        new Label(group, SWT.LEFT).setText("Where");
        keyLinkCombo = new Combo(group, SWT.BORDER | SWT.V_SCROLL | SWT.H_SCROLL);
        keyLinkCombo.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));

        keyLinkCombo.add(VALUE_SENTINAL);
        keyLinkCombo.setText(VALUE_SENTINAL);

        new Label(group, SWT.LEFT).setText("of key equals");
        valueLinkCombo = new Combo(group, SWT.BORDER | SWT.V_SCROLL | SWT.H_SCROLL);
        valueLinkCombo.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));

        valueLinkCombo.add(VALUE_SENTINAL);
        valueLinkCombo.setText(VALUE_SENTINAL);

        new Label(group, SWT.LEFT).setText("of value");
    }

    private class KeyTreeLabelProvider extends LabelProvider {
        @Override
        public String getText(Object element) {
            if (element instanceof ICapability) {
                ICapability capability = (ICapability) element;
                return capability.getName() + ": " + capability.getDescription();
            } else if (element instanceof DerivedCapability) {
                DerivedCapability capability = (DerivedCapability) element;
                return capability.getGetter().getName();
            } else {
                throw new RuntimeException("Unexpected tree element of type " + element.getClass());
            }
        }
    }

    /**
     * Returns <tt>true</tt> iff the given value generator capability is compatible with the currently
     * select key object or capability. The capability is valid if it is a generator, or if it has a single
     * required input that is compatible with the input type of the key generator.
     * @param generator the generator capability
     * @return <tt>true</tt> iff the given value generator capability is compatible with the currently
     * select key object or capability
     */
    private boolean isValidValue(ICapability generator) {
        if (keyAsType) {
            return CapabilityUtil.isGenerator(generator);
        } else if (keySet != null) {
            if (CapabilityUtil.isGenerator(keySet)) {
                return CapabilityUtil.isGenerator(generator);
            } else {
                return CapabilityUtil.isGenerator(generator) || TypeManager.isCompatible(
                        CapabilityUtil.unaryParameter(generator), CapabilityUtil.unaryParameter(keySet).getType());
            }
        } else {
            return true;
        }
    }

    private void createMetaGroup(Composite parent) {
        Group metaGroup = new Group(parent, SWT.SHADOW_ETCHED_IN);
        GridData data = new GridData(GridData.FILL_HORIZONTAL);
        data.horizontalSpan = 2;
        metaGroup.setText("Meta Information");
        metaGroup.setLayoutData(data);

        GridLayout layout = new GridLayout();
        layout.numColumns = 4;
        metaGroup.setLayout(layout);

        Label nameLabel = new Label(metaGroup, SWT.LEFT);
        nameLabel.setText("Name:");

        nameEntry = new Text(metaGroup, SWT.SINGLE | SWT.BORDER);
        nameEntry.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
        nameEntry.setText(DEFAULT_NAME);

        Label descriptionLabel = new Label(metaGroup, SWT.LEFT);
        descriptionLabel.setText("Description:");

        descriptionEntry = new Text(metaGroup, SWT.SINGLE | SWT.BORDER);
        descriptionEntry.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
        descriptionEntry.setText(DEFAULT_DESCRIPTION);

        metaGroup.pack();
    }

    private class ValuesLabelProvider extends LabelProvider implements ITableLabelProvider, ITableColorProvider {

        @Override
        public String getText(Object element) {
            if (element instanceof ICapability) {
                ICapability capability = (ICapability) element;
                return capability.getName() + ": " + capability.getDescription();
            } else if (element instanceof DerivedCapability) {
                DerivedCapability capability = (DerivedCapability) element;
                return capability.getGetter().getName();
            } else {
                throw new RuntimeException("Unexpected tree element of type " + element.getClass());
            }
        }

        @Override
        public Color getForeground(Object element, int columnIndex) {
            ICapability capability = element instanceof ICapability ? (ICapability) element
                    : ((DerivedCapability) element).getCapability();

            return isValidValue(capability) ? null : Display.getCurrent().getSystemColor(SWT.COLOR_DARK_RED);

        }

        @Override
        public Color getBackground(Object element, int columnIndex) {
            // USE DEFAULT
            return null;
        }

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

        @Override
        public String getColumnText(Object element, int columnIndex) {
            if (columnIndex == 0) {
                return getText(element);
            } else {
                return null;
            }
        }

    }

    private class TreeContentProvider implements ITreeContentProvider {

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

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

        @Override
        public Object[] getElements(Object inputElement) {
            Collection<ICapability> xs = (Collection<ICapability>) inputElement;
            return xs.toArray();
        }

        @Override
        public Object[] getChildren(Object parentElement) {
            if (parentElement instanceof ICapability) {
                ICapability c = (ICapability) parentElement;

                if (CapabilityUtil.hasSingleOutput(c)) {
                    return DerivedCapability.derivedProjections(c, CapabilityUtil.singleOutput(c)).toArray();
                } else {
                    return DerivedCapability.derived((ICapability) parentElement).toArray();
                }

            } else if (parentElement instanceof DerivedCapability) {
                DerivedCapability c = (DerivedCapability) parentElement;

                return c.getGetter() == null
                        ? DerivedCapability.derivedProjections(c.getCapability(), c.getOutput()).toArray()
                        : new Object[] {};
            } else {
                return new Object[] {};
            }
        }

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

        @Override
        public boolean hasChildren(Object element) {
            // TODO: make efficient
            return getChildren(element).length > 0;
        }
    }

    private String pullComboLink(Combo box) {
        return box.getText().equals(VALUE_SENTINAL) ? null : box.getText();

    }

    public boolean hasKeyAsType() {
        return keyAsType;
    }

    @SuppressWarnings({ "rawtypes", "unchecked" })
    public ValueMapping<?, ?> getValueMapping() {
        String name = nameEntry.getText();
        String description = descriptionEntry.getText();

        return new ValueMapping(name, description, keyType, pullComboLink(keyLinkCombo), valueSet,
                CapabilityUtil.singleOutput(valueSet).getType(), pullComboLink(valueLinkCombo));

    }

    public CapabilityMapping<?, ?, ?> getCapabilityMapping() {
        String name = nameEntry.getText();
        String description = descriptionEntry.getText();

        return new CapabilityMapping(name, description,
                CapabilityUtil.isGenerator(keySet) ? TypeToken.of(Void.class)
                        : CapabilityUtil.unaryParameter(keySet).getType(),
                keySet, CapabilityUtil.singleOutput(keySet).getType(), pullComboLink(keyLinkCombo), valueSet,
                CapabilityUtil.singleOutput(valueSet).getType(), pullComboLink(valueLinkCombo));
    }
}