/**
* 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.widgets.properties;
import java.awt.BorderLayout;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.TreeSet;
import java.util.WeakHashMap;
import javax.swing.JComponent;
import org.datacleaner.api.InputColumn;
import org.datacleaner.descriptors.ConfiguredPropertyDescriptor;
import org.datacleaner.job.builder.ComponentBuilder;
import org.datacleaner.metadata.ColumnMeaningCollection;
import org.datacleaner.metadata.HasColumnMeaning;
import org.datacleaner.panels.DCPanel;
import org.datacleaner.widgets.DCCheckBox;
import org.datacleaner.widgets.DCGroupComboBox;
/**
* A specialized property widget for multiple input columns that are mapped to string values.
* This widget looks like the {@link MultipleInputColumnsPropertyWidget}, but is enhanced with combo boxes.
*/
public class MultipleMappedComboBoxPropertyWidget extends MultipleInputColumnsPropertyWidget {
public class MappedComboBoxPropertyWidget extends MinimalPropertyWidget<Object[]> {
private MultipleMappedComboBoxPropertyWidget _multipleMappedComboBoxPropertyWidget;
public MappedComboBoxPropertyWidget(
final MultipleMappedComboBoxPropertyWidget multipleMappedComboBoxPropertyWidget,
final ComponentBuilder componentBuilder, final ConfiguredPropertyDescriptor propertyDescriptor) {
super(componentBuilder, propertyDescriptor);
_multipleMappedComboBoxPropertyWidget = multipleMappedComboBoxPropertyWidget;
}
@Override
public JComponent getWidget() {
return null; // do not return a visual widget
}
@Override
public boolean isSet() {
return _multipleMappedComboBoxPropertyWidget.isSet();
}
@Override
public Object[] getValue() {
final InputColumn<?>[] inputColumns = MultipleMappedComboBoxPropertyWidget.this.getValue();
final List<HasColumnMeaning> result = new ArrayList<>();
for (final InputColumn<?> inputColumn : inputColumns) {
final DCGroupComboBox comboBox = _mappedComboBoxes.get(inputColumn);
if (comboBox == null || !comboBox.isVisible()) {
result.add(null);
} else {
result.add((HasColumnMeaning) comboBox.getSelectedItem());
}
}
return result.toArray(new HasColumnMeaning[result.size()]);
}
@Override
protected void setValue(final Object[] value) {
final List<InputColumn<?>> inputColumns =
MultipleMappedComboBoxPropertyWidget.this.getSelectedInputColumns();
for (int i = 0; i < inputColumns.size(); i++) {
final HasColumnMeaning meaning;
if (value != null && i < value.length) {
meaning = (HasColumnMeaning) value[i];
} else {
meaning = null;
}
final InputColumn<?> inputColumn = inputColumns.get(i);
final DCGroupComboBox comboBox = _mappedComboBoxes.get(inputColumn);
if (meaning != null) {
comboBox.setVisible(true);
comboBox.setEditable(true);
comboBox.setSelectedItem(meaning);
comboBox.setEditable(false);
}
}
}
}
private final Map<InputColumn<?>, DCGroupComboBox> _mappedComboBoxes;
private final MappedComboBoxPropertyWidget _mappedComboBoxPropertyWidget;
private final ConfiguredPropertyDescriptor _mappedColumnsProperty;
private final ColumnMeaningCollection _availableColumnMeanings;
public MultipleMappedComboBoxPropertyWidget(final ComponentBuilder componentBuilder,
final ConfiguredPropertyDescriptor inputColumnsProperty,
final ConfiguredPropertyDescriptor mappedColumnsProperty,
final ColumnMeaningCollection availableColumnMeanings) {
super(componentBuilder, inputColumnsProperty);
_mappedComboBoxes = new WeakHashMap<>();
_mappedColumnsProperty = mappedColumnsProperty;
_availableColumnMeanings = availableColumnMeanings;
_mappedComboBoxPropertyWidget =
new MappedComboBoxPropertyWidget(this, componentBuilder, _mappedColumnsProperty);
}
public MappedComboBoxPropertyWidget getMappedComboBoxPropertyWidget() {
return _mappedComboBoxPropertyWidget;
}
@Override
protected boolean isAllInputColumnsSelectedIfNoValueExist() {
return false;
}
protected DCGroupComboBox createComboBox(final InputColumn<?> inputColumn,
final HasColumnMeaning mappedValue) {
final DCGroupComboBox comboBox = new DCGroupComboBox();
fillComboBox(comboBox);
_mappedComboBoxes.put(inputColumn, comboBox);
comboBox.setEditable(true);
if (mappedValue == null) {
comboBox.setSelectedItem(findMeaningByColumnName(inputColumn.getName()));
} else {
comboBox.setSelectedItem(mappedValue);
}
comboBox.setEditable(false);
comboBox.addListener(item -> _mappedComboBoxPropertyWidget.fireValueChanged());
return comboBox;
}
private void fillComboBox(final DCGroupComboBox comboBox) {
final Map<String, Set<HasColumnMeaning>> groupedMeanings = getGroupedColumnMeanings();
for (final String group : groupedMeanings.keySet()) {
if (groupedMeanings.size() > 1) {
comboBox.addDelimiter(group);
}
for (final HasColumnMeaning meaning : groupedMeanings.get(group)) {
comboBox.addItem(meaning);
}
}
}
private HashMap<String, Set<HasColumnMeaning>> getGroupedColumnMeanings() {
final HashMap<String, Set<HasColumnMeaning>> groupedMeanings = new HashMap<>();
final String nullCategory = "Meanings";
for (final HasColumnMeaning meaning : _availableColumnMeanings.getColumnMeanings()) {
final String categoryName = meaning.getGroup() == null ? nullCategory : meaning.getGroup();
if (groupedMeanings.containsKey(categoryName)) {
groupedMeanings.get(categoryName).add(meaning);
} else {
final Set<HasColumnMeaning> categorySet =
new TreeSet<>(Comparator.comparing(HasColumnMeaning::getName, String.CASE_INSENSITIVE_ORDER));
categorySet.add(meaning);
groupedMeanings.put(categoryName, categorySet);
}
}
return groupedMeanings;
}
private HasColumnMeaning findMeaningByColumnName(final String columnName) {
final HasColumnMeaning meaning = _availableColumnMeanings.find(columnName);
if (meaning == null) {
return _availableColumnMeanings.getDefault();
}
return meaning;
}
@Override
protected JComponent decorateCheckBox(final DCCheckBox<InputColumn<?>> checkBox) {
final DCGroupComboBox comboBox;
final InputColumn<?> inputColumn = checkBox.getValue();
if (_mappedComboBoxes.containsKey(inputColumn)) {
comboBox = _mappedComboBoxes.get(inputColumn);
} else {
comboBox = createComboBox(inputColumn, null);
}
checkBox.addListener((item, selected) -> {
if (isBatchUpdating()) {
return;
}
comboBox.setVisible(selected);
_mappedComboBoxPropertyWidget.fireValueChanged();
});
final boolean selected = checkBox.isSelected();
comboBox.setVisible(selected);
final DCPanel panel = new DCPanel();
panel.setLayout(new BorderLayout());
panel.add(checkBox, BorderLayout.CENTER);
panel.add(comboBox, BorderLayout.EAST);
return panel;
}
@Override
protected void onValuesBatchSelected(final List<InputColumn<?>> values) {
_mappedComboBoxes.values().forEach(cb -> cb.setVisible(false));
values.stream().map(_mappedComboBoxes::get).filter(Objects::nonNull).forEach(cb -> cb.setVisible(true));
}
@Override
protected void onBatchFinished() {
super.onBatchFinished();
_mappedComboBoxPropertyWidget.fireValueChanged();
}
}