|
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 } |