001package jmri.util; 002 003import com.sun.jna.platform.win32.Advapi32Util; 004import com.sun.jna.platform.win32.Win32Exception; 005import com.sun.jna.platform.win32.WinReg; 006import java.util.ArrayList; 007import java.util.HashMap; 008import java.util.Map.Entry; 009import java.util.TreeMap; 010import org.slf4j.Logger; 011import org.slf4j.LoggerFactory; 012 013/** 014 * Class used to provide a mapping between port numbers and 'friendly' names, 015 * aimed at users of Microsoft Windows. 016 * <p> 017 * Typically, most USB-Serial adapters have an alternate descriptive name as 018 * well as the more usual technical COMx name. 019 * <p> 020 * This class attempts to provide a mapping between the technical COMx name and 021 * the 'friendly' descriptive name which is stored within the Windows registry 022 * <hr> 023 * This file is part of JMRI. 024 * <p> 025 * JMRI is free software; you can redistribute it and/or modify it under the 026 * terms of version 2 of the GNU General Public License as published by the Free 027 * Software Foundation. See the "COPYING" file for a copy of this license. 028 * <p> 029 * JMRI is distributed in the hope that it will be useful, but WITHOUT ANY 030 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 031 * A PARTICULAR PURPOSE. See the GNU General Public License for more details. 032 * 033 * @author Kevin Dickerson Copyright (C) 2011 034 * @author Matthew Harris Copyright (C) 2011 035 */ 036public class PortNameMapper { 037 038 private static final HashMap<String, SerialPortFriendlyName> SERIAL_PORT_NAMES = new HashMap<String, SerialPortFriendlyName>(); 039 040 private static boolean portsRetrieved = false; 041 042 /* 043 * We only go through the windows registry once looking for friendly names 044 * if a new device is added then the new friendly name will not be picked up. 045 */ 046 private static synchronized void getWindowsSerialPortNames() { 047 if (portsRetrieved) { 048 return; 049 } 050 /* Retrieving the friendly name is only available to windows clients 051 so if the OS is not windows, we make the portsRetrieved as completed 052 and exit out. 053 */ 054 if (!SystemType.isWindows()) { 055 portsRetrieved = true; 056 return; 057 } 058 059 getDetailsFromWinRegistry("SYSTEM\\CurrentControlSet\\Enum\\FTDIBUS\\"); 060 getDetailsFromWinRegistry("SYSTEM\\CurrentControlSet\\Enum\\KEYSPAN\\"); 061 getDetailsFromWinRegistry("SYSTEM\\CurrentControlSet\\Enum\\USB\\"); 062 //some modems are assigned in the HDAUDIO se we retrieve these 063 getDetailsFromWinRegistry("SYSTEM\\CurrentControlSet\\Enum\\HDAUDIO\\"); 064 //some PCI software devices are located here 065 getDetailsFromWinRegistry("SYSTEM\\CurrentControlSet\\Enum\\PCI\\"); 066 //some hardware devices are located here 067 getDetailsFromWinRegistry("SYSTEM\\CurrentControlSet\\Enum\\ACPI\\"); 068 069 portsRetrieved = true; 070 } 071 072 private static void getDetailsFromWinRegistry(String path) { 073 ArrayList<String> friendlyName = new ArrayList<>(); 074 if (!Advapi32Util.registryKeyExists(WinReg.HKEY_LOCAL_MACHINE, path)) { 075 return; 076 } 077 String[] regEntries = Advapi32Util.registryGetKeys(WinReg.HKEY_LOCAL_MACHINE, path); 078 if (regEntries == null) { 079 return; 080 } 081 for (String regEntry : regEntries) { 082 String[] subRegEntries = Advapi32Util.registryGetKeys(WinReg.HKEY_LOCAL_MACHINE, path + regEntry); 083 if (subRegEntries != null) { 084 if (subRegEntries.length > 0) { 085 String name = null; 086 String port = null; 087 TreeMap<String, Object> values = Advapi32Util.registryGetValues(WinReg.HKEY_LOCAL_MACHINE, path + regEntry + "\\" + subRegEntries[0]); 088 if (values.containsKey("Class")) { 089 String pathKey = path + regEntry + "\\" + subRegEntries[0]; 090 String deviceClass = Advapi32Util.registryGetStringValue(WinReg.HKEY_LOCAL_MACHINE, pathKey, "Class"); 091 if (deviceClass.equals("Ports") || deviceClass.equals("Modem")) { 092 try { 093 name = Advapi32Util.registryGetStringValue(WinReg.HKEY_LOCAL_MACHINE, pathKey, "FriendlyName"); 094 } 095 catch (Win32Exception | NullPointerException e) { 096 log.warn("'FriendlyName' not found while querying 'HKLM.{}`. JMRI cannot use the device, so will skip it.", pathKey ); 097 } 098 try { 099 String pathKey2 = path + regEntry + "\\" + subRegEntries[0] + "\\Device Parameters"; 100 port = Advapi32Util.registryGetStringValue(WinReg.HKEY_LOCAL_MACHINE, pathKey2, "PortName"); 101 } catch (Win32Exception | NullPointerException e) { 102 // ...\\Device Parameters does not exist for some odd-ball Windows 103 // serial devices, so cannot get the "PortName" from there. 104 // Instead, leave port as null and ignore the exception 105 } 106 } 107 } 108 if ((name != null) && (port != null)) { 109 SERIAL_PORT_NAMES.put(port, new SerialPortFriendlyName(port, name)); 110 } else if (name != null) { 111 friendlyName.add(name); 112 } 113 } 114 } 115 } 116 for (int i = 0; i < friendlyName.size(); i++) { 117 int commst = friendlyName.get(i).lastIndexOf('(') + 1; 118 int commls = friendlyName.get(i).lastIndexOf(')'); 119 String commPort = friendlyName.get(i).substring(commst, commls); 120 SERIAL_PORT_NAMES.put(commPort, new SerialPortFriendlyName(commPort, friendlyName.get(i))); 121 } 122 } 123 124 public static String getPortFromName(String name) { 125 if (!portsRetrieved) { 126 getWindowsSerialPortNames(); 127 } 128 for (Entry<String, SerialPortFriendlyName> en : SERIAL_PORT_NAMES.entrySet()) { 129 if (en.getValue().getDisplayName().equals(name)) { 130 return en.getKey(); 131 } 132 } 133 return ""; 134 } 135 136 public static HashMap<String, SerialPortFriendlyName> getPortNameMap() { 137 if (!portsRetrieved) { 138 getWindowsSerialPortNames(); 139 } 140 return SERIAL_PORT_NAMES; 141 } 142 143 public static class SerialPortFriendlyName { 144 145 String serialPortFriendly = ""; 146 boolean valid = false; 147 148 public SerialPortFriendlyName(String port, String Friendly) { 149 serialPortFriendly = Friendly; 150 if (serialPortFriendly == null) { 151 serialPortFriendly = port; 152 } else if (!serialPortFriendly.contains(port)) { 153 serialPortFriendly = Friendly + " (" + port + ")"; 154 } 155 } 156 157 public String getDisplayName() { 158 return serialPortFriendly; 159 } 160 161 public boolean isValidPort() { 162 return valid; 163 } 164 165 public void setValidPort(boolean boo) { 166 valid = boo; 167 } 168 169 } 170 private final static Logger log = LoggerFactory.getLogger(PortNameMapper.class); 171 172 173}