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