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 }