// License: GPL. For details, see LICENSE file.
package org.openstreetmap.josm.tools;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.prefs.Preferences;
/**
* Utility class to access Window registry (read access only).
* As the implementation relies on internal JDK class {@code java.util.prefs.WindowsPreferences} and its native JNI
* method {@code Java_java_util_prefs_WindowsPreferences_WindowsRegQueryValueEx}, only String values (REG_SZ)
* are supported.
* Adapted from StackOverflow.
* @since 12217
*/
public final class WinRegistry {
/**
* Registry entries subordinate to this key define the preferences of the current user.
* These preferences include the settings of environment variables, data about program groups,
* colors, printers, network connections, and application preferences.
* See Predefined Keys
*/
public static final int HKEY_CURRENT_USER = 0x80000001;
/**
* Registry entries subordinate to this key define the physical state of the computer, including data about the bus type,
* system memory, and installed hardware and software. It contains subkeys that hold current configuration data,
* including Plug and Play information (the Enum branch, which includes a complete list of all hardware that has ever been
* on the system), network logon preferences, network security information, software-related information (such as server
* names and the location of the server), and other system information.
* See Predefined Keys
*/
public static final int HKEY_LOCAL_MACHINE = 0x80000002;
private static final int REG_SUCCESS = 0;
private static final int KEY_READ = 0x20019;
private static final Preferences userRoot = Preferences.userRoot();
private static final Preferences systemRoot = Preferences.systemRoot();
private static final Class extends Preferences> userClass = userRoot.getClass();
private static final Method regOpenKey;
private static final Method regCloseKey;
private static final Method regQueryValueEx;
private static final Method regEnumValue;
private static final Method regQueryInfoKey;
private static final Method regEnumKeyEx;
static {
try {
regOpenKey = userClass.getDeclaredMethod("WindowsRegOpenKey", int.class, byte[].class, int.class);
regCloseKey = userClass.getDeclaredMethod("WindowsRegCloseKey", int.class);
regQueryValueEx = userClass.getDeclaredMethod("WindowsRegQueryValueEx", int.class, byte[].class);
regEnumValue = userClass.getDeclaredMethod("WindowsRegEnumValue", int.class, int.class, int.class);
regQueryInfoKey = userClass.getDeclaredMethod("WindowsRegQueryInfoKey1", int.class);
regEnumKeyEx = userClass.getDeclaredMethod("WindowsRegEnumKeyEx", int.class, int.class, int.class);
Utils.setObjectsAccessible(regOpenKey, regCloseKey, regQueryValueEx, regEnumValue, regQueryInfoKey, regEnumKeyEx);
} catch (RuntimeException | ReflectiveOperationException e) {
throw new JosmRuntimeException(e);
}
}
private WinRegistry() {
// Hide default constructor for utilities classes
}
/**
* Read a value from key and value name
* @param hkey HKEY_CURRENT_USER/HKEY_LOCAL_MACHINE
* @param key key name
* @param valueName value name
* @return the value
* @throws IllegalArgumentException if hkey not HKEY_CURRENT_USER/HKEY_LOCAL_MACHINE
* @throws IllegalAccessException if Java language access control is enforced and the underlying method is inaccessible
* @throws InvocationTargetException if the underlying method throws an exception
*/
public static String readString(int hkey, String key, String valueName)
throws IllegalAccessException, InvocationTargetException {
if (hkey == HKEY_LOCAL_MACHINE) {
return readString(systemRoot, hkey, key, valueName);
} else if (hkey == HKEY_CURRENT_USER) {
return readString(userRoot, hkey, key, valueName);
} else {
throw new IllegalArgumentException("hkey=" + hkey);
}
}
/**
* Read value(s) and value name(s) form given key
* @param hkey HKEY_CURRENT_USER/HKEY_LOCAL_MACHINE
* @param key key name
* @return the value name(s) plus the value(s)
* @throws IllegalArgumentException if hkey not HKEY_CURRENT_USER/HKEY_LOCAL_MACHINE
* @throws IllegalAccessException if Java language access control is enforced and the underlying method is inaccessible
* @throws InvocationTargetException if the underlying method throws an exception
*/
public static Map readStringValues(int hkey, String key)
throws IllegalAccessException, InvocationTargetException {
if (hkey == HKEY_LOCAL_MACHINE) {
return readStringValues(systemRoot, hkey, key);
} else if (hkey == HKEY_CURRENT_USER) {
return readStringValues(userRoot, hkey, key);
} else {
throw new IllegalArgumentException("hkey=" + hkey);
}
}
/**
* Read the value name(s) from a given key
* @param hkey HKEY_CURRENT_USER/HKEY_LOCAL_MACHINE
* @param key key name
* @return the value name(s)
* @throws IllegalArgumentException if hkey not HKEY_CURRENT_USER/HKEY_LOCAL_MACHINE
* @throws IllegalAccessException if Java language access control is enforced and the underlying method is inaccessible
* @throws InvocationTargetException if the underlying method throws an exception
*/
public static List readStringSubKeys(int hkey, String key)
throws IllegalAccessException, InvocationTargetException {
if (hkey == HKEY_LOCAL_MACHINE) {
return readStringSubKeys(systemRoot, hkey, key);
} else if (hkey == HKEY_CURRENT_USER) {
return readStringSubKeys(userRoot, hkey, key);
} else {
throw new IllegalArgumentException("hkey=" + hkey);
}
}
// =====================
private static String readString(Preferences root, int hkey, String key, String value)
throws IllegalAccessException, InvocationTargetException {
int[] handles = (int[]) regOpenKey.invoke(root, Integer.valueOf(hkey), toCstr(key), Integer.valueOf(KEY_READ));
if (handles[1] != REG_SUCCESS) {
return null;
}
byte[] valb = (byte[]) regQueryValueEx.invoke(root, Integer.valueOf(handles[0]), toCstr(value));
regCloseKey.invoke(root, Integer.valueOf(handles[0]));
return (valb != null ? new String(valb, StandardCharsets.UTF_8).trim() : null);
}
private static Map readStringValues(Preferences root, int hkey, String key)
throws IllegalAccessException, InvocationTargetException {
HashMap results = new HashMap<>();
int[] handles = (int[]) regOpenKey.invoke(root, Integer.valueOf(hkey), toCstr(key), Integer.valueOf(KEY_READ));
if (handles[1] != REG_SUCCESS) {
return null;
}
int[] info = (int[]) regQueryInfoKey.invoke(root, Integer.valueOf(handles[0]));
int count = info[0]; // count
int maxlen = info[3]; // value length max
for (int index = 0; index < count; index++) {
byte[] name = (byte[]) regEnumValue.invoke(root, Integer.valueOf(handles[0]), Integer.valueOf(index), Integer.valueOf(maxlen + 1));
String value = readString(hkey, key, new String(name, StandardCharsets.UTF_8));
results.put(new String(name, StandardCharsets.UTF_8).trim(), value);
}
regCloseKey.invoke(root, Integer.valueOf(handles[0]));
return results;
}
private static List readStringSubKeys(Preferences root, int hkey, String key)
throws IllegalAccessException, InvocationTargetException {
List results = new ArrayList<>();
int[] handles = (int[]) regOpenKey.invoke(root, Integer.valueOf(hkey), toCstr(key), Integer.valueOf(KEY_READ));
if (handles[1] != REG_SUCCESS) {
return Collections.emptyList();
}
int[] info = (int[]) regQueryInfoKey.invoke(root, Integer.valueOf(handles[0]));
int count = info[0]; // Fix: info[2] was being used here with wrong results. Suggested by davenpcj, confirmed by Petrucio
int maxlen = info[3]; // value length max
for (int index = 0; index < count; index++) {
byte[] name = (byte[]) regEnumKeyEx.invoke(root, Integer.valueOf(handles[0]), Integer.valueOf(index), Integer.valueOf(maxlen + 1));
results.add(new String(name, StandardCharsets.UTF_8).trim());
}
regCloseKey.invoke(root, Integer.valueOf(handles[0]));
return results;
}
// utility
private static byte[] toCstr(String str) {
byte[] array = str.getBytes(StandardCharsets.UTF_8);
byte[] biggerCopy = Arrays.copyOf(array, array.length + 1);
biggerCopy[array.length] = 0;
return biggerCopy;
}
}