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

org.bidib.wizard.mvc.main.model.MainModel Maven / Gradle / Ivy

There is a newer version: 2.0.29
Show newest version
package org.bidib.wizard.mvc.main.model;

import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;

import javax.swing.SwingUtilities;

import org.bidib.api.json.types.NodeInfo.NodeAction;
import org.bidib.jbidibc.core.schema.bidibbase.DefaultLabelsActionType;
import org.bidib.jbidibc.core.schema.bidiblabels.NodeLabels;
import org.bidib.jbidibc.messages.Node;
import org.bidib.jbidibc.messages.SoftwareVersion;
import org.bidib.jbidibc.messages.utils.ByteUtils;
import org.bidib.wizard.api.event.DefaultLabelsWorkListItemEvent;
import org.bidib.wizard.api.event.WorkListItemEvent;
import org.bidib.wizard.api.model.Accessory;
import org.bidib.wizard.api.model.AccessorySaveState;
import org.bidib.wizard.api.model.Flag;
import org.bidib.wizard.api.model.Macro;
import org.bidib.wizard.api.model.MacroSaveState;
import org.bidib.wizard.api.model.NodeInterface;
import org.bidib.wizard.api.model.NodeListProvider;
import org.bidib.wizard.api.model.NodeProvider;
import org.bidib.wizard.api.model.listener.NodeErrorListener;
import org.bidib.wizard.api.model.listener.NodeListListener;
import org.bidib.wizard.api.model.listener.NodeSelectionListener;
import org.bidib.wizard.api.notification.NodeUpdate;
import org.bidib.wizard.api.service.console.ConsoleColor;
import org.bidib.wizard.api.service.console.ConsoleService;
import org.bidib.wizard.api.utils.XmlLocaleUtils;
import org.bidib.wizard.client.common.controller.NodeSelectionProvider;
import org.bidib.wizard.client.common.uils.SwingUtils;
import org.bidib.wizard.common.labels.BidibLabelUtils;
import org.bidib.wizard.common.labels.DefaultWizardLabelFactory.DefaultLabelsApplied;
import org.bidib.wizard.common.labels.DefaultWizardLabelFactory.VersionedDefaultNodeLabelsWrapper;
import org.bidib.wizard.common.labels.WizardLabelWrapper;
import org.bidib.wizard.common.node.Node.ErrorStatePropertyChange;
import org.bidib.wizard.mvc.console.controller.ConsoleController;
import org.bidib.wizard.mvc.main.model.listener.AccessorySelectionListener;
import org.bidib.wizard.mvc.main.model.listener.MacroSelectionListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.util.Assert;

import io.reactivex.rxjava3.core.Observable;
import io.reactivex.rxjava3.core.Observer;
import io.reactivex.rxjava3.disposables.Disposable;

public class MainModel implements NodeSelectionProvider, NodeListProvider {
    private static final Logger LOGGER = LoggerFactory.getLogger(MainModel.class);

    private final List macroSelectionListeners = new LinkedList<>();

    private List accessorySelectionListeners = new LinkedList<>();

    private final List nodeListListeners = new LinkedList<>();

    private final List nodeSelectionListeners = new LinkedList<>();

    // the node provider
    private NodeProvider nodeProvider;

    private Accessory selectedAccessory;

    private Macro selectedMacro;

    private NodeInterface selectedNode;

    private final String connectionId;

    private Object nodeLock = new Object();

    private final StatusModel statusModel;

    private NodeErrorListener nodeErrorListener;

    private AtomicBoolean initialLoadFinished = new AtomicBoolean(false);

    @Autowired
    private WizardLabelWrapper wizardLabelWrapper;

    @Autowired
    private ConsoleService consoleService;

    private final ApplicationEventPublisher applicationEventPublisher;

    public MainModel(final StatusModel statusModel, final String connectionId,
        final ApplicationEventPublisher applicationEventPublisher) {

        this.statusModel = statusModel;
        this.connectionId = connectionId;
        this.applicationEventPublisher = applicationEventPublisher;
    }

    /**
     * @return the connection id
     */
    public String getConnectionId() {
        return this.connectionId;
    }

