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

org.datacleaner.widgets.properties.MultipleMappedColumnsPropertyWidget Maven / Gradle / Ivy

/**
 * DataCleaner (community edition)
 * Copyright (C) 2014 Free Software Foundation, Inc.
 *
 * 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.widgets.properties;

import java.awt.BorderLayout;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.WeakHashMap;

import javax.swing.JComponent;

import org.apache.metamodel.schema.Column;
import org.apache.metamodel.schema.MutableColumn;
import org.apache.metamodel.schema.Table;
import org.apache.metamodel.util.MutableRef;
import org.datacleaner.api.InputColumn;
import org.datacleaner.descriptors.ConfiguredPropertyDescriptor;
import org.datacleaner.job.builder.ComponentBuilder;
import org.datacleaner.panels.DCPanel;
import org.datacleaner.widgets.DCCheckBox;
import org.datacleaner.widgets.SourceColumnComboBox;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * A specialized property widget for multiple input columns that are mapped to
 * physical columns. This widget looks like the
 * {@link MultipleInputColumnsPropertyWidget}, but is enhanced with source
 * column combo boxes and awareness of changes to selected table.
 */
public class MultipleMappedColumnsPropertyWidget extends MultipleInputColumnsPropertyWidget {

    public class MappedColumnNamesPropertyWidget extends MinimalPropertyWidget {

        public MappedColumnNamesPropertyWidget(final ComponentBuilder componentBuilder,
                final ConfiguredPropertyDescriptor propertyDescriptor) {
            super(componentBuilder, propertyDescriptor);
        }

        @Override
        public JComponent getWidget() {
            // do not return a visual widget
            return null;
        }

        @Override
        public boolean isSet() {
            return MultipleMappedColumnsPropertyWidget.this.isSet();
        }

        @Override
        public String[] getValue() {
            return getMappedColumnNames();
        }

        @Override
        protected void setValue(final String[] value) {
            if (MultipleMappedColumnsPropertyWidget.this.isUpdating()) {
                return;
            }
            setMappedColumnNames(value);
        }
    }

    private static final Logger logger = LoggerFactory.getLogger(MultipleMappedColumnsPropertyWidget.class);

    private final Map, SourceColumnComboBox> _mappedColumnComboBoxes;
    private final Map _comboBoxDecorations;
    private final MutableRef _tableRef;
    private final ConfiguredPropertyDescriptor _mappedColumnsProperty;
    private final MappedColumnNamesPropertyWidget _mappedColumnNamesPropertyWidget;

    /**
     * Constructs the property widget
     *
     * @param beanJobBuilder
     *            the transformer job builder for the table lookup
     * @param inputColumnsProperty
     *            the property representing the columns to use for setting up
     *            conditional lookup (InputColumn[])
     * @param mappedColumnsProperty
     *            the property representing the mapped columns in the datastore
     *            (String[])
     */
    public MultipleMappedColumnsPropertyWidget(final ComponentBuilder componentBuilder,
            final ConfiguredPropertyDescriptor inputColumnsProperty,
            final ConfiguredPropertyDescriptor mappedColumnsProperty) {
        super(componentBuilder, inputColumnsProperty);
        _mappedColumnComboBoxes = new WeakHashMap<>();
        _comboBoxDecorations = new IdentityHashMap<>();
        _mappedColumnsProperty = mappedColumnsProperty;

        _tableRef = new MutableRef<>();
        _mappedColumnNamesPropertyWidget = new MappedColumnNamesPropertyWidget(componentBuilder, mappedColumnsProperty);

        final InputColumn[] currentValue = getCurrentValue();
        final String[] currentMappedColumnsValue =
                (String[]) componentBuilder.getConfiguredProperty(mappedColumnsProperty);
        if (currentValue != null && currentMappedColumnsValue != null) {
            // first create combo's, then set value (so combo is ready before it
            // is requested)

            _mappedColumnNamesPropertyWidget.setValue(currentMappedColumnsValue);
            final int minLength = Math.min(currentValue.length, currentMappedColumnsValue.length);
            for (int i = 0; i < minLength; i++) {
                final InputColumn inputColumn = currentValue[i];
                final String mappedColumnName = currentMappedColumnsValue[i];
                createComboBox(inputColumn, new MutableColumn(mappedColumnName));
            }

            setValue(currentValue);
        }
    }

