001package jmri.jmrix.openlcb; 002 003import javax.swing.event.SwingPropertyChangeSupport; 004import java.beans.*; 005import java.text.Collator; 006import java.util.*; 007 008import jmri.InstanceManager; 009import jmri.InstanceManagerAutoDefault; 010import jmri.ShutDownManager; 011import jmri.jmrix.openlcb.configurexml.OlcbNodeGroupStoreXml; 012 013import org.openlcb.NodeID; 014 015/** 016 * Maintain information about which group(s) contain a node. 017 * 018 * @author Bob Jacobsen Copyright (C) 2024 019 */ 020public class OlcbNodeGroupStore implements InstanceManagerAutoDefault { // not final for testing 021 022 public OlcbNodeGroupStore() { 023 log.debug("Initialising"); 024 // Load when created 025 load(); 026 initShutdownTask(); 027 dirty = false; // undo changes during load 028 } 029 030 // maps a string group name to a set of NodeIDs it contains 031 private HashMap<String, HashSet<NodeID>> mapOfGroups = new HashMap<>(); 032 033 private HashMap<NodeID, TreeSet<String>> mapOfNodes = new HashMap<>(); 034 035 private boolean dirty = false; 036 private Runnable shutDownTask = null; 037 038 protected void load() { 039 new OlcbNodeGroupStoreXml(this,"NodeGroupAssociations.xml").load(); // NOI18N 040 } 041 042 protected void store() throws java.io.IOException { 043 log.debug("Store invoked"); 044 if (dirty) { 045 new OlcbNodeGroupStoreXml(this,"NodeGroupAssociations.xml").store(); // NOI18N 046 dirty = false; 047 } 048 } 049 050 protected void initShutdownTask() { 051 // Create shutdown task to save 052 log.debug("Register ShutDown task"); 053 if (this.shutDownTask == null) { 054 this.shutDownTask = () -> { 055 try { 056 store(); 057 } catch (java.io.IOException ioe) { 058 log.error("Exception writing node group associations", ioe); 059 } 060 }; 061 InstanceManager.getDefault(ShutDownManager.class).register(this.shutDownTask); 062 } 063 } 064 065 /** 066 * Add a Node to a group 067 */ 068 public void addNodeToGroup(NodeID node, String group) { 069 if (!mapOfGroups.containsKey(group)) mapOfGroups.put(group, new HashSet<NodeID>()); 070 if (!mapOfNodes.containsKey(node)) mapOfNodes.put(node, new TreeSet<String>()); 071 mapOfGroups.get(group).add(node); 072 mapOfNodes.get(node).add(group); 073 fireChangeEvent(); 074 } 075 076 /** 077 * Remove a node from a group 078 */ 079 public void removeNodeFromGroup(NodeID node, String group) { 080 var listNodes = mapOfGroups.get(group); 081 if (listNodes != null) listNodes.remove(node); 082 var listGroups = mapOfNodes.get(node); 083 if (listGroups != null) listGroups.remove(group); 084 085 // when you remove the last entry in the group, the 086 // group itself goes away 087 listNodes = mapOfGroups.get(group); 088 if (listNodes != null && listNodes.size() == 0) { 089 removeGroup(group); 090 } 091 fireChangeEvent(); 092 } 093 094 /** 095 * Remove a group, including all the associations it contains 096 */ 097 public void removeGroup(String group) { 098 mapOfGroups.remove(group); 099 // find all the references in the mapOfNodes and remove each one 100 for (Collection<NodeID> set : mapOfGroups.values()) { 101 for (NodeID node : set) { 102 mapOfNodes.get(node).remove(group); 103 } 104 } 105 fireChangeEvent(); 106 } 107 108 /** 109 * Get alphanumerically-sorted List of existing group names 110 */ 111 public List<String> getGroupNames() { 112 var retval = new ArrayList<String>(mapOfGroups.keySet()); 113 retval.sort(Collator.getInstance()); 114 return retval; 115 } 116 117 /** 118 * Get a Set of nodes in a group 119 */ 120 public Set<NodeID> getGroupNodes(String group) { 121 return mapOfGroups.get(group); 122 } 123 124 /** 125 * Get an ordered set of groups a node belongs to. 126 */ 127 public List<String> getNodesGroups(NodeID node) { 128 var retval = new ArrayList<String>(mapOfNodes.get(node)); 129 retval.sort(Collator.getInstance()); 130 return retval; 131 } 132 133 /** 134 * Does a particular node belong to a specific group? 135 */ 136 public boolean isNodeInGroup(NodeID node, String group) { 137 var list = mapOfNodes.get(node); 138 if (list == null) return false; 139 return list.contains(group); 140 } 141 142 // notify listeners that the content has changed; 143 // doesn't have a finer resolution than that. 144 protected void fireChangeEvent() { 145 pcs.firePropertyChange("Associations", false, true); 146 dirty = true; 147 } 148 149 private final SwingPropertyChangeSupport pcs = new SwingPropertyChangeSupport(this, true); 150 151 public void addPropertyChangeListener(PropertyChangeListener listener) { 152 this.pcs.addPropertyChangeListener(listener); 153 } 154 155 public void removePropertyChangeListener(PropertyChangeListener listener) { 156 this.pcs.removePropertyChangeListener(listener); 157 } 158 159 private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(OlcbNodeGroupStore.class); 160 161}