org.bidib.wizard.mvc.main.model.MainModel Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of bidibwizard-client Show documentation
Show all versions of bidibwizard-client Show documentation
jBiDiB BiDiB Wizard Client Application POM
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();
}
}