Path: blob/master/src/java.prefs/macosx/native/libprefs/MacOSXPreferencesFile.m
41149 views
/*1* Copyright (c) 2011, 2018, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425/*26Hierarchical storage layout:2728<dict>29<key>/</key>30<dict>31<key>foo</key>32<string>/foo's value</string>33<key>foo/</key>34<dict>35<key>bar</key>36<string>/foo/bar's value</string>37</dict>38</dict>39</dict>4041Java pref nodes are stored in several different files. Pref nodes42with at least three components in the node name (e.g. /com/MyCompany/MyApp/)43are stored in a CF prefs file with the first three components as the name.44This way, all preferences for MyApp end up in com.MyCompany.MyApp.plist .45Pref nodes with shorter names are stored in com.apple.java.util.prefs.plist4647The filesystem is assumed to be case-insensitive (like HFS+).48Java pref node names are case-sensitive. If two pref node names differ49only in case, they may end up in the same pref file. This is ok50because the CF keys identifying the node span the entire absolute path51to the node and are case-sensitive.5253Java node names may contain '.' . When mapping to the CF file name,54these dots are left as-is, even though '/' is mapped to '.' .55This is ok because the CF key contains the correct node name.56*/57585960#include <CoreFoundation/CoreFoundation.h>6162#include "jni_util.h"63#include "jlong.h"64#include "jvm.h"65#include "java_util_prefs_MacOSXPreferencesFile.h"6667/*68* Declare library specific JNI_Onload entry if static build69*/70DEF_STATIC_JNI_OnLoad717273// Throw an OutOfMemoryError with the given message.74static void throwOutOfMemoryError(JNIEnv *env, const char *msg)75{76static jclass exceptionClass = NULL;77jclass c;7879(*env)->ExceptionClear(env); // If an exception is pending, clear it before80// calling FindClass() and/or ThrowNew().81if (exceptionClass) {82c = exceptionClass;83} else {84c = (*env)->FindClass(env, "java/lang/OutOfMemoryError");85if ((*env)->ExceptionOccurred(env)) return;86exceptionClass = (*env)->NewGlobalRef(env, c);87}8889(*env)->ThrowNew(env, c, msg);90}919293// throwIfNull macro94// If var is NULL, throw an OutOfMemoryError and goto badvar.95// var must be a variable. env must be the current JNIEnv.96// fixme throw BackingStoreExceptions sometimes?97#define throwIfNull(var, msg) \98do { \99if (var == NULL) { \100throwOutOfMemoryError(env, msg); \101goto bad##var; \102} \103} while (0)104105106// Converts CFNumber, CFBoolean, CFString to CFString107// returns NULL if value is of some other type108// throws and returns NULL on memory error109// result must be released (even if value was already a CFStringRef)110// value must not be null111static CFStringRef copyToCFString(JNIEnv *env, CFTypeRef value)112{113CFStringRef result;114CFTypeID type;115116type = CFGetTypeID(value);117118if (type == CFStringGetTypeID()) {119result = (CFStringRef)CFRetain(value);120}121else if (type == CFBooleanGetTypeID()) {122// Java Preferences API expects "true" and "false" for boolean values.123result = CFStringCreateCopy(NULL, (value == kCFBooleanTrue) ? CFSTR("true") : CFSTR("false"));124throwIfNull(result, "copyToCFString failed");125}126else if (type == CFNumberGetTypeID()) {127CFNumberRef number = (CFNumberRef) value;128if (CFNumberIsFloatType(number)) {129double d;130CFNumberGetValue(number, kCFNumberDoubleType, &d);131result = CFStringCreateWithFormat(NULL, NULL, CFSTR("%g"), d);132throwIfNull(result, "copyToCFString failed");133}134else {135long l;136CFNumberGetValue(number, kCFNumberLongType, &l);137result = CFStringCreateWithFormat(NULL, NULL, CFSTR("%ld"), l);138throwIfNull(result, "copyToCFString failed");139}140}141else {142// unknown type - return NULL143result = NULL;144}145146badresult:147return result;148}149150151// Create a Java string from the given CF string.152// returns NULL if cfString is NULL153// throws and returns NULL on memory error154static jstring toJavaString(JNIEnv *env, CFStringRef cfString)155{156if (cfString == NULL) {157return NULL;158} else {159jstring javaString = NULL;160161CFIndex length = CFStringGetLength(cfString);162const UniChar *constchars = CFStringGetCharactersPtr(cfString);163if (constchars) {164javaString = (*env)->NewString(env, constchars, length);165} else {166UniChar *chars = malloc(length * sizeof(UniChar));167throwIfNull(chars, "toJavaString failed");168CFStringGetCharacters(cfString, CFRangeMake(0, length), chars);169javaString = (*env)->NewString(env, chars, length);170free(chars);171}172badchars:173return javaString;174}175}176177178179// Create a CF string from the given Java string.180// returns NULL if javaString is NULL181// throws and returns NULL on memory error182static CFStringRef toCF(JNIEnv *env, jstring javaString)183{184if (javaString == NULL) {185return NULL;186} else {187CFStringRef result = NULL;188jsize length = (*env)->GetStringLength(env, javaString);189const jchar *chars = (*env)->GetStringChars(env, javaString, NULL);190throwIfNull(chars, "toCF failed");191result =192CFStringCreateWithCharacters(NULL, (const UniChar *)chars, length);193(*env)->ReleaseStringChars(env, javaString, chars);194throwIfNull(result, "toCF failed");195badchars:196badresult:197return result;198}199}200201202// Create an empty Java string array of the given size.203// Throws and returns NULL on error.204static jarray createJavaStringArray(JNIEnv *env, CFIndex count)205{206static jclass stringClass = NULL;207jclass c;208209if (stringClass) {210c = stringClass;211} else {212c = (*env)->FindClass(env, "java/lang/String");213if ((*env)->ExceptionOccurred(env)) return NULL;214stringClass = (*env)->NewGlobalRef(env, c);215}216217return (*env)->NewObjectArray(env, count, c, NULL); // AWT_THREADING Safe (known object)218}219220221// Java accessors for CF constants.222JNIEXPORT jlong JNICALL223Java_java_util_prefs_MacOSXPreferencesFile_currentUser(JNIEnv *env,224jobject klass)225{226return ptr_to_jlong(kCFPreferencesCurrentUser);227}228229JNIEXPORT jlong JNICALL230Java_java_util_prefs_MacOSXPreferencesFile_anyUser(JNIEnv *env, jobject klass)231{232return ptr_to_jlong(kCFPreferencesAnyUser);233}234235JNIEXPORT jlong JNICALL236Java_java_util_prefs_MacOSXPreferencesFile_currentHost(JNIEnv *env,237jobject klass)238{239return ptr_to_jlong(kCFPreferencesCurrentHost);240}241242JNIEXPORT jlong JNICALL243Java_java_util_prefs_MacOSXPreferencesFile_anyHost(JNIEnv *env, jobject klass)244{245return ptr_to_jlong(kCFPreferencesAnyHost);246}247248249// Create an empty node.250// Does not store the node in any prefs file.251// returns NULL on memory error252static CFMutableDictionaryRef createEmptyNode(void)253{254return CFDictionaryCreateMutable(NULL, 0,255&kCFTypeDictionaryKeyCallBacks,256&kCFTypeDictionaryValueCallBacks);257}258259260// Create a string that consists of path minus its last component.261// path must end with '/'262// The result will end in '/' (unless path itself is '/')263static CFStringRef copyParentOf(CFStringRef path)264{265CFRange searchRange;266CFRange slashRange;267CFRange parentRange;268Boolean found;269270searchRange = CFRangeMake(0, CFStringGetLength(path) - 1);271found = CFStringFindWithOptions(path, CFSTR("/"), searchRange,272kCFCompareBackwards, &slashRange);273if (!found) return CFSTR("");274parentRange = CFRangeMake(0, slashRange.location + 1); // include '/'275return CFStringCreateWithSubstring(NULL, path, parentRange);276}277278279// Create a string that consists of path's last component.280// path must end with '/'281// The result will end in '/'.282// The result will not start with '/' (unless path itself is '/')283static CFStringRef copyChildOf(CFStringRef path)284{285CFRange searchRange;286CFRange slashRange;287CFRange childRange;288Boolean found;289CFIndex length = CFStringGetLength(path);290291searchRange = CFRangeMake(0, length - 1);292found = CFStringFindWithOptions(path, CFSTR("/"), searchRange,293kCFCompareBackwards, &slashRange);294if (!found) return CFSTR("");295childRange = CFRangeMake(slashRange.location + 1,296length - slashRange.location - 1); // skip '/'297return CFStringCreateWithSubstring(NULL, path, childRange);298}299300301// Return the first three components of path, with leading and trailing '/'.302// If path does not have three components, return NULL.303// path must begin and end in '/'304static CFStringRef copyFirstThreeComponentsOf(CFStringRef path)305{306CFRange searchRange;307CFRange slashRange;308CFRange prefixRange;309CFStringRef prefix;310Boolean found;311CFIndex length = CFStringGetLength(path);312313searchRange = CFRangeMake(1, length - 1); // skip leading '/'314found = CFStringFindWithOptions(path, CFSTR("/"), searchRange, 0,315&slashRange);316if (!found) return NULL; // no second slash!317318searchRange = CFRangeMake(slashRange.location + 1,319length - slashRange.location - 1);320found = CFStringFindWithOptions(path, CFSTR("/"), searchRange, 0,321&slashRange);322if (!found) return NULL; // no third slash!323324searchRange = CFRangeMake(slashRange.location + 1,325length - slashRange.location - 1);326found = CFStringFindWithOptions(path, CFSTR("/"), searchRange, 0,327&slashRange);328if (!found) return NULL; // no fourth slash!329330prefixRange = CFRangeMake(0, slashRange.location + 1); // keep last '/'331prefix = CFStringCreateWithSubstring(NULL, path, prefixRange);332333return prefix;334}335336337// Copy the CFPreferences key and value at the base of path's tree.338// path must end in '/'339// topKey or topValue may be NULL340// Returns NULL on error or if there is no tree for path in this file.341static void copyTreeForPath(CFStringRef path, CFStringRef name,342CFStringRef user, CFStringRef host,343CFStringRef *topKey, CFDictionaryRef *topValue)344{345CFStringRef key;346CFPropertyListRef value;347348if (topKey) *topKey = NULL;349if (topValue) *topValue = NULL;350351if (CFEqual(name, CFSTR("com.apple.java.util.prefs"))) {352// Top-level file. Only key "/" is an acceptable root.353key = (CFStringRef) CFRetain(CFSTR("/"));354} else {355// Second-level file. Key must be the first three components of path.356key = copyFirstThreeComponentsOf(path);357if (!key) return;358}359360value = CFPreferencesCopyValue(key, name, user, host);361if (value) {362if (CFGetTypeID(value) == CFDictionaryGetTypeID()) {363// (key, value) is acceptable364if (topKey) *topKey = (CFStringRef)CFRetain(key);365if (topValue) *topValue = (CFDictionaryRef)CFRetain(value);366}367CFRelease(value);368}369CFRelease(key);370}371372373// Find the node for path in the given tree.374// Returns NULL on error or if path doesn't have a node in this tree.375// path must end in '/'376static CFDictionaryRef copyNodeInTree(CFStringRef path, CFStringRef topKey,377CFDictionaryRef topValue)378{379CFMutableStringRef p;380CFDictionaryRef result = NULL;381382p = CFStringCreateMutableCopy(NULL, 0, path);383if (!p) return NULL;384CFStringDelete(p, CFRangeMake(0, CFStringGetLength(topKey)));385result = topValue;386387while (CFStringGetLength(p) > 0) {388CFDictionaryRef child;389CFStringRef part = NULL;390CFRange slashRange = CFStringFind(p, CFSTR("/"), 0);391// guaranteed to succeed because path must end in '/'392CFRange partRange = CFRangeMake(0, slashRange.location + 1);393part = CFStringCreateWithSubstring(NULL, p, partRange);394if (!part) { result = NULL; break; }395CFStringDelete(p, partRange);396397child = CFDictionaryGetValue(result, part);398CFRelease(part);399if (child && CFGetTypeID(child) == CFDictionaryGetTypeID()) {400// continue search401result = child;402} else {403// didn't find target node404result = NULL;405break;406}407}408409CFRelease(p);410if (result) return (CFDictionaryRef)CFRetain(result);411else return NULL;412}413414415// Return a retained copy of the node at path from the given file.416// path must end in '/'417// returns NULL if node doesn't exist.418// returns NULL if the value for key "path" isn't a valid node.419static CFDictionaryRef copyNodeIfPresent(CFStringRef path, CFStringRef name,420CFStringRef user, CFStringRef host)421{422CFStringRef topKey;423CFDictionaryRef topValue;424CFDictionaryRef result;425426copyTreeForPath(path, name, user, host, &topKey, &topValue);427if (!topKey) return NULL;428429result = copyNodeInTree(path, topKey, topValue);430431CFRelease(topKey);432if (topValue) CFRelease(topValue);433return result;434}435436437// Create a new tree that would store path in the given file.438// Only the root of the tree is created, not all of the links leading to path.439// returns NULL on error440static void createTreeForPath(CFStringRef path, CFStringRef name,441CFStringRef user, CFStringRef host,442CFStringRef *outTopKey,443CFMutableDictionaryRef *outTopValue)444{445*outTopKey = NULL;446*outTopValue = NULL;447448// if name is "com.apple.java.util.prefs" then create tree "/"449// else create tree "/foo/bar/baz/"450// "com.apple.java.util.prefs.plist" is also in MacOSXPreferences.java451if (CFEqual(name, CFSTR("com.apple.java.util.prefs"))) {452*outTopKey = CFSTR("/");453*outTopValue = createEmptyNode();454} else {455CFStringRef prefix = copyFirstThreeComponentsOf(path);456if (prefix) {457*outTopKey = prefix;458*outTopValue = createEmptyNode();459}460}461}462463464// Return a mutable copy of the tree containing path and the dict for465// path itself. *outTopKey and *outTopValue can be used to write the466// modified tree back to the prefs file.467// *outTopKey and *outTopValue must be released iff the actual return468// value is not NULL.469static CFMutableDictionaryRef470copyMutableNode(CFStringRef path, CFStringRef name,471CFStringRef user, CFStringRef host,472CFStringRef *outTopKey,473CFMutableDictionaryRef *outTopValue)474{475CFStringRef topKey = NULL;476CFDictionaryRef oldTopValue = NULL;477CFMutableDictionaryRef topValue;478CFMutableDictionaryRef result = NULL;479CFMutableStringRef p;480481if (outTopKey) *outTopKey = NULL;482if (outTopValue) *outTopValue = NULL;483484copyTreeForPath(path, name, user, host, &topKey, &oldTopValue);485if (!topKey) {486createTreeForPath(path, name, user, host, &topKey, &topValue);487} else {488topValue = (CFMutableDictionaryRef)489CFPropertyListCreateDeepCopy(NULL, (CFPropertyListRef)oldTopValue,490kCFPropertyListMutableContainers);491}492if (!topValue) goto badtopValue;493494p = CFStringCreateMutableCopy(NULL, 0, path);495if (!p) goto badp;496CFStringDelete(p, CFRangeMake(0, CFStringGetLength(topKey)));497result = topValue;498499while (CFStringGetLength(p) > 0) {500CFMutableDictionaryRef child;501CFStringRef part = NULL;502CFRange slashRange = CFStringFind(p, CFSTR("/"), 0);503// guaranteed to succeed because path must end in '/'504CFRange partRange = CFRangeMake(0, slashRange.location + 1);505part = CFStringCreateWithSubstring(NULL, p, partRange);506if (!part) { result = NULL; break; }507CFStringDelete(p, partRange);508509child = (CFMutableDictionaryRef)CFDictionaryGetValue(result, part);510if (child && CFGetTypeID(child) == CFDictionaryGetTypeID()) {511// continue search512result = child;513} else {514// didn't find target node - add it and continue515child = createEmptyNode();516if (!child) { CFRelease(part); result = NULL; break; }517CFDictionaryAddValue(result, part, child);518result = child;519}520CFRelease(part);521}522523if (result) {524*outTopKey = (CFStringRef)CFRetain(topKey);525*outTopValue = (CFMutableDictionaryRef)CFRetain(topValue);526CFRetain(result);527}528529CFRelease(p);530badp:531CFRelease(topValue);532badtopValue:533if (topKey) CFRelease(topKey);534if (oldTopValue) CFRelease(oldTopValue);535return result;536}537538539JNIEXPORT jboolean JNICALL540Java_java_util_prefs_MacOSXPreferencesFile_addNode541(JNIEnv *env, jobject klass, jobject jpath,542jobject jname, jlong juser, jlong jhost)543{544CFStringRef path = NULL;545CFStringRef name = NULL;546547path = toCF(env, jpath);548if (path != NULL) {549name = toCF(env, jname);550}551CFStringRef user = (CFStringRef)jlong_to_ptr(juser);552CFStringRef host = (CFStringRef)jlong_to_ptr(jhost);553CFDictionaryRef node = NULL;554jboolean neededNewNode = false;555556if (!path || !name) goto badparams;557558node = copyNodeIfPresent(path, name, user, host);559560if (node) {561neededNewNode = false;562CFRelease(node);563} else {564CFStringRef topKey = NULL;565CFMutableDictionaryRef topValue = NULL;566567neededNewNode = true;568569// copyMutableNode creates the node if necessary570node = copyMutableNode(path, name, user, host, &topKey, &topValue);571throwIfNull(node, "copyMutableNode failed");572573CFPreferencesSetValue(topKey, topValue, name, user, host);574575CFRelease(node);576if (topKey) CFRelease(topKey);577if (topValue) CFRelease(topValue);578}579580badnode:581badparams:582if (path) CFRelease(path);583if (name) CFRelease(name);584585return neededNewNode;586}587588589JNIEXPORT void JNICALL590Java_java_util_prefs_MacOSXPreferencesFile_removeNode591(JNIEnv *env, jobject klass, jobject jpath,592jobject jname, jlong juser, jlong jhost)593{594CFStringRef path = NULL;595CFStringRef name = NULL;596597path = toCF(env, jpath);598if (path != NULL) {599name = toCF(env, jname);600}601CFStringRef user = (CFStringRef)jlong_to_ptr(juser);602CFStringRef host = (CFStringRef)jlong_to_ptr(jhost);603CFStringRef parentName;604CFStringRef childName;605CFDictionaryRef constParent;606607if (!path || !name) goto badparams;608609parentName = copyParentOf(path);610throwIfNull(parentName, "copyParentOf failed");611childName = copyChildOf(path);612throwIfNull(childName, "copyChildOf failed");613614// root node is not allowed to be removed, so parentName is never empty615616constParent = copyNodeIfPresent(parentName, name, user, host);617if (constParent && CFDictionaryContainsKey(constParent, childName)) {618CFStringRef topKey;619CFMutableDictionaryRef topValue;620CFMutableDictionaryRef parent;621622parent = copyMutableNode(parentName, name, user, host,623&topKey, &topValue);624throwIfNull(parent, "copyMutableNode failed");625626CFDictionaryRemoveValue(parent, childName);627CFPreferencesSetValue(topKey, topValue, name, user, host);628629CFRelease(parent);630if (topKey) CFRelease(topKey);631if (topValue) CFRelease(topValue);632} else {633// might be trying to remove the root itself in a non-root file634CFStringRef topKey;635CFDictionaryRef topValue;636copyTreeForPath(path, name, user, host, &topKey, &topValue);637if (topKey) {638if (CFEqual(topKey, path)) {639CFPreferencesSetValue(topKey, NULL, name, user, host);640}641642if (topKey) CFRelease(topKey);643if (topValue) CFRelease(topValue);644}645}646647648badparent:649if (constParent) CFRelease(constParent);650CFRelease(childName);651badchildName:652CFRelease(parentName);653badparentName:654badparams:655if (path) CFRelease(path);656if (name) CFRelease(name);657}658659660// child must end with '/'661JNIEXPORT Boolean JNICALL662Java_java_util_prefs_MacOSXPreferencesFile_addChildToNode663(JNIEnv *env, jobject klass, jobject jpath, jobject jchild,664jobject jname, jlong juser, jlong jhost)665{666// like addNode, but can put a three-level-deep dict into the root file667CFStringRef path = NULL;668CFStringRef child = NULL;669CFStringRef name = NULL;670671path = toCF(env, jpath);672if (path != NULL) {673child = toCF(env, jchild);674}675if (child != NULL) {676name = toCF(env, jname);677}678CFStringRef user = (CFStringRef)jlong_to_ptr(juser);679CFStringRef host = (CFStringRef)jlong_to_ptr(jhost);680CFMutableDictionaryRef parent;681CFDictionaryRef node;682CFStringRef topKey;683CFMutableDictionaryRef topValue;684Boolean beforeAdd = false;685686if (!path || !child || !name) goto badparams;687688node = createEmptyNode();689throwIfNull(node, "createEmptyNode failed");690691// copyMutableNode creates the node if necessary692parent = copyMutableNode(path, name, user, host, &topKey, &topValue);693throwIfNull(parent, "copyMutableNode failed");694beforeAdd = CFDictionaryContainsKey(parent, child);695CFDictionaryAddValue(parent, child, node);696if (!beforeAdd)697beforeAdd = CFDictionaryContainsKey(parent, child);698else699beforeAdd = false;700CFPreferencesSetValue(topKey, topValue, name, user, host);701702CFRelease(parent);703if (topKey) CFRelease(topKey);704if (topValue) CFRelease(topValue);705badparent:706CFRelease(node);707badnode:708badparams:709if (path) CFRelease(path);710if (child) CFRelease(child);711if (name) CFRelease(name);712return beforeAdd;713}714715716JNIEXPORT void JNICALL717Java_java_util_prefs_MacOSXPreferencesFile_removeChildFromNode718(JNIEnv *env, jobject klass, jobject jpath, jobject jchild,719jobject jname, jlong juser, jlong jhost)720{721CFStringRef path = NULL;722CFStringRef child = NULL;723CFStringRef name = NULL;724725path = toCF(env, jpath);726if (path != NULL) {727child = toCF(env, jchild);728}729if (child != NULL) {730name = toCF(env, jname);731}732CFStringRef user = (CFStringRef)jlong_to_ptr(juser);733CFStringRef host = (CFStringRef)jlong_to_ptr(jhost);734CFDictionaryRef constParent;735736if (!path || !child || !name) goto badparams;737738constParent = copyNodeIfPresent(path, name, user, host);739if (constParent && CFDictionaryContainsKey(constParent, child)) {740CFStringRef topKey;741CFMutableDictionaryRef topValue;742CFMutableDictionaryRef parent;743744parent = copyMutableNode(path, name, user, host, &topKey, &topValue);745throwIfNull(parent, "copyMutableNode failed");746747CFDictionaryRemoveValue(parent, child);748CFPreferencesSetValue(topKey, topValue, name, user, host);749750CFRelease(parent);751if (topKey) CFRelease(topKey);752if (topValue) CFRelease(topValue);753}754755badparent:756if (constParent) CFRelease(constParent);757badparams:758if (path) CFRelease(path);759if (child) CFRelease(child);760if (name) CFRelease(name);761}762763764765JNIEXPORT void JNICALL766Java_java_util_prefs_MacOSXPreferencesFile_addKeyToNode767(JNIEnv *env, jobject klass, jobject jpath, jobject jkey, jobject jvalue,768jobject jname, jlong juser, jlong jhost)769{770CFStringRef path = NULL;771CFStringRef key = NULL;772CFStringRef value = NULL;773CFStringRef name = NULL;774775path = toCF(env, jpath);776if (path != NULL) {777key = toCF(env, jkey);778}779if (key != NULL) {780value = toCF(env, jvalue);781}782if (value != NULL) {783name = toCF(env, jname);784}785CFStringRef user = (CFStringRef)jlong_to_ptr(juser);786CFStringRef host = (CFStringRef)jlong_to_ptr(jhost);787CFMutableDictionaryRef node = NULL;788CFStringRef topKey;789CFMutableDictionaryRef topValue;790791if (!path || !key || !value || !name) goto badparams;792793// fixme optimization: check whether old value and new value are identical794node = copyMutableNode(path, name, user, host, &topKey, &topValue);795throwIfNull(node, "copyMutableNode failed");796797CFDictionarySetValue(node, key, value);798CFPreferencesSetValue(topKey, topValue, name, user, host);799800CFRelease(node);801if (topKey) CFRelease(topKey);802if (topValue) CFRelease(topValue);803804badnode:805badparams:806if (path) CFRelease(path);807if (key) CFRelease(key);808if (value) CFRelease(value);809if (name) CFRelease(name);810}811812813JNIEXPORT void JNICALL814Java_java_util_prefs_MacOSXPreferencesFile_removeKeyFromNode815(JNIEnv *env, jobject klass, jobject jpath, jobject jkey,816jobject jname, jlong juser, jlong jhost)817{818CFStringRef path = NULL;819CFStringRef key = NULL;820CFStringRef name = NULL;821822path = toCF(env, jpath);823if (path != NULL) {824key = toCF(env, jkey);825}826if (key != NULL) {827name = toCF(env, jname);828}829CFStringRef user = (CFStringRef)jlong_to_ptr(juser);830CFStringRef host = (CFStringRef)jlong_to_ptr(jhost);831CFDictionaryRef constNode;832833if (!path || !key || !name) goto badparams;834835constNode = copyNodeIfPresent(path, name, user, host);836if (constNode && CFDictionaryContainsKey(constNode, key)) {837CFStringRef topKey;838CFMutableDictionaryRef topValue;839CFMutableDictionaryRef node;840841node = copyMutableNode(path, name, user, host, &topKey, &topValue);842throwIfNull(node, "copyMutableNode failed");843844CFDictionaryRemoveValue(node, key);845CFPreferencesSetValue(topKey, topValue, name, user, host);846847CFRelease(node);848if (topKey) CFRelease(topKey);849if (topValue) CFRelease(topValue);850}851852badnode:853if (constNode) CFRelease(constNode);854badparams:855if (path) CFRelease(path);856if (key) CFRelease(key);857if (name) CFRelease(name);858}859860861// path must end in '/'862JNIEXPORT jstring JNICALL863Java_java_util_prefs_MacOSXPreferencesFile_getKeyFromNode864(JNIEnv *env, jobject klass, jobject jpath, jobject jkey,865jobject jname, jlong juser, jlong jhost)866{867CFStringRef path = NULL;868CFStringRef key = NULL;869CFStringRef name = NULL;870871path = toCF(env, jpath);872if (path != NULL) {873key = toCF(env, jkey);874}875if (key != NULL) {876name = toCF(env, jname);877}878CFStringRef user = (CFStringRef)jlong_to_ptr(juser);879CFStringRef host = (CFStringRef)jlong_to_ptr(jhost);880CFPropertyListRef value;881CFDictionaryRef node;882jstring result = NULL;883884if (!path || !key || !name) goto badparams;885886node = copyNodeIfPresent(path, name, user, host);887if (node) {888value = (CFPropertyListRef)CFDictionaryGetValue(node, key);889if (!value) {890// key doesn't exist, or other error - no Java errors available891result = NULL;892} else {893CFStringRef cfString = copyToCFString(env, value);894if ((*env)->ExceptionOccurred(env)) {895// memory error in copyToCFString896result = NULL;897} else if (cfString == NULL) {898// bogus value type in prefs file - no Java errors available899result = NULL;900} else {901// good cfString902result = toJavaString(env, cfString);903CFRelease(cfString);904}905}906CFRelease(node);907}908909badparams:910if (path) CFRelease(path);911if (key) CFRelease(key);912if (name) CFRelease(name);913914return result;915}916917918typedef struct {919jarray result;920JNIEnv *env;921CFIndex used;922Boolean allowSlash;923} BuildJavaArrayArgs;924925// CFDictionary applier function that builds an array of Java strings926// from a CFDictionary of CFPropertyListRefs.927// If args->allowSlash, only strings that end in '/' are added to the array,928// with the slash removed. Otherwise, only strings that do not end in '/'929// are added.930// args->result must already exist and be large enough to hold all931// strings from the dictionary.932// After complete application, args->result may not be full because933// some of the dictionary values weren't convertible to string. In934// this case, args->used will be the count of used elements.935static void BuildJavaArrayFn(const void *key, const void *value, void *context)936{937BuildJavaArrayArgs *args = (BuildJavaArrayArgs *)context;938CFPropertyListRef propkey = (CFPropertyListRef)key;939CFStringRef cfString = NULL;940JNIEnv *env = args->env;941942if ((*env)->ExceptionOccurred(env)) return; // already failed943944cfString = copyToCFString(env, propkey);945if ((*env)->ExceptionOccurred(env)) {946// memory error in copyToCFString947} else if (!cfString) {948// bogus value type in prefs file - no Java errors available949} else if (args->allowSlash != CFStringHasSuffix(cfString, CFSTR("/"))) {950// wrong suffix - ignore951} else {952// good cfString953jstring javaString;954if (args->allowSlash) {955CFRange range = CFRangeMake(0, CFStringGetLength(cfString) - 1);956CFStringRef s = CFStringCreateWithSubstring(NULL, cfString, range);957CFRelease(cfString);958cfString = s;959}960if (CFStringGetLength(cfString) <= 0) goto bad; // ignore empty961javaString = toJavaString(env, cfString);962if ((*env)->ExceptionOccurred(env)) goto bad;963(*env)->SetObjectArrayElement(env, args->result,args->used,javaString);964if ((*env)->ExceptionOccurred(env)) goto bad;965args->used++;966}967968bad:969if (cfString) CFRelease(cfString);970}971972973static jarray getStringsForNode(JNIEnv *env, jobject klass, jobject jpath,974jobject jname, jlong juser, jlong jhost,975Boolean allowSlash)976{977CFStringRef path = NULL;978CFStringRef name = NULL;979980path = toCF(env, jpath);981if (path != NULL) {982name = toCF(env, jname);983}984CFStringRef user = (CFStringRef)jlong_to_ptr(juser);985CFStringRef host = (CFStringRef)jlong_to_ptr(jhost);986CFDictionaryRef node;987jarray result = NULL;988CFIndex count;989990if (!path || !name) goto badparams;991992node = copyNodeIfPresent(path, name, user, host);993if (!node) {994result = createJavaStringArray(env, 0);995} else {996count = CFDictionaryGetCount(node);997result = createJavaStringArray(env, count);998if (result) {999BuildJavaArrayArgs args;1000args.result = result;1001args.env = env;1002args.used = 0;1003args.allowSlash = allowSlash;1004CFDictionaryApplyFunction(node, BuildJavaArrayFn, &args);1005if (!(*env)->ExceptionOccurred(env)) {1006// array construction succeeded1007if (args.used < count) {1008// finished array is smaller than expected.1009// Make a new array of precisely the right size.1010jarray newresult = createJavaStringArray(env, args.used);1011if (newresult) {1012JVM_ArrayCopy(env,0, result,0, newresult,0, args.used);1013result = newresult;1014}1015}1016}1017}10181019CFRelease(node);1020}10211022badparams:1023if (path) CFRelease(path);1024if (name) CFRelease(name);10251026return result;1027}102810291030JNIEXPORT jarray JNICALL1031Java_java_util_prefs_MacOSXPreferencesFile_getKeysForNode1032(JNIEnv *env, jobject klass, jobject jpath,1033jobject jname, jlong juser, jlong jhost)1034{1035return getStringsForNode(env, klass, jpath, jname, juser, jhost, false);1036}10371038JNIEXPORT jarray JNICALL1039Java_java_util_prefs_MacOSXPreferencesFile_getChildrenForNode1040(JNIEnv *env, jobject klass, jobject jpath,1041jobject jname, jlong juser, jlong jhost)1042{1043return getStringsForNode(env, klass, jpath, jname, juser, jhost, true);1044}104510461047// Returns false on error instead of throwing.1048JNIEXPORT jboolean JNICALL1049Java_java_util_prefs_MacOSXPreferencesFile_synchronize1050(JNIEnv *env, jobject klass,1051jstring jname, jlong juser, jlong jhost)1052{1053CFStringRef name = toCF(env, jname);1054CFStringRef user = (CFStringRef)jlong_to_ptr(juser);1055CFStringRef host = (CFStringRef)jlong_to_ptr(jhost);1056jboolean result = 0;10571058if (name) {1059result = CFPreferencesSynchronize(name, user, host);1060CFRelease(name);1061}10621063return result;1064}106510661067