/**
* Copyright (C) 2001-2017 by RapidMiner and the contributors
*
* Complete list of developers available at our web site:
*
* http://rapidminer.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.studio.io.data.internal.file.excel;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import javax.swing.JTable;
import javax.swing.table.TableModel;
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import com.rapidminer.gui.tools.ProgressThread;
import com.rapidminer.operator.OperatorException;
import com.rapidminer.operator.nio.model.ParseException;
import com.rapidminer.operator.nio.model.xlsx.XlsxResultSet.XlsxReadMode;
import com.rapidminer.operator.nio.model.xlsx.XlsxSheetMetaDataParser;
import com.rapidminer.operator.nio.model.xlsx.XlsxSheetTableModel;
import com.rapidminer.operator.nio.model.xlsx.XlsxUtilities.XlsxCellCoordinates;
import com.rapidminer.studio.io.data.internal.ResultSetAdapter;
import jxl.read.biff.BiffException;
/**
* Model for the {@link ExcelSheetSelectionPanel} which stores the selected sheet index, the header
* row index and the cell range selection.
*
* @author Nils Woehler
* @since 7.0.0
*/
class ExcelSheetSelectionPanelModel {
private static final String LOAD_WORKBOOK_PG_ID = "load_workbook";
private final Map<Integer, TableModel> tableModelCache = new HashMap<>();
private final ExcelSheetSelectionModelListener listener;
private final ExcelDataSource dataSource;
private boolean isShowingPreview = false;
private CellRangeSelection cellRangeSelection = null;
private int headerRowIndex = 0;
private int sheetIndex = 0;
/**
* Constructs a new {@link ExcelSheetSelectionPanelModel} instance.
*
* @param ds
* the {@link ExcelDataSource}
* @param listener
* the listener which is informed about model changes
*/
ExcelSheetSelectionPanelModel(ExcelDataSource ds, ExcelSheetSelectionModelListener listener) {
this.dataSource = ds;
this.listener = listener;
}
/**
* Uses the current table selection to update the cell range selection.
*/
void updateCellRangeByTableSelection(JTable contentTable) {
int columnIndexStart = contentTable.getSelectedColumn();
int rowIndexStart = contentTable.getSelectedRow();
int columnIndexEnd = columnIndexStart + contentTable.getSelectedColumnCount() - 1;
int rowIndexEnd = rowIndexStart + contentTable.getSelectedRowCount() - 1;
setCellRangeSelection(new CellRangeSelection(columnIndexStart, rowIndexStart, columnIndexEnd, rowIndexEnd));
}
/**
* Updates the cell range selection
*
* @param newSelection
* the new selection
*/
void setCellRangeSelection(CellRangeSelection newSelection) {
if (newSelection != null) {
this.cellRangeSelection = new CellRangeSelection(newSelection);
} else {
this.cellRangeSelection = null;
}
listener.cellRangeSelectionUpdate(newSelection);
}
/**
* @return a copy of the current cell range selection or {@code null} in case the selection is
* invalid
*/
CellRangeSelection getCellRangeSelection() {
if (cellRangeSelection == null) {
return null;
} else {
return new CellRangeSelection(cellRangeSelection);
}
}
/**
* @param headerRowSpinner
* the new header row index
*/
void setHeaderRowIndex(int headerRowIndex) {
this.headerRowIndex = headerRowIndex;
listener.headerRowIndexUpdated(headerRowIndex);
}
/**
* @return the 0-based index of the header row or {@link ResultSetAdapter#NO_HEADER_ROW} in case
* no header row is defined
*/
int getHeaderRowIndex() {
return headerRowIndex;
}
/**
* @return the selected sheet index
*/
int getSheetIndex() {
return sheetIndex;
}
/**
* Updates the selected sheet index and loads a new table model. As a side-effect both the cell
* range selection and the header row index are reseted.
*
* @param newSheetIndex
* the new sheet index
*/
void setSheetIndex(final int newSheetIndex) {
int oldSheetIndex = this.sheetIndex;
if (oldSheetIndex != newSheetIndex) {
this.sheetIndex = newSheetIndex;
updateModel(newSheetIndex, 0, new CellRangeSelection(0, XlsxCellCoordinates.NO_ROW_NUMBER, Integer.MAX_VALUE,
XlsxSheetMetaDataParser.MAXIMUM_XLSX_ROW_INDEX));
}
}
/**
* Updates the cell selection model with the provided new sheetIndex, headerRowIndex, and
* {@link CellRangeSelection}.
*
* @param sheetIndex
* the new sheetIndex
* @param headerRowIndex
* the new headerRowIndex
* @param selection
* the new cell range selection
*/
void updateModel(final int sheetIndex, final int headerRowIndex, final CellRangeSelection selection) {
ProgressThread loadWorkbook = new ProgressThread(LOAD_WORKBOOK_PG_ID, false) {
@Override
public void run() {
// load new table model or use cached model
if (loadTableModel(sheetIndex)) {
// reset header row and cell range selection
setHeaderRowIndex(headerRowIndex);
setCellRangeSelection(new CellRangeSelection(selection));
}
}
/**
* Loads the table model for the provided sheet. In case the model has been loaded
* before, a cached version is returned.
*
* @param sheetIndex
* the index of the sheet
* @return {@code true} in case the loading was successful, {@code false} otherwise
*/
private boolean loadTableModel(int sheetIndex) {
// initializing progress
getProgressListener().setTotal(130);
getProgressListener().setCompleted(0);
try {
// loading workbook if necessary
final int numberOfSheets = dataSource.getResultSetConfiguration().getNumberOfSheets();
final String[] sheetNames = dataSource.getResultSetConfiguration().getSheetNames();
if (sheetIndex >= numberOfSheets) {
sheetIndex = 0;
}
final int selectedSheet = sheetIndex;
// check whether a table model is already cached and use it
boolean modelLoaded = false;
TableModel model = tableModelCache.get(Integer.valueOf(selectedSheet));
if (model == null) {
listener.loadingNewTableModel();
model = dataSource.getResultSetConfiguration().createExcelTableModel(selectedSheet,
XlsxReadMode.WIZARD_SHEET_SELECTION, getProgressListener());
tableModelCache.put(Integer.valueOf(selectedSheet), model);
modelLoaded = true;
getProgressListener().setCompleted(110);
}
// check whether only a data preview is shown because the file content is
// too large (only XLSX files can create a preview)
if (model instanceof XlsxSheetTableModel) {
isShowingPreview = ((XlsxSheetTableModel) model).isPreview();
} else {
isShowingPreview = false;
}
final TableModel selectedModel = model;
final boolean wasModelLoaded = modelLoaded;
getProgressListener().setCompleted(130);
// inform about model change
fireSheetIndexUpdated(sheetIndex, sheetNames, selectedModel, wasModelLoaded);
return true;
} catch (BiffException | IOException | OperatorException | InvalidFormatException | ParseException e) {
listener.reportErrorLoadingTableModel(e);
return false;
} finally {
getProgressListener().complete();
}
}
private void fireSheetIndexUpdated(int newSheetIndex, String[] sheetNames, TableModel newTableModel,
boolean wasModelLoaded) {
listener.sheetIndexUpdated(newSheetIndex, sheetNames, newTableModel, isShowingPreview(), wasModelLoaded);
}
};
loadWorkbook.addDependency(LOAD_WORKBOOK_PG_ID);
loadWorkbook.start();
}
/**
* @return whether the current table model is only showing a preview instead of the whole data
*/
boolean isShowingPreview() {
return isShowingPreview;
}
/**
* Clears the table model cache.
*/
void clearTableModelCache() {
tableModelCache.clear();
}
}