/*
* 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.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Iterator;
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.usermodel.WorkbookFactory;
import org.deidentifier.arx.DataType;
import org.deidentifier.arx.gui.Controller;
import org.deidentifier.arx.gui.resources.Resources;
import org.deidentifier.arx.gui.view.SWTUtil;
import org.deidentifier.arx.io.ImportAdapter;
import org.deidentifier.arx.io.ImportColumn;
import org.deidentifier.arx.io.ImportColumnExcel;
import org.deidentifier.arx.io.ImportConfigurationExcel;
import org.eclipse.jface.viewers.ArrayContentProvider;
import org.eclipse.jface.viewers.ColumnLabelProvider;
import org.eclipse.jface.viewers.ColumnViewerToolTipSupport;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.viewers.TableViewerColumn;
import org.eclipse.jface.window.ToolTip;
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.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
/**
* Excel page
*
* This page offers means to import data from an Excel file. It contains
* mechanisms to select such a file, and offers the user the ability to choose
* the sheet to import from and whether or not the first row contains a header
* describing each column. A live preview makes sure the user will immediately
* see whether or not his choices make any sense.
*
* All of the data gathered on this page is stored within {@link ImportWizardModel}.
*
* This includes:
*
* <ul>
* <li>{@link ImportWizardModel#setWizardColumns(List)}</li>
* <li>{@link ImportWizardModel#setFirstRowContainsHeader(boolean)</li>
* <li>{@link ImportWizardModel#setFileLocation(String)}</li>
* <li>{@link ImportWizardModel#setExcelSheetIndex(int)}</li>
* </ul>
*
* @author Karol Babioch
* @author Fabian Prasser
*/
public class ImportWizardPageExcel extends WizardPage {
/**
* Label provider for Excel columns
*
* A new instance of this object will be initiated for each column of
* {@link tableViewerPreview}. This class holds the index of the
* appropriate column {@link #index}, making sure they will return the
* correct value for each column.
*/
class ExcelColumnLabelProvider extends ColumnLabelProvider {
/** Index of the column this instance is representing. */
private int index;
/**
* Creates new instance of this class for the given index.
*
* @param index Index the instance should be created for
*/
public ExcelColumnLabelProvider(int index) {
this.index = index;
}
/**
* Returns the string value for the given column.
*
* @param element
* @return
*/
@Override
public String getText(Object element) {
return ((String[]) element)[index];
}
}
/** Reference to the wizard containing this page. */
private ImportWizard wizardImport;
/** Columns detected by this page and passed on to {@link ImportWizardModel}. */
private ArrayList<ImportWizardModelColumn> wizardColumns;
/* Widgets */
/** TODO */
private Label lblLocation;
/** TODO */
private Combo comboLocation;
/** TODO */
private Button btnChoose;
/** TODO */
private Button btnContainsHeader;
/** TODO */
private Combo comboSheet;
/** TODO */
private Label lblSheet;
/** TODO */
private Table tablePreview;
/** TODO */
private TableViewer tableViewerPreview;
/** Preview data. */
ArrayList<String[]> previewData = new ArrayList<String[]>();
/** Workbook Either HSSFWorkbook or XSSFWorkbook, depending upon file type. */
private Workbook workbook;
/** Input stream. */
private InputStream stream;
/**
* Creates a new instance of this page and sets its title and description.
*
* @param wizardImport Reference to wizard containing this page
*/
public ImportWizardPageExcel(ImportWizard wizardImport)
{
super("WizardImportExcelPage"); //$NON-NLS-1$
setTitle("Excel"); //$NON-NLS-1$
setDescription(Resources.getMessage("ImportWizardPageExcel.2")); //$NON-NLS-1$
this.wizardImport = wizardImport;
}
/**
* Creates the design of this page
*
* This adds all the controls to the page along with their listeners.
*
* @param parent
* @note {@link #tablePreview} is not visible until a file is loaded.
*/
public void createControl(Composite parent)
{
Composite container = new Composite(parent, SWT.NULL);
setControl(container);
container.setLayout(new GridLayout(3, false));
/* Location label */
lblLocation = new Label(container, SWT.NONE);
lblLocation.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1));
lblLocation.setText(Resources.getMessage("ImportWizardPageExcel.3")); //$NON-NLS-1$
/* Combo box for selection of file */
comboLocation = new Combo(container, SWT.READ_ONLY);
comboLocation.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1));
comboLocation.addSelectionListener(new SelectionAdapter() {
/**
* Reads the sheets and selects active one
*/
@Override
public void widgetSelected(SelectionEvent arg0) {
/* Try to read in sheets */
try {
readSheets();
} catch (IOException e) {
setErrorMessage(Resources.getMessage("ImportWizardPageExcel.4")); //$NON-NLS-1$
resetPage();
return;
}
/* Make widgets visible */
comboSheet.setVisible(true);
lblSheet.setVisible(true);
btnContainsHeader.setVisible(true);
/* Select active sheet and notify comboSheet about change */
comboSheet.select(workbook.getActiveSheetIndex());
comboSheet.notifyListeners(SWT.Selection, null);
}
});
/* Button to open file selection dialog */
btnChoose = new Button(container, SWT.NONE);
btnChoose.setText(Resources.getMessage("ImportWizardPageExcel.5")); //$NON-NLS-1$
btnChoose.addSelectionListener(new SelectionAdapter() {
/**
* Opens a file selection dialog for Excel files
*
* Both XLS and XLSX files can be selected. If a valid file was
* selected, it is added to {@link #comboLocation} when it wasn't
* already there. In either case it gets preselected.
*
* @see {@link Controller#actionShowOpenFileDialog(String)}
*/
@Override
public void widgetSelected(SelectionEvent arg0) {
/* Open file dialog */
final String path = wizardImport.getController().actionShowOpenFileDialog(getShell(),
"*.xls;*.xlsx"); //$NON-NLS-1$
if (path == null) {
return;
}
/* Check whether path was already added */
if (comboLocation.indexOf(path) == -1) {
comboLocation.add(path, 0);
}
/* Select path and notify comboLocation about change */
comboLocation.select(comboLocation.indexOf(path));
comboLocation.notifyListeners(SWT.Selection, null);
}
});
/* Sheet label */
lblSheet = new Label(container, SWT.NONE);
lblSheet.setVisible(false);
lblSheet.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1));
lblSheet.setText(Resources.getMessage("ImportWizardPageExcel.7")); //$NON-NLS-1$
/* Sheet combobox */
comboSheet = new Combo(container, SWT.READ_ONLY);
comboSheet.setVisible(false);
comboSheet.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1));
comboSheet.addSelectionListener(new SelectionAdapter() {
/**
* (Re-)Evaluate page
*/
@Override
public void widgetSelected(final SelectionEvent arg0) {
evaluatePage();
}
});
/* Place holders */
new Label(container, SWT.NONE);
new Label(container, SWT.NONE);
/* Contains header button */
btnContainsHeader = new Button(container, SWT.CHECK);
btnContainsHeader.setVisible(false);
btnContainsHeader.setText(Resources.getMessage("ImportWizardPageExcel.8")); //$NON-NLS-1$
btnContainsHeader.setSelection(true);
btnContainsHeader.addSelectionListener(new SelectionAdapter() {
/**
* (Re-)Evaluate page
*/
@Override
public void widgetSelected(SelectionEvent arg0) {
evaluatePage();
}
});
/* Place holders */
new Label(container, SWT.NONE);
new Label(container, SWT.NONE);
new Label(container, SWT.NONE);
new Label(container, SWT.NONE);
/* Preview table viewer */
tableViewerPreview = SWTUtil.createTableViewer(container, SWT.BORDER | SWT.FULL_SELECTION);
tableViewerPreview.setContentProvider(new ArrayContentProvider());
/* Actual table for {@link #tableViewerPreview} */
tablePreview = tableViewerPreview.getTable();
GridData gd_tablePreview = new GridData(SWT.FILL, SWT.FILL, true, true, 3, 1);
gd_tablePreview.heightHint = 150;
tablePreview.setLayoutData(gd_tablePreview);
tablePreview.setLinesVisible(true);
tablePreview.setVisible(false);
/* Set page to incomplete by default */
setPageComplete(false);
}
@Override
public void setVisible(boolean value){
super.setVisible(value);
try {
if (stream != null) stream.close();
} catch (Exception e){
/* Die silently*/
}
}
/**
* Evaluates the page
*
* This checks whether the current settings on the page make any sense
* and applies them appropriately. It basically checks tries to read in
* the preview data {@link #readPreview()}.
*
* If everything is fine, the settings are being put into the appropriate
* data container {@link ImportWizardModel} and the current page is marked as
* complete by invoking {@link #setPageComplete(boolean)}. Otherwise an
* error message is set, which will make sure the user is informed about
* the reason for the error.
*/
private void evaluatePage() {
setPageComplete(false);
setErrorMessage(null);
tablePreview.setVisible(false);
if (comboLocation.getText().equals("")) { //$NON-NLS-1$
return;
}
try {
readPreview();
} catch (IOException | IllegalArgumentException e) {
setErrorMessage(e.getMessage());
return;
}
/* Put data into container */
ImportWizardModel data = wizardImport.getData();
data.setWizardColumns(wizardColumns);
data.setPreviewData(previewData);
data.setFirstRowContainsHeader(btnContainsHeader.getSelection());
data.setFileLocation(comboLocation.getText());
data.setExcelSheetIndex(comboSheet.getSelectionIndex());
/* Mark page as completed */
setPageComplete(true);
}
/**
* Reads in preview data
*
* This goes through up to {@link ImportWizardModel#PREVIEW_MAX_LINES} lines
* within the appropriate file and reads them in. It uses {@link ImportAdapter} in combination with {@link ImportConfigurationExcel} to actually read in the data.
*
* @throws IOException
*/
private void readPreview() throws IOException {
/* Reset preview data */
previewData.clear();
/* Parameters from the user interface */
final String location = comboLocation.getText();
final int sheetIndex = comboSheet.getSelectionIndex();
final boolean containsHeader = btnContainsHeader.getSelection();
/* Variables needed for processing */
Sheet sheet = workbook.getSheetAt(sheetIndex);
Iterator<Row> rowIterator = sheet.iterator();
ImportConfigurationExcel config = new ImportConfigurationExcel(location, sheetIndex, containsHeader);
wizardColumns = new ArrayList<ImportWizardModelColumn>();
/* Check whether there is at least one row in sheet and retrieve it */
if (!rowIterator.hasNext()) {
throw new IOException(Resources.getMessage("ImportWizardPageExcel.10")); //$NON-NLS-1$
}
/* Get first row */
Row firstRow = rowIterator.next();
/* Check whether there is at least one column in row */
if (firstRow.getPhysicalNumberOfCells() < 1) {
throw new IOException(Resources.getMessage("ImportWizardPageExcel.11")); //$NON-NLS-1$
}
/* Iterate over columns and add them */
for (int i = 0; i < firstRow.getPhysicalNumberOfCells(); i++) {
ImportColumn column = new ImportColumnExcel(i, DataType.STRING);
ImportWizardModelColumn wizardColumn = new ImportWizardModelColumn(column);
wizardColumns.add(wizardColumn);
config.addColumn(column);
}
/* Create adapter to import data with given configuration */
ImportAdapter importAdapter = ImportAdapter.create(config);
/* Get up to {ImportData#previewDataMaxLines} lines for previewing */
int count = 0;
while (importAdapter.hasNext() && (count <= ImportWizardModel.PREVIEW_MAX_LINES)) {
previewData.add(importAdapter.next());
count++;
}
/* Remove first entry as it always contains name of columns */
previewData.remove(0);
/* Check whether there is actual any data */
if (previewData.size() == 0) {
throw new IOException(Resources.getMessage("ImportWizardPageExcel.12")); //$NON-NLS-1$
}
/* Disable redrawing once redesign is finished */
tablePreview.setRedraw(false);
/* Remove all of the old columns */
while (tablePreview.getColumnCount() > 0) {
tablePreview.getColumns()[0].dispose();
}
/* Add new columns */
for (ImportWizardModelColumn column : wizardColumns) {
TableViewerColumn tableViewerColumn = new TableViewerColumn(tableViewerPreview, SWT.NONE);
tableViewerColumn.setLabelProvider(new ExcelColumnLabelProvider(((ImportColumnExcel) column.getColumn()).getIndex()));
TableColumn tableColumn = tableViewerColumn.getColumn();
tableColumn.setWidth(100);
if (btnContainsHeader.getSelection()) {
tableColumn.setText(column.getColumn().getAliasName());
tableColumn.setToolTipText(Resources.getMessage("ImportWizardPageExcel.13") + ((ImportColumnExcel) column.getColumn()).getIndex()); //$NON-NLS-1$
}
ColumnViewerToolTipSupport.enableFor(tableViewerPreview, ToolTip.NO_RECREATE);
}
/* Setup preview table */
tableViewerPreview.setInput(previewData);
tablePreview.setHeaderVisible(btnContainsHeader.getSelection());
tablePreview.setVisible(true);
tablePreview.layout();
tablePreview.setRedraw(true);
}
/**
* Reads in the available sheets from file
*
* This reads in the available sheets from the file chosen at {@link #comboLocation} and adds them as items to {@link #comboSheet}.
*
* @throws IOException
*/
private void readSheets() throws IOException {
/* Remove previous items */
comboSheet.removeAll();
/* Get workbook */
try {
try {
if (stream != null) stream.close();
} catch (Exception e){
/* Die silently*/
}
stream = new FileInputStream(comboLocation.getText());
workbook = WorkbookFactory.create(stream);
} catch (InvalidFormatException e) {
throw new IOException(Resources.getMessage("ImportWizardPageExcel.14")); //$NON-NLS-1$
} catch (IllegalArgumentException e) {
throw new IOException(Resources.getMessage("ImportWizardPageExcel.14")); //$NON-NLS-1$
} catch (Exception e) {
throw new IOException(Resources.getMessage("ImportWizardPageExcel.15")); //$NON-NLS-1$
}
/* Add all sheets to combo */
for (int i = 0; i < workbook.getNumberOfSheets(); i++) {
comboSheet.add(workbook.getSheetName(i));
}
}
/**
* Reset page
*/
private void resetPage() {
comboSheet.setVisible(false);
comboSheet.removeAll();
lblSheet.setVisible(false);
btnContainsHeader.setEnabled(true);
btnContainsHeader.setVisible(false);
tablePreview.removeAll();
tablePreview.setVisible(false);
}
}