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}