001package jmri.jmrix;
002
003import java.util.HashMap;
004import java.util.Map;
005
006import javax.annotation.Nonnull;
007
008import jmri.SystemConnectionMemo;
009
010import org.slf4j.Logger;
011import org.slf4j.LoggerFactory;
012
013/**
014 * Interface for classes that wish to get notification when the connection to
015 * the layout changes.
016 * <p>
017 * Maintains a single instance, as there is only one set of connections for the
018 * running program.
019 * <p>
020 * The "system name" referred to here is the human-readable name like "LocoNet 2"
021 * which can be obtained from i.e.
022 * {@link jmri.SystemConnectionMemo#getUserName}.
023 * Not clear whether {@link ConnectionConfig#getConnectionName} is correct.
024 * It's not intended to be the prefix from i.e. {@link PortAdapter#getSystemPrefix}.
025 * Maybe the right thing is to pass in the SystemConnectionMemo?
026 *
027 * @author Daniel Boudreau Copyright (C) 2007
028 * @author Paul Bender Copyright (C) 2016
029 */
030public class ConnectionStatus {
031
032    public static final String CONNECTION_UNKNOWN = "Unknown";
033    public static final String CONNECTION_UP = "Connected";
034    public static final String CONNECTION_DOWN = "Not Connected";
035
036    // hashmap of SystemConnectionMemo's and their status
037    private final HashMap<SystemConnectionMemo, String> portStatus = new HashMap<>();
038
039    /**
040     * Record the single instance *
041     */
042    private static ConnectionStatus _instance = null;
043
044    public static synchronized ConnectionStatus instance() {
045        if (_instance == null) {
046            log.debug("ConnectionStatus creating instance");
047            // create and load
048            _instance = new ConnectionStatus();
049        }
050        // log.debug("ConnectionStatus returns instance {}", _instance);
051        return _instance;
052    }
053
054    // Used by ConnectionStatusTest
055    static synchronized void clearInstance() {
056        _instance = null;
057    }
058
059    private ConnectionStatus() {
060        // Private constructor to protect singleton
061    }
062
063    /**
064     * Add a connection with a given memo to the portStatus set
065     * if not yet present in the set.
066     *
067     * @param memo the system memo
068     */
069    public synchronized void addConnection(@Nonnull SystemConnectionMemo memo) {
070        log.debug("addConnection memo {}", memo);
071        if (!portStatus.containsKey(memo)) {
072            portStatus.put(memo, CONNECTION_UNKNOWN);
073            firePropertyChange("add", null, memo);
074        }
075    }
076
077    /**
078     * Set the connection state of a communication port.
079     *
080     * @param memo the system memo
081     * @param state      one of ConnectionStatus.UP, ConnectionStatus.DOWN, or
082     *                   ConnectionStatus.UNKNOWN.
083     */
084    public synchronized void setConnectionState(@Nonnull SystemConnectionMemo memo, @Nonnull String state) {
085        log.debug("setConnectionState memo: {} state: {}", memo, state);
086        if (!portStatus.containsKey(memo)) {
087            portStatus.put(memo, state);
088            firePropertyChange("add", null, memo);
089            log.debug("New Connection added: {} ", memo);
090        } else {
091            firePropertyChange("change", portStatus.put(memo, state), memo);
092        }
093    }
094
095    /**
096     * Get the status of a communication port with a given name.
097     *
098     * @param memo the system memo
099     * @return the status
100     */
101    public synchronized String getConnectionState(@Nonnull SystemConnectionMemo memo) {
102        log.debug("getConnectionState memo {}", memo);
103        String stateText = CONNECTION_UNKNOWN;
104        if (portStatus.containsKey(memo)) {
105            stateText = portStatus.get(memo);
106            log.debug("connection found : {}", stateText);
107        } else {
108            log.debug("connection memo {} not found, {}", memo, stateText);
109        }
110        return stateText;
111    }
112
113    /**
114     * Confirm status of a communication port is not down.
115     *
116     * @param memo the system memo
117     * @return true if port connection is operational or unknown, false if not
118     */
119    public synchronized boolean isConnectionOk(@Nonnull SystemConnectionMemo memo) {
120        String stateText = getConnectionState(memo);
121        return !stateText.equals(CONNECTION_DOWN);
122    }
123
124    /**
125     * Confirm status of a communication port is not down, based on the system name.
126     *
127     * @param systemName human-readable name for system like "LocoNet 2"
128     *                      which can be obtained from i.e. {@link SystemConnectionMemo#getUserName}.
129     * @return true if port connection is operational or unknown, false if not. This includes
130     *                      returning true if the connection is not recognized.
131     */
132    public synchronized boolean isSystemOk(@Nonnull String systemName) {
133        // see if there is a key that has systemName as the port value.
134        for (var entry : portStatus.entrySet()) {
135            var memoSystemName = entry.getKey().getUserName();
136            if ((memoSystemName != null) && (memoSystemName.equals(systemName))) {
137                // if we find a match, return it
138                return !portStatus.get(entry.getKey()).equals(CONNECTION_DOWN);
139            }
140        }
141        // and if we still don't find a match, go ahead and reply true
142        // as we consider the state unknown.
143        return true;
144    }
145
146    java.beans.PropertyChangeSupport pcs = new java.beans.PropertyChangeSupport(this);
147    Map<SystemConnectionMemo, java.beans.PropertyChangeSupport> pcsMap = new HashMap<>();
148
149    public synchronized void addPropertyChangeListener(
150            @Nonnull java.beans.PropertyChangeListener l) {
151        pcs.addPropertyChangeListener(l);
152    }
153
154    public synchronized void addPropertyChangeListener(
155            @Nonnull SystemConnectionMemo memo, @Nonnull java.beans.PropertyChangeListener l) {
156        pcs.addPropertyChangeListener(l);
157        pcsMap.computeIfAbsent(memo, k -> new java.beans.PropertyChangeSupport(this))
158                .addPropertyChangeListener(l);
159    }
160
161    protected void firePropertyChange(@Nonnull String p, Object old, @Nonnull SystemConnectionMemo memo) {
162        log.debug("firePropertyChange {} old: {} new: {}", p, old, memo);
163        pcs.firePropertyChange(p, old, memo);
164        var memoPCS = pcsMap.get(memo);
165        if (memoPCS != null) {
166            memoPCS.firePropertyChange(p, old, memo);
167        }
168    }
169
170    public synchronized void removePropertyChangeListener(
171            @Nonnull java.beans.PropertyChangeListener l) {
172        pcs.removePropertyChangeListener(l);
173    }
174
175    public synchronized void removePropertyChangeListener(
176            @Nonnull SystemConnectionMemo memo, @Nonnull java.beans.PropertyChangeListener l) {
177        pcs.removePropertyChangeListener(l);
178        var memoPCS = pcsMap.get(memo);
179        if (memoPCS != null) {
180            memoPCS.removePropertyChangeListener(l);
181        }
182    }
183
184    private static final Logger log = LoggerFactory.getLogger(ConnectionStatus.class);
185
186}