/* * RapidMiner * * Copyright (C) 2001-2011 by Rapid-I and the contributors * * Complete list of developers available at our web site: * * http://rapid-i.com * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero 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 Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see http://www.gnu.org/licenses/. */ package com.rapidminer.operator.nio; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Dimension; import java.awt.FlowLayout; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.LinkedList; import java.util.List; import java.util.logging.Level; import javax.swing.Action; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JComboBox; import javax.swing.JComponent; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTable; import javax.swing.SwingUtilities; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import javax.swing.table.TableColumnModel; import javax.swing.table.TableModel; import com.rapidminer.datatable.DataTableExampleSetAdapter; import com.rapidminer.example.ExampleSet; import com.rapidminer.gui.tools.CellColorProviderAlternating; import com.rapidminer.gui.tools.ExtendedJTable; import com.rapidminer.gui.tools.ProgressThread; import com.rapidminer.gui.tools.ResourceAction; import com.rapidminer.gui.tools.ResourceLabel; import com.rapidminer.gui.tools.SwingTools; import com.rapidminer.gui.tools.dialogs.wizards.AbstractWizard.WizardStepDirection; import com.rapidminer.gui.tools.dialogs.wizards.WizardStep; import com.rapidminer.gui.tools.table.EditableTableHeader; import com.rapidminer.gui.tools.table.EditableTableHeaderColumn; import com.rapidminer.gui.viewer.DataTableViewerTableModel; import com.rapidminer.operator.OperatorException; import com.rapidminer.operator.nio.model.ColumnMetaData; import com.rapidminer.operator.nio.model.DataResultSet; import com.rapidminer.operator.nio.model.ParsingError; import com.rapidminer.operator.nio.model.WizardState; import com.rapidminer.parameter.ParameterTypeDateFormat; import com.rapidminer.tools.I18N; import com.rapidminer.tools.LogService; /** * This Wizard Step might be used to defined the meta data of each attribute. * * @author Sebastian Land, Simon Fischer */ public class MetaDataDeclarationWizardStep extends WizardStep { /** Publicly exposes the method {@link #configurePropertiesFromAction(Action)} public. */ private static class ReconfigurableButton extends JButton { private static final long serialVersionUID = 1L; private ReconfigurableButton(Action action) { super(action); } @Override protected void configurePropertiesFromAction(Action a) { super.configurePropertiesFromAction(a); } } private Action reloadAction = new ResourceAction("wizard.validate_value_types") { private static final long serialVersionUID = 1L; @Override public void actionPerformed(ActionEvent e) { toggleReload(); } }; private Action cancelReloadAction = new ResourceAction("wizard.abort_validate_value_types") { private static final long serialVersionUID = 1L; @Override public void actionPerformed(ActionEvent e) { toggleReload(); } }; private ReconfigurableButton reloadButton = new ReconfigurableButton(reloadAction); private Action guessValueTypes = new ResourceAction("wizard.guess_value_types") { private static final long serialVersionUID = 1L; @Override public void actionPerformed(ActionEvent e) { toggleGuessValueTypes(); } }; private Action cancelGuessValueTypes = new ResourceAction("wizard.abort_guess_value_types") { private static final long serialVersionUID = 1L; @Override public void actionPerformed(ActionEvent e) { toggleGuessValueTypes(); } }; private ReconfigurableButton guessButton = new ReconfigurableButton(guessValueTypes); private JCheckBox errorsAsMissingBox = new JCheckBox(new ResourceAction("wizard.error_tolerant") { private static final long serialVersionUID = 1L; @Override public void actionPerformed(ActionEvent e) { state.getTranslationConfiguration().setFaultTolerant(errorsAsMissingBox.isSelected()); } }); private JCheckBox filterErrorsBox = new JCheckBox(new ResourceAction("wizard.show_error_rows") { private static final long serialVersionUID = 1L; @Override public void actionPerformed(ActionEvent e) { if (filteredModel != null) { filteredModel.setFilterEnabled(filterErrorsBox.isSelected()); } } }); private JComboBox dateFormatField = new JComboBox(ParameterTypeDateFormat.PREDEFINED_DATE_FORMATS); private JCheckBox limitedPreviewBox = new JCheckBox(I18N.getMessage(I18N.getGUIBundle(), "gui.action.importwizard.limited_preview.label", ImportWizardUtils.getPreviewLength())); private WizardState state; private JPanel panel = new JPanel(new BorderLayout()); private JScrollPane tableScrollPane; private ErrorTableModel errorTableModel = new ErrorTableModel(); private RowFilteringTableModel filteredModel; private JLabel errorLabel = new JLabel(); public MetaDataDeclarationWizardStep(WizardState state) { super("importwizard.metadata"); limitedPreviewBox.setSelected(true); this.state = state; dateFormatField.setEditable(true); dateFormatField.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { MetaDataDeclarationWizardStep.this.state.getTranslationConfiguration().setDatePattern((String) dateFormatField.getSelectedItem()); } }); JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); buttonPanel.add(reloadButton); buttonPanel.add(guessButton); buttonPanel.add(limitedPreviewBox); JLabel label = new ResourceLabel("date_format"); label.setLabelFor(dateFormatField); buttonPanel.add(label); buttonPanel.add(dateFormatField); panel.add(buttonPanel, BorderLayout.NORTH); JPanel errorPanel = new JPanel(new GridBagLayout()); GridBagConstraints c = new GridBagConstraints(); c.fill = GridBagConstraints.BOTH; c.anchor = GridBagConstraints.FIRST_LINE_START; c.ipadx = c.ipady = 4; c.weighty = 0; c.weightx = 1; c.gridwidth = 1; c.weightx = 1; errorPanel.add(errorLabel, c); c.weightx = 0; c.gridwidth = GridBagConstraints.RELATIVE; errorPanel.add(errorsAsMissingBox, c); c.weightx = 0; c.gridwidth = GridBagConstraints.REMAINDER; errorPanel.add(filterErrorsBox, c); final JTable errorTable = new JTable(errorTableModel); errorTable.getSelectionModel().addListSelectionListener(new ListSelectionListener() { @Override public void valueChanged(ListSelectionEvent e) { if (!e.getValueIsAdjusting()) { final int selected = errorTable.getSelectedRow(); if (selected >= 0) { ParsingError error = errorTableModel.getErrorInRow(selected); int row = error.getExampleIndex(); row = filteredModel.inverseTranslateRow(row); if (row == -1) { return; } int col = error.getColumn(); previewTable.setRowSelectionInterval(row, row); previewTable.setColumnSelectionInterval(col, col); } } } }); final JScrollPane errorScrollPane = new JScrollPane(errorTable); errorScrollPane.setPreferredSize(new Dimension(500, 80)); c.weighty = 1; c.gridwidth = GridBagConstraints.REMAINDER; errorPanel.add(errorScrollPane, c); panel.add(errorPanel, BorderLayout.SOUTH); final JLabel dummy = new JLabel("-"); dummy.setPreferredSize(new Dimension(500, 500)); dummy.setMinimumSize(new Dimension(500, 500)); tableScrollPane = new JScrollPane(dummy, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS); panel.add(tableScrollPane, BorderLayout.CENTER); } @Override protected boolean performEnteringAction(WizardStepDirection direction) { dateFormatField.setSelectedItem(state.getTranslationConfiguration().getDatePattern()); errorsAsMissingBox.setSelected(state.getTranslationConfiguration().isFaultTolerant()); new ProgressThread("loading_data") { @Override public void run() { try { final DataResultSet previewResultSet = state.getDataResultSetFactory().makeDataResultSet(state.getOperator()); state.getTranslationConfiguration().reconfigure(previewResultSet); } catch (OperatorException e1) { ImportWizardUtils.showErrorMessage(state.getDataResultSetFactory().getResourceName(), e1.toString(), e1); return; } try { TableModel dataPreview = state.getDataResultSetFactory().makePreviewTableModel(getProgressListener()); // Copy name annotations to name int nameIndex = state.getTranslationConfiguration().getNameRow(); if (nameIndex != -1) { for (int i = 0; i < dataPreview.getColumnCount(); i++) { ColumnMetaData columnMetaData = state.getTranslationConfiguration().getColumnMetaData(i); final String foundName = (String) dataPreview.getValueAt(nameIndex, i); if ((foundName != null) && !foundName.isEmpty()) { columnMetaData.setUserDefinedAttributeName(foundName); } } } } catch (Exception e) { ImportWizardUtils.showErrorMessage(state.getDataResultSetFactory().getResourceName(), e.toString(), e); return; } guessValueTypes(); } }.start(); return true; } private void updateErrors() { final int size = state.getTranslator().getErrors().size(); errorLabel.setText(size + " errors."); if (size == 0) { errorLabel.setIcon(SwingTools.createIcon("16/ok.png")); } else { errorLabel.setIcon(SwingTools.createIcon("16/error.png")); } errorTableModel.setErrors(state.getTranslator().getErrors()); } private void updateTableModel(ExampleSet exampleSet) { if (previewTable == null) { previewTable = new ExtendedJTable(false, false, false); } // data model DataTableViewerTableModel model = new DataTableViewerTableModel(new DataTableExampleSetAdapter(exampleSet, null)); List<Integer> rowsList = new LinkedList<Integer>(); int lastHit = -1; for (ParsingError error : state.getTranslator().getErrors()) { if (error.getExampleIndex() != lastHit) { rowsList.add(error.getExampleIndex()); lastHit = error.getExampleIndex(); } } int[] rowMap = new int[rowsList.size()]; int j = 0; for (Integer row : rowsList) { rowMap[j++] = row; } filteredModel = new RowFilteringTableModel(model, rowMap, filterErrorsBox.isSelected()); previewTable.setModel(filteredModel); // Header model TableColumnModel columnModel = previewTable.getColumnModel(); previewTable.setTableHeader(new EditableTableHeader(columnModel)); // header editors and renderers and values MetaDataTableHeaderCellEditor headerEditor = new MetaDataTableHeaderCellEditor(); MetaDataTableHeaderCellEditor headerRenderer = new MetaDataTableHeaderCellEditor(); for (int i = 0; i < previewTable.getColumnCount(); i++) { EditableTableHeaderColumn col = (EditableTableHeaderColumn) previewTable.getColumnModel().getColumn(i); col.setHeaderValue(state.getTranslationConfiguration().getColumnMetaData()[i]); col.setHeaderRenderer(headerRenderer); col.setHeaderEditor(headerEditor); } previewTable.getTableHeader().setReorderingAllowed(false); previewTable.setCellColorProvider(new CellColorProviderAlternating() { @Override public Color getCellColor(int row, int column) { row = filteredModel.translateRow(row); ParsingError error = state.getTranslator().getErrorByExampleIndexAndColumn(row, column); if (error != null) { return SwingTools.DARK_YELLOW; } else { return super.getCellColor(row, column); } } }); tableScrollPane.setViewportView(previewTable); } @Override protected boolean performLeavingAction(WizardStepDirection direction) { if (direction == WizardStepDirection.FINISH) { try { if (state.getTranslator() != null) { state.getTranslator().close(); } } catch (OperatorException e) { ImportWizardUtils.showErrorMessage(state.getDataResultSetFactory().getResourceName(), e.toString(), e); } } return true; } @Override protected boolean canGoBack() { return true; } @Override protected boolean canProceed() { return true; } @Override protected JComponent getComponent() { return panel; } private void reload() { reloadButton.configurePropertiesFromAction(cancelReloadAction); new ProgressThread("loading_data") { @Override public void run() { DataResultSet resultSet = null; try { if (state.getTranslator() != null) { state.getTranslator().close(); } resultSet = state.getDataResultSetFactory().makeDataResultSet(null); state.getTranslator().clearErrors(); final ExampleSet exampleSet = state.readNow(resultSet, limitedPreviewBox.isSelected(), getProgressListener()); SwingUtilities.invokeLater(new Runnable() { @Override public void run() { updateTableModel(exampleSet); updateErrors(); } }); } catch (OperatorException e) { ImportWizardUtils.showErrorMessage(state.getDataResultSetFactory().getResourceName(), e.toString(), e); } finally { if (resultSet != null) { try { resultSet.close(); } catch (OperatorException e) { LogService.getRoot().log(Level.WARNING, "Failed to close result set: " + e, e); } } getProgressListener().complete(); SwingUtilities.invokeLater(new Runnable() { @Override public void run() { reloadButton.configurePropertiesFromAction(reloadAction); reloadButton.setEnabled(true); isReloading = false; } }); } } }.start(); } private void cancelReload() { state.getTranslator().cancelLoading(); reloadButton.setEnabled(false); } private void guessValueTypes() { guessButton.configurePropertiesFromAction(cancelGuessValueTypes); isGuessing = true; new ProgressThread("guessing_value_types") { @Override public void run() { Thread.yield(); DataResultSet resultSet = null; try { if (state.getTranslator() != null) { state.getTranslator().close(); } resultSet = state.getDataResultSetFactory().makeDataResultSet(null); state.getTranslator().clearErrors(); state.getTranslationConfiguration().resetValueTypes(); state.getTranslator().guessValueTypes(state.getTranslationConfiguration(), resultSet, state.getNumberOfPreviewRows(), getProgressListener()); if (!state.getTranslator().isGuessingCancelled()) { final ExampleSet exampleSet = state.readNow(resultSet, limitedPreviewBox.isSelected(), getProgressListener()); SwingUtilities.invokeLater(new Runnable() { @Override public void run() { updateTableModel(exampleSet); updateErrors(); } }); } } catch (OperatorException e) { ImportWizardUtils.showErrorMessage(state.getDataResultSetFactory().getResourceName(), e.toString(), e); } finally { if (resultSet != null) { try { resultSet.close(); } catch (OperatorException e) { LogService.getRoot().log(Level.WARNING, "Failed to close result set: " + e, e); } } getProgressListener().complete(); SwingUtilities.invokeLater(new Runnable() { @Override public void run() { guessButton.configurePropertiesFromAction(guessValueTypes); guessButton.setEnabled(true); isGuessing = false; } }); } } }.start(); } private boolean isGuessing = false; private boolean isReloading = false; private ExtendedJTable previewTable; private void cancelGuessing() { state.getTranslator().cancelGuessing(); state.getTranslator().cancelLoading(); guessButton.setEnabled(false); } private void toggleGuessValueTypes() { isGuessing = !isGuessing; if (isGuessing) { guessValueTypes(); } else { cancelGuessing(); } } private void toggleReload() { isReloading = !isReloading; if (isReloading) { reload(); } else { cancelReload(); } } }