    public void setNodeProvider(final NodeProvider nodeProvider) {
        LOGGER.info("Set the node provider: {}", nodeProvider);
        this.nodeProvider = nodeProvider;

        this.nodeProvider.subscribeNodeListChanges(new Observer() {

            @Override
            public void onSubscribe(Disposable disposable) {
                LOGGER.info("Subscribe to nodeProvider passed, disposable.isDisposed: {}", disposable.isDisposed());
            }

            @Override
            public void onNext(final NodeUpdate nodeUpdate) {
                LOGGER.info("Update received from node list: {}", nodeUpdate);

                SwingUtils.executeInEDT(() -> {
                    switch (nodeUpdate.getNodeAction()) {
                        case REMOVE:
                            handleNodeRemove(nodeUpdate);
                            break;
                        default:
                            handleNodeUpdate(nodeUpdate);
                            break;
                    }
                });
            }

            @Override
            public void onError(Throwable e) {
                LOGGER.warn("Subscription on nodeProvider signalled an error: {}", e);
            }

            @Override
            public void onComplete() {
                LOGGER.info("Subscription on nodeProvider signalled complete");
            }
        });

        this.nodeProvider.subscribeNodePropertyChanges(npu -> {
            LOGGER.debug("The property has changed: {}", npu);

            switch (npu.getProperty()) {
                case NodeInterface.PROPERTY_ADDRESSMESSAGESENABLED:
                    LOGGER.info("Address messsages enabled was changed.");
                    break;
                case NodeInterface.PROPERTY_ERROR_STATE:
                case NodeInterface.PROPERTY_REASON_DATA:
                    LOGGER.info("The error state was changed.");
                    if (nodeErrorListener != null) {

                        final ErrorStatePropertyChange propertyChange = npu.getValue(ErrorStatePropertyChange.class);
                        nodeErrorListener
                            .nodeErrorChanged(npu.getNode(), propertyChange.getSysError(),
                                propertyChange.getReasonData());
                    }
                    break;
                case NodeInterface.PROPERTY_NODE_PREFIX + Node.PROPERTY_STALL:
                    LOGGER.info("The stall state was changed.");
                    if (nodeErrorListener != null) {
                        nodeErrorListener.nodeStallChanged(npu.getNode(), npu.getValue(Boolean.class));
                    }
                    break;

                case NodeInterface.PROPERTY_NODE_PREFIX + Node.PROPERTY_SOFTWARE_VERSION:
                    LOGGER
                        .info("The firmware version of the node has changed: {}",
                            npu.getNode().getNode().getSoftwareVersion());

                    final NodeUpdate nodeUpdate = new NodeUpdate(connectionId, NodeAction.NOTIFY, npu.getNode());
                    this.applicationEventPublisher.publishEvent(nodeUpdate);

                    final NodeInterface updatedNode = npu.getNode();
                    SwingUtils.executeInEDT(() -> checkDefaultLabelsApplied(updatedNode));

                    break;

                default:
                    break;
            }

        }, error -> {
            LOGGER.warn("Subscription on nodeProvider for node property changes signalled an error: {}", error);
        });
    }

    private void handleNodeRemove(final NodeUpdate nodeUpdate) {
        NodeInterface currentNode = nodeUpdate.getNode();
        fireNodeListRemoved(currentNode);

        if (currentNode != null && currentNode.equals(getSelectedNode())) {
            LOGGER.info("The active node in model was removed: {}", getSelectedNode());
            setSelectedNode(null, true);
        }
        fireNodeListChanged();
    }

    private void handleNodeUpdate(final NodeUpdate nodeUpdate) {

        LOGGER.info("Node was updated: {}", nodeUpdate);

        // load the labels

        final NodeInterface node = nodeUpdate.getNode();

        if (nodeUpdate.getNodeAction() == NodeAction.ADD) {
            checkDefaultLabelsApplied(node);
        }

        if (nodeUpdate.getNodeAction() == NodeAction.ADD) {
            fireNodeListChanged();

            fireNodeListAdded(node);
        }
    }

