001package jmri.jmrix.secsi; 002 003import java.util.Locale; 004import javax.annotation.Nonnull; 005import jmri.Manager.NameValidity; 006import jmri.NamedBean; 007import org.slf4j.Logger; 008import org.slf4j.LoggerFactory; 009 010/** 011 * Utility Class supporting parsing and testing of addresses 012 * <p> 013 * Two address formats are supported: 014 * <ul> 015 * <li>Vtnnnxxx where: 016 * <ul> 017 * <li>V is the system connection prefix with optional index 018 * <li>t is the type code: 'T' for turnouts, 'S' for sensors, 019 * and 'L' for lights 020 * <li>nn is the node address (0-127) 021 * <li>xxx is a bit number of the input or 022 * <li>output bit (001-999) nnxxx = (node address x 1000) + bit number. 023 * </ul> 024 * Examples: VT2 (node address 0, bit 2), V2S1003 (node address 1, 025 * bit 3), VL11234 (node address 11, bit234) 026 * </li> 027 * <li>VtnnnBxxxx where: 028 * <ul> 029 * <li>V is the system connection prefix with optional index 030 * <li>t is the type code: 'T' for turnouts, 'S' for sensors, 031 * and 'L' for lights 032 * <li>nnn is the node address of the input or output bit (0-127) 033 * <li>xxxx is a bit number of the input or output bit (1-999). 034 * </ul> 035 * Examples: VT0B2 (node address 0, bit 2), VS1B3 (node address 1, bit 3), 036 * VL11B234 (node address 11, bit234) 037 * </li> 038 * </ul> 039 * 040 * @author Dave Duchamp, Copyright (C) 2004 041 * @author Bob Jacobsen, Copyright (C) 2006, 2007, 2008 042 */ 043public class SerialAddress { 044 045 public SerialAddress() { 046 } 047 048 /** 049 * Parse a system name and return the Serial Node. 050 * 051 * @param systemName system name. 052 * @param tc system connection traffic controller. 053 * @return 'NULL' if illegal systemName format or if the node is not 054 * found 055 */ 056 public static SerialNode getNodeFromSystemName(String systemName, SerialTrafficController tc) { 057 String prefix = tc.getSystemConnectionMemo().getSystemPrefix(); 058 // validate the system Name leader characters 059 if (!(systemName.startsWith(prefix)) || ((systemName.charAt(prefix.length()) != 'L') 060 && (systemName.charAt(prefix.length()) != 'S') && (systemName.charAt(prefix.length()) != 'T'))) { 061 // here if an illegal format 062 log.error("illegal character in header field of system name: {}", systemName); 063 return (null); 064 } 065 String s = ""; 066 boolean noB = true; 067 for (int i = prefix.length() + 1; (i < systemName.length()) && noB; i++) { 068 if (systemName.charAt(i) == 'B') { 069 s = systemName.substring(prefix.length() + 1, i); 070 noB = false; 071 } 072 } 073 int ua; 074 if (noB) { 075 // This is a ViLnnxxx address 076 int num = Integer.parseInt(systemName.substring(prefix.length() + 1)); 077 if (num > 0) { 078 ua = num / 1000; 079 } else { 080 log.error("invalid system name: {}", systemName); 081 return (null); 082 } 083 } else { 084 if (s.length() == 0) { 085 log.error("no node address before 'B' in system name: {}", systemName); 086 return (null); 087 } else { 088 try { 089 ua = Integer.parseInt(s); 090 } catch (Exception e) { 091 log.error("illegal character in system name: {}", systemName); 092 return (null); 093 } 094 } 095 } 096 return (SerialNode) tc.getNodeFromAddress(ua); 097 } 098 099 /** 100 * Parse a system name and return the bit number. 101 * <p> 102 * Note: Bits are numbered from 1. 103 * 104 * @param systemName system name. 105 * @param prefix system prefix. 106 * @return the bit number, 0 if an error occurred 107 */ 108 public static int getBitFromSystemName(String systemName, String prefix) { 109 // validate the system Name leader characters 110 if (!(systemName.startsWith(prefix)) || ((systemName.charAt(prefix.length()) != 'L') 111 && (systemName.charAt(prefix.length()) != 'S') && (systemName.charAt(prefix.length()) != 'T'))) { 112 // here if an illegal format 113 log.error("invalid character in header field of system name: {}", systemName); 114 return (0); 115 } 116 // Find the beginning of the bit number field 117 int k = 0; 118 for (int i = prefix.length() + 1; ((i < systemName.length()) && (k == 0)); i++) { 119 if (systemName.charAt(i) == 'B') { 120 k = i + 1; 121 } 122 } 123 int n = 0; 124 if (k == 0) { 125 // here if 'B' not found, name must be ViLnnxxx format 126 int num; 127 try { 128 num = Integer.parseInt(systemName.substring(prefix.length() + 1)); 129 } catch (Exception e) { 130 log.error("invalid character in number field of system name: {}", systemName); 131 return (0); 132 } 133 if (num > 0) { 134 n = num - ((num / 1000) * 1000); 135 } else { 136 log.error("invalid system name: {}", systemName); 137 return (0); 138 } 139 } else { 140 // This is a ViLnnBxxxx address 141 try { 142 n = Integer.parseInt(systemName.substring(k, systemName.length())); 143 } catch (Exception e) { 144 log.error("illegal character in bit number field system name: {}", systemName); 145 return (0); 146 } 147 } 148 return (n); 149 } 150 151 /** 152 * Validate system name format. Does not check whether that node is defined 153 * on current system. 154 * 155 * @param systemName the system name 156 * @param prefix the system connection prefix 157 * @param locale the Locale for user messages 158 * @return systemName unmodified 159 * @throws IllegalArgumentException if unable to validate systemName 160 */ 161 public static String validateSystemNameFormat(String systemName, String prefix, Locale locale) throws IllegalArgumentException { 162 if (!systemName.startsWith(prefix)) { 163 throw new NamedBean.BadSystemNameException( 164 Bundle.getMessage(Locale.ENGLISH, "InvalidSystemNameInvalidPrefix", systemName), 165 Bundle.getMessage(locale, "InvalidSystemNameInvalidPrefix", systemName)); 166 } 167 String address = systemName.substring(prefix.length()); 168 int node = 0; 169 int bit = 0; 170 if (!address.contains("B")) { 171 // This is a CLnnnxxx pattern address 172 int num; 173 try { 174 num = Integer.parseInt(address); 175 node = num / 1000; 176 bit = num - ((num / 1000) * 1000); 177 } catch (NumberFormatException ex) { 178 throw new NamedBean.BadSystemNameException( 179 Bundle.getMessage(Locale.ENGLISH, "InvalidSystemNameNotInteger", systemName, prefix), 180 Bundle.getMessage(locale, "InvalidSystemNameNotInteger", systemName, prefix)); 181 } 182 } else { 183 // This is a CLnBxxx pattern address 184 String[] parts = address.split("B"); 185 if (parts.length != 2) { 186 if (address.indexOf("B") == 0) { 187 // no node 188 throw new NamedBean.BadSystemNameException( 189 Bundle.getMessage(Locale.ENGLISH, "InvalidSystemNameNodeInvalid", systemName, ""), 190 Bundle.getMessage(locale, "InvalidSystemNameNodeInvalid", systemName, "")); 191 } else { 192 // no bit 193 throw new NamedBean.BadSystemNameException( 194 Bundle.getMessage(Locale.ENGLISH, "InvalidSystemNameBitInvalid", systemName, ""), 195 Bundle.getMessage(locale, "InvalidSystemNameBitInvalid", systemName, "")); 196 } 197 } 198 try { 199 node = Integer.parseInt(parts[0]); 200 } catch (NumberFormatException ex) { 201 throw new NamedBean.BadSystemNameException( 202 Bundle.getMessage(Locale.ENGLISH, "InvalidSystemNameNodeInvalid", systemName, parts[0]), 203 Bundle.getMessage(locale, "InvalidSystemNameNodeInvalid", systemName, parts[0])); 204 } 205 try { 206 bit = Integer.parseInt(parts[1]); 207 } catch (NumberFormatException ex) { 208 throw new NamedBean.BadSystemNameException( 209 Bundle.getMessage(Locale.ENGLISH, "InvalidSystemNameBitInvalid", systemName, parts[1]), 210 Bundle.getMessage(locale, "InvalidSystemNameBitInvalid", systemName, parts[1])); 211 } 212 } 213 if (node < 0 || node >= 128) { 214 throw new NamedBean.BadSystemNameException( 215 Bundle.getMessage(Locale.ENGLISH, "InvalidSystemNameNodeInvalid", systemName, node), 216 Bundle.getMessage(locale, "InvalidSystemNameNodeInvalid", systemName, node)); 217 } 218 if (bit < 1 || bit > 32) { 219 throw new NamedBean.BadSystemNameException( 220 Bundle.getMessage(Locale.ENGLISH, "InvalidSystemNameBitInvalid", systemName, bit), 221 Bundle.getMessage(locale, "InvalidSystemNameBitInvalid", systemName, bit)); 222 } 223 return systemName; 224 } 225 226 /** 227 * Public static method to validate system name format. 228 * <p> 229 * Logging of handled cases no higher than WARN. 230 * 231 * @param systemName system name. 232 * @param type Letter indicating device type expected 233 * @param prefix system prefix. 234 * @return 'true' if system name has a valid format, else returns 'false' 235 */ 236 public static NameValidity validSystemNameFormat(@Nonnull String systemName, char type, String prefix) { 237 // validate the system Name leader characters 238 if (!(systemName.startsWith(prefix)) || (systemName.charAt(prefix.length()) != type )) { 239 // here if an illegal format 240 log.error("illegal character in header field system name: {}", systemName); 241 return NameValidity.INVALID; 242 } 243 // check for the presence of a 'B' to differentiate the two address formats 244 String s = ""; 245 int k = 0; 246 boolean noB = true; 247 for (int i = prefix.length() + 1; (i < systemName.length()) && noB; i++) { 248 if (systemName.charAt(i) == 'B') { 249 s = systemName.substring(prefix.length() + 1, i); 250 k = i + 1; 251 noB = false; 252 } 253 } 254 if (noB) { 255 // This is a ViLnnnxxx address 256 int num; 257 try { 258 num = Integer.parseInt(systemName.substring(prefix.length() + 1)); 259 } catch (Exception e) { 260 log.debug("invalid character in number field system name: {}", systemName); 261 return NameValidity.INVALID; 262 } 263 if ((num < 1) || (num >= 128000)) { 264 log.debug("number field out of range in system name: {}", systemName); 265 return NameValidity.INVALID; 266 } 267 if ((num - ((num / 1000) * 1000)) == 0) { 268 log.debug("bit number not in range 1 - 999 in system name: {}", systemName); 269 return NameValidity.INVALID; 270 } 271 } else { 272 // This is a ViLnnnBxxxx address - validate the node address field 273 if (s.length() == 0) { 274 log.debug("no node address before 'B' in system name: {}", systemName); 275 return NameValidity.INVALID; 276 } 277 int num; 278 try { 279 num = Integer.parseInt(s); 280 } catch (Exception e) { 281 log.debug("invalid character in node address field of system name: {}", 282 systemName); 283 return NameValidity.INVALID; 284 } 285 if ((num < 0) || (num >= 128)) { 286 log.debug("node address field out of range in system name: {}", systemName); 287 return NameValidity.INVALID; 288 } 289 // validate the bit number field 290 try { 291 num = Integer.parseInt(systemName.substring(k, systemName.length())); 292 } catch (Exception e) { 293 log.debug("invalid character in bit number field of system name: {}", 294 systemName); 295 return NameValidity.INVALID; 296 } 297 if ((num < 1) || (num > 32)) { 298 log.debug("bit number field out of range in system name: {}", systemName); 299 return NameValidity.INVALID; 300 } 301 } 302 303 return NameValidity.VALID; 304 } 305 306 /** 307 * Public static method to validate system name for configuration. 308 * 309 * @param systemName system name. 310 * @param type bean type, e.g. S for Sensor, T for Turnout. 311 * @param tc system traffic controller. 312 * @return 'true' if system name has a valid meaning in current configuration, else 313 * returns 'false' 314 */ 315 public static boolean validSystemNameConfig(String systemName, char type, SerialTrafficController tc) { 316 String prefix = tc.getSystemConnectionMemo().getSystemPrefix(); 317 if (validSystemNameFormat(systemName, type, prefix) != NameValidity.VALID) { 318 // No point in trying if a valid system name format is not present 319 log.warn("{} invalid; bad format", systemName); 320 return false; 321 } 322 SerialNode node = getNodeFromSystemName(systemName, tc); 323 if (node == null) { 324 log.warn("{} invalid; no such node", systemName); 325 // The node indicated by this system address is not present 326 return false; 327 } 328 int bit = getBitFromSystemName(systemName, prefix); 329 if ((type == 'T') || (type == 'L')) { 330 if ((bit <= 0) || (bit > SerialNode.outputBits[node.nodeType])) { 331 // The bit is not valid for this defined Serial node 332 log.warn("{} invalid; bad bit number", systemName); 333 return false; 334 } 335 } else if (type == 'S') { 336 if ((bit <= 0) || (bit > SerialNode.inputBits[node.nodeType])) { 337 // The bit is not valid for this defined Serial node 338 log.warn("{} invalid; bad bit number", systemName); 339 return false; 340 } 341 } else { 342 log.error("Invalid type specification in validSystemNameConfig call"); 343 return false; 344 } 345 // System name has passed all tests 346 return true; 347 } 348 349 /** 350 * Public static method to convert one format system name for the alternate 351 * format. 352 * 353 * @param systemName system name. 354 * @param prefix system prefix. 355 * @return an empty string if the supplied system name does not have a valid 356 * format, or if there is no representation in the alternate naming scheme 357 */ 358 public static String convertSystemNameToAlternate(String systemName, String prefix) { 359 // ensure that input system name has a valid format 360 if (validSystemNameFormat(systemName, systemName.charAt(prefix.length()), prefix) != NameValidity.VALID) { 361 // No point in trying if a valid system name format is not present 362 return ""; 363 } 364 String altName = ""; 365 // check for the presence of a 'B' to differentiate the two address formats 366 String s = ""; 367 int k = 0; 368 boolean noB = true; 369 for (int i = prefix.length() + 1; (i < systemName.length()) && noB; i++) { 370 if (systemName.charAt(i) == 'B') { 371 s = systemName.substring(prefix.length() + 1, i); 372 k = i + 1; 373 noB = false; 374 } 375 } 376 if (noB) { 377 // This is a ViLnnnxxx address, convert to num-style 378 int num = Integer.parseInt(systemName.substring(prefix.length() + 1)); 379 int nAddress = num / 1000; 380 int bitNum = num - (nAddress * 1000); 381 altName = prefix + systemName.charAt(prefix.length()) + Integer.toString(nAddress) + "B" 382 + Integer.toString(bitNum); 383 } else { 384 // This is a VLnnnBxxxx address 385 int nAddress = Integer.parseInt(s); 386 int bitNum = Integer.parseInt(systemName.substring(k, systemName.length())); 387 if (bitNum > 999) { 388 // bit number is out-of-range for a CLnnnxxx address 389 return ""; 390 } 391 altName = prefix + systemName.charAt(prefix.length()) + Integer.toString((nAddress * 1000) + bitNum); 392 } 393 return altName; 394 } 395 396 /** 397 * Normalize a system name. 398 * <p> 399 * This routine is used to ensure that each system name is uniquely linked 400 * to one bit, by removing extra zeros inserted by the user. 401 * 402 * @param systemName system name. 403 * @param prefix system prefix. 404 * @return an empty string if the supplied system name does not have a valid 405 * format. Otherwise a normalized name is returned in the same format 406 * as the input name. 407 */ 408 public static String normalizeSystemName(String systemName, String prefix) { 409 if (prefix.length() < 1) { 410 log.error("invalid system name prefix: {}", prefix); 411 return ""; 412 } 413 // ensure that input system name has a valid format 414 if (validSystemNameFormat(systemName, systemName.charAt(prefix.length()), prefix) != NameValidity.VALID) { 415 // No point in normalizing if a valid system name format is not present 416 return ""; 417 } 418 String nName = ""; 419 // check for the presence of a 'B' to differentiate the two address formats 420 String s = ""; 421 int k = 0; 422 boolean noB = true; 423 for (int i = prefix.length() + 1; (i < systemName.length()) && noB; i++) { 424 if (systemName.charAt(i) == 'B') { 425 s = systemName.substring(prefix.length() + 1, i); 426 k = i + 1; 427 noB = false; 428 } 429 } 430 char type = systemName.charAt(prefix.length()); 431 if (noB) { 432 // This is a ViLnnnxxx address 433 int num = Integer.parseInt(systemName.substring(prefix.length() + 1)); 434 int nAddress = num / 1000; 435 int bitNum = num - (nAddress * 1000); 436 nName = prefix + type + Integer.toString((nAddress * 1000) + bitNum); 437 } else { 438 // This is a ViLnnnBxxxx address 439 int nAddress = Integer.parseInt(s); 440 int bitNum = Integer.parseInt(systemName.substring(k, systemName.length())); 441 nName = prefix + type + Integer.toString(nAddress) + "B" 442 + Integer.toString(bitNum); 443 } 444 return nName; 445 } 446 447 private final static Logger log = LoggerFactory.getLogger(SerialAddress.class); 448 449}