001package jmri.jmrix;
002
003import java.awt.Color;
004import java.awt.Component;
005import java.awt.GridBagConstraints;
006import java.awt.Insets;
007import java.awt.event.ActionEvent;
008import java.awt.event.FocusEvent;
009import java.awt.event.FocusListener;
010import java.awt.event.ItemEvent;
011import java.util.Collections;
012import java.util.Map;
013import java.util.ResourceBundle;
014import java.util.Vector;
015
016import javax.swing.JButton;
017import javax.swing.JComboBox;
018import javax.swing.JComponent;
019import javax.swing.JLabel;
020import javax.swing.JList;
021import javax.swing.JPanel;
022import javax.swing.JSpinner;
023import javax.swing.JTextField;
024import javax.swing.ListCellRenderer;
025import javax.swing.SpinnerNumberModel;
026import javax.swing.event.PopupMenuEvent;
027import javax.swing.event.PopupMenuListener;
028
029import jmri.util.PortNameMapper;
030import jmri.util.PortNameMapper.SerialPortFriendlyName;
031import jmri.util.swing.JComboBoxUtil;
032
033/**
034 * Abstract base class for common implementation of the SerialConnectionConfig.
035 *
036 * @author Bob Jacobsen Copyright (C) 2001, 2003
037 */
038abstract public class AbstractSerialConnectionConfig extends AbstractConnectionConfig {
039
040    /**
041     * Ctor for an object being created during load process.
042     *
043     * @param p port being configured
044     */
045    @edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value = "BC_UNCONFIRMED_CAST", justification = "Thought to be safe as default connection config")
046    public AbstractSerialConnectionConfig(jmri.jmrix.PortAdapter p) {
047        this((jmri.jmrix.SerialPortAdapter) p);
048    }
049
050    public AbstractSerialConnectionConfig(jmri.jmrix.SerialPortAdapter p) {
051        adapter = p;
052    }
053
054    /**
055     * Ctor for a functional object with no preexisting adapter. Expect that the
056     * subclass setInstance() will fill the adapter member.
057     */
058    public AbstractSerialConnectionConfig() {
059        adapter = null;
060
061    }
062
063    @Override
064    public jmri.jmrix.SerialPortAdapter getAdapter() {
065        return adapter;
066    }
067
068    protected boolean init = false;
069
070    /**
071     * {@inheritDoc}
072     */
073    @Override
074    protected void checkInitDone() {
075        log.debug("init called for {}", name());
076        if (init) {
077            return;
078        }
079
080        baudBox.addActionListener(e -> {
081            adapter.configureBaudRate((String) baudBox.getSelectedItem());
082            p.setComboBoxLastSelection(adapter.getClass().getName() + ".baud", (String) baudBox.getSelectedItem()); // NOI18N
083        });
084
085        addNameEntryCheckers(adapter);
086
087        portBox.addFocusListener(new FocusListener() {
088            @Override
089            public void focusGained(FocusEvent e) {
090                refreshPortBox();
091            }
092
093            @Override
094            public void focusLost(FocusEvent e) {
095            }
096        });
097
098        // set/change delay interval between (actually before) output (Turnout) commands
099        outputIntervalSpinner.addChangeListener(e -> adapter.getSystemConnectionMemo().setOutputInterval((Integer) outputIntervalSpinner.getValue()));
100
101        for (Map.Entry<String, Option> entry : options.entrySet()) {
102            final String item = entry.getKey();
103            if (entry.getValue().getComponent() instanceof JComboBox) {
104                ((JComboBox<?>) entry.getValue().getComponent()).addActionListener((ActionEvent e) -> {
105                    adapter.setOptionState(item, options.get(item).getItem());
106                });
107                JComboBoxUtil.setupComboBoxMaxRows((JComboBox<?>) entry.getValue().getComponent());
108            }
109        }
110
111        init = true;
112    }
113
114    @Override
115    public void updateAdapter() {
116        log.debug("updateAdapter() to {}", systemPrefixField.getText());
117        adapter.setPort(PortNameMapper.getPortFromName((String) portBox.getSelectedItem()));
118        adapter.configureBaudRateFromIndex(baudBox.getSelectedIndex()); // manage by index, not item value
119        for (Map.Entry<String, Option> entry : options.entrySet()) {
120            adapter.setOptionState(entry.getKey(), entry.getValue().getItem());
121        }
122
123        if (adapter.getSystemConnectionMemo() != null && !adapter.getSystemConnectionMemo().setSystemPrefix(systemPrefixField.getText())) {
124            systemPrefixField.setText(adapter.getSystemConnectionMemo().getSystemPrefix());
125            connectionNameField.setText(adapter.getSystemConnectionMemo().getUserName());
126        }
127    }
128
129    jmri.UserPreferencesManager p = jmri.InstanceManager.getDefault(jmri.UserPreferencesManager.class);
130    protected JComboBox<String> portBox = new JComboBox<>();
131    protected JLabel portBoxLabel;
132    protected JComboBox<String> baudBox = new JComboBox<>();
133    protected JLabel baudBoxLabel;
134    protected String[] baudList;
135
136    private final SpinnerNumberModel intervalSpinner = new SpinnerNumberModel(250, 0, 10000, 1); // 10 sec max seems long enough
137    // the following items are protected so they can be hidden when not applicable from a specific ConnectionConfig (ie. Simulator) implementation
138    protected JSpinner outputIntervalSpinner = new JSpinner(intervalSpinner);
139    protected JLabel outputIntervalLabel;
140    protected JButton outputIntervalReset = new JButton(Bundle.getMessage("ButtonReset"));
141
142    protected jmri.jmrix.SerialPortAdapter adapter;
143
144    /**
145     * {@inheritDoc}
146     */
147    @Override
148    abstract protected void setInstance();
149
150    @Override
151    public String getInfo() {
152        String t = (String) portBox.getSelectedItem();
153        if (t != null) {
154            return PortNameMapper.getPortFromName(t);
155            //return t;
156        } else if ((adapter != null) && (adapter.getCurrentPortName() != null)) {
157            return adapter.getCurrentPortName();
158        }
159
160        return JmrixConfigPane.NONE;
161    }
162
163//    @SuppressWarnings("UseOfObsoleteCollectionType")
164    Vector<String> v;
165//    @SuppressWarnings("UseOfObsoleteCollectionType")
166    Vector<String> originalList;
167    String invalidPort = null;
168
169//    @SuppressWarnings("UseOfObsoleteCollectionType")
170    protected void refreshPortBox() {
171        log.debug("entering refreshPortBox");
172        if (!init) {
173            v = getPortNames();
174            portBox.setRenderer(new ComboBoxRenderer());
175            // Add this line to ensure that the combo box header isn't made too narrow
176            portBox.setPrototypeDisplayValue("A fairly long port name of 40 characters"); //NO18N
177        } else {
178            Vector<String> v2 = getPortNames();
179            if (v2.equals(originalList)) {
180                log.debug("List of valid Ports has not changed, therefore we will not refresh the port list");
181                // but we will insist on setting the current value into the port
182                adapter.setPort(PortNameMapper.getPortFromName((String) portBox.getSelectedItem()));
183                return;
184            }
185            log.debug("List of valid Ports has been changed, therefore we will refresh the port list");
186            v = new Vector<>();
187            v.setSize(v2.size());
188            Collections.copy(v, v2);
189        }
190
191        if (v == null) {
192            log.error("port name Vector v is null!");
193            return;
194        }
195        
196        // Display differences between the present and previous list of ports
197        if (originalList != null) {
198            Vector<String> newPorts = new Vector<>(v);
199            newPorts.removeAll(originalList);
200            for (var port : newPorts) { log.info("Found new port {}", port);}
201            Vector<String> lostPorts = new Vector<>(originalList);
202            lostPorts.removeAll(v);
203            for (var port : lostPorts) { log.info("Port {} no longer present", port);}
204        }
205        
206        /* as we make amendments to the list of port in vector v, we keep a copy of it before
207         modification, this copy is then used to validate against any changes in the port lists.
208         */
209        originalList = new Vector<>();
210        originalList.setSize(v.size());
211        Collections.copy(originalList, v);
212        if (portBox.getActionListeners().length > 0) {
213            portBox.removeActionListener(portBox.getActionListeners()[0]);
214        }
215        portBox.removeAllItems();
216        log.debug("getting fresh list of available Serial Ports");
217
218        if (v.isEmpty()) {
219            v.add(0, Bundle.getMessage("noPortsFound"));
220        }
221        String portName = adapter.getCurrentPortName();
222        portBox.setForeground(Color.black);
223        if (portName != null && !portName.equals(Bundle.getMessage("noneSelected")) && !portName.equals(Bundle.getMessage("noPortsFound"))) {
224            if (!v.contains(portName)) {
225                v.add(0, portName);
226                invalidPort = portName;
227                portBox.setForeground(Color.red);
228            } else if (invalidPort != null && invalidPort.equals(portName)) {
229                invalidPort = null;
230            }
231        } else {
232            if (!v.contains(portName)) {
233                v.add(0, Bundle.getMessage("noneSelected"));
234            } else if (p.getComboBoxLastSelection(adapter.getClass().getName() + ".port") == null) {
235                v.add(0, Bundle.getMessage("noneSelected"));
236            }
237        }
238        updateSerialPortNames(portName, portBox, v);
239        JComboBoxUtil.setupComboBoxMaxRows(portBox);
240
241        // If there's no name selected, select one that seems most likely
242        boolean didSetName = false;
243        if (portName == null || portName.equals(Bundle.getMessage("noneSelected")) || portName.equals(Bundle.getMessage("noPortsFound"))) {
244            for (int i = 0; i < portBox.getItemCount(); i++) {
245                for (String friendlyName : getPortFriendlyNames()) {
246                    if ((portBox.getItemAt(i)).contains(friendlyName)) {
247                        portBox.setSelectedIndex(i);
248                        adapter.setPort(PortNameMapper.getPortFromName(portBox.getItemAt(i)));
249                        didSetName = true;
250                        break;
251                    }
252                }
253            }
254            // if didn't set name, don't leave it hanging
255            if (!didSetName) {
256                portBox.setSelectedIndex(0);
257            }
258        }
259        // finally, insist on synchronization of selected port name with underlying port
260        adapter.setPort(PortNameMapper.getPortFromName((String) portBox.getSelectedItem()));
261
262        // add a listener for later changes
263        portBox.addActionListener((ActionEvent e) -> {
264            log.debug("portBox action listener fired");
265            String port = PortNameMapper.getPortFromName((String) portBox.getSelectedItem());
266            adapter.setPort(port);
267        });
268        portBox.addPopupMenuListener(new PopupMenuListener(){
269            public void popupMenuCanceled(PopupMenuEvent e) {
270                log.trace("popupMenuCanceled");
271            }
272            public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
273                log.trace("popupMenuWillBecomeInvisible");
274            }
275            public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
276                log.debug("popupMenuWillBecomeVisible");
277                refreshPortBox();
278            }
279        });
280    }
281
282    /**
283     * {@inheritDoc}
284     */
285    @Override
286//    @SuppressWarnings("UseOfObsoleteCollectionType")
287    public void loadDetails(final JPanel details) {
288        _details = details;
289        setInstance();
290        if (!init) {
291            //Build up list of options
292            String[] optionsAvailable = adapter.getOptions();
293            options.clear();
294            for (String i : optionsAvailable) {
295                JComboBox<String> opt = new JComboBox<>(adapter.getOptionChoices(i));
296                opt.setSelectedItem(adapter.getOptionState(i));
297                // check that it worked
298                if (!adapter.getOptionState(i).equals(opt.getSelectedItem())) {
299                    // no, set 1st option choice
300                    opt.setSelectedIndex(0);
301                    // log before setting new value to show old value
302                    log.warn("Loading found invalid value for option {}, found \"{}\", setting to \"{}\"", i, adapter.getOptionState(i), opt.getSelectedItem());
303                    adapter.setOptionState(i, (String) opt.getSelectedItem());
304                }
305                options.put(i, new Option(adapter.getOptionDisplayName(i), opt, adapter.isOptionAdvanced(i)));
306            }
307        }
308
309        try {
310            v = getPortNames();
311            if (log.isDebugEnabled()) {
312                log.debug("loadDetails called in class {}", this.getClass().getName());
313                log.debug("adapter class: {}", adapter.getClass().getName());
314                log.debug("loadDetails called for {}", name());
315                if (v != null) {
316                    log.debug("Found {} ports", v.size());
317                } else {
318                    log.debug("Zero-length port vector");
319                }
320            }
321        } catch (java.lang.UnsatisfiedLinkError e1) {
322            log.error("UnsatisfiedLinkError - the serial library has not been installed properly");
323            log.error("java.library.path={}", System.getProperty("java.library.path", "<unknown>"));
324            jmri.util.swing.JmriJOptionPane.showMessageDialog(null, "Failed to load comm library.\nYou have to fix that before setting preferences.");
325            return;
326        }
327
328        if (adapter.getSystemConnectionMemo() != null) {
329            systemPrefixField.setText(adapter.getSystemConnectionMemo().getSystemPrefix());
330            connectionNameField.setText(adapter.getSystemConnectionMemo().getUserName());
331            NUMOPTIONS = NUMOPTIONS + 2;
332        }
333
334        refreshPortBox();
335
336        baudList = adapter.validBaudRates(); // when not supported should not return null, but an empty String[] {}
337        // need to remove ActionListener before addItem() or action event will occur
338        if (baudBox.getActionListeners().length > 0) {
339            baudBox.removeActionListener(baudBox.getActionListeners()[0]);
340        }
341        // rebuild baudBox combo list
342        baudBox.removeAllItems();
343        if (log.isDebugEnabled()) {
344            log.debug("after remove, {} items, first is {}", baudBox.getItemCount(),
345                    baudBox.getItemAt(0));
346        }
347
348        // empty array means: baud not supported by adapter (but extends serialConnConfig)
349        if (baudList.length == 0) {
350            log.debug("empty array received from adapter");
351        }
352        for (String baudList1 : baudList) {
353            baudBox.addItem(baudList1);
354        }
355        if (log.isDebugEnabled()) {
356            log.debug("after reload, {} items, first is {}", baudBox.getItemCount(),
357                    baudBox.getItemAt(0));
358        }
359
360        if (baudList.length > 1) {
361            baudBox.setToolTipText(Bundle.getMessage("TipBaudRateMatch"));
362            baudBox.setEnabled(true);
363        } else {
364            baudBox.setToolTipText(Bundle.getMessage("TipBaudRateFixed"));
365            baudBox.setEnabled(false);
366        }
367
368        NUMOPTIONS = NUMOPTIONS + options.size();
369
370        portBoxLabel = new JLabel(Bundle.getMessage("SerialPortLabel"));
371        baudBoxLabel = new JLabel(Bundle.getMessage("BaudRateLabel"));
372        if (baudBox.getItemCount() > 0) { // skip when adapter returned an empty array (= spotbug's preference)
373            baudBox.setSelectedIndex(adapter.getCurrentBaudIndex());
374        }
375        // connection (memo) specific output command delay option, calls jmri.jmrix.SystemConnectionMemo#setOutputInterval(int)
376        outputIntervalLabel = new JLabel(Bundle.getMessage("OutputIntervalLabel"));
377        outputIntervalSpinner.setToolTipText(Bundle.getMessage("OutputIntervalTooltip",
378                adapter.getSystemConnectionMemo().getDefaultOutputInterval(),adapter.getManufacturer()));
379        JTextField field = ((JSpinner.DefaultEditor) outputIntervalSpinner.getEditor()).getTextField();
380        field.setColumns(6);
381        outputIntervalSpinner.setMaximumSize(outputIntervalSpinner.getPreferredSize()); // set spinner JTextField width
382        outputIntervalSpinner.setValue(adapter.getSystemConnectionMemo().getOutputInterval());
383        outputIntervalSpinner.setEnabled(true);
384        outputIntervalReset.addActionListener((ActionEvent event) -> {
385            outputIntervalSpinner.setValue(adapter.getSystemConnectionMemo().getDefaultOutputInterval());
386            adapter.getSystemConnectionMemo().setOutputInterval(adapter.getSystemConnectionMemo().getDefaultOutputInterval());
387        });
388
389        showAdvanced.setFont(showAdvanced.getFont().deriveFont(9f));
390        showAdvanced.setForeground(Color.blue);
391        showAdvanced.addItemListener((ItemEvent e) -> {
392            showAdvancedItems();
393        });
394        showAdvancedItems();
395        init = false;       // need to reload action listeners
396        checkInitDone();
397    }
398
399    @Override
400    @edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value = "BC_UNCONFIRMED_CAST_OF_RETURN_VALUE",
401        justification = "Type is checked before casting")
402    protected void showAdvancedItems() {
403        _details.removeAll();
404        cL.anchor = GridBagConstraints.WEST;
405        cL.insets = new Insets(2, 5, 0, 5);
406        cR.insets = new Insets(2, 0, 0, 5);
407        cR.anchor = GridBagConstraints.WEST;
408        cR.gridx = 1;
409        cL.gridx = 0;
410        int i = 0;
411        int stdrows = 0;
412        boolean incAdvancedOptions = true;
413        if (!isBaudAdvanced()) {
414            stdrows++;
415        }
416        if (!isPortAdvanced()) {
417            stdrows++;
418        }
419        for (Map.Entry<String, Option> entry : options.entrySet()) {
420            if (!entry.getValue().isAdvanced()) {
421                stdrows++;
422            }
423        }
424
425        if (adapter.getSystemConnectionMemo() != null) {
426            stdrows = stdrows + 2;
427        }
428        if (stdrows == NUMOPTIONS) {
429            incAdvancedOptions = false;
430        }
431        _details.setLayout(gbLayout);
432        i = addStandardDetails(incAdvancedOptions, i);
433        if (showAdvanced.isSelected()) {
434
435            if (isPortAdvanced()) {
436                cR.gridy = i;
437                cL.gridy = i;
438                gbLayout.setConstraints(portBoxLabel, cL);
439                gbLayout.setConstraints(portBox, cR);
440
441                _details.add(portBoxLabel);
442                _details.add(portBox);
443                i++;
444            }
445
446            if (isBaudAdvanced()) {
447                cR.gridy = i;
448                cL.gridy = i;
449                gbLayout.setConstraints(baudBoxLabel, cL);
450                gbLayout.setConstraints(baudBox, cR);
451                _details.add(baudBoxLabel);
452                _details.add(baudBox);
453                i++;
454            }
455
456            for (Map.Entry<String, Option> entry : options.entrySet()) {
457                if (entry.getValue().isAdvanced()) {
458                    cR.gridy = i;
459                    cL.gridy = i;
460                    gbLayout.setConstraints(entry.getValue().getLabel(), cL);
461                    gbLayout.setConstraints(entry.getValue().getComponent(), cR);
462                    _details.add(entry.getValue().getLabel());
463                    _details.add(entry.getValue().getComponent());
464                    i++;
465                }
466            }
467
468            // interval config field
469            cR.gridy = i;
470            cL.gridy = i;
471            gbLayout.setConstraints(outputIntervalLabel, cL);
472            _details.add(outputIntervalLabel);
473            JPanel intervalPanel = new JPanel();
474            gbLayout.setConstraints(intervalPanel, cR);
475            intervalPanel.add(outputIntervalSpinner);
476            intervalPanel.add(outputIntervalReset);
477            _details.add(intervalPanel);
478            i++;
479
480        }
481        cL.gridwidth = 2;
482        for (JComponent item : additionalItems) {
483            cL.gridy = i;
484            gbLayout.setConstraints(item, cL);
485            _details.add(item);
486            i++;
487        }
488        cL.gridwidth = 1;
489
490        if (_details.getParent() != null && _details.getParent() instanceof javax.swing.JViewport) {
491            javax.swing.JViewport vp = (javax.swing.JViewport) _details.getParent();
492            vp.revalidate();
493            vp.repaint();
494        }
495    }
496
497    protected int addStandardDetails(boolean incAdvanced, int i) {
498        if (!isPortAdvanced()) {
499            cR.gridy = i;
500            cL.gridy = i;
501            gbLayout.setConstraints(portBoxLabel, cL);
502            gbLayout.setConstraints(portBox, cR);
503            _details.add(portBoxLabel);
504            _details.add(portBox);
505            i++;
506        }
507
508        if (!isBaudAdvanced()) {
509            cR.gridy = i;
510            cL.gridy = i;
511            gbLayout.setConstraints(baudBoxLabel, cL);
512            gbLayout.setConstraints(baudBox, cR);
513            _details.add(baudBoxLabel);
514            _details.add(baudBox);
515            i++;
516        }
517
518        return addStandardDetails(adapter, incAdvanced, i);
519    }
520
521    public boolean isPortAdvanced() {
522        return false;
523    }
524
525    public boolean isBaudAdvanced() {
526        return true;
527    }
528
529    @Override
530    public String getManufacturer() {
531        return adapter.getManufacturer();
532    }
533
534    @Override
535    public void setManufacturer(String manufacturer) {
536        setInstance();
537        adapter.setManufacturer(manufacturer);
538    }
539
540    @Override
541    public boolean getDisabled() {
542        if (adapter == null) {
543            return true;
544        }
545        return adapter.getDisabled();
546    }
547
548    @Override
549    public void setDisabled(boolean disabled) {
550        if (adapter != null) {
551            adapter.setDisabled(disabled);
552        }
553    }
554
555    @Override
556    public String getConnectionName() {
557        if ((adapter != null) && (adapter.getSystemConnectionMemo() != null)) {
558            return adapter.getSystemConnectionMemo().getUserName();
559        } else {
560            return name();
561        }
562    }
563
564    @Override
565    public void dispose() {
566        super.dispose();
567        if (adapter != null) {
568            adapter.dispose();
569            adapter = null;
570        }
571    }
572
573    class ComboBoxRenderer extends JLabel
574            implements ListCellRenderer<String> {
575
576        public ComboBoxRenderer() {
577            setHorizontalAlignment(LEFT);
578            setVerticalAlignment(CENTER);
579        }
580
581        /*
582         * This method finds the image and text corresponding
583         * to the selected value and returns the label, set up
584         * to display the text and image.
585         */
586        @Override
587        public Component getListCellRendererComponent(
588                JList<? extends String> list,
589                String name,
590                int index,
591                boolean isSelected,
592                boolean cellHasFocus) {
593
594            setOpaque(index > -1);
595            setForeground(Color.black);
596            list.setSelectionForeground(Color.black);
597            if (isSelected && index > -1) {
598                setBackground(list.getSelectionBackground());
599            } else {
600                setBackground(list.getBackground());
601            }
602            if (invalidPort != null) {
603                String port = PortNameMapper.getPortFromName(name);
604                if (port.equals(invalidPort)) {
605                    list.setSelectionForeground(Color.red);
606                    setForeground(Color.red);
607                }
608            }
609
610            setText(name);
611
612            return this;
613        }
614    }
615
616    /**
617     * Handle friendly port names. Note that this
618     * changes the selection in portCombo, so
619     * that should be tracked after this returns.
620     *
621     * @param portName The currently-selected port name
622     * @param portCombo The combo box that's displaying the available ports
623     * @param portList The list of valid (unfriendly) port names
624     */
625//    @SuppressWarnings("UseOfObsoleteCollectionType")
626    protected synchronized static void updateSerialPortNames(String portName, JComboBox<String> portCombo, Vector<String> portList) {
627        for (Map.Entry<String, SerialPortFriendlyName> en : PortNameMapper.getPortNameMap().entrySet()) {
628            en.getValue().setValidPort(false);
629        }
630        for (int i = 0; i < portList.size(); i++) {
631            String commPort = portList.elementAt(i);
632            SerialPortFriendlyName port = PortNameMapper.getPortNameMap().get(commPort);
633            if (port == null) {
634                port = new SerialPortFriendlyName(commPort, null);
635                PortNameMapper.getPortNameMap().put(commPort, port);
636            }
637            port.setValidPort(true);
638            portCombo.addItem(port.getDisplayName());
639            if (commPort.equals(portName)) {
640                portCombo.setSelectedIndex(i);
641            }
642        }
643    }
644
645    /**
646     * Provide a vector of valid port names, each a String.
647     * This may be implemented differently in subclasses 
648     * that e.g. do loopback or use a custom port-access library.
649     * @return Valid port names in the form used to select them later.
650     */
651//    @SuppressWarnings("UseOfObsoleteCollectionType") // historical interface
652    protected Vector<String> getPortNames() {
653        log.trace("enter getPortNames");
654        var retval = AbstractSerialPortController.getActualPortNames();
655        for (var port : retval) {
656            log.trace("  port {}", port);
657        }
658        return retval;
659    }
660        
661    /**
662     * This provides a method to return potentially meaningful names that are
663     * used in OS to help identify ports against Hardware.
664     *
665     * @return array of friendly port names
666     */
667    protected String[] getPortFriendlyNames() {
668        return new String[]{};
669    }
670
671    /**
672     * This is purely here for systems that do not implement the
673     * SystemConnectionMemo and can be removed once they have been migrated.
674     *
675     * @return Resource bundle for action model
676     */
677    protected ResourceBundle getActionModelResourceBundle() {
678        return null;
679    }
680
681    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(AbstractSerialConnectionConfig.class);
682
683}