    /**
     * Check if the default labels are applied for the provided node.
     * 
     * @param node
     *            the node
     */
    private void checkDefaultLabelsApplied(final NodeInterface node) {
        Long uniqueId = Long.valueOf(node.getUniqueId());
        final SoftwareVersion softwareVersion = node.getNode().getSoftwareVersion();

        // get the current default labels applied data for this node
        final DefaultLabelsApplied defaultLabelsApplied = wizardLabelWrapper.getDefaultLabelsApplied(uniqueId);
        LOGGER
            .info("The default labels have been applied for this node: {}, uniqueId: {}, softwareVersion: {}",
                defaultLabelsApplied, ByteUtils.formatHexUniqueId(uniqueId), softwareVersion);

        try {
            // and reload the labels
            reloadLabels(node);
        }
        catch (Exception ex) {
            LOGGER.warn("Load labels from BiDiB failed.", ex);

            // display warning in console
            SwingUtilities.invokeLater(() -> {
                ConsoleController.ensureConsoleVisible();

                // show an error message in the console
                consoleService
                    .addConsoleLine(ConsoleColor.red,
                        String
                            .format("Load lables failed for node with uniqueId: %s",
                                ByteUtils.getUniqueIdAsString(uniqueId)));
            });
        }

        // apply labels to node before apply default labels
        final NodeLabels nodeLabels = wizardLabelWrapper.loadLabels(uniqueId);

        SwingUtils.executeInEDT(() -> node.setLabel(BidibLabelUtils.getNodeLabel(nodeLabels)));

        // check if we must apply the default labels
        if (softwareVersion != null && defaultLabelsApplied != null
            && (!(DefaultLabelsActionType.APPLIED.equals(defaultLabelsApplied.getDefaultLabelsActionType())
                || DefaultLabelsActionType.IGNORED.equals(defaultLabelsApplied.getDefaultLabelsActionType()))
                || defaultLabelsApplied.getDefaultLabelsVersion() == null
                || softwareVersion.isHigherThan(defaultLabelsApplied.getDefaultLabelsVersion()))) {

            LOGGER
                .info(
                    "The default labels have not been applied or the version of applied labels is not available. Current softwareVersion: {}, uniqueId: {}",
                    softwareVersion, ByteUtils.formatHexUniqueId(uniqueId));

            int relevantPidBits = node.getNode().getRelevantPidBits();
            final String lang = XmlLocaleUtils.getXmlLocaleVendorCV();

            // check if default labels are available
            final Observable defaultLabelsAvailableObservable = Observable.create(emitter -> {
                try {

                    // compare the versions
                    final SoftwareVersion appliedVersion = defaultLabelsApplied.getDefaultLabelsVersion();

                    final VersionedDefaultNodeLabelsWrapper available =
                        wizardLabelWrapper.isDefaultLabelsAvailable(lang, uniqueId, softwareVersion, relevantPidBits);

                    LOGGER
                        .info("Default labels available: {}, appliedVersion: {}, uniqueId: {}", available,
                            appliedVersion, ByteUtils.formatHexUniqueId(uniqueId));

                    final SoftwareVersion availableVersion =
                        available != null ? available.getDefaultLabelsVersion() : null;

                    // we force apply once to get the applied version set and if the applied version is lower then we
                    // show the update
                    if ((appliedVersion == null && availableVersion != null)
                        || (appliedVersion != null && appliedVersion.isLowerThan(availableVersion))) {
                        emitter.onNext(Boolean.TRUE);
                    }
                    else {
                        emitter.onNext(Boolean.FALSE);
                    }

                    emitter.onComplete();
                }
                catch (Exception ex) {
                    LOGGER.warn("Check if defaultLabels are available failed.", ex);
                    emitter.onError(new RuntimeException("Check if defaultLabels are available failed."));
                }
            });

            defaultLabelsAvailableObservable.subscribe(available -> {
                LOGGER
                    .info("The defaultLabels for this node are available: {}, uniqueId: {}", available,
                        ByteUtils.formatHexUniqueId(uniqueId));

                if (available) {

                    // add item to work list
                    addWorkListItem(uniqueId, relevantPidBits);
                }
            }, error -> {
                LOGGER.warn("Check for default labels failed.", error);
            });
        }
    }

    private void addWorkListItem(final Long uniqueId, int relevantPidBits) {

        final WorkListItemEvent workListItemEvent =
            new DefaultLabelsWorkListItemEvent(ByteUtils.getUniqueIdAsString(uniqueId),
                WorkListItemEvent.Status.pending, this.connectionId, uniqueId, relevantPidBits);

        LOGGER.info("Publish new workListItemEvent: {}", workListItemEvent);

        this.applicationEventPublisher.publishEvent(workListItemEvent);
    }

    public void setNodeErrorListener(NodeErrorListener nodeErrorListener) {
        this.nodeErrorListener = nodeErrorListener;
    }

    /**
     * Clear the cached nodes.
     */
    public void clearNodes() {
        LOGGER.info("Clear the nodes and reset the selected node.");

        setSelectedNode(null, true);

        fireNodeListChanged();
    }

    @Override
    public Collection getNodes() {
        if (getNodeProvider() != null) {
            return getNodeProvider().getNodes();
        }
        return Collections.emptyList();
    }

    /**
     * @return the nodeProvider
     */
    @Override
    public NodeProvider getNodeProvider() {
        return nodeProvider;
    }

