1 /* 2 * Copyright (c) 2011, 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 void 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 660 if (!path || !child || !name) goto badparams; 661 662 node = createEmptyNode(); 663 throwIfNull(node, "createEmptyNode failed"); 664 665 // copyMutableNode creates the node if necessary 666 parent = copyMutableNode(path, name, user, host, &topKey, &topValue); 667 throwIfNull(parent, "copyMutableNode failed"); 668 669 CFDictionaryAddValue(parent, child, node); 670 671 CFPreferencesSetValue(topKey, topValue, name, user, host); 672 673 CFRelease(parent); 674 if (topKey) CFRelease(topKey); 675 if (topValue) CFRelease(topValue); 676 badparent: 677 CFRelease(node); 678 badnode: 679 badparams: 680 if (path) CFRelease(path); 681 if (child) CFRelease(child); 682 if (name) CFRelease(name); 683 } 684 685 686 JNIEXPORT void JNICALL 687 Java_java_util_prefs_MacOSXPreferencesFile_removeChildFromNode 688 (JNIEnv *env, jobject klass, jobject jpath, jobject jchild, 689 jobject jname, jlong juser, jlong jhost) 690 { 691 CFStringRef path = toCF(env, jpath); 692 CFStringRef child = toCF(env, jchild); 693 CFStringRef name = toCF(env, jname); 694 CFStringRef user = (CFStringRef)jlong_to_ptr(juser); 695 CFStringRef host = (CFStringRef)jlong_to_ptr(jhost); 696 CFDictionaryRef constParent; 697 698 if (!path || !child || !name) goto badparams; 699 700 constParent = copyNodeIfPresent(path, name, user, host); 701 if (constParent && CFDictionaryContainsKey(constParent, child)) { 702 CFStringRef topKey; 703 CFMutableDictionaryRef topValue; 704 CFMutableDictionaryRef parent; 705 706 parent = copyMutableNode(path, name, user, host, &topKey, &topValue); 707 throwIfNull(parent, "copyMutableNode failed"); 708 709 CFDictionaryRemoveValue(parent, child); 710 CFPreferencesSetValue(topKey, topValue, name, user, host); 711 712 CFRelease(parent); 713 if (topKey) CFRelease(topKey); 714 if (topValue) CFRelease(topValue); 715 } 716 717 badparent: 718 if (constParent) CFRelease(constParent); 719 badparams: 720 if (path) CFRelease(path); 721 if (child) CFRelease(child); 722 if (name) CFRelease(name); 723 } 724 725 726 727 JNIEXPORT void JNICALL 728 Java_java_util_prefs_MacOSXPreferencesFile_addKeyToNode 729 (JNIEnv *env, jobject klass, jobject jpath, jobject jkey, jobject jvalue, 730 jobject jname, jlong juser, jlong jhost) 731 { 732 CFStringRef path = toCF(env, jpath); 733 CFStringRef key = toCF(env, jkey); 734 CFStringRef value = toCF(env, jvalue); 735 CFStringRef name = toCF(env, jname); 736 CFStringRef user = (CFStringRef)jlong_to_ptr(juser); 737 CFStringRef host = (CFStringRef)jlong_to_ptr(jhost); 738 CFMutableDictionaryRef node = NULL; 739 CFStringRef topKey; 740 CFMutableDictionaryRef topValue; 741 742 if (!path || !key || !value || !name) goto badparams; 743 744 // fixme optimization: check whether old value and new value are identical 745 node = copyMutableNode(path, name, user, host, &topKey, &topValue); 746 throwIfNull(node, "copyMutableNode failed"); 747 748 CFDictionarySetValue(node, key, value); 749 CFPreferencesSetValue(topKey, topValue, name, user, host); 750 751 CFRelease(node); 752 if (topKey) CFRelease(topKey); 753 if (topValue) CFRelease(topValue); 754 755 badnode: 756 badparams: 757 if (path) CFRelease(path); 758 if (key) CFRelease(key); 759 if (value) CFRelease(value); 760 if (name) CFRelease(name); 761 } 762 763 764 JNIEXPORT void JNICALL 765 Java_java_util_prefs_MacOSXPreferencesFile_removeKeyFromNode 766 (JNIEnv *env, jobject klass, jobject jpath, jobject jkey, 767 jobject jname, jlong juser, jlong jhost) 768 { 769 CFStringRef path = toCF(env, jpath); 770 CFStringRef key = toCF(env, jkey); 771 CFStringRef name = toCF(env, jname); 772 CFStringRef user = (CFStringRef)jlong_to_ptr(juser); 773 CFStringRef host = (CFStringRef)jlong_to_ptr(jhost); 774 CFDictionaryRef constNode; 775 776 if (!path || !key || !name) goto badparams; 777 778 constNode = copyNodeIfPresent(path, name, user, host); 779 if (constNode && CFDictionaryContainsKey(constNode, key)) { 780 CFStringRef topKey; 781 CFMutableDictionaryRef topValue; 782 CFMutableDictionaryRef node; 783 784 node = copyMutableNode(path, name, user, host, &topKey, &topValue); 785 throwIfNull(node, "copyMutableNode failed"); 786 787 CFDictionaryRemoveValue(node, key); 788 CFPreferencesSetValue(topKey, topValue, name, user, host); 789 790 CFRelease(node); 791 if (topKey) CFRelease(topKey); 792 if (topValue) CFRelease(topValue); 793 } 794 795 badnode: 796 if (constNode) CFRelease(constNode); 797 badparams: 798 if (path) CFRelease(path); 799 if (key) CFRelease(key); 800 if (name) CFRelease(name); 801 } 802 803 804 // path must end in '/' 805 JNIEXPORT jstring JNICALL 806 Java_java_util_prefs_MacOSXPreferencesFile_getKeyFromNode 807 (JNIEnv *env, jobject klass, jobject jpath, jobject jkey, 808 jobject jname, jlong juser, jlong jhost) 809 { 810 CFStringRef path = toCF(env, jpath); 811 CFStringRef key = toCF(env, jkey); 812 CFStringRef name = toCF(env, jname); 813 CFStringRef user = (CFStringRef)jlong_to_ptr(juser); 814 CFStringRef host = (CFStringRef)jlong_to_ptr(jhost); 815 CFPropertyListRef value; 816 CFDictionaryRef node; 817 jstring result = NULL; 818 819 if (!path || !key || !name) goto badparams; 820 821 node = copyNodeIfPresent(path, name, user, host); 822 if (node) { 823 value = (CFPropertyListRef)CFDictionaryGetValue(node, key); 824 if (!value) { 825 // key doesn't exist, or other error - no Java errors available 826 result = NULL; 827 } else { 828 CFStringRef cfString = copyToCFString(env, value); 829 if ((*env)->ExceptionOccurred(env)) { 830 // memory error in copyToCFString 831 result = NULL; 832 } else if (cfString == NULL) { 833 // bogus value type in prefs file - no Java errors available 834 result = NULL; 835 } else { 836 // good cfString 837 result = toJavaString(env, cfString); 838 CFRelease(cfString); 839 } 840 } 841 CFRelease(node); 842 } 843 844 badparams: 845 if (path) CFRelease(path); 846 if (key) CFRelease(key); 847 if (name) CFRelease(name); 848 849 return result; 850 } 851 852 853 typedef struct { 854 jarray result; 855 JNIEnv *env; 856 CFIndex used; 857 Boolean allowSlash; 858 } BuildJavaArrayArgs; 859 860 // CFDictionary applier function that builds an array of Java strings 861 // from a CFDictionary of CFPropertyListRefs. 862 // If args->allowSlash, only strings that end in '/' are added to the array, 863 // with the slash removed. Otherwise, only strings that do not end in '/' 864 // are added. 865 // args->result must already exist and be large enough to hold all 866 // strings from the dictionary. 867 // After complete application, args->result may not be full because 868 // some of the dictionary values weren't convertible to string. In 869 // this case, args->used will be the count of used elements. 870 static void BuildJavaArrayFn(const void *key, const void *value, void *context) 871 { 872 BuildJavaArrayArgs *args = (BuildJavaArrayArgs *)context; 873 CFPropertyListRef propkey = (CFPropertyListRef)key; 874 CFStringRef cfString = NULL; 875 JNIEnv *env = args->env; 876 877 if ((*env)->ExceptionOccurred(env)) return; // already failed 878 879 cfString = copyToCFString(env, propkey); 880 if ((*env)->ExceptionOccurred(env)) { 881 // memory error in copyToCFString 882 } else if (!cfString) { 883 // bogus value type in prefs file - no Java errors available 884 } else if (args->allowSlash != CFStringHasSuffix(cfString, CFSTR("/"))) { 885 // wrong suffix - ignore 886 } else { 887 // good cfString 888 jstring javaString; 889 if (args->allowSlash) { 890 CFRange range = CFRangeMake(0, CFStringGetLength(cfString) - 1); 891 CFStringRef s = CFStringCreateWithSubstring(NULL, cfString, range); 892 CFRelease(cfString); 893 cfString = s; 894 } 895 if (CFStringGetLength(cfString) <= 0) goto bad; // ignore empty 896 javaString = toJavaString(env, cfString); 897 if ((*env)->ExceptionOccurred(env)) goto bad; 898 (*env)->SetObjectArrayElement(env, args->result,args->used,javaString); 899 if ((*env)->ExceptionOccurred(env)) goto bad; 900 args->used++; 901 } 902 903 bad: 904 if (cfString) CFRelease(cfString); 905 } 906 907 908 static jarray getStringsForNode(JNIEnv *env, jobject klass, jobject jpath, 909 jobject jname, jlong juser, jlong jhost, 910 Boolean allowSlash) 911 { 912 CFStringRef path = toCF(env, jpath); 913 CFStringRef name = toCF(env, jname); 914 CFStringRef user = (CFStringRef)jlong_to_ptr(juser); 915 CFStringRef host = (CFStringRef)jlong_to_ptr(jhost); 916 CFDictionaryRef node; 917 jarray result = NULL; 918 CFIndex count; 919 920 if (!path || !name) goto badparams; 921 922 node = copyNodeIfPresent(path, name, user, host); 923 if (!node) { 924 result = createJavaStringArray(env, 0); 925 } else { 926 count = CFDictionaryGetCount(node); 927 result = createJavaStringArray(env, count); 928 if (result) { 929 BuildJavaArrayArgs args; 930 args.result = result; 931 args.env = env; 932 args.used = 0; 933 args.allowSlash = allowSlash; 934 CFDictionaryApplyFunction(node, BuildJavaArrayFn, &args); 935 if (!(*env)->ExceptionOccurred(env)) { 936 // array construction succeeded 937 if (args.used < count) { 938 // finished array is smaller than expected. 939 // Make a new array of precisely the right size. 940 jarray newresult = createJavaStringArray(env, args.used); 941 if (newresult) { 942 JVM_ArrayCopy(env,0, result,0, newresult,0, args.used); 943 result = newresult; 944 } 945 } 946 } 947 } 948 949 CFRelease(node); 950 } 951 952 badparams: 953 if (path) CFRelease(path); 954 if (name) CFRelease(name); 955 956 return result; 957 } 958 959 960 JNIEXPORT jarray JNICALL 961 Java_java_util_prefs_MacOSXPreferencesFile_getKeysForNode 962 (JNIEnv *env, jobject klass, jobject jpath, 963 jobject jname, jlong juser, jlong jhost) 964 { 965 return getStringsForNode(env, klass, jpath, jname, juser, jhost, false); 966 } 967 968 JNIEXPORT jarray JNICALL 969 Java_java_util_prefs_MacOSXPreferencesFile_getChildrenForNode 970 (JNIEnv *env, jobject klass, jobject jpath, 971 jobject jname, jlong juser, jlong jhost) 972 { 973 return getStringsForNode(env, klass, jpath, jname, juser, jhost, true); 974 } 975 976 977 // Returns false on error instead of throwing. 978 JNIEXPORT jboolean JNICALL 979 Java_java_util_prefs_MacOSXPreferencesFile_synchronize 980 (JNIEnv *env, jobject klass, 981 jstring jname, jlong juser, jlong jhost) 982 { 983 CFStringRef name = toCF(env, jname); 984 CFStringRef user = (CFStringRef)jlong_to_ptr(juser); 985 CFStringRef host = (CFStringRef)jlong_to_ptr(jhost); 986 jboolean result = 0; 987 988 if (name) { 989 result = CFPreferencesSynchronize(name, user, host); 990 CFRelease(name); 991 } 992 993 return result; 994 }