| /* | |
| * Copyright (c) 2000, 2024, Oracle and/or its affiliates. All rights reserved. | |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. | |
| * | |
| * This code is free software; you can redistribute it and/or modify it | |
| * under the terms of the GNU General Public License version 2 only, as | |
| * published by the Free Software Foundation. Oracle designates this | |
| * particular file as subject to the "Classpath" exception as provided | |
| * by Oracle in the LICENSE file that accompanied this code. | |
| * | |
| * This code is distributed in the hope that it will be useful, but WITHOUT | |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | |
| * version 2 for more details (a copy is included in the LICENSE file that | |
| * accompanied this code). | |
| * | |
| * You should have received a copy of the GNU General Public License version | |
| * 2 along with this work; if not, write to the Free Software Foundation, | |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | |
| * | |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | |
| * or visit www.oracle.com if you need additional information or have any | |
| * questions. | |
| */ | |
| package java.util.prefs; | |
| import java.util.StringTokenizer; | |
| import java.io.ByteArrayOutputStream; | |
| import sun.util.logging.PlatformLogger; | |
| /** | |
| * Windows registry based implementation of {@code Preferences}. | |
| * {@code Preferences}' {@code systemRoot} and {@code userRoot} are stored in | |
| * {@code HKEY_LOCAL_MACHINE\SOFTWARE\JavaSoft\Prefs} and | |
| * {@code HKEY_CURRENT_USER\Software\JavaSoft\Prefs} correspondingly. | |
| * | |
| * @author Konstantin Kladko | |
| * @see Preferences | |
| * @see PreferencesFactory | |
| * @since 1.4 | |
| */ | |
| class WindowsPreferences extends AbstractPreferences { | |
| static { | |
| loadPrefsLib(); | |
| } | |
| @SuppressWarnings("restricted") | |
| private static void loadPrefsLib() { | |
| System.loadLibrary("prefs"); | |
| } | |
| /** | |
| * Logger for error messages | |
| */ | |
| private static PlatformLogger logger; | |
| /** | |
| * Windows registry path to {@code Preferences}'s root nodes. | |
| */ | |
| private static final byte[] WINDOWS_ROOT_PATH = | |
| stringToByteArray("Software\\JavaSoft\\Prefs"); | |
| /** | |
| * Windows handles to {@code HKEY_CURRENT_USER} and | |
| * {@code HKEY_LOCAL_MACHINE} hives. | |
| */ | |
| private static final int HKEY_CURRENT_USER = 0x80000001; | |
| private static final int HKEY_LOCAL_MACHINE = 0x80000002; | |
| /** | |
| * Mount point for {@code Preferences}' user root. | |
| */ | |
| private static final int USER_ROOT_NATIVE_HANDLE = HKEY_CURRENT_USER; | |
| /** | |
| * Mount point for {@code Preferences}' system root. | |
| */ | |
| private static final int SYSTEM_ROOT_NATIVE_HANDLE = HKEY_LOCAL_MACHINE; | |
| /** | |
| * Maximum byte-encoded path length for Windows native functions, | |
| * ending {@code null} character not included. | |
| */ | |
| private static final int MAX_WINDOWS_PATH_LENGTH = 256; | |
| /** | |
| * User root node. | |
| */ | |
| private static volatile Preferences userRoot; | |
| static Preferences getUserRoot() { | |
| Preferences root = userRoot; | |
| if (root == null) { | |
| synchronized (WindowsPreferences.class) { | |
| root = userRoot; | |
| if (root == null) { | |
| root = new WindowsPreferences(USER_ROOT_NATIVE_HANDLE, WINDOWS_ROOT_PATH); | |
| userRoot = root; | |
| } | |
| } | |
| } | |
| return root; | |
| } | |
| /** | |
| * System root node. | |
| */ | |
| private static volatile Preferences systemRoot; | |
| static Preferences getSystemRoot() { | |
| Preferences root = systemRoot; | |
| if (root == null) { | |
| synchronized (WindowsPreferences.class) { | |
| root = systemRoot; | |
| if (root == null) { | |
| root = new WindowsPreferences(SYSTEM_ROOT_NATIVE_HANDLE, WINDOWS_ROOT_PATH); | |
| systemRoot = root; | |
| } | |
| } | |
| } | |
| return root; | |
| } | |
| /* Windows error codes. */ | |
| private static final int ERROR_SUCCESS = 0; | |
| private static final int ERROR_FILE_NOT_FOUND = 2; | |
| private static final int ERROR_ACCESS_DENIED = 5; | |
| /* Constants used to interpret returns of native functions */ | |
| private static final int NATIVE_HANDLE = 0; | |
| private static final int ERROR_CODE = 1; | |
| private static final int SUBKEYS_NUMBER = 0; | |
| private static final int VALUES_NUMBER = 2; | |
| private static final int MAX_KEY_LENGTH = 3; | |
| private static final int MAX_VALUE_NAME_LENGTH = 4; | |
| private static final int DISPOSITION = 2; | |
| private static final int REG_CREATED_NEW_KEY = 1; | |
| private static final int REG_OPENED_EXISTING_KEY = 2; | |
| private static final int NULL_NATIVE_HANDLE = 0; | |
| /* Windows security masks */ | |
| private static final int DELETE = 0x10000; | |
| private static final int KEY_QUERY_VALUE = 1; | |
| private static final int KEY_SET_VALUE = 2; | |
| private static final int KEY_CREATE_SUB_KEY = 4; | |
| private static final int KEY_ENUMERATE_SUB_KEYS = 8; | |
| private static final int KEY_READ = 0x20019; | |
| private static final int KEY_WRITE = 0x20006; | |
| private static final int KEY_ALL_ACCESS = 0xf003f; | |
| /** | |
| * Initial time between registry access attempts, in ms. The time is doubled | |
| * after each failing attempt (except the first). | |
| */ | |
| private static int INIT_SLEEP_TIME = 50; | |
| /** | |
| * Maximum number of registry access attempts. | |
| */ | |
| private static int MAX_ATTEMPTS = 5; | |
| /** | |
| * BackingStore availability flag. | |
| */ | |
| private boolean isBackingStoreAvailable = true; | |
| /** | |
| * Java wrapper for Windows registry API RegOpenKey() | |
| */ | |
| private static native long[] WindowsRegOpenKey(long hKey, byte[] subKey, | |
| int securityMask); | |
| /** | |
| * Retries RegOpenKey() MAX_ATTEMPTS times before giving up. | |
| */ | |
| private static long[] WindowsRegOpenKey1(long hKey, byte[] subKey, | |
| int securityMask) { | |
| long[] result = WindowsRegOpenKey(hKey, subKey, securityMask); | |
| if (result[ERROR_CODE] == ERROR_SUCCESS) { | |
| return result; | |
| } else if (result[ERROR_CODE] == ERROR_FILE_NOT_FOUND) { | |
| logger().warning("Trying to recreate Windows registry node " + | |
| byteArrayToString(subKey) + " at root 0x" + | |
| Long.toHexString(hKey) + "."); | |
| // Try recreation | |
| long handle = WindowsRegCreateKeyEx(hKey, subKey)[NATIVE_HANDLE]; | |
| WindowsRegCloseKey(handle); | |
| return WindowsRegOpenKey(hKey, subKey, securityMask); | |
| } else if (result[ERROR_CODE] != ERROR_ACCESS_DENIED) { | |
| long sleepTime = INIT_SLEEP_TIME; | |
| for (int i = 0; i < MAX_ATTEMPTS; i++) { | |
| try { | |
| Thread.sleep(sleepTime); | |
| } catch(InterruptedException e) { | |
| return result; | |
| } | |
| sleepTime *= 2; | |
| result = WindowsRegOpenKey(hKey, subKey, securityMask); | |
| if (result[ERROR_CODE] == ERROR_SUCCESS) { | |
| return result; | |
| } | |
| } | |
| } | |
| return result; | |
| } | |
| /** | |
| * Java wrapper for Windows registry API RegCloseKey() | |
| */ | |
| private static native int WindowsRegCloseKey(long hKey); | |
| /** | |
| * Java wrapper for Windows registry API RegCreateKeyEx() | |
| */ | |
| private static native long[] WindowsRegCreateKeyEx(long hKey, byte[] subKey); | |
| /** | |
| * Retries RegCreateKeyEx() MAX_ATTEMPTS times before giving up. | |
| */ | |
| private static long[] WindowsRegCreateKeyEx1(long hKey, byte[] subKey) { | |
| long[] result = WindowsRegCreateKeyEx(hKey, subKey); | |
| if (result[ERROR_CODE] == ERROR_SUCCESS) { | |
| return result; | |
| } else { | |
| long sleepTime = INIT_SLEEP_TIME; | |
| for (int i = 0; i < MAX_ATTEMPTS; i++) { | |
| try { | |
| Thread.sleep(sleepTime); | |
| } catch(InterruptedException e) { | |
| return result; | |
| } | |
| sleepTime *= 2; | |
| result = WindowsRegCreateKeyEx(hKey, subKey); | |
| if (result[ERROR_CODE] == ERROR_SUCCESS) { | |
| return result; | |
| } | |
| } | |
| } | |
| return result; | |
| } | |
| /** | |
| * Java wrapper for Windows registry API RegDeleteKey() | |
| */ | |
| private static native int WindowsRegDeleteKey(long hKey, byte[] subKey); | |
| /** | |
| * Java wrapper for Windows registry API RegFlushKey() | |
| */ | |
| private static native int WindowsRegFlushKey(long hKey); | |
| /** | |
| * Retries RegFlushKey() MAX_ATTEMPTS times before giving up. | |
| */ | |
| private static int WindowsRegFlushKey1(long hKey) { | |
| int result = WindowsRegFlushKey(hKey); | |
| if (result == ERROR_SUCCESS) { | |
| return result; | |
| } else { | |
| long sleepTime = INIT_SLEEP_TIME; | |
| for (int i = 0; i < MAX_ATTEMPTS; i++) { | |
| try { | |
| Thread.sleep(sleepTime); | |
| } catch(InterruptedException e) { | |
| return result; | |
| } | |
| sleepTime *= 2; | |
| result = WindowsRegFlushKey(hKey); | |
| if (result == ERROR_SUCCESS) { | |
| return result; | |
| } | |
| } | |
| } | |
| return result; | |
| } | |
| /** | |
| * Java wrapper for Windows registry API RegQueryValueEx() | |
| */ | |
| private static native byte[] WindowsRegQueryValueEx(long hKey, | |
| byte[] valueName); | |
| /** | |
| * Java wrapper for Windows registry API RegSetValueEx() | |
| */ | |
| private static native int WindowsRegSetValueEx(long hKey, byte[] valueName, | |
| byte[] value); | |
| /** | |
| * Retries RegSetValueEx() MAX_ATTEMPTS times before giving up. | |
| */ | |
| private static int WindowsRegSetValueEx1(long hKey, byte[] valueName, | |
| byte[] value) { | |
| int result = WindowsRegSetValueEx(hKey, valueName, value); | |
| if (result == ERROR_SUCCESS) { | |
| return result; | |
| } else { | |
| long sleepTime = INIT_SLEEP_TIME; | |
| for (int i = 0; i < MAX_ATTEMPTS; i++) { | |
| try { | |
| Thread.sleep(sleepTime); | |
| } catch(InterruptedException e) { | |
| return result; | |
| } | |
| sleepTime *= 2; | |
| result = WindowsRegSetValueEx(hKey, valueName, value); | |
| if (result == ERROR_SUCCESS) { | |
| return result; | |
| } | |
| } | |
| } | |
| return result; | |
| } | |
| /** | |
| * Java wrapper for Windows registry API RegDeleteValue() | |
| */ | |
| private static native int WindowsRegDeleteValue(long hKey, byte[] valueName); | |
| /** | |
| * Java wrapper for Windows registry API RegQueryInfoKey() | |
| */ | |
| private static native long[] WindowsRegQueryInfoKey(long hKey); | |
| /** | |
| * Retries RegQueryInfoKey() MAX_ATTEMPTS times before giving up. | |
| */ | |
| private static long[] WindowsRegQueryInfoKey1(long hKey) { | |
| long[] result = WindowsRegQueryInfoKey(hKey); | |
| if (result[ERROR_CODE] == ERROR_SUCCESS) { | |
| return result; | |
| } else { | |
| long sleepTime = INIT_SLEEP_TIME; | |
| for (int i = 0; i < MAX_ATTEMPTS; i++) { | |
| try { | |
| Thread.sleep(sleepTime); | |
| } catch(InterruptedException e) { | |
| return result; | |
| } | |
| sleepTime *= 2; | |
| result = WindowsRegQueryInfoKey(hKey); | |
| if (result[ERROR_CODE] == ERROR_SUCCESS) { | |
| return result; | |
| } | |
| } | |
| } | |
| return result; | |
| } | |
| /** | |
| * Java wrapper for Windows registry API RegEnumKeyEx() | |
| */ | |
| private static native byte[] WindowsRegEnumKeyEx(long hKey, int subKeyIndex, | |
| int maxKeyLength); | |
| /** | |
| * Retries RegEnumKeyEx() MAX_ATTEMPTS times before giving up. | |
| */ | |
| private static byte[] WindowsRegEnumKeyEx1(long hKey, int subKeyIndex, | |
| int maxKeyLength) { | |
| byte[] result = WindowsRegEnumKeyEx(hKey, subKeyIndex, maxKeyLength); | |
| if (result != null) { | |
| return result; | |
| } else { | |
| long sleepTime = INIT_SLEEP_TIME; | |
| for (int i = 0; i < MAX_ATTEMPTS; i++) { | |
| try { | |
| Thread.sleep(sleepTime); | |
| } catch(InterruptedException e) { | |
| return result; | |
| } | |
| sleepTime *= 2; | |
| result = WindowsRegEnumKeyEx(hKey, subKeyIndex, maxKeyLength); | |
| if (result != null) { | |
| return result; | |
| } | |
| } | |
| } | |
| return result; | |
| } | |
| /** | |
| * Java wrapper for Windows registry API RegEnumValue() | |
| */ | |
| private static native byte[] WindowsRegEnumValue(long hKey, int valueIndex, | |
| int maxValueNameLength); | |
| /** | |
| * Retries RegEnumValueEx() MAX_ATTEMPTS times before giving up. | |
| */ | |
| private static byte[] WindowsRegEnumValue1(long hKey, int valueIndex, | |
| int maxValueNameLength) { | |
| byte[] result = WindowsRegEnumValue(hKey, valueIndex, | |
| maxValueNameLength); | |
| if (result != null) { | |
| return result; | |
| } else { | |
| long sleepTime = INIT_SLEEP_TIME; | |
| for (int i = 0; i < MAX_ATTEMPTS; i++) { | |
| try { | |
| Thread.sleep(sleepTime); | |
| } catch(InterruptedException e) { | |
| return result; | |
| } | |
| sleepTime *= 2; | |
| result = WindowsRegEnumValue(hKey, valueIndex, | |
| maxValueNameLength); | |
| if (result != null) { | |
| return result; | |
| } | |
| } | |
| } | |
| return result; | |
| } | |
| /** | |
| * Constructs a {@code WindowsPreferences} node, creating underlying | |
| * Windows registry node and all its Windows parents, if they are not yet | |
| * created. | |
| * Logs a warning message, if Windows Registry is unavailable. | |
| */ | |
| private WindowsPreferences(WindowsPreferences parent, String name) { | |
| super(parent, name); | |
| long parentNativeHandle = parent.openKey(KEY_CREATE_SUB_KEY, KEY_READ); | |
| if (parentNativeHandle == NULL_NATIVE_HANDLE) { | |
| // if here, openKey failed and logged | |
| isBackingStoreAvailable = false; | |
| return; | |
| } | |
| long[] result = | |
| WindowsRegCreateKeyEx1(parentNativeHandle, toWindowsName(name)); | |
| if (result[ERROR_CODE] != ERROR_SUCCESS) { | |
| logger().warning("Could not create windows registry node " + | |
| byteArrayToString(windowsAbsolutePath()) + | |
| " at root 0x" + Long.toHexString(parentNativeHandle) + | |
| ". Windows RegCreateKeyEx(...) returned error code " + | |
| result[ERROR_CODE] + "."); | |
| isBackingStoreAvailable = false; | |
| return; | |
| } | |
| newNode = (result[DISPOSITION] == REG_CREATED_NEW_KEY); | |
| closeKey(parentNativeHandle); | |
| closeKey(result[NATIVE_HANDLE]); | |
| } | |
| /** | |
| * Constructs a root node creating the underlying | |
| * Windows registry node and all of its parents, if they have not yet been | |
| * created. | |
| * Logs a warning message, if Windows Registry is unavailable. | |
| * @param rootNativeHandle Native handle to one of Windows top level keys. | |
| * @param rootDirectory Path to root directory, as a byte-encoded string. | |
| */ | |
| private WindowsPreferences(long rootNativeHandle, byte[] rootDirectory) { | |
| super(null, ""); | |
| long[] result = | |
| WindowsRegCreateKeyEx1(rootNativeHandle, rootDirectory); | |
| if (result[ERROR_CODE] != ERROR_SUCCESS) { | |
| logger().warning("Could not open/create prefs root node " + | |
| byteArrayToString(windowsAbsolutePath()) + | |
| " at root 0x" + Long.toHexString(rootNativeHandle) + | |
| ". Windows RegCreateKeyEx(...) returned error code " + | |
| result[ERROR_CODE] + "."); | |
| isBackingStoreAvailable = false; | |
| return; | |
| } | |
| // Check if a new node | |
| newNode = (result[DISPOSITION] == REG_CREATED_NEW_KEY); | |
| closeKey(result[NATIVE_HANDLE]); | |
| } | |
| /** | |
| * Returns Windows absolute path of the current node as a byte array. | |
| * Java "/" separator is transformed into Windows "\". | |
| * @see Preferences#absolutePath() | |
| */ | |
| private byte[] windowsAbsolutePath() { | |
| ByteArrayOutputStream bstream = new ByteArrayOutputStream(); | |
| bstream.write(WINDOWS_ROOT_PATH, 0, WINDOWS_ROOT_PATH.length-1); | |
| StringTokenizer tokenizer = new StringTokenizer(absolutePath(), "/"); | |
| while (tokenizer.hasMoreTokens()) { | |
| bstream.write((byte)'\\'); | |
| String nextName = tokenizer.nextToken(); | |
| byte[] windowsNextName = toWindowsName(nextName); | |
| bstream.write(windowsNextName, 0, windowsNextName.length-1); | |
| } | |
| bstream.write(0); | |
| return bstream.toByteArray(); | |
| } | |
| /** | |
| * Opens current node's underlying Windows registry key using a | |
| * given security mask. | |
| * @param securityMask Windows security mask. | |
| * @return Windows registry key's handle. | |
| * @see #openKey(byte[], int, int) | |
| * @see #openKey(long, byte[], int, int) | |
| * @see #closeKey(long) | |
| */ | |
| private long openKey(int securityMask) { | |
| return openKey(securityMask, securityMask); | |
| } | |
| /** | |
| * Opens current node's underlying Windows registry key using a | |
| * given security mask. | |
| * @param mask1 Preferred Windows security mask. | |
| * @param mask2 Alternate Windows security mask. | |
| * @return Windows registry key's handle. | |
| * @see #openKey(byte[], int, int) | |
| * @see #openKey(long, byte[], int, int) | |
| * @see #closeKey(long) | |
| */ | |
| private long openKey(int mask1, int mask2) { | |
| return openKey(windowsAbsolutePath(), mask1, mask2); | |
| } | |
| /** | |
| * Opens Windows registry key at a given absolute path using a given | |
| * security mask. | |
| * @param windowsAbsolutePath Windows absolute path of the | |
| * key as a byte-encoded string. | |
| * @param mask1 Preferred Windows security mask. | |
| * @param mask2 Alternate Windows security mask. | |
| * @return Windows registry key's handle. | |
| * @see #openKey(int) | |
| * @see #openKey(long, byte[], int, int) | |
| * @see #closeKey(long) | |
| */ | |
| private long openKey(byte[] windowsAbsolutePath, int mask1, int mask2) { | |
| /* Check if key's path is short enough be opened at once | |
| otherwise use a path-splitting procedure */ | |
| if (windowsAbsolutePath.length <= MAX_WINDOWS_PATH_LENGTH + 1) { | |
| long[] result = WindowsRegOpenKey1(rootNativeHandle(), | |
| windowsAbsolutePath, mask1); | |
| if (result[ERROR_CODE] == ERROR_ACCESS_DENIED && mask2 != mask1) | |
| result = WindowsRegOpenKey1(rootNativeHandle(), | |
| windowsAbsolutePath, mask2); | |
| if (result[ERROR_CODE] != ERROR_SUCCESS) { | |
| logger().warning("Could not open windows registry node " + | |
| byteArrayToString(windowsAbsolutePath()) + | |
| " at root 0x" + | |
| Long.toHexString(rootNativeHandle()) + | |
| ". Windows RegOpenKey(...) returned error code " + | |
| result[ERROR_CODE] + "."); | |
| result[NATIVE_HANDLE] = NULL_NATIVE_HANDLE; | |
| if (result[ERROR_CODE] == ERROR_ACCESS_DENIED) { | |
| throw new SecurityException( | |
| "Could not open windows registry node " + | |
| byteArrayToString(windowsAbsolutePath()) + | |
| " at root 0x" + | |
| Long.toHexString(rootNativeHandle()) + | |
| ": Access denied"); | |
| } | |
| } | |
| return result[NATIVE_HANDLE]; | |
| } else { | |
| return openKey(rootNativeHandle(), windowsAbsolutePath, mask1, mask2); | |
| } | |
| } | |
| /** | |
| * Opens Windows registry key at a given relative path | |
| * with respect to a given Windows registry key. | |
| * @param windowsRelativePath Windows relative path of the | |
| * key as a byte-encoded string. | |
| * @param nativeHandle handle to the base Windows key. | |
| * @param mask1 Preferred Windows security mask. | |
| * @param mask2 Alternate Windows security mask. | |
| * @return Windows registry key's handle. | |
| * @see #openKey(int) | |
| * @see #openKey(byte[], int, int) | |
| * @see #closeKey(long) | |
| */ | |
| private long openKey(long nativeHandle, byte[] windowsRelativePath, | |
| int mask1, int mask2) { | |
| /* If the path is short enough open at once. Otherwise split the path */ | |
| if (windowsRelativePath.length <= MAX_WINDOWS_PATH_LENGTH + 1 ) { | |
| long[] result = WindowsRegOpenKey1(nativeHandle, | |
| windowsRelativePath, mask1); | |
| if (result[ERROR_CODE] == ERROR_ACCESS_DENIED && mask2 != mask1) | |
| result = WindowsRegOpenKey1(nativeHandle, | |
| windowsRelativePath, mask2); | |
| if (result[ERROR_CODE] != ERROR_SUCCESS) { | |
| logger().warning("Could not open windows registry node " + | |
| byteArrayToString(windowsAbsolutePath()) + | |
| " at root 0x" + Long.toHexString(nativeHandle) + | |
| ". Windows RegOpenKey(...) returned error code " + | |
| result[ERROR_CODE] + "."); | |
| result[NATIVE_HANDLE] = NULL_NATIVE_HANDLE; | |
| } | |
| return result[NATIVE_HANDLE]; | |
| } else { | |
| int separatorPosition = -1; | |
| // Be greedy - open the longest possible path | |
| for (int i = MAX_WINDOWS_PATH_LENGTH; i > 0; i--) { | |
| if (windowsRelativePath[i] == ((byte)'\\')) { | |
| separatorPosition = i; | |
| break; | |
| } | |
| } | |
| // Split the path and do the recursion | |
| byte[] nextRelativeRoot = new byte[separatorPosition+1]; | |
| System.arraycopy(windowsRelativePath, 0, nextRelativeRoot,0, | |
| separatorPosition); | |
| nextRelativeRoot[separatorPosition] = 0; | |
| byte[] nextRelativePath = new byte[windowsRelativePath.length - | |
| separatorPosition - 1]; | |
| System.arraycopy(windowsRelativePath, separatorPosition+1, | |
| nextRelativePath, 0, nextRelativePath.length); | |
| long nextNativeHandle = openKey(nativeHandle, nextRelativeRoot, | |
| mask1, mask2); | |
| if (nextNativeHandle == NULL_NATIVE_HANDLE) { | |
| return NULL_NATIVE_HANDLE; | |
| } | |
| long result = openKey(nextNativeHandle, nextRelativePath, | |
| mask1,mask2); | |
| closeKey(nextNativeHandle); | |
| return result; | |
| } | |
| } | |
| /** | |
| * Closes Windows registry key. | |
| * Logs a warning if Windows registry is unavailable. | |
| * @param nativeHandle Windows registry handle. | |
| * @see #openKey(int) | |
| * @see #openKey(byte[], int, int) | |
| * @see #openKey(long, byte[], int, int) | |
| */ | |
| private void closeKey(long nativeHandle) { | |
| int result = WindowsRegCloseKey(nativeHandle); | |
| if (result != ERROR_SUCCESS) { | |
| logger().warning("Could not close windows registry node " + | |
| byteArrayToString(windowsAbsolutePath()) + | |
| " at root 0x" + | |
| Long.toHexString(rootNativeHandle()) + | |
| ". Windows RegCloseKey(...) returned error code " + | |
| result + "."); | |
| } | |
| } | |
| /** | |
| * Implements {@code AbstractPreferences} {@code putSpi()} method. | |
| * Puts name-value pair into the underlying Windows registry node. | |
| * Logs a warning, if Windows registry is unavailable. | |
| * @see #getSpi(String) | |
| */ | |
| protected void putSpi(String javaName, String value) { | |
| long nativeHandle = openKey(KEY_SET_VALUE); | |
| if (nativeHandle == NULL_NATIVE_HANDLE) { | |
| isBackingStoreAvailable = false; | |
| return; | |
| } | |
| int result = WindowsRegSetValueEx1(nativeHandle, | |
| toWindowsName(javaName), toWindowsValueString(value)); | |
| if (result != ERROR_SUCCESS) { | |
| logger().warning("Could not assign value to key " + | |
| byteArrayToString(toWindowsName(javaName)) + | |
| " at Windows registry node " + | |
| byteArrayToString(windowsAbsolutePath()) + | |
| " at root 0x" + | |
| Long.toHexString(rootNativeHandle()) + | |
| ". Windows RegSetValueEx(...) returned error code " + | |
| result + "."); | |
| isBackingStoreAvailable = false; | |
| } | |
| closeKey(nativeHandle); | |
| } | |
| /** | |
| * Implements {@code AbstractPreferences} {@code getSpi()} method. | |
| * Gets a string value from the underlying Windows registry node. | |
| * Logs a warning, if Windows registry is unavailable. | |
| * @see #putSpi(String, String) | |
| */ | |
| protected String getSpi(String javaName) { | |
| long nativeHandle = openKey(KEY_QUERY_VALUE); | |
| if (nativeHandle == NULL_NATIVE_HANDLE) { | |
| return null; | |
| } | |
| Object resultObject = WindowsRegQueryValueEx(nativeHandle, | |
| toWindowsName(javaName)); | |
| if (resultObject == null) { | |
| closeKey(nativeHandle); | |
| return null; | |
| } | |
| closeKey(nativeHandle); | |
| return toJavaValueString((byte[]) resultObject); | |
| } | |
| /** | |
| * Implements {@code AbstractPreferences} {@code removeSpi()} method. | |
| * Deletes a string name-value pair from the underlying Windows registry | |
| * node, if this value still exists. | |
| * Logs a warning, if Windows registry is unavailable or key has already | |
| * been deleted. | |
| */ | |
| protected void removeSpi(String key) { | |
| long nativeHandle = openKey(KEY_SET_VALUE); | |
| if (nativeHandle == NULL_NATIVE_HANDLE) { | |
| return; | |
| } | |
| int result = | |
| WindowsRegDeleteValue(nativeHandle, toWindowsName(key)); | |
| if (result != ERROR_SUCCESS && result != ERROR_FILE_NOT_FOUND) { | |
| logger().warning("Could not delete windows registry value " + | |
| byteArrayToString(windowsAbsolutePath()) + "\\" + | |
| toWindowsName(key) + " at root 0x" + | |
| Long.toHexString(rootNativeHandle()) + | |
| ". Windows RegDeleteValue(...) returned error code " + | |
| result + "."); | |
| isBackingStoreAvailable = false; | |
| } | |
| closeKey(nativeHandle); | |
| } | |
| /** | |
| * Implements {@code AbstractPreferences} {@code keysSpi()} method. | |
| * Gets value names from the underlying Windows registry node. | |
| * Throws a BackingStoreException and logs a warning, if | |
| * Windows registry is unavailable. | |
| */ | |
| protected String[] keysSpi() throws BackingStoreException{ | |
| // Find out the number of values | |
| long nativeHandle = openKey(KEY_QUERY_VALUE); | |
| if (nativeHandle == NULL_NATIVE_HANDLE) { | |
| throw new BackingStoreException( | |
| "Could not open windows registry node " + | |
| byteArrayToString(windowsAbsolutePath()) + | |
| " at root 0x" + | |
| Long.toHexString(rootNativeHandle()) + "."); | |
| } | |
| long[] result = WindowsRegQueryInfoKey1(nativeHandle); | |
| if (result[ERROR_CODE] != ERROR_SUCCESS) { | |
| String info = "Could not query windows registry node " + | |
| byteArrayToString(windowsAbsolutePath()) + | |
| " at root 0x" + | |
| Long.toHexString(rootNativeHandle()) + | |
| ". Windows RegQueryInfoKeyEx(...) returned error code " + | |
| result[ERROR_CODE] + "."; | |
| logger().warning(info); | |
| throw new BackingStoreException(info); | |
| } | |
| int maxValueNameLength = (int)result[MAX_VALUE_NAME_LENGTH]; | |
| int valuesNumber = (int)result[VALUES_NUMBER]; | |
| if (valuesNumber == 0) { | |
| closeKey(nativeHandle); | |
| return new String[0]; | |
| } | |
| // Get the values | |
| String[] valueNames = new String[valuesNumber]; | |
| for (int i = 0; i < valuesNumber; i++) { | |
| byte[] windowsName = WindowsRegEnumValue1(nativeHandle, i, | |
| maxValueNameLength+1); | |
| if (windowsName == null) { | |
| String info = | |
| "Could not enumerate value #" + i + " of windows node " + | |
| byteArrayToString(windowsAbsolutePath()) + " at root 0x" + | |
| Long.toHexString(rootNativeHandle()) + "."; | |
| logger().warning(info); | |
| throw new BackingStoreException(info); | |
| } | |
| valueNames[i] = toJavaName(windowsName); | |
| } | |
| closeKey(nativeHandle); | |
| return valueNames; | |
| } | |
| /** | |
| * Implements {@code AbstractPreferences} {@code childrenNamesSpi()} method. | |
| * Calls Windows registry to retrieve children of this node. | |
| * Throws a BackingStoreException and logs a warning message, | |
| * if Windows registry is not available. | |
| */ | |
| protected String[] childrenNamesSpi() throws BackingStoreException { | |
| // Open key | |
| long nativeHandle = openKey(KEY_ENUMERATE_SUB_KEYS | KEY_QUERY_VALUE); | |
| if (nativeHandle == NULL_NATIVE_HANDLE) { | |
| throw new BackingStoreException( | |
| "Could not open windows registry node " + | |
| byteArrayToString(windowsAbsolutePath()) + | |
| " at root 0x" + | |
| Long.toHexString(rootNativeHandle()) + "."); | |
| } | |
| // Get number of children | |
| long[] result = WindowsRegQueryInfoKey1(nativeHandle); | |
| if (result[ERROR_CODE] != ERROR_SUCCESS) { | |
| String info = "Could not query windows registry node " + | |
| byteArrayToString(windowsAbsolutePath()) + | |
| " at root 0x" + Long.toHexString(rootNativeHandle()) + | |
| ". Windows RegQueryInfoKeyEx(...) returned error code " + | |
| result[ERROR_CODE] + "."; | |
| logger().warning(info); | |
| throw new BackingStoreException(info); | |
| } | |
| int maxKeyLength = (int)result[MAX_KEY_LENGTH]; | |
| int subKeysNumber = (int)result[SUBKEYS_NUMBER]; | |
| if (subKeysNumber == 0) { | |
| closeKey(nativeHandle); | |
| return new String[0]; | |
| } | |
| String[] children = new String[subKeysNumber]; | |
| // Get children | |
| for (int i = 0; i < subKeysNumber; i++) { | |
| byte[] windowsName = WindowsRegEnumKeyEx1(nativeHandle, i, | |
| maxKeyLength+1); | |
| if (windowsName == null) { | |
| String info = | |
| "Could not enumerate key #" + i + " of windows node " + | |
| byteArrayToString(windowsAbsolutePath()) + " at root 0x" + | |
| Long.toHexString(rootNativeHandle()) + ". "; | |
| logger().warning(info); | |
| throw new BackingStoreException(info); | |
| } | |
| String javaName = toJavaName(windowsName); | |
| children[i] = javaName; | |
| } | |
| closeKey(nativeHandle); | |
| return children; | |
| } | |
| /** | |
| * Implements {@code Preferences} {@code flush()} method. | |
| * Flushes Windows registry changes to disk. | |
| * Throws a BackingStoreException and logs a warning message if Windows | |
| * registry is not available. | |
| */ | |
| public void flush() throws BackingStoreException{ | |
| if (isRemoved()) { | |
| parent.flush(); | |
| return; | |
| } | |
| if (!isBackingStoreAvailable) { | |
| throw new BackingStoreException( | |
| "flush(): Backing store not available."); | |
| } | |
| long nativeHandle = openKey(KEY_READ); | |
| if (nativeHandle == NULL_NATIVE_HANDLE) { | |
| throw new BackingStoreException( | |
| "Could not open windows registry node " + | |
| byteArrayToString(windowsAbsolutePath()) + | |
| " at root 0x" + | |
| Long.toHexString(rootNativeHandle()) + "."); | |
| } | |
| int result = WindowsRegFlushKey1(nativeHandle); | |
| if (result != ERROR_SUCCESS) { | |
| String info = "Could not flush windows registry node " + | |
| byteArrayToString(windowsAbsolutePath()) + | |
| " at root 0x" + | |
| Long.toHexString(rootNativeHandle()) + | |
| ". Windows RegFlushKey(...) returned error code " + | |
| result + "."; | |
| logger().warning(info); | |
| throw new BackingStoreException(info); | |
| } | |
| closeKey(nativeHandle); | |
| } | |
| /** | |
| * Implements {@code Preferences} {@code sync()} method. | |
| * Flushes Windows registry changes to disk. Equivalent to flush(). | |
| * @see #flush() | |
| */ | |
| public void sync() throws BackingStoreException{ | |
| if (isRemoved()) | |
| throw new IllegalStateException("Node has been removed"); | |
| flush(); | |
| } | |
| /** | |
| * Implements {@code AbstractPreferences} {@code childSpi()} method. | |
| * Constructs a child node with a | |
| * given name and creates its underlying Windows registry node, | |
| * if it does not exist. | |
| * Logs a warning message, if Windows Registry is unavailable. | |
| */ | |
| protected AbstractPreferences childSpi(String name) { | |
| return new WindowsPreferences(this, name); | |
| } | |
| /** | |
| * Implements {@code AbstractPreferences} {@code removeNodeSpi()} method. | |
| * Deletes underlying Windows registry node. | |
| * Throws a BackingStoreException and logs a warning, if Windows registry | |
| * is not available. | |
| */ | |
| public void removeNodeSpi() throws BackingStoreException { | |
| long parentNativeHandle = | |
| ((WindowsPreferences)parent()).openKey(DELETE); | |
| if (parentNativeHandle == NULL_NATIVE_HANDLE) { | |
| throw new BackingStoreException( | |
| "Could not open parent windows registry node of " + | |
| byteArrayToString(windowsAbsolutePath()) + | |
| " at root 0x" + | |
| Long.toHexString(rootNativeHandle()) + "."); | |
| } | |
| int result = | |
| WindowsRegDeleteKey(parentNativeHandle, toWindowsName(name())); | |
| if (result != ERROR_SUCCESS) { | |
| String info = "Could not delete windows registry node " + | |
| byteArrayToString(windowsAbsolutePath()) + | |
| " at root 0x" + Long.toHexString(rootNativeHandle()) + | |
| ". Windows RegDeleteKeyEx(...) returned error code " + | |
| result + "."; | |
| logger().warning(info); | |
| throw new BackingStoreException(info); | |
| } | |
| closeKey(parentNativeHandle); | |
| } | |
| /** | |
| * Converts value's or node's name from its byte array representation to | |
| * java string. Two encodings, simple and altBase64 are used. See | |
| * {@link #toWindowsName(String) toWindowsName()} for a detailed | |
| * description of encoding conventions. | |
| * @param windowsNameArray Null-terminated byte array. | |
| */ | |
| private static String toJavaName(byte[] windowsNameArray) { | |
| String windowsName = byteArrayToString(windowsNameArray); | |
| // check if Alt64 | |
| if (windowsName.startsWith("/!")) { | |
| return toJavaAlt64Name(windowsName); | |
| } | |
| StringBuilder javaName = new StringBuilder(); | |
| char ch; | |
| // Decode from simple encoding | |
| for (int i = 0; i < windowsName.length(); i++) { | |
| if ((ch = windowsName.charAt(i)) == '/') { | |
| char next = ' '; | |
| if ((windowsName.length() > i + 1) && | |
| ((next = windowsName.charAt(i+1)) >= 'A') && | |
| (next <= 'Z')) { | |
| ch = next; | |
| i++; | |
| } else if ((windowsName.length() > i + 1) && | |
| (next == '/')) { | |
| ch = '\\'; | |
| i++; | |
| } | |
| } else if (ch == '\\') { | |
| ch = '/'; | |
| } | |
| javaName.append(ch); | |
| } | |
| return javaName.toString(); | |
| } | |
| /** | |
| * Converts value's or node's name from its Windows representation to java | |
| * string, using altBase64 encoding. See | |
| * {@link #toWindowsName(String) toWindowsName()} for a detailed | |
| * description of encoding conventions. | |
| */ | |
| private static String toJavaAlt64Name(String windowsName) { | |
| byte[] byteBuffer = | |
| Base64.altBase64ToByteArray(windowsName.substring(2)); | |
| StringBuilder result = new StringBuilder(); | |
| for (int i = 0; i < byteBuffer.length; i++) { | |
| int firstbyte = (byteBuffer[i++] & 0xff); | |
| int secondbyte = (byteBuffer[i] & 0xff); | |
| result.append((char)((firstbyte << 8) + secondbyte)); | |
| } | |
| return result.toString(); | |
| } | |
| /** | |
| * Converts value's or node's name to its Windows representation | |
| * as a byte-encoded string. | |
| * Two encodings, simple and altBase64 are used. | |
| * <p> | |
| * <i>Simple</i> encoding is used, if java string does not contain | |
| * any characters less, than 0x0020, or greater, than 0x007f. | |
| * Simple encoding adds "/" character to capital letters, i.e. | |
| * "A" is encoded as "/A". Character '\' is encoded as '//', | |
| * '/' is encoded as '\'. | |
| * The constructed string is converted to byte array by truncating the | |
| * highest byte and adding the terminating {@code null} character. | |
| * <p> | |
| * <i>altBase64</i> encoding is used, if java string does contain at least | |
| * one character less, than 0x0020, or greater, than 0x007f. | |
| * This encoding is marked by setting first two bytes of the | |
| * Windows string to '/!'. The java name is then encoded using | |
| * byteArrayToAltBase64() method from | |
| * Base64 class. | |
| */ | |
| private static byte[] toWindowsName(String javaName) { | |
| StringBuilder windowsName = new StringBuilder(); | |
| for (int i = 0; i < javaName.length(); i++) { | |
| char ch = javaName.charAt(i); | |
| if ((ch < 0x0020) || (ch > 0x007f)) { | |
| // If a non-trivial character encountered, use altBase64 | |
| return toWindowsAlt64Name(javaName); | |
| } | |
| if (ch == '\\') { | |
| windowsName.append("//"); | |
| } else if (ch == '/') { | |
| windowsName.append('\\'); | |
| } else if ((ch >= 'A') && (ch <='Z')) { | |
| windowsName.append('/').append(ch); | |
| } else { | |
| windowsName.append(ch); | |
| } | |
| } | |
| return stringToByteArray(windowsName.toString()); | |
| } | |
| /** | |
| * Converts value's or node's name to its Windows representation | |
| * as a byte-encoded string, using altBase64 encoding. See | |
| * {@link #toWindowsName(String) toWindowsName()} for a detailed | |
| * description of encoding conventions. | |
| */ | |
| private static byte[] toWindowsAlt64Name(String javaName) { | |
| byte[] javaNameArray = new byte[2*javaName.length()]; | |
| // Convert to byte pairs | |
| int counter = 0; | |
| for (int i = 0; i < javaName.length();i++) { | |
| int ch = javaName.charAt(i); | |
| javaNameArray[counter++] = (byte)(ch >>> 8); | |
| javaNameArray[counter++] = (byte)ch; | |
| } | |
| return stringToByteArray("/!" + | |
| Base64.byteArrayToAltBase64(javaNameArray)); | |
| } | |
| /** | |
| * Converts value string from its Windows representation | |
| * to java string. See | |
| * {@link #toWindowsValueString(String) toWindowsValueString()} for the | |
| * description of the encoding algorithm. | |
| */ | |
| private static String toJavaValueString(byte[] windowsNameArray) { | |
| String windowsName = byteArrayToString(windowsNameArray); | |
| StringBuilder javaName = new StringBuilder(); | |
| char ch; | |
| for (int i = 0; i < windowsName.length(); i++){ | |
| if ((ch = windowsName.charAt(i)) == '/') { | |
| char next = ' '; | |
| if (windowsName.length() > i + 1 && | |
| (next = windowsName.charAt(i + 1)) == 'u') { | |
| if (windowsName.length() < i + 6) { | |
| break; | |
| } else { | |
| ch = (char)Integer.parseInt( | |
| windowsName.substring(i + 2, i + 6), 16); | |
| i += 5; | |
| } | |
| } else | |
| if ((windowsName.length() > i + 1) && | |
| ((windowsName.charAt(i+1)) >= 'A') && | |
| (next <= 'Z')) { | |
| ch = next; | |
| i++; | |
| } else if ((windowsName.length() > i + 1) && | |
| (next == '/')) { | |
| ch = '\\'; | |
| i++; | |
| } | |
| } else if (ch == '\\') { | |
| ch = '/'; | |
| } | |
| javaName.append(ch); | |
| } | |
| return javaName.toString(); | |
| } | |
| /** | |
| * Converts value string to it Windows representation. | |
| * as a byte-encoded string. | |
| * Encoding algorithm adds "/" character to capital letters, i.e. | |
| * "A" is encoded as "/A". Character '\' is encoded as '//', | |
| * '/' is encoded as '\'. | |
| * Then convert java string to a byte array of ASCII characters. | |
| */ | |
| private static byte[] toWindowsValueString(String javaName) { | |
| StringBuilder windowsName = new StringBuilder(); | |
| for (int i = 0; i < javaName.length(); i++) { | |
| char ch = javaName.charAt(i); | |
| if ((ch < 0x0020) || (ch > 0x007f)){ | |
| // write \udddd | |
| windowsName.append("/u"); | |
| String hex = Long.toHexString(javaName.charAt(i)); | |
| StringBuilder hex4 = new StringBuilder(hex); | |
| hex4.reverse(); | |
| int len = 4 - hex4.length(); | |
| for (int j = 0; j < len; j++){ | |
| hex4.append('0'); | |
| } | |
| for (int j = 0; j < 4; j++){ | |
| windowsName.append(hex4.charAt(3 - j)); | |
| } | |
| } else if (ch == '\\') { | |
| windowsName.append("//"); | |
| } else if (ch == '/') { | |
| windowsName.append('\\'); | |
| } else if ((ch >= 'A') && (ch <='Z')) { | |
| windowsName.append('/').append(ch); | |
| } else { | |
| windowsName.append(ch); | |
| } | |
| } | |
| return stringToByteArray(windowsName.toString()); | |
| } | |
| /** | |
| * Returns native handle for the top Windows node for this node. | |
| */ | |
| private long rootNativeHandle() { | |
| return (isUserNode() | |
| ? USER_ROOT_NATIVE_HANDLE | |
| : SYSTEM_ROOT_NATIVE_HANDLE); | |
| } | |
| /** | |
| * Returns this java string as a null-terminated byte array | |
| */ | |
| private static byte[] stringToByteArray(String str) { | |
| byte[] result = new byte[str.length()+1]; | |
| for (int i = 0; i < str.length(); i++) { | |
| result[i] = (byte) str.charAt(i); | |
| } | |
| result[str.length()] = 0; | |
| return result; | |
| } | |
| /** | |
| * Converts a null-terminated byte array to java string | |
| */ | |
| private static String byteArrayToString(byte[] array) { | |
| StringBuilder result = new StringBuilder(); | |
| for (int i = 0; i < array.length - 1; i++) { | |
| result.append((char)array[i]); | |
| } | |
| return result.toString(); | |
| } | |
| /** | |
| * Empty, never used implementation of AbstractPreferences.flushSpi(). | |
| */ | |
| protected void flushSpi() throws BackingStoreException { | |
| // assert false; | |
| } | |
| /** | |
| * Empty, never used implementation of AbstractPreferences.flushSpi(). | |
| */ | |
| protected void syncSpi() throws BackingStoreException { | |
| // assert false; | |
| } | |
| private static synchronized PlatformLogger logger() { | |
| if (logger == null) { | |
| logger = PlatformLogger.getLogger("java.util.prefs"); | |
| } | |
| return logger; | |
| } | |
| } |