    @Override
    public void addNodeListListener(final NodeListListener listener) {
        LOGGER.info("Add the NodeListListener: {}", listener);
        nodeListListeners.add(listener);
    }

    @Override
    public void removeNodeListListener(final NodeListListener listener) {
        LOGGER.info("Remove the NodeListListener: {}", listener);

        boolean removed = nodeListListeners.remove(listener);
        Assert.isTrue(removed, "Remove NodeListListener failed: " + listener);
    }

    @Override
    public void addNodeSelectionListener(NodeSelectionListener l) {
        nodeSelectionListeners.add(l);
    }

    @Override
    public void removeNodeSelectionListener(NodeSelectionListener l) {
        nodeSelectionListeners.remove(l);
    }

    public void addMacroSelectionListener(MacroSelectionListener l) {
        macroSelectionListeners.add(l);
    }

    public void removeMacroSelectionListener(MacroSelectionListener l) {
        macroSelectionListeners.remove(l);
    }

    public void addAccessorySelectionListener(AccessorySelectionListener l) {
        accessorySelectionListeners.add(l);
    }

    public void removeAccessorySelectionListener(AccessorySelectionListener l) {
        accessorySelectionListeners.remove(l);
    }

    private void fireMacroSelectionChanged() {
        SwingUtils.executeInEDT(() -> {
            for (MacroSelectionListener l : macroSelectionListeners) {
                l.macroChanged();
            }
        });
    }

    private void fireAccessorySelectionChanged() {
        SwingUtils.executeInEDT(() -> {
            for (AccessorySelectionListener l : accessorySelectionListeners) {
                l.accessoryChanged();
            }
        });
    }

    private void fireSelectedNodeWillChange(final NodeInterface node) {
        final Collection listeners =
            new LinkedList<>(Collections.unmodifiableCollection(nodeListListeners));

        for (NodeListListener l : listeners) {
            l.nodeWillChange(node);
        }
    }

    private void fireSelectedNodeChanged(final NodeInterface node) {
        final Collection listeners =
            new LinkedList<>(Collections.unmodifiableCollection(nodeListListeners));

        for (NodeListListener l : listeners) {
            l.nodeChanged(node);
        }

        final Collection nodeSelectionListeners =
            Collections.unmodifiableCollection(this.nodeSelectionListeners);

        for (NodeSelectionListener l : nodeSelectionListeners) {
            l.selectedNodeChanged(selectedNode);
        }
    }

    private void fireNodeListChanged() {
        LOGGER.debug("Notify the listeners that the node list has changed.");
        final Collection listeners =
            new LinkedList<>(Collections.unmodifiableCollection(nodeListListeners));
        for (NodeListListener l : listeners) {
            l.listChanged();
        }
    }

    private void fireNodeListAdded(final NodeInterface node) {
        LOGGER.debug("Notify the listeners that the node list has added a node.");
        final Collection listeners =
            new LinkedList<>(Collections.unmodifiableCollection(nodeListListeners));

        for (NodeListListener l : listeners) {
            l.listNodeAdded(node);
        }
    }

    private void fireNodeListRemoved(final NodeInterface node) {
        LOGGER.debug("Notify the listeners that the node list has removed a node.");
        final Collection listeners =
            new LinkedList<>(Collections.unmodifiableCollection(nodeListListeners));

        for (NodeListListener l : listeners) {
            l.listNodeRemoved(node);
        }
    }

    private void fireNodeStateChanged(final NodeInterface node) {
        final Collection listeners =
            new LinkedList<>(Collections.unmodifiableCollection(nodeListListeners));

        for (NodeListListener l : listeners) {
            l.nodeStateChanged(node);
        }
    }

    public List getFlags() {
        NodeInterface node = getSelectedNode();
        if (node != null) {
            return node.getFlags();
        }
        LOGGER.warn("No node selected to get the flags from.");
        return Collections.emptyList();
    }

    @Override
    public NodeInterface getSelectedNode() {
        synchronized (nodeLock) {
            return selectedNode;
        }
    }

    public StatusModel getStatusModel() {
        return statusModel;
    }

    /**
     * Returns the currently selected macro.
     * 
     * @return the currently selected macro
     */
    public Macro getSelectedMacro() {
        return selectedMacro;
    }

