001package jmri.jmrix.maple; 002 003import java.util.Locale; 004import jmri.Manager; 005import jmri.Manager.NameValidity; 006import org.slf4j.Logger; 007import org.slf4j.LoggerFactory; 008 009import javax.annotation.Nonnull; 010 011/** 012 * Utility Class supporting parsing and testing of Maple addresses. 013 * <p> 014 * One address format is supported: Ktxxxx where: 015 * <ul> 016 * <li>K is (user configurable) system prefix for Maple</li> 017 * <li>t is the type code: 'T' for turnouts, 'S' for sensors, 018 * and 'L' for lights</li> 019 * <li>xxxx is a bit number of the input or output bit (001-9999)</li> 020 * </ul> 021 * Note: with Maple, all panels (nodes) have the 022 * same address space, so there is no node number in the address. 023 * 024 * @author Dave Duchamp, Copyright (C) 2004 - 2009 025 * @author Egbert Broerse, Copyright (C) 2017 026 */ 027public class SerialAddress { 028 029 public SerialAddress() { 030 } 031 032 /** 033 * Public static method to parse a Maple system name and return the bit number. 034 * <p> 035 * Notes: Bits are numbered from 1. 036 * 037 * @param systemName system name. 038 * @param prefix system prefix. 039 * @return the bit number, return 0 if an error is found 040 */ 041 public static int getBitFromSystemName(String systemName, String prefix) { 042 if (prefix.length() < 1) { 043 return 0; 044 } 045 log.debug("systemName = {}", systemName); 046 log.debug("prefix = {}", prefix); 047 // validate the system Name leader characters 048 if (!(systemName.startsWith(prefix)) || ((systemName.charAt(prefix.length()) != 'L') 049 && (systemName.charAt(prefix.length()) != 'S') && (systemName.charAt(prefix.length()) != 'T'))) { 050 // here if an illegal format 051 log.debug("invalid character in header field of system name: {}", systemName); 052 return (0); 053 } 054 // try to parse remaining system name part 055 int num = 0; 056 try { 057 num = Integer.parseInt(systemName.substring(prefix.length() + 1)); // multi char prefix 058 } catch (NumberFormatException ex) { 059 log.warn("invalid character in number field of system name: {}", systemName); 060 return (0); 061 } 062 if (num <= 0) { 063 log.debug("invalid Maple system name: {}", systemName); 064 return (0); 065 } 066 return (num); 067 } 068 069 /** 070 * Validate the system name. 071 * 072 * @param name the name to validate 073 * @param manager the manager requesting validation 074 * @param locale the locale for user messages 075 * @return the name; unchanged 076 * @throws IllegalArgumentException if name is not valid 077 * @see Manager#validateSystemNameFormat(java.lang.String, java.util.Locale) 078 */ 079 public static String validateSystemNameFormat(String name, Manager<?> manager, Locale locale) throws IllegalArgumentException { 080 int max = manager.typeLetter() == 'S' ? 1000 : 8000; 081 return manager.validateIntegerSystemNameFormat(name, 0, max, locale); 082 } 083 084 /** 085 * Public static method to validate system name format. 086 * 087 * @param systemName system name to validate. 088 * @param type bean type, ie S for Sensor, T for Turnout. 089 * @param prefix system prefix. 090 * @return 'true' if system name has a valid format, 091 * else returns 'false' 092 */ 093 public static NameValidity validSystemNameFormat(@Nonnull String systemName, char type, String prefix) { 094 // validate the system Name leader characters 095 if (!(systemName.startsWith(prefix)) || (systemName.charAt(prefix.length()) != type )) { 096 // here if an illegal format 097 log.error("invalid character in header field of system name: {}", systemName); 098 return NameValidity.INVALID; 099 } 100 if (systemName.length() <= prefix.length() + 1) { 101 log.warn("missing numerical node address in system name: {}", systemName); 102 return NameValidity.INVALID; 103 } 104 // This is a KLxxxx (or KTxxxx or KSxxxx) address, make sure xxxx is OK 105 int bit = getBitFromSystemName(systemName, prefix); 106 // now check range 107 if ((bit <= 0) || (type == 'S' && bit > 1000) || (bit > 8000)) { 108 log.warn("node address field out of range in system name - {}", systemName); 109 return NameValidity.INVALID; 110 } 111 return NameValidity.VALID; 112 } 113 114 /** 115 * Public static method to validate system name for configuration. 116 * 117 * @param systemName system name to validate. 118 * @param type bean type, ie S for Sensor, T for Turnout. 119 * @param memo system connection. 120 * @return 'true' if system name has a valid meaning in current configuration, 121 * else returns 'false' 122 */ 123 public static boolean validSystemNameConfig(String systemName, char type, MapleSystemConnectionMemo memo) { 124 if (validSystemNameFormat(systemName, type, memo.getSystemPrefix()) != NameValidity.VALID) { 125 // No point in trying if a valid system name format is not present 126 return false; 127 } 128 int bit = getBitFromSystemName(systemName, memo.getSystemPrefix()); 129 switch (type) { 130 case 'T': 131 case 'L': 132 if ((bit > 0) && (bit <= OutputBits.getNumOutputBits())) { 133 // The bit is within valid range for this Maple configuration 134 return true; 135 } 136 break; 137 case 'S': 138 if ((bit > 0) && (bit <= InputBits.getNumInputBits())) { 139 // The bit is within valid range for this Maple configuration 140 return true; 141 } 142 break; 143 default: 144 log.error("Invalid type specification in validSystemNameConfig call"); 145 return false; 146 } 147 // System name has failed all tests 148 log.warn("Maple hardware address out of range in system name: {}", systemName); 149 return false; 150 } 151 152 /** 153 * Public static method to normalize a system name. 154 * <p> 155 * This routine is used to ensure that each system name is uniquely linked 156 * to a bit, by removing extra zeros inserted by the user. 157 * It's not applied to sensors (whick might be addressed using the KS3:5 format. 158 * 159 * @param systemName systemname to normalize. 160 * @param prefix system prefix. 161 * @return if the supplied system name does not have a valid format, an empty string 162 * is returned. If the address in the system name is not within the legal 163 * maximum range for the type of item (L, T, or S), an empty string is 164 * returned. Otherwise a normalized name is returned in the same format as 165 * the input name. 166 */ 167 public static String normalizeSystemName(String systemName, String prefix) { 168 if (prefix.length() < 1) { 169 log.error("invalid system name prefix: {}", prefix); 170 return ""; 171 } 172 // ensure that input system name has a valid format 173 // precheck startsWith(prefix) to pass jmri.managers.AbstractSensorMgrTestBase line 95/96 calling "foo" and "bar" 174 if ((systemName.length() < prefix.length() + 1) || (!systemName.startsWith(prefix)) || 175 (validSystemNameFormat(systemName, systemName.charAt(prefix.length()), prefix) != NameValidity.VALID)) { 176 // No point in normalizing if a valid system name format is not present 177 return ""; 178 } 179 // check if bit number is within the valid range 180 int bitNum = getBitFromSystemName(systemName, prefix); 181 char type = systemName.charAt(prefix.length()); 182 if ((bitNum <= 0) || ((type == 'S') && bitNum > 1000) || (bitNum > 8000)) { 183 log.warn("node address field out of range in system name - {}", systemName); 184 return ""; 185 } 186 // everything OK, normalize the address 187 String nName = ""; 188 nName = prefix + type + bitNum; 189 return nName; 190 } 191 192 /** 193 * Public static method to construct a system name from type character and 194 * bit number. 195 * <p> 196 * This routine returns a system name in the KLxxxx, KTxxxx, or KSxxxx 197 * format. The returned name is normalized. 198 * 199 * @param type bean type, ie S Sensor, T Turnout. 200 * @param bitNum bit number. 201 * @param prefix system prefix. 202 * @return "" (null string) if the supplied type character is not valid, 203 * or the bit number is out of the 1 - 9000 range, and an error message is 204 * logged. 205 */ 206 public static String makeSystemName(String type, int bitNum, String prefix) { 207 if (prefix.length() < 1) { 208 log.error("invalid system name prefix: {}", prefix); 209 return ""; 210 } 211 String nName = ""; 212 // check the type character 213 if ((!type.equals("S")) && (!type.equals("L")) && (!type.equals("T"))) { 214 // here if an illegal type character 215 log.error("illegal type character proposed for system name - {}", type); 216 return (nName); 217 } 218 // check the bit number 219 if ((bitNum < 1) || ((type.equals("S")) && (bitNum > 1000)) || (bitNum > 8000)) { 220 // here if an illegal bit number 221 log.warn("illegal address range proposed for system name - {}", bitNum); 222 return (nName); 223 } 224 // construct the address 225 nName = prefix + type + Integer.toString(bitNum); 226 return (nName); 227 } 228 229 /** 230 * Public static method to test if an output bit is free for assignment. 231 * 232 * @param bitNum bit number. 233 * @param prefix system prefix. 234 * @return "" (null string) if the specified output bit is free for 235 * assignment, else returns the system name of the conflicting assignment. 236 * Test is not performed if the node address or bit number are valid. 237 */ 238 public static String isOutputBitFree(int bitNum, String prefix) { 239 if (prefix.length() < 1) { 240 log.error("invalid system name prefix: {}", prefix); 241 return ""; 242 } 243 // check the bit number 244 if ((bitNum < 1) || (bitNum > 8000)) { 245 // here if an illegal bit number 246 log.error("illegal bit number in free bit test - {}", bitNum); 247 return (""); 248 } 249 // check for a turnout using the bit 250 jmri.Turnout t = null; 251 String sysName = ""; 252 sysName = makeSystemName("T", bitNum, prefix); 253 t = jmri.InstanceManager.turnoutManagerInstance().getBySystemName(sysName); 254 if (t != null) { 255 return (sysName); 256 } 257 // check for a two-bit turnout assigned to the previous bit 258 if (bitNum > 1) { 259 sysName = makeSystemName("T", bitNum - 1, prefix); 260 t = jmri.InstanceManager.turnoutManagerInstance().getBySystemName(sysName); 261 if (t != null) { 262 if (t.getNumberControlBits() == 2) { 263 // bit is second bit for this Turnout 264 return (sysName); 265 } 266 } 267 } 268 // check for a light using the bit 269 jmri.Light lgt = null; 270 sysName = makeSystemName("L", bitNum, prefix); 271 lgt = jmri.InstanceManager.lightManagerInstance().getBySystemName(sysName); 272 if (lgt != null) { 273 return (sysName); 274 } 275 // not assigned to a turnout or a light 276 return (""); 277 } 278 279 /** 280 * Public static method to test if an input bit is free for assignment. 281 * 282 * @param bitNum bit number. 283 * @param prefix system prefix. 284 * @return "" (empty string) if the specified input bit is free for 285 * assignment, else returns the system name of the conflicting assignment. 286 * Test is not performed if the node address is illegal or bit number is 287 * valid. 288 */ 289 public static String isInputBitFree(int bitNum, String prefix) { 290 if (prefix.length() < 1) { 291 log.error("invalid system name prefix: {}", prefix); 292 return ""; 293 } 294 // check the bit number 295 if ((bitNum < 1) || (bitNum > 1000)) { 296 // here if an illegal bit number 297 log.error("illegal bit number in free bit test"); 298 return (""); 299 } 300 // check for a sensor using the bit 301 jmri.Sensor s = null; 302 String sysName = ""; 303 sysName = makeSystemName("S", bitNum, prefix); 304 s = jmri.InstanceManager.sensorManagerInstance().getBySystemName(sysName); 305 if (s != null) { 306 return (sysName); 307 } 308 // not assigned to a sensor 309 return (""); 310 } 311 312 /** 313 * Public static method to get the user name for a valid system name. 314 * 315 * @param systemName system name. 316 * @param prefix system prefix. 317 * @return "" (empty string) if the system name is not valid or does not exist 318 */ 319 public static String getUserNameFromSystemName(String systemName, String prefix) { 320 if (prefix.length() < 1) { 321 log.error("invalid system name prefix: {}", prefix); 322 return ""; 323 } 324 // check for a valid system name 325 if ((systemName.length() < (prefix.length() + 2)) || (!systemName.startsWith(prefix))) { // use multi char prefix 326 // not a valid system name 327 return (""); 328 } 329 // check for a sensor 330 if (systemName.charAt(prefix.length()) == 'S') { 331 jmri.Sensor s = null; 332 s = jmri.InstanceManager.sensorManagerInstance().getBySystemName(systemName); 333 if (s != null) { 334 return s.getUserName(); 335 } else { 336 return (""); 337 } 338 } // check for a turnout 339 else if (systemName.charAt(prefix.length()) == 'T') { 340 jmri.Turnout t = null; 341 t = jmri.InstanceManager.turnoutManagerInstance().getBySystemName(systemName); 342 if (t != null) { 343 return t.getUserName(); 344 } else { 345 return (""); 346 } 347 } // check for a light 348 else if (systemName.charAt(prefix.length()) == 'L') { 349 jmri.Light lgt = null; 350 lgt = jmri.InstanceManager.lightManagerInstance().getBySystemName(systemName); 351 if (lgt != null) { 352 return lgt.getUserName(); 353 } else { 354 return (""); 355 } 356 } 357 // not any known sensor, light, or turnout 358 return (""); 359 } 360 361 private final static Logger log = LoggerFactory.getLogger(SerialAddress.class); 362 363}