1/*
2 * Copyright (c) 2011, 2018, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26/*
27   Hierarchical storage layout:
28
29   <dict>
30     <key>/</key>
31     <dict>
32       <key>foo</key>
33       <string>/foo's value</string>
34       <key>foo/</key>
35       <dict>
36         <key>bar</key>
37         <string>/foo/bar's value</string>
38       </dict>
39     </dict>
40   </dict>
41
42   Java pref nodes are stored in several different files. Pref nodes
43   with at least three components in the node name (e.g. /com/MyCompany/MyApp/)
44   are stored in a CF prefs file with the first three components as the name.
45   This way, all preferences for MyApp end up in com.MyCompany.MyApp.plist .
46   Pref nodes with shorter names are stored in com.apple.java.util.prefs.plist
47
48   The filesystem is assumed to be case-insensitive (like HFS+).
49   Java pref node names are case-sensitive. If two pref node names differ
50   only in case, they may end up in the same pref file. This is ok
51   because the CF keys identifying the node span the entire absolute path
52   to the node and are case-sensitive.
53
54   Java node names may contain '.' . When mapping to the CF file name,
55   these dots are left as-is, even though '/' is mapped to '.' .
56   This is ok because the CF key contains the correct node name.
57*/
58
59
60
61#include <CoreFoundation/CoreFoundation.h>
62
63#include "jni_util.h"
64#include "jlong.h"
65#include "jvm.h"
66#include "java_util_prefs_MacOSXPreferencesFile.h"
67
68/*
69 * Declare library specific JNI_Onload entry if static build
70 */
71DEF_STATIC_JNI_OnLoad
72
73
74// Throw an OutOfMemoryError with the given message.
75static void throwOutOfMemoryError(JNIEnv *env, const char *msg)
76{
77    static jclass exceptionClass = NULL;
78    jclass c;
79
80    (*env)->ExceptionClear(env);  // If an exception is pending, clear it before
81                                  // calling FindClass() and/or ThrowNew().
82    if (exceptionClass) {
83        c = exceptionClass;
84    } else {
85        c = (*env)->FindClass(env, "java/lang/OutOfMemoryError");
86        if ((*env)->ExceptionOccurred(env)) return;
87        exceptionClass = (*env)->NewGlobalRef(env, c);
88    }
89
90    (*env)->ThrowNew(env, c, msg);
91}
92
93
94// throwIfNull macro
95// If var is NULL, throw an OutOfMemoryError and goto badvar.
96// var must be a variable. env must be the current JNIEnv.
97// fixme throw BackingStoreExceptions sometimes?
98#define throwIfNull(var, msg) \
99    do { \
100        if (var == NULL) { \
101            throwOutOfMemoryError(env, msg); \
102            goto bad##var; \
103        } \
104    } while (0)
105
106
107// Converts CFNumber, CFBoolean, CFString to CFString
108// returns NULL if value is of some other type
109// throws and returns NULL on memory error
110// result must be released (even if value was already a CFStringRef)
111// value must not be null
112static CFStringRef copyToCFString(JNIEnv *env, CFTypeRef value)
113{
114    CFStringRef result;
115    CFTypeID type;
116
117    type = CFGetTypeID(value);
118
119    if (type == CFStringGetTypeID()) {
120        result = (CFStringRef)CFRetain(value);
121    }
122    else if (type == CFBooleanGetTypeID()) {
123        // Java Preferences API expects "true" and "false" for boolean values.
124        result = CFStringCreateCopy(NULL, (value == kCFBooleanTrue) ? CFSTR("true") : CFSTR("false"));
125        throwIfNull(result, "copyToCFString failed");
126    }
127    else if (type == CFNumberGetTypeID()) {
128        CFNumberRef number = (CFNumberRef) value;
129        if (CFNumberIsFloatType(number)) {
130            double d;
131            CFNumberGetValue(number, kCFNumberDoubleType, &d);
132            result = CFStringCreateWithFormat(NULL, NULL, CFSTR("%g"), d);
133            throwIfNull(result, "copyToCFString failed");
134        }
135        else {
136            long l;
137            CFNumberGetValue(number, kCFNumberLongType, &l);
138            result = CFStringCreateWithFormat(NULL, NULL, CFSTR("%ld"), l);
139            throwIfNull(result, "copyToCFString failed");
140        }
141    }
142    else {
143        // unknown type - return NULL
144        result = NULL;
145    }
146
147 badresult:
148    return result;
149}
150
151
152// Create a Java string from the given CF string.
153// returns NULL if cfString is NULL
154// throws and returns NULL on memory error
155static jstring toJavaString(JNIEnv *env, CFStringRef cfString)
156{
157    if (cfString == NULL) {
158        return NULL;
159    } else {
160        jstring javaString = NULL;
161
162        CFIndex length = CFStringGetLength(cfString);
163        const UniChar *constchars = CFStringGetCharactersPtr(cfString);
164        if (constchars) {
165            javaString = (*env)->NewString(env, constchars, length);
166        } else {
167            UniChar *chars = malloc(length * sizeof(UniChar));
168            throwIfNull(chars, "toJavaString failed");
169            CFStringGetCharacters(cfString, CFRangeMake(0, length), chars);
170            javaString = (*env)->NewString(env, chars, length);
171            free(chars);
172        }
173    badchars:
174        return javaString;
175    }
176}
177
178
179
180// Create a CF string from the given Java string.
181// returns NULL if javaString is NULL
182// throws and returns NULL on memory error
183static CFStringRef toCF(JNIEnv *env, jstring javaString)
184{
185    if (javaString == NULL) {
186        return NULL;
187    } else {
188        CFStringRef result = NULL;
189        jsize length = (*env)->GetStringLength(env, javaString);
190        const jchar *chars = (*env)->GetStringChars(env, javaString, NULL);
191        throwIfNull(chars, "toCF failed");
192        result =
193            CFStringCreateWithCharacters(NULL, (const UniChar *)chars, length);
194        (*env)->ReleaseStringChars(env, javaString, chars);
195        throwIfNull(result, "toCF failed");
196    badchars:
197    badresult:
198        return result;
199    }
200}
201
202
203// Create an empty Java string array of the given size.
204// Throws and returns NULL on error.
205static jarray createJavaStringArray(JNIEnv *env, CFIndex count)
206{
207    static jclass stringClass = NULL;
208    jclass c;
209
210    if (stringClass) {
211        c = stringClass;
212    } else {
213        c = (*env)->FindClass(env, "java/lang/String");
214        if ((*env)->ExceptionOccurred(env)) return NULL;
215        stringClass = (*env)->NewGlobalRef(env, c);
216    }
217
218    return (*env)->NewObjectArray(env, count, c, NULL); // AWT_THREADING Safe (known object)
219}
220
221
222// Java accessors for CF constants.
223JNIEXPORT jlong JNICALL
224Java_java_util_prefs_MacOSXPreferencesFile_currentUser(JNIEnv *env,
225                                                       jobject klass)
226{
227    return ptr_to_jlong(kCFPreferencesCurrentUser);
228}
229
230JNIEXPORT jlong JNICALL
231Java_java_util_prefs_MacOSXPreferencesFile_anyUser(JNIEnv *env, jobject klass)
232{
233    return ptr_to_jlong(kCFPreferencesAnyUser);
234}
235
236JNIEXPORT jlong JNICALL
237Java_java_util_prefs_MacOSXPreferencesFile_currentHost(JNIEnv *env,
238                                                       jobject klass)
239{
240    return ptr_to_jlong(kCFPreferencesCurrentHost);
241}
242
243JNIEXPORT jlong JNICALL
244Java_java_util_prefs_MacOSXPreferencesFile_anyHost(JNIEnv *env, jobject klass)
245{
246    return ptr_to_jlong(kCFPreferencesAnyHost);
247}
248
249
250// Create an empty node.
251// Does not store the node in any prefs file.
252// returns NULL on memory error
253static CFMutableDictionaryRef createEmptyNode(void)
254{
255    return CFDictionaryCreateMutable(NULL, 0,
256                                     &kCFTypeDictionaryKeyCallBacks,
257                                     &kCFTypeDictionaryValueCallBacks);
258}
259
260
261// Create a string that consists of path minus its last component.
262// path must end with '/'
263// The result will end in '/' (unless path itself is '/')
264static CFStringRef copyParentOf(CFStringRef path)
265{
266    CFRange searchRange;
267    CFRange slashRange;
268    CFRange parentRange;
269    Boolean found;
270
271    searchRange = CFRangeMake(0, CFStringGetLength(path) - 1);
272    found = CFStringFindWithOptions(path, CFSTR("/"), searchRange,
273                                    kCFCompareBackwards, &slashRange);
274    if (!found) return CFSTR("");
275    parentRange = CFRangeMake(0, slashRange.location + 1); // include '/'
276    return CFStringCreateWithSubstring(NULL, path, parentRange);
277}
278
279
280// Create a string that consists of path's last component.
281// path must end with '/'
282// The result will end in '/'.
283// The result will not start with '/' (unless path itself is '/')
284static CFStringRef copyChildOf(CFStringRef path)
285{
286    CFRange searchRange;
287    CFRange slashRange;
288    CFRange childRange;
289    Boolean found;
290    CFIndex length = CFStringGetLength(path);
291
292    searchRange = CFRangeMake(0, length - 1);
293    found = CFStringFindWithOptions(path, CFSTR("/"), searchRange,
294                                    kCFCompareBackwards, &slashRange);
295    if (!found) return CFSTR("");
296    childRange = CFRangeMake(slashRange.location + 1,
297                             length - slashRange.location - 1); // skip '/'
298    return CFStringCreateWithSubstring(NULL, path, childRange);
299}
300
301
302// Return the first three components of path, with leading and trailing '/'.
303// If path does not have three components, return NULL.
304// path must begin and end in '/'
305static CFStringRef copyFirstThreeComponentsOf(CFStringRef path)
306{
307    CFRange searchRange;
308    CFRange slashRange;
309    CFRange prefixRange;
310    CFStringRef prefix;
311    Boolean found;
312    CFIndex length = CFStringGetLength(path);
313
314    searchRange = CFRangeMake(1, length - 1);  // skip leading '/'
315    found = CFStringFindWithOptions(path, CFSTR("/"), searchRange, 0,
316                                    &slashRange);
317    if (!found) return NULL;  // no second slash!
318
319    searchRange = CFRangeMake(slashRange.location + 1,
320                              length - slashRange.location - 1);
321    found = CFStringFindWithOptions(path, CFSTR("/"), searchRange, 0,
322                                    &slashRange);
323    if (!found) return NULL;  // no third slash!
324
325    searchRange = CFRangeMake(slashRange.location + 1,
326                              length - slashRange.location - 1);
327    found = CFStringFindWithOptions(path, CFSTR("/"), searchRange, 0,
328                                    &slashRange);
329    if (!found) return NULL;  // no fourth slash!
330
331    prefixRange = CFRangeMake(0, slashRange.location + 1); // keep last '/'
332    prefix = CFStringCreateWithSubstring(NULL, path, prefixRange);
333
334    return prefix;
335}
336
337
338// Copy the CFPreferences key and value at the base of path's tree.
339// path must end in '/'
340// topKey or topValue may be NULL
341// Returns NULL on error or if there is no tree for path in this file.
342static void copyTreeForPath(CFStringRef path, CFStringRef name,
343                            CFStringRef user, CFStringRef host,
344                            CFStringRef *topKey, CFDictionaryRef *topValue)
345{
346    CFStringRef key;
347    CFPropertyListRef value;
348
349    if (topKey) *topKey = NULL;
350    if (topValue) *topValue = NULL;
351
352    if (CFEqual(name, CFSTR("com.apple.java.util.prefs"))) {
353        // Top-level file. Only key "/" is an acceptable root.
354        key = (CFStringRef) CFRetain(CFSTR("/"));
355    } else {
356        // Second-level file. Key must be the first three components of path.
357        key = copyFirstThreeComponentsOf(path);
358        if (!key) return;
359    }
360
361    value = CFPreferencesCopyValue(key, name, user, host);
362    if (value) {
363        if (CFGetTypeID(value) == CFDictionaryGetTypeID()) {
364            // (key, value) is acceptable
365            if (topKey) *topKey = (CFStringRef)CFRetain(key);
366            if (topValue) *topValue = (CFDictionaryRef)CFRetain(value);
367        }
368        CFRelease(value);
369    }
370    CFRelease(key);
371}
372
373
374// Find the node for path in the given tree.
375// Returns NULL on error or if path doesn't have a node in this tree.
376// path must end in '/'
377static CFDictionaryRef copyNodeInTree(CFStringRef path, CFStringRef topKey,
378                                      CFDictionaryRef topValue)
379{
380    CFMutableStringRef p;
381    CFDictionaryRef result = NULL;
382
383    p = CFStringCreateMutableCopy(NULL, 0, path);
384    if (!p) return NULL;
385    CFStringDelete(p, CFRangeMake(0, CFStringGetLength(topKey)));
386    result = topValue;
387
388    while (CFStringGetLength(p) > 0) {
389        CFDictionaryRef child;
390        CFStringRef part = NULL;
391        CFRange slashRange = CFStringFind(p, CFSTR("/"), 0);
392        // guaranteed to succeed because path must end in '/'
393        CFRange partRange = CFRangeMake(0, slashRange.location + 1);
394        part = CFStringCreateWithSubstring(NULL, p, partRange);
395        if (!part) { result = NULL; break; }
396        CFStringDelete(p, partRange);
397
398        child = CFDictionaryGetValue(result, part);
399        CFRelease(part);
400        if (child  &&  CFGetTypeID(child) == CFDictionaryGetTypeID()) {
401            // continue search
402            result = child;
403        } else {
404            // didn't find target node
405            result = NULL;
406            break;
407        }
408    }
409
410    CFRelease(p);
411    if (result) return (CFDictionaryRef)CFRetain(result);
412    else return NULL;
413}
414
415
416// Return a retained copy of the node at path from the given file.
417// path must end in '/'
418// returns NULL if node doesn't exist.
419// returns NULL if the value for key "path" isn't a valid node.
420static CFDictionaryRef copyNodeIfPresent(CFStringRef path, CFStringRef name,
421                                         CFStringRef user, CFStringRef host)
422{
423    CFStringRef topKey;
424    CFDictionaryRef topValue;
425    CFDictionaryRef result;
426
427    copyTreeForPath(path, name, user, host, &topKey, &topValue);
428    if (!topKey) return NULL;
429
430    result = copyNodeInTree(path, topKey, topValue);
431
432    CFRelease(topKey);
433    if (topValue) CFRelease(topValue);
434    return result;
435}
436
437
438// Create a new tree that would store path in the given file.
439// Only the root of the tree is created, not all of the links leading to path.
440// returns NULL on error
441static void createTreeForPath(CFStringRef path, CFStringRef name,
442                              CFStringRef user, CFStringRef host,
443                              CFStringRef *outTopKey,
444                              CFMutableDictionaryRef *outTopValue)
445{
446    *outTopKey = NULL;
447    *outTopValue = NULL;
448
449    // if name is "com.apple.java.util.prefs" then create tree "/"
450    // else create tree "/foo/bar/baz/"
451    // "com.apple.java.util.prefs.plist" is also in MacOSXPreferences.java
452    if (CFEqual(name, CFSTR("com.apple.java.util.prefs"))) {
453        *outTopKey = CFSTR("/");
454        *outTopValue = createEmptyNode();
455    } else {
456        CFStringRef prefix = copyFirstThreeComponentsOf(path);
457        if (prefix) {
458            *outTopKey = prefix;
459            *outTopValue = createEmptyNode();
460        }
461    }
462}
463
464
465// Return a mutable copy of the tree containing path and the dict for
466//   path itself. *outTopKey and *outTopValue can be used to write the
467//   modified tree back to the prefs file.
468// *outTopKey and *outTopValue must be released iff the actual return
469//   value is not NULL.
470static CFMutableDictionaryRef
471copyMutableNode(CFStringRef path, CFStringRef name,
472                CFStringRef user, CFStringRef host,
473                CFStringRef *outTopKey,
474                CFMutableDictionaryRef *outTopValue)
475{
476    CFStringRef topKey = NULL;
477    CFDictionaryRef oldTopValue = NULL;
478    CFMutableDictionaryRef topValue;
479    CFMutableDictionaryRef result = NULL;
480    CFMutableStringRef p;
481
482    if (outTopKey) *outTopKey = NULL;
483    if (outTopValue) *outTopValue = NULL;
484
485    copyTreeForPath(path, name, user, host, &topKey, &oldTopValue);
486    if (!topKey) {
487        createTreeForPath(path, name, user, host, &topKey, &topValue);
488    } else {
489        topValue = (CFMutableDictionaryRef)
490            CFPropertyListCreateDeepCopy(NULL, (CFPropertyListRef)oldTopValue,
491                                         kCFPropertyListMutableContainers);
492    }
493    if (!topValue) goto badtopValue;
494
495    p = CFStringCreateMutableCopy(NULL, 0, path);
496    if (!p) goto badp;
497    CFStringDelete(p, CFRangeMake(0, CFStringGetLength(topKey)));
498    result = topValue;
499
500    while (CFStringGetLength(p) > 0) {
501        CFMutableDictionaryRef child;
502        CFStringRef part = NULL;
503        CFRange slashRange = CFStringFind(p, CFSTR("/"), 0);
504        // guaranteed to succeed because path must end in '/'
505        CFRange partRange = CFRangeMake(0, slashRange.location + 1);
506        part = CFStringCreateWithSubstring(NULL, p, partRange);
507        if (!part) { result = NULL; break; }
508        CFStringDelete(p, partRange);
509
510        child = (CFMutableDictionaryRef)CFDictionaryGetValue(result, part);
511        if (child  &&  CFGetTypeID(child) == CFDictionaryGetTypeID()) {
512            // continue search
513            result = child;
514        } else {
515            // didn't find target node - add it and continue
516            child = createEmptyNode();
517            if (!child) { CFRelease(part); result = NULL; break; }
518            CFDictionaryAddValue(result, part, child);
519            result = child;
520        }
521        CFRelease(part);
522    }
523
524    if (result) {
525        *outTopKey = (CFStringRef)CFRetain(topKey);
526        *outTopValue = (CFMutableDictionaryRef)CFRetain(topValue);
527        CFRetain(result);
528    }
529
530    CFRelease(p);
531 badp:
532    CFRelease(topValue);
533 badtopValue:
534    if (topKey) CFRelease(topKey);
535    if (oldTopValue) CFRelease(oldTopValue);
536    return result;
537}
538
539
540JNIEXPORT jboolean JNICALL
541Java_java_util_prefs_MacOSXPreferencesFile_addNode
542(JNIEnv *env, jobject klass, jobject jpath,
543 jobject jname, jlong juser, jlong jhost)
544{
545    CFStringRef path = NULL;
546    CFStringRef name = NULL;
547
548    path = toCF(env, jpath);
549    if (path != NULL) {
550        name = toCF(env, jname);
551    }
552    CFStringRef user = (CFStringRef)jlong_to_ptr(juser);
553    CFStringRef host = (CFStringRef)jlong_to_ptr(jhost);
554    CFDictionaryRef node = NULL;
555    jboolean neededNewNode = false;
556
557    if (!path  ||  !name) goto badparams;
558
559    node = copyNodeIfPresent(path, name, user, host);
560
561    if (node) {
562        neededNewNode = false;
563        CFRelease(node);
564    } else {
565        CFStringRef topKey = NULL;
566        CFMutableDictionaryRef topValue = NULL;
567
568        neededNewNode = true;
569
570        // copyMutableNode creates the node if necessary
571        node = copyMutableNode(path, name, user, host, &topKey, &topValue);
572        throwIfNull(node, "copyMutableNode failed");
573
574        CFPreferencesSetValue(topKey, topValue, name, user, host);
575
576        CFRelease(node);
577        if (topKey) CFRelease(topKey);
578        if (topValue) CFRelease(topValue);
579    }
580
581 badnode:
582 badparams:
583    if (path) CFRelease(path);
584    if (name) CFRelease(name);
585
586    return neededNewNode;
587}
588
589
590JNIEXPORT void JNICALL
591Java_java_util_prefs_MacOSXPreferencesFile_removeNode
592(JNIEnv *env, jobject klass, jobject jpath,
593 jobject jname, jlong juser, jlong jhost)
594{
595    CFStringRef path = NULL;
596    CFStringRef name = NULL;
597
598    path = toCF(env, jpath);
599    if (path != NULL) {
600        name = toCF(env, jname);
601    }
602    CFStringRef user = (CFStringRef)jlong_to_ptr(juser);
603    CFStringRef host = (CFStringRef)jlong_to_ptr(jhost);
604    CFStringRef parentName;
605    CFStringRef childName;
606    CFDictionaryRef constParent;
607
608    if (!path  ||  !name) goto badparams;
609
610    parentName = copyParentOf(path);
611    throwIfNull(parentName, "copyParentOf failed");
612    childName  = copyChildOf(path);
613    throwIfNull(childName, "copyChildOf failed");
614
615    // root node is not allowed to be removed, so parentName is never empty
616
617    constParent = copyNodeIfPresent(parentName, name, user, host);
618    if (constParent  &&  CFDictionaryContainsKey(constParent, childName)) {
619        CFStringRef topKey;
620        CFMutableDictionaryRef topValue;
621        CFMutableDictionaryRef parent;
622
623        parent = copyMutableNode(parentName, name, user, host,
624                                 &topKey, &topValue);
625        throwIfNull(parent, "copyMutableNode failed");
626
627        CFDictionaryRemoveValue(parent, childName);
628        CFPreferencesSetValue(topKey, topValue, name, user, host);
629
630        CFRelease(parent);
631        if (topKey) CFRelease(topKey);
632        if (topValue) CFRelease(topValue);
633    } else {
634        // might be trying to remove the root itself in a non-root file
635        CFStringRef topKey;
636        CFDictionaryRef topValue;
637        copyTreeForPath(path, name, user, host, &topKey, &topValue);
638        if (topKey) {
639            if (CFEqual(topKey, path)) {
640                CFPreferencesSetValue(topKey, NULL, name, user, host);
641            }
642
643            if (topKey) CFRelease(topKey);
644            if (topValue) CFRelease(topValue);
645        }
646    }
647
648
649 badparent:
650    if (constParent) CFRelease(constParent);
651    CFRelease(childName);
652 badchildName:
653    CFRelease(parentName);
654 badparentName:
655 badparams:
656    if (path) CFRelease(path);
657    if (name) CFRelease(name);
658}
659
660
661// child must end with '/'
662JNIEXPORT Boolean JNICALL
663Java_java_util_prefs_MacOSXPreferencesFile_addChildToNode
664(JNIEnv *env, jobject klass, jobject jpath, jobject jchild,
665 jobject jname, jlong juser, jlong jhost)
666{
667    // like addNode, but can put a three-level-deep dict into the root file
668    CFStringRef path = NULL;
669    CFStringRef child = NULL;
670    CFStringRef name = NULL;
671
672    path = toCF(env, jpath);
673    if (path != NULL) {
674        child = toCF(env, jchild);
675    }
676    if (child != NULL) {
677        name = toCF(env, jname);
678    }
679    CFStringRef user = (CFStringRef)jlong_to_ptr(juser);
680    CFStringRef host = (CFStringRef)jlong_to_ptr(jhost);
681    CFMutableDictionaryRef parent;
682    CFDictionaryRef node;
683    CFStringRef topKey;
684    CFMutableDictionaryRef topValue;
685    Boolean beforeAdd = false;
686
687    if (!path  ||  !child  ||  !name) goto badparams;
688
689    node = createEmptyNode();
690    throwIfNull(node, "createEmptyNode failed");
691
692    // copyMutableNode creates the node if necessary
693    parent = copyMutableNode(path, name, user, host, &topKey, &topValue);
694    throwIfNull(parent, "copyMutableNode failed");
695    beforeAdd = CFDictionaryContainsKey(parent, child);
696    CFDictionaryAddValue(parent, child, node);
697    if (!beforeAdd)
698        beforeAdd = CFDictionaryContainsKey(parent, child);
699    else
700        beforeAdd = false;
701    CFPreferencesSetValue(topKey, topValue, name, user, host);
702
703    CFRelease(parent);
704    if (topKey) CFRelease(topKey);
705    if (topValue) CFRelease(topValue);
706 badparent:
707    CFRelease(node);
708 badnode:
709 badparams:
710    if (path) CFRelease(path);
711    if (child) CFRelease(child);
712    if (name) CFRelease(name);
713    return beforeAdd;
714}
715
716
717JNIEXPORT void JNICALL
718Java_java_util_prefs_MacOSXPreferencesFile_removeChildFromNode
719(JNIEnv *env, jobject klass, jobject jpath, jobject jchild,
720 jobject jname, jlong juser, jlong jhost)
721{
722    CFStringRef path = NULL;
723    CFStringRef child = NULL;
724    CFStringRef name = NULL;
725
726    path = toCF(env, jpath);
727    if (path != NULL) {
728        child = toCF(env, jchild);
729    }
730    if (child != NULL) {
731        name = toCF(env, jname);
732    }
733    CFStringRef user = (CFStringRef)jlong_to_ptr(juser);
734    CFStringRef host = (CFStringRef)jlong_to_ptr(jhost);
735    CFDictionaryRef constParent;
736
737    if (!path  ||  !child  ||  !name) goto badparams;
738
739    constParent = copyNodeIfPresent(path, name, user, host);
740    if (constParent  &&  CFDictionaryContainsKey(constParent, child)) {
741        CFStringRef topKey;
742        CFMutableDictionaryRef topValue;
743        CFMutableDictionaryRef parent;
744
745        parent = copyMutableNode(path, name, user, host, &topKey, &topValue);
746        throwIfNull(parent, "copyMutableNode failed");
747
748        CFDictionaryRemoveValue(parent, child);
749        CFPreferencesSetValue(topKey, topValue, name, user, host);
750
751        CFRelease(parent);
752        if (topKey) CFRelease(topKey);
753        if (topValue) CFRelease(topValue);
754    }
755
756 badparent:
757    if (constParent) CFRelease(constParent);
758 badparams:
759    if (path) CFRelease(path);
760    if (child) CFRelease(child);
761    if (name) CFRelease(name);
762}
763
764
765
766JNIEXPORT void JNICALL
767Java_java_util_prefs_MacOSXPreferencesFile_addKeyToNode
768(JNIEnv *env, jobject klass, jobject jpath, jobject jkey, jobject jvalue,
769 jobject jname, jlong juser, jlong jhost)
770{
771    CFStringRef path = NULL;
772    CFStringRef key = NULL;
773    CFStringRef value = NULL;
774    CFStringRef name = NULL;
775
776    path = toCF(env, jpath);
777    if (path != NULL) {
778        key = toCF(env, jkey);
779    }
780    if (key != NULL) {
781        value = toCF(env, jvalue);
782    }
783    if (value != NULL) {
784        name = toCF(env, jname);
785    }
786    CFStringRef user = (CFStringRef)jlong_to_ptr(juser);
787    CFStringRef host = (CFStringRef)jlong_to_ptr(jhost);
788    CFMutableDictionaryRef node = NULL;
789    CFStringRef topKey;
790    CFMutableDictionaryRef topValue;
791
792    if (!path  ||  !key  || !value  ||  !name) goto badparams;
793
794    // fixme optimization: check whether old value and new value are identical
795    node = copyMutableNode(path, name, user, host, &topKey, &topValue);
796    throwIfNull(node, "copyMutableNode failed");
797
798    CFDictionarySetValue(node, key, value);
799    CFPreferencesSetValue(topKey, topValue, name, user, host);
800
801    CFRelease(node);
802    if (topKey) CFRelease(topKey);
803    if (topValue) CFRelease(topValue);
804
805 badnode:
806 badparams:
807    if (path) CFRelease(path);
808    if (key) CFRelease(key);
809    if (value) CFRelease(value);
810    if (name) CFRelease(name);
811}
812
813
814JNIEXPORT void JNICALL
815Java_java_util_prefs_MacOSXPreferencesFile_removeKeyFromNode
816(JNIEnv *env, jobject klass, jobject jpath, jobject jkey,
817 jobject jname, jlong juser, jlong jhost)
818{
819    CFStringRef path = NULL;
820    CFStringRef key = NULL;
821    CFStringRef name = NULL;
822
823    path = toCF(env, jpath);
824    if (path != NULL) {
825        key = toCF(env, jkey);
826    }
827    if (key != NULL) {
828        name = toCF(env, jname);
829    }
830    CFStringRef user = (CFStringRef)jlong_to_ptr(juser);
831    CFStringRef host = (CFStringRef)jlong_to_ptr(jhost);
832    CFDictionaryRef constNode;
833
834    if (!path  ||  !key  ||  !name) goto badparams;
835
836    constNode = copyNodeIfPresent(path, name, user, host);
837    if (constNode  &&  CFDictionaryContainsKey(constNode, key)) {
838        CFStringRef topKey;
839        CFMutableDictionaryRef topValue;
840        CFMutableDictionaryRef node;
841
842        node = copyMutableNode(path, name, user, host, &topKey, &topValue);
843        throwIfNull(node, "copyMutableNode failed");
844
845        CFDictionaryRemoveValue(node, key);
846        CFPreferencesSetValue(topKey, topValue, name, user, host);
847
848        CFRelease(node);
849        if (topKey) CFRelease(topKey);
850        if (topValue) CFRelease(topValue);
851    }
852
853 badnode:
854    if (constNode) CFRelease(constNode);
855 badparams:
856    if (path) CFRelease(path);
857    if (key) CFRelease(key);
858    if (name) CFRelease(name);
859}
860
861
862// path must end in '/'
863JNIEXPORT jstring JNICALL
864Java_java_util_prefs_MacOSXPreferencesFile_getKeyFromNode
865(JNIEnv *env, jobject klass, jobject jpath, jobject jkey,
866 jobject jname, jlong juser, jlong jhost)
867{
868    CFStringRef path = NULL;
869    CFStringRef key = NULL;
870    CFStringRef name = NULL;
871
872    path = toCF(env, jpath);
873    if (path != NULL) {
874        key = toCF(env, jkey);
875    }
876    if (key != NULL) {
877        name = toCF(env, jname);
878    }
879    CFStringRef user = (CFStringRef)jlong_to_ptr(juser);
880    CFStringRef host = (CFStringRef)jlong_to_ptr(jhost);
881    CFPropertyListRef value;
882    CFDictionaryRef node;
883    jstring result = NULL;
884
885    if (!path  ||  !key  ||  !name) goto badparams;
886
887    node = copyNodeIfPresent(path, name, user, host);
888    if (node) {
889        value = (CFPropertyListRef)CFDictionaryGetValue(node, key);
890        if (!value) {
891            // key doesn't exist, or other error - no Java errors available
892            result = NULL;
893        } else {
894            CFStringRef cfString = copyToCFString(env, value);
895            if ((*env)->ExceptionOccurred(env)) {
896                // memory error in copyToCFString
897                result = NULL;
898            } else if (cfString == NULL) {
899                // bogus value type in prefs file - no Java errors available
900                result = NULL;
901            } else {
902                // good cfString
903                result = toJavaString(env, cfString);
904                CFRelease(cfString);
905            }
906        }
907        CFRelease(node);
908    }
909
910 badparams:
911    if (path) CFRelease(path);
912    if (key) CFRelease(key);
913    if (name) CFRelease(name);
914
915    return result;
916}
917
918
919typedef struct {
920    jarray result;
921    JNIEnv *env;
922    CFIndex used;
923    Boolean allowSlash;
924} BuildJavaArrayArgs;
925
926// CFDictionary applier function that builds an array of Java strings
927//   from a CFDictionary of CFPropertyListRefs.
928// If args->allowSlash, only strings that end in '/' are added to the array,
929//   with the slash removed. Otherwise, only strings that do not end in '/'
930//   are added.
931// args->result must already exist and be large enough to hold all
932//   strings from the dictionary.
933// After complete application, args->result may not be full because
934//   some of the dictionary values weren't convertible to string. In
935//   this case, args->used will be the count of used elements.
936static void BuildJavaArrayFn(const void *key, const void *value, void *context)
937{
938    BuildJavaArrayArgs *args = (BuildJavaArrayArgs *)context;
939    CFPropertyListRef propkey = (CFPropertyListRef)key;
940    CFStringRef cfString = NULL;
941    JNIEnv *env = args->env;
942
943    if ((*env)->ExceptionOccurred(env)) return; // already failed
944
945    cfString = copyToCFString(env, propkey);
946    if ((*env)->ExceptionOccurred(env)) {
947        // memory error in copyToCFString
948    } else if (!cfString) {
949        // bogus value type in prefs file - no Java errors available
950    } else if (args->allowSlash != CFStringHasSuffix(cfString, CFSTR("/"))) {
951        // wrong suffix - ignore
952    } else {
953        // good cfString
954        jstring javaString;
955        if (args->allowSlash) {
956            CFRange range = CFRangeMake(0, CFStringGetLength(cfString) - 1);
957            CFStringRef s = CFStringCreateWithSubstring(NULL, cfString, range);
958            CFRelease(cfString);
959            cfString = s;
960        }
961        if (CFStringGetLength(cfString) <= 0) goto bad; // ignore empty
962        javaString = toJavaString(env, cfString);
963        if ((*env)->ExceptionOccurred(env)) goto bad;
964        (*env)->SetObjectArrayElement(env, args->result,args->used,javaString);
965        if ((*env)->ExceptionOccurred(env)) goto bad;
966        args->used++;
967    }
968
969 bad:
970    if (cfString) CFRelease(cfString);
971}
972
973
974static jarray getStringsForNode(JNIEnv *env, jobject klass, jobject jpath,
975                                jobject jname, jlong juser, jlong jhost,
976                                Boolean allowSlash)
977{
978    CFStringRef path = NULL;
979    CFStringRef name = NULL;
980
981    path = toCF(env, jpath);
982    if (path != NULL) {
983        name = toCF(env, jname);
984    }
985    CFStringRef user = (CFStringRef)jlong_to_ptr(juser);
986    CFStringRef host = (CFStringRef)jlong_to_ptr(jhost);
987    CFDictionaryRef node;
988    jarray result = NULL;
989    CFIndex count;
990
991    if (!path  ||  !name) goto badparams;
992
993    node = copyNodeIfPresent(path, name, user, host);
994    if (!node) {
995        result = createJavaStringArray(env, 0);
996    } else {
997        count = CFDictionaryGetCount(node);
998        result = createJavaStringArray(env, count);
999        if (result) {
1000            BuildJavaArrayArgs args;
1001            args.result = result;
1002            args.env = env;
1003            args.used = 0;
1004            args.allowSlash = allowSlash;
1005            CFDictionaryApplyFunction(node, BuildJavaArrayFn, &args);
1006            if (!(*env)->ExceptionOccurred(env)) {
1007                // array construction succeeded
1008                if (args.used < count) {
1009                    // finished array is smaller than expected.
1010                    // Make a new array of precisely the right size.
1011                    jarray newresult = createJavaStringArray(env, args.used);
1012                    if (newresult) {
1013                        JVM_ArrayCopy(env,0, result,0, newresult,0, args.used);
1014                        result = newresult;
1015                    }
1016                }
1017            }
1018        }
1019
1020        CFRelease(node);
1021    }
1022
1023 badparams:
1024    if (path) CFRelease(path);
1025    if (name) CFRelease(name);
1026
1027    return result;
1028}
1029
1030
1031JNIEXPORT jarray JNICALL
1032Java_java_util_prefs_MacOSXPreferencesFile_getKeysForNode
1033(JNIEnv *env, jobject klass, jobject jpath,
1034 jobject jname, jlong juser, jlong jhost)
1035{
1036    return getStringsForNode(env, klass, jpath, jname, juser, jhost, false);
1037}
1038
1039JNIEXPORT jarray JNICALL
1040Java_java_util_prefs_MacOSXPreferencesFile_getChildrenForNode
1041(JNIEnv *env, jobject klass, jobject jpath,
1042 jobject jname, jlong juser, jlong jhost)
1043{
1044    return getStringsForNode(env, klass, jpath, jname, juser, jhost, true);
1045}
1046
1047
1048// Returns false on error instead of throwing.
1049JNIEXPORT jboolean JNICALL
1050Java_java_util_prefs_MacOSXPreferencesFile_synchronize
1051(JNIEnv *env, jobject klass,
1052 jstring jname, jlong juser, jlong jhost)
1053{
1054    CFStringRef name = toCF(env, jname);
1055    CFStringRef user = (CFStringRef)jlong_to_ptr(juser);
1056    CFStringRef host = (CFStringRef)jlong_to_ptr(jhost);
1057    jboolean result = 0;
1058
1059    if (name) {
1060        result = CFPreferencesSynchronize(name, user, host);
1061        CFRelease(name);
1062    }
1063
1064    return result;
1065}
1066