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 /** 054 * Add a connection with a given name and port name to the portStatus set 055 * if not yet present in the set. 056 * 057 * @param systemName human-readable name for system like "LocoNet 2" 058 * which can be obtained from i.e. {@link SystemConnectionMemo#getUserName}. 059 * @param portName the port name 060 */ 061 public synchronized void addConnection(String systemName, @Nonnull String portName) { 062 log.debug("addConnection systemName {} portName {}", systemName, portName); 063 ConnectionKey newKey = new ConnectionKey(systemName, portName); 064 if (!portStatus.containsKey(newKey)) { 065 portStatus.put(newKey, CONNECTION_UNKNOWN); 066 firePropertyChange("add", null, portName); 067 } 068 } 069 070 /** 071 * Set the connection state of a communication port. 072 * 073 * @param systemName human-readable name for system like "LocoNet 2" 074 * which can be obtained from i.e. {@link SystemConnectionMemo#getUserName}. 075 * @param portName the port name 076 * @param state one of ConnectionStatus.UP, ConnectionStatus.DOWN, or 077 * ConnectionStatus.UNKNOWN. 078 */ 079 public synchronized void setConnectionState(String systemName, @Nonnull String portName, @Nonnull String state) { 080 log.debug("setConnectionState systemName: {} portName: {} state: {}", systemName, portName, state); 081 ConnectionKey newKey = new ConnectionKey(systemName, portName); 082 if (!portStatus.containsKey(newKey)) { 083 portStatus.put(newKey, state); 084 firePropertyChange("add", null, portName); 085 log.debug("New Connection added: {} ", newKey); 086 } else { 087 firePropertyChange("change", portStatus.put(newKey, state), portName); 088 } 089 } 090 091 /** 092 * Get the status of a communication port with a given name. 093 * 094 * @param systemName human-readable name for system like "LocoNet 2" 095 * which can be obtained from i.e. {@link SystemConnectionMemo#getUserName}. 096 * @param portName the port name 097 * @return the status 098 */ 099 public synchronized String getConnectionState(String systemName, @Nonnull String portName) { 100 log.debug("getConnectionState systemName: {} portName: {}", systemName, portName); 101 String stateText = CONNECTION_UNKNOWN; 102 ConnectionKey newKey = new ConnectionKey(systemName, portName); 103 if (portStatus.containsKey(newKey)) { 104 stateText = portStatus.get(newKey); 105 log.debug("connection found : {}", stateText); 106 } else { 107 log.debug("connection systemName {} portName {} not found, {}", systemName, portName, stateText); 108 } 109 return stateText; 110 } 111 112 /** 113 * Get the status of a communication port, based on the system name only. 114 * 115 * @param systemName human-readable name for system like "LocoNet 2" 116 * which can be obtained from i.e. {@link SystemConnectionMemo#getUserName}. 117 * @return the status 118 */ 119 public synchronized String getSystemState(@Nonnull String systemName) { 120 log.debug("getSystemState systemName: {} ", systemName); 121 // see if there is a key that has systemName as the port value. 122 for (Map.Entry<ConnectionKey, String> entry : portStatus.entrySet()) { 123 if ((entry.getKey().getSystemName() != null) && (entry.getKey().getSystemName().equals(systemName))) { 124 // if we find a match, return it 125 return entry.getValue(); 126 } 127 } 128 // If we still don't find a match, then we don't know the status 129 log.warn("Didn't find system status for system {}", systemName); 130 return CONNECTION_UNKNOWN; 131 } 132 133 /** 134 * Confirm status of a communication port is not down. 135 * 136 * @param systemName human-readable name for system like "LocoNet 2" 137 * which can be obtained from i.e. {@link SystemConnectionMemo#getUserName}. 138 * @param portName the port name 139 * @return true if port connection is operational or unknown, false if not 140 */ 141 public synchronized boolean isConnectionOk(String systemName, @Nonnull String portName) { 142 String stateText = getConnectionState(systemName, portName); 143 return !stateText.equals(CONNECTION_DOWN); 144 } 145 146 /** 147 * Confirm status of a communication port is not down, based on the system name. 148 * 149 * @param systemName human-readable name for system like "LocoNet 2" 150 * which can be obtained from i.e. {@link SystemConnectionMemo#getUserName}. 151 * @return true if port connection is operational or unknown, false if not. This includes 152 * returning true if the connection is not recognized. 153 */ 154 public synchronized boolean isSystemOk(@Nonnull String systemName) { 155 // see if there is a key that has systemName as the port value. 156 for (Map.Entry<ConnectionKey, String> entry : portStatus.entrySet()) { 157 if ((entry.getKey().getSystemName() != null) && (entry.getKey().getSystemName().equals(systemName))) { 158 // if we find a match, return it 159 return !portStatus.get(entry.getKey()).equals(CONNECTION_DOWN); 160 } 161 } 162 // and if we still don't find a match, go ahead and reply true 163 // as we consider the state unknown. 164 return true; 165 } 166 167 java.beans.PropertyChangeSupport pcs = new java.beans.PropertyChangeSupport(this); 168 169 public synchronized void addPropertyChangeListener(java.beans.PropertyChangeListener l) { 170 pcs.addPropertyChangeListener(l); 171 } 172 173 protected void firePropertyChange(@Nonnull String p, Object old, Object n) { 174 log.debug("firePropertyChange {} old: {} new: {}", p, old, n); 175 pcs.firePropertyChange(p, old, n); 176 } 177 178 public synchronized void removePropertyChangeListener(java.beans.PropertyChangeListener l) { 179 pcs.removePropertyChangeListener(l); 180 } 181 182 /** 183 * ConnectionKey is an internal class containing the port name and system 184 * name of a connection. 185 * <p> 186 * ConnectionKey is used as a key in a HashMap of the connections on the 187 * system. 188 * <p> 189 * It is allowable for either the port name or the system name to be null, 190 * but not both. 191 */ 192 static private class ConnectionKey { 193 194 String portName = null; 195 String systemName = null; // human-readable name for system 196 197 /** 198 * constructor 199 * 200 * @param system human-readable name for system like "LocoNet 2" 201 * which can be obtained from i.e. {@link SystemConnectionMemo#getUserName}. 202 * @param port port name 203 * @throws IllegalArgumentException if both system and port are null; 204 */ 205 public ConnectionKey(String system, @Nonnull String port) { 206 if (system == null && port == null) { 207 throw new IllegalArgumentException("At least the port name must be provided"); 208 } 209 systemName = system; 210 portName = port; 211 } 212 213 public String getSystemName() { 214 return systemName; 215 } 216 217 public String getPortName() { 218 return portName; 219 } 220 221 @Override 222 public boolean equals(Object o) { 223 if (o == null || !(o instanceof ConnectionKey)) { 224 return false; 225 } 226 ConnectionKey other = (ConnectionKey) o; 227 228 return (systemName == null ? other.getSystemName() == null : systemName.equals(other.getSystemName())) 229 && (portName == null ? other.getPortName() == null : portName.equals(other.getPortName())); 230 } 231 232 @Override 233 public int hashCode() { 234 if (systemName == null) { 235 return portName.hashCode(); 236 } else if (portName == null) { 237 return systemName.hashCode(); 238 } else { 239 return (systemName.hashCode() + portName.hashCode()); 240 } 241 } 242 243 } 244 245 private final static Logger log = LoggerFactory.getLogger(ConnectionStatus.class); 246 247}