    private void setMappedColumnVisible(final InputColumn inputColumn, final boolean visible) {
        final SourceColumnComboBox comboBox = _mappedColumnComboBoxes.get(inputColumn);
        setMappedColumnVisible(comboBox, visible);
    }

    private void setMappedColumnVisible(final SourceColumnComboBox comboBox, final boolean visible) {
        final JComponent decoration = _comboBoxDecorations.get(comboBox);
        decoration.setVisible(visible);
    }

    public void setTable(final Table table) {
        if (table != _tableRef.get()) {
            _tableRef.set(table);
            updateMappedColumns();
        }
    }

    private void updateMappedColumns() {
        final Table table = _tableRef.get();
        final Set, SourceColumnComboBox>> entrySet = _mappedColumnComboBoxes.entrySet();

        batchUpdateWidget(() -> {
            for (final Entry, SourceColumnComboBox> entry : entrySet) {
                final InputColumn inputColumn = entry.getKey();
                final SourceColumnComboBox comboBox = entry.getValue();

                if (table == null) {
                    comboBox.setEmptyModel();
                } else {
                    comboBox.setModel(table);
                    if (comboBox.getSelectedItem() == null) {
                        final Column column = getDefaultMappedColumn(inputColumn, table);
                        if (column != null) {
                            comboBox.setEditable(true);
                            comboBox.setSelectedItem(column);
                            comboBox.setEditable(false);
                        }
                    }
                }
            }
        });
    }

    private SourceColumnComboBox createComboBox(final InputColumn inputColumn, Column mappedColumn) {
        final SourceColumnComboBox sourceColumnComboBox = new SourceColumnComboBox();
        _mappedColumnComboBoxes.put(inputColumn, sourceColumnComboBox);

        final Table table = _tableRef.get();
        if (mappedColumn == null && table != null) {
            mappedColumn = getDefaultMappedColumn(inputColumn, table);
        }

        if (mappedColumn == null) {
            logger.info("No default mapping found for column: {}", inputColumn);
        } else {
            sourceColumnComboBox.setEditable(true);
            sourceColumnComboBox.setSelectedItem(mappedColumn);
            sourceColumnComboBox.setEditable(false);
        }
        sourceColumnComboBox.addColumnSelectedListener(item -> {
            if (isBatchUpdating()) {
                return;
            }
            _mappedColumnNamesPropertyWidget.fireValueChanged();
            fireValueChanged();
        });
        return sourceColumnComboBox;
    }

    protected Column getDefaultMappedColumn(final InputColumn inputColumn, final Table table) {
        // automatically select a column by name, if it exists
        return table.getColumnByName(inputColumn.getName());
    }

    /**
     * Gets the {@link ConfiguredPropertyDescriptor} of the property that has
     * the column names that are being mapped to.
     *
     * @return
     */
    public ConfiguredPropertyDescriptor getMappedColumnsProperty() {
        return _mappedColumnsProperty;
    }

