001package jmri.jmrix.roco.z21;
002
003import java.util.Locale;
004import javax.annotation.Nonnull;
005import jmri.Manager;
006import jmri.Manager.NameValidity;
007import jmri.NamedBean;
008import jmri.ReporterManager;
009
010/**
011 * Utility Class supporting parsing and testing of addresses for Z21 CanBus
012 * <p>
013 * One address format is supported for Reporters and Sensors:
014 * <ul>
015 * <li>
016 * Ztmm:pp where t is either R or S, mm is the module address and pp is the contact pin number (1-8).
017 * </li>
018 * </ul>
019 *
020 * @author Dave Duchamp, Copyright (C) 2004 - 2006
021 * @author Bob Coleman Copyright (C) 2007, 2008, 2009
022 * @author Egbert Broerse (C) 2017 Based on Acela example, modified for XpressNet.
023 */
024public class Z21CanBusAddress {
025
026    private Z21CanBusAddress() {
027        // this is a class of static methods.
028    }
029
030    /**
031     * Public static method to parse a Z21CanBus system name.
032     * Note: Bits are numbered from 0.
033     *
034     * @param systemName system name.
035     * @param prefix system prefix.
036     * @return the hardware address number, return -1 if an error is found
037     */
038    public static int getBitFromSystemName(String systemName, String prefix) {
039        // validate the system Name leader characters
040        if(!systemNameStartsWithPrefix(systemName,prefix)) {
041            return (-1);
042        }
043        // name must be in the Ztmm:pp format (Z is user
044        // configurable)
045        try {
046            String curAddress = systemName.substring(prefix.length() + 1);
047            if( ( systemName.charAt(prefix.length())=='R' ||
048                  systemName.charAt(prefix.length())=='r' ||
049                   systemName.charAt(prefix.length())=='S' ||
050                  systemName.charAt(prefix.length())=='s' ) &&
051                  curAddress.contains(":")) {
052               //Address format passed is in the form of encoderAddress:input
053               int seperator = curAddress.indexOf(':');
054               int encoderAddress = parseEncoderAddress(curAddress,0,seperator);
055               log.debug("found module address {}",encoderAddress);
056                // since we aren't supporting bit number, just return the contact
057                // since we know now the module address is valid.
058               return Integer.parseInt(curAddress.substring(seperator + 1));
059            } else {
060               log.warn("system name {} is in the wrong format.  Should be mm:pp.",systemName);
061            }
062        } catch (NumberFormatException e) {
063            log.warn("invalid character in number field of system name: {}", systemName);
064        }
065        return (-1);
066    }
067
068    private static boolean systemNameStartsWithPrefix(String systemName,String prefix){
069        if (!systemName.startsWith(prefix)) {
070            // here if an invalid Z21 Can Bus system name
071            log.error("invalid character in header field of Z21 Can Bus system name: {}", systemName);
072            return false;
073        }
074        return true;
075    }
076
077    private static int parseEncoderAddress(String addressWithoutPrefix,int start, int end) {
078       int encoderAddress;
079       try {
080          encoderAddress = Integer.parseInt(addressWithoutPrefix.substring(start,end));
081       } catch (NumberFormatException ex) {
082          // didn't parse as a decimal, check to see if network ID
083          // was used instead.
084          encoderAddress = Integer.parseInt(addressWithoutPrefix.substring(start,end),16);
085       }
086       return encoderAddress;
087    }
088
089    public static String getEncoderAddressString(String systemName, String prefix) {
090
091        // validate the system Name leader characters
092        if (!systemNameStartsWithPrefix(systemName, prefix)) {
093            throw new NamedBean.BadSystemNameException(
094                    Bundle.getMessage(Locale.ENGLISH, "InvalidSystemNameInvalidPrefix", prefix),
095                    Bundle.getMessage("InvalidSystemNameInvalidPrefix", prefix));
096
097        }
098        int seperator = systemName.indexOf(':');
099        return systemName.substring(prefix.length() + 1,seperator);
100    }
101
102    /**
103     * Validate a system name format.
104     *
105     * @param name    the name to validate
106     * @param manager the manager requesting validation
107     * @param locale  the locale for user messages
108     * @return name, unchanged
109     * @see jmri.Manager#validateSystemNameFormat(java.lang.String,
110     * java.util.Locale)
111     */
112    public static String validateSystemNameFormat(String name, Manager<?> manager, Locale locale) {
113        name = manager.validateSystemNamePrefix(name, locale);
114        String[] parts = name.substring(manager.getSystemNamePrefix().length()).split(":");
115        if (parts.length != 2) {
116            throw newBadSystemNameException(name,"SystemNameInvalidMissingParts",locale);
117        }
118        int num;
119        try {
120            num = parseEncoderAddress(parts[0],0,parts[0].length());
121            if (num < 0 || num > 65535) {
122                throw newBadSystemNameException(name, "SysteNameInvalidCanAddress", locale);
123            }
124        } catch (NumberFormatException ex) {
125            throw newBadSystemNameException(name,"SysteNameInvalidCanAddress",locale);
126        }
127        try {
128            num = Integer.parseInt(parts[1]);
129            if (num < 0 || num > 7) {
130                throw newBadSystemNameException(name,"SystemNameInvalidPin",locale);
131            }
132        } catch (NumberFormatException ex) {
133            throw newBadSystemNameException(name,"SystemNameInvalidPin",locale);
134        }
135        return name;
136    }
137
138    private static NamedBean.BadSystemNameException newBadSystemNameException(String name, String reasonKey, Locale locale){
139        return new NamedBean.BadSystemNameException(
140                Bundle.getMessage(Locale.ENGLISH, reasonKey, name),
141                Bundle.getMessage(locale, reasonKey, name));
142    }
143
144    /**
145     * Public static method to validate system name format.
146     * Logging of handled cases no higher than WARN.
147     *
148     * @param systemName system name.
149     * @param type bean type, S for Sensor, T for Turnout.
150     * @param prefix system prefix.
151     * @return VALID if system name has a valid format, else return INVALID
152     */
153    public static NameValidity validSystemNameFormat(@Nonnull String systemName, char type, String prefix) {
154        // validate the system Name leader characters
155        if (!(systemName.startsWith(prefix + type))) {
156            // here if an illegal format
157            log.error("invalid character in header field of system name: {}", systemName);
158            return NameValidity.INVALID;
159        }
160        if (getBitFromSystemName(systemName, prefix) >= 0) {
161            return NameValidity.VALID;
162        } else {
163            return NameValidity.INVALID;
164        }
165    }
166
167    /**
168     * Public static method to check the user name for a valid system name.
169     *
170     * @param systemName system name.
171     * @param prefix system prefix.
172     * @return "" (null string) if the system name is not valid or does not exist
173     */
174    public static String getUserNameFromSystemName(String systemName, String prefix) {
175        // check for a valid system name
176        if ((systemName.length() < (prefix.length() + 2)) || (!systemName.startsWith(prefix))) {
177            // not a valid system name for Z21 Can Bus
178            return ("");
179        }
180        // check for a Reporter
181        if (systemName.charAt(prefix.length() + 1) == 'R') {
182            jmri.Reporter r;
183            r = jmri.InstanceManager.getDefault(ReporterManager.class).getBySystemName(systemName);
184            if (r != null) {
185                return r.getUserName();
186            } else {
187                return ("");
188            }
189        }
190        // check for a Sensor
191        if (systemName.charAt(prefix.length() + 1) == 'S') {
192            jmri.Sensor s;
193            s = jmri.InstanceManager.sensorManagerInstance().getBySystemName(systemName);
194            if (s != null) {
195                return s.getUserName();
196            } else {
197                return ("");
198            }
199        }
200        // not any known sensor
201        return ("");
202    }
203
204    public static String buildDecimalSystemNameFromParts(String prefix, char typeLetter, int userAddress,int pin){
205        return String.format("%s%c%d:%d",prefix,typeLetter, userAddress,pin);
206    }
207
208    public static String buildHexSystemNameFromParts(String prefix, char typeLetter,int globalCANaddress,int pin){
209            return String.format("%s%c%4X:%d",prefix,typeLetter, globalCANaddress,pin);
210    }
211
212    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(Z21CanBusAddress.class);
213
214}