001package jmri.jmrix.powerline; 002 003import java.util.Locale; 004import jmri.Manager.NameValidity; 005import java.util.regex.Matcher; 006import java.util.regex.Pattern; 007import jmri.NamedBean; 008import org.slf4j.Logger; 009import org.slf4j.LoggerFactory; 010 011/** 012 * Utility Class supporting parsing and testing of addresses. 013 * <p> 014 * Two address formats are supported: For X10: Ptnxx where: t is the type code, 015 * 'S' for sensors, and 'L' for lights n is the house code of the input or 016 * output bit (A - P) xx is a bit number of the input or output bit (1-16) 017 * examples: PLA2 (House Code A, Unit 2), PSK1 (House Code K, Unit 1) For 018 * Insteon: Pthh.hh.hh where: t is the type code, 'S' for sensors, and 'L' for 019 * lights aa is two hexadecimal digits examples: PLA2.43.CB 020 * 021 * @author Dave Duchamp, Copyright (C) 2004 022 * @author Bob Jacobsen, Copyright (C) 2006, 2007, 2008, 2009 023 * @author Ken Cameron, Copyright (C) 2008, 2009, 2010 024 */ 025public class SerialAddress { 026 027 private Matcher hCodes = null; 028 private Matcher aCodes = null; 029 private Matcher iCodes = null; 030 private Matcher dCodes = null; 031 private static final char MIN_HOUSE_CODE = 'A'; 032 private static final char MAX_HOUSE_CODE = 'P'; 033 034 public SerialAddress(SerialSystemConnectionMemo m) { 035 this.memo = m; 036 hCodes = Pattern.compile("^(" + memo.getSystemPrefix() + ")([LTS])([" + MIN_HOUSE_CODE + "-" + MAX_HOUSE_CODE + "])(\\d++)$").matcher(""); 037 aCodes = Pattern.compile("^(" + memo.getSystemPrefix() + ")([LTS]).*$").matcher(""); 038 iCodes = Pattern.compile("^(" + memo.getSystemPrefix() + ")([LTS])(\\p{XDigit}\\p{XDigit})[.](\\p{XDigit}\\p{XDigit})[.](\\p{XDigit}\\p{XDigit})$").matcher(""); 039 dCodes = Pattern.compile("^(" + memo.getSystemPrefix() + ")([L])(\\p{Digit}{1,3}+)$").matcher(""); 040 } 041 042 SerialSystemConnectionMemo memo = null; 043 044 /** 045 * Validate the format for a system name. 046 * 047 * @param name the name to validate 048 * @param type the type letter for the name 049 * @param locale the locale for messages to the user 050 * @return the name, unchanged 051 */ 052 String validateSystemNameFormat(String name, char type, Locale locale) { 053 boolean aTest = aCodes.reset(name).matches(); 054 boolean hTest = hCodes.reset(name).matches(); 055 boolean iTest = iCodes.reset(name).matches(); 056 boolean dTest = dCodes.reset(name).matches(); 057 if (!aTest || aCodes.group(2).charAt(0) != type) { 058 throw new NamedBean.BadSystemNameException( 059 Bundle.getMessage(Locale.ENGLISH, "InvalidSystemNameInvalidPrefix", memo.getSystemPrefix() + type), 060 Bundle.getMessage(locale, "InvalidSystemNameInvalidPrefix", memo.getSystemPrefix() + type)); 061 } else if (hTest && hCodes.groupCount() == 4) { 062 // This is a PLaxx address - validate the house code and unit address fields 063 if (hCodes.group(3).charAt(0) < MIN_HOUSE_CODE || hCodes.group(3).charAt(0) > MAX_HOUSE_CODE) { 064 throw new NamedBean.BadSystemNameException( 065 Bundle.getMessage(Locale.ENGLISH, "InvalidSystemNameInvalidHouseCode", name), 066 Bundle.getMessage(locale, "InvalidSystemNameInvalidHouseCode", name)); 067 } 068 try { 069 int num; 070 num = Integer.parseInt(hCodes.group(4)); 071 if ((num < 1) || (num > 16)) { 072 throw new NamedBean.BadSystemNameException( 073 Bundle.getMessage(Locale.ENGLISH, "InvalidSystemNameInvalidDevice", name), 074 Bundle.getMessage(locale, "InvalidSystemNameInvalidDevice", name)); 075 } 076 } catch (NumberFormatException e) { 077 throw new NamedBean.BadSystemNameException( 078 Bundle.getMessage(Locale.ENGLISH, "InvalidSystemNameInvalidDevice", name), 079 Bundle.getMessage(locale, "InvalidSystemNameInvalidDevice", name)); 080 } 081 } else if (iTest) { 082 // This is a PLaa.bb.cc address - validate the Insteon address fields 083 if (iCodes.groupCount() != 5) { 084 throw new NamedBean.BadSystemNameException( 085 Bundle.getMessage(Locale.ENGLISH, "InvalidSystemNameInvalidInsteon", name), 086 Bundle.getMessage(locale, "InvalidSystemNameInvalidInsteon", name)); 087 } 088 } else if (dTest) { 089 // This is a PLnnn address - validate the DMX address fields 090 if (dCodes.groupCount() != 3) { 091 throw new NamedBean.BadSystemNameException( 092 Bundle.getMessage(Locale.ENGLISH, "InvalidSystemNameInvalidDmx", name), 093 Bundle.getMessage(locale, "InvalidSystemNameInvalidDmx", name)); 094 } 095 try { 096 int num; 097 num = Integer.parseInt(dCodes.group(3)); 098 if ((num < 1) || (num > 512)) { 099 throw new NamedBean.BadSystemNameException( 100 Bundle.getMessage(Locale.ENGLISH, "InvalidSystemNameInvalidDevice", name), 101 Bundle.getMessage(locale, "InvalidSystemNameInvalidDevice", name)); 102 } 103 } catch (NumberFormatException e) { 104 throw new NamedBean.BadSystemNameException( 105 Bundle.getMessage(Locale.ENGLISH, "InvalidSystemNameInvalidDevice", name), 106 Bundle.getMessage(locale, "InvalidSystemNameInvalidDevice", name)); 107 } 108 } else { 109 throw new NamedBean.BadSystemNameException( 110 Bundle.getMessage(Locale.ENGLISH, "InvalidSystemNameInvalidFormat", name), 111 Bundle.getMessage(locale, "InvalidSystemNameInvalidFormat", name)); 112 } 113 return name; 114 } 115 116 /** 117 * Public static method to validate system name format. 118 * 119 * @param systemName name to test 120 * @param type Letter indicating device type expected 121 * @return VALID if system name has a valid format, else return INVALID 122 */ 123 public NameValidity validSystemNameFormat(String systemName, char type) { 124 try { 125 validateSystemNameFormat(systemName, type, Locale.getDefault()); 126 } catch (IllegalArgumentException ex) { 127 // TODO: match possible prefixes as VALID_AS_PREFIX 128 return NameValidity.INVALID; 129 } 130 return NameValidity.VALID; 131 } 132 133 /** 134 * Public static method to validate system name for configuration returns 135 * 'true' if system name has a valid meaning in current configuration, else 136 * returns 'false'. 137 * 138 * @param systemName name to test 139 * @param type type to test 140 * @return true for valid names 141 */ 142 public boolean validSystemNameConfig(String systemName, char type) { 143 return validSystemNameFormat(systemName, type) == NameValidity.VALID; 144 } 145 146 /** 147 * Public static method determines whether a systemName names an Insteon 148 * device. 149 * 150 * @param systemName name to test 151 * @return true if system name corresponds to Insteon device 152 */ 153 public boolean isInsteon(String systemName) { 154 // ensure that input system name has a valid format 155 if ((!aCodes.reset(systemName).matches()) || (validSystemNameFormat(systemName, aCodes.group(2).charAt(0)) != NameValidity.VALID)) { 156 // No point in normalizing if a valid system name format is not present 157 return false; 158 } else { 159 if (hCodes.reset(systemName).matches() && hCodes.groupCount() == 4) { 160 // This is a PLaxx address 161 try { 162 return false; // is X10, or at least not Insteon 163 } catch (Exception e) { 164 log.error("illegal character in house code field system name: {}", systemName); 165 return false; // can't be parsed, isn't Insteon 166 } 167 } 168 } 169 return true; 170 } 171 172 /** 173 * Public static method to normalize a system name. 174 * <p> 175 * This routine is used to ensure that each system name is uniquely linked 176 * to one bit, by removing extra zeros inserted by the user. 177 * <p> 178 * If the supplied system name does not have a valid format, an empty string 179 * is returned. Otherwise a normalized name is returned in the same format 180 * as the input name. 181 * 182 * @param systemName name to process 183 * @return If the supplied system name does not have a valid format, an empty string 184 * is returned. Otherwise a normalized name is returned in the same format 185 * as the input name. 186 */ 187 public String normalizeSystemName(String systemName) { 188 // ensure that input system name has a valid format, test all formats 189 boolean aMatch = aCodes.reset(systemName).matches(); 190 int aCount = aCodes.groupCount(); 191 boolean hMatch = hCodes.reset(systemName).matches(); 192 int hCount = hCodes.groupCount(); 193 boolean iMatch = iCodes.reset(systemName).matches(); 194 int iCount = iCodes.groupCount(); 195 if (!aMatch || aCount != 2 || (validSystemNameFormat(systemName, aCodes.group(2).charAt(0)) != NameValidity.VALID)) { 196 // No point in normalizing if a valid system name format is not present 197 // DMX addresses are normalized already 198 return ""; 199 } 200 String nName = ""; 201 // check for the presence of a char to differentiate the two address formats 202 if (hMatch && hCount == 4) { 203 // This is a PLaxx address 204 nName = hCodes.group(1) + hCodes.group(2) + hCodes.group(3) + Integer.toString(Integer.parseInt(hCodes.group(4))); 205 } 206 if (nName.equals("")) { 207 // check for the presence of a char to differentiate the two address formats 208 if (iMatch && iCount == 5) { 209 // This is a PLaa.bb.cc Insteon address 210 nName = iCodes.group(1) + iCodes.group(2) + iCodes.group(3) + "." + iCodes.group(4) + "." + iCodes.group(5); 211 } else { 212 if (log.isDebugEnabled()) { 213 log.debug("valid name doesn't normalize: {} hMatch: {} hCount: {}", systemName, hMatch, hCount); 214 } 215 } 216 } 217 return nName; 218 } 219 220 /** 221 * Extract housecode from system name, as a letter A-P. 222 * <p> 223 * If the supplied system name does not have a valid format, an empty string 224 * is returned. 225 * 226 * @param systemName system name 227 * @return house code letter 228 */ 229 public String houseCodeFromSystemName(String systemName) { 230 String hCode = ""; 231 // ensure that input system name has a valid format 232 if ((!aCodes.reset(systemName).matches()) || (validSystemNameFormat(systemName, aCodes.group(2).charAt(0)) != NameValidity.VALID)) { 233 // No point in normalizing if a valid system name format is not present 234 } else { 235 if (hCodes.reset(systemName).matches() && hCodes.groupCount() == 2) { 236 // This is a PLaxx address 237 try { 238 hCode = hCodes.group(1); 239 } catch (Exception e) { 240 log.error("illegal character in house code field system name: {}", systemName); 241 return ""; 242 } 243 } 244 } 245 return hCode; 246 } 247 248 /** 249 * Extract devicecode from system name, as a string 1-16. 250 * 251 * @param systemName name 252 * @return If the supplied system name does not have a valid format, an empty string 253 * is returned. X10 type device code 254 */ 255 public String deviceCodeFromSystemName(String systemName) { 256 String dCode = ""; 257 // ensure that input system name has a valid format 258 if ((!aCodes.reset(systemName).matches()) || (validSystemNameFormat(systemName, aCodes.group(2).charAt(0)) != NameValidity.VALID)) { 259 // No point in normalizing if a valid system name format is not present 260 } else { 261 if (hCodes.reset(systemName).matches()) { 262 if (hCodes.groupCount() == 2) { 263 // This is a PLaxx address 264 try { 265 dCode = hCodes.group(2); 266 } catch (Exception e) { 267 log.error("illegal character in number field system name: {}", systemName); 268 return ""; 269 } 270 } 271 } else { 272 if (iCodes.reset(systemName).matches()) { 273 dCode = iCodes.group(3) + iCodes.group(4) + iCodes.group(5); 274 } else { 275 log.error("illegal insteon address: {}", systemName); 276 return ""; 277 } 278 } 279 } 280 return dCode; 281 } 282 283 /** 284 * Extract housecode from system name, as a value 1-16. 285 * <p> 286 * If the supplied system name does not have a valid format, an -1 is 287 * returned. 288 * 289 * @param systemName name 290 * @return valid 1-16, invalid, return -1 291 */ 292 public int x10HouseCodeAsValueFromSystemName(String systemName) { 293 int hCode = -1; 294 // ensure that input system name has a valid format 295 if ((!aCodes.reset(systemName).matches()) || (validSystemNameFormat(systemName, aCodes.group(2).charAt(0)) != NameValidity.VALID)) { 296 // No point in normalizing if a valid system name format is not present 297 } else { 298 if (hCodes.reset(systemName).matches() && hCodes.groupCount() == 4) { 299 // This is a PLaxx address 300 try { 301 hCode = hCodes.group(3).charAt(0) - 0x40; 302 } catch (Exception e) { 303 log.error("illegal character in number field system name: {}", systemName); 304 return -1; 305 } 306 } 307 } 308 return hCode; 309 } 310 311 /** 312 * Extract devicecode from system name, as a value 1-16. 313 * <p> 314 * If the supplied system name does not have a valid format, an -1 is 315 * returned. 316 * 317 * @param systemName name 318 * @return value of X10 device code, -1 if invalid 319 */ 320 public int x10DeviceCodeAsValueFromSystemName(String systemName) { 321 int dCode = -1; 322 // ensure that input system name has a valid format 323 if ((!aCodes.reset(systemName).matches()) || (validSystemNameFormat(systemName, aCodes.group(2).charAt(0)) != NameValidity.VALID)) { 324 // No point in normalizing if a valid system name format is not present 325 } else { 326 if (hCodes.reset(systemName).matches() && hCodes.groupCount() == 4) { 327 // This is a PLaxx address 328 try { 329 dCode = Integer.parseInt(hCodes.group(4)); 330 } catch (NumberFormatException e) { 331 log.error("illegal character in number field system name: {}", systemName); 332 return -1; 333 } 334 } 335 } 336 return dCode; 337 } 338 339 /** 340 * Extract Insteon high device id from system name. 341 * <p> 342 * If the supplied system name does not have a valid format, an empty string 343 * is returned. 344 * 345 * @param systemName name 346 * @return Insteon high byte value 347 */ 348 public int insteonIdHighCodeAsValueFromSystemName(String systemName) { 349 int dCode = -1; 350 // ensure that input system name has a valid format 351 if (!iCodes.reset(systemName).matches() || validSystemNameFormat(systemName, iCodes.group(2).charAt(0)) != NameValidity.VALID) { 352 // No point in normalizing if a valid system name format is not present 353 } else { 354 if (iCodes.groupCount() == 5) { 355 // This is a PLhh.mm.ll address 356 try { 357 dCode = Integer.parseInt(iCodes.group(3), 16); 358 } catch (NumberFormatException e) { 359 log.error("illegal character in high id system name: {}", systemName); 360 return -1; 361 } 362 } 363 } 364 return dCode; 365 } 366 367 /** 368 * Extract Insteon middle device id from system name. 369 * <p> 370 * If the supplied system name does not have a valid format, an empty string 371 * is returned. 372 * 373 * @param systemName name 374 * @return Insteon middle id value, -1 if invalid 375 */ 376 public int insteonIdMiddleCodeAsValueFromSystemName(String systemName) { 377 int dCode = -1; 378 // ensure that input system name has a valid format 379 if (!iCodes.reset(systemName).matches() || validSystemNameFormat(systemName, iCodes.group(2).charAt(0)) != NameValidity.VALID) { 380 // No point in normalizing if a valid system name format is not present 381 } else { 382 if (iCodes.groupCount() == 5) { 383 // This is a PLhh.mm.ll address 384 try { 385 dCode = Integer.parseInt(iCodes.group(4), 16); 386 } catch (NumberFormatException e) { 387 log.error("illegal character in high id system name: {}", systemName); 388 return -1; 389 } 390 } 391 } 392 return dCode; 393 } 394 395 /** 396 * Extract Insteon low device id from system name. 397 * <p> 398 * If the supplied system name does not have a valid format, an empty string 399 * is returned. 400 * 401 * @param systemName name 402 * @return Insteon low value id, -1 if invalid 403 */ 404 public int insteonIdLowCodeAsValueFromSystemName(String systemName) { 405 int dCode = -1; 406 // ensure that input system name has a valid format 407 if (!iCodes.reset(systemName).matches() || validSystemNameFormat(systemName, iCodes.group(2).charAt(0)) != NameValidity.VALID) { 408 // No point in normalizing if a valid system name format is not present 409 } else { 410 if (iCodes.groupCount() == 5) { 411 // This is a PLhh.mm.ll address 412 try { 413 dCode = Integer.parseInt(iCodes.group(5), 16); 414 } catch (NumberFormatException e) { 415 log.error("illegal character in high id system name: {}", systemName); 416 return -1; 417 } 418 } 419 } 420 return dCode; 421 } 422 423 /** 424 * Extract DMX unit id from system name. 425 * <p> 426 * If the supplied system name does not have a valid format, an empty string 427 * is returned. 428 * 429 * @param systemName name 430 * @return dmx unit id, -1 if invalid 431 */ 432 433 public int dmxUnitIdCodeAsValueFromSystemName(String systemName) { 434 int dCode = -1; 435 // ensure that input system name has a valid format 436 if (!dCodes.reset(systemName).matches() || validSystemNameFormat(systemName, dCodes.group(2).charAt(0)) != NameValidity.VALID) { 437 // No point in normalizing if a valid system name format is not present 438 } else { 439 if (dCodes.groupCount() == 3) { 440 // This is a PLddd address 441 try { 442 dCode = Integer.parseInt(dCodes.group(3)); 443 } catch (NumberFormatException e) { 444 log.error("illegal character in dmx unit id system name: {}", systemName); 445 return -1; 446 } 447 } 448 } 449 return dCode; 450 } 451 452 private final static Logger log = LoggerFactory.getLogger(SerialAddress.class); 453 454}