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}