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