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