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