/* * ARX: Powerful Data Anonymization * Copyright 2012 - 2017 Fabian Prasser, Florian Kohlmayer and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.deidentifier.arx.gui.view.impl.wizard; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import org.apache.commons.math3.util.Pair; import org.deidentifier.arx.DataType; import org.deidentifier.arx.DataType.DataTypeWithFormat; import org.deidentifier.arx.gui.resources.Resources; import org.deidentifier.arx.gui.view.SWTUtil; import org.deidentifier.arx.io.ImportColumn; import org.eclipse.jface.layout.GridDataFactory; 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.ComboBoxCellEditor; 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 { /** * Implements editing support for data type column within the column page * * This allows to change the data type of columns. The modifications are * performed with a combo box {@link ComboBoxCellEditor}. */ public class DatatypeEditingSupport extends EditingSupport { /** Reference to actual viewer. */ private TableViewer viewer; /** Editors*/ private Map<ImportWizardModelColumn, AutoDropComboBoxViewerCellEditor> editors = new HashMap<ImportWizardModelColumn, AutoDropComboBoxViewerCellEditor>(); /** Types*/ private Map<ImportWizardModelColumn, Map<String, DataType<?>>> types = new HashMap<ImportWizardModelColumn, Map<String, DataType<?>>>(); /** * Creates a new editor for the given {@link TableViewer}. * * @param viewer The TableViewer this editor is implemented for * @param columns The columns */ public DatatypeEditingSupport(TableViewer viewer) { super(viewer); this.viewer = viewer; } /** * Updates this editing support * @param columns */ public void update(List<ImportWizardModelColumn> columns) { for (ImportWizardModelColumn column : columns) { this.types.put(column, new HashMap<String, DataType<?>>()); List<Pair<DataType<?>, Double>> matchingtypes = wizardImport.getData().getMatchingDataTypes(column); List<String> labels = new ArrayList<String>(); List<DataType<?>> types = new ArrayList<DataType<?>>(); for (Pair<DataType<?>, Double> match : matchingtypes) { StringBuilder builder = new StringBuilder(); builder.append(match.getFirst().getDescription().getLabel()); if (match.getFirst() instanceof DataTypeWithFormat && ((DataTypeWithFormat)match.getFirst()).getFormat() != null) { builder.append(" ("); //$NON-NLS-1$ builder.append(((DataTypeWithFormat)match.getFirst()).getFormat()); builder.append(")"); //$NON-NLS-1$ } builder.append(" "); //$NON-NLS-1$ builder.append((int)(match.getSecond() * 100d)); builder.append("%"); //$NON-NLS-1$ String label = builder.toString(); DataType<?> type = match.getFirst(); labels.add(label); types.add(type); this.types.get(column).put(label, type); } AutoDropComboBoxViewerCellEditor editor = new AutoDropComboBoxViewerCellEditor(viewer.getTable()); editor.setContentProvider(new ArrayContentProvider()); editor.setInput(labels.toArray(new String[labels.size()])); editors.put(column, editor); } } /** * Indicates that enabled cells within this column can be edited. * * @param column * @return */ @Override protected boolean canEdit(Object column) { return ((ImportWizardModelColumn) column).isEnabled(); } /** * Returns a reference to {@link #editor}. * * @param arg0 * @return */ @Override protected CellEditor getCellEditor(Object arg0) { return editors.get(arg0); } /** * Returns current index of {@link #choices} for given column datatype. * * @param element * @return */ @Override protected Object getValue(Object element) { ImportWizardModelColumn column = (ImportWizardModelColumn) element; for (Entry<String, DataType<?>> entry : types.get(column).entrySet()) { if (entry.getValue() == column.getColumn().getDataType()) { return entry.getKey(); } } return null; } /** * Applies data type choice made by the user * * If a data type, 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 data type. 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. * * @param element * @param value */ @Override protected void setValue(Object element, Object value) { // Extract String label = (String)value; ImportWizardModelColumn wizardColumn = (ImportWizardModelColumn) element; ImportColumn column = wizardColumn.getColumn(); DataType<?> type = types.get(wizardColumn).get(label); /* Apply datatype */ column.setDataType(type); 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. * * @param column * @return */ @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()}). * * @param arg0 * @return */ @Override protected Object getValue(Object arg0) { return ((ImportWizardModelColumn) arg0).getColumn().getAliasName(); } /** * Sets name for given column ({@link ImportColumn#setAliasName(String)}). * * @param element * @param value */ @Override protected void setValue(Object element, Object value) { ((ImportWizardModelColumn) element).getColumn().setAliasName((String) value); getViewer().update(element, null); check(); } } /** * Works around JFace bugs. * @see https://bugs.eclipse.org/bugs/show_bug.cgi?id=230398 * */ private static class AutoDropComboBoxViewerCellEditor extends ComboBoxViewerCellEditor { /** * * * @param parent */ 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; } } /** * 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. * * @param arg0 */ @Override public void widgetSelected(SelectionEvent arg0) { for (int i = 0; i < table.getItems().length; i++) { setChecked(i, selectAll); } selectAll = !selectAll; if (selectAll) { tblclmnEnabled.setToolTipText(Resources.getMessage("ImportWizardPageColumns.4")); //$NON-NLS-1$ tblclmnEnabled.setText(Resources.getMessage("ImportWizardPageColumns.5")); //$NON-NLS-1$ } else { tblclmnEnabled.setToolTipText(Resources.getMessage("ImportWizardPageColumns.6")); //$NON-NLS-1$ tblclmnEnabled.setText(Resources.getMessage("ImportWizardPageColumns.7")); //$NON-NLS-1$ } check(); } /** * 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; /** View */ private Table table; /** View */ private CheckboxTableViewer checkboxTableViewer; /** View */ private TableColumn tblclmnName; /** View */ private TableViewerColumn tableViewerColumnName; /** View */ private TableColumn tblclmnDatatype; /** View */ private TableViewerColumn tableViewerColumnDatatype; /** View */ private TableColumn tblclmnEnabled; /** View */ private TableViewerColumn tableViewerColumnEnabled; /** View */ private TableColumn tblclmnFormat; /** View */ private TableViewerColumn tableViewerColumnFormat; /** View */ private DatatypeEditingSupport tableViewerColumnDatatypeEditingSupport; /** View */ private Button btnUp; /** View */ private Button btnDown; /** View */ private Button btnCleansing; /** 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"); //$NON-NLS-1$ this.wizardImport = wizardImport; setTitle(Resources.getMessage("ImportWizardPageColumns.9")); //$NON-NLS-1$ setDescription(Resources.getMessage("ImportWizardPageColumns.10")); //$NON-NLS-1$ } /** * Creates the design of this page along with the appropriate listeners. * * @param parent */ 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 = SWTUtil.createTableViewerCheckbox(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) { ((ImportWizardModelColumn) event.getElement()).setEnabled(event.getChecked()); check(); } }); /* 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(Resources.getMessage("ImportWizardPageColumns.11")); //$NON-NLS-1$ tblclmnEnabled.setText(Resources.getMessage("ImportWizardPageColumns.12")); //$NON-NLS-1$ 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) { ImportWizardModelColumn column = (ImportWizardModelColumn) element; return column.getColumn().getAliasName(); } }); /* Actual column for {@link tableViewerColumnName} */ tblclmnName = tableViewerColumnName.getColumn(); tblclmnName.setToolTipText(Resources.getMessage("ImportWizardPageColumns.13")); //$NON-NLS-1$ tblclmnName.setWidth(300); tblclmnName.setText(Resources.getMessage("ImportWizardPageColumns.14")); //$NON-NLS-1$ /* Column containing the datatypes */ tableViewerColumnDatatype = new TableViewerColumn(checkboxTableViewer, SWT.NONE); tableViewerColumnDatatypeEditingSupport = new DatatypeEditingSupport(checkboxTableViewer); tableViewerColumnDatatype.setEditingSupport(tableViewerColumnDatatypeEditingSupport); 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) { ImportWizardModelColumn column = (ImportWizardModelColumn) element; DataType<?> datatype = column.getColumn().getDataType(); return datatype.getDescription().getLabel(); } }); /* Actual column for {@link tableViewerColumnDatatype} */ tblclmnDatatype = tableViewerColumnDatatype.getColumn(); tblclmnDatatype.setToolTipText(Resources.getMessage("ImportWizardPageColumns.15")); //$NON-NLS-1$ tblclmnDatatype.setWidth(120); tblclmnDatatype.setText(Resources.getMessage("ImportWizardPageColumns.16")); //$NON-NLS-1$ /* 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 ""; //$NON-NLS-1$ } }); /* Actual column for {@link tableViewerColumnFormat} */ tblclmnFormat = tableViewerColumnFormat.getColumn(); tblclmnFormat.setWidth(120); tblclmnFormat.setToolTipText(Resources.getMessage("ImportWizardPageColumns.18")); //$NON-NLS-1$ tblclmnFormat.setWidth(100); tblclmnFormat.setText(Resources.getMessage("ImportWizardPageColumns.19")); //$NON-NLS-1$ /* Buttons to move column up */ btnUp = new Button(container, SWT.NONE); btnUp.setText(Resources.getMessage("ImportWizardPageColumns.20")); //$NON-NLS-1$ btnUp.setImage(wizardImport.getController() .getResources() .getManagedImage("arrow_up.png")); //$NON-NLS-1$ 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(Resources.getMessage("ImportWizardPageColumns.22")); //$NON-NLS-1$ btnDown.setImage(wizardImport.getController() .getResources() .getManagedImage("arrow_down.png")); //$NON-NLS-1$ 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); } } }); btnCleansing = new Button(container, SWT.CHECK); btnCleansing.setLayoutData(GridDataFactory.fillDefaults().grab(true, false).span(2, 1).create()); btnCleansing.setText(Resources.getMessage("ImportWizardPageColumns.24")); //$NON-NLS-1$ btnCleansing.setToolTipText(Resources.getMessage("ImportWizardPageColumns.25")); //$NON-NLS-1$ btnCleansing.setEnabled(true); btnCleansing.setSelection(wizardImport.getData().isPerformCleansing()); btnCleansing.addSelectionListener(new SelectionAdapter() { public void widgetSelected(SelectionEvent arg0) { wizardImport.getData().setPerformCleansing(btnCleansing.getSelection()); } }); /* Wait for at least one column to be enabled */ setPageComplete(false); } /** * Adds input to table viewer once page gets visible. * * @param visible */ @Override public void setVisible(boolean visible) { super.setVisible(visible); if (visible) { for (ImportWizardModelColumn column : wizardImport.getData().getWizardColumns()) { column.getColumn().setDataType(wizardImport.getData().getMatchingDataTypes(column).iterator().next().getFirst()); } tableViewerColumnDatatypeEditingSupport.update(wizardImport.getData() .getWizardColumns()); checkboxTableViewer.setInput(wizardImport.getData() .getWizardColumns()); check(); } } /** * Checks whether the current selection of columns is suited for import */ private void check(){ // Check selection boolean selected = false; for (ImportWizardModelColumn column : wizardImport.getData() .getWizardColumns()) { selected |= column.isEnabled(); } if (!selected) { setErrorMessage(Resources.getMessage("ImportWizardPageColumns.26")); //$NON-NLS-1$ setPageComplete(false); return; } // Check names for (ImportWizardModelColumn c1 : wizardImport.getData().getWizardColumns()) { if (c1.isEnabled()) { String name1 = c1.getColumn().getAliasName(); for (ImportWizardModelColumn c2 : wizardImport.getData().getWizardColumns()) { if (c2.isEnabled() && c1 != c2 && name1.equals(c2.getColumn().getAliasName())) { setErrorMessage(Resources.getMessage("ImportWizardPageColumns.27") + name1); //$NON-NLS-1$ setPageComplete(false); return; } } } } // Everything is fine setErrorMessage(null); setPageComplete(true); } }