org.deidentifier.arx.gui.view.impl.wizard.ImportWizardPageColumns.java Source code

Java tutorial

Introduction

Here is the source code for org.deidentifier.arx.gui.view.impl.wizard.ImportWizardPageColumns.java

Source

/*
 * ARX: Powerful Data Anonymization
 * Copyright (C) 2014 Karol Babioch <karol@babioch.de>
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

package org.deidentifier.arx.gui.view.impl.wizard;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.deidentifier.arx.DataType;
import org.deidentifier.arx.DataType.DataTypeDescription;
import org.deidentifier.arx.DataType.DataTypeWithFormat;
import org.deidentifier.arx.gui.Controller;
import org.deidentifier.arx.io.ImportColumn;
import org.eclipse.jface.viewers.ArrayContentProvider;
import org.eclipse.jface.viewers.CellEditor;
import org.eclipse.jface.viewers.CheckStateChangedEvent;
import org.eclipse.jface.viewers.CheckboxTableViewer;
import org.eclipse.jface.viewers.ColumnLabelProvider;
import org.eclipse.jface.viewers.ComboBoxViewerCellEditor;
import org.eclipse.jface.viewers.EditingSupport;
import org.eclipse.jface.viewers.ICheckStateListener;
import org.eclipse.jface.viewers.ICheckStateProvider;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.viewers.TableViewerColumn;
import org.eclipse.jface.viewers.TextCellEditor;
import org.eclipse.jface.wizard.WizardPage;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;

/**
 * Column overview page
 * 
 * This pages gives the user an overview of the detected columns and allows him
 * to change things around. First of all columns can be enabled or disabled on
 * an individual basis. Secondly the order of the columns can be changed around.
 * Furthermore a data type along with a format string can be defined for each
 * column.
 * 
 * A single column is represented by {@link ImportWizardModelColumn}, the list
 * of all detected columns can be accessed by
 * {@link ImportWizardModel#getWizardColumns()}.
 * 
 * @author Karol Babioch
 * @author Fabian Prasser
 */
public class ImportWizardPageColumns extends WizardPage {

    /**
     * Works around JFace bugs. 
     * @see https://bugs.eclipse.org/bugs/show_bug.cgi?id=230398
     *
     */
    private static class AutoDropComboBoxViewerCellEditor extends ComboBoxViewerCellEditor {
        protected AutoDropComboBoxViewerCellEditor(Composite parent) {
            super(parent, SWT.READ_ONLY);
            setActivationStyle(DROP_DOWN_ON_MOUSE_ACTIVATION);
        }

        @Override
        protected Control createControl(Composite parent) {
            final Control control = super.createControl(parent);
            getViewer().getCCombo().addSelectionListener(new SelectionAdapter() {
                @Override
                public void widgetSelected(SelectionEvent e) {
                    focusLost();
                }
            });
            return control;
        }
    }

    /**
     * Implements editing support for datatype column within the column page
     * 
     * This allows to change the datatype of columns. The modifications are
     * performed with a combo box {@link ComboBoxCellEditor}.
     */
    public class DatatypeEditingSupport extends EditingSupport {

        /**
         * Reference to actual editor
         */
        private AutoDropComboBoxViewerCellEditor editor;

        /**
         * Allowed values for the user to choose from
         * 
         * This array contains all of the choices the user can make. The array
         * gets populated during runtime.
         */
        private String[] choices;

        /**
         * Creates a new editor for the given {@link TableViewer}.
         * 
         * @param viewer
         *            The TableViewer this editor is implemented for
         */
        public DatatypeEditingSupport(TableViewer viewer) {

            super(viewer);

            List<String> labels = new ArrayList<String>();
            for (DataTypeDescription<?> description : DataType.list()) {
                /* Remove OrderedString from list of choices for now */
                if (description.newInstance().getClass() == DataType.ORDERED_STRING.getClass()) {
                    continue;
                }
                labels.add(description.getLabel());
            }

            choices = labels.toArray(new String[labels.size()]);
            editor = new AutoDropComboBoxViewerCellEditor(viewer.getTable());
            editor.setContentProvider(new ArrayContentProvider());
            editor.setInput(choices);
        }

