--- old/src/macosx/native/java/util/MacOSXPreferencesFile.m 2014-02-09 17:05:58.861428653 +0000 +++ /dev/null 2013-11-25 15:01:06.400581647 +0000 @@ -1,999 +0,0 @@ -/* - * Copyright (c) 2011, 2012, 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. - */ - -/* - Hierarchical storage layout: - - - / - - foo - /foo's value - foo/ - - bar - /foo/bar's value - - - - - Java pref nodes are stored in several different files. Pref nodes - with at least three components in the node name (e.g. /com/MyCompany/MyApp/) - are stored in a CF prefs file with the first three components as the name. - This way, all preferences for MyApp end up in com.MyCompany.MyApp.plist . - Pref nodes with shorter names are stored in com.apple.java.util.prefs.plist - - The filesystem is assumed to be case-insensitive (like HFS+). - Java pref node names are case-sensitive. If two pref node names differ - only in case, they may end up in the same pref file. This is ok - because the CF keys identifying the node span the entire absolute path - to the node and are case-sensitive. - - Java node names may contain '.' . When mapping to the CF file name, - these dots are left as-is, even though '/' is mapped to '.' . - This is ok because the CF key contains the correct node name. -*/ - - - -#include - -#include "jni_util.h" -#include "jlong.h" -#include "jvm.h" - - -// Throw an OutOfMemoryError with the given message. -static void throwOutOfMemoryError(JNIEnv *env, const char *msg) -{ - static jclass exceptionClass = NULL; - jclass c; - - if (exceptionClass) { - c = exceptionClass; - } else { - c = (*env)->FindClass(env, "java/lang/OutOfMemoryError"); - if ((*env)->ExceptionOccurred(env)) return; - exceptionClass = (*env)->NewGlobalRef(env, c); - } - - (*env)->ThrowNew(env, c, msg); -} - - -// throwIfNull macro -// If var is NULL, throw an OutOfMemoryError and goto badvar. -// var must be a variable. env must be the current JNIEnv. -// fixme throw BackingStoreExceptions sometimes? -#define throwIfNull(var, msg) \ - do { \ - if (var == NULL) { \ - throwOutOfMemoryError(env, msg); \ - goto bad##var; \ - } \ - } while (0) - - -// Converts CFNumber, CFBoolean, CFString to CFString -// returns NULL if value is of some other type -// throws and returns NULL on memory error -// result must be released (even if value was already a CFStringRef) -// value must not be null -static CFStringRef copyToCFString(JNIEnv *env, CFTypeRef value) -{ - CFStringRef result; - CFTypeID type; - - type = CFGetTypeID(value); - - if (type == CFStringGetTypeID()) { - result = (CFStringRef)CFRetain(value); - } - else if (type == CFBooleanGetTypeID()) { - // Java Preferences API expects "true" and "false" for boolean values. - result = CFStringCreateCopy(NULL, (value == kCFBooleanTrue) ? CFSTR("true") : CFSTR("false")); - throwIfNull(result, "copyToCFString failed"); - } - else if (type == CFNumberGetTypeID()) { - CFNumberRef number = (CFNumberRef) value; - if (CFNumberIsFloatType(number)) { - double d; - CFNumberGetValue(number, kCFNumberDoubleType, &d); - result = CFStringCreateWithFormat(NULL, NULL, CFSTR("%g"), d); - throwIfNull(result, "copyToCFString failed"); - } - else { - long l; - CFNumberGetValue(number, kCFNumberLongType, &l); - result = CFStringCreateWithFormat(NULL, NULL, CFSTR("%ld"), l); - throwIfNull(result, "copyToCFString failed"); - } - } - else { - // unknown type - return NULL - result = NULL; - } - - badresult: - return result; -} - - -// Create a Java string from the given CF string. -// returns NULL if cfString is NULL -// throws and returns NULL on memory error -static jstring toJavaString(JNIEnv *env, CFStringRef cfString) -{ - if (cfString == NULL) { - return NULL; - } else { - jstring javaString = NULL; - - CFIndex length = CFStringGetLength(cfString); - const UniChar *constchars = CFStringGetCharactersPtr(cfString); - if (constchars) { - javaString = (*env)->NewString(env, constchars, length); - } else { - UniChar *chars = malloc(length * sizeof(UniChar)); - throwIfNull(chars, "toJavaString failed"); - CFStringGetCharacters(cfString, CFRangeMake(0, length), chars); - javaString = (*env)->NewString(env, chars, length); - free(chars); - } - badchars: - return javaString; - } -} - - - -// Create a CF string from the given Java string. -// returns NULL if javaString is NULL -// throws and returns NULL on memory error -static CFStringRef toCF(JNIEnv *env, jstring javaString) -{ - if (javaString == NULL) { - return NULL; - } else { - CFStringRef result = NULL; - jsize length = (*env)->GetStringLength(env, javaString); - const jchar *chars = (*env)->GetStringChars(env, javaString, NULL); - throwIfNull(chars, "toCF failed"); - result = - CFStringCreateWithCharacters(NULL, (const UniChar *)chars, length); - (*env)->ReleaseStringChars(env, javaString, chars); - throwIfNull(result, "toCF failed"); - badchars: - badresult: - return result; - } -} - - -// Create an empty Java string array of the given size. -// Throws and returns NULL on error. -static jarray createJavaStringArray(JNIEnv *env, CFIndex count) -{ - static jclass stringClass = NULL; - jclass c; - - if (stringClass) { - c = stringClass; - } else { - c = (*env)->FindClass(env, "java/lang/String"); - if ((*env)->ExceptionOccurred(env)) return NULL; - stringClass = (*env)->NewGlobalRef(env, c); - } - - return (*env)->NewObjectArray(env, count, c, NULL); // AWT_THREADING Safe (known object) -} - - -// Java accessors for CF constants. -JNIEXPORT jlong JNICALL -Java_java_util_prefs_MacOSXPreferencesFile_currentUser(JNIEnv *env, - jobject klass) -{ - return ptr_to_jlong(kCFPreferencesCurrentUser); -} - -JNIEXPORT jlong JNICALL -Java_java_util_prefs_MacOSXPreferencesFile_anyUser(JNIEnv *env, jobject klass) -{ - return ptr_to_jlong(kCFPreferencesAnyUser); -} - -JNIEXPORT jlong JNICALL -Java_java_util_prefs_MacOSXPreferencesFile_currentHost(JNIEnv *env, - jobject klass) -{ - return ptr_to_jlong(kCFPreferencesCurrentHost); -} - -JNIEXPORT jlong JNICALL -Java_java_util_prefs_MacOSXPreferencesFile_anyHost(JNIEnv *env, jobject klass) -{ - return ptr_to_jlong(kCFPreferencesAnyHost); -} - - -// Create an empty node. -// Does not store the node in any prefs file. -// returns NULL on memory error -static CFMutableDictionaryRef createEmptyNode(void) -{ - return CFDictionaryCreateMutable(NULL, 0, - &kCFTypeDictionaryKeyCallBacks, - &kCFTypeDictionaryValueCallBacks); -} - - -// Create a string that consists of path minus its last component. -// path must end with '/' -// The result will end in '/' (unless path itself is '/') -static CFStringRef copyParentOf(CFStringRef path) -{ - CFRange searchRange; - CFRange slashRange; - CFRange parentRange; - Boolean found; - - searchRange = CFRangeMake(0, CFStringGetLength(path) - 1); - found = CFStringFindWithOptions(path, CFSTR("/"), searchRange, - kCFCompareBackwards, &slashRange); - if (!found) return CFSTR(""); - parentRange = CFRangeMake(0, slashRange.location + 1); // include '/' - return CFStringCreateWithSubstring(NULL, path, parentRange); -} - - -// Create a string that consists of path's last component. -// path must end with '/' -// The result will end in '/'. -// The result will not start with '/' (unless path itself is '/') -static CFStringRef copyChildOf(CFStringRef path) -{ - CFRange searchRange; - CFRange slashRange; - CFRange childRange; - Boolean found; - CFIndex length = CFStringGetLength(path); - - searchRange = CFRangeMake(0, length - 1); - found = CFStringFindWithOptions(path, CFSTR("/"), searchRange, - kCFCompareBackwards, &slashRange); - if (!found) return CFSTR(""); - childRange = CFRangeMake(slashRange.location + 1, - length - slashRange.location - 1); // skip '/' - return CFStringCreateWithSubstring(NULL, path, childRange); -} - - -// Return the first three components of path, with leading and trailing '/'. -// If path does not have three components, return NULL. -// path must begin and end in '/' -static CFStringRef copyFirstThreeComponentsOf(CFStringRef path) -{ - CFRange searchRange; - CFRange slashRange; - CFRange prefixRange; - CFStringRef prefix; - Boolean found; - CFIndex length = CFStringGetLength(path); - - searchRange = CFRangeMake(1, length - 1); // skip leading '/' - found = CFStringFindWithOptions(path, CFSTR("/"), searchRange, 0, - &slashRange); - if (!found) return NULL; // no second slash! - - searchRange = CFRangeMake(slashRange.location + 1, - length - slashRange.location - 1); - found = CFStringFindWithOptions(path, CFSTR("/"), searchRange, 0, - &slashRange); - if (!found) return NULL; // no third slash! - - searchRange = CFRangeMake(slashRange.location + 1, - length - slashRange.location - 1); - found = CFStringFindWithOptions(path, CFSTR("/"), searchRange, 0, - &slashRange); - if (!found) return NULL; // no fourth slash! - - prefixRange = CFRangeMake(0, slashRange.location + 1); // keep last '/' - prefix = CFStringCreateWithSubstring(NULL, path, prefixRange); - - return prefix; -} - - -// Copy the CFPreferences key and value at the base of path's tree. -// path must end in '/' -// topKey or topValue may be NULL -// Returns NULL on error or if there is no tree for path in this file. -static void copyTreeForPath(CFStringRef path, CFStringRef name, - CFStringRef user, CFStringRef host, - CFStringRef *topKey, CFDictionaryRef *topValue) -{ - CFStringRef key; - CFPropertyListRef value; - - if (topKey) *topKey = NULL; - if (topValue) *topValue = NULL; - - if (CFEqual(name, CFSTR("com.apple.java.util.prefs"))) { - // Top-level file. Only key "/" is an acceptable root. - key = (CFStringRef) CFRetain(CFSTR("/")); - } else { - // Second-level file. Key must be the first three components of path. - key = copyFirstThreeComponentsOf(path); - if (!key) return; - } - - value = CFPreferencesCopyValue(key, name, user, host); - if (value) { - if (CFGetTypeID(value) == CFDictionaryGetTypeID()) { - // (key, value) is acceptable - if (topKey) *topKey = (CFStringRef)CFRetain(key); - if (topValue) *topValue = (CFDictionaryRef)CFRetain(value); - } - CFRelease(value); - } - CFRelease(key); -} - - -// Find the node for path in the given tree. -// Returns NULL on error or if path doesn't have a node in this tree. -// path must end in '/' -static CFDictionaryRef copyNodeInTree(CFStringRef path, CFStringRef topKey, - CFDictionaryRef topValue) -{ - CFMutableStringRef p; - CFDictionaryRef result = NULL; - - p = CFStringCreateMutableCopy(NULL, 0, path); - if (!p) return NULL; - CFStringDelete(p, CFRangeMake(0, CFStringGetLength(topKey))); - result = topValue; - - while (CFStringGetLength(p) > 0) { - CFDictionaryRef child; - CFStringRef part = NULL; - CFRange slashRange = CFStringFind(p, CFSTR("/"), 0); - // guaranteed to succeed because path must end in '/' - CFRange partRange = CFRangeMake(0, slashRange.location + 1); - part = CFStringCreateWithSubstring(NULL, p, partRange); - if (!part) { result = NULL; break; } - CFStringDelete(p, partRange); - - child = CFDictionaryGetValue(result, part); - CFRelease(part); - if (child && CFGetTypeID(child) == CFDictionaryGetTypeID()) { - // continue search - result = child; - } else { - // didn't find target node - result = NULL; - break; - } - } - - CFRelease(p); - if (result) return (CFDictionaryRef)CFRetain(result); - else return NULL; -} - - -// Return a retained copy of the node at path from the given file. -// path must end in '/' -// returns NULL if node doesn't exist. -// returns NULL if the value for key "path" isn't a valid node. -static CFDictionaryRef copyNodeIfPresent(CFStringRef path, CFStringRef name, - CFStringRef user, CFStringRef host) -{ - CFStringRef topKey; - CFDictionaryRef topValue; - CFDictionaryRef result; - - copyTreeForPath(path, name, user, host, &topKey, &topValue); - if (!topKey) return NULL; - - result = copyNodeInTree(path, topKey, topValue); - - CFRelease(topKey); - if (topValue) CFRelease(topValue); - return result; -} - - -// Create a new tree that would store path in the given file. -// Only the root of the tree is created, not all of the links leading to path. -// returns NULL on error -static void createTreeForPath(CFStringRef path, CFStringRef name, - CFStringRef user, CFStringRef host, - CFStringRef *outTopKey, - CFMutableDictionaryRef *outTopValue) -{ - *outTopKey = NULL; - *outTopValue = NULL; - - // if name is "com.apple.java.util.prefs" then create tree "/" - // else create tree "/foo/bar/baz/" - // "com.apple.java.util.prefs.plist" is also in MacOSXPreferences.java - if (CFEqual(name, CFSTR("com.apple.java.util.prefs"))) { - *outTopKey = CFSTR("/"); - *outTopValue = createEmptyNode(); - } else { - CFStringRef prefix = copyFirstThreeComponentsOf(path); - if (prefix) { - *outTopKey = prefix; - *outTopValue = createEmptyNode(); - } - } -} - - -// Return a mutable copy of the tree containing path and the dict for -// path itself. *outTopKey and *outTopValue can be used to write the -// modified tree back to the prefs file. -// *outTopKey and *outTopValue must be released iff the actual return -// value is not NULL. -static CFMutableDictionaryRef -copyMutableNode(CFStringRef path, CFStringRef name, - CFStringRef user, CFStringRef host, - CFStringRef *outTopKey, - CFMutableDictionaryRef *outTopValue) -{ - CFStringRef topKey = NULL; - CFDictionaryRef oldTopValue = NULL; - CFMutableDictionaryRef topValue; - CFMutableDictionaryRef result = NULL; - CFMutableStringRef p; - - if (outTopKey) *outTopKey = NULL; - if (outTopValue) *outTopValue = NULL; - - copyTreeForPath(path, name, user, host, &topKey, &oldTopValue); - if (!topKey) { - createTreeForPath(path, name, user, host, &topKey, &topValue); - } else { - topValue = (CFMutableDictionaryRef) - CFPropertyListCreateDeepCopy(NULL, (CFPropertyListRef)oldTopValue, - kCFPropertyListMutableContainers); - } - if (!topValue) goto badtopValue; - - p = CFStringCreateMutableCopy(NULL, 0, path); - if (!p) goto badp; - CFStringDelete(p, CFRangeMake(0, CFStringGetLength(topKey))); - result = topValue; - - while (CFStringGetLength(p) > 0) { - CFMutableDictionaryRef child; - CFStringRef part = NULL; - CFRange slashRange = CFStringFind(p, CFSTR("/"), 0); - // guaranteed to succeed because path must end in '/' - CFRange partRange = CFRangeMake(0, slashRange.location + 1); - part = CFStringCreateWithSubstring(NULL, p, partRange); - if (!part) { result = NULL; break; } - CFStringDelete(p, partRange); - - child = (CFMutableDictionaryRef)CFDictionaryGetValue(result, part); - if (child && CFGetTypeID(child) == CFDictionaryGetTypeID()) { - // continue search - result = child; - } else { - // didn't find target node - add it and continue - child = createEmptyNode(); - if (!child) { CFRelease(part); result = NULL; break; } - CFDictionaryAddValue(result, part, child); - result = child; - } - CFRelease(part); - } - - if (result) { - *outTopKey = (CFStringRef)CFRetain(topKey); - *outTopValue = (CFMutableDictionaryRef)CFRetain(topValue); - CFRetain(result); - } - - CFRelease(p); - badp: - CFRelease(topValue); - badtopValue: - if (topKey) CFRelease(topKey); - if (oldTopValue) CFRelease(oldTopValue); - return result; -} - - -JNIEXPORT jboolean JNICALL -Java_java_util_prefs_MacOSXPreferencesFile_addNode -(JNIEnv *env, jobject klass, jobject jpath, - jobject jname, jlong juser, jlong jhost) -{ - CFStringRef path = toCF(env, jpath); - CFStringRef name = toCF(env, jname); - CFStringRef user = (CFStringRef)jlong_to_ptr(juser); - CFStringRef host = (CFStringRef)jlong_to_ptr(jhost); - CFDictionaryRef node = NULL; - jboolean neededNewNode = false; - - if (!path || !name) goto badparams; - - node = copyNodeIfPresent(path, name, user, host); - - if (node) { - neededNewNode = false; - CFRelease(node); - } else { - CFStringRef topKey = NULL; - CFMutableDictionaryRef topValue = NULL; - - neededNewNode = true; - - // copyMutableNode creates the node if necessary - node = copyMutableNode(path, name, user, host, &topKey, &topValue); - throwIfNull(node, "copyMutableNode failed"); - - CFPreferencesSetValue(topKey, topValue, name, user, host); - - CFRelease(node); - if (topKey) CFRelease(topKey); - if (topValue) CFRelease(topValue); - } - - badnode: - badparams: - if (path) CFRelease(path); - if (name) CFRelease(name); - - return neededNewNode; -} - - -JNIEXPORT void JNICALL -Java_java_util_prefs_MacOSXPreferencesFile_removeNode -(JNIEnv *env, jobject klass, jobject jpath, - jobject jname, jlong juser, jlong jhost) -{ - CFStringRef path = toCF(env, jpath); - CFStringRef name = toCF(env, jname); - CFStringRef user = (CFStringRef)jlong_to_ptr(juser); - CFStringRef host = (CFStringRef)jlong_to_ptr(jhost); - CFStringRef parentName; - CFStringRef childName; - CFDictionaryRef constParent; - - if (!path || !name) goto badparams; - - parentName = copyParentOf(path); - throwIfNull(parentName, "copyParentOf failed"); - childName = copyChildOf(path); - throwIfNull(childName, "copyChildOf failed"); - - // root node is not allowed to be removed, so parentName is never empty - - constParent = copyNodeIfPresent(parentName, name, user, host); - if (constParent && CFDictionaryContainsKey(constParent, childName)) { - CFStringRef topKey; - CFMutableDictionaryRef topValue; - CFMutableDictionaryRef parent; - - parent = copyMutableNode(parentName, name, user, host, - &topKey, &topValue); - throwIfNull(parent, "copyMutableNode failed"); - - CFDictionaryRemoveValue(parent, childName); - CFPreferencesSetValue(topKey, topValue, name, user, host); - - CFRelease(parent); - if (topKey) CFRelease(topKey); - if (topValue) CFRelease(topValue); - } else { - // might be trying to remove the root itself in a non-root file - CFStringRef topKey; - CFDictionaryRef topValue; - copyTreeForPath(path, name, user, host, &topKey, &topValue); - if (topKey) { - if (CFEqual(topKey, path)) { - CFPreferencesSetValue(topKey, NULL, name, user, host); - } - - if (topKey) CFRelease(topKey); - if (topValue) CFRelease(topValue); - } - } - - - badparent: - if (constParent) CFRelease(constParent); - CFRelease(childName); - badchildName: - CFRelease(parentName); - badparentName: - badparams: - if (path) CFRelease(path); - if (name) CFRelease(name); -} - - -// child must end with '/' -JNIEXPORT Boolean JNICALL -Java_java_util_prefs_MacOSXPreferencesFile_addChildToNode -(JNIEnv *env, jobject klass, jobject jpath, jobject jchild, - jobject jname, jlong juser, jlong jhost) -{ - // like addNode, but can put a three-level-deep dict into the root file - CFStringRef path = toCF(env, jpath); - CFStringRef child = toCF(env, jchild); - CFStringRef name = toCF(env, jname); - CFStringRef user = (CFStringRef)jlong_to_ptr(juser); - CFStringRef host = (CFStringRef)jlong_to_ptr(jhost); - CFMutableDictionaryRef parent; - CFDictionaryRef node; - CFStringRef topKey; - CFMutableDictionaryRef topValue; - Boolean beforeAdd = false; - - if (!path || !child || !name) goto badparams; - - node = createEmptyNode(); - throwIfNull(node, "createEmptyNode failed"); - - // copyMutableNode creates the node if necessary - parent = copyMutableNode(path, name, user, host, &topKey, &topValue); - throwIfNull(parent, "copyMutableNode failed"); - beforeAdd = CFDictionaryContainsKey(parent, child); - CFDictionaryAddValue(parent, child, node); - if (!beforeAdd) - beforeAdd = CFDictionaryContainsKey(parent, child); - else - beforeAdd = false; - CFPreferencesSetValue(topKey, topValue, name, user, host); - - CFRelease(parent); - if (topKey) CFRelease(topKey); - if (topValue) CFRelease(topValue); - badparent: - CFRelease(node); - badnode: - badparams: - if (path) CFRelease(path); - if (child) CFRelease(child); - if (name) CFRelease(name); - return beforeAdd; -} - - -JNIEXPORT void JNICALL -Java_java_util_prefs_MacOSXPreferencesFile_removeChildFromNode -(JNIEnv *env, jobject klass, jobject jpath, jobject jchild, - jobject jname, jlong juser, jlong jhost) -{ - CFStringRef path = toCF(env, jpath); - CFStringRef child = toCF(env, jchild); - CFStringRef name = toCF(env, jname); - CFStringRef user = (CFStringRef)jlong_to_ptr(juser); - CFStringRef host = (CFStringRef)jlong_to_ptr(jhost); - CFDictionaryRef constParent; - - if (!path || !child || !name) goto badparams; - - constParent = copyNodeIfPresent(path, name, user, host); - if (constParent && CFDictionaryContainsKey(constParent, child)) { - CFStringRef topKey; - CFMutableDictionaryRef topValue; - CFMutableDictionaryRef parent; - - parent = copyMutableNode(path, name, user, host, &topKey, &topValue); - throwIfNull(parent, "copyMutableNode failed"); - - CFDictionaryRemoveValue(parent, child); - CFPreferencesSetValue(topKey, topValue, name, user, host); - - CFRelease(parent); - if (topKey) CFRelease(topKey); - if (topValue) CFRelease(topValue); - } - - badparent: - if (constParent) CFRelease(constParent); - badparams: - if (path) CFRelease(path); - if (child) CFRelease(child); - if (name) CFRelease(name); -} - - - -JNIEXPORT void JNICALL -Java_java_util_prefs_MacOSXPreferencesFile_addKeyToNode -(JNIEnv *env, jobject klass, jobject jpath, jobject jkey, jobject jvalue, - jobject jname, jlong juser, jlong jhost) -{ - CFStringRef path = toCF(env, jpath); - CFStringRef key = toCF(env, jkey); - CFStringRef value = toCF(env, jvalue); - CFStringRef name = toCF(env, jname); - CFStringRef user = (CFStringRef)jlong_to_ptr(juser); - CFStringRef host = (CFStringRef)jlong_to_ptr(jhost); - CFMutableDictionaryRef node = NULL; - CFStringRef topKey; - CFMutableDictionaryRef topValue; - - if (!path || !key || !value || !name) goto badparams; - - // fixme optimization: check whether old value and new value are identical - node = copyMutableNode(path, name, user, host, &topKey, &topValue); - throwIfNull(node, "copyMutableNode failed"); - - CFDictionarySetValue(node, key, value); - CFPreferencesSetValue(topKey, topValue, name, user, host); - - CFRelease(node); - if (topKey) CFRelease(topKey); - if (topValue) CFRelease(topValue); - - badnode: - badparams: - if (path) CFRelease(path); - if (key) CFRelease(key); - if (value) CFRelease(value); - if (name) CFRelease(name); -} - - -JNIEXPORT void JNICALL -Java_java_util_prefs_MacOSXPreferencesFile_removeKeyFromNode -(JNIEnv *env, jobject klass, jobject jpath, jobject jkey, - jobject jname, jlong juser, jlong jhost) -{ - CFStringRef path = toCF(env, jpath); - CFStringRef key = toCF(env, jkey); - CFStringRef name = toCF(env, jname); - CFStringRef user = (CFStringRef)jlong_to_ptr(juser); - CFStringRef host = (CFStringRef)jlong_to_ptr(jhost); - CFDictionaryRef constNode; - - if (!path || !key || !name) goto badparams; - - constNode = copyNodeIfPresent(path, name, user, host); - if (constNode && CFDictionaryContainsKey(constNode, key)) { - CFStringRef topKey; - CFMutableDictionaryRef topValue; - CFMutableDictionaryRef node; - - node = copyMutableNode(path, name, user, host, &topKey, &topValue); - throwIfNull(node, "copyMutableNode failed"); - - CFDictionaryRemoveValue(node, key); - CFPreferencesSetValue(topKey, topValue, name, user, host); - - CFRelease(node); - if (topKey) CFRelease(topKey); - if (topValue) CFRelease(topValue); - } - - badnode: - if (constNode) CFRelease(constNode); - badparams: - if (path) CFRelease(path); - if (key) CFRelease(key); - if (name) CFRelease(name); -} - - -// path must end in '/' -JNIEXPORT jstring JNICALL -Java_java_util_prefs_MacOSXPreferencesFile_getKeyFromNode -(JNIEnv *env, jobject klass, jobject jpath, jobject jkey, - jobject jname, jlong juser, jlong jhost) -{ - CFStringRef path = toCF(env, jpath); - CFStringRef key = toCF(env, jkey); - CFStringRef name = toCF(env, jname); - CFStringRef user = (CFStringRef)jlong_to_ptr(juser); - CFStringRef host = (CFStringRef)jlong_to_ptr(jhost); - CFPropertyListRef value; - CFDictionaryRef node; - jstring result = NULL; - - if (!path || !key || !name) goto badparams; - - node = copyNodeIfPresent(path, name, user, host); - if (node) { - value = (CFPropertyListRef)CFDictionaryGetValue(node, key); - if (!value) { - // key doesn't exist, or other error - no Java errors available - result = NULL; - } else { - CFStringRef cfString = copyToCFString(env, value); - if ((*env)->ExceptionOccurred(env)) { - // memory error in copyToCFString - result = NULL; - } else if (cfString == NULL) { - // bogus value type in prefs file - no Java errors available - result = NULL; - } else { - // good cfString - result = toJavaString(env, cfString); - CFRelease(cfString); - } - } - CFRelease(node); - } - - badparams: - if (path) CFRelease(path); - if (key) CFRelease(key); - if (name) CFRelease(name); - - return result; -} - - -typedef struct { - jarray result; - JNIEnv *env; - CFIndex used; - Boolean allowSlash; -} BuildJavaArrayArgs; - -// CFDictionary applier function that builds an array of Java strings -// from a CFDictionary of CFPropertyListRefs. -// If args->allowSlash, only strings that end in '/' are added to the array, -// with the slash removed. Otherwise, only strings that do not end in '/' -// are added. -// args->result must already exist and be large enough to hold all -// strings from the dictionary. -// After complete application, args->result may not be full because -// some of the dictionary values weren't convertible to string. In -// this case, args->used will be the count of used elements. -static void BuildJavaArrayFn(const void *key, const void *value, void *context) -{ - BuildJavaArrayArgs *args = (BuildJavaArrayArgs *)context; - CFPropertyListRef propkey = (CFPropertyListRef)key; - CFStringRef cfString = NULL; - JNIEnv *env = args->env; - - if ((*env)->ExceptionOccurred(env)) return; // already failed - - cfString = copyToCFString(env, propkey); - if ((*env)->ExceptionOccurred(env)) { - // memory error in copyToCFString - } else if (!cfString) { - // bogus value type in prefs file - no Java errors available - } else if (args->allowSlash != CFStringHasSuffix(cfString, CFSTR("/"))) { - // wrong suffix - ignore - } else { - // good cfString - jstring javaString; - if (args->allowSlash) { - CFRange range = CFRangeMake(0, CFStringGetLength(cfString) - 1); - CFStringRef s = CFStringCreateWithSubstring(NULL, cfString, range); - CFRelease(cfString); - cfString = s; - } - if (CFStringGetLength(cfString) <= 0) goto bad; // ignore empty - javaString = toJavaString(env, cfString); - if ((*env)->ExceptionOccurred(env)) goto bad; - (*env)->SetObjectArrayElement(env, args->result,args->used,javaString); - if ((*env)->ExceptionOccurred(env)) goto bad; - args->used++; - } - - bad: - if (cfString) CFRelease(cfString); -} - - -static jarray getStringsForNode(JNIEnv *env, jobject klass, jobject jpath, - jobject jname, jlong juser, jlong jhost, - Boolean allowSlash) -{ - CFStringRef path = toCF(env, jpath); - CFStringRef name = toCF(env, jname); - CFStringRef user = (CFStringRef)jlong_to_ptr(juser); - CFStringRef host = (CFStringRef)jlong_to_ptr(jhost); - CFDictionaryRef node; - jarray result = NULL; - CFIndex count; - - if (!path || !name) goto badparams; - - node = copyNodeIfPresent(path, name, user, host); - if (!node) { - result = createJavaStringArray(env, 0); - } else { - count = CFDictionaryGetCount(node); - result = createJavaStringArray(env, count); - if (result) { - BuildJavaArrayArgs args; - args.result = result; - args.env = env; - args.used = 0; - args.allowSlash = allowSlash; - CFDictionaryApplyFunction(node, BuildJavaArrayFn, &args); - if (!(*env)->ExceptionOccurred(env)) { - // array construction succeeded - if (args.used < count) { - // finished array is smaller than expected. - // Make a new array of precisely the right size. - jarray newresult = createJavaStringArray(env, args.used); - if (newresult) { - JVM_ArrayCopy(env,0, result,0, newresult,0, args.used); - result = newresult; - } - } - } - } - - CFRelease(node); - } - - badparams: - if (path) CFRelease(path); - if (name) CFRelease(name); - - return result; -} - - -JNIEXPORT jarray JNICALL -Java_java_util_prefs_MacOSXPreferencesFile_getKeysForNode -(JNIEnv *env, jobject klass, jobject jpath, - jobject jname, jlong juser, jlong jhost) -{ - return getStringsForNode(env, klass, jpath, jname, juser, jhost, false); -} - -JNIEXPORT jarray JNICALL -Java_java_util_prefs_MacOSXPreferencesFile_getChildrenForNode -(JNIEnv *env, jobject klass, jobject jpath, - jobject jname, jlong juser, jlong jhost) -{ - return getStringsForNode(env, klass, jpath, jname, juser, jhost, true); -} - - -// Returns false on error instead of throwing. -JNIEXPORT jboolean JNICALL -Java_java_util_prefs_MacOSXPreferencesFile_synchronize -(JNIEnv *env, jobject klass, - jstring jname, jlong juser, jlong jhost) -{ - CFStringRef name = toCF(env, jname); - CFStringRef user = (CFStringRef)jlong_to_ptr(juser); - CFStringRef host = (CFStringRef)jlong_to_ptr(jhost); - jboolean result = 0; - - if (name) { - result = CFPreferencesSynchronize(name, user, host); - CFRelease(name); - } - - return result; -} --- /dev/null 2013-11-25 15:01:06.400581647 +0000 +++ new/src/macosx/native/java/util/prefs/MacOSXPreferencesFile.m 2014-02-09 17:05:58.521428661 +0000 @@ -0,0 +1,999 @@ +/* + * Copyright (c) 2011, 2012, 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. + */ + +/* + Hierarchical storage layout: + + + / + + foo + /foo's value + foo/ + + bar + /foo/bar's value + + + + + Java pref nodes are stored in several different files. Pref nodes + with at least three components in the node name (e.g. /com/MyCompany/MyApp/) + are stored in a CF prefs file with the first three components as the name. + This way, all preferences for MyApp end up in com.MyCompany.MyApp.plist . + Pref nodes with shorter names are stored in com.apple.java.util.prefs.plist + + The filesystem is assumed to be case-insensitive (like HFS+). + Java pref node names are case-sensitive. If two pref node names differ + only in case, they may end up in the same pref file. This is ok + because the CF keys identifying the node span the entire absolute path + to the node and are case-sensitive. + + Java node names may contain '.' . When mapping to the CF file name, + these dots are left as-is, even though '/' is mapped to '.' . + This is ok because the CF key contains the correct node name. +*/ + + + +#include + +#include "jni_util.h" +#include "jlong.h" +#include "jvm.h" + + +// Throw an OutOfMemoryError with the given message. +static void throwOutOfMemoryError(JNIEnv *env, const char *msg) +{ + static jclass exceptionClass = NULL; + jclass c; + + if (exceptionClass) { + c = exceptionClass; + } else { + c = (*env)->FindClass(env, "java/lang/OutOfMemoryError"); + if ((*env)->ExceptionOccurred(env)) return; + exceptionClass = (*env)->NewGlobalRef(env, c); + } + + (*env)->ThrowNew(env, c, msg); +} + + +// throwIfNull macro +// If var is NULL, throw an OutOfMemoryError and goto badvar. +// var must be a variable. env must be the current JNIEnv. +// fixme throw BackingStoreExceptions sometimes? +#define throwIfNull(var, msg) \ + do { \ + if (var == NULL) { \ + throwOutOfMemoryError(env, msg); \ + goto bad##var; \ + } \ + } while (0) + + +// Converts CFNumber, CFBoolean, CFString to CFString +// returns NULL if value is of some other type +// throws and returns NULL on memory error +// result must be released (even if value was already a CFStringRef) +// value must not be null +static CFStringRef copyToCFString(JNIEnv *env, CFTypeRef value) +{ + CFStringRef result; + CFTypeID type; + + type = CFGetTypeID(value); + + if (type == CFStringGetTypeID()) { + result = (CFStringRef)CFRetain(value); + } + else if (type == CFBooleanGetTypeID()) { + // Java Preferences API expects "true" and "false" for boolean values. + result = CFStringCreateCopy(NULL, (value == kCFBooleanTrue) ? CFSTR("true") : CFSTR("false")); + throwIfNull(result, "copyToCFString failed"); + } + else if (type == CFNumberGetTypeID()) { + CFNumberRef number = (CFNumberRef) value; + if (CFNumberIsFloatType(number)) { + double d; + CFNumberGetValue(number, kCFNumberDoubleType, &d); + result = CFStringCreateWithFormat(NULL, NULL, CFSTR("%g"), d); + throwIfNull(result, "copyToCFString failed"); + } + else { + long l; + CFNumberGetValue(number, kCFNumberLongType, &l); + result = CFStringCreateWithFormat(NULL, NULL, CFSTR("%ld"), l); + throwIfNull(result, "copyToCFString failed"); + } + } + else { + // unknown type - return NULL + result = NULL; + } + + badresult: + return result; +} + + +// Create a Java string from the given CF string. +// returns NULL if cfString is NULL +// throws and returns NULL on memory error +static jstring toJavaString(JNIEnv *env, CFStringRef cfString) +{ + if (cfString == NULL) { + return NULL; + } else { + jstring javaString = NULL; + + CFIndex length = CFStringGetLength(cfString); + const UniChar *constchars = CFStringGetCharactersPtr(cfString); + if (constchars) { + javaString = (*env)->NewString(env, constchars, length); + } else { + UniChar *chars = malloc(length * sizeof(UniChar)); + throwIfNull(chars, "toJavaString failed"); + CFStringGetCharacters(cfString, CFRangeMake(0, length), chars); + javaString = (*env)->NewString(env, chars, length); + free(chars); + } + badchars: + return javaString; + } +} + + + +// Create a CF string from the given Java string. +// returns NULL if javaString is NULL +// throws and returns NULL on memory error +static CFStringRef toCF(JNIEnv *env, jstring javaString) +{ + if (javaString == NULL) { + return NULL; + } else { + CFStringRef result = NULL; + jsize length = (*env)->GetStringLength(env, javaString); + const jchar *chars = (*env)->GetStringChars(env, javaString, NULL); + throwIfNull(chars, "toCF failed"); + result = + CFStringCreateWithCharacters(NULL, (const UniChar *)chars, length); + (*env)->ReleaseStringChars(env, javaString, chars); + throwIfNull(result, "toCF failed"); + badchars: + badresult: + return result; + } +} + + +// Create an empty Java string array of the given size. +// Throws and returns NULL on error. +static jarray createJavaStringArray(JNIEnv *env, CFIndex count) +{ + static jclass stringClass = NULL; + jclass c; + + if (stringClass) { + c = stringClass; + } else { + c = (*env)->FindClass(env, "java/lang/String"); + if ((*env)->ExceptionOccurred(env)) return NULL; + stringClass = (*env)->NewGlobalRef(env, c); + } + + return (*env)->NewObjectArray(env, count, c, NULL); // AWT_THREADING Safe (known object) +} + + +// Java accessors for CF constants. +JNIEXPORT jlong JNICALL +Java_java_util_prefs_MacOSXPreferencesFile_currentUser(JNIEnv *env, + jobject klass) +{ + return ptr_to_jlong(kCFPreferencesCurrentUser); +} + +JNIEXPORT jlong JNICALL +Java_java_util_prefs_MacOSXPreferencesFile_anyUser(JNIEnv *env, jobject klass) +{ + return ptr_to_jlong(kCFPreferencesAnyUser); +} + +JNIEXPORT jlong JNICALL +Java_java_util_prefs_MacOSXPreferencesFile_currentHost(JNIEnv *env, + jobject klass) +{ + return ptr_to_jlong(kCFPreferencesCurrentHost); +} + +JNIEXPORT jlong JNICALL +Java_java_util_prefs_MacOSXPreferencesFile_anyHost(JNIEnv *env, jobject klass) +{ + return ptr_to_jlong(kCFPreferencesAnyHost); +} + + +// Create an empty node. +// Does not store the node in any prefs file. +// returns NULL on memory error +static CFMutableDictionaryRef createEmptyNode(void) +{ + return CFDictionaryCreateMutable(NULL, 0, + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks); +} + + +// Create a string that consists of path minus its last component. +// path must end with '/' +// The result will end in '/' (unless path itself is '/') +static CFStringRef copyParentOf(CFStringRef path) +{ + CFRange searchRange; + CFRange slashRange; + CFRange parentRange; + Boolean found; + + searchRange = CFRangeMake(0, CFStringGetLength(path) - 1); + found = CFStringFindWithOptions(path, CFSTR("/"), searchRange, + kCFCompareBackwards, &slashRange); + if (!found) return CFSTR(""); + parentRange = CFRangeMake(0, slashRange.location + 1); // include '/' + return CFStringCreateWithSubstring(NULL, path, parentRange); +} + + +// Create a string that consists of path's last component. +// path must end with '/' +// The result will end in '/'. +// The result will not start with '/' (unless path itself is '/') +static CFStringRef copyChildOf(CFStringRef path) +{ + CFRange searchRange; + CFRange slashRange; + CFRange childRange; + Boolean found; + CFIndex length = CFStringGetLength(path); + + searchRange = CFRangeMake(0, length - 1); + found = CFStringFindWithOptions(path, CFSTR("/"), searchRange, + kCFCompareBackwards, &slashRange); + if (!found) return CFSTR(""); + childRange = CFRangeMake(slashRange.location + 1, + length - slashRange.location - 1); // skip '/' + return CFStringCreateWithSubstring(NULL, path, childRange); +} + + +// Return the first three components of path, with leading and trailing '/'. +// If path does not have three components, return NULL. +// path must begin and end in '/' +static CFStringRef copyFirstThreeComponentsOf(CFStringRef path) +{ + CFRange searchRange; + CFRange slashRange; + CFRange prefixRange; + CFStringRef prefix; + Boolean found; + CFIndex length = CFStringGetLength(path); + + searchRange = CFRangeMake(1, length - 1); // skip leading '/' + found = CFStringFindWithOptions(path, CFSTR("/"), searchRange, 0, + &slashRange); + if (!found) return NULL; // no second slash! + + searchRange = CFRangeMake(slashRange.location + 1, + length - slashRange.location - 1); + found = CFStringFindWithOptions(path, CFSTR("/"), searchRange, 0, + &slashRange); + if (!found) return NULL; // no third slash! + + searchRange = CFRangeMake(slashRange.location + 1, + length - slashRange.location - 1); + found = CFStringFindWithOptions(path, CFSTR("/"), searchRange, 0, + &slashRange); + if (!found) return NULL; // no fourth slash! + + prefixRange = CFRangeMake(0, slashRange.location + 1); // keep last '/' + prefix = CFStringCreateWithSubstring(NULL, path, prefixRange); + + return prefix; +} + + +// Copy the CFPreferences key and value at the base of path's tree. +// path must end in '/' +// topKey or topValue may be NULL +// Returns NULL on error or if there is no tree for path in this file. +static void copyTreeForPath(CFStringRef path, CFStringRef name, + CFStringRef user, CFStringRef host, + CFStringRef *topKey, CFDictionaryRef *topValue) +{ + CFStringRef key; + CFPropertyListRef value; + + if (topKey) *topKey = NULL; + if (topValue) *topValue = NULL; + + if (CFEqual(name, CFSTR("com.apple.java.util.prefs"))) { + // Top-level file. Only key "/" is an acceptable root. + key = (CFStringRef) CFRetain(CFSTR("/")); + } else { + // Second-level file. Key must be the first three components of path. + key = copyFirstThreeComponentsOf(path); + if (!key) return; + } + + value = CFPreferencesCopyValue(key, name, user, host); + if (value) { + if (CFGetTypeID(value) == CFDictionaryGetTypeID()) { + // (key, value) is acceptable + if (topKey) *topKey = (CFStringRef)CFRetain(key); + if (topValue) *topValue = (CFDictionaryRef)CFRetain(value); + } + CFRelease(value); + } + CFRelease(key); +} + + +// Find the node for path in the given tree. +// Returns NULL on error or if path doesn't have a node in this tree. +// path must end in '/' +static CFDictionaryRef copyNodeInTree(CFStringRef path, CFStringRef topKey, + CFDictionaryRef topValue) +{ + CFMutableStringRef p; + CFDictionaryRef result = NULL; + + p = CFStringCreateMutableCopy(NULL, 0, path); + if (!p) return NULL; + CFStringDelete(p, CFRangeMake(0, CFStringGetLength(topKey))); + result = topValue; + + while (CFStringGetLength(p) > 0) { + CFDictionaryRef child; + CFStringRef part = NULL; + CFRange slashRange = CFStringFind(p, CFSTR("/"), 0); + // guaranteed to succeed because path must end in '/' + CFRange partRange = CFRangeMake(0, slashRange.location + 1); + part = CFStringCreateWithSubstring(NULL, p, partRange); + if (!part) { result = NULL; break; } + CFStringDelete(p, partRange); + + child = CFDictionaryGetValue(result, part); + CFRelease(part); + if (child && CFGetTypeID(child) == CFDictionaryGetTypeID()) { + // continue search + result = child; + } else { + // didn't find target node + result = NULL; + break; + } + } + + CFRelease(p); + if (result) return (CFDictionaryRef)CFRetain(result); + else return NULL; +} + + +// Return a retained copy of the node at path from the given file. +// path must end in '/' +// returns NULL if node doesn't exist. +// returns NULL if the value for key "path" isn't a valid node. +static CFDictionaryRef copyNodeIfPresent(CFStringRef path, CFStringRef name, + CFStringRef user, CFStringRef host) +{ + CFStringRef topKey; + CFDictionaryRef topValue; + CFDictionaryRef result; + + copyTreeForPath(path, name, user, host, &topKey, &topValue); + if (!topKey) return NULL; + + result = copyNodeInTree(path, topKey, topValue); + + CFRelease(topKey); + if (topValue) CFRelease(topValue); + return result; +} + + +// Create a new tree that would store path in the given file. +// Only the root of the tree is created, not all of the links leading to path. +// returns NULL on error +static void createTreeForPath(CFStringRef path, CFStringRef name, + CFStringRef user, CFStringRef host, + CFStringRef *outTopKey, + CFMutableDictionaryRef *outTopValue) +{ + *outTopKey = NULL; + *outTopValue = NULL; + + // if name is "com.apple.java.util.prefs" then create tree "/" + // else create tree "/foo/bar/baz/" + // "com.apple.java.util.prefs.plist" is also in MacOSXPreferences.java + if (CFEqual(name, CFSTR("com.apple.java.util.prefs"))) { + *outTopKey = CFSTR("/"); + *outTopValue = createEmptyNode(); + } else { + CFStringRef prefix = copyFirstThreeComponentsOf(path); + if (prefix) { + *outTopKey = prefix; + *outTopValue = createEmptyNode(); + } + } +} + + +// Return a mutable copy of the tree containing path and the dict for +// path itself. *outTopKey and *outTopValue can be used to write the +// modified tree back to the prefs file. +// *outTopKey and *outTopValue must be released iff the actual return +// value is not NULL. +static CFMutableDictionaryRef +copyMutableNode(CFStringRef path, CFStringRef name, + CFStringRef user, CFStringRef host, + CFStringRef *outTopKey, + CFMutableDictionaryRef *outTopValue) +{ + CFStringRef topKey = NULL; + CFDictionaryRef oldTopValue = NULL; + CFMutableDictionaryRef topValue; + CFMutableDictionaryRef result = NULL; + CFMutableStringRef p; + + if (outTopKey) *outTopKey = NULL; + if (outTopValue) *outTopValue = NULL; + + copyTreeForPath(path, name, user, host, &topKey, &oldTopValue); + if (!topKey) { + createTreeForPath(path, name, user, host, &topKey, &topValue); + } else { + topValue = (CFMutableDictionaryRef) + CFPropertyListCreateDeepCopy(NULL, (CFPropertyListRef)oldTopValue, + kCFPropertyListMutableContainers); + } + if (!topValue) goto badtopValue; + + p = CFStringCreateMutableCopy(NULL, 0, path); + if (!p) goto badp; + CFStringDelete(p, CFRangeMake(0, CFStringGetLength(topKey))); + result = topValue; + + while (CFStringGetLength(p) > 0) { + CFMutableDictionaryRef child; + CFStringRef part = NULL; + CFRange slashRange = CFStringFind(p, CFSTR("/"), 0); + // guaranteed to succeed because path must end in '/' + CFRange partRange = CFRangeMake(0, slashRange.location + 1); + part = CFStringCreateWithSubstring(NULL, p, partRange); + if (!part) { result = NULL; break; } + CFStringDelete(p, partRange); + + child = (CFMutableDictionaryRef)CFDictionaryGetValue(result, part); + if (child && CFGetTypeID(child) == CFDictionaryGetTypeID()) { + // continue search + result = child; + } else { + // didn't find target node - add it and continue + child = createEmptyNode(); + if (!child) { CFRelease(part); result = NULL; break; } + CFDictionaryAddValue(result, part, child); + result = child; + } + CFRelease(part); + } + + if (result) { + *outTopKey = (CFStringRef)CFRetain(topKey); + *outTopValue = (CFMutableDictionaryRef)CFRetain(topValue); + CFRetain(result); + } + + CFRelease(p); + badp: + CFRelease(topValue); + badtopValue: + if (topKey) CFRelease(topKey); + if (oldTopValue) CFRelease(oldTopValue); + return result; +} + + +JNIEXPORT jboolean JNICALL +Java_java_util_prefs_MacOSXPreferencesFile_addNode +(JNIEnv *env, jobject klass, jobject jpath, + jobject jname, jlong juser, jlong jhost) +{ + CFStringRef path = toCF(env, jpath); + CFStringRef name = toCF(env, jname); + CFStringRef user = (CFStringRef)jlong_to_ptr(juser); + CFStringRef host = (CFStringRef)jlong_to_ptr(jhost); + CFDictionaryRef node = NULL; + jboolean neededNewNode = false; + + if (!path || !name) goto badparams; + + node = copyNodeIfPresent(path, name, user, host); + + if (node) { + neededNewNode = false; + CFRelease(node); + } else { + CFStringRef topKey = NULL; + CFMutableDictionaryRef topValue = NULL; + + neededNewNode = true; + + // copyMutableNode creates the node if necessary + node = copyMutableNode(path, name, user, host, &topKey, &topValue); + throwIfNull(node, "copyMutableNode failed"); + + CFPreferencesSetValue(topKey, topValue, name, user, host); + + CFRelease(node); + if (topKey) CFRelease(topKey); + if (topValue) CFRelease(topValue); + } + + badnode: + badparams: + if (path) CFRelease(path); + if (name) CFRelease(name); + + return neededNewNode; +} + + +JNIEXPORT void JNICALL +Java_java_util_prefs_MacOSXPreferencesFile_removeNode +(JNIEnv *env, jobject klass, jobject jpath, + jobject jname, jlong juser, jlong jhost) +{ + CFStringRef path = toCF(env, jpath); + CFStringRef name = toCF(env, jname); + CFStringRef user = (CFStringRef)jlong_to_ptr(juser); + CFStringRef host = (CFStringRef)jlong_to_ptr(jhost); + CFStringRef parentName; + CFStringRef childName; + CFDictionaryRef constParent; + + if (!path || !name) goto badparams; + + parentName = copyParentOf(path); + throwIfNull(parentName, "copyParentOf failed"); + childName = copyChildOf(path); + throwIfNull(childName, "copyChildOf failed"); + + // root node is not allowed to be removed, so parentName is never empty + + constParent = copyNodeIfPresent(parentName, name, user, host); + if (constParent && CFDictionaryContainsKey(constParent, childName)) { + CFStringRef topKey; + CFMutableDictionaryRef topValue; + CFMutableDictionaryRef parent; + + parent = copyMutableNode(parentName, name, user, host, + &topKey, &topValue); + throwIfNull(parent, "copyMutableNode failed"); + + CFDictionaryRemoveValue(parent, childName); + CFPreferencesSetValue(topKey, topValue, name, user, host); + + CFRelease(parent); + if (topKey) CFRelease(topKey); + if (topValue) CFRelease(topValue); + } else { + // might be trying to remove the root itself in a non-root file + CFStringRef topKey; + CFDictionaryRef topValue; + copyTreeForPath(path, name, user, host, &topKey, &topValue); + if (topKey) { + if (CFEqual(topKey, path)) { + CFPreferencesSetValue(topKey, NULL, name, user, host); + } + + if (topKey) CFRelease(topKey); + if (topValue) CFRelease(topValue); + } + } + + + badparent: + if (constParent) CFRelease(constParent); + CFRelease(childName); + badchildName: + CFRelease(parentName); + badparentName: + badparams: + if (path) CFRelease(path); + if (name) CFRelease(name); +} + + +// child must end with '/' +JNIEXPORT Boolean JNICALL +Java_java_util_prefs_MacOSXPreferencesFile_addChildToNode +(JNIEnv *env, jobject klass, jobject jpath, jobject jchild, + jobject jname, jlong juser, jlong jhost) +{ + // like addNode, but can put a three-level-deep dict into the root file + CFStringRef path = toCF(env, jpath); + CFStringRef child = toCF(env, jchild); + CFStringRef name = toCF(env, jname); + CFStringRef user = (CFStringRef)jlong_to_ptr(juser); + CFStringRef host = (CFStringRef)jlong_to_ptr(jhost); + CFMutableDictionaryRef parent; + CFDictionaryRef node; + CFStringRef topKey; + CFMutableDictionaryRef topValue; + Boolean beforeAdd = false; + + if (!path || !child || !name) goto badparams; + + node = createEmptyNode(); + throwIfNull(node, "createEmptyNode failed"); + + // copyMutableNode creates the node if necessary + parent = copyMutableNode(path, name, user, host, &topKey, &topValue); + throwIfNull(parent, "copyMutableNode failed"); + beforeAdd = CFDictionaryContainsKey(parent, child); + CFDictionaryAddValue(parent, child, node); + if (!beforeAdd) + beforeAdd = CFDictionaryContainsKey(parent, child); + else + beforeAdd = false; + CFPreferencesSetValue(topKey, topValue, name, user, host); + + CFRelease(parent); + if (topKey) CFRelease(topKey); + if (topValue) CFRelease(topValue); + badparent: + CFRelease(node); + badnode: + badparams: + if (path) CFRelease(path); + if (child) CFRelease(child); + if (name) CFRelease(name); + return beforeAdd; +} + + +JNIEXPORT void JNICALL +Java_java_util_prefs_MacOSXPreferencesFile_removeChildFromNode +(JNIEnv *env, jobject klass, jobject jpath, jobject jchild, + jobject jname, jlong juser, jlong jhost) +{ + CFStringRef path = toCF(env, jpath); + CFStringRef child = toCF(env, jchild); + CFStringRef name = toCF(env, jname); + CFStringRef user = (CFStringRef)jlong_to_ptr(juser); + CFStringRef host = (CFStringRef)jlong_to_ptr(jhost); + CFDictionaryRef constParent; + + if (!path || !child || !name) goto badparams; + + constParent = copyNodeIfPresent(path, name, user, host); + if (constParent && CFDictionaryContainsKey(constParent, child)) { + CFStringRef topKey; + CFMutableDictionaryRef topValue; + CFMutableDictionaryRef parent; + + parent = copyMutableNode(path, name, user, host, &topKey, &topValue); + throwIfNull(parent, "copyMutableNode failed"); + + CFDictionaryRemoveValue(parent, child); + CFPreferencesSetValue(topKey, topValue, name, user, host); + + CFRelease(parent); + if (topKey) CFRelease(topKey); + if (topValue) CFRelease(topValue); + } + + badparent: + if (constParent) CFRelease(constParent); + badparams: + if (path) CFRelease(path); + if (child) CFRelease(child); + if (name) CFRelease(name); +} + + + +JNIEXPORT void JNICALL +Java_java_util_prefs_MacOSXPreferencesFile_addKeyToNode +(JNIEnv *env, jobject klass, jobject jpath, jobject jkey, jobject jvalue, + jobject jname, jlong juser, jlong jhost) +{ + CFStringRef path = toCF(env, jpath); + CFStringRef key = toCF(env, jkey); + CFStringRef value = toCF(env, jvalue); + CFStringRef name = toCF(env, jname); + CFStringRef user = (CFStringRef)jlong_to_ptr(juser); + CFStringRef host = (CFStringRef)jlong_to_ptr(jhost); + CFMutableDictionaryRef node = NULL; + CFStringRef topKey; + CFMutableDictionaryRef topValue; + + if (!path || !key || !value || !name) goto badparams; + + // fixme optimization: check whether old value and new value are identical + node = copyMutableNode(path, name, user, host, &topKey, &topValue); + throwIfNull(node, "copyMutableNode failed"); + + CFDictionarySetValue(node, key, value); + CFPreferencesSetValue(topKey, topValue, name, user, host); + + CFRelease(node); + if (topKey) CFRelease(topKey); + if (topValue) CFRelease(topValue); + + badnode: + badparams: + if (path) CFRelease(path); + if (key) CFRelease(key); + if (value) CFRelease(value); + if (name) CFRelease(name); +} + + +JNIEXPORT void JNICALL +Java_java_util_prefs_MacOSXPreferencesFile_removeKeyFromNode +(JNIEnv *env, jobject klass, jobject jpath, jobject jkey, + jobject jname, jlong juser, jlong jhost) +{ + CFStringRef path = toCF(env, jpath); + CFStringRef key = toCF(env, jkey); + CFStringRef name = toCF(env, jname); + CFStringRef user = (CFStringRef)jlong_to_ptr(juser); + CFStringRef host = (CFStringRef)jlong_to_ptr(jhost); + CFDictionaryRef constNode; + + if (!path || !key || !name) goto badparams; + + constNode = copyNodeIfPresent(path, name, user, host); + if (constNode && CFDictionaryContainsKey(constNode, key)) { + CFStringRef topKey; + CFMutableDictionaryRef topValue; + CFMutableDictionaryRef node; + + node = copyMutableNode(path, name, user, host, &topKey, &topValue); + throwIfNull(node, "copyMutableNode failed"); + + CFDictionaryRemoveValue(node, key); + CFPreferencesSetValue(topKey, topValue, name, user, host); + + CFRelease(node); + if (topKey) CFRelease(topKey); + if (topValue) CFRelease(topValue); + } + + badnode: + if (constNode) CFRelease(constNode); + badparams: + if (path) CFRelease(path); + if (key) CFRelease(key); + if (name) CFRelease(name); +} + + +// path must end in '/' +JNIEXPORT jstring JNICALL +Java_java_util_prefs_MacOSXPreferencesFile_getKeyFromNode +(JNIEnv *env, jobject klass, jobject jpath, jobject jkey, + jobject jname, jlong juser, jlong jhost) +{ + CFStringRef path = toCF(env, jpath); + CFStringRef key = toCF(env, jkey); + CFStringRef name = toCF(env, jname); + CFStringRef user = (CFStringRef)jlong_to_ptr(juser); + CFStringRef host = (CFStringRef)jlong_to_ptr(jhost); + CFPropertyListRef value; + CFDictionaryRef node; + jstring result = NULL; + + if (!path || !key || !name) goto badparams; + + node = copyNodeIfPresent(path, name, user, host); + if (node) { + value = (CFPropertyListRef)CFDictionaryGetValue(node, key); + if (!value) { + // key doesn't exist, or other error - no Java errors available + result = NULL; + } else { + CFStringRef cfString = copyToCFString(env, value); + if ((*env)->ExceptionOccurred(env)) { + // memory error in copyToCFString + result = NULL; + } else if (cfString == NULL) { + // bogus value type in prefs file - no Java errors available + result = NULL; + } else { + // good cfString + result = toJavaString(env, cfString); + CFRelease(cfString); + } + } + CFRelease(node); + } + + badparams: + if (path) CFRelease(path); + if (key) CFRelease(key); + if (name) CFRelease(name); + + return result; +} + + +typedef struct { + jarray result; + JNIEnv *env; + CFIndex used; + Boolean allowSlash; +} BuildJavaArrayArgs; + +// CFDictionary applier function that builds an array of Java strings +// from a CFDictionary of CFPropertyListRefs. +// If args->allowSlash, only strings that end in '/' are added to the array, +// with the slash removed. Otherwise, only strings that do not end in '/' +// are added. +// args->result must already exist and be large enough to hold all +// strings from the dictionary. +// After complete application, args->result may not be full because +// some of the dictionary values weren't convertible to string. In +// this case, args->used will be the count of used elements. +static void BuildJavaArrayFn(const void *key, const void *value, void *context) +{ + BuildJavaArrayArgs *args = (BuildJavaArrayArgs *)context; + CFPropertyListRef propkey = (CFPropertyListRef)key; + CFStringRef cfString = NULL; + JNIEnv *env = args->env; + + if ((*env)->ExceptionOccurred(env)) return; // already failed + + cfString = copyToCFString(env, propkey); + if ((*env)->ExceptionOccurred(env)) { + // memory error in copyToCFString + } else if (!cfString) { + // bogus value type in prefs file - no Java errors available + } else if (args->allowSlash != CFStringHasSuffix(cfString, CFSTR("/"))) { + // wrong suffix - ignore + } else { + // good cfString + jstring javaString; + if (args->allowSlash) { + CFRange range = CFRangeMake(0, CFStringGetLength(cfString) - 1); + CFStringRef s = CFStringCreateWithSubstring(NULL, cfString, range); + CFRelease(cfString); + cfString = s; + } + if (CFStringGetLength(cfString) <= 0) goto bad; // ignore empty + javaString = toJavaString(env, cfString); + if ((*env)->ExceptionOccurred(env)) goto bad; + (*env)->SetObjectArrayElement(env, args->result,args->used,javaString); + if ((*env)->ExceptionOccurred(env)) goto bad; + args->used++; + } + + bad: + if (cfString) CFRelease(cfString); +} + + +static jarray getStringsForNode(JNIEnv *env, jobject klass, jobject jpath, + jobject jname, jlong juser, jlong jhost, + Boolean allowSlash) +{ + CFStringRef path = toCF(env, jpath); + CFStringRef name = toCF(env, jname); + CFStringRef user = (CFStringRef)jlong_to_ptr(juser); + CFStringRef host = (CFStringRef)jlong_to_ptr(jhost); + CFDictionaryRef node; + jarray result = NULL; + CFIndex count; + + if (!path || !name) goto badparams; + + node = copyNodeIfPresent(path, name, user, host); + if (!node) { + result = createJavaStringArray(env, 0); + } else { + count = CFDictionaryGetCount(node); + result = createJavaStringArray(env, count); + if (result) { + BuildJavaArrayArgs args; + args.result = result; + args.env = env; + args.used = 0; + args.allowSlash = allowSlash; + CFDictionaryApplyFunction(node, BuildJavaArrayFn, &args); + if (!(*env)->ExceptionOccurred(env)) { + // array construction succeeded + if (args.used < count) { + // finished array is smaller than expected. + // Make a new array of precisely the right size. + jarray newresult = createJavaStringArray(env, args.used); + if (newresult) { + JVM_ArrayCopy(env,0, result,0, newresult,0, args.used); + result = newresult; + } + } + } + } + + CFRelease(node); + } + + badparams: + if (path) CFRelease(path); + if (name) CFRelease(name); + + return result; +} + + +JNIEXPORT jarray JNICALL +Java_java_util_prefs_MacOSXPreferencesFile_getKeysForNode +(JNIEnv *env, jobject klass, jobject jpath, + jobject jname, jlong juser, jlong jhost) +{ + return getStringsForNode(env, klass, jpath, jname, juser, jhost, false); +} + +JNIEXPORT jarray JNICALL +Java_java_util_prefs_MacOSXPreferencesFile_getChildrenForNode +(JNIEnv *env, jobject klass, jobject jpath, + jobject jname, jlong juser, jlong jhost) +{ + return getStringsForNode(env, klass, jpath, jname, juser, jhost, true); +} + + +// Returns false on error instead of throwing. +JNIEXPORT jboolean JNICALL +Java_java_util_prefs_MacOSXPreferencesFile_synchronize +(JNIEnv *env, jobject klass, + jstring jname, jlong juser, jlong jhost) +{ + CFStringRef name = toCF(env, jname); + CFStringRef user = (CFStringRef)jlong_to_ptr(juser); + CFStringRef host = (CFStringRef)jlong_to_ptr(jhost); + jboolean result = 0; + + if (name) { + result = CFPreferencesSynchronize(name, user, host); + CFRelease(name); + } + + return result; +}