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