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}