001package jmri.jmrix.acela;
002
003import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
004import javax.annotation.Nonnull;
005import jmri.Manager.NameValidity;
006import org.slf4j.Logger;
007import org.slf4j.LoggerFactory;
008
009/**
010 * Utility Class supporting parsing and testing of addresses for Acela.
011 * <p>
012 * One address format is supported: Atxxxx where: t is the type code, 'T' for
013 * turnouts, 'S' for sensors, and 'L' for lights xxxx is a bit number of the
014 * input or output bit (0-16383) examples: AT2 (bit 2), AS1003 (bit 1003), AL134
015 * (bit134).<p>
016 * Note: Not fully supporting long system connection prefix yet
017 *
018 * @author Dave Duchamp, Copyright (C) 2004 - 2006
019 * @author Bob Coleman Copyright (C) 2007, 2008, 2009 Based on CMRI serial
020 * example, modified to establish Acela support.
021 */
022public class AcelaAddress {
023
024    public AcelaAddress() {
025    }
026    
027    static final int MINSENSORADDRESS = 0;
028    static final int MAXSENSORADDRESS = AcelaNode.MAXSENSORBITS * AcelaNode.MAXNODE -1;
029    static final int MINOUTPUTADDRESS = 0;
030    static final int MAXOUTPUTADDRESS = AcelaNode.MAXOUTPUTBITS * AcelaNode.MAXNODE -1;
031
032    /**
033     * Public static method to parse an Acela system name and return the Acela
034     * Node Address.
035     * <p>
036     * Note: Returns '-1' if illegal systemName format or if the
037     * node is not found.
038     * Nodes are numbered from 0 - {@value AcelaNode#MAXNODE}.
039     * @param systemName system name.
040     * @param memo system connection.
041     * @return node address number.
042     */
043    public static int getNodeAddressFromSystemName(String systemName, AcelaSystemConnectionMemo memo) {
044        // validate the system Name leader characters
045        if (validSystemNameFormat(systemName, systemName.charAt(memo.getSystemPrefix().length()), memo.getSystemPrefix()) != NameValidity.VALID) {
046            // No point in trying if a valid system name format is not present
047            return (-1);
048        }
049        int num = getBitFromSystemName(systemName, memo.getSystemPrefix());
050        if (num < 0) {
051            log.error("invalid Acela system name: {}", systemName);
052            return (-1);
053        }
054        // This is a ALnnxxx address
055        int nodeaddress = -1;
056        if (systemName.charAt(memo.getSystemPrefix().length()) == 'S') {
057            // Acela has two address spaces: true == sensor address space; false == output address space
058            nodeaddress = memo.getTrafficController().lookupAcelaNodeAddress(num, true);
059        } else {
060            // Acela has two address spaces: true == sensor address space; false == output address space
061            nodeaddress = memo.getTrafficController().lookupAcelaNodeAddress(num, false);
062        }
063        return (nodeaddress);
064    }
065
066    /**
067     * Public static method to parse an Acela system name.
068     *
069     * @param systemName system name to parse.
070     * @param memo system connection.
071     * @return the Acela Node number, return 'null' if illegal systemName format or if the node is
072     * not found
073     */
074    public static AcelaNode getNodeFromSystemName(String systemName, AcelaSystemConnectionMemo memo) {
075        // get the node address
076        int ua;
077
078        ua = getNodeAddressFromSystemName(systemName, memo);
079        if (ua == -1) {
080            // error messages have already been issued by getNodeAddressFromSystemName
081            return null;
082        }
083
084        AcelaNode tempnode;
085        tempnode = (AcelaNode) (memo.getTrafficController().getNodeFromAddress(ua));
086
087        return tempnode;
088    }
089
090    /**
091     * Public static method to parse an Acela system name and return the bit number.
092     * Note: Bits are numbered from 1.
093     *
094     * @param systemName system name.
095     * @param prefix bean type, S, T, L or H.
096     * @return the bit number, return -1 if an error is found (0 is a valid bit?)
097     */
098    public static int getBitFromSystemName(String systemName, String prefix) {
099        // validate the System Name leader characters
100        if (!(systemName.startsWith(prefix)) || ((systemName.charAt(prefix.length()) != 'L')
101                && (systemName.charAt(prefix.length()) != 'S') && (systemName.charAt(prefix.length()) != 'T')
102                && (systemName.charAt(prefix.length()) != 'H'))) {
103            // here if an invalid Acela format
104            log.error("illegal character in header field of system name: {}", systemName);
105            return (-1);
106        }
107        // try to parse remaining system name part
108        int num = -1;
109        try {
110            num = Integer.parseInt(systemName.substring(prefix.length() + 1)); // multi char prefix
111        } catch (NumberFormatException e) {
112            log.warn("invalid character in number field of system name: {}", systemName);
113            return (-1);
114        }
115        if (num < 0) {
116            log.warn("invalid Acela system name: {}", systemName);
117            return (-1);
118        }
119        return (num);
120    }
121
122    /**
123     * Public static method to validate system name format.
124     * Logging of handled cases no higher than WARN.
125     *
126     * @param systemName system name to validate.
127     * @param type bean type, S, T or L.
128     * @param prefix system prefix.
129     * @return 'true' if system name has a valid format, else return 'false'
130     */
131    public static NameValidity validSystemNameFormat(@Nonnull String systemName, char type, String prefix) {
132        // validate the system Name leader characters
133        if (!systemName.startsWith(prefix + type )) {
134            // here if an illegal format 
135            log.error("invalid character in header field of system name: {}", systemName);
136            return NameValidity.INVALID;
137        }
138        int num;
139        try {
140            num = Integer.parseInt(systemName.substring(prefix.length() + 1));
141        } catch (NumberFormatException e) {
142            log.debug("invalid character in number field of system name: {}", systemName);
143            return NameValidity.INVALID;
144        }
145        if (num >= 0) {
146            // This is an ALnnxxx address
147            return NameValidity.VALID;
148        } else {
149            log.debug("invalid Acela system name: {}", systemName);
150            return NameValidity.INVALID;
151        }
152    }
153
154    /**
155     * Public static method to validate Acela system name for configuration.
156     *
157     * @param systemName system name to validate.
158     * @param type bean type, S, T or L.
159     * @param memo system connection.
160     * @return 'true' if system name has a valid meaning in current
161     * configuration, else return 'false'
162     */
163    @SuppressFBWarnings(value = "DB_DUPLICATE_SWITCH_CLAUSES", justification="additional check for valid bit value")
164    public static boolean validSystemNameConfig(String systemName, char type, AcelaSystemConnectionMemo memo) {
165        if (validSystemNameFormat(systemName, type, memo.getSystemPrefix()) != NameValidity.VALID) {
166            // No point in trying if a valid system name format is not present
167            return false;
168        }
169        AcelaNode node = getNodeFromSystemName(systemName, memo);
170        if (node == null) {
171            // The node indicated by this system address is not present
172            return false;
173        }
174        int bit = getBitFromSystemName(systemName, memo.getSystemPrefix());
175        switch (type) {
176            case 'T':
177            case 'L':
178                if ((bit >= MINOUTPUTADDRESS) && (bit <= MAXOUTPUTADDRESS)) {
179                    // The bit is within valid range for this defined Acela node
180                    return true;
181                }
182                break;
183            case 'S':
184                if ((bit >= MINSENSORADDRESS) && (bit <= MAXSENSORADDRESS)) {
185                    // The bit is within valid range for this defined Acela node
186                    return true;
187                }
188                break;
189            default:
190                log.error("Invalid type specification in validSystemNameConfig call");
191                return false;
192        }
193        // System name has failed all tests
194        log.warn("Acela hardware address out of range in system name: {}", systemName);
195        return false;
196    }
197
198    public static boolean validSystemNameConfig(String systemName, AcelaSystemConnectionMemo memo) {
199        char type = systemName.charAt(memo.getSystemPrefix().length());
200        return validSystemNameConfig(systemName, type, memo);
201    }
202
203    /**
204     * Public static method to convert one format Acela system name for the
205     * alternate format.
206     *
207     * @param systemName system name to convert.
208     * @param prefix system prefix.
209     * @return name (string) in alternate format, or empty string if the supplied
210     * system name does not have a valid format, or if there is no representation
211     * in the alternate naming scheme.
212     */
213    public static String convertSystemNameToAlternate(String systemName, String prefix) {
214        // ensure that input system name has a valid format
215        if (validSystemNameFormat(systemName, systemName.charAt(prefix.length()), prefix) != NameValidity.VALID) {
216            // No point in trying if a valid system name format is not present
217            return "";
218        }
219        String altName = "";
220        altName = systemName;
221        return altName;
222    }
223
224    /**
225     * Public static method to normalize an Acela system name.
226     * <p>
227     * This routine is used to ensure that each system name is uniquely linked
228     * to one Acela bit, by removing extra zeros inserted by the user.
229     *
230     * @param systemName system name to normalize.
231     * @param prefix system prefix.
232     * @return a normalized name is returned in the same format as the input name,
233     * or an empty string if the supplied system name does not have a valid format.
234     */
235    public static String normalizeSystemName(String systemName, String prefix) {
236        // ensure that input system name has a valid format
237        if (validSystemNameFormat(systemName, systemName.charAt(prefix.length()), prefix) != NameValidity.VALID) {
238            // No point in normalizing if a valid system name format is not present
239            return "";
240        }
241        // check if bit number is within the valid range
242        int bitNum = getBitFromSystemName(systemName, prefix);
243        char type = systemName.charAt(prefix.length());
244        if (bitNum < 0) {
245            return "";
246        }
247        // everything OK, normalize the address
248        String nName = "";
249        nName = prefix + type + Integer.toString(bitNum);
250        return nName;
251    }
252
253    /**
254     * Public static method to construct an Acela system name from type
255     * character, node address, and bit number.
256     *
257     * @param type bean type letter, S, T or L.
258     * @param nAddress node address.
259     * @param bitNum bit number.
260     * @param memo system connection.
261     * @return a system name in the ALxxxx, ATxxxx, or ASxxxx
262     * format. The returned name is normalized.
263     * Return the null string "" if the supplied character is not valid,
264     * or if the node address is out of the 0 - 127 range, or the bit number is
265     * out of the 1 - 2048 range and an error message is logged.
266     */
267    public static String makeSystemName(String type, int nAddress, int bitNum, AcelaSystemConnectionMemo memo) {
268        String nName = "";
269        // check the type character
270        if (!type.equalsIgnoreCase("S") && !type.equalsIgnoreCase("L") && !type.equalsIgnoreCase("T")) {
271            // here if an illegal type character 
272            log.error("invalid type character proposed for system name");
273            return (nName);
274        }
275        // check the node address
276        if ((nAddress < memo.getTrafficController().getMinimumNodeAddress()) || (nAddress > memo.getTrafficController().getMaximumNumberOfNodes())) {
277            // here if an illegal node address 
278            log.warn("invalid node adddress proposed for system name");
279            return (nName);
280        }
281        // check the bit number
282        if (type.equalsIgnoreCase("S") && ((bitNum < 0) || (bitNum > MAXSENSORADDRESS))) {
283            // here if an illegal bit number 
284            log.warn("invalid bit number proposed for Acela Sensor");
285            return (nName);
286        }
287        if ((type.equalsIgnoreCase("L") || type.equalsIgnoreCase("T")) && ((bitNum < 0) || (bitNum > MAXOUTPUTADDRESS))) {
288            // here if an illegal bit number 
289            log.warn("invalid bit number proposed for Acela Turnout or Light");
290            return (nName);
291        }
292        // construct the address
293        nName = memo.getSystemPrefix() + type + Integer.toString(bitNum);
294        return (nName);
295    }
296
297    /**
298     * Public static method to check the user name for a valid system name.
299     *
300     * @param systemName system name to check.
301     * @param prefix bean prefix, S, T or L.
302     * @return "" (null string) if the system name is not valid or does not exist
303     */
304    public static String getUserNameFromSystemName(String systemName, String prefix) {
305        // check for a valid system name
306        if ((systemName.length() < (prefix.length() + 2)) || (!systemName.startsWith(prefix))) {
307            // not a valid system name for Acela
308            return ("");
309        }
310        // check for a sensor
311        if (systemName.charAt(prefix.length() + 1) == 'S') {
312            jmri.Sensor s = null;
313            s = jmri.InstanceManager.sensorManagerInstance().getBySystemName(systemName);
314            if (s != null) {
315                return s.getUserName();
316            } else {
317                return ("");
318            }
319        } // check for a turnout
320        else if (systemName.charAt(prefix.length() + 1) == 'T') {
321            jmri.Turnout t = null;
322            t = jmri.InstanceManager.turnoutManagerInstance().getBySystemName(systemName);
323            if (t != null) {
324                return t.getUserName();
325            } else {
326                return ("");
327            }
328        } // check for a light
329        else if (systemName.charAt(prefix.length() + 1) == 'L') {
330            jmri.Light lgt = null;
331            lgt = jmri.InstanceManager.lightManagerInstance().getBySystemName(systemName);
332            if (lgt != null) {
333                return lgt.getUserName();
334            } else {
335                return ("");
336            }
337        }
338
339        // not any known sensor, light, or turnout
340        return ("");
341    }
342
343    private final static Logger log = LoggerFactory.getLogger(AcelaAddress.class);
344
345}