001package jmri.jmrix.acela; 002 003import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 004import javax.annotation.Nonnull; 005import jmri.Manager.NameValidity; 006import org.slf4j.Logger; 007import org.slf4j.LoggerFactory; 008 009/** 010 * Utility Class supporting parsing and testing of addresses for Acela. 011 * <p> 012 * One address format is supported: Atxxxx where: t is the type code, 'T' for 013 * turnouts, 'S' for sensors, and 'L' for lights xxxx is a bit number of the 014 * input or output bit (0-16383) examples: AT2 (bit 2), AS1003 (bit 1003), AL134 015 * (bit134).<p> 016 * Note: Not fully supporting long system connection prefix yet 017 * 018 * @author Dave Duchamp, Copyright (C) 2004 - 2006 019 * @author Bob Coleman Copyright (C) 2007, 2008, 2009 Based on CMRI serial 020 * example, modified to establish Acela support. 021 */ 022public class AcelaAddress { 023 024 public AcelaAddress() { 025 } 026 027 static final int MINSENSORADDRESS = 0; 028 static final int MAXSENSORADDRESS = AcelaNode.MAXSENSORBITS * AcelaNode.MAXNODE -1; 029 static final int MINOUTPUTADDRESS = 0; 030 static final int MAXOUTPUTADDRESS = AcelaNode.MAXOUTPUTBITS * AcelaNode.MAXNODE -1; 031 032 /** 033 * Public static method to parse an Acela system name and return the Acela 034 * Node Address. 035 * <p> 036 * Note: Returns '-1' if illegal systemName format or if the 037 * node is not found. 038 * Nodes are numbered from 0 - {@value AcelaNode#MAXNODE}. 039 * @param systemName system name. 040 * @param memo system connection. 041 * @return node address number. 042 */ 043 public static int getNodeAddressFromSystemName(String systemName, AcelaSystemConnectionMemo memo) { 044 // validate the system Name leader characters 045 if (validSystemNameFormat(systemName, systemName.charAt(memo.getSystemPrefix().length()), memo.getSystemPrefix()) != NameValidity.VALID) { 046 // No point in trying if a valid system name format is not present 047 return (-1); 048 } 049 int num = getBitFromSystemName(systemName, memo.getSystemPrefix()); 050 if (num < 0) { 051 log.error("invalid Acela system name: {}", systemName); 052 return (-1); 053 } 054 // This is a ALnnxxx address 055 int nodeaddress = -1; 056 if (systemName.charAt(memo.getSystemPrefix().length()) == 'S') { 057 // Acela has two address spaces: true == sensor address space; false == output address space 058 nodeaddress = memo.getTrafficController().lookupAcelaNodeAddress(num, true); 059 } else { 060 // Acela has two address spaces: true == sensor address space; false == output address space 061 nodeaddress = memo.getTrafficController().lookupAcelaNodeAddress(num, false); 062 } 063 return (nodeaddress); 064 } 065 066 /** 067 * Public static method to parse an Acela system name. 068 * 069 * @param systemName system name to parse. 070 * @param memo system connection. 071 * @return the Acela Node number, return 'null' if illegal systemName format or if the node is 072 * not found 073 */ 074 public static AcelaNode getNodeFromSystemName(String systemName, AcelaSystemConnectionMemo memo) { 075 // get the node address 076 int ua; 077 078 ua = getNodeAddressFromSystemName(systemName, memo); 079 if (ua == -1) { 080 // error messages have already been issued by getNodeAddressFromSystemName 081 return null; 082 } 083 084 AcelaNode tempnode; 085 tempnode = (AcelaNode) (memo.getTrafficController().getNodeFromAddress(ua)); 086 087 return tempnode; 088 } 089 090 /** 091 * Public static method to parse an Acela system name and return the bit number. 092 * Note: Bits are numbered from 1. 093 * 094 * @param systemName system name. 095 * @param prefix bean type, S, T, L or H. 096 * @return the bit number, return -1 if an error is found (0 is a valid bit?) 097 */ 098 public static int getBitFromSystemName(String systemName, String prefix) { 099 // validate the System Name leader characters 100 if (!(systemName.startsWith(prefix)) || ((systemName.charAt(prefix.length()) != 'L') 101 && (systemName.charAt(prefix.length()) != 'S') && (systemName.charAt(prefix.length()) != 'T') 102 && (systemName.charAt(prefix.length()) != 'H'))) { 103 // here if an invalid Acela format 104 log.error("illegal character in header field of system name: {}", systemName); 105 return (-1); 106 } 107 // try to parse remaining system name part 108 int num = -1; 109 try { 110 num = Integer.parseInt(systemName.substring(prefix.length() + 1)); // multi char prefix 111 } catch (NumberFormatException e) { 112 log.warn("invalid character in number field of system name: {}", systemName); 113 return (-1); 114 } 115 if (num < 0) { 116 log.warn("invalid Acela system name: {}", systemName); 117 return (-1); 118 } 119 return (num); 120 } 121 122 /** 123 * Public static method to validate system name format. 124 * Logging of handled cases no higher than WARN. 125 * 126 * @param systemName system name to validate. 127 * @param type bean type, S, T or L. 128 * @param prefix system prefix. 129 * @return 'true' if system name has a valid format, else return 'false' 130 */ 131 public static NameValidity validSystemNameFormat(@Nonnull String systemName, char type, String prefix) { 132 // validate the system Name leader characters 133 if (!systemName.startsWith(prefix + type )) { 134 // here if an illegal format 135 log.error("invalid character in header field of system name: {}", systemName); 136 return NameValidity.INVALID; 137 } 138 int num; 139 try { 140 num = Integer.parseInt(systemName.substring(prefix.length() + 1)); 141 } catch (NumberFormatException e) { 142 log.debug("invalid character in number field of system name: {}", systemName); 143 return NameValidity.INVALID; 144 } 145 if (num >= 0) { 146 // This is an ALnnxxx address 147 return NameValidity.VALID; 148 } else { 149 log.debug("invalid Acela system name: {}", systemName); 150 return NameValidity.INVALID; 151 } 152 } 153 154 /** 155 * Public static method to validate Acela system name for configuration. 156 * 157 * @param systemName system name to validate. 158 * @param type bean type, S, T or L. 159 * @param memo system connection. 160 * @return 'true' if system name has a valid meaning in current 161 * configuration, else return 'false' 162 */ 163 @SuppressFBWarnings(value = "DB_DUPLICATE_SWITCH_CLAUSES", justification="additional check for valid bit value") 164 public static boolean validSystemNameConfig(String systemName, char type, AcelaSystemConnectionMemo memo) { 165 if (validSystemNameFormat(systemName, type, memo.getSystemPrefix()) != NameValidity.VALID) { 166 // No point in trying if a valid system name format is not present 167 return false; 168 } 169 AcelaNode node = getNodeFromSystemName(systemName, memo); 170 if (node == null) { 171 // The node indicated by this system address is not present 172 return false; 173 } 174 int bit = getBitFromSystemName(systemName, memo.getSystemPrefix()); 175 switch (type) { 176 case 'T': 177 case 'L': 178 if ((bit >= MINOUTPUTADDRESS) && (bit <= MAXOUTPUTADDRESS)) { 179 // The bit is within valid range for this defined Acela node 180 return true; 181 } 182 break; 183 case 'S': 184 if ((bit >= MINSENSORADDRESS) && (bit <= MAXSENSORADDRESS)) { 185 // The bit is within valid range for this defined Acela node 186 return true; 187 } 188 break; 189 default: 190 log.error("Invalid type specification in validSystemNameConfig call"); 191 return false; 192 } 193 // System name has failed all tests 194 log.warn("Acela hardware address out of range in system name: {}", systemName); 195 return false; 196 } 197 198 public static boolean validSystemNameConfig(String systemName, AcelaSystemConnectionMemo memo) { 199 char type = systemName.charAt(memo.getSystemPrefix().length()); 200 return validSystemNameConfig(systemName, type, memo); 201 } 202 203 /** 204 * Public static method to convert one format Acela system name for the 205 * alternate format. 206 * 207 * @param systemName system name to convert. 208 * @param prefix system prefix. 209 * @return name (string) in alternate format, or empty string if the supplied 210 * system name does not have a valid format, or if there is no representation 211 * in the alternate naming scheme. 212 */ 213 public static String convertSystemNameToAlternate(String systemName, String prefix) { 214 // ensure that input system name has a valid format 215 if (validSystemNameFormat(systemName, systemName.charAt(prefix.length()), prefix) != NameValidity.VALID) { 216 // No point in trying if a valid system name format is not present 217 return ""; 218 } 219 String altName = ""; 220 altName = systemName; 221 return altName; 222 } 223 224 /** 225 * Public static method to normalize an Acela system name. 226 * <p> 227 * This routine is used to ensure that each system name is uniquely linked 228 * to one Acela bit, by removing extra zeros inserted by the user. 229 * 230 * @param systemName system name to normalize. 231 * @param prefix system prefix. 232 * @return a normalized name is returned in the same format as the input name, 233 * or an empty string if the supplied system name does not have a valid format. 234 */ 235 public static String normalizeSystemName(String systemName, String prefix) { 236 // ensure that input system name has a valid format 237 if (validSystemNameFormat(systemName, systemName.charAt(prefix.length()), prefix) != NameValidity.VALID) { 238 // No point in normalizing if a valid system name format is not present 239 return ""; 240 } 241 // check if bit number is within the valid range 242 int bitNum = getBitFromSystemName(systemName, prefix); 243 char type = systemName.charAt(prefix.length()); 244 if (bitNum < 0) { 245 return ""; 246 } 247 // everything OK, normalize the address 248 String nName = ""; 249 nName = prefix + type + Integer.toString(bitNum); 250 return nName; 251 } 252 253 /** 254 * Public static method to construct an Acela system name from type 255 * character, node address, and bit number. 256 * 257 * @param type bean type letter, S, T or L. 258 * @param nAddress node address. 259 * @param bitNum bit number. 260 * @param memo system connection. 261 * @return a system name in the ALxxxx, ATxxxx, or ASxxxx 262 * format. The returned name is normalized. 263 * Return the null string "" if the supplied character is not valid, 264 * or if the node address is out of the 0 - 127 range, or the bit number is 265 * out of the 1 - 2048 range and an error message is logged. 266 */ 267 public static String makeSystemName(String type, int nAddress, int bitNum, AcelaSystemConnectionMemo memo) { 268 String nName = ""; 269 // check the type character 270 if (!type.equalsIgnoreCase("S") && !type.equalsIgnoreCase("L") && !type.equalsIgnoreCase("T")) { 271 // here if an illegal type character 272 log.error("invalid type character proposed for system name"); 273 return (nName); 274 } 275 // check the node address 276 if ((nAddress < memo.getTrafficController().getMinimumNodeAddress()) || (nAddress > memo.getTrafficController().getMaximumNumberOfNodes())) { 277 // here if an illegal node address 278 log.warn("invalid node adddress proposed for system name"); 279 return (nName); 280 } 281 // check the bit number 282 if (type.equalsIgnoreCase("S") && ((bitNum < 0) || (bitNum > MAXSENSORADDRESS))) { 283 // here if an illegal bit number 284 log.warn("invalid bit number proposed for Acela Sensor"); 285 return (nName); 286 } 287 if ((type.equalsIgnoreCase("L") || type.equalsIgnoreCase("T")) && ((bitNum < 0) || (bitNum > MAXOUTPUTADDRESS))) { 288 // here if an illegal bit number 289 log.warn("invalid bit number proposed for Acela Turnout or Light"); 290 return (nName); 291 } 292 // construct the address 293 nName = memo.getSystemPrefix() + type + Integer.toString(bitNum); 294 return (nName); 295 } 296 297 /** 298 * Public static method to check the user name for a valid system name. 299 * 300 * @param systemName system name to check. 301 * @param prefix bean prefix, S, T or L. 302 * @return "" (null string) if the system name is not valid or does not exist 303 */ 304 public static String getUserNameFromSystemName(String systemName, String prefix) { 305 // check for a valid system name 306 if ((systemName.length() < (prefix.length() + 2)) || (!systemName.startsWith(prefix))) { 307 // not a valid system name for Acela 308 return (""); 309 } 310 // check for a sensor 311 if (systemName.charAt(prefix.length() + 1) == 'S') { 312 jmri.Sensor s = null; 313 s = jmri.InstanceManager.sensorManagerInstance().getBySystemName(systemName); 314 if (s != null) { 315 return s.getUserName(); 316 } else { 317 return (""); 318 } 319 } // check for a turnout 320 else if (systemName.charAt(prefix.length() + 1) == 'T') { 321 jmri.Turnout t = null; 322 t = jmri.InstanceManager.turnoutManagerInstance().getBySystemName(systemName); 323 if (t != null) { 324 return t.getUserName(); 325 } else { 326 return (""); 327 } 328 } // check for a light 329 else if (systemName.charAt(prefix.length() + 1) == 'L') { 330 jmri.Light lgt = null; 331 lgt = jmri.InstanceManager.lightManagerInstance().getBySystemName(systemName); 332 if (lgt != null) { 333 return lgt.getUserName(); 334 } else { 335 return (""); 336 } 337 } 338 339 // not any known sensor, light, or turnout 340 return (""); 341 } 342 343 private final static Logger log = LoggerFactory.getLogger(AcelaAddress.class); 344 345}