
org.datacleaner.windows.FixedWidthDatastoreDialog Maven / Gradle / Ivy
/**
* DataCleaner (community edition)
* Copyright (C) 2014 Neopost - Customer Information Management
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.datacleaner.windows;
import java.awt.BorderLayout;
import java.awt.FlowLayout;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Map.Entry;
import java.util.Set;
import javax.inject.Inject;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComponent;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.filechooser.FileFilter;
import org.apache.metamodel.fixedwidth.FixedWidthConfiguration;
import org.apache.metamodel.util.Resource;
import org.datacleaner.bootstrap.WindowContext;
import org.datacleaner.configuration.DataCleanerConfiguration;
import org.datacleaner.connection.FixedWidthDatastore;
import org.datacleaner.guice.Nullable;
import org.datacleaner.panels.DCPanel;
import org.datacleaner.user.MutableDatastoreCatalog;
import org.datacleaner.user.UserPreferences;
import org.datacleaner.util.DCDocumentListener;
import org.datacleaner.util.FileFilters;
import org.datacleaner.util.IconUtils;
import org.datacleaner.util.ImmutableEntry;
import org.datacleaner.util.NumberDocument;
import org.datacleaner.util.StringUtils;
import org.datacleaner.util.WidgetFactory;
import org.datacleaner.util.WidgetUtils;
import org.datacleaner.widgets.CharSetEncodingComboBox;
import org.datacleaner.widgets.CustomColumnNamesWidget;
import org.datacleaner.widgets.DCLabel;
import org.datacleaner.widgets.HeaderLineComboBox;
import org.datacleaner.widgets.ResourceSelector;
import org.datacleaner.widgets.ResourceTypePresenter;
import org.jdesktop.swingx.JXTextField;
public final class FixedWidthDatastoreDialog extends AbstractResourceBasedDatastoreDialog {
private static final long serialVersionUID = 1L;
private final CharSetEncodingComboBox _encodingComboBox;
private final JCheckBox _failOnInconsistenciesCheckBox;
private final JCheckBox _skipEbcdicHeaderCheckBox;
private final JCheckBox _eolPresentCheckBox;
private final List _valueWidthTextFields;
private final DCPanel _valueWidthsPanel;
private final DCLabel _lineWidthLabel;
private final HeaderLineComboBox _headerLineComboBox;
private final JButton _addValueWidthButton;
private final JButton _removeValueWidthButton;
private final DocumentListener _updatePreviewTableDocumentListener;
private final CustomColumnNamesWidget _columnNamesWidget;
private volatile boolean showPreview = true;
private Set _columnNameFields = new HashSet<>();
@Inject
protected FixedWidthDatastoreDialog(@Nullable final FixedWidthDatastore originalDatastore,
final MutableDatastoreCatalog mutableDatastoreCatalog, final WindowContext windowContext,
final DataCleanerConfiguration configuration, final UserPreferences userPreferences) {
super(originalDatastore, mutableDatastoreCatalog, windowContext, configuration, userPreferences);
_updatePreviewTableDocumentListener = new DCDocumentListener() {
@Override
protected void onChange(final DocumentEvent event) {
onSettingsUpdated(false);
}
};
_lineWidthLabel = DCLabel.bright("");
_valueWidthsPanel = new DCPanel();
_valueWidthsPanel.setLayout(new FlowLayout(FlowLayout.LEFT, 2, 0));
_valueWidthTextFields = new ArrayList<>();
_encodingComboBox = new CharSetEncodingComboBox();
_addValueWidthButton = WidgetFactory.createSmallButton(IconUtils.ACTION_ADD_DARK);
_removeValueWidthButton = WidgetFactory.createSmallButton(IconUtils.ACTION_REMOVE_DARK);
_headerLineComboBox = new HeaderLineComboBox();
_failOnInconsistenciesCheckBox = createCheckBox("Fail on inconsistent line length", true);
_skipEbcdicHeaderCheckBox = createCheckBox("Input file contains a header that should be skipped", false);
_eolPresentCheckBox = createCheckBox("Input file contains new line characters", true);
if (originalDatastore != null) {
_columnNamesWidget = new CustomColumnNamesWidget(originalDatastore.getCustomColumnNames());
_encodingComboBox.setSelectedItem(originalDatastore.getEncoding());
_failOnInconsistenciesCheckBox.setSelected(originalDatastore.isFailOnInconsistencies());
_skipEbcdicHeaderCheckBox.setSelected(originalDatastore.isSkipEbcdicHeader());
_eolPresentCheckBox.setSelected(originalDatastore.isEolPresent());
final int[] valueWidths = originalDatastore.getValueWidths();
for (final int valueWidth : valueWidths) {
addValueWidthTextField(valueWidth);
}
_headerLineComboBox.setSelectedIndex(originalDatastore.getHeaderLineNumber());
onSettingsUpdated(false);
} else {
_columnNamesWidget = new CustomColumnNamesWidget(null);
addValueWidthTextField();
addValueWidthTextField();
addValueWidthTextField();
}
_addValueWidthButton.addActionListener(e -> addValueWidthTextField());
_removeValueWidthButton.addActionListener(e -> removeValueWidthTextField());
_encodingComboBox.addListener(item -> onSettingsUpdated(false));
_headerLineComboBox.addListener(item -> onSettingsUpdated(false));
_columnNamesWidget.getButtons().forEach(button -> button.addActionListener(action -> {
onSettingsUpdated(false);
SwingUtilities.invokeLater(this::registerColumnNameFields);
}));
registerColumnNameFields();
}
private JCheckBox createCheckBox(final String label, final boolean selected) {
final JCheckBox checkBox = new JCheckBox(label, selected);
checkBox.setOpaque(false);
checkBox.setForeground(WidgetUtils.BG_COLOR_BRIGHTEST);
checkBox.addItemListener(item -> onSettingsUpdated(false));
return checkBox;
}
@Override
protected boolean validateForm() {
final Object selectedEncoding = _encodingComboBox.getSelectedItem();
if (selectedEncoding == null || selectedEncoding.toString().length() == 0) {
setStatusError("Please select a character encoding!");
return false;
}
return super.validateForm();
}
@Override
protected void onSelected(final Resource resource) {
onSettingsUpdated(true);
}
private void onSettingsUpdated(final boolean autoDetectEncoding) {
if (!validateForm()) {
return;
}
final byte[] sampleBuffer = getSampleBuffer();
if (sampleBuffer == null || sampleBuffer.length == 0) {
logger.debug("No bytes read to autodetect settings");
return;
}
final String charSet;
if (autoDetectEncoding) {
charSet = _encodingComboBox.autoDetectEncoding(sampleBuffer);
} else {
charSet = _encodingComboBox.getSelectedItem();
}
final char[] sampleChars = readSampleBuffer(sampleBuffer, charSet);
final int lineLength = StringUtils.indexOf('\n', sampleChars);
if (_eolPresentCheckBox.isSelected() && lineLength == -1) {
setStatusWarning("No newline in first " + sampleChars.length + " chars");
// don't show the preview if no newlines were found (it may try to treat the whole file as a single row)
showPreview = false;
} else {
final int[] valueWidths = getValueWidths(false);
int totalMappedWidth = 0;
for (final int valueWidth : valueWidths) {
totalMappedWidth += valueWidth;
}
_lineWidthLabel.setText(lineLength + " chars in first line. " + totalMappedWidth + " mapped.");
_lineWidthLabel.updateUI();
showPreview = true;
validateAndUpdate();
}
}
@Override
protected byte[] getSampleBuffer() {
final Resource resource = getResource();
final int bufferSize = getBufferSize();
byte[] bytes = new byte[bufferSize];
try (InputStream fileInputStream = resource.read()) {
final int startPosition = getStartPosition();
fileInputStream.skip(startPosition);
final int bytesRead = fileInputStream.read(bytes, 0, bufferSize);
if (bytesRead != -1 && bytesRead <= bufferSize) {
bytes = Arrays.copyOf(bytes, bytesRead);
}
return bytes;
} catch (final IOException e) {
logger.error("IOException occurred while reading sample buffer", e);
return new byte[0];
}
}
private int getStartPosition() {
return _skipEbcdicHeaderCheckBox.isSelected() ? getRecordDataLength() : 0;
}
private int getBufferSize() {
return _eolPresentCheckBox.isSelected() ? SAMPLE_BUFFER_SIZE : getRecordDataLength();
}
private int getRecordDataLength() {
int length = 0;
if (_valueWidthTextFields != null && _valueWidthTextFields.size() > 0) {
for (final JXTextField textField : _valueWidthTextFields) {
try {
final int columnWidth = Integer.parseInt(textField.getText());
length += columnWidth;
} catch (final NumberFormatException e) {
throw new IllegalStateException("Value width must be a valid number.");
}
}
}
return length;
}
@Override
protected FixedWidthDatastore getPreviewDatastore(final Resource resource) {
return createDatastore("Preview", resource, false, _skipEbcdicHeaderCheckBox.isSelected(),
_eolPresentCheckBox.isSelected());
}
@Override
protected boolean isPreviewDataAvailable() {
return showPreview;
}
@Override
protected boolean isPreviewTableEnabled() {
return true;
}
private JXTextField addValueWidthTextField() {
return addValueWidthTextField(8);
}
private JXTextField addValueWidthTextField(final int valueWidth) {
final JXTextField textField = WidgetFactory.createTextField();
textField.setColumns(2);
final NumberDocument document = new NumberDocument();
document.addDocumentListener(_updatePreviewTableDocumentListener);
textField.setDocument(document);
textField.setText(valueWidth + "");
_valueWidthTextFields.add(textField);
_valueWidthsPanel.add(textField);
if (_valueWidthTextFields.size() > 1) {
_removeValueWidthButton.setEnabled(true);
}
_valueWidthsPanel.updateUI();
onSettingsUpdated(false);
return textField;
}
private JXTextField removeValueWidthTextField() {
if (_valueWidthTextFields.isEmpty()) {
return null;
}
final JXTextField textField = _valueWidthTextFields.get(_valueWidthTextFields.size() - 1);
_valueWidthTextFields.remove(textField);
_valueWidthsPanel.remove(textField);
if (_valueWidthTextFields.size() == 1) {
_removeValueWidthButton.setEnabled(false);
} else {
_removeValueWidthButton.setEnabled(true);
}
_valueWidthsPanel.updateUI();
onSettingsUpdated(false);
return textField;
}
@Override
protected boolean isWindowResizable() {
return true;
}
@Override
protected List> getFormElements() {
final DCPanel buttonPanel = new DCPanel();
buttonPanel.setLayout(new FlowLayout(FlowLayout.LEFT, 2, 0));
buttonPanel.add(_addValueWidthButton);
buttonPanel.add(_removeValueWidthButton);
final DCPanel valueWidthConfigurationPanel = new DCPanel();
valueWidthConfigurationPanel.setLayout(new BorderLayout());
valueWidthConfigurationPanel.add(_valueWidthsPanel, BorderLayout.CENTER);
valueWidthConfigurationPanel.add(buttonPanel, BorderLayout.EAST);
valueWidthConfigurationPanel.add(_lineWidthLabel, BorderLayout.SOUTH);
final List> result = super.getFormElements();
result.add(new ImmutableEntry<>("Character encoding", _encodingComboBox));
result.add(new ImmutableEntry<>("Column widths", valueWidthConfigurationPanel));
result.add(new ImmutableEntry<>("Header line", _headerLineComboBox));
result.add(new ImmutableEntry<>("", _failOnInconsistenciesCheckBox));
result.add(new ImmutableEntry<>("", _skipEbcdicHeaderCheckBox));
result.add(new ImmutableEntry<>("", _eolPresentCheckBox));
// TODO: Uncomment the line about columns names panel after the release of metamodel 4.5.5
//result.add(new ImmutableEntry<>("Column Names", _columnNamesWidget.getPanel()));
return result;
}
@Override
protected String getBannerTitle() {
return "Fixed width file";
}
@Override
public String getWindowTitle() {
return "Fixed width file | Datastore";
}
@Override
protected FixedWidthDatastore createDatastore(final String name, final Resource resource) {
final boolean failOnInconsistencies = _failOnInconsistenciesCheckBox.isSelected();
final boolean skipEbcdicHeader = _skipEbcdicHeaderCheckBox.isSelected();
final boolean eolPresent = _eolPresentCheckBox.isSelected();
return createDatastore(name, resource, failOnInconsistencies, skipEbcdicHeader, eolPresent);
}
private FixedWidthDatastore createDatastore(final String name, final Resource resource,
final boolean failOnInconsistencies, final boolean skipEbcdicHeader, final boolean eolPresent) {
final int[] valueWidths = getValueWidths(true);
try {
return new FixedWidthDatastore(name, resource, resource.getQualifiedPath(),
_encodingComboBox.getSelectedItem(), valueWidths, failOnInconsistencies, skipEbcdicHeader,
eolPresent, getHeaderLine(), _columnNamesWidget.getColumnNames());
} catch (final NumberFormatException e) {
throw new IllegalStateException("Value width must be a valid number.");
}
}
private int[] getValueWidths(final boolean failOnMissingValue) {
final int[] valueWidths = new int[_valueWidthTextFields.size()];
try {
for (int i = 0; i < valueWidths.length; i++) {
String text = _valueWidthTextFields.get(i).getText();
if (StringUtils.isNullOrEmpty(text)) {
if (failOnMissingValue) {
throw new IllegalStateException("Please fill out all column widths.");
} else {
text = "0";
}
}
valueWidths[i] = Integer.parseInt(text);
}
} catch (final NumberFormatException e) {
throw new IllegalStateException("Please specify all column widths as numbers. ");
}
return valueWidths;
}
@Override
protected String getDatastoreIconPath() {
return IconUtils.FIXEDWIDTH_IMAGEPATH;
}
public int getHeaderLine() {
final Number headerLineComboValue = _headerLineComboBox.getSelectedItem();
if (headerLineComboValue != null) {
final int intComboValue = headerLineComboValue.intValue();
if (intComboValue < 0) {
return FixedWidthConfiguration.NO_COLUMN_NAME_LINE;
} else {
// MetaModel's headerline number is 0-based
return intComboValue;
}
} else {
return FixedWidthConfiguration.DEFAULT_COLUMN_NAME_LINE;
}
}
private void registerColumnNameFields() {
_columnNamesWidget.getColumnNameFields().stream().filter(field -> !_columnNameFields.contains(field))
.forEach(field -> {
field.addKeyListener(new KeyAdapter() {
@Override
public void keyTyped(final KeyEvent e) {
onSettingsUpdated(false);
}
});
_columnNameFields.add(field);
});
}
@Override
protected void initializeFileFilters(final ResourceSelector resourceSelector) {
final FileFilter combinedFilter = FileFilters
.combined("Any text, data or EBCDIC files (.txt, .dat, .ebc)", FileFilters.TXT, FileFilters.DAT,
FileFilters.EBC);
resourceSelector.addChoosableFileFilter(combinedFilter);
resourceSelector.addChoosableFileFilter(FileFilters.TXT);
resourceSelector.addChoosableFileFilter(FileFilters.DAT);
resourceSelector.addChoosableFileFilter(FileFilters.EBC);
resourceSelector.setSelectedFileFilter(combinedFilter);
resourceSelector.addListener(new ResourceTypePresenter.Listener() {
@Override
public void onResourceSelected(final ResourceTypePresenter> presenter, final Resource resource) {
onSettingsUpdated(true);
}
@Override
public void onPathEntered(final ResourceTypePresenter> presenter, final String path) {
}
});
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy