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

org.bidib.wizard.nodes.client.view.NodesClientView Maven / Gradle / Ivy

package org.bidib.wizard.nodes.client.view;

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Frame;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.function.BiConsumer;
import java.util.function.Consumer;

import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.filechooser.FileFilter;
import javax.swing.filechooser.FileNameExtensionFilter;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.TableColumn;
import javax.swing.table.TableModel;

import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.io.IOUtils;
import org.bidib.api.json.types.dccmapping.AccessoryAspectMapping;
import org.bidib.api.json.types.dccmapping.BidibAccessoryAspect;
import org.bidib.api.json.types.dccmapping.DccAccessoryAspect;
import org.bidib.api.json.types.dccmapping.DccAccessoryAspectMapping;
import org.bidib.jbidibc.core.node.ConfigurationVariable;
import org.bidib.jbidibc.core.schema.bidibbase.BaseLabel;
import org.bidib.jbidibc.core.schema.bidiblabels.AccessoryLabel;
import org.bidib.jbidibc.core.schema.bidiblabels.NodeLabels;
import org.bidib.jbidibc.messages.DccAccessory;
import org.bidib.jbidibc.messages.DccAspect;
import org.bidib.jbidibc.messages.utils.ByteUtils;
import org.bidib.jbidibc.messages.utils.NodeUtils;
import org.bidib.wizard.api.locale.Resources;
import org.bidib.wizard.api.model.Accessory;
import org.bidib.wizard.api.model.AccessoryAspect;
import org.bidib.wizard.api.model.AccessoryAspectMacro;
import org.bidib.wizard.api.model.Macro;
import org.bidib.wizard.api.model.MacroRef;
import org.bidib.wizard.api.model.NodeInterface;
import org.bidib.wizard.api.model.NodeProvider;
import org.bidib.wizard.client.common.view.ButtonUtils;
import org.bidib.wizard.client.common.view.DockKeys;
import org.bidib.wizard.common.labels.AccessoryLabelUtils;
import org.bidib.wizard.common.labels.WizardLabelFactory;
import org.bidib.wizard.common.labels.WizardLabelWrapper;
import org.bidib.wizard.common.model.settings.MiscSettingsInterface;
import org.bidib.wizard.core.dialog.FileDialog;
import org.bidib.wizard.nodes.client.controller.InvalidMappingException;
import org.bidib.wizard.nodes.client.controller.listener.NodesClientControllerListener;
import org.bidib.wizard.nodes.client.model.AccessoryAspectTreeTableModel;
import org.bidib.wizard.nodes.client.model.AccessoryNode;
import org.bidib.wizard.nodes.client.model.AccessoryRow;
import org.bidib.wizard.nodes.client.model.AccessorySortableTreeTableModel;
import org.bidib.wizard.nodes.client.model.AspectRow;
import org.bidib.wizard.nodes.client.model.MappingAction;
import org.bidib.wizard.nodes.client.model.NodeRow;
import org.bidib.wizard.nodes.client.model.NodesAccessoryAspect;
import org.oxbow.swingbits.dialog.task.TaskDialogs;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.jgoodies.common.collect.ArrayListModel;
import com.jgoodies.forms.builder.FormBuilder;
import com.jgoodies.forms.debug.FormDebugPanel;
import com.jgoodies.forms.factories.Paddings;
import com.jidesoft.converter.ObjectConverterManager;
import com.jidesoft.grid.AutoFilterTableHeader;
import com.jidesoft.grid.CellEditorManager;
import com.jidesoft.grid.CellRendererManager;
import com.jidesoft.grid.CellStyleTableHeader;
import com.jidesoft.grid.DefaultExpandableRow;
import com.jidesoft.grid.FilterableTreeTableModel;
import com.jidesoft.grid.IntegerCellEditor;
import com.jidesoft.grid.RolloverTableUtils;
import com.jidesoft.grid.Row;
import com.jidesoft.grid.SortableTreeTableModel;
import com.jidesoft.grid.TableModelWrapperUtils;
import com.jidesoft.grid.TablePopupMenuInstaller;
import com.jidesoft.swing.DefaultOverlayable;
import com.jidesoft.swing.InfiniteProgressPanel;
import com.jidesoft.swing.JideScrollPane;
import com.jidesoft.swing.JideSwingUtilities;
import com.jidesoft.swing.TableSearchable;
import com.vlsolutions.swing.docking.DockKey;
import com.vlsolutions.swing.docking.Dockable;
import com.vlsolutions.swing.docking.DockableState;
import com.vlsolutions.swing.docking.DockingDesktop;
import com.vlsolutions.swing.docking.event.DockableSelectionListener;
import com.vlsolutions.swing.docking.event.DockableStateChangeEvent;
import com.vlsolutions.swing.docking.event.DockableStateChangeListener;

public class NodesClientView implements Dockable {

    private static final Logger LOGGER = LoggerFactory.getLogger(NodesClientView.class);

    private static final String ENCODED_DIALOG_COLUMN_SPECS = "pref, 3dlu, pref, 3dlu, fill:pref:grow";

    private static final String ENCODED_DIALOG_ROW_SPECS = "pref, 5dlu, fill:50dlu:grow";

    private JComponent contentPanel;

    private final DockingDesktop desktop;

    private InfiniteProgressPanel progressPanel;

    private DefaultOverlayable overlayTable;

    private final DockableStateChangeListener dockableStateChangeListener;

    private final MiscSettingsInterface miscSettings;

    private final NodesClientControllerListener controllerListener;

    private AccessoryAspectTreeTableModel accessoryAspectTreeTableModel;

    private ArrayListModel accessoryListModel;

    private AccessorySortableTreeTableModel accessorySortableTreeTableModel;

    private AspectMappingTreeTable treeTable;

    private static final String LOAD = "loadFromFile";

    private static final String SAVE = "saveToFile";

    private static final String CLEAR_ALL = "clearAll";

    private JButton loadFromFileButton;

    private JButton saveToFileButton;

    private JButton loadFromNodeButton;

    private JButton saveToNodeButton;

    private JButton clearAllOnNodeButton;

    private DockableSelectionListener dockableSelectionListener;

    private final WizardLabelWrapper wizardLabelWrapper;

    public NodesClientView(final DockingDesktop desktop, final NodesClientControllerListener controllerListener,
        final MiscSettingsInterface miscSettings, final WizardLabelWrapper wizardLabelWrapper) {
        this.desktop = desktop;
        this.controllerListener = controllerListener;
        this.miscSettings = miscSettings;
        this.wizardLabelWrapper = wizardLabelWrapper;

        DockKeys.DOCKKEY_NODES_CLIENT_VIEW.setName(Resources.getString(getClass(), "title"));
        DockKeys.DOCKKEY_NODES_CLIENT_VIEW.setFloatEnabled(true);
        DockKeys.DOCKKEY_NODES_CLIENT_VIEW.setAutoHideEnabled(false);

        dockableStateChangeListener = new DockableStateChangeListener() {

            @Override
            public void dockableStateChanged(DockableStateChangeEvent event) {
                LOGGER
                    .info("The state has changed, newState: {}, prevState: {}", event.getNewState(),
                        event.getPreviousState());

                DockableState newState = event.getNewState();
                if (newState.getDockable().equals(NodesClientView.this) && newState.isClosed()) {
                    LOGGER.info("The NodesClientView is closed.");
                    // we are closed
                    desktop.removeDockableStateChangeListener(dockableStateChangeListener);

                    if (NodesClientView.this.dockableSelectionListener != null) {
                        desktop.removeDockableSelectionListener(NodesClientView.this.dockableSelectionListener);

                        NodesClientView.this.dockableSelectionListener = null;
                    }

                    // removeToolBar(toolbarNodesClient);

                    if (controllerListener != null) {
                        LOGGER.info("Close the view.");

                        controllerListener.viewClosed();
                    }
                }

            }
        };
        desktop.addDockableStateChangeListener(dockableStateChangeListener);
    }

    public JComponent initComponents() {

        // create form builder
        FormBuilder dialogBuilder = null;
        boolean debugDialog = false;
        if (debugDialog) {
            JPanel panel = new FormDebugPanel();
            dialogBuilder =
                FormBuilder.create().columns(ENCODED_DIALOG_COLUMN_SPECS).rows(ENCODED_DIALOG_ROW_SPECS).panel(panel);
        }
        else {
            JPanel panel = new JPanel(new BorderLayout());
            dialogBuilder =
                FormBuilder.create().columns(ENCODED_DIALOG_COLUMN_SPECS).rows(ENCODED_DIALOG_ROW_SPECS).panel(panel);
        }
        dialogBuilder.border(Paddings.DIALOG);

        createToolbarButtons();

        // prepare the button bar
        final FormBuilder buttonBarBuilder =
            FormBuilder.create().columns("pref, 3dlu, pref, 3dlu, pref, 3dlu, pref, 3dlu, pref").rows("pref");
        buttonBarBuilder.add(loadFromFileButton).xy(1, 1);
        buttonBarBuilder.add(saveToFileButton).xy(3, 1);
        buttonBarBuilder.add(loadFromNodeButton).xy(5, 1);
        buttonBarBuilder.add(saveToNodeButton).xy(7, 1);
        buttonBarBuilder.add(clearAllOnNodeButton).xy(9, 1);
        JPanel buttons = buttonBarBuilder.build();
        dialogBuilder.add(buttons).xyw(1, 1, 5);

        // add content

        this.accessoryListModel = new ArrayListModel<>();

        this.accessoryAspectTreeTableModel = new AccessoryAspectTreeTableModel<>(accessoryListModel);

        this.accessorySortableTreeTableModel = new AccessorySortableTreeTableModel(this.accessoryAspectTreeTableModel);

        final BiConsumer writeConsumer = (aspectRow, action) -> {

            LOGGER.info("Write current aspectRow: {}, action: {}", aspectRow, action);

            final List configVariables = new LinkedList<>();

            prepareCv(configVariables, aspectRow, action);

            if (CollectionUtils.isNotEmpty(configVariables)) {

                if (MappingAction.read == action) {
                    final AccessoryAspectMapping accessoryAspectMapping =
                        controllerListener.readAccessoryMapping(configVariables);
                    applyDccAccessoryAspectMapping(accessoryAspectMapping);
                }
                else {
                    final AccessoryAspectMapping accessoryAspectMapping =
                        controllerListener.writeAccessoryMapping(configVariables);
                    applyDccAccessoryAspectMapping(accessoryAspectMapping);
                }
            }
            else {
                LOGGER.warn("No configuration variables to write available.");

                throw new InvalidMappingException("Action failed.");
            }
        };

        this.treeTable = new AspectMappingTreeTable(accessorySortableTreeTableModel, writeConsumer);
        this.treeTable.setSortable(true);

        // JTableHeader header = this.treeTable.getTableHeader();
        final AutoFilterTableHeader header = new AutoFilterTableHeader(this.treeTable) {
            private static final long serialVersionUID = 1L;
        };
        header.setAutoFilterEnabled(true);
        header.setUseNativeHeaderRenderer(true);
        this.treeTable.setTableHeader(header);

        this.treeTable.adjustRowHeight();

        JideSwingUtilities.insertMouseListener(header, new MouseAdapter() {
            @Override
            public void mouseClicked(MouseEvent e) {
                if (e.getSource() instanceof CellStyleTableHeader) {
                    CellStyleTableHeader _header = (CellStyleTableHeader) e.getSource();
                    Point p = e.getPoint();
                    int index = _header.originalColumnAtPoint(p);
                    if (_header.getTable() != null && index == 0 && p.x < 20) {
                        LOGGER.info("Collapse/Expand all items.");

                        boolean isCollapsed = accessorySortableTreeTableModel.isCollapsed();
                        accessorySortableTreeTableModel.setCollapsed(!isCollapsed);
                        if (isCollapsed) {
                            treeTable.expandAll();
                        }
                        else {
                            treeTable.collapseAll();
                        }
                        // prevent change sort order
                        e.consume();
                    }
                }
            }
        }, 0);

        final String[] dccAspectNames =
            new String[] { Resources.getString(NodesClientView.class, "aspect-0"),
                Resources.getString(NodesClientView.class, "aspect-1") };

        ObjectConverterManager
            .registerConverter(Integer.class, new DccAspectConverter(dccAspectNames), DccAspectConverter.CONTEXT);

        CellEditorManager
            .registerEditor(Integer.class, () -> new DccAspectCellEditor(dccAspectNames), DccAspectCellEditor.CONTEXT);

        final DefaultTableCellRenderer dccAspectCellRenderer = new DefaultTableCellRenderer() {
            private static final long serialVersionUID = 1L;

            @Override
            protected void setValue(Object value) {
                if (value instanceof Integer) {
                    Integer aspectNumber = (Integer) value;
                    value = dccAspectNames[aspectNumber];
                }
                setText((value == null) ? "" : value.toString());
            }
        };
        CellRendererManager.registerRenderer(DccAspect.class, dccAspectCellRenderer, DccAspectCellEditor.CONTEXT);

        CellEditorManager.registerEditor(DccAccessory.class, () -> {
            IntegerCellEditor editor = new IntegerCellEditor();
            editor.setMinInclusive(1);
            editor.setMaxInclusive(31);
            return editor;
        });

        final DefaultTableCellRenderer dccAccessoryCellRenderer = new DefaultTableCellRenderer();
        dccAccessoryCellRenderer.setHorizontalAlignment(JLabel.TRAILING);
        CellRendererManager.registerRenderer(DccAccessory.class, dccAccessoryCellRenderer);

        CellRendererManager.registerRenderer(MappingAction.class, new ButtonsCellEditorRenderer());
        CellEditorManager.registerEditor(MappingAction.class, () -> new ButtonsCellEditorRenderer());

        // popup menu
        final AspectMappingTableMenu aspectMappingTableMenu = new AspectMappingTableMenu(this);
        final TablePopupMenuInstaller installer = new TablePopupMenuInstaller(this.treeTable) {
            @Override
            protected JPopupMenu createPopupMenu() {
                return aspectMappingTableMenu;
            }

            @Override
            protected void customizeMenuItems(JTable table, JPopupMenu popup, int clickingRow, int clickingColumn) {
                LOGGER.info("Customize the popup menu, clickingRow: {}", clickingRow);

                super.customizeMenuItems(table, popup, clickingRow, clickingColumn);

                popup.removeAll();

                if (clickingRow > -1) {

                    table.setRowSelectionInterval(clickingRow, clickingRow);
                    SortableTreeTableModel actualFilterableTableModel =
                        (SortableTreeTableModel) TableModelWrapperUtils
                            .getActualTableModel(NodesClientView.this.treeTable.getModel(),
                                SortableTreeTableModel.class);

                    final Row row = actualFilterableTableModel.getRowAt(clickingRow);
                    if (row instanceof AspectRow) {
                        popup.add(aspectMappingTableMenu.getAddMappingItem((AspectRow) row));
                        popup.add(aspectMappingTableMenu.getDeleteMappingItem((AspectRow) row));
                    }
                }
            }
        };

        final TableColumn column =
            this.treeTable.getColumnModel().getColumn(AccessoryAspectTreeTableModel.COLUMN_ACTION);

        int columnWidth = 80;
        column.setPreferredWidth(columnWidth);
        column.setMaxWidth(columnWidth);
        column.setMinWidth(columnWidth);

        // add searchable feature
        final TableSearchable searchable = new TableSearchable(this.treeTable) {
            @Override
            protected String convertElementToString(Object item) {
                if (item instanceof AccessoryRow) {
                    return ((AccessoryRow) item).getName();
                }
                return super.convertElementToString(item);
            }
        };
        searchable.setMainIndex(0); // only search for name column

        RolloverTableUtils.install(this.treeTable);

        JScrollPane scrollTree = new JScrollPane(treeTable);

        JideScrollPane scrollPane = new JideScrollPane(scrollTree);

        this.overlayTable = new DefaultOverlayable(scrollPane);

        this.progressPanel = new InfiniteProgressPanel(3) {
            private static final long serialVersionUID = 1L;

            @Override
            public Dimension getPreferredSize() {
                return new Dimension(60, 60);
            }
        };
        overlayTable.addOverlayComponent(progressPanel);
        progressPanel.stop();
        overlayTable.setOverlayVisible(true);

        dialogBuilder.add(overlayTable).xyw(1, 3, 5);

        this.contentPanel = dialogBuilder.build();

        return this.contentPanel;
    }

    @Override
    public DockKey getDockKey() {
        return DockKeys.DOCKKEY_NODES_CLIENT_VIEW;
    }

    @Override
    public Component getComponent() {
        return this.contentPanel;
    }

    private void createToolbarButtons() {

        // load button
        loadFromFileButton =
            ButtonUtils
                .makeNavigationButton("loadfromfile.png", "/16x16", LOAD.toLowerCase(),
                    Resources.getString(getClass(), "button.loadfromfile"),
                    Resources.getString(getClass(), "button.loadfromfile.alttext"));
        loadFromFileButton.setEnabled(true);

        loadFromFileButton.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                loadAccessoryMappingFromFile();
            }
        });

        // save button
        saveToFileButton =
            ButtonUtils
                .makeNavigationButton("savetofile.png", "/16x16", SAVE.toLowerCase(),
                    Resources.getString(getClass(), "button.savetofile"),
                    Resources.getString(getClass(), "button.savetofile.alttext"));
        saveToFileButton.setEnabled(true);

        saveToFileButton.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                saveAccessoryMapping();
            }
        });

        // load button
        loadFromNodeButton =
            ButtonUtils
                .makeNavigationButton("loadfromnode.png", "/16x16", LOAD.toLowerCase(),
                    Resources.getString(getClass(), "button.loadfromnode"),
                    Resources.getString(getClass(), "button.loadfromnode.alttext"));
        loadFromNodeButton.setEnabled(false);

        loadFromNodeButton.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                loadAccessoryMappingFromNode();
            }
        });

        // save button
        saveToNodeButton =
            ButtonUtils
                .makeNavigationButton("savetonode.png", "/16x16", SAVE.toLowerCase(),
                    Resources.getString(getClass(), "button.savetonode"),
                    Resources.getString(getClass(), "button.savetonode.alttext"));
        saveToNodeButton.setEnabled(false);

        saveToNodeButton.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                saveAccessoryMappingToNode();
            }
        });

        // clearAll button
        clearAllOnNodeButton =
            ButtonUtils
                .makeNavigationButton("clearallonnode.png", "/16x16", CLEAR_ALL.toLowerCase(),
                    Resources.getString(getClass(), "button.clearallonnode"),
                    Resources.getString(getClass(), "button.clearallonnode.alttext"));
        clearAllOnNodeButton.setEnabled(false);

        clearAllOnNodeButton.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                clearAccessoryMappingOnNode();
            }
        });

    }

    public void setNodeAccessSupported(boolean nodeAccessSupported) {

        if (saveToNodeButton != null) {
            saveToNodeButton.setEnabled(nodeAccessSupported);
        }
        if (loadFromNodeButton != null) {
            loadFromNodeButton.setEnabled(nodeAccessSupported);
        }
        if (clearAllOnNodeButton != null) {
            clearAllOnNodeButton.setEnabled(nodeAccessSupported);
        }
    }

    /**
     * Prepare the model.
     * 
     * @param nodeProvider
     *            the node provider
     */
    public void prepareModel(final NodeProvider nodeProvider) {

        LOGGER.info("Prepare the model, nodeProvider: {}", nodeProvider);

        final List accessoryNodes = new LinkedList<>();

        if (nodeProvider != null) {
            for (NodeInterface node : nodeProvider.getNodes()) {
                if (node.hasAccessories()) {

                    AccessoryNode accessoryNode = createAccessoryNode(node);
                    accessoryNodes.add(accessoryNode);
                }
            }
        }

        // remove all existing items
        this.accessoryListModel.clear();
        this.accessorySortableTreeTableModel.setCollapsed(true);

        for (AccessoryNode accessoryNode : accessoryNodes) {

            LOGGER.info("Add accessoryNode: {}", accessoryNode);
            // prepare the rows
            NodeRow nodeRow = new NodeRow(accessoryNode);

            this.accessoryAspectTreeTableModel.addRow(nodeRow);

            for (NodesAccessoryAspect accessoryAspect : accessoryNode.getAccessories()) {
                AccessoryRow accessoryRow = new AccessoryRow(accessoryAspect);
                this.accessoryAspectTreeTableModel.addRow(nodeRow, accessoryRow);

                if (CollectionUtils.isEmpty(accessoryAspect.getAspects())) {
                    LOGGER.warn("No aspects available.");

                    for (int aspectNumber = 0; aspectNumber < accessoryAspect.getTotalAspects(); aspectNumber++) {
                        AspectRow aspectRow = new AspectRow(accessoryAspect, aspectNumber, null);
                        this.accessoryAspectTreeTableModel.addRow(accessoryRow, aspectRow);
                    }

                }
                else {

                    int aspectNumber = 0;
                    for (AccessoryAspect aspect : accessoryAspect.getAspects()) {
                        AspectRow aspectRow = new AspectRow(accessoryAspect, aspectNumber, aspect);
                        this.accessoryAspectTreeTableModel.addRow(accessoryRow, aspectRow);

                        aspectNumber++;
                    }
                }
            }
        }

        overlayTable.setOverlayVisible(false);
    }

    private AccessoryNode createAccessoryNode(final NodeInterface node) {

        List accessoryAspects = new LinkedList<>();
        for (Accessory accessory : node.getAccessories()) {

            AccessoryLabel accessoryLabel = getAccessoryAspectsLabels(node, accessory.getId());

            final List aspectList = new ArrayList<>();
            if (CollectionUtils.isNotEmpty(accessory.getAspects())) {
                // prepare the aspects of the selected accessory
                // get all macros
                List macros = node.getMacros();

                MacroRef[] aspects = accessory.getAspects().toArray(new MacroRef[0]);
                for (int row = 0; row < aspects.length; row++) {
                    // get the number of the macro of the selected aspect

                    MacroRef currentMacro = aspects[row];
                    if (currentMacro != null && currentMacro.getId() != null) {
                        final int macroNumber = currentMacro.getId();

                        LOGGER.info("Adding new aspect, row: {}, macroNumber: {}", row, macroNumber);

                        if (macroNumber > -1 && macroNumber < macros.size()) {
                            AccessoryAspectMacro accessoryAspect = new AccessoryAspectMacro(row, currentMacro);

                            // try to get the user-defined label
                            accessoryAspect.setLabel(getAccessoryAspectLabel(accessoryLabel, row));

                            aspectList.add(accessoryAspect);
                        }
                    }
                }

            }
            else if (accessory.getTotalAspects() > 0) {
                int totalAspects = accessory.getTotalAspects();

                if (totalAspects > 0) {
                    for (int row = 0; row < totalAspects; row++) {
                        AccessoryAspectMacro accessoryAspect = new AccessoryAspectMacro(row, null);

                        // try to get the user-defined label
                        accessoryAspect.setLabel(getAccessoryAspectLabel(accessoryLabel, row));

                        // prepare AccessoryAspects for non macro mapped nodes
                        accessoryAspect.setImmutableAccessory(true);

                        aspectList.add(accessoryAspect);
                    }
                }
            }
            else {
                LOGGER.warn("No aspects available for accessory: {}", accessory);
            }

            NodesAccessoryAspect accessoryAspect =
                new NodesAccessoryAspect(node, accessory, accessory.getTotalAspects(), aspectList);
            accessoryAspects.add(accessoryAspect);
        }

        AccessoryNode accessoryNode = new AccessoryNode(node, accessoryAspects);

        return accessoryNode;
    }

    private NodeLabels getNodeLabels(final NodeInterface node) {
        final WizardLabelFactory wizardLabelFactory = wizardLabelWrapper.getWizardLabelFactory();

        NodeLabels nodeLabels = wizardLabelFactory.loadLabels(node.getUniqueId());
        return nodeLabels;
    }

    public AccessoryLabel getAccessoryAspectsLabels(final NodeInterface node, int accessoryId) {

        NodeLabels nodeLabels = getNodeLabels(node);
        if (nodeLabels == null) {
            LOGGER.warn("No node labels avaialble.");
            return null;
        }

        return AccessoryLabelUtils.getAccessoryLabel(nodeLabels, accessoryId);
    }

    private String getAccessoryAspectLabel(AccessoryLabel labels, int aspectIndex) {

        if (labels != null) {
            BaseLabel baseLabel = AccessoryLabelUtils.getAccessoryAspectLabel(labels, aspectIndex);
            if (baseLabel != null) {
                String labelString = baseLabel.getLabel();
                LOGGER.info("Found the aspect label: {}", labelString);
                return labelString;
            }
        }
        return null;
    }

    private List collectAspectRows(
        final AccessoryAspectTreeTableModel treeTableModel) {
        List aspectRows = new LinkedList<>();

        List rows = treeTableModel.getRows();
        for (DefaultExpandableRow row : rows) {

            LOGGER.info("Current row: {}", row);

            if (row instanceof AspectRow) {
                LOGGER.info("Current row is a aspect row: {}", row);

                final AspectRow aspectRow = (AspectRow) row;
                if (aspectRow.hasValidMapping()) {
                    aspectRows.add(aspectRow);
                }
            }
        }
        return aspectRows;
    }

    private AccessoryAspectTreeTableModel getTreeTableModel() {
        TableModel model = this.treeTable.getModel();
        if (model instanceof FilterableTreeTableModel) {
            model = ((FilterableTreeTableModel) model).getActualModel();
        }
        if (model instanceof SortableTreeTableModel) {
            model = ((SortableTreeTableModel) model).getActualModel();
        }
        if (model instanceof AccessoryAspectTreeTableModel) {
            AccessoryAspectTreeTableModel treeTableModel =
                (AccessoryAspectTreeTableModel) model;
            return treeTableModel;
        }

        throw new IllegalArgumentException("The tableModel is not a AccessoryAspectTreeTableModel.");
    }

    private void saveAccessoryMapping() {

        final List aspectRows = collectAspectRows(getTreeTableModel());

        if (!aspectRows.isEmpty()) {

            final ObjectMapper objectMapper = new ObjectMapper().registerModule(new JavaTimeModule());

            final AccessoryAspectMapping accessoryAspectMapping = new AccessoryAspectMapping();

            for (final AspectRow aspectRow : aspectRows) {

                // save the mapping
                DccAccessoryAspectMapping dccAccessoryAspectMapping = new DccAccessoryAspectMapping();
                DccAccessoryAspect dccAccessoryAspect =
                    new DccAccessoryAspect()
                        .withDccAccessoryAddress(aspectRow.getDccAccessoryAddress())
                        .withAspectNumber(aspectRow.getDccAspect());
                dccAccessoryAspectMapping.setDccAccessoryAspect(dccAccessoryAspect);

                String uniqueId = ByteUtils.formatHexUniqueId(aspectRow.getUniqueId());
                BidibAccessoryAspect bidibAccessoryAspect =
                    new BidibAccessoryAspect()
                        .withUniqueId(uniqueId).withAccessoryNumber(aspectRow.getAccessoryNumber())
                        .withAspectNumber(aspectRow.getAspectNumber()).withAccessoryLabel(aspectRow.getAccessoryLabel())
                        .withAspectLabel(aspectRow.getAspectLabel());
                dccAccessoryAspectMapping.setBidibAccessoryAspect(bidibAccessoryAspect);
                accessoryAspectMapping.getMappings().add(dccAccessoryAspectMapping);
            }

            try {
                final String mapping =
                    objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(accessoryAspectMapping);
                LOGGER.info("The mapping: {}", mapping);

                Frame parentComponent = JideSwingUtilities.getFrame(this.contentPanel);
                String storedWorkingDirectory = this.miscSettings.getNodesConfigDir();

                FileDialog dialog =
                    new FileDialog(parentComponent, FileDialog.SAVE, storedWorkingDirectory,
                        "accessoryNodesMapping" + "." + JSON_EXTENSION, getJsonFileFilter()) {

                        @Override
                        public void approve(String fileName) {

                            LOGGER.info("Save accessory nodes mapping, fileName: {}", fileName);

                            final File file = new File(fileName);

                            try (FileOutputStream fos = new FileOutputStream(file)) {

                                IOUtils.write(mapping, fos, StandardCharsets.UTF_8);
                                fos.flush();
                            }
                            catch (IOException ex) {
                                LOGGER.warn("Write accessory node mapping to file failed.", ex);

                                // TODO show dialog
                            }
                        }
                    };
                dialog.showDialog();
            }
            catch (JsonProcessingException ex) {
                LOGGER.warn("Convert to JSON failed.", ex);
            }
        }
    }

    private void loadAccessoryMappingFromFile() {

        Frame parentComponent = JideSwingUtilities.getFrame(this.contentPanel);
        String storedWorkingDirectory = this.miscSettings.getNodesConfigDir();

        FileDialog dialog =
            new FileDialog(parentComponent, FileDialog.OPEN, storedWorkingDirectory,
                "accessoryNodesMapping" + "." + JSON_EXTENSION, getJsonFileFilter()) {
                @Override
                public void approve(String selectedFile) {
                    File file = new File(selectedFile);

                    try (FileInputStream fis = new FileInputStream(file)) {

                        final ObjectMapper objectMapper =
                            new ObjectMapper().registerModule(new Jdk8Module()).registerModule(new JavaTimeModule());

                        clearAllDccAccessoryAspectMapping();

                        final AccessoryAspectMapping accessoryAspectMapping =
                            objectMapper.readValue(fis, AccessoryAspectMapping.class);
                        LOGGER.info("Loaded accessoryAspectMapping: {}", accessoryAspectMapping);

                        NodesClientView.this.miscSettings.setNodesConfigDir(file.getParent());

                        applyDccAccessoryAspectMapping(accessoryAspectMapping);

                    }
                    catch (Exception ex) {
                        LOGGER.warn("Open file failed.", ex);
                    }
                }
            };

        dialog.showDialog();
    }

    private static final String JSON_EXTENSION = "json";

    private final String accessoryNodesMappingDescription = "Accessory Nodes Mapping";

    private FileFilter getJsonFileFilter() {

        FileFilter nodeFilter = new FileNameExtensionFilter(accessoryNodesMappingDescription, JSON_EXTENSION);
        return nodeFilter;
    }

    private void saveAccessoryMappingToNode() {
        final List aspectRows = collectAspectRows(getTreeTableModel());

        if (!aspectRows.isEmpty()) {
            LOGGER.info("Write the aspect mapping to the node.");

            final List configVariables = new LinkedList<>();

            for (final AspectRow aspectRow : aspectRows) {
                prepareCv(configVariables, aspectRow, MappingAction.create);
            }

            final AccessoryAspectMapping accessoryAspectMapping =
                controllerListener.writeAccessoryMapping(configVariables);
            applyDccAccessoryAspectMapping(accessoryAspectMapping);
        }
    }

    private void clearAccessoryMappingOnNode() {
        LOGGER.info("Delete all aspect mappings on the node.");

        final List configVariables = new LinkedList<>();

        String aspectMappingWriteCommand = NodesClientControllerListener.MAPPING_AMR_ALL;
        String aspectMappingAction = NodesClientControllerListener.MAPPING_DELETE;

        final ConfigurationVariable cv = new ConfigurationVariable(aspectMappingWriteCommand, aspectMappingAction);
        configVariables.add(cv);

        final Consumer resultCallback = successful -> {
            // show a dialog
            if (successful) {
                TaskDialogs
                    .inform(JOptionPane.getFrameForComponent(desktop),
                        Resources.getString(NodesClientView.class, "delete-all-mappings-passed.instruction"),
                        Resources.getString(NodesClientView.class, "delete-all-mappings-passed.text"));
            }
            else {
                TaskDialogs
                    .error(JOptionPane.getFrameForComponent(desktop),
                        Resources.getString(NodesClientView.class, "delete-all-mappings-failed.instruction"),
                        Resources.getString(NodesClientView.class, "delete-all-mappings-failed.text"));
            }
        };

        final AccessoryAspectMapping accessoryAspectMapping = controllerListener.writeAccessoryMapping(configVariables);
        evaluateRangeDeleteAll(accessoryAspectMapping, resultCallback);
    }

    private void prepareCv(
        final List configVariables, final AspectRow aspectRow, final MappingAction action) {
        long uniqueId = aspectRow.getUniqueId();
        Integer accessoryNumber = aspectRow.getAccessoryNumber();
        Integer aspectNumber = aspectRow.getAspectNumber();
        Integer dccAddress = aspectRow.getDccAccessoryAddress();
        Integer dccAspect = aspectRow.getDccAspect();

        if (MappingAction.read == action) {

        }
        else {
            if (aspectRow.hasValidMapping()) {
                // prepare the aspect mapping write
                String aspectMappingValue =
                    AspectMappingUtils
                        .prepareAspectMapping(uniqueId, accessoryNumber, aspectNumber, dccAddress, dccAspect);

                String aspectMappingWriteCommand =
                    NodesClientControllerListener.MAPPING_PREFIX_AME + aspectMappingValue;

                String aspectMappingAction =
                    action == MappingAction.delete ? NodesClientControllerListener.MAPPING_DELETE
                        : NodesClientControllerListener.MAPPING_CREATE;

                ConfigurationVariable cv = new ConfigurationVariable(aspectMappingWriteCommand, aspectMappingAction);
                configVariables.add(cv);
            }
            else {
                LOGGER.info("The mapping of the aspect row is not valid: {}", aspectRow);
            }
        }
    }

    private void loadAccessoryMappingFromNode() {
        LOGGER.info("Load the accessory mapping from the node.");

        // read the range for all entries from the node
        clearAllDccAccessoryAspectMapping();

        String aspectMappingReadRangeCommand = NodesClientControllerListener.MAPPING_AMR_ALL;
        final List configVariables = new LinkedList<>();
        ConfigurationVariable cv = new ConfigurationVariable(aspectMappingReadRangeCommand, null);
        configVariables.add(cv);

        final AccessoryAspectMapping accessoryAspectMapping = controllerListener.readAccessoryMapping(configVariables);
        applyDccAccessoryAspectMapping(accessoryAspectMapping);
    }

    /**
     * Clear all dcc accessory aspect mapping rows.
     */
    private void clearAllDccAccessoryAspectMapping() {

        for (DefaultExpandableRow row : accessoryListModel) {
            if (row instanceof NodeRow) {
                final NodeRow nodeRow = (NodeRow) row;

                if (nodeRow.hasChildren()) {
                    List nodeChildren = (List) nodeRow.getChildren();
                    for (Row nodeRowChild : nodeChildren) {
                        if (nodeRowChild instanceof AccessoryRow) {
                            final AccessoryRow accessoryRow = (AccessoryRow) nodeRowChild;

                            List accessoryChildren = (List) accessoryRow.getChildren();

                            if (accessoryChildren != null) {
                                final List saveAccessoryChildren = new ArrayList<>(accessoryChildren);

                                AspectRow prevAspectRow = null;

                                for (Row accessoryRowChild : saveAccessoryChildren) {
                                    if (accessoryRowChild instanceof AspectRow) {
                                        final AspectRow aspectRow = (AspectRow) accessoryRowChild;

                                        if (prevAspectRow != null
                                            && prevAspectRow.getAspectNumber().equals(aspectRow.getAspectNumber())
                                            && prevAspectRow
                                                .getAccessoryNumber().equals(aspectRow.getAccessoryNumber())) {
                                            // duplicate row found --> remove it
                                            LOGGER.info("Remove duplicate row: {}", aspectRow);
                                            accessoryRow.removeChild(aspectRow);
                                        }
                                        else {
                                            // set the dcc address and dcc aspect to null
                                            aspectRow
                                                .setValueAt(null, AccessoryAspectTreeTableModel.COLUMN_DCC_ADDRESS);
                                            aspectRow.setValueAt(null, AccessoryAspectTreeTableModel.COLUMN_ASPECT);
                                            aspectRow.setDirty(false);

                                            // keep the previous aspect row
                                            prevAspectRow = aspectRow;
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }

    private void evaluateRangeDeleteAll(
        final AccessoryAspectMapping accessoryAspectMapping, final Consumer resultCallback) {
        if (accessoryAspectMapping.getMappings().size() == 1) {
            final DccAccessoryAspectMapping mapping = accessoryAspectMapping.getMappings().get(0);
            if (mapping.getAdditionalProperties().size() == 1) {
                for (Entry entry : mapping.getAdditionalProperties().entrySet()) {
                    if (NodesClientControllerListener.MAPPING_AMR_ALL.equals(entry.getKey())
                        && NodesClientControllerListener.MAPPING_DELETE.equals(entry.getValue())) {
                        LOGGER.info("Range delete all passed.");
                        resultCallback.accept(Boolean.TRUE);
                    }
                    else {
                        LOGGER.info("Range delete all failed.");
                        resultCallback.accept(Boolean.FALSE);
                    }
                }
            }
            else {
                LOGGER.warn("Received invalid result for range delete all.");
            }
        }
        else {
            LOGGER.warn("Received invalid result for range delete all.");
        }
    }

    private void applyDccAccessoryAspectMapping(final AccessoryAspectMapping accessoryAspectMapping) {

        AspectRow firstMappedAspectRow = null;

        for (DccAccessoryAspectMapping dccAccessoryAspectMapping : accessoryAspectMapping.getMappings()) {
            final BidibAccessoryAspect bidibAccessoryAspect = dccAccessoryAspectMapping.getBidibAccessoryAspect();
            final DccAccessoryAspect dccAccessoryAspect = dccAccessoryAspectMapping.getDccAccessoryAspect();

            if (bidibAccessoryAspect == null) {
                LOGGER.info("Skip current dccAccessoryAspectMapping: {}", dccAccessoryAspectMapping);
                continue;
            }

            // search the node by uniqueId
            Long uniqueId = ByteUtils.parseHexUniqueId(bidibAccessoryAspect.getUniqueId());

            LOGGER.info("Apply current dccAccessoryAspectMapping: {}", dccAccessoryAspectMapping);

            for (DefaultExpandableRow row : accessoryListModel) {
                if (row instanceof NodeRow) {
                    final NodeRow nodeRow = (NodeRow) row;
                    if (NodeUtils.compareUniqueIdIgnoreClassbits(uniqueId, nodeRow.getUniqueId())) {

                        if (nodeRow.hasChildren()) {
                            List nodeChildren = (List) nodeRow.getChildren();
                            for (Row nodeRowChild : nodeChildren) {
                                if (nodeRowChild instanceof AccessoryRow) {
                                    final AccessoryRow accessoryRow = (AccessoryRow) nodeRowChild;

                                    if (Objects
                                        .equals(bidibAccessoryAspect.getAccessoryNumber(),
                                            accessoryRow.getAccessoryId())) {
                                        List accessoryChildren = (List) accessoryRow.getChildren();

                                        int aspectIndex = 0;
                                        for (Row accessoryRowChild : accessoryChildren) {
                                            if (accessoryRowChild instanceof AspectRow) {
                                                final AspectRow aspectRow = (AspectRow) accessoryRowChild;

                                                aspectIndex++;

                                                if (Objects
                                                    .equals(bidibAccessoryAspect.getAspectNumber(),
                                                        aspectRow.getAspectNumber())) {
                                                    // found the row to update
                                                    LOGGER.info("Found row to update: {}", aspectRow);

                                                    Integer dccAccessoryAddress =
                                                        dccAccessoryAspect.getDccAccessoryAddress();
                                                    Integer dccAspect = dccAccessoryAspect.getAspectNumber();

                                                    // check if a mapping is assigned already (dcc address and dcc
                                                    // aspect not null)
                                                    if (!aspectRow.hasValidMapping() || (aspectRow
                                                        .getDccAccessoryAddress().equals(dccAccessoryAddress)
                                                        && aspectRow.getDccAspect().equals(dccAspect))) {
                                                        LOGGER
                                                            .info("Update existing aspect row: {}, aspectIndex: {}",
                                                                aspectRow, aspectIndex);

                                                        aspectRow
                                                            .setValueAt(dccAccessoryAddress,
                                                                AccessoryAspectTreeTableModel.COLUMN_DCC_ADDRESS);
                                                        aspectRow
                                                            .setValueAt(dccAspect,
                                                                AccessoryAspectTreeTableModel.COLUMN_ASPECT);
                                                    }
                                                    else {
                                                        // create a new aspect row
                                                        final NodesAccessoryAspect accessoryAspect =
                                                            accessoryRow.getAccessoryAspect();
                                                        final AccessoryAspect aspect = aspectRow.getAspect();

                                                        final AspectRow newAspectRow =
                                                            new AspectRow(accessoryAspect, aspectRow.getAspectNumber(),
                                                                aspect);
                                                        newAspectRow
                                                            .setValueAt(dccAccessoryAddress,
                                                                AccessoryAspectTreeTableModel.COLUMN_DCC_ADDRESS);
                                                        newAspectRow
                                                            .setValueAt(dccAspect,
                                                                AccessoryAspectTreeTableModel.COLUMN_ASPECT);
                                                        LOGGER
                                                            .info("Insert new aspect row: {}, aspectIndex: {}",
                                                                newAspectRow, aspectIndex);
                                                        this.accessoryAspectTreeTableModel
                                                            .addRow(accessoryRow, aspectIndex, newAspectRow);

                                                        aspectIndex++;
                                                    }

                                                    aspectRow.setDirty(false);

                                                    if (firstMappedAspectRow == null) {
                                                        firstMappedAspectRow = aspectRow;
                                                    }

                                                    break;
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }

        treeTable.expandAll();

        if (firstMappedAspectRow != null) {
            final AccessoryAspectTreeTableModel treeTableModel = getTreeTableModel();

            int rowIndex = treeTableModel.getRowIndex(firstMappedAspectRow);
            treeTable.scrollRowToVisible(rowIndex);
        }

    }

    public void addMapping(final AspectRow row) {
        LOGGER.info("Add mapping for aspectRow: {}", row);

        final AccessoryRow accessoryRow = (AccessoryRow) row.getParent();

        int rowNumber = accessoryRow.getChildIndex(row);
        rowNumber++;

        final NodesAccessoryAspect accessoryAspect = accessoryRow.getAccessoryAspect();
        AccessoryAspect aspect = row.getAspect();

        final AspectRow aspectRow = new AspectRow(accessoryAspect, row.getAspectNumber(), aspect);
        this.accessoryAspectTreeTableModel.addRow(accessoryRow, rowNumber, aspectRow);
    }

    public void deleteMapping(AspectRow originalAspectRow) {
        LOGGER.info("Delete mapping for aspectRow: {}", originalAspectRow);

        final AccessoryRow accessoryRow = (AccessoryRow) originalAspectRow.getParent();

        // search all items with the same aspectNumber and check if the mapping is different
        // if the mapping is different -> delete the row
        // if the mapping is not different -> clear the mapping
        List accessoryChildren = (List) accessoryRow.getChildren();

        if (accessoryChildren != null) {
            final List saveAccessoryChildren = new ArrayList<>(accessoryChildren);
            final List matchingAspectRows = new ArrayList<>();

            for (Row accessoryRowChild : saveAccessoryChildren) {
                if (accessoryRowChild instanceof AspectRow) {
                    final AspectRow aspectRow = (AspectRow) accessoryRowChild;

                    if (originalAspectRow != null
                        && originalAspectRow.getAspectNumber().equals(aspectRow.getAspectNumber())
                        && originalAspectRow.getAccessoryNumber().equals(aspectRow.getAccessoryNumber())) {
                        // duplicate row found --> add it to the list
                        LOGGER.info("Found duplicate row: {}", aspectRow);
                        matchingAspectRows.add(aspectRow);
                    }
                }
            }

            if (matchingAspectRows.size() == 1) {
                originalAspectRow.setValueAt(null, AccessoryAspectTreeTableModel.COLUMN_DCC_ADDRESS);
                originalAspectRow.setValueAt(null, AccessoryAspectTreeTableModel.COLUMN_ASPECT);
                originalAspectRow.setDirty(false);
            }
            else if (matchingAspectRows.size() > 1) {
                accessoryRow.removeChild(originalAspectRow);
            }
        }

    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy