001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.tools; 003 004import java.lang.reflect.InvocationTargetException; 005import java.lang.reflect.Method; 006import java.nio.charset.StandardCharsets; 007import java.util.ArrayList; 008import java.util.Arrays; 009import java.util.Collections; 010import java.util.HashMap; 011import java.util.List; 012import java.util.Map; 013import java.util.prefs.Preferences; 014 015/** 016 * Utility class to access Window registry (read access only). 017 * As the implementation relies on internal JDK class {@code java.util.prefs.WindowsPreferences} and its native JNI 018 * method {@code Java_java_util_prefs_WindowsPreferences_WindowsRegQueryValueEx}, only String values (REG_SZ) 019 * are supported. 020 * Adapted from <a href="http://stackoverflow.com/a/6163701/2257172">StackOverflow</a>. 021 * @since 12217 022 */ 023public final class WinRegistry { 024 025 /** 026 * Registry entries subordinate to this key define the preferences of the current user. 027 * These preferences include the settings of environment variables, data about program groups, 028 * colors, printers, network connections, and application preferences. 029 * See <a href="https://msdn.microsoft.com/en-us/library/windows/desktop/ms724836(v=vs.85).aspx">Predefined Keys</a> 030 */ 031 public static final int HKEY_CURRENT_USER = 0x80000001; 032 033 /** 034 * Registry entries subordinate to this key define the physical state of the computer, including data about the bus type, 035 * system memory, and installed hardware and software. It contains subkeys that hold current configuration data, 036 * including Plug and Play information (the Enum branch, which includes a complete list of all hardware that has ever been 037 * on the system), network logon preferences, network security information, software-related information (such as server 038 * names and the location of the server), and other system information. 039 * See <a href="https://msdn.microsoft.com/en-us/library/windows/desktop/ms724836(v=vs.85).aspx">Predefined Keys</a> 040 */ 041 public static final int HKEY_LOCAL_MACHINE = 0x80000002; 042 043 private static final int REG_SUCCESS = 0; 044 045 private static final int KEY_READ = 0x20019; 046 private static final Preferences userRoot = Preferences.userRoot(); 047 private static final Preferences systemRoot = Preferences.systemRoot(); 048 private static final Class<? extends Preferences> userClass = userRoot.getClass(); 049 private static final Method regOpenKey; 050 private static final Method regCloseKey; 051 private static final Method regQueryValueEx; 052 private static final Method regEnumValue; 053 private static final Method regQueryInfoKey; 054 private static final Method regEnumKeyEx; 055 056 static { 057 try { 058 regOpenKey = userClass.getDeclaredMethod("WindowsRegOpenKey", int.class, byte[].class, int.class); 059 regCloseKey = userClass.getDeclaredMethod("WindowsRegCloseKey", int.class); 060 regQueryValueEx = userClass.getDeclaredMethod("WindowsRegQueryValueEx", int.class, byte[].class); 061 regEnumValue = userClass.getDeclaredMethod("WindowsRegEnumValue", int.class, int.class, int.class); 062 regQueryInfoKey = userClass.getDeclaredMethod("WindowsRegQueryInfoKey1", int.class); 063 regEnumKeyEx = userClass.getDeclaredMethod("WindowsRegEnumKeyEx", int.class, int.class, int.class); 064 Utils.setObjectsAccessible(regOpenKey, regCloseKey, regQueryValueEx, regEnumValue, regQueryInfoKey, regEnumKeyEx); 065 } catch (RuntimeException | ReflectiveOperationException e) { 066 throw new JosmRuntimeException(e); 067 } 068 } 069 070 private WinRegistry() { 071 // Hide default constructor for utilities classes 072 } 073 074 /** 075 * Read a value from key and value name 076 * @param hkey HKEY_CURRENT_USER/HKEY_LOCAL_MACHINE 077 * @param key key name 078 * @param valueName value name 079 * @return the value 080 * @throws IllegalArgumentException if hkey not HKEY_CURRENT_USER/HKEY_LOCAL_MACHINE 081 * @throws IllegalAccessException if Java language access control is enforced and the underlying method is inaccessible 082 * @throws InvocationTargetException if the underlying method throws an exception 083 */ 084 public static String readString(int hkey, String key, String valueName) 085 throws IllegalAccessException, InvocationTargetException { 086 if (hkey == HKEY_LOCAL_MACHINE) { 087 return readString(systemRoot, hkey, key, valueName); 088 } else if (hkey == HKEY_CURRENT_USER) { 089 return readString(userRoot, hkey, key, valueName); 090 } else { 091 throw new IllegalArgumentException("hkey=" + hkey); 092 } 093 } 094 095 /** 096 * Read value(s) and value name(s) form given key 097 * @param hkey HKEY_CURRENT_USER/HKEY_LOCAL_MACHINE 098 * @param key key name 099 * @return the value name(s) plus the value(s) 100 * @throws IllegalArgumentException if hkey not HKEY_CURRENT_USER/HKEY_LOCAL_MACHINE 101 * @throws IllegalAccessException if Java language access control is enforced and the underlying method is inaccessible 102 * @throws InvocationTargetException if the underlying method throws an exception 103 */ 104 public static Map<String, String> readStringValues(int hkey, String key) 105 throws IllegalAccessException, InvocationTargetException { 106 if (hkey == HKEY_LOCAL_MACHINE) { 107 return readStringValues(systemRoot, hkey, key); 108 } else if (hkey == HKEY_CURRENT_USER) { 109 return readStringValues(userRoot, hkey, key); 110 } else { 111 throw new IllegalArgumentException("hkey=" + hkey); 112 } 113 } 114 115 /** 116 * Read the value name(s) from a given key 117 * @param hkey HKEY_CURRENT_USER/HKEY_LOCAL_MACHINE 118 * @param key key name 119 * @return the value name(s) 120 * @throws IllegalArgumentException if hkey not HKEY_CURRENT_USER/HKEY_LOCAL_MACHINE 121 * @throws IllegalAccessException if Java language access control is enforced and the underlying method is inaccessible 122 * @throws InvocationTargetException if the underlying method throws an exception 123 */ 124 public static List<String> readStringSubKeys(int hkey, String key) 125 throws IllegalAccessException, InvocationTargetException { 126 if (hkey == HKEY_LOCAL_MACHINE) { 127 return readStringSubKeys(systemRoot, hkey, key); 128 } else if (hkey == HKEY_CURRENT_USER) { 129 return readStringSubKeys(userRoot, hkey, key); 130 } else { 131 throw new IllegalArgumentException("hkey=" + hkey); 132 } 133 } 134 135 // ===================== 136 137 private static String readString(Preferences root, int hkey, String key, String value) 138 throws IllegalAccessException, InvocationTargetException { 139 int[] handles = (int[]) regOpenKey.invoke(root, Integer.valueOf(hkey), toCstr(key), Integer.valueOf(KEY_READ)); 140 if (handles[1] != REG_SUCCESS) { 141 return null; 142 } 143 byte[] valb = (byte[]) regQueryValueEx.invoke(root, Integer.valueOf(handles[0]), toCstr(value)); 144 regCloseKey.invoke(root, Integer.valueOf(handles[0])); 145 return (valb != null ? new String(valb, StandardCharsets.UTF_8).trim() : null); 146 } 147 148 private static Map<String, String> readStringValues(Preferences root, int hkey, String key) 149 throws IllegalAccessException, InvocationTargetException { 150 HashMap<String, String> results = new HashMap<>(); 151 int[] handles = (int[]) regOpenKey.invoke(root, Integer.valueOf(hkey), toCstr(key), Integer.valueOf(KEY_READ)); 152 if (handles[1] != REG_SUCCESS) { 153 return null; 154 } 155 int[] info = (int[]) regQueryInfoKey.invoke(root, Integer.valueOf(handles[0])); 156 157 int count = info[0]; // count 158 int maxlen = info[3]; // value length max 159 for (int index = 0; index < count; index++) { 160 byte[] name = (byte[]) regEnumValue.invoke(root, Integer.valueOf(handles[0]), Integer.valueOf(index), Integer.valueOf(maxlen + 1)); 161 String value = readString(hkey, key, new String(name, StandardCharsets.UTF_8)); 162 results.put(new String(name, StandardCharsets.UTF_8).trim(), value); 163 } 164 regCloseKey.invoke(root, Integer.valueOf(handles[0])); 165 return results; 166 } 167 168 private static List<String> readStringSubKeys(Preferences root, int hkey, String key) 169 throws IllegalAccessException, InvocationTargetException { 170 List<String> results = new ArrayList<>(); 171 int[] handles = (int[]) regOpenKey.invoke(root, Integer.valueOf(hkey), toCstr(key), Integer.valueOf(KEY_READ)); 172 if (handles[1] != REG_SUCCESS) { 173 return Collections.emptyList(); 174 } 175 int[] info = (int[]) regQueryInfoKey.invoke(root, Integer.valueOf(handles[0])); 176 177 int count = info[0]; // Fix: info[2] was being used here with wrong results. Suggested by davenpcj, confirmed by Petrucio 178 int maxlen = info[3]; // value length max 179 for (int index = 0; index < count; index++) { 180 byte[] name = (byte[]) regEnumKeyEx.invoke(root, Integer.valueOf(handles[0]), Integer.valueOf(index), Integer.valueOf(maxlen + 1)); 181 results.add(new String(name, StandardCharsets.UTF_8).trim()); 182 } 183 regCloseKey.invoke(root, Integer.valueOf(handles[0])); 184 return results; 185 } 186 187 // utility 188 private static byte[] toCstr(String str) { 189 byte[] array = str.getBytes(StandardCharsets.UTF_8); 190 byte[] biggerCopy = Arrays.copyOf(array, array.length + 1); 191 biggerCopy[array.length] = 0; 192 return biggerCopy; 193 } 194}