        /**
         * Indicates that enabled cells within this column can be edited
         */
        @Override
        protected boolean canEdit(Object column) {
            return ((ImportWizardModelColumn) column).isEnabled();
        }

        /**
         * Returns a reference to {@link #editor}.
         */
        @Override
        protected CellEditor getCellEditor(Object arg0) {
            return editor;
        }

        /**
         * Returns current index of {@link #choices} for given column datatype
         */
        @Override
        protected Object getValue(Object element) {

            DataType<?> datatype = ((ImportWizardModelColumn) element).getColumn().getDataType();
            return datatype.getDescription().getLabel();
        }

        /**
         * Applies datatype choice made by the user
         * 
         * If a datatype, which requires a format string, was selected an input
         * dialog will be shown {@link actionShowFormatInputDialog}. Otherwise
         * the choice is directly applied. THe input dialog itself will make
         * sure that the format string is valid for the datatype. This method on
         * the other hand will try to apply the format string to the available
         * preview data {@link ImportWizardModel#getPreviewData()} making sure
         * that it matches. In case of an error the choice is discarded.
         */
        @Override
        protected void setValue(Object element, Object value) {

            final String HEADER = "Format string";
            final String BODY = "Please provide a format string describing each item of this column";

            String label = (String) value;
            ImportWizardModelColumn wizardColumn = (ImportWizardModelColumn) element;
            ImportColumn column = wizardColumn.getColumn();
            List<String> previewData = wizardImport.getData().getPreviewData(wizardColumn);

            for (DataTypeDescription<?> description : DataType.list()) {
                if (description.getLabel().equals(label)) {

                    DataType<?> datatype = null;
                    if (description.hasFormat()) {

                        final Controller controller = wizardImport.getController();
                        String format = null;
                        if (column.getDataType().getClass() == description.newInstance().getClass()) {

                            format = controller.actionShowFormatInputDialog(getShell(), HEADER, BODY,
                                    ((DataTypeWithFormat) column.getDataType()).getFormat(), description,
                                    previewData);

                        } else {
                            format = controller.actionShowFormatInputDialog(getShell(), HEADER, BODY, description,
                                    previewData);
                        }

                        if (format != null) {
                            datatype = description.newInstance(format);
                        } else {
                            /* Invalid string or aborted by user */
                            return;
                        }
                    } else {
                        /* Datatype has no format */
                        datatype = description.newInstance();
                    }

                    for (String data : previewData) {
                        if (!datatype.isValid(data)) {
                            datatype = DataType.STRING;
                        }
                    }

                    /* Apply datatype */
                    column.setDataType(datatype);
                    getViewer().update(element, null);
                    return;
                }
            }
        }
    }

    /**
     * Implements the editing support for name column
     * 
     * This allows to change the name of columns. The modifications are
     * performed within a simple text field {@link TextCellEditor}.
     */
    public class NameEditingSupport extends EditingSupport {

        /**
         * Reference to actual editor
         */
        private TextCellEditor editor;

        /**
         * Creates a new editor for the given {@link TableViewer}.
         * 
         * @param viewer
         *            The TableViewer this editor is implemented for
         */
        public NameEditingSupport(TableViewer viewer) {

            super(viewer);
            editor = new TextCellEditor(viewer.getTable());
        }

        /**
         * Indicates that enabled cells within this column can be edited
         */
        @Override
        protected boolean canEdit(Object column) {
            return ((ImportWizardModelColumn) column).isEnabled();
        }

        @Override
        protected CellEditor getCellEditor(Object arg0) {
            return editor;
        }

        /**
         * Retrieves name of column ({@link ImportColumn#getAliasName()})
         */
        @Override
        protected Object getValue(Object arg0) {
            return ((ImportWizardModelColumn) arg0).getColumn().getAliasName();
        }

        /**
         * Sets name for given column ({@link ImportColumn#setAliasName(String)})
         */
        @Override
        protected void setValue(Object element, Object value) {

            ((ImportWizardModelColumn) element).getColumn().setAliasName((String) value);
            getViewer().update(element, null);
        }
    }

