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}