    @Override
    protected JComponent decorateCheckBox(final DCCheckBox> checkBox) {
        final SourceColumnComboBox sourceColumnComboBox;
        final InputColumn inputColumn = checkBox.getValue();
        if (_mappedColumnComboBoxes.containsKey(inputColumn)) {
            sourceColumnComboBox = _mappedColumnComboBoxes.get(inputColumn);
        } else {
            sourceColumnComboBox = createComboBox(inputColumn, null);
        }

        final JComponent decoratedSourceColumnComboBox =
                decorateSourceColumnComboBox(inputColumn, sourceColumnComboBox);
        _comboBoxDecorations.put(sourceColumnComboBox, decoratedSourceColumnComboBox);

        checkBox.addListenerToHead(this::setMappedColumnVisible);
        checkBox.addListener((item, selected) -> {
            if (isBatchUpdating()) {
                return;
            }
            _mappedColumnNamesPropertyWidget.fireValueChanged();
        });

        final Table table = _tableRef.get();
        if (table != null) {
            sourceColumnComboBox.setModel(table);
        }

        setMappedColumnVisible(sourceColumnComboBox, checkBox.isSelected());

        final DCPanel panel = new DCPanel();
        panel.setLayout(new BorderLayout());
        panel.add(checkBox, BorderLayout.CENTER);
        panel.add(decoratedSourceColumnComboBox, BorderLayout.EAST);
        return panel;
    }

    /**
     * Method which decorates the UI component of an inserted
     * {@link SourceColumnComboBox}. Subclasses can override this method if eg.
     * additional widgets should be added.
     *
     * @param sourceColumnComboBox
     * @return
     */
    protected JComponent decorateSourceColumnComboBox(final InputColumn inputColumn,
            final SourceColumnComboBox sourceColumnComboBox) {
        return sourceColumnComboBox;
    }

    public MappedColumnNamesPropertyWidget getMappedColumnNamesPropertyWidget() {
        return _mappedColumnNamesPropertyWidget;
    }

    public String[] getMappedColumnNames() {
        final List> selectedInputColumns = getSelectedInputColumns();
        final List result = new ArrayList<>();
        for (final InputColumn inputColumn : selectedInputColumns) {
            final SourceColumnComboBox comboBox = _mappedColumnComboBoxes.get(inputColumn);
            if (comboBox == null) {
                logger.warn("No SourceColumnComboBox found for input column: {}", inputColumn);
                result.add(null);
            } else {
                final Column column = comboBox.getSelectedItem();
                if (column == null) {
                    result.add(null);
                } else {
                    result.add(column.getName());
                }
            }
        }

        logger.debug("getMappedColumnNames() returning: {}", result);

        return result.toArray(new String[result.size()]);
    }

    public void setMappedColumnNames(final String[] mappedColumnNames) {
        if (logger.isDebugEnabled()) {
            logger.debug("setMappedColumnNames({})", Arrays.toString(mappedColumnNames));
        }

        final List> inputColumns = MultipleMappedColumnsPropertyWidget.this.getSelectedInputColumns();
        for (int i = 0; i < inputColumns.size(); i++) {
            final InputColumn inputColumn = inputColumns.get(i);
            final String mappedColumnName;
            if (mappedColumnNames == null) {
                mappedColumnName = null;
            } else if (i < mappedColumnNames.length) {
                mappedColumnName = mappedColumnNames[i];
            } else {
                mappedColumnName = null;
            }
            final SourceColumnComboBox comboBox = _mappedColumnComboBoxes.get(inputColumn);
            setMappedColumnVisible(comboBox, true);
            comboBox.setEditable(true);
            comboBox.setSelectedItem(mappedColumnName);
            comboBox.setEditable(false);
        }
    }

    public Map, SourceColumnComboBox> getMappedColumnComboBoxes() {
        return Collections.unmodifiableMap(_mappedColumnComboBoxes);
    }

    @Override
    protected void onValuesBatchSelected(final List> values) {
        for (final SourceColumnComboBox sourceColumnComboBox : _mappedColumnComboBoxes.values()) {
            setMappedColumnVisible(sourceColumnComboBox, false);
        }
        for (final InputColumn inputColumn : values) {
            setMappedColumnVisible(inputColumn, true);
        }
    }

    @Override
    protected void onBatchFinished() {
        super.onBatchFinished();
        _mappedColumnNamesPropertyWidget.fireValueChanged();
    }
}