    /**
     * Listener for click events of the "enabled" column
     * 
     * By clicking on the column all items can be selected and/or deselected at
     * once. The result of the action depends upon {@link #selectAll}.
     */
    private final class ColumnEnabledSelectionListener extends SelectionAdapter {

        /**
         * (Un)checks all of the items at once
         * 
         * This iterates through all of the items and invokes
         * {@link #setChecked(int, Boolean)} for all of them. Furthermore the
         * tooltip is changed appropriately.
         */
        @Override
        public void widgetSelected(SelectionEvent arg0) {

            for (int i = 0; i < table.getItems().length; i++) {
                setChecked(i, selectAll);
            }

            selectAll = !selectAll;

            if (selectAll) {
                tblclmnEnabled.setToolTipText("Select all");
                tblclmnEnabled.setText("SA");
            } else {
                tblclmnEnabled.setToolTipText("Deselect all");
                tblclmnEnabled.setText("DA");
            }
        }

        /**
         * Applies a boolean value to the given item
         * 
         * @param i
         *            Item that <code>check</code> should be applied to
         * @param check
         *            Value that should be applied to item <code>i</code>
         */
        private void setChecked(int i, Boolean check) {

            table.getItem(i).setChecked(check);
            wizardImport.getData().getWizardColumns().get(i).setEnabled(check);

            setPageComplete(false);

            for (ImportWizardModelColumn column : wizardImport.getData().getWizardColumns()) {

                if (column.isEnabled()) {
                    setPageComplete(true);
                    return;
                }
            }
        }
    }

    /**
     * Reference to the wizard containing this page
     */
    private ImportWizard wizardImport;
    /* Widgets */
    private Table table;
    private CheckboxTableViewer checkboxTableViewer;
    private TableColumn tblclmnName;
    private TableViewerColumn tableViewerColumnName;
    private TableColumn tblclmnDatatype;
    private TableViewerColumn tableViewerColumnDatatype;
    private TableColumn tblclmnEnabled;
    private TableViewerColumn tableViewerColumnEnabled;
    private TableColumn tblclmnFormat;

    private TableViewerColumn tableViewerColumnFormat;

    private Button btnUp;

    private Button btnDown;

    /**
     * Indicator for the next action of {@link ColumnEnabledSelectionListener}
     */
    private boolean selectAll = false;

    /**
     * Creates a new instance of this page and sets its title and description
     * 
     * @param wizardImport
     *            Reference to wizard containing this page
     */
    public ImportWizardPageColumns(ImportWizard wizardImport) {

        super("WizardImportCsvPage");

        this.wizardImport = wizardImport;

        setTitle("Columns");
        setDescription("Please check and/or modify the detected columns");

    }

