From 077298ddf43a05324f4c58162fbe02fab45585cf Mon Sep 17 00:00:00 2001 From: Adam Murdoch Date: Tue, 10 Dec 2013 18:02:35 +1100 Subject: [PATCH] Added WindowsRegistry. --- build.gradle | 2 + src/main/cpp/win.cpp | 78 +++++++++++++++++++ src/main/headers/generic.h | 2 +- .../MissingRegistryEntryException.java | 10 +++ .../platform/WindowsRegistry.java | 26 +++++++ .../internal/DefaultWindowsRegistry.java | 41 ++++++++++ .../platform/internal/Platform.java | 3 + .../internal/jni/NativeLibraryFunctions.java | 2 +- .../jni/WindowsRegistryFunctions.java | 13 ++++ .../platform/WindowsRegistryTest.groovy | 50 ++++++++++++ 10 files changed, 225 insertions(+), 2 deletions(-) create mode 100644 src/main/java/net/rubygrapefruit/platform/MissingRegistryEntryException.java create mode 100644 src/main/java/net/rubygrapefruit/platform/WindowsRegistry.java create mode 100644 src/main/java/net/rubygrapefruit/platform/internal/DefaultWindowsRegistry.java create mode 100755 src/main/java/net/rubygrapefruit/platform/internal/jni/WindowsRegistryFunctions.java create mode 100644 src/test/groovy/net/rubygrapefruit/platform/WindowsRegistryTest.groovy diff --git a/build.gradle b/build.gradle index ecc647f..5232b67 100755 --- a/build.gradle +++ b/build.gradle @@ -60,6 +60,7 @@ task nativeHeaders { args 'net.rubygrapefruit.platform.internal.jni.TerminfoFunctions' args 'net.rubygrapefruit.platform.internal.jni.WindowsConsoleFunctions' args 'net.rubygrapefruit.platform.internal.jni.WindowsHandleFunctions' + args 'net.rubygrapefruit.platform.internal.jni.WindowsRegistryFunctions' } } } @@ -117,6 +118,7 @@ libraries { cppCompiler.args "-I${org.gradle.internal.jvm.Jvm.current().javaHome}/include" cppCompiler.args "-I${org.gradle.internal.jvm.Jvm.current().javaHome}/include/win32" cppCompiler.define("WIN32") + linker.args "Shlwapi.lib", "Advapi32.lib" } cppCompiler.args "-I${nativeHeadersDir}" tasks.withType(CppCompile) { task -> diff --git a/src/main/cpp/win.cpp b/src/main/cpp/win.cpp index 435e255..3ea6196 100755 --- a/src/main/cpp/win.cpp +++ b/src/main/cpp/win.cpp @@ -19,6 +19,7 @@ #include "native.h" #include "generic.h" #include +#include #include /* @@ -475,4 +476,81 @@ JNIEXPORT void JNICALL Java_net_rubygrapefruit_platform_internal_jni_WindowsHandleFunctions_restoreStandardHandles(JNIEnv *env, jclass target, jobject result) { } +HKEY get_key_from_ordinal(jint keyNum) { + return keyNum == 0 ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER; +} + +JNIEXPORT jstring JNICALL +Java_net_rubygrapefruit_platform_internal_jni_WindowsRegistryFunctions_getStringValue(JNIEnv *env, jclass target, jint keyNum, jstring subkey, jstring valueName, jobject result) { + HKEY key = get_key_from_ordinal(keyNum); + wchar_t* subkeyStr = java_to_wchar(env, subkey, result); + wchar_t* valueNameStr = java_to_wchar(env, valueName, result); + DWORD size = 0; + + LONG retval = SHRegGetValueW(key, subkeyStr, valueNameStr, SRRF_RT_REG_SZ, NULL, NULL, &size); + if (retval != ERROR_SUCCESS) { + free(subkeyStr); + free(valueNameStr); + if (retval != ERROR_FILE_NOT_FOUND) { + mark_failed_with_code(env, "could not determine size of registry value", retval, NULL, result); + } + return NULL; + } + + wchar_t* value = (wchar_t*)malloc(sizeof(wchar_t) * (size+1)); + retval = SHRegGetValueW(key, subkeyStr, valueNameStr, SRRF_RT_REG_SZ, NULL, value, &size); + free(subkeyStr); + free(valueNameStr); + if (retval != ERROR_SUCCESS) { + free(value); + mark_failed_with_code(env, "could not get registry value", retval, NULL, result); + return NULL; + } + + jstring jvalue = wchar_to_java(env, value, wcslen(value), result); + free(value); + + return jvalue; +} + +JNIEXPORT jboolean JNICALL +Java_net_rubygrapefruit_platform_internal_jni_WindowsRegistryFunctions_getSubkeys(JNIEnv *env, jclass target, jint keyNum, jstring subkey, jobject subkeys, jobject result) { + wchar_t* subkeyStr = java_to_wchar(env, subkey, result); + jclass subkeys_class = env->GetObjectClass(subkeys); + jmethodID method = env->GetMethodID(subkeys_class, "add", "(Ljava/lang/Object;)Z"); + + HKEY key; + LONG retval = RegOpenKeyExW(get_key_from_ordinal(keyNum), subkeyStr, 0, KEY_READ, &key); + if (retval != ERROR_SUCCESS) { + free(subkeyStr); + if (retval != ERROR_FILE_NOT_FOUND) { + mark_failed_with_code(env, "could open registry key", retval, NULL, result); + } + return false; + } + + DWORD subkeyCount; + DWORD maxSubkeyLen; + retval = RegQueryInfoKeyW(key, NULL, NULL, NULL, &subkeyCount, &maxSubkeyLen, NULL, NULL, NULL, NULL, NULL, NULL); + if (retval != ERROR_SUCCESS) { + mark_failed_with_code(env, "could query registry key", retval, NULL, result); + } else { + wchar_t* keyNameStr = (wchar_t*)malloc(sizeof(wchar_t) * (maxSubkeyLen+1)); + for (int i = 0; i < subkeyCount; i++) { + DWORD keyNameLen = maxSubkeyLen + 1; + retval = RegEnumKeyExW(key, i, keyNameStr, &keyNameLen, NULL, NULL, NULL, NULL); + if (retval != ERROR_SUCCESS) { + mark_failed_with_code(env, "could enumerate registry subkey", retval, NULL, result); + break; + } + env->CallVoidMethod(subkeys, method, wchar_to_java(env, keyNameStr, wcslen(keyNameStr), result)); + } + free(keyNameStr); + } + + RegCloseKey(key); + free(subkeyStr); + return true; +} + #endif diff --git a/src/main/headers/generic.h b/src/main/headers/generic.h index 35b50af..3509d2d 100755 --- a/src/main/headers/generic.h +++ b/src/main/headers/generic.h @@ -23,7 +23,7 @@ extern "C" { #endif -#define NATIVE_VERSION 15 +#define NATIVE_VERSION 16 /* * Marks the given result as failed, using the given error message diff --git a/src/main/java/net/rubygrapefruit/platform/MissingRegistryEntryException.java b/src/main/java/net/rubygrapefruit/platform/MissingRegistryEntryException.java new file mode 100644 index 0000000..4d79688 --- /dev/null +++ b/src/main/java/net/rubygrapefruit/platform/MissingRegistryEntryException.java @@ -0,0 +1,10 @@ +package net.rubygrapefruit.platform; + +/** + * Thrown when attempting to query an unknown registry key or value. + */ +public class MissingRegistryEntryException extends NativeException { + public MissingRegistryEntryException(String message) { + super(message); + } +} diff --git a/src/main/java/net/rubygrapefruit/platform/WindowsRegistry.java b/src/main/java/net/rubygrapefruit/platform/WindowsRegistry.java new file mode 100644 index 0000000..e924c08 --- /dev/null +++ b/src/main/java/net/rubygrapefruit/platform/WindowsRegistry.java @@ -0,0 +1,26 @@ +package net.rubygrapefruit.platform; + +import java.util.List; + +@ThreadSafe +public interface WindowsRegistry extends NativeIntegration { + public enum Key { + HKEY_LOCAL_MACHINE, HKEY_CURRENT_USER + } + + /** + * Returns a registry key value as a String. + * + * @throws NativeException On failure. + * @throws MissingRegistryEntryException When the requested key or value does not exist. + */ + String getStringValue(Key key, String subkey, String value) throws NativeException; + + /** + * Lists the subkeys of a registry key. + * + * @throws NativeException On failure. + * @throws MissingRegistryEntryException When the requested key does not exist. + */ + List getSubkeys(Key key, String subkey) throws NativeException; +} diff --git a/src/main/java/net/rubygrapefruit/platform/internal/DefaultWindowsRegistry.java b/src/main/java/net/rubygrapefruit/platform/internal/DefaultWindowsRegistry.java new file mode 100644 index 0000000..b313b8e --- /dev/null +++ b/src/main/java/net/rubygrapefruit/platform/internal/DefaultWindowsRegistry.java @@ -0,0 +1,41 @@ +package net.rubygrapefruit.platform.internal; + +import net.rubygrapefruit.platform.MissingRegistryEntryException; +import net.rubygrapefruit.platform.NativeException; +import net.rubygrapefruit.platform.WindowsRegistry; +import net.rubygrapefruit.platform.internal.jni.WindowsRegistryFunctions; + +import java.util.ArrayList; +import java.util.List; + +public class DefaultWindowsRegistry implements WindowsRegistry { + public String getStringValue(Key key, String subkey, String valueName) throws NativeException { + FunctionResult result = new FunctionResult(); + String value = WindowsRegistryFunctions.getStringValue(key.ordinal(), subkey, valueName, result); + if (result.isFailed()) { + throw new NativeException(String.format("Could not get value '%s' of registry key '%s\\%s': %s", valueName, + key, + subkey, result.getMessage())); + } + if (value == null) { + throw new MissingRegistryEntryException(String.format( + "Could not get value '%s' of registry key '%s\\%s' as it does not exist.", valueName, key, subkey)); + } + return value; + } + + public List getSubkeys(Key key, String subkey) throws NativeException { + FunctionResult result = new FunctionResult(); + ArrayList subkeys = new ArrayList(); + boolean found = WindowsRegistryFunctions.getSubkeys(key.ordinal(), subkey, subkeys, result); + if (result.isFailed()) { + throw new NativeException(String.format("Could not get subkeys of registry key '%s\\%s': %s", key, + subkey, result.getMessage())); + } + if (!found) { + throw new MissingRegistryEntryException(String.format( + "Could not list the subkeys of registry key '%s\\%s' as it does not exist.", key, subkey)); + } + return subkeys; + } +} diff --git a/src/main/java/net/rubygrapefruit/platform/internal/Platform.java b/src/main/java/net/rubygrapefruit/platform/internal/Platform.java index 7123c8b..e5c4b5c 100755 --- a/src/main/java/net/rubygrapefruit/platform/internal/Platform.java +++ b/src/main/java/net/rubygrapefruit/platform/internal/Platform.java @@ -114,6 +114,9 @@ public abstract class Platform { if (type.equals(FileSystems.class)) { return type.cast(new PosixFileSystems()); } + if (type.equals(WindowsRegistry.class)) { + return type.cast(new DefaultWindowsRegistry()); + } return super.get(type, nativeLibraryLoader); } } diff --git a/src/main/java/net/rubygrapefruit/platform/internal/jni/NativeLibraryFunctions.java b/src/main/java/net/rubygrapefruit/platform/internal/jni/NativeLibraryFunctions.java index 1a3576d..458898d 100755 --- a/src/main/java/net/rubygrapefruit/platform/internal/jni/NativeLibraryFunctions.java +++ b/src/main/java/net/rubygrapefruit/platform/internal/jni/NativeLibraryFunctions.java @@ -20,7 +20,7 @@ import net.rubygrapefruit.platform.internal.FunctionResult; import net.rubygrapefruit.platform.internal.MutableSystemInfo; public class NativeLibraryFunctions { - public static final int VERSION = 15; + public static final int VERSION = 16; public static native int getVersion(); diff --git a/src/main/java/net/rubygrapefruit/platform/internal/jni/WindowsRegistryFunctions.java b/src/main/java/net/rubygrapefruit/platform/internal/jni/WindowsRegistryFunctions.java new file mode 100755 index 0000000..6708ee1 --- /dev/null +++ b/src/main/java/net/rubygrapefruit/platform/internal/jni/WindowsRegistryFunctions.java @@ -0,0 +1,13 @@ +package net.rubygrapefruit.platform.internal.jni; + +import net.rubygrapefruit.platform.internal.FunctionResult; + +import java.util.List; + +public class WindowsRegistryFunctions { + // Returns null for unknown key or value + public static native String getStringValue(int key, String subkey, String value, FunctionResult result); + + // Returns false for unknown key + public static native boolean getSubkeys(int key, String subkey, List subkeys, FunctionResult result); +} diff --git a/src/test/groovy/net/rubygrapefruit/platform/WindowsRegistryTest.groovy b/src/test/groovy/net/rubygrapefruit/platform/WindowsRegistryTest.groovy new file mode 100644 index 0000000..bf5057f --- /dev/null +++ b/src/test/groovy/net/rubygrapefruit/platform/WindowsRegistryTest.groovy @@ -0,0 +1,50 @@ +package net.rubygrapefruit.platform + +import net.rubygrapefruit.platform.internal.Platform +import spock.lang.IgnoreIf +import spock.lang.Specification + +@IgnoreIf({!Platform.current().windows}) +class WindowsRegistryTest extends Specification { + def windowsRegistry = Native.get(WindowsRegistry) + + def "can read string value"() { + expect: + def currentVersion = windowsRegistry.getStringValue(WindowsRegistry.Key.HKEY_LOCAL_MACHINE, /SOFTWARE\Microsoft\Windows NT\CurrentVersion/, "CurrentVersion") + currentVersion.matches("\\d+\\.\\d+") + def path = new File(windowsRegistry.getStringValue(WindowsRegistry.Key.HKEY_CURRENT_USER, "Volatile Environment", "APPDATA")) + path.directory + } + + def "cannot read value that does not exist"() { + when: + windowsRegistry.getStringValue(WindowsRegistry.Key.HKEY_LOCAL_MACHINE, /SOFTWARE\Microsoft\Windows NT\CurrentVersion/, "Unknown") + + then: + def e = thrown(MissingRegistryEntryException) + e.message == /Could not get value 'Unknown' of registry key 'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion' as it does not exist./ + } + + def "cannot read value of key that does not exist"() { + when: + windowsRegistry.getStringValue(WindowsRegistry.Key.HKEY_LOCAL_MACHINE, /SOFTWARE\Unknown/, "Value") + + then: + def e = thrown(MissingRegistryEntryException) + e.message == /Could not get value 'Value' of registry key 'HKEY_LOCAL_MACHINE\SOFTWARE\Unknown' as it does not exist./ + } + + def "can read subkeys"() { + expect: + windowsRegistry.getSubkeys(WindowsRegistry.Key.HKEY_LOCAL_MACHINE, /SOFTWARE\Microsoft/).flatten().contains("Windows NT") + } + + def "cannot read subkeys of key that does not exist"() { + when: + windowsRegistry.getSubkeys(WindowsRegistry.Key.HKEY_LOCAL_MACHINE, /SOFTWARE\Unknown/) + + then: + def e = thrown(MissingRegistryEntryException) + e.message == /Could not list the subkeys of registry key 'HKEY_LOCAL_MACHINE\SOFTWARE\Unknown' as it does not exist./ + } +}