/** * 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.panels.fuse; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.swing.BoxLayout; import javax.swing.JComponent; import javax.swing.JScrollPane; import org.apache.metamodel.schema.Table; import org.datacleaner.api.InputColumn; import org.datacleaner.api.OutputDataStream; import org.datacleaner.components.fuse.CoalesceUnit; import org.datacleaner.data.MutableInputColumn; import org.datacleaner.descriptors.ConfiguredPropertyDescriptor; import org.datacleaner.job.builder.AnalysisJobBuilder; import org.datacleaner.job.builder.ComponentBuilder; import org.datacleaner.job.builder.TransformerChangeListener; import org.datacleaner.job.builder.TransformerComponentBuilder; import org.datacleaner.panels.DCPanel; import org.datacleaner.util.SourceColumnFinder; import org.datacleaner.widgets.properties.AbstractPropertyWidget; import org.datacleaner.widgets.properties.MinimalPropertyWidget; import org.datacleaner.widgets.properties.PropertyWidget; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.Multimap; /** * A {@link PropertyWidget} for representing both a property with an array of * {@link CoalesceUnit}s and another property with an array of * {@link InputColumn}s. * * This widget presents the incoming streams as columns and available * {@link InputColumn}s from each stream as rows in a matrix where the user gets * the design a new {@link OutputDataStream}. */ public class StreamColumnMatrixMultipleCoalesceUnitPropertyWidget extends AbstractPropertyWidget<InputColumn<?>[]> implements TransformerChangeListener, MutableInputColumn.Listener { private static final Logger logger = LoggerFactory.getLogger(StreamColumnMatrixMultipleCoalesceUnitPropertyWidget.class); private final ConfiguredPropertyDescriptor _unitProperty; private final MinimalPropertyWidget<CoalesceUnit[]> _unitPropertyWidget; private final DCPanel _containerPanel; private final List<StreamColumnListPanel> _tablePanels; public StreamColumnMatrixMultipleCoalesceUnitPropertyWidget(final ComponentBuilder componentBuilder, final ConfiguredPropertyDescriptor inputProperty, final ConfiguredPropertyDescriptor unitProperty) { super(componentBuilder, inputProperty); _unitProperty = unitProperty; _tablePanels = new ArrayList<>(); getAnalysisJobBuilder().addTransformerChangeListener(this); _containerPanel = new DCPanel(); _containerPanel.setLayout(new BoxLayout(_containerPanel, BoxLayout.X_AXIS)); _unitPropertyWidget = createUnitPropertyWidget(); // only facilitate horizontal scroll final JScrollPane scroll = new JScrollPane(_containerPanel); scroll.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_NEVER); add(scroll); final CoalesceUnit[] coalesceUnits = (CoalesceUnit[]) getComponentBuilder().getConfiguredProperty(_unitProperty); refresh(coalesceUnits); if (coalesceUnits != null) { // Call fireValueChanged() to provoke the check for non-used // streams. This cleans up the InputColumn[] property of streams // that are not relevant at all. Another way to think of it is that // irrelevant lines on the graph behind will be removed. fireValueChanged(); } } private void refresh(final CoalesceUnit[] units) { _tablePanels.clear(); _containerPanel.removeAll(); final AnalysisJobBuilder ajb = getAnalysisJobBuilder(); final InputColumn<?>[] inputColumns = ajb.getSourceColumns().toArray(new InputColumn[0]); // TODO: We need a SourceColumnFinder that is aware of also nested jobs final SourceColumnFinder sourceColumnFinder = new SourceColumnFinder(); sourceColumnFinder.addSources(ajb); // build registries of available columns and coalesced columns final Multimap<Table, InputColumn<?>> allTablesAndColumns = ArrayListMultimap.create(); final Multimap<Table, InputColumn<?>> coalescedTablesAndColumns = ArrayListMultimap.create(); for (final InputColumn<?> inputColumn : inputColumns) { final Table table = sourceColumnFinder.findOriginatingTable(inputColumn); allTablesAndColumns.put(table, inputColumn); } if (units != null) { final List<CoalesceUnit> survivingCoalesceUnits = new ArrayList<>(units.length); for (CoalesceUnit unit : units) { // Not necessarily initialized yet, so no _initializedUnits available final InputColumn<?>[] updatedInputColumns = unit.getUpdatedInputColumns(inputColumns, false); if (updatedInputColumns.length > 0) { unit = unit.getUpdatedCoalesceUnit(updatedInputColumns); survivingCoalesceUnits.add(unit); for (final InputColumn<?> inputColumn : updatedInputColumns) { final Table originatingTable = sourceColumnFinder.findOriginatingTable(inputColumn); coalescedTablesAndColumns.put(originatingTable, inputColumn); } } } if (!Arrays .equals(units, survivingCoalesceUnits.toArray(new CoalesceUnit[survivingCoalesceUnits.size()]))) { final Map<ConfiguredPropertyDescriptor, Object> properties = new HashMap<>(); properties.put(getPropertyDescriptor(), getValue()); properties.put(_unitProperty, survivingCoalesceUnits.toArray(new CoalesceUnit[survivingCoalesceUnits.size()])); fireValuesChanged(properties); } } for (final Table table : allTablesAndColumns.keySet()) { final StreamColumnListPanel tablePanel = new StreamColumnListPanel(ajb, table, panel -> { fireValueChanged(); _unitPropertyWidget.fireValueChanged(); }); final Collection<InputColumn<?>> selectedColumns = coalescedTablesAndColumns.get(table); for (final InputColumn<?> inputColumn : selectedColumns) { tablePanel.addInputColumn(inputColumn, true); } final Collection<InputColumn<?>> columns = new ArrayList<>(allTablesAndColumns.get(table)); columns.removeAll(selectedColumns); for (final InputColumn<?> inputColumn : columns) { tablePanel.addInputColumn(inputColumn, false); } tablePanel.refresh(); _tablePanels.add(tablePanel); _containerPanel.add(tablePanel); } } @Override public void onPanelRemove() { super.onPanelRemove(); getAnalysisJobBuilder().removeTransformerChangeListener(this); } public PropertyWidget<?> getUnitPropertyWidget() { return _unitPropertyWidget; } private MinimalPropertyWidget<CoalesceUnit[]> createUnitPropertyWidget() { return new MinimalPropertyWidget<CoalesceUnit[]>(getComponentBuilder(), _unitProperty) { @Override public JComponent getWidget() { // do not return a visual widget return null; } @Override public CoalesceUnit[] getValue() { final CoalesceUnit[] units = getCoalesceUnits(); if (units.length == 0) { logger.debug("Returning Units.value = null"); return null; } if (logger.isDebugEnabled()) { logger.debug("Returning Units.value = {}", Arrays.toString(units)); } return units; } @Override protected void setValue(final CoalesceUnit[] value) { } @Override public boolean isSet() { return StreamColumnMatrixMultipleCoalesceUnitPropertyWidget.this.isSet(); } }; } protected CoalesceUnit[] getCoalesceUnits() { int max = 0; final List<List<InputColumn<?>>> allCoalescedInputColumns = new ArrayList<>(); for (final StreamColumnListPanel tablePanel : _tablePanels) { final List<InputColumn<?>> coalescedInputColumns = tablePanel.getCoalescedInputColumns(); allCoalescedInputColumns.add(coalescedInputColumns); max = Math.max(max, coalescedInputColumns.size()); } final CoalesceUnit[] result = new CoalesceUnit[max]; for (int i = 0; i < result.length; i++) { final List<InputColumn<?>> coalesceUnitInputColumns = new ArrayList<>(); for (final List<InputColumn<?>> coalescedInputColumnsForTable : allCoalescedInputColumns) { if (coalescedInputColumnsForTable.size() - 1 >= i) { coalesceUnitInputColumns.add(coalescedInputColumnsForTable.get(i)); } } final CoalesceUnit unit = new CoalesceUnit(coalesceUnitInputColumns); result[i] = unit; } return result; } @Override public InputColumn<?>[] getValue() { final List<InputColumn<?>> result = new ArrayList<>(); for (final StreamColumnListPanel tablePanel : _tablePanels) { if (tablePanel.hasSelectedInputColumns()) { result.addAll(tablePanel.getAllInputColumns()); } } return result.toArray(new InputColumn<?>[result.size()]); } @Override protected void setValue(final InputColumn<?>[] value) { } @Override public void onNameChanged(final MutableInputColumn<?> inputColumn, final String oldName, final String newName) { } @Override public void onVisibilityChanged(final MutableInputColumn<?> inputColumn, final boolean hidden) { } @Override public void onOutputChanged(final TransformerComponentBuilder<?> transformerJobBuilder, final List<MutableInputColumn<?>> outputColumns) { if (transformerJobBuilder != getComponentBuilder()) { refresh(getCoalesceUnits()); } } @Override public void onAdd(final TransformerComponentBuilder<?> builder) { } @Override public void onConfigurationChanged(final TransformerComponentBuilder<?> builder) { } @Override public void onRequirementChanged(final TransformerComponentBuilder<?> builder) { } @Override public void onRemove(final TransformerComponentBuilder<?> componentBuilder) { } }