    /**
     * Creates the design of this page along with the appropriate listeners
     */
    public void createControl(Composite parent) {

        Composite container = new Composite(parent, SWT.NULL);

        setControl(container);
        container.setLayout(new GridLayout(2, false));

        /* TableViewer for the columns with a checkbox in each row */
        checkboxTableViewer = CheckboxTableViewer.newCheckList(container, SWT.BORDER | SWT.FULL_SELECTION);
        checkboxTableViewer.setContentProvider(new ArrayContentProvider());
        checkboxTableViewer.setCheckStateProvider(new ICheckStateProvider() {

            /** @return {@link ImportWizardModelColumn#isEnabled()} */
            @Override
            public boolean isChecked(Object column) {
                return ((ImportWizardModelColumn) column).isEnabled();
            }

            /** No column should be grayed out */
            @Override
            public boolean isGrayed(Object column) {
                return false;
            }
        });
        checkboxTableViewer.addCheckStateListener(new ICheckStateListener() {

            /**
             * Sets the enabled status for the given item
             * 
             * Using {@link ImportWizardModelColumn#setEnabled(boolean)} this
             * method will set the enabled flag for the given column.
             * Furthermore it makes sure the page is marked as complete once at
             * least one item is selected.
             */
            @Override
            public void checkStateChanged(CheckStateChangedEvent event) {

                setPageComplete(false);
                ((ImportWizardModelColumn) event.getElement()).setEnabled(event.getChecked());
                for (ImportWizardModelColumn column : wizardImport.getData().getWizardColumns()) {

                    if (column.isEnabled()) {
                        setPageComplete(true);
                        return;
                    }
                }
            }
        });

        /* Actual table for {@link #checkboxTableViewer} */
        table = checkboxTableViewer.getTable();
        table.setHeaderVisible(true);
        table.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 2, 1));
        table.addSelectionListener(new SelectionAdapter() {

            /**
             * Makes the buttons for column reordering (un)clickable
             * 
             * This checks the current selection and will enable and/or disable
             * the {@link #btnUp} and {@link #btnDown} if either the first or
             * last item is currently selected.
             */
            @Override
            public void widgetSelected(SelectionEvent e) {

                /* Check for first item */
                if (table.getSelectionIndex() == 0) {
                    btnUp.setEnabled(false);
                } else {
                    btnUp.setEnabled(true);
                }

                /* Check for last item */
                if (table.getSelectionIndex() == table.getItemCount() - 1) {
                    btnDown.setEnabled(false);
                } else {
                    btnDown.setEnabled(true);
                }
            }
        });

        /* Empty column to make checkboxes appear in an own cell */
        tableViewerColumnEnabled = new TableViewerColumn(checkboxTableViewer, SWT.NONE);
        tableViewerColumnEnabled.setLabelProvider(new ColumnLabelProvider() {
            /** Cells within this column should always be empty */
            @Override
            public String getText(Object element) {
                return null;
            }
        });

        /* Actual column for {@link tableViewerColumnEnabled} */
        tblclmnEnabled = tableViewerColumnEnabled.getColumn();
        tblclmnEnabled.setToolTipText("Deselect all");
        tblclmnEnabled.setText("DA");
        tblclmnEnabled.setWidth(40);
        tblclmnEnabled.addSelectionListener(new ColumnEnabledSelectionListener());

        /* Column containing the names */
        tableViewerColumnName = new TableViewerColumn(checkboxTableViewer, SWT.NONE);
        tableViewerColumnName.setEditingSupport(new NameEditingSupport(checkboxTableViewer));
        tableViewerColumnName.setLabelProvider(new ColumnLabelProvider() {

            /**
             * Gets name of cells from {@link ImportColumn#getAliasName()}
             * 
             * This also makes sure that all column names are unique using
             * {@link #uniqueColumnNames()}. In case there are duplicates it
             * sets an error message.
             */
            @Override
            public String getText(Object element) {

                if (!uniqueColumnNames()) {
                    setErrorMessage("Column names need to be unique");
                    setPageComplete(false);
                } else {
                    setErrorMessage(null);
                    setPageComplete(true);
                }

                ImportWizardModelColumn column = (ImportWizardModelColumn) element;
                return column.getColumn().getAliasName();
            }
        });

        /* Actual column for {@link tableViewerColumnName} */
        tblclmnName = tableViewerColumnName.getColumn();
        tblclmnName.setToolTipText("Name of the column");
        tblclmnName.setWidth(300);
        tblclmnName.setText("Name");

        /* Column containing the datatypes */
        tableViewerColumnDatatype = new TableViewerColumn(checkboxTableViewer, SWT.NONE);
        tableViewerColumnDatatype.setEditingSupport(new DatatypeEditingSupport(checkboxTableViewer));
        tableViewerColumnDatatype.setLabelProvider(new ColumnLabelProvider() {

            /**
             * Gets string representation for given datatype of column
             * 
             * Internally it makes use of {@link ImportColumn#getDataType()}.
             */
            @Override
            public String getText(Object element) {

                DataType<?> datatype = ((ImportWizardModelColumn) element).getColumn().getDataType();

                for (DataTypeDescription<?> description : DataType.list()) {
                    if (description.newInstance().getClass() == datatype.getClass()) {
                        return description.getLabel();
                    }
                }
                return null;
            }
        });

        /* Actual column for {@link tableViewerColumnDatatype} */
        tblclmnDatatype = tableViewerColumnDatatype.getColumn();
        tblclmnDatatype.setToolTipText("Datatype of the column");
        tblclmnDatatype.setWidth(120);
        tblclmnDatatype.setText("Datatype");

        /* Column containing the format of the format */
        tableViewerColumnFormat = new TableViewerColumn(checkboxTableViewer, SWT.NONE);
        tableViewerColumnFormat.setLabelProvider(new ColumnLabelProvider() {

            /**
             * Returns format string of datatype for column
             * 
             * This retrieves the used format string of the chosen datatype for
             * each column.
             * 
             * @note In case of simple datatypes without a format specifier an
             *       empty string is returned.
             * 
             * @param element
             *            Column in question
             */
            @Override
            public String getText(Object element) {

                DataType<?> column = ((ImportWizardModelColumn) element).getColumn().getDataType();
                if (column instanceof DataTypeWithFormat) {
                    return ((DataTypeWithFormat) column).getFormat();
                }
                return "";
            }
        });

        /* Actual column for {@link tableViewerColumnFormat} */
        tblclmnFormat = tableViewerColumnFormat.getColumn();
        tblclmnFormat.setWidth(120);
        tblclmnFormat.setToolTipText("Format of the associated datatype");
        tblclmnFormat.setWidth(100);
        tblclmnFormat.setText("Format");

        /* Buttons to move column up */
        btnUp = new Button(container, SWT.NONE);
        btnUp.setText("Move up");
        btnUp.setImage(wizardImport.getController().getResources().getImage("arrow_up.png"));
        btnUp.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false, 1, 1));
        btnUp.setEnabled(false);
        btnUp.addSelectionListener(new SelectionAdapter() {

            /**
             * Swaps the current element with the one above
             * 
             * This makes also sure that the button is disabled once the top is
             * reached by notifying the appropriate selection listener.
             */
            @Override
            public void widgetSelected(SelectionEvent e) {

                int current = table.getSelectionIndex();
                if (current > 0) {
                    List<ImportWizardModelColumn> columns = wizardImport.getData().getWizardColumns();
                    Collections.swap(columns, current, current - 1);
                    checkboxTableViewer.setInput(columns);
                    table.notifyListeners(SWT.Selection, null);
                }
            }
        });

        /* Buttons to move column down */
        btnDown = new Button(container, SWT.NONE);
        btnDown.setText("Move down");
        btnDown.setImage(wizardImport.getController().getResources().getImage("arrow_down.png"));
        btnDown.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false, 1, 1));
        btnDown.setEnabled(false);
        btnDown.addSelectionListener(new SelectionAdapter() {

            /**
             * Swaps the current element with the one below
             * 
             * This makes also sure that the button is disabled once the bottom
             * is reached by notifying the appropriate selection listener.
             */
            @Override
            public void widgetSelected(SelectionEvent e) {

                int current = table.getSelectionIndex();
                if (current < table.getItemCount() - 1) {

                    List<ImportWizardModelColumn> columns = wizardImport.getData().getWizardColumns();
                    Collections.swap(columns, current, current + 1);
                    checkboxTableViewer.setInput(columns);
                    table.notifyListeners(SWT.Selection, null);
                }
            }
        });

        /* Wait for at least one column to be enabled */
        setPageComplete(false);
    }

    /**
     * Adds input to table viewer once page gets visible
     */
    @Override
    public void setVisible(boolean visible) {

        super.setVisible(visible);
        if (visible) {
            checkboxTableViewer.setInput(wizardImport.getData().getWizardColumns());
            setPageComplete((wizardImport.getData().getWizardColumns().size() > 0));
        }
    }

    /**
     * Checks whether column names are unique
     * 
     * @return True if column names are unique, false otherwise
     */
    protected boolean uniqueColumnNames() {

        for (ImportWizardModelColumn c1 : wizardImport.getData().getWizardColumns()) {

            for (ImportWizardModelColumn c2 : wizardImport.getData().getWizardColumns()) {

                if (c1 != c2 && c1.getColumn().getAliasName().equals(c2.getColumn().getAliasName())) {

                    return false;
                }
            }
        }
        return true;
    }
}