/**
* 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;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.filechooser.FileFilter;
import com.rapidminer.core.io.data.source.FileDataSource;
import com.rapidminer.core.io.data.source.FileDataSourceFactory;
import com.rapidminer.core.io.gui.ImportWizard;
import com.rapidminer.core.io.gui.InvalidConfigurationException;
import com.rapidminer.core.io.gui.WizardDirection;
import com.rapidminer.core.io.gui.WizardStep;
import com.rapidminer.gui.tools.SwingTools;
import com.rapidminer.studio.io.gui.internal.DataImportWizardUtils;
import com.rapidminer.studio.io.gui.internal.DataWizardEventType;
import com.rapidminer.studio.io.gui.internal.steps.AbstractWizardStep;
import com.rapidminer.tools.I18N;
import com.rapidminer.tools.container.Pair;
/**
* A step that allows to select a local file location for data import with a {@link FileDataSource}.
* The file type is detected automatically and the responsible {@link FileDataSource} is selected.
*
* @author Nils Woehler
* @since 7.0.0
*
*/
final class LocalFileLocationWizardStep extends AbstractWizardStep {
/**
* Listens for changes in the view to update the current selected path.
*/
private final ChangeListener changeListener = new ChangeListener() {
@Override
public void stateChanged(ChangeEvent e) {
fireStateChanged();
}
};
private transient LocalFileLocationChooserView view;
private final List<FileFilter> fileFilters;
private final ImportWizard wizard;
/**
* Creates a new {@link LocalFileLocationWizardStep} instance.
*
* @param allFileEndingsAndDescriptions
* all list of all registered file endings with descriptions
*/
public LocalFileLocationWizardStep(List<Pair<String, Set<String>>> allFileEndingsAndDescriptions, ImportWizard wizard) {
this.wizard = wizard;
this.fileFilters = new LinkedList<>();
for (final Pair<String, Set<String>> item : allFileEndingsAndDescriptions) {
if (item.getSecond().isEmpty()) {
// If there are no file endings associated with this item, skip it (it is covered by
// the 'All Files' entry).
continue;
}
fileFilters.add(new FileFilter() {
@Override
public String getDescription() {
StringBuilder builder = new StringBuilder();
builder.append(I18N.getGUIMessage("gui.io.dataimport.source." + item.getFirst() + ".label"));
builder.append(" (");
boolean first = true;
for (String fileEnding : item.getSecond()) {
if (first) {
first = false;
} else {
builder.append(", ");
}
builder.append(".");
builder.append(fileEnding);
}
builder.append(")");
return builder.toString();
}
@Override
public boolean accept(File f) {
boolean accept = f.isDirectory();
for (String fileEnding : item.getSecond()) {
accept |= f.getName().endsWith(fileEnding);
}
return accept;
}
});
}
this.view = new LocalFileLocationChooserView(fileFilters);
this.view.registerChangeListener(changeListener);
}
@Override
public String getI18NKey() {
return null;
}
@Override
public LocalFileLocationChooserView getView() {
return view;
}
@Override
public void viewWillBecomeVisible(WizardDirection direction) throws InvalidConfigurationException {
wizard.setProgress(20);
LocalFileDataSource dataSource = wizard.getDataSource(LocalFileDataSource.class);
Path location = dataSource.getLocation();
if (location != null) {
getView().setSelectedFile(location);
}
// in case a factory is available...
FileDataSourceFactory<?> factory = dataSource.getFileDataSourceFactory();
if (factory != null) {
// ...use the provided data source file factory
getView().setFileDataSourceFactory(factory);
} else {
// otherwise trigger a new file type lookup
changeListener.stateChanged(null);
}
}
@Override
public void viewWillBecomeInvisible(WizardDirection direction) throws InvalidConfigurationException {
FileDataSourceFactory<?> fileDataSourceFactory = getView().getFileDataSourceFactory();
Path selectedLocation = getView().getSelectedLocation();
LocalFileDataSource dataSource = wizard.getDataSource(LocalFileDataSource.class);
boolean locationChanged = dataSource.getLocation() == null && selectedLocation != null
|| dataSource.getLocation() != null && !dataSource.getLocation().equals(selectedLocation);
FileDataSourceFactory<?> dsFileFactory = dataSource.getFileDataSourceFactory();
boolean fileTypeChanged = dsFileFactory == null && fileDataSourceFactory != null || dsFileFactory != null
&& !dsFileFactory.getDataSourceClass().equals(fileDataSourceFactory.getDataSourceClass());
// update the data source location
dataSource.setLocation(selectedLocation);
// update the data source factory in case the location or file type has changed
if (fileDataSourceFactory != null && (locationChanged || fileTypeChanged)) {
updateFileDataSource(dataSource, fileDataSourceFactory, wizard, selectedLocation);
}
// if going on with the selected file store the last directory
if (direction == WizardDirection.NEXT && selectedLocation != null) {
SwingTools.storeLastDirectory(selectedLocation);
}
}
@Override
public void validate() throws InvalidConfigurationException {
Path selectedLocation = getView().getSelectedLocation();
if (selectedLocation == null) {
throw new InvalidConfigurationException();
}
if (!Files.exists(selectedLocation)) {
throw new InvalidConfigurationException();
}
if (getView().getFileDataSourceFactory() == null) {
throw new InvalidConfigurationException();
}
}
@Override
public String getNextStepID() {
FileDataSourceFactory<?> fileDataSourceFactory;
try {
fileDataSourceFactory = wizard.getDataSource(LocalFileDataSource.class).getFileDataSourceFactory();
} catch (InvalidConfigurationException e) {
// cannot happen, printing a stacktrace anyway
e.printStackTrace();
return null;
}
if (fileDataSourceFactory == null) {
// return random string in case no file has been chosen yet. We cannot return null here
// as this would indicate the end of the import wizard.
return UUID.randomUUID().toString();
}
return fileDataSourceFactory.getFirstStepId();
}
private <F extends FileDataSource> void updateFileDataSource(LocalFileDataSource dataSource,
FileDataSourceFactory<F> factory, ImportWizard wizard, Path selectedLocation) {
// log selection
DataImportWizardUtils.logStats(DataWizardEventType.FILE_DATASOURCE_SELECTED, factory.getI18NKey());
// set file data source factory
dataSource.setFileDataSourceFactory(factory);
// create new file data source instance
F fileDataSource = factory.createNew(selectedLocation);
dataSource.setFileDataSource(fileDataSource);
// add custom steps to the wizard
for (WizardStep customStep : getCustomSteps(fileDataSource, factory, wizard)) {
wizard.addStep(customStep);
}
}
private static <F extends FileDataSource> List<WizardStep> getCustomSteps(F dataSource, FileDataSourceFactory<F> factory,
ImportWizard wizard) {
return factory.createCustomSteps(wizard, dataSource);
}
}