001package jmri.jmrix.cmri;
002
003import java.util.Comparator;
004import java.util.Locale;
005import java.util.ResourceBundle;
006import javax.annotation.Nonnull;
007import javax.annotation.CheckReturnValue;
008
009import jmri.*;
010import jmri.Manager.NameValidity;
011import jmri.jmrix.AbstractNode;
012import jmri.jmrix.ConfiguringSystemConnectionMemo;
013import jmri.jmrix.DefaultSystemConnectionMemo;
014import jmri.jmrix.cmri.serial.*;
015import jmri.jmrix.cmri.swing.CMRIComponentFactory;
016import jmri.jmrix.swing.ComponentFactory;
017import jmri.util.NamedBeanComparator;
018
019/**
020 * Minimal SystemConnectionMemo for C/MRI systems.
021 *
022 * @author Randall Wood
023 */
024public class CMRISystemConnectionMemo extends DefaultSystemConnectionMemo implements ConfiguringSystemConnectionMemo {
025
026    public CMRISystemConnectionMemo() {
027        this("C", CMRIConnectionTypeList.CMRI); // default to "C" prefix
028    }
029
030    public CMRISystemConnectionMemo(@Nonnull String prefix, @Nonnull String userName) {
031        super(prefix, userName);
032
033        InstanceManager.store(this, CMRISystemConnectionMemo.class); // also register as specific type
034
035        // create and register the ComponentFactory for the GUI
036        cf = new CMRIComponentFactory(this);
037        InstanceManager.store(cf, ComponentFactory.class);
038
039        log.debug("Created CMRISystemConnectionMemo");
040    }
041
042    private SerialTrafficController tc = null;
043    ComponentFactory cf = null;
044
045    /**
046     * Set the traffic controller instance associated with this connection memo.
047     *
048     * @param s jmri.jmrix.cmri.serial.SerialTrafficController object to use.
049     */
050    public void setTrafficController(SerialTrafficController s) {
051        tc = s;
052    }
053
054    /**
055     * Get the traffic controller instance associated with this connection memo.
056     *
057     * @return the traffic controller, created if needed
058     */
059    public SerialTrafficController getTrafficController() {
060        if (tc == null) {
061            setTrafficController(new SerialTrafficController());
062            log.debug("Auto create of SerialTrafficController for initial configuration");
063        }
064        return tc;
065    }
066
067    /**
068     * Get the user name for a valid system name.
069     *
070     * @param systemName the system name
071     * @return "" (null string) if the system name is not valid or does not
072     *         exist
073     */
074    public String getUserNameFromSystemName(String systemName) {
075        int offset = checkSystemPrefix(systemName);
076        if (offset < 1) {
077            return "";
078        }
079        if (systemName.length() < offset + 1) {
080            // not a valid system name for C/MRI
081            return "";
082        }
083        switch (systemName.charAt(offset)) {
084            case 'S':
085                Sensor s = InstanceManager.sensorManagerInstance().getBySystemName(systemName);
086                if (s != null) {
087                    return s.getUserName();
088                } else {
089                    return "";
090                }
091            case 'T':
092                Turnout t = InstanceManager.turnoutManagerInstance().getBySystemName(systemName);
093                if (t != null) {
094                    return t.getUserName();
095                } else {
096                    return "";
097                }
098            case 'L':
099                Light lgt = InstanceManager.lightManagerInstance().getBySystemName(systemName);
100                if (lgt != null) {
101                    return lgt.getUserName();
102                } else {
103                    return "";
104                }
105            default:
106                break;
107        }
108        // not any known sensor, light, or turnout
109        return "";
110    }
111
112    /**
113     * Get the bit number from a C/MRI system name. Bits are numbered from 1.
114     * Does not check whether that node is defined on current system.
115     *
116     * @param systemName the system name
117     * @return 0 if an error is found
118     */
119    public int getBitFromSystemName(String systemName) {
120        int offset = checkSystemPrefix(systemName);
121        if (offset < 1) {
122//            log.error("invalid system prefix in CMRI system name: {}", systemName); // fix test first
123            return 0;
124        }
125        if (validSystemNameFormat(systemName, systemName.charAt(offset)) != NameValidity.VALID) {
126            // No point in normalizing if a valid system name format is not present
127            return 0;
128        }
129        // Find the beginning of the bit number field
130        int k = 0;
131        for (int i = offset + 1; (i < systemName.length()) && (k == 0); i++) {
132            if (systemName.charAt(i) == 'B') {
133                k = i + 1;
134            }
135        }
136        int n; // bit number
137        if (k == 0) {
138            // here if 'B' not found, name must be CLnnxxx format
139            int num;
140            try {
141                num = Integer.parseInt(systemName.substring(offset + 1));
142            } catch (NumberFormatException e) {
143                log.warn("invalid character in number field of system name: {}", systemName);
144                return 0;
145            }
146            if (num > 0) {
147                n = num - ((num / 1000) * 1000);
148            } else {
149                log.warn("invalid CMRI system name: {}", systemName);
150                return 0;
151            }
152        } else { // k = position of "B" char in name
153            try {
154                n = Integer.parseInt(systemName.substring(k));
155            } catch (NumberFormatException e) {
156                log.warn("invalid character in bit number field of CMRI system name: {}", systemName);
157                return 0;
158            }
159        }
160        return n;
161    }
162
163    /**
164     * Check and skip the System Prefix string on a system name.
165     *
166     * @param systemName the system name
167     * @return offset of the 1st character past the prefix, or -1 if not valid
168     *         for this connection
169     */
170    public int checkSystemPrefix(String systemName) {
171        if (!systemName.startsWith(getSystemPrefix())) {
172            return -1;
173        }
174        return getSystemPrefix().length();
175    }
176
177    /**
178     * Test if a C/MRI output bit is free for assignment. Test is not performed
179     * if the node address or bit number is invalid.
180     *
181     * @param nAddress the node address
182     * @param bitNum   the output bit number
183     * @return "" (empty string) if the specified output bit is free for
184     *         assignment, else returns the system name of the conflicting
185     *         assignment.
186     */
187    public String isOutputBitFree(int nAddress, int bitNum) {
188        if ((nAddress < 0) || (nAddress > 127)) {
189            log.warn("invalid node address in free bit test");
190            return "";
191        }
192        if ((bitNum < 1) || (bitNum > 2048)) {
193            log.warn("invalid bit number in free bit test");
194            return "";
195        }
196        String sysName = makeSystemName("T", nAddress, bitNum);
197        Turnout t = InstanceManager.turnoutManagerInstance().getBySystemName(sysName);
198        if (t != null) {
199            return sysName;
200        }
201        String altName = convertSystemNameToAlternate(sysName);
202        t = InstanceManager.turnoutManagerInstance().getBySystemName(altName);
203        if (t != null) {
204            return altName;
205        }
206        if (bitNum > 1) {
207            sysName = makeSystemName("T", nAddress, bitNum - 1);
208            t = InstanceManager.turnoutManagerInstance().getBySystemName(sysName);
209            if (t != null) {
210                if (t.getNumberOutputBits() == 2) {
211                    return sysName;
212                }
213            } else {
214                altName = convertSystemNameToAlternate(sysName);
215                t = InstanceManager.turnoutManagerInstance().getBySystemName(altName);
216                if (t != null) {
217                    if (t.getNumberOutputBits() == 2) {
218                        return altName;
219                    }
220                }
221            }
222        }
223        sysName = makeSystemName("L", nAddress, bitNum);
224        Light lgt = InstanceManager.lightManagerInstance().getBySystemName(sysName);
225        if (lgt != null) {
226            return sysName;
227        }
228        altName = convertSystemNameToAlternate(sysName);
229        lgt = InstanceManager.lightManagerInstance().getBySystemName(altName);
230        if (lgt != null) {
231            return altName;
232        }
233        // not assigned to a turnout or a light
234        return "";
235    }
236
237    /**
238     * Normalize a C/MRI system name.
239     * <p>
240     * This routine is used to ensure that each system name is uniquely linked
241     * to one C/MRI bit, by removing extra zeros inserted by the user.
242     *
243     * @param systemName the system name
244     * @return "" (empty string) if the supplied system name does not have a
245     *         valid format. Otherwise a normalized name is returned in the same
246     *         format as the input name.
247     */
248    public String normalizeSystemName(String systemName) {
249        int offset = checkSystemPrefix(systemName);
250        if (offset < 1) {
251//            log.error("invalid system prefix in CMRI system name: {}", systemName); // fix test first
252            return "";
253        }
254        if (validSystemNameFormat(systemName, systemName.charAt(offset)) != NameValidity.VALID) {
255            // No point in normalizing if a valid system name format is not present
256            return "";
257        }
258        String nName;
259        String s = "";
260        int k = 0;
261        boolean noB = true;
262        for (int i = offset + 1; (i < systemName.length()) && noB; i++) {
263            if (systemName.charAt(i) == 'B') {
264                s = systemName.substring(offset + 1, i);
265                k = i + 1;
266                noB = false;
267            }
268        }
269        if (noB) {
270            int num = Integer.parseInt(systemName.substring(offset + 1));
271            int nAddress = num / 1000;
272            int bitNum = num - (nAddress * 1000);
273            nName = systemName.substring(0, offset + 1) + Integer.toString((nAddress * 1000) + bitNum);
274        } else {
275            int nAddress = Integer.parseInt(s);
276            int bitNum = Integer.parseInt(systemName.substring(k, systemName.length()));
277            nName = systemName.substring(0, offset + 1) + Integer.toString(nAddress) + "B" + Integer.toString(bitNum);
278        }
279        return nName;
280    }
281
282    /**
283     * Convert one format C/MRI system name to the alternate format.
284     *
285     * @param systemName the system name
286     * @return "" (empty string) if the supplied system name does not have a
287     *         valid format, or if there is no representation in the alternate
288     *         naming scheme
289     */
290    public String convertSystemNameToAlternate(String systemName) {
291        int offset = checkSystemPrefix(systemName);
292        if (offset < 1) {
293            log.error("invalid system prefix in CMRI system name: {}", systemName);
294            return "";
295        }
296        if (validSystemNameFormat(systemName, systemName.charAt(offset)) != NameValidity.VALID) {
297            // No point in trying if a valid system name format is not present
298            return "";
299        }
300        String altName;
301        String s = "";
302        int k = 0;
303        boolean noB = true;
304        for (int i = offset + 1; (i < systemName.length()) && noB; i++) {
305            if (systemName.charAt(i) == 'B') {
306                s = systemName.substring(offset + 1, i);
307                k = i + 1;
308                noB = false;
309            }
310        }
311        if (noB) {
312            int num = Integer.parseInt(systemName.substring(offset + 1));
313            int nAddress = num / 1000;
314            int bitNum = num - (nAddress * 1000);
315            altName = systemName.substring(0, offset + 1) + Integer.toString(nAddress) + "B" + Integer.toString(bitNum);
316        } else {
317            int nAddress = Integer.parseInt(s);
318            int bitNum = Integer.parseInt(systemName.substring(k, systemName.length()));
319            if (bitNum > 999) {
320                // bit number is out-of-range for a CLnnnxxx address
321                return "";
322            }
323            altName = systemName.substring(0, offset + 1) + Integer.toString((nAddress * 1000) + bitNum);
324        }
325        return altName;
326    }
327
328    /**
329     * Validate system name format. Does not check whether that node is defined
330     * on current system.
331     *
332     * @param systemName the system name
333     * @param type       the device type
334     * @return enum indicating current validity, which might be just as a prefix
335     */
336    public NameValidity validSystemNameFormat(@Nonnull String systemName, char type) {
337        int offset = checkSystemPrefix(systemName);
338        if (offset < 1) {
339            log.debug("invalid system prefix in CMRI system name: {}", systemName);
340            return NameValidity.INVALID;
341        }
342        if (systemName.charAt(offset) != type) {
343            log.debug("invalid type character in CMRI system name: {}", systemName);
344            return NameValidity.INVALID;
345        }
346        String s = "";
347        int k = 0;
348        boolean noB = true;
349        for (int i = offset + 1; (i < systemName.length()) && noB; i++) {
350            if (systemName.charAt(i) == 'B') {
351                s = systemName.substring(offset + 1, i);
352                k = i + 1;
353                noB = false;
354            }
355        }
356        if (noB) {
357            // This is a CLnnnxxx pattern address
358            int num;
359            try {
360                num = Integer.parseInt(systemName.substring(offset + 1));
361            } catch (NumberFormatException e) {
362                log.debug("invalid character in number field of CMRI system name: {}", systemName);
363                return NameValidity.INVALID;
364            }
365            if ((num < 1) || (num >= 128000)) {
366                log.debug("number field out of range in CMRI system name: {}", systemName);
367                return NameValidity.INVALID;
368            }
369            if ((num - ((num / 1000) * 1000)) == 0) {
370                log.debug("bit number not in range 1 - 999 in CMRI system name: {}", systemName);
371                if (systemName.length() <= offset + 6) {
372                    return NameValidity.VALID_AS_PREFIX_ONLY;
373                    // may become valid by adding 1 or more digits > 0
374                } else { // unless systemName.length() > offset + 6
375                    return NameValidity.INVALID;
376                }
377            }
378        } else {
379            // This is a CLnBxxx pattern address
380            if (s.length() == 0) {
381                log.debug("no node address before 'B' in CMRI system name: {}", systemName);
382                return NameValidity.INVALID;
383            }
384            int num;
385            try {
386                num = Integer.parseInt(s);
387            } catch (NumberFormatException e) {
388                log.debug("invalid character in node address field of CMRI system name: {}", systemName);
389                return NameValidity.INVALID;
390            }
391            if ((num < 0) || (num >= 128)) {
392                log.debug("node address field out of range in CMRI system name: {}", systemName);
393                return NameValidity.INVALID;
394            }
395            try {
396                num = Integer.parseInt(systemName.substring(k));
397            } catch (NumberFormatException e) {
398                log.debug("invalid character in bit number field of CMRI system name: {}", systemName);
399                return NameValidity.INVALID;
400            }
401            if (num == 0) {
402                return NameValidity.VALID_AS_PREFIX_ONLY;
403                // may become valid by adding 1 or more digits > 0, all zeros will be removed later so total length irrelevant
404            }
405            if ((num < 1) || (num > 2048)) {
406                log.debug("bit number field out of range in CMRI system name: {}", systemName);
407                return NameValidity.INVALID;
408            }
409        } // TODO add format check for CLnn:xxx format
410        return NameValidity.VALID;
411    }
412
413    /**
414     * Validate system name format. Does not check whether that node is defined
415     * on current system.
416     *
417     * @param systemName the system name
418     * @param type       the device type
419     * @param locale     the Locale for user messages
420     * @return systemName unmodified
421     * @throws IllegalArgumentException if unable to validate systemName
422     */
423    public String validateSystemNameFormat(String systemName, char type, Locale locale) throws IllegalArgumentException {
424        String prefix = getSystemPrefix() + type;
425        if (!systemName.startsWith(prefix)) {
426            throw new NamedBean.BadSystemNameException(
427                    Bundle.getMessage(Locale.ENGLISH, "InvalidSystemNameInvalidPrefix", systemName),
428                    Bundle.getMessage(locale, "InvalidSystemNameInvalidPrefix", systemName));
429        }
430        String address = systemName.substring(prefix.length());
431        int node = 0;
432        int bit = 0;
433        if (!address.contains("B") && !address.contains(":")) {
434            // This is a CLnnnxxx pattern address
435            int num;
436            try {
437                num = Integer.parseInt(address);
438                node = num / 1000;
439                bit = num - ((num / 1000) * 1000);
440            } catch (NumberFormatException ex) {
441                throw new NamedBean.BadSystemNameException(
442                        Bundle.getMessage(Locale.ENGLISH, "InvalidSystemNameNotInteger", systemName, prefix),
443                        Bundle.getMessage(locale, "InvalidSystemNameNotInteger", systemName, prefix));
444            }
445        } else {
446            // This is a CLnBxxx or CLn:xxx pattern address
447            String[] parts = address.split("B");
448            if (parts.length != 2) {
449                parts = address.split(":");
450                if (parts.length != 2) {
451                    if (address.indexOf(":") == 0 && address.indexOf("B") == 0) {
452                        // no node
453                        throw new NamedBean.BadSystemNameException(
454                                Bundle.getMessage(Locale.ENGLISH, "InvalidSystemNameNodeInvalid", systemName, ""),
455                                Bundle.getMessage(locale, "InvalidSystemNameNodeInvalid", systemName, ""));
456                    } else {
457                        // no bit
458                        throw new NamedBean.BadSystemNameException(
459                                Bundle.getMessage(Locale.ENGLISH, "InvalidSystemNameBitInvalid", systemName, ""),
460                                Bundle.getMessage(locale, "InvalidSystemNameBitInvalid", systemName, ""));
461                    }
462                }
463            }
464            try {
465                node = Integer.parseInt(parts[0]);
466            } catch (NumberFormatException ex) {
467                throw new NamedBean.BadSystemNameException(
468                        Bundle.getMessage(Locale.ENGLISH, "InvalidSystemNameNodeInvalid", systemName, parts[0]),
469                        Bundle.getMessage(locale, "InvalidSystemNameNodeInvalid", systemName, parts[0]));
470            }
471            try {
472                bit = Integer.parseInt(parts[1]);
473            } catch (NumberFormatException ex) {
474                throw new NamedBean.BadSystemNameException(
475                        Bundle.getMessage(Locale.ENGLISH, "InvalidSystemNameBitInvalid", systemName, parts[1]),
476                        Bundle.getMessage(locale, "InvalidSystemNameBitInvalid", systemName, parts[1]));
477            }
478        }
479        if (node < 0 || node >= 128) {
480            throw new NamedBean.BadSystemNameException(
481                    Bundle.getMessage(Locale.ENGLISH, "InvalidSystemNameNodeInvalid", systemName, node),
482                    Bundle.getMessage(locale, "InvalidSystemNameNodeInvalid", systemName, node));
483        }
484        if (bit < 1 || bit > 2048) {
485            throw new NamedBean.BadSystemNameException(
486                    Bundle.getMessage(Locale.ENGLISH, "InvalidSystemNameBitInvalid", systemName, bit),
487                    Bundle.getMessage(locale, "InvalidSystemNameBitInvalid", systemName, bit));
488        }
489        return systemName;
490    }
491
492    /**
493     * Test if a C/MRI input bit is free for assignment. Test is not performed
494     * if the node address is invalid or bit number is greater than 2048.
495     *
496     * @param nAddress the address to test
497     * @param bitNum   the bit number to tests
498     * @return "" (empty string) if the specified input bit is free for
499     *         assignment, else returns the system name of the conflicting
500     *         assignment.
501     */
502    public String isInputBitFree(int nAddress, int bitNum) {
503        if ((nAddress < 0) || (nAddress > 127)) {
504            log.warn("invalid node address in free bit test");
505            return "";
506        }
507        if ((bitNum < 1) || (bitNum > 2048)) {
508            log.warn("invalid bit number in free bit test");
509            return "";
510        }
511        String sysName = makeSystemName("S", nAddress, bitNum);
512        Sensor s = InstanceManager.sensorManagerInstance().getBySystemName(sysName);
513        if (s != null) {
514            return sysName;
515        }
516        String altName = convertSystemNameToAlternate(sysName);
517        s = InstanceManager.sensorManagerInstance().getBySystemName(altName);
518        if (s != null) {
519            return altName;
520        }
521        // not assigned to a sensor
522        return "";
523    }
524
525    /**
526     * Construct a C/MRI system name from type character, node address, and bit
527     * number.
528     * <p>
529     * If the supplied character is not valid, or the node address is out of the
530     * 0 - 127 range, or the bit number is out of the 1 - 2048 range, an error
531     * message is logged and the null string "" is returned.
532     *
533     * @param type     the device type
534     * @param nAddress the address to use
535     * @param bitNum   the bit number to assign
536     * @return a system name in the CLnnnxxx, CTnnnxxx, or CSnnnxxx format if
537     *         the bit number is 1 - 999. If the bit number is 1000 - 2048, the
538     *         system name is returned in the CLnnnBxxxx, CTnnnBxxxx, or
539     *         CSnnnBxxxx format. The returned name is normalized.
540     */
541    public String makeSystemName(String type, int nAddress, int bitNum) {
542        String nName = "";
543        if ((!type.equals("S")) && (!type.equals("L")) && (!type.equals("T"))) {
544            log.error("invalid type character proposed for system name");
545            return nName;
546        }
547        if ((nAddress < 0) || (nAddress > 127)) {
548            log.warn("invalid node address proposed for system name");
549            return nName;
550        }
551        if ((bitNum < 1) || (bitNum > 2048)) {
552            log.warn("invalid bit number proposed for system name");
553            return nName;
554        }
555        if (bitNum < 1000) {
556            nName = getSystemPrefix() + type + Integer.toString((nAddress * 1000) + bitNum);
557        } else {
558            nName = getSystemPrefix() + type + Integer.toString(nAddress) + "B" + Integer.toString(bitNum);
559        }
560        return nName;
561    }
562
563    /**
564     * Get the serial node from a C/MRI system name.
565     *
566     * @param systemName the system name
567     * @param tc         the controller for the node
568     * @return the node or null if invalid systemName format or if the node is
569     *         not found
570     */
571    public AbstractNode getNodeFromSystemName(String systemName, SerialTrafficController tc) {
572        // get the node address
573        int ua;
574        ua = getNodeAddressFromSystemName(systemName);
575        if (ua == -1) {
576            return null;
577        }
578        return tc.getNodeFromAddress(ua);
579    }
580
581    /**
582     * Validate C/MRI system name for configuration. Validates node number and
583     * system prefix.
584     *
585     * @param systemName the system name to check
586     * @param type       the device type
587     * @param tc         the controller for the device
588     * @return true if system name has a valid meaning in current configuration;
589     *         otherwise false
590     */
591    public boolean validSystemNameConfig(String systemName, char type, SerialTrafficController tc) {
592        if (validSystemNameFormat(systemName, type) != NameValidity.VALID) {
593            // No point in trying if a valid system name format is not present
594            return false;
595        }
596        SerialNode node = (SerialNode) getNodeFromSystemName(systemName, tc);
597        if (node == null) {
598            // The node indicated by this system address is not present
599            return false;
600        }
601        int bit = getBitFromSystemName(systemName);
602        if ((type == 'T') || (type == 'L')) {
603            if ((bit <= 0) || (bit > (node.numOutputCards() * node.getNumBitsPerCard()))) {
604                // The bit is not valid for this defined Serial node
605                return false;
606            }
607        } else if (type == 'S') {
608            if ((bit <= 0) || (bit > (node.numInputCards() * node.getNumBitsPerCard()))) {
609                // The bit is not valid for this defined Serial node
610                return false;
611            }
612        } else {
613            log.error("Invalid type specification in validSystemNameConfig call");
614            return false;
615        }
616        // System name has passed all tests
617        return true;
618    }
619
620    /**
621     * Get the serial node address from a C/MRI system name.
622     * <p>
623     * Nodes are numbered from 0 - 127. Does not check whether that node is
624     * defined on current system.
625     *
626     * @param systemName the name containing the node
627     * @return '-1' if invalid systemName format or if the node is not found.
628     */
629    public int getNodeAddressFromSystemName(String systemName) {
630        int offset = checkSystemPrefix(systemName);
631        if (offset < 1) {
632            return -1;
633        }
634        if ((systemName.charAt(offset) != 'L') && (systemName.charAt(offset) != 'S') && (systemName.charAt(offset) != 'T')) {
635            log.error("invalid character in header field of system name: {}", systemName);
636            return -1;
637        }
638        String s = "";
639        boolean noB = true;
640        for (int i = offset + 1; (i < systemName.length()) && noB; i++) {
641            if (systemName.charAt(i) == 'B') {
642                s = systemName.substring(offset + 1, i);
643                noB = false;
644            }
645        }
646        int ua;
647        if (noB) {
648            int num = Integer.parseInt(systemName.substring(offset + 1));
649            if (num > 0) {
650                ua = num / 1000;
651            } else {
652                log.warn("invalid CMRI system name: {}", systemName);
653                return -1;
654            }
655        } else {
656            if (s.length() == 0) {
657                log.warn("no node address before 'B' in CMRI system name: {}", systemName);
658                return -1;
659            } else {
660                try {
661                    ua = Integer.parseInt(s);
662                } catch (NumberFormatException e) {
663                    log.warn("invalid character in CMRI system name: {}", systemName);
664                    return -1;
665                }
666            }
667        }
668        return ua;
669    }
670
671    /**
672     * See {@link jmri.NamedBean#compareSystemNameSuffix} for background.
673     * 
674     * This is a common implementation for C/MRI Lights, Sensors and Turnouts
675     * of the comparison method.
676     * @param suffix1 suffix to compare.
677     * @param suffix2 suffix to compare.
678     * @return CMRI comparison of suffixes.
679     */
680    @CheckReturnValue
681    public static int compareSystemNameSuffix(@Nonnull String suffix1, @Nonnull String suffix2) {
682        
683        // extract node numbers and bit numbers
684        int node1 = 0, node2 = 0, bit1, bit2;
685        int t; // a temporary
686        
687        t = suffix1.indexOf("B");
688        if (t < 0) t = suffix1.indexOf(":");
689        if (t >= 0) {
690            // alt format
691            bit1 = Integer.parseInt(suffix1.substring(t+1));
692            if (t>0) node1 = Integer.parseInt(suffix1.substring(0, t));
693        } else {
694            // std format
695            int len = suffix1.length();
696            bit1 = Integer.parseInt(suffix1.substring(Math.max(0, len-3)));
697            if (len>3) node1 = Integer.parseInt(suffix1.substring(0, len-3));
698        }
699        
700        t = suffix2.indexOf("B");
701        if (t < 0) t = suffix2.indexOf(":");
702        if (t >= 0) {
703            // alt format
704            bit2 = Integer.parseInt(suffix2.substring(t+1));
705            if (t>0) node2 = Integer.parseInt(suffix2.substring(0, t));
706        } else {
707            // std format
708            int len = suffix2.length();
709            bit2 = Integer.parseInt(suffix2.substring(Math.max(0, len-3)));
710            if (len>3) node2 = Integer.parseInt(suffix2.substring(0, len-3));
711        }
712        
713        if (node1 != node2 ) return Integer.signum(node1-node2);
714        return Integer.signum(bit1-bit2);
715    }
716
717    /**
718     * Configure the common managers for CMRI connections. This puts the common
719     * manager config in one place.
720     */
721    public void configureManagers() {
722        InstanceManager.setSensorManager(getSensorManager());
723        getTrafficController().setSensorManager(getSensorManager());
724
725        InstanceManager.setTurnoutManager(getTurnoutManager());
726
727        InstanceManager.setLightManager(getLightManager());
728        register();
729    }
730
731    public SerialTurnoutManager getTurnoutManager() {
732        if (getDisabled()) {
733            return null;
734        }
735        return (SerialTurnoutManager) classObjectMap.computeIfAbsent(TurnoutManager.class, (Class c) -> new SerialTurnoutManager(this));
736    }
737
738    public SerialSensorManager getSensorManager() {
739        if (getDisabled()) {
740            return null;
741        }
742        return (SerialSensorManager) classObjectMap.computeIfAbsent(SensorManager.class, (Class c) -> new SerialSensorManager(this));
743    }
744
745    public SerialLightManager getLightManager() {
746        if (getDisabled()) {
747            return null;
748        }
749        return (SerialLightManager) classObjectMap.computeIfAbsent(LightManager.class, (Class c) -> new SerialLightManager(this));
750    }
751
752    @Override
753    protected ResourceBundle getActionModelResourceBundle() {
754        return ResourceBundle.getBundle("jmri.jmrix.cmri.CmriActionListBundle");
755    }
756
757    @Override
758    public <B extends NamedBean> Comparator<B> getNamedBeanComparator(Class<B> type) {
759        return new NamedBeanComparator<>();
760    }
761
762    @Override
763    public void dispose() {
764        InstanceManager.deregister(this, CMRISystemConnectionMemo.class);
765        if (cf != null) {
766            InstanceManager.deregister(cf, ComponentFactory.class);
767        }
768        super.dispose();
769    }
770
771    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(CMRISystemConnectionMemo.class);
772
773}