    /**
     * Set the selected macro.
     * 
     * @param macro
     *            the selected macro
     */
    public void setSelectedMacro(Macro macro) {

        if (selectedMacro != null && MacroSaveState.PENDING_CHANGES.equals(selectedMacro.getMacroSaveState())) {
            // log warning
            LOGGER.warn("The current macro has pending changes.");
        }

        this.selectedMacro = macro;

        // notify the macro panel of the selection
        fireMacroSelectionChanged();
    }

    public List getMacros() {

        NodeInterface node = getSelectedNode();
        if (node != null) {
            List macros = node.getMacros();
            return macros;
        }
        LOGGER.warn("No node selected to get the macros from.");
        return Collections.emptyList();
    }

    /**
     * Replace the macro with the provided macro.
     * 
     * @param macro
     *            the new macro
     */
    public void replaceMacro(final Macro macro) {
        LOGGER.info("Replace the macro: {}", macro);

        NodeInterface node = getSelectedNode();
        if (node != null) {
            Macro replacedMacro = node.replaceMacro(macro, false);
            setSelectedMacro(replacedMacro);
            return;
        }

        // fire an exception
        throw new RuntimeException("Replace macro failed because no node is selected.");
    }

    /**
     * @return the selected accessory
     */
    public Accessory getSelectedAccessory() {
        return selectedAccessory;
    }

    /**
     * Set the selected accessory.
     * 
     * @param accessory
     *            the selected accessory
     */
    public void setSelectedAccessory(Accessory accessory) {
        if (selectedAccessory != null
            && (AccessorySaveState.PERMANENTLY_STORED_ON_NODE != selectedAccessory.getAccessorySaveState())) {
            // log warning
            LOGGER.warn("The current accessory has pending changes.");
        }

        this.selectedAccessory = accessory;

        // notify the accessory panel of the selection
        fireAccessorySelectionChanged();
    }

    public List getAccessories() {
        NodeInterface node = getSelectedNode();
        if (node != null) {
            List accessories = node.getAccessories();
            return accessories;
        }
        LOGGER.warn("No node selected to get the accessories from.");
        return Collections.emptyList();
    }

    public void replaceAccessory(Accessory accessory) {

        NodeInterface node = getSelectedNode();
        if (node != null) {
            Accessory replacedAccessory = node.replaceAccessory(accessory, false);
            setSelectedAccessory(replacedAccessory);
            return;
        }

        // fire an exception
        throw new RuntimeException("Replace accessory failed because no node is selected.");
    }

    /**
     * Set the selected node instance
     * 
     * @param node
     *            the selected node instance
     * @param forceChange
     *            force the change of the node
     */
    public void setSelectedNode(NodeInterface node, boolean forceChange) {
        LOGGER.info("Set the selected node in the main model: {}", node);

        synchronized (nodeLock) {

            if ((node != null && node.equals(selectedNode)) || (node == null && selectedNode == null)) {
                LOGGER.info("The selected node has not changed.");
                return;
            }

            if (!forceChange) {
                // allow the pending changes to prevent the change of the node
                fireSelectedNodeWillChange(node);
            }

            selectedNode = node;

            // clear the cached node values
            clearCachedNodeValues();
        }

        SwingUtils.executeInEDT(() -> fireSelectedNodeChanged(node));
    }

    private void clearCachedNodeValues() {
        LOGGER.info("Clear the cached node values.");

        setSelectedMacro(null);
        setSelectedAccessory(null);
    }

    private void reloadLabels(final NodeInterface node) {
        // load the labels of the node
        LOGGER.info("Load labels for node: {}", node);

        if (node == null) {
            LOGGER.info("No node selected to load the labels.");
            return;
        }

        // load all labels
        wizardLabelWrapper.loadLabels(node.getUniqueId());

        LOGGER.info("Finished load labels for node: {}", node);
    }

    public void setNodeHasError(final NodeInterface node, boolean nodeHasError) {
        setNodeHasError(node, nodeHasError, null);
    }

    public void setNodeHasError(final NodeInterface node, boolean nodeHasError, String reason) {
        LOGGER.info("setErrorState, node: {}, nodeHasError: {}", node, nodeHasError);
        node.setNodeHasError(nodeHasError);
        if (reason != null) {
            node.setReasonData(reason);
        }
        fireNodeStateChanged(node);
    }

    public void signalInitialLoadFinished() {
        LOGGER.info("The initial load has finished.");
        initialLoadFinished.set(true);
    }

    public void signalResetInitialLoadFinished() {
        LOGGER.info("The initial load is reset.");
        initialLoadFinished.set(false);
    }

    public boolean isInitialLoadFinished() {
        return initialLoadFinished.get();
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy