001package jmri.jmrix; 002 003import java.util.Map; 004import java.util.HashMap; 005import javax.annotation.Nonnull; 006 007import jmri.SystemConnectionMemo; 008 009import org.slf4j.Logger; 010import org.slf4j.LoggerFactory; 011 012/** 013 * Interface for classes that wish to get notification when the connection to 014 * the layout changes. 015 * <p> 016 * Maintains a single instance, as there is only one set of connections for the 017 * running program. 018 * <p> 019 * The "system name" referred to here is the human-readable name like "LocoNet 2" 020 * which can be obtained from i.e. 021 * {@link jmri.SystemConnectionMemo#getUserName}. 022 * Not clear whether {@link ConnectionConfig#getConnectionName} is correct. 023 * It's not intended to be the prefix from i.e. {@link PortAdapter#getSystemPrefix}. 024 * Maybe the right thing is to pass in the SystemConnectionMemo? 025 * 026 * @author Daniel Boudreau Copyright (C) 2007 027 * @author Paul Bender Copyright (C) 2016 028 */ 029public class ConnectionStatus { 030 031 public static final String CONNECTION_UNKNOWN = "Unknown"; 032 public static final String CONNECTION_UP = "Connected"; 033 public static final String CONNECTION_DOWN = "Not Connected"; 034 035 // hashmap of ConnectionKey objects and their status 036 private final HashMap<ConnectionKey, String> portStatus = new HashMap<>(); 037 038 /** 039 * Record the single instance * 040 */ 041 private static ConnectionStatus _instance = null; 042 043 public static synchronized ConnectionStatus instance() { 044 if (_instance == null) { 045 log.debug("ConnectionStatus creating instance"); 046 // create and load 047 _instance = new ConnectionStatus(); 048 } 049 // log.debug("ConnectionStatus returns instance {}", _instance); 050 return _instance; 051 } 052 053 // Used by ConnectionStatusTest 054 static synchronized void clearInstance() { 055 _instance = null; 056 } 057 058 private ConnectionStatus() { 059 // Private constructor to protect singleton 060 } 061 062 /** 063 * Add a connection with a given name and port name to the portStatus set 064 * if not yet present in the set. 065 * 066 * @param systemName human-readable name for system like "LocoNet 2" 067 * which can be obtained from i.e. {@link SystemConnectionMemo#getUserName}. 068 * @param portName the port name 069 */ 070 public synchronized void addConnection(String systemName, @Nonnull String portName) { 071 log.debug("addConnection systemName {} portName {}", systemName, portName); 072 ConnectionKey newKey = new ConnectionKey(systemName, portName); 073 if (!portStatus.containsKey(newKey)) { 074 portStatus.put(newKey, CONNECTION_UNKNOWN); 075 firePropertyChange("add", null, portName); 076 } 077 } 078 079 /** 080 * Set the connection state of a communication port. 081 * 082 * @param systemName human-readable name for system like "LocoNet 2" 083 * which can be obtained from i.e. {@link SystemConnectionMemo#getUserName}. 084 * @param portName the port name 085 * @param state one of ConnectionStatus.UP, ConnectionStatus.DOWN, or 086 * ConnectionStatus.UNKNOWN. 087 */ 088 public synchronized void setConnectionState(String systemName, @Nonnull String portName, @Nonnull String state) { 089 log.debug("setConnectionState systemName: {} portName: {} state: {}", systemName, portName, state); 090 ConnectionKey newKey = new ConnectionKey(systemName, portName); 091 if (!portStatus.containsKey(newKey)) { 092 portStatus.put(newKey, state); 093 firePropertyChange("add", null, portName); 094 log.debug("New Connection added: {} ", newKey); 095 } else { 096 firePropertyChange("change", portStatus.put(newKey, state), portName); 097 } 098 } 099 100 /** 101 * Get the status of a communication port with a given name. 102 * 103 * @param systemName human-readable name for system like "LocoNet 2" 104 * which can be obtained from i.e. {@link SystemConnectionMemo#getUserName}. 105 * @param portName the port name 106 * @return the status 107 */ 108 public synchronized String getConnectionState(String systemName, @Nonnull String portName) { 109 log.debug("getConnectionState systemName: {} portName: {}", systemName, portName); 110 String stateText = CONNECTION_UNKNOWN; 111 ConnectionKey newKey = new ConnectionKey(systemName, portName); 112 if (portStatus.containsKey(newKey)) { 113 stateText = portStatus.get(newKey); 114 log.debug("connection found : {}", stateText); 115 } else { 116 log.debug("connection systemName {} portName {} not found, {}", systemName, portName, stateText); 117 } 118 return stateText; 119 } 120 121 /** 122 * Get the status of a communication port, based on the system name only. 123 * 124 * @param systemName human-readable name for system like "LocoNet 2" 125 * which can be obtained from i.e. {@link SystemConnectionMemo#getUserName}. 126 * @return the status 127 */ 128 public synchronized String getSystemState(@Nonnull String systemName) { 129 log.debug("getSystemState systemName: {} ", systemName); 130 // see if there is a key that has systemName as the port value. 131 for (Map.Entry<ConnectionKey, String> entry : portStatus.entrySet()) { 132 if ((entry.getKey().getSystemName() != null) && (entry.getKey().getSystemName().equals(systemName))) { 133 // if we find a match, return it 134 return entry.getValue(); 135 } 136 } 137 // If we still don't find a match, then we don't know the status 138 log.warn("Didn't find system status for system {}", systemName); 139 return CONNECTION_UNKNOWN; 140 } 141 142 /** 143 * Confirm status of a communication port is not down. 144 * 145 * @param systemName human-readable name for system like "LocoNet 2" 146 * which can be obtained from i.e. {@link SystemConnectionMemo#getUserName}. 147 * @param portName the port name 148 * @return true if port connection is operational or unknown, false if not 149 */ 150 public synchronized boolean isConnectionOk(String systemName, @Nonnull String portName) { 151 String stateText = getConnectionState(systemName, portName); 152 return !stateText.equals(CONNECTION_DOWN); 153 } 154 155 /** 156 * Confirm status of a communication port is not down, based on the system name. 157 * 158 * @param systemName human-readable name for system like "LocoNet 2" 159 * which can be obtained from i.e. {@link SystemConnectionMemo#getUserName}. 160 * @return true if port connection is operational or unknown, false if not. This includes 161 * returning true if the connection is not recognized. 162 */ 163 public synchronized boolean isSystemOk(@Nonnull String systemName) { 164 // see if there is a key that has systemName as the port value. 165 for (Map.Entry<ConnectionKey, String> entry : portStatus.entrySet()) { 166 if ((entry.getKey().getSystemName() != null) && (entry.getKey().getSystemName().equals(systemName))) { 167 // if we find a match, return it 168 return !portStatus.get(entry.getKey()).equals(CONNECTION_DOWN); 169 } 170 } 171 // and if we still don't find a match, go ahead and reply true 172 // as we consider the state unknown. 173 return true; 174 } 175 176 java.beans.PropertyChangeSupport pcs = new java.beans.PropertyChangeSupport(this); 177 178 public synchronized void addPropertyChangeListener(java.beans.PropertyChangeListener l) { 179 pcs.addPropertyChangeListener(l); 180 } 181 182 protected void firePropertyChange(@Nonnull String p, Object old, Object n) { 183 log.debug("firePropertyChange {} old: {} new: {}", p, old, n); 184 pcs.firePropertyChange(p, old, n); 185 } 186 187 public synchronized void removePropertyChangeListener(java.beans.PropertyChangeListener l) { 188 pcs.removePropertyChangeListener(l); 189 } 190 191 /** 192 * ConnectionKey is an internal class containing the port name and system 193 * name of a connection. 194 * <p> 195 * ConnectionKey is used as a key in a HashMap of the connections on the 196 * system. 197 * <p> 198 * It is allowable for either the port name or the system name to be null, 199 * but not both. 200 */ 201 static private class ConnectionKey { 202 203 String portName = null; 204 String systemName = null; // human-readable name for system 205 206 /** 207 * constructor 208 * 209 * @param system human-readable name for system like "LocoNet 2" 210 * which can be obtained from i.e. {@link SystemConnectionMemo#getUserName}. 211 * @param port port name 212 * @throws IllegalArgumentException if both system and port are null; 213 */ 214 public ConnectionKey(String system, @Nonnull String port) { 215 if (system == null && port == null) { 216 throw new IllegalArgumentException("At least the port name must be provided"); 217 } 218 systemName = system; 219 portName = port; 220 } 221 222 public String getSystemName() { 223 return systemName; 224 } 225 226 public String getPortName() { 227 return portName; 228 } 229 230 @Override 231 public boolean equals(Object o) { 232 if (o == null || !(o instanceof ConnectionKey)) { 233 return false; 234 } 235 ConnectionKey other = (ConnectionKey) o; 236 237 return (systemName == null ? other.getSystemName() == null : systemName.equals(other.getSystemName())) 238 && (portName == null ? other.getPortName() == null : portName.equals(other.getPortName())); 239 } 240 241 @Override 242 public int hashCode() { 243 if (systemName == null) { 244 return portName.hashCode(); 245 } else if (portName == null) { 246 return systemName.hashCode(); 247 } else { 248 return (systemName.hashCode() + portName.hashCode()); 249 } 250 } 251 252 } 253 254 private final static Logger log = LoggerFactory.getLogger(ConnectionStatus.class); 255 256}