All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.datacleaner.windows.FixedWidthDatastoreDialog Maven / Gradle / Ivy

There is a newer version: 6.0.0
Show newest version
/**
 * 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