Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
PojavLauncherTeam
GitHub Repository: PojavLauncherTeam/mobile
Path: blob/master/src/java.prefs/share/classes/java/util/prefs/AbstractPreferences.java
41159 views
1
/*
2
* Copyright (c) 2000, 2021, 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
package java.util.prefs;
27
28
import java.util.*;
29
import java.io.*;
30
import java.security.AccessController;
31
import java.security.PrivilegedAction;
32
// These imports needed only as a workaround for a JavaDoc bug
33
import java.lang.Integer;
34
import java.lang.Long;
35
import java.lang.Float;
36
import java.lang.Double;
37
38
/**
39
* This class provides a skeletal implementation of the {@link Preferences}
40
* class, greatly easing the task of implementing it.
41
*
42
* <p><strong>This class is for {@code Preferences} implementers only.
43
* Normal users of the {@code Preferences} facility should have no need to
44
* consult this documentation. The {@link Preferences} documentation
45
* should suffice.</strong>
46
*
47
* <p>Implementors must override the nine abstract service-provider interface
48
* (SPI) methods: {@link #getSpi(String)}, {@link #putSpi(String,String)},
49
* {@link #removeSpi(String)}, {@link #childSpi(String)}, {@link
50
* #removeNodeSpi()}, {@link #keysSpi()}, {@link #childrenNamesSpi()}, {@link
51
* #syncSpi()} and {@link #flushSpi()}. All of the concrete methods specify
52
* precisely how they are implemented atop these SPI methods. The implementor
53
* may, at his discretion, override one or more of the concrete methods if the
54
* default implementation is unsatisfactory for any reason, such as
55
* performance.
56
*
57
* <p>The SPI methods fall into three groups concerning exception
58
* behavior. The {@code getSpi} method should never throw exceptions, but it
59
* doesn't really matter, as any exception thrown by this method will be
60
* intercepted by {@link #get(String,String)}, which will return the specified
61
* default value to the caller. The {@code removeNodeSpi, keysSpi,
62
* childrenNamesSpi, syncSpi} and {@code flushSpi} methods are specified
63
* to throw {@link BackingStoreException}, and the implementation is required
64
* to throw this checked exception if it is unable to perform the operation.
65
* The exception propagates outward, causing the corresponding API method
66
* to fail.
67
*
68
* <p>The remaining SPI methods {@link #putSpi(String,String)}, {@link
69
* #removeSpi(String)} and {@link #childSpi(String)} have more complicated
70
* exception behavior. They are not specified to throw
71
* {@code BackingStoreException}, as they can generally obey their contracts
72
* even if the backing store is unavailable. This is true because they return
73
* no information and their effects are not required to become permanent until
74
* a subsequent call to {@link Preferences#flush()} or
75
* {@link Preferences#sync()}. Generally speaking, these SPI methods should not
76
* throw exceptions. In some implementations, there may be circumstances
77
* under which these calls cannot even enqueue the requested operation for
78
* later processing. Even under these circumstances it is generally better to
79
* simply ignore the invocation and return, rather than throwing an
80
* exception. Under these circumstances, however, subsequently invoking
81
* {@code flush()} or {@code sync} would not imply that all previous
82
* operations had successfully been made permanent.
83
*
84
* <p>There is one circumstance under which {@code putSpi, removeSpi and
85
* childSpi} <i>should</i> throw an exception: if the caller lacks
86
* sufficient privileges on the underlying operating system to perform the
87
* requested operation. This will, for instance, occur on most systems
88
* if a non-privileged user attempts to modify system preferences.
89
* (The required privileges will vary from implementation to
90
* implementation. On some implementations, they are the right to modify the
91
* contents of some directory in the file system; on others they are the right
92
* to modify contents of some key in a registry.) Under any of these
93
* circumstances, it would generally be undesirable to let the program
94
* continue executing as if these operations would become permanent at a later
95
* time. While implementations are not required to throw an exception under
96
* these circumstances, they are encouraged to do so. A {@link
97
* SecurityException} would be appropriate.
98
*
99
* <p>Most of the SPI methods require the implementation to read or write
100
* information at a preferences node. The implementor should beware of the
101
* fact that another VM may have concurrently deleted this node from the
102
* backing store. It is the implementation's responsibility to recreate the
103
* node if it has been deleted.
104
*
105
* <p>Implementation note: In Sun's default {@code Preferences}
106
* implementations, the user's identity is inherited from the underlying
107
* operating system and does not change for the lifetime of the virtual
108
* machine. It is recognized that server-side {@code Preferences}
109
* implementations may have the user identity change from request to request,
110
* implicitly passed to {@code Preferences} methods via the use of a
111
* static {@link ThreadLocal} instance. Authors of such implementations are
112
* <i>strongly</i> encouraged to determine the user at the time preferences
113
* are accessed (for example by the {@link #get(String,String)} or {@link
114
* #put(String,String)} method) rather than permanently associating a user
115
* with each {@code Preferences} instance. The latter behavior conflicts
116
* with normal {@code Preferences} usage and would lead to great confusion.
117
*
118
* @author Josh Bloch
119
* @see Preferences
120
* @since 1.4
121
*/
122
public abstract class AbstractPreferences extends Preferences {
123
/**
124
* The code point U+0000, assigned to the null control character, is the
125
* only character encoded in Unicode and ISO/IEC 10646 that is always
126
* invalid in any XML 1.0 and 1.1 document.
127
*/
128
static final int CODE_POINT_U0000 = '\u0000';
129
130
/**
131
* Our name relative to parent.
132
*/
133
private final String name;
134
135
/**
136
* Our absolute path name.
137
*/
138
private final String absolutePath;
139
140
/**
141
* Our parent node.
142
*/
143
final AbstractPreferences parent;
144
145
/**
146
* Our root node.
147
*/
148
private final AbstractPreferences root; // Relative to this node
149
150
/**
151
* This field should be {@code true} if this node did not exist in the
152
* backing store prior to the creation of this object. The field
153
* is initialized to false, but may be set to true by a subclass
154
* constructor (and should not be modified thereafter). This field
155
* indicates whether a node change event should be fired when
156
* creation is complete.
157
*/
158
protected boolean newNode = false;
159
160
/**
161
* All known unremoved children of this node. (This "cache" is consulted
162
* prior to calling childSpi() or getChild().
163
*/
164
private Map<String, AbstractPreferences> kidCache = new HashMap<>();
165
166
/**
167
* This field is used to keep track of whether or not this node has
168
* been removed. Once it's set to true, it will never be reset to false.
169
*/
170
private boolean removed = false;
171
172
/**
173
* Registered preference change listeners.
174
*/
175
private PreferenceChangeListener[] prefListeners =
176
new PreferenceChangeListener[0];
177
178
/**
179
* Registered node change listeners.
180
*/
181
private NodeChangeListener[] nodeListeners = new NodeChangeListener[0];
182
183
/**
184
* An object whose monitor is used to lock this node. This object
185
* is used in preference to the node itself to reduce the likelihood of
186
* intentional or unintentional denial of service due to a locked node.
187
* To avoid deadlock, a node is <i>never</i> locked by a thread that
188
* holds a lock on a descendant of that node.
189
*/
190
protected final Object lock = new Object();
191
192
/**
193
* Creates a preference node with the specified parent and the specified
194
* name relative to its parent.
195
*
196
* @param parent the parent of this preference node, or null if this
197
* is the root.
198
* @param name the name of this preference node, relative to its parent,
199
* or {@code ""} if this is the root.
200
* @throws IllegalArgumentException if {@code name} contains a slash
201
* ({@code '/'}), or {@code parent} is {@code null} and
202
* name isn't {@code ""}.
203
*/
204
protected AbstractPreferences(AbstractPreferences parent, String name) {
205
if (parent==null) {
206
if (!name.isEmpty())
207
throw new IllegalArgumentException("Root name '"+name+
208
"' must be \"\"");
209
this.absolutePath = "/";
210
root = this;
211
} else {
212
if (name.indexOf('/') != -1)
213
throw new IllegalArgumentException("Name '" + name +
214
"' contains '/'");
215
if (name.isEmpty())
216
throw new IllegalArgumentException("Illegal name: empty string");
217
218
root = parent.root;
219
absolutePath = (parent==root ? "/" + name
220
: parent.absolutePath() + "/" + name);
221
}
222
this.name = name;
223
this.parent = parent;
224
}
225
226
/**
227
* Implements the {@code put} method as per the specification in
228
* {@link Preferences#put(String,String)}.
229
*
230
* <p>This implementation checks that the key and value are legal,
231
* obtains this preference node's lock, checks that the node
232
* has not been removed, invokes {@link #putSpi(String,String)}, and if
233
* there are any preference change listeners, enqueues a notification
234
* event for processing by the event dispatch thread.
235
*
236
* @param key key with which the specified value is to be associated.
237
* @param value value to be associated with the specified key.
238
* @throws NullPointerException if key or value is {@code null}.
239
* @throws IllegalArgumentException if {@code key.length()} exceeds
240
* {@code MAX_KEY_LENGTH} or if {@code value.length} exceeds
241
* {@code MAX_VALUE_LENGTH}.
242
* @throws IllegalArgumentException if either key or value contain
243
* the null control character, code point U+0000.
244
* @throws IllegalStateException if this node (or an ancestor) has been
245
* removed with the {@link #removeNode()} method.
246
*/
247
public void put(String key, String value) {
248
if (key==null || value==null)
249
throw new NullPointerException();
250
if (key.length() > MAX_KEY_LENGTH)
251
throw new IllegalArgumentException("Key too long: "+key);
252
if (value.length() > MAX_VALUE_LENGTH)
253
throw new IllegalArgumentException("Value too long: "+value);
254
if (key.indexOf(CODE_POINT_U0000) != -1)
255
throw new IllegalArgumentException("Key contains code point U+0000");
256
if (value.indexOf(CODE_POINT_U0000) != -1)
257
throw new IllegalArgumentException("Value contains code point U+0000");
258
259
synchronized(lock) {
260
if (removed)
261
throw new IllegalStateException("Node has been removed.");
262
263
putSpi(key, value);
264
enqueuePreferenceChangeEvent(key, value);
265
}
266
}
267
268
/**
269
* Implements the {@code get} method as per the specification in
270
* {@link Preferences#get(String,String)}.
271
*
272
* <p>This implementation first checks to see if {@code key} is
273
* {@code null} throwing a {@code NullPointerException} if this is
274
* the case. Then it obtains this preference node's lock,
275
* checks that the node has not been removed, invokes {@link
276
* #getSpi(String)}, and returns the result, unless the {@code getSpi}
277
* invocation returns {@code null} or throws an exception, in which case
278
* this invocation returns {@code def}.
279
*
280
* @param key key whose associated value is to be returned.
281
* @param def the value to be returned in the event that this
282
* preference node has no value associated with {@code key}.
283
* @return the value associated with {@code key}, or {@code def}
284
* if no value is associated with {@code key}.
285
* @throws IllegalStateException if this node (or an ancestor) has been
286
* removed with the {@link #removeNode()} method.
287
* @throws NullPointerException if key is {@code null}. (A
288
* {@code null} default <i>is</i> permitted.)
289
* @throws IllegalArgumentException if key contains the null control
290
* character, code point U+0000.
291
*/
292
public String get(String key, String def) {
293
if (key==null)
294
throw new NullPointerException("Null key");
295
if (key.indexOf(CODE_POINT_U0000) != -1)
296
throw new IllegalArgumentException("Key contains code point U+0000");
297
synchronized(lock) {
298
if (removed)
299
throw new IllegalStateException("Node has been removed.");
300
301
String result = null;
302
try {
303
result = getSpi(key);
304
} catch (Exception e) {
305
// Ignoring exception causes default to be returned
306
}
307
return (result==null ? def : result);
308
}
309
}
310
311
/**
312
* Implements the {@code remove(String)} method as per the specification
313
* in {@link Preferences#remove(String)}.
314
*
315
* <p>This implementation obtains this preference node's lock,
316
* checks that the node has not been removed, invokes
317
* {@link #removeSpi(String)} and if there are any preference
318
* change listeners, enqueues a notification event for processing by the
319
* event dispatch thread.
320
*
321
* @param key key whose mapping is to be removed from the preference node.
322
* @throws IllegalStateException if this node (or an ancestor) has been
323
* removed with the {@link #removeNode()} method.
324
* @throws IllegalArgumentException if key contains the null control
325
* character, code point U+0000.
326
* @throws NullPointerException {@inheritDoc}.
327
*/
328
public void remove(String key) {
329
Objects.requireNonNull(key, "Specified key cannot be null");
330
if (key.indexOf(CODE_POINT_U0000) != -1)
331
throw new IllegalArgumentException("Key contains code point U+0000");
332
synchronized(lock) {
333
if (removed)
334
throw new IllegalStateException("Node has been removed.");
335
336
removeSpi(key);
337
enqueuePreferenceChangeEvent(key, null);
338
}
339
}
340
341
/**
342
* Implements the {@code clear} method as per the specification in
343
* {@link Preferences#clear()}.
344
*
345
* <p>This implementation obtains this preference node's lock,
346
* invokes {@link #keys()} to obtain an array of keys, and
347
* iterates over the array invoking {@link #remove(String)} on each key.
348
*
349
* @throws BackingStoreException if this operation cannot be completed
350
* due to a failure in the backing store, or inability to
351
* communicate with it.
352
* @throws IllegalStateException if this node (or an ancestor) has been
353
* removed with the {@link #removeNode()} method.
354
*/
355
public void clear() throws BackingStoreException {
356
synchronized(lock) {
357
for (String key : keys())
358
remove(key);
359
}
360
}
361
362
/**
363
* Implements the {@code putInt} method as per the specification in
364
* {@link Preferences#putInt(String,int)}.
365
*
366
* <p>This implementation translates {@code value} to a string with
367
* {@link Integer#toString(int)} and invokes {@link #put(String,String)}
368
* on the result.
369
*
370
* @param key key with which the string form of value is to be associated.
371
* @param value value whose string form is to be associated with key.
372
* @throws NullPointerException if key is {@code null}.
373
* @throws IllegalArgumentException if {@code key.length()} exceeds
374
* {@code MAX_KEY_LENGTH}.
375
* @throws IllegalArgumentException if key contains
376
* the null control character, code point U+0000.
377
* @throws IllegalStateException if this node (or an ancestor) has been
378
* removed with the {@link #removeNode()} method.
379
*/
380
public void putInt(String key, int value) {
381
put(key, Integer.toString(value));
382
}
383
384
/**
385
* Implements the {@code getInt} method as per the specification in
386
* {@link Preferences#getInt(String,int)}.
387
*
388
* <p>This implementation invokes {@link #get(String,String) get(key,
389
* null)}. If the return value is non-null, the implementation
390
* attempts to translate it to an {@code int} with
391
* {@link Integer#parseInt(String)}. If the attempt succeeds, the return
392
* value is returned by this method. Otherwise, {@code def} is returned.
393
*
394
* @param key key whose associated value is to be returned as an int.
395
* @param def the value to be returned in the event that this
396
* preference node has no value associated with {@code key}
397
* or the associated value cannot be interpreted as an int.
398
* @return the int value represented by the string associated with
399
* {@code key} in this preference node, or {@code def} if the
400
* associated value does not exist or cannot be interpreted as
401
* an int.
402
* @throws IllegalStateException if this node (or an ancestor) has been
403
* removed with the {@link #removeNode()} method.
404
* @throws NullPointerException if {@code key} is {@code null}.
405
* @throws IllegalArgumentException if key contains the null control
406
* character, code point U+0000.
407
*/
408
public int getInt(String key, int def) {
409
int result = def;
410
try {
411
String value = get(key, null);
412
if (value != null)
413
result = Integer.parseInt(value);
414
} catch (NumberFormatException e) {
415
// Ignoring exception causes specified default to be returned
416
}
417
418
return result;
419
}
420
421
/**
422
* Implements the {@code putLong} method as per the specification in
423
* {@link Preferences#putLong(String,long)}.
424
*
425
* <p>This implementation translates {@code value} to a string with
426
* {@link Long#toString(long)} and invokes {@link #put(String,String)}
427
* on the result.
428
*
429
* @param key key with which the string form of value is to be associated.
430
* @param value value whose string form is to be associated with key.
431
* @throws NullPointerException if key is {@code null}.
432
* @throws IllegalArgumentException if {@code key.length()} exceeds
433
* {@code MAX_KEY_LENGTH}.
434
* @throws IllegalArgumentException if key contains
435
* the null control character, code point U+0000.
436
* @throws IllegalStateException if this node (or an ancestor) has been
437
* removed with the {@link #removeNode()} method.
438
*/
439
public void putLong(String key, long value) {
440
put(key, Long.toString(value));
441
}
442
443
/**
444
* Implements the {@code getLong} method as per the specification in
445
* {@link Preferences#getLong(String,long)}.
446
*
447
* <p>This implementation invokes {@link #get(String,String) get(key,
448
* null)}. If the return value is non-null, the implementation
449
* attempts to translate it to a {@code long} with
450
* {@link Long#parseLong(String)}. If the attempt succeeds, the return
451
* value is returned by this method. Otherwise, {@code def} is returned.
452
*
453
* @param key key whose associated value is to be returned as a long.
454
* @param def the value to be returned in the event that this
455
* preference node has no value associated with {@code key}
456
* or the associated value cannot be interpreted as a long.
457
* @return the long value represented by the string associated with
458
* {@code key} in this preference node, or {@code def} if the
459
* associated value does not exist or cannot be interpreted as
460
* a long.
461
* @throws IllegalStateException if this node (or an ancestor) has been
462
* removed with the {@link #removeNode()} method.
463
* @throws NullPointerException if {@code key} is {@code null}.
464
* @throws IllegalArgumentException if key contains the null control
465
* character, code point U+0000.
466
*/
467
public long getLong(String key, long def) {
468
long result = def;
469
try {
470
String value = get(key, null);
471
if (value != null)
472
result = Long.parseLong(value);
473
} catch (NumberFormatException e) {
474
// Ignoring exception causes specified default to be returned
475
}
476
477
return result;
478
}
479
480
/**
481
* Implements the {@code putBoolean} method as per the specification in
482
* {@link Preferences#putBoolean(String,boolean)}.
483
*
484
* <p>This implementation translates {@code value} to a string with
485
* {@link String#valueOf(boolean)} and invokes {@link #put(String,String)}
486
* on the result.
487
*
488
* @param key key with which the string form of value is to be associated.
489
* @param value value whose string form is to be associated with key.
490
* @throws NullPointerException if key is {@code null}.
491
* @throws IllegalArgumentException if {@code key.length()} exceeds
492
* {@code MAX_KEY_LENGTH}.
493
* @throws IllegalArgumentException if key contains
494
* the null control character, code point U+0000.
495
* @throws IllegalStateException if this node (or an ancestor) has been
496
* removed with the {@link #removeNode()} method.
497
*/
498
public void putBoolean(String key, boolean value) {
499
put(key, String.valueOf(value));
500
}
501
502
/**
503
* Implements the {@code getBoolean} method as per the specification in
504
* {@link Preferences#getBoolean(String,boolean)}.
505
*
506
* <p>This implementation invokes {@link #get(String,String) get(key,
507
* null)}. If the return value is non-null, it is compared with
508
* {@code "true"} using {@link String#equalsIgnoreCase(String)}. If the
509
* comparison returns {@code true}, this invocation returns
510
* {@code true}. Otherwise, the original return value is compared with
511
* {@code "false"}, again using {@link String#equalsIgnoreCase(String)}.
512
* If the comparison returns {@code true}, this invocation returns
513
* {@code false}. Otherwise, this invocation returns {@code def}.
514
*
515
* @param key key whose associated value is to be returned as a boolean.
516
* @param def the value to be returned in the event that this
517
* preference node has no value associated with {@code key}
518
* or the associated value cannot be interpreted as a boolean.
519
* @return the boolean value represented by the string associated with
520
* {@code key} in this preference node, or {@code def} if the
521
* associated value does not exist or cannot be interpreted as
522
* a boolean.
523
* @throws IllegalStateException if this node (or an ancestor) has been
524
* removed with the {@link #removeNode()} method.
525
* @throws NullPointerException if {@code key} is {@code null}.
526
* @throws IllegalArgumentException if key contains the null control
527
* character, code point U+0000.
528
*/
529
public boolean getBoolean(String key, boolean def) {
530
boolean result = def;
531
String value = get(key, null);
532
if (value != null) {
533
if (value.equalsIgnoreCase("true"))
534
result = true;
535
else if (value.equalsIgnoreCase("false"))
536
result = false;
537
}
538
539
return result;
540
}
541
542
/**
543
* Implements the {@code putFloat} method as per the specification in
544
* {@link Preferences#putFloat(String,float)}.
545
*
546
* <p>This implementation translates {@code value} to a string with
547
* {@link Float#toString(float)} and invokes {@link #put(String,String)}
548
* on the result.
549
*
550
* @param key key with which the string form of value is to be associated.
551
* @param value value whose string form is to be associated with key.
552
* @throws NullPointerException if key is {@code null}.
553
* @throws IllegalArgumentException if {@code key.length()} exceeds
554
* {@code MAX_KEY_LENGTH}.
555
* @throws IllegalArgumentException if key contains
556
* the null control character, code point U+0000.
557
* @throws IllegalStateException if this node (or an ancestor) has been
558
* removed with the {@link #removeNode()} method.
559
*/
560
public void putFloat(String key, float value) {
561
put(key, Float.toString(value));
562
}
563
564
/**
565
* Implements the {@code getFloat} method as per the specification in
566
* {@link Preferences#getFloat(String,float)}.
567
*
568
* <p>This implementation invokes {@link #get(String,String) get(key,
569
* null)}. If the return value is non-null, the implementation
570
* attempts to translate it to an {@code float} with
571
* {@link Float#parseFloat(String)}. If the attempt succeeds, the return
572
* value is returned by this method. Otherwise, {@code def} is returned.
573
*
574
* @param key key whose associated value is to be returned as a float.
575
* @param def the value to be returned in the event that this
576
* preference node has no value associated with {@code key}
577
* or the associated value cannot be interpreted as a float.
578
* @return the float value represented by the string associated with
579
* {@code key} in this preference node, or {@code def} if the
580
* associated value does not exist or cannot be interpreted as
581
* a float.
582
* @throws IllegalStateException if this node (or an ancestor) has been
583
* removed with the {@link #removeNode()} method.
584
* @throws NullPointerException if {@code key} is {@code null}.
585
* @throws IllegalArgumentException if key contains the null control
586
* character, code point U+0000.
587
*/
588
public float getFloat(String key, float def) {
589
float result = def;
590
try {
591
String value = get(key, null);
592
if (value != null)
593
result = Float.parseFloat(value);
594
} catch (NumberFormatException e) {
595
// Ignoring exception causes specified default to be returned
596
}
597
598
return result;
599
}
600
601
/**
602
* Implements the {@code putDouble} method as per the specification in
603
* {@link Preferences#putDouble(String,double)}.
604
*
605
* <p>This implementation translates {@code value} to a string with
606
* {@link Double#toString(double)} and invokes {@link #put(String,String)}
607
* on the result.
608
*
609
* @param key key with which the string form of value is to be associated.
610
* @param value value whose string form is to be associated with key.
611
* @throws NullPointerException if key is {@code null}.
612
* @throws IllegalArgumentException if {@code key.length()} exceeds
613
* {@code MAX_KEY_LENGTH}.
614
* @throws IllegalArgumentException if key contains
615
* the null control character, code point U+0000.
616
* @throws IllegalStateException if this node (or an ancestor) has been
617
* removed with the {@link #removeNode()} method.
618
*/
619
public void putDouble(String key, double value) {
620
put(key, Double.toString(value));
621
}
622
623
/**
624
* Implements the {@code getDouble} method as per the specification in
625
* {@link Preferences#getDouble(String,double)}.
626
*
627
* <p>This implementation invokes {@link #get(String,String) get(key,
628
* null)}. If the return value is non-null, the implementation
629
* attempts to translate it to an {@code double} with
630
* {@link Double#parseDouble(String)}. If the attempt succeeds, the return
631
* value is returned by this method. Otherwise, {@code def} is returned.
632
*
633
* @param key key whose associated value is to be returned as a double.
634
* @param def the value to be returned in the event that this
635
* preference node has no value associated with {@code key}
636
* or the associated value cannot be interpreted as a double.
637
* @return the double value represented by the string associated with
638
* {@code key} in this preference node, or {@code def} if the
639
* associated value does not exist or cannot be interpreted as
640
* a double.
641
* @throws IllegalStateException if this node (or an ancestor) has been
642
* removed with the {@link #removeNode()} method.
643
* @throws NullPointerException if {@code key} is {@code null}.
644
* @throws IllegalArgumentException if key contains the null control
645
* character, code point U+0000.
646
*/
647
public double getDouble(String key, double def) {
648
double result = def;
649
try {
650
String value = get(key, null);
651
if (value != null)
652
result = Double.parseDouble(value);
653
} catch (NumberFormatException e) {
654
// Ignoring exception causes specified default to be returned
655
}
656
657
return result;
658
}
659
660
/**
661
* Implements the {@code putByteArray} method as per the specification in
662
* {@link Preferences#putByteArray(String,byte[])}.
663
*
664
* @param key key with which the string form of value is to be associated.
665
* @param value value whose string form is to be associated with key.
666
* @throws NullPointerException if key or value is {@code null}.
667
* @throws IllegalArgumentException if key.length() exceeds MAX_KEY_LENGTH
668
* or if value.length exceeds MAX_VALUE_LENGTH*3/4.
669
* @throws IllegalArgumentException if key contains
670
* the null control character, code point U+0000.
671
* @throws IllegalStateException if this node (or an ancestor) has been
672
* removed with the {@link #removeNode()} method.
673
*/
674
public void putByteArray(String key, byte[] value) {
675
put(key, Base64.byteArrayToBase64(value));
676
}
677
678
/**
679
* Implements the {@code getByteArray} method as per the specification in
680
* {@link Preferences#getByteArray(String,byte[])}.
681
*
682
* @param key key whose associated value is to be returned as a byte array.
683
* @param def the value to be returned in the event that this
684
* preference node has no value associated with {@code key}
685
* or the associated value cannot be interpreted as a byte array.
686
* @return the byte array value represented by the string associated with
687
* {@code key} in this preference node, or {@code def} if the
688
* associated value does not exist or cannot be interpreted as
689
* a byte array.
690
* @throws IllegalStateException if this node (or an ancestor) has been
691
* removed with the {@link #removeNode()} method.
692
* @throws NullPointerException if {@code key} is {@code null}. (A
693
* {@code null} value for {@code def} <i>is</i> permitted.)
694
* @throws IllegalArgumentException if key contains the null control
695
* character, code point U+0000.
696
*/
697
public byte[] getByteArray(String key, byte[] def) {
698
byte[] result = def;
699
String value = get(key, null);
700
try {
701
if (value != null)
702
result = Base64.base64ToByteArray(value);
703
}
704
catch (RuntimeException e) {
705
// Ignoring exception causes specified default to be returned
706
}
707
708
return result;
709
}
710
711
/**
712
* Implements the {@code keys} method as per the specification in
713
* {@link Preferences#keys()}.
714
*
715
* <p>This implementation obtains this preference node's lock, checks that
716
* the node has not been removed and invokes {@link #keysSpi()}.
717
*
718
* @return an array of the keys that have an associated value in this
719
* preference node.
720
* @throws BackingStoreException if this operation cannot be completed
721
* due to a failure in the backing store, or inability to
722
* communicate with it.
723
* @throws IllegalStateException if this node (or an ancestor) has been
724
* removed with the {@link #removeNode()} method.
725
*/
726
public String[] keys() throws BackingStoreException {
727
synchronized(lock) {
728
if (removed)
729
throw new IllegalStateException("Node has been removed.");
730
731
return keysSpi();
732
}
733
}
734
735
/**
736
* Implements the {@code children} method as per the specification in
737
* {@link Preferences#childrenNames()}.
738
*
739
* <p>This implementation obtains this preference node's lock, checks that
740
* the node has not been removed, constructs a {@code TreeSet} initialized
741
* to the names of children already cached (the children in this node's
742
* "child-cache"), invokes {@link #childrenNamesSpi()}, and adds all of the
743
* returned child-names into the set. The elements of the tree set are
744
* dumped into a {@code String} array using the {@code toArray} method,
745
* and this array is returned.
746
*
747
* @return the names of the children of this preference node.
748
* @throws BackingStoreException if this operation cannot be completed
749
* due to a failure in the backing store, or inability to
750
* communicate with it.
751
* @throws IllegalStateException if this node (or an ancestor) has been
752
* removed with the {@link #removeNode()} method.
753
* @see #cachedChildren()
754
*/
755
public String[] childrenNames() throws BackingStoreException {
756
synchronized(lock) {
757
if (removed)
758
throw new IllegalStateException("Node has been removed.");
759
760
Set<String> s = new TreeSet<>(kidCache.keySet());
761
for (String kid : childrenNamesSpi())
762
s.add(kid);
763
return s.toArray(EMPTY_STRING_ARRAY);
764
}
765
}
766
767
private static final String[] EMPTY_STRING_ARRAY = new String[0];
768
769
/**
770
* Returns all known unremoved children of this node.
771
*
772
* @return all known unremoved children of this node.
773
*/
774
protected final AbstractPreferences[] cachedChildren() {
775
return kidCache.values().toArray(EMPTY_ABSTRACT_PREFS_ARRAY);
776
}
777
778
private static final AbstractPreferences[] EMPTY_ABSTRACT_PREFS_ARRAY
779
= new AbstractPreferences[0];
780
781
/**
782
* Implements the {@code parent} method as per the specification in
783
* {@link Preferences#parent()}.
784
*
785
* <p>This implementation obtains this preference node's lock, checks that
786
* the node has not been removed and returns the parent value that was
787
* passed to this node's constructor.
788
*
789
* @return the parent of this preference node.
790
* @throws IllegalStateException if this node (or an ancestor) has been
791
* removed with the {@link #removeNode()} method.
792
*/
793
public Preferences parent() {
794
synchronized(lock) {
795
if (removed)
796
throw new IllegalStateException("Node has been removed.");
797
798
return parent;
799
}
800
}
801
802
/**
803
* Implements the {@code node} method as per the specification in
804
* {@link Preferences#node(String)}.
805
*
806
* <p>This implementation obtains this preference node's lock and checks
807
* that the node has not been removed. If {@code path} is {@code ""},
808
* this node is returned; if {@code path} is {@code "/"}, this node's
809
* root is returned. If the first character in {@code path} is
810
* not {@code '/'}, the implementation breaks {@code path} into
811
* tokens and recursively traverses the path from this node to the
812
* named node, "consuming" a name and a slash from {@code path} at
813
* each step of the traversal. At each step, the current node is locked
814
* and the node's child-cache is checked for the named node. If it is
815
* not found, the name is checked to make sure its length does not
816
* exceed {@code MAX_NAME_LENGTH}. Then the {@link #childSpi(String)}
817
* method is invoked, and the result stored in this node's child-cache.
818
* If the newly created {@code Preferences} object's {@link #newNode}
819
* field is {@code true} and there are any node change listeners,
820
* a notification event is enqueued for processing by the event dispatch
821
* thread.
822
*
823
* <p>When there are no more tokens, the last value found in the
824
* child-cache or returned by {@code childSpi} is returned by this
825
* method. If during the traversal, two {@code "/"} tokens occur
826
* consecutively, or the final token is {@code "/"} (rather than a name),
827
* an appropriate {@code IllegalArgumentException} is thrown.
828
*
829
* <p> If the first character of {@code path} is {@code '/'}
830
* (indicating an absolute path name) this preference node's
831
* lock is dropped prior to breaking {@code path} into tokens, and
832
* this method recursively traverses the path starting from the root
833
* (rather than starting from this node). The traversal is otherwise
834
* identical to the one described for relative path names. Dropping
835
* the lock on this node prior to commencing the traversal at the root
836
* node is essential to avoid the possibility of deadlock, as per the
837
* {@link #lock locking invariant}.
838
*
839
* @param path the path name of the preference node to return.
840
* @return the specified preference node.
841
* @throws IllegalArgumentException if the path name is invalid (i.e.,
842
* it contains multiple consecutive slash characters, or ends
843
* with a slash character and is more than one character long).
844
* @throws IllegalStateException if this node (or an ancestor) has been
845
* removed with the {@link #removeNode()} method.
846
*/
847
public Preferences node(String path) {
848
synchronized(lock) {
849
if (removed)
850
throw new IllegalStateException("Node has been removed.");
851
if (path.isEmpty())
852
return this;
853
if (path.equals("/"))
854
return root;
855
if (path.charAt(0) != '/')
856
return node(new StringTokenizer(path, "/", true));
857
}
858
859
// Absolute path. Note that we've dropped our lock to avoid deadlock
860
return root.node(new StringTokenizer(path.substring(1), "/", true));
861
}
862
863
/**
864
* tokenizer contains <name> {'/' <name>}*
865
*/
866
private Preferences node(StringTokenizer path) {
867
String token = path.nextToken();
868
if (token.equals("/")) // Check for consecutive slashes
869
throw new IllegalArgumentException("Consecutive slashes in path");
870
synchronized(lock) {
871
AbstractPreferences child = kidCache.get(token);
872
if (child == null) {
873
if (token.length() > MAX_NAME_LENGTH)
874
throw new IllegalArgumentException(
875
"Node name " + token + " too long");
876
child = childSpi(token);
877
if (child.newNode)
878
enqueueNodeAddedEvent(child);
879
kidCache.put(token, child);
880
}
881
if (!path.hasMoreTokens())
882
return child;
883
path.nextToken(); // Consume slash
884
if (!path.hasMoreTokens())
885
throw new IllegalArgumentException("Path ends with slash");
886
return child.node(path);
887
}
888
}
889
890
/**
891
* Implements the {@code nodeExists} method as per the specification in
892
* {@link Preferences#nodeExists(String)}.
893
*
894
* <p>This implementation is very similar to {@link #node(String)},
895
* except that {@link #getChild(String)} is used instead of {@link
896
* #childSpi(String)}.
897
*
898
* @param path the path name of the node whose existence is to be checked.
899
* @return true if the specified node exists.
900
* @throws BackingStoreException if this operation cannot be completed
901
* due to a failure in the backing store, or inability to
902
* communicate with it.
903
* @throws IllegalArgumentException if the path name is invalid (i.e.,
904
* it contains multiple consecutive slash characters, or ends
905
* with a slash character and is more than one character long).
906
* @throws IllegalStateException if this node (or an ancestor) has been
907
* removed with the {@link #removeNode()} method and
908
* {@code pathname} is not the empty string ({@code ""}).
909
*/
910
public boolean nodeExists(String path)
911
throws BackingStoreException
912
{
913
synchronized(lock) {
914
if (path.isEmpty())
915
return !removed;
916
if (removed)
917
throw new IllegalStateException("Node has been removed.");
918
if (path.equals("/"))
919
return true;
920
if (path.charAt(0) != '/')
921
return nodeExists(new StringTokenizer(path, "/", true));
922
}
923
924
// Absolute path. Note that we've dropped our lock to avoid deadlock
925
return root.nodeExists(new StringTokenizer(path.substring(1), "/",
926
true));
927
}
928
929
/**
930
* tokenizer contains <name> {'/' <name>}*
931
*/
932
private boolean nodeExists(StringTokenizer path)
933
throws BackingStoreException
934
{
935
String token = path.nextToken();
936
if (token.equals("/")) // Check for consecutive slashes
937
throw new IllegalArgumentException("Consecutive slashes in path");
938
synchronized(lock) {
939
AbstractPreferences child = kidCache.get(token);
940
if (child == null)
941
child = getChild(token);
942
if (child==null)
943
return false;
944
if (!path.hasMoreTokens())
945
return true;
946
path.nextToken(); // Consume slash
947
if (!path.hasMoreTokens())
948
throw new IllegalArgumentException("Path ends with slash");
949
return child.nodeExists(path);
950
}
951
}
952
953
/**
954
955
* Implements the {@code removeNode()} method as per the specification in
956
* {@link Preferences#removeNode()}.
957
*
958
* <p>This implementation checks to see that this node is the root; if so,
959
* it throws an appropriate exception. Then, it locks this node's parent,
960
* and calls a recursive helper method that traverses the subtree rooted at
961
* this node. The recursive method locks the node on which it was called,
962
* checks that it has not already been removed, and then ensures that all
963
* of its children are cached: The {@link #childrenNamesSpi()} method is
964
* invoked and each returned child name is checked for containment in the
965
* child-cache. If a child is not already cached, the {@link
966
* #childSpi(String)} method is invoked to create a {@code Preferences}
967
* instance for it, and this instance is put into the child-cache. Then
968
* the helper method calls itself recursively on each node contained in its
969
* child-cache. Next, it invokes {@link #removeNodeSpi()}, marks itself
970
* as removed, and removes itself from its parent's child-cache. Finally,
971
* if there are any node change listeners, it enqueues a notification
972
* event for processing by the event dispatch thread.
973
*
974
* <p>Note that the helper method is always invoked with all ancestors up
975
* to the "closest non-removed ancestor" locked.
976
*
977
* @throws IllegalStateException if this node (or an ancestor) has already
978
* been removed with the {@link #removeNode()} method.
979
* @throws UnsupportedOperationException if this method is invoked on
980
* the root node.
981
* @throws BackingStoreException if this operation cannot be completed
982
* due to a failure in the backing store, or inability to
983
* communicate with it.
984
*/
985
public void removeNode() throws BackingStoreException {
986
if (this==root)
987
throw new UnsupportedOperationException("Can't remove the root!");
988
synchronized(parent.lock) {
989
removeNode2();
990
parent.kidCache.remove(name);
991
}
992
}
993
994
/*
995
* Called with locks on all nodes on path from parent of "removal root"
996
* to this (including the former but excluding the latter).
997
*/
998
private void removeNode2() throws BackingStoreException {
999
synchronized(lock) {
1000
if (removed)
1001
throw new IllegalStateException("Node already removed.");
1002
1003
// Ensure that all children are cached
1004
String[] kidNames = childrenNamesSpi();
1005
for (String kidName : kidNames)
1006
if (!kidCache.containsKey(kidName))
1007
kidCache.put(kidName, childSpi(kidName));
1008
1009
// Recursively remove all cached children
1010
for (Iterator<AbstractPreferences> i = kidCache.values().iterator();
1011
i.hasNext();) {
1012
try {
1013
i.next().removeNode2();
1014
i.remove();
1015
} catch (BackingStoreException x) { }
1016
}
1017
1018
// Now we have no descendants - it's time to die!
1019
removeNodeSpi();
1020
removed = true;
1021
parent.enqueueNodeRemovedEvent(this);
1022
}
1023
}
1024
1025
/**
1026
* Implements the {@code name} method as per the specification in
1027
* {@link Preferences#name()}.
1028
*
1029
* <p>This implementation merely returns the name that was
1030
* passed to this node's constructor.
1031
*
1032
* @return this preference node's name, relative to its parent.
1033
*/
1034
public String name() {
1035
return name;
1036
}
1037
1038
/**
1039
* Implements the {@code absolutePath} method as per the specification in
1040
* {@link Preferences#absolutePath()}.
1041
*
1042
* <p>This implementation merely returns the absolute path name that
1043
* was computed at the time that this node was constructed (based on
1044
* the name that was passed to this node's constructor, and the names
1045
* that were passed to this node's ancestors' constructors).
1046
*
1047
* @return this preference node's absolute path name.
1048
*/
1049
public String absolutePath() {
1050
return absolutePath;
1051
}
1052
1053
/**
1054
* Implements the {@code isUserNode} method as per the specification in
1055
* {@link Preferences#isUserNode()}.
1056
*
1057
* <p>This implementation compares this node's root node (which is stored
1058
* in a private field) with the value returned by
1059
* {@link Preferences#userRoot()}. If the two object references are
1060
* identical, this method returns true.
1061
*
1062
* @return {@code true} if this preference node is in the user
1063
* preference tree, {@code false} if it's in the system
1064
* preference tree.
1065
*/
1066
@SuppressWarnings("removal")
1067
public boolean isUserNode() {
1068
return AccessController.doPrivileged(
1069
new PrivilegedAction<Boolean>() {
1070
public Boolean run() {
1071
return root == Preferences.userRoot();
1072
}
1073
}).booleanValue();
1074
}
1075
1076
public void addPreferenceChangeListener(PreferenceChangeListener pcl) {
1077
if (pcl==null)
1078
throw new NullPointerException("Change listener is null.");
1079
synchronized(lock) {
1080
if (removed)
1081
throw new IllegalStateException("Node has been removed.");
1082
1083
// Copy-on-write
1084
PreferenceChangeListener[] old = prefListeners;
1085
prefListeners = new PreferenceChangeListener[old.length + 1];
1086
System.arraycopy(old, 0, prefListeners, 0, old.length);
1087
prefListeners[old.length] = pcl;
1088
}
1089
startEventDispatchThreadIfNecessary();
1090
}
1091
1092
public void removePreferenceChangeListener(PreferenceChangeListener pcl) {
1093
synchronized(lock) {
1094
if (removed)
1095
throw new IllegalStateException("Node has been removed.");
1096
if ((prefListeners == null) || (prefListeners.length == 0))
1097
throw new IllegalArgumentException("Listener not registered.");
1098
1099
// Copy-on-write
1100
PreferenceChangeListener[] newPl =
1101
new PreferenceChangeListener[prefListeners.length - 1];
1102
int i = 0;
1103
while (i < newPl.length && prefListeners[i] != pcl)
1104
newPl[i] = prefListeners[i++];
1105
1106
if (i == newPl.length && prefListeners[i] != pcl)
1107
throw new IllegalArgumentException("Listener not registered.");
1108
while (i < newPl.length)
1109
newPl[i] = prefListeners[++i];
1110
prefListeners = newPl;
1111
}
1112
}
1113
1114
public void addNodeChangeListener(NodeChangeListener ncl) {
1115
if (ncl==null)
1116
throw new NullPointerException("Change listener is null.");
1117
synchronized(lock) {
1118
if (removed)
1119
throw new IllegalStateException("Node has been removed.");
1120
1121
// Copy-on-write
1122
if (nodeListeners == null) {
1123
nodeListeners = new NodeChangeListener[1];
1124
nodeListeners[0] = ncl;
1125
} else {
1126
NodeChangeListener[] old = nodeListeners;
1127
nodeListeners = new NodeChangeListener[old.length + 1];
1128
System.arraycopy(old, 0, nodeListeners, 0, old.length);
1129
nodeListeners[old.length] = ncl;
1130
}
1131
}
1132
startEventDispatchThreadIfNecessary();
1133
}
1134
1135
public void removeNodeChangeListener(NodeChangeListener ncl) {
1136
synchronized(lock) {
1137
if (removed)
1138
throw new IllegalStateException("Node has been removed.");
1139
if ((nodeListeners == null) || (nodeListeners.length == 0))
1140
throw new IllegalArgumentException("Listener not registered.");
1141
1142
// Copy-on-write
1143
int i = 0;
1144
while (i < nodeListeners.length && nodeListeners[i] != ncl)
1145
i++;
1146
if (i == nodeListeners.length)
1147
throw new IllegalArgumentException("Listener not registered.");
1148
NodeChangeListener[] newNl =
1149
new NodeChangeListener[nodeListeners.length - 1];
1150
if (i != 0)
1151
System.arraycopy(nodeListeners, 0, newNl, 0, i);
1152
if (i != newNl.length)
1153
System.arraycopy(nodeListeners, i + 1,
1154
newNl, i, newNl.length - i);
1155
nodeListeners = newNl;
1156
}
1157
}
1158
1159
// "SPI" METHODS
1160
1161
/**
1162
* Put the given key-value association into this preference node. It is
1163
* guaranteed that {@code key} and {@code value} are non-null and of
1164
* legal length. Also, it is guaranteed that this node has not been
1165
* removed. (The implementor needn't check for any of these things.)
1166
*
1167
* <p>This method is invoked with the lock on this node held.
1168
* @param key the key
1169
* @param value the value
1170
*/
1171
protected abstract void putSpi(String key, String value);
1172
1173
/**
1174
* Return the value associated with the specified key at this preference
1175
* node, or {@code null} if there is no association for this key, or the
1176
* association cannot be determined at this time. It is guaranteed that
1177
* {@code key} is non-null. Also, it is guaranteed that this node has
1178
* not been removed. (The implementor needn't check for either of these
1179
* things.)
1180
*
1181
* <p> Generally speaking, this method should not throw an exception
1182
* under any circumstances. If, however, if it does throw an exception,
1183
* the exception will be intercepted and treated as a {@code null}
1184
* return value.
1185
*
1186
* <p>This method is invoked with the lock on this node held.
1187
*
1188
* @param key the key
1189
* @return the value associated with the specified key at this preference
1190
* node, or {@code null} if there is no association for this
1191
* key, or the association cannot be determined at this time.
1192
*/
1193
protected abstract String getSpi(String key);
1194
1195
/**
1196
* Remove the association (if any) for the specified key at this
1197
* preference node. It is guaranteed that {@code key} is non-null.
1198
* Also, it is guaranteed that this node has not been removed.
1199
* (The implementor needn't check for either of these things.)
1200
*
1201
* <p>This method is invoked with the lock on this node held.
1202
* @param key the key
1203
*/
1204
protected abstract void removeSpi(String key);
1205
1206
/**
1207
* Removes this preference node, invalidating it and any preferences that
1208
* it contains. The named child will have no descendants at the time this
1209
* invocation is made (i.e., the {@link Preferences#removeNode()} method
1210
* invokes this method repeatedly in a bottom-up fashion, removing each of
1211
* a node's descendants before removing the node itself).
1212
*
1213
* <p>This method is invoked with the lock held on this node and its
1214
* parent (and all ancestors that are being removed as a
1215
* result of a single invocation to {@link Preferences#removeNode()}).
1216
*
1217
* <p>The removal of a node needn't become persistent until the
1218
* {@code flush} method is invoked on this node (or an ancestor).
1219
*
1220
* <p>If this node throws a {@code BackingStoreException}, the exception
1221
* will propagate out beyond the enclosing {@link #removeNode()}
1222
* invocation.
1223
*
1224
* @throws BackingStoreException if this operation cannot be completed
1225
* due to a failure in the backing store, or inability to
1226
* communicate with it.
1227
*/
1228
protected abstract void removeNodeSpi() throws BackingStoreException;
1229
1230
/**
1231
* Returns all of the keys that have an associated value in this
1232
* preference node. (The returned array will be of size zero if
1233
* this node has no preferences.) It is guaranteed that this node has not
1234
* been removed.
1235
*
1236
* <p>This method is invoked with the lock on this node held.
1237
*
1238
* <p>If this node throws a {@code BackingStoreException}, the exception
1239
* will propagate out beyond the enclosing {@link #keys()} invocation.
1240
*
1241
* @return an array of the keys that have an associated value in this
1242
* preference node.
1243
* @throws BackingStoreException if this operation cannot be completed
1244
* due to a failure in the backing store, or inability to
1245
* communicate with it.
1246
*/
1247
protected abstract String[] keysSpi() throws BackingStoreException;
1248
1249
/**
1250
* Returns the names of the children of this preference node. (The
1251
* returned array will be of size zero if this node has no children.)
1252
* This method need not return the names of any nodes already cached,
1253
* but may do so without harm.
1254
*
1255
* <p>This method is invoked with the lock on this node held.
1256
*
1257
* <p>If this node throws a {@code BackingStoreException}, the exception
1258
* will propagate out beyond the enclosing {@link #childrenNames()}
1259
* invocation.
1260
*
1261
* @return an array containing the names of the children of this
1262
* preference node.
1263
* @throws BackingStoreException if this operation cannot be completed
1264
* due to a failure in the backing store, or inability to
1265
* communicate with it.
1266
*/
1267
protected abstract String[] childrenNamesSpi()
1268
throws BackingStoreException;
1269
1270
/**
1271
* Returns the named child if it exists, or {@code null} if it does not.
1272
* It is guaranteed that {@code nodeName} is non-null, non-empty,
1273
* does not contain the slash character ('/'), and is no longer than
1274
* {@link #MAX_NAME_LENGTH} characters. Also, it is guaranteed
1275
* that this node has not been removed. (The implementor needn't check
1276
* for any of these things if he chooses to override this method.)
1277
*
1278
* <p>Finally, it is guaranteed that the named node has not been returned
1279
* by a previous invocation of this method or {@link #childSpi} after the
1280
* last time that it was removed. In other words, a cached value will
1281
* always be used in preference to invoking this method. (The implementor
1282
* needn't maintain his own cache of previously returned children if he
1283
* chooses to override this method.)
1284
*
1285
* <p>This implementation obtains this preference node's lock, invokes
1286
* {@link #childrenNames()} to get an array of the names of this node's
1287
* children, and iterates over the array comparing the name of each child
1288
* with the specified node name. If a child node has the correct name,
1289
* the {@link #childSpi(String)} method is invoked and the resulting
1290
* node is returned. If the iteration completes without finding the
1291
* specified name, {@code null} is returned.
1292
*
1293
* @param nodeName name of the child to be searched for.
1294
* @return the named child if it exists, or null if it does not.
1295
* @throws BackingStoreException if this operation cannot be completed
1296
* due to a failure in the backing store, or inability to
1297
* communicate with it.
1298
*/
1299
protected AbstractPreferences getChild(String nodeName)
1300
throws BackingStoreException {
1301
synchronized(lock) {
1302
// assert kidCache.get(nodeName)==null;
1303
String[] kidNames = childrenNames();
1304
for (String kidName : kidNames)
1305
if (kidName.equals(nodeName))
1306
return childSpi(kidName);
1307
}
1308
return null;
1309
}
1310
1311
/**
1312
* Returns the named child of this preference node, creating it if it does
1313
* not already exist. It is guaranteed that {@code name} is non-null,
1314
* non-empty, does not contain the slash character ('/'), and is no longer
1315
* than {@link #MAX_NAME_LENGTH} characters. Also, it is guaranteed that
1316
* this node has not been removed. (The implementor needn't check for any
1317
* of these things.)
1318
*
1319
* <p>Finally, it is guaranteed that the named node has not been returned
1320
* by a previous invocation of this method or {@link #getChild(String)}
1321
* after the last time that it was removed. In other words, a cached
1322
* value will always be used in preference to invoking this method.
1323
* Subclasses need not maintain their own cache of previously returned
1324
* children.
1325
*
1326
* <p>The implementer must ensure that the returned node has not been
1327
* removed. If a like-named child of this node was previously removed, the
1328
* implementer must return a newly constructed {@code AbstractPreferences}
1329
* node; once removed, an {@code AbstractPreferences} node
1330
* cannot be "resuscitated."
1331
*
1332
* <p>If this method causes a node to be created, this node is not
1333
* guaranteed to be persistent until the {@code flush} method is
1334
* invoked on this node or one of its ancestors (or descendants).
1335
*
1336
* <p>This method is invoked with the lock on this node held.
1337
*
1338
* @param name The name of the child node to return, relative to
1339
* this preference node.
1340
* @return The named child node.
1341
*/
1342
protected abstract AbstractPreferences childSpi(String name);
1343
1344
/**
1345
* Returns the absolute path name of this preferences node.
1346
*/
1347
public String toString() {
1348
return (this.isUserNode() ? "User" : "System") +
1349
" Preference Node: " + this.absolutePath();
1350
}
1351
1352
/**
1353
* Implements the {@code sync} method as per the specification in
1354
* {@link Preferences#sync()}.
1355
*
1356
* <p>This implementation calls a recursive helper method that locks this
1357
* node, invokes syncSpi() on it, unlocks this node, and recursively
1358
* invokes this method on each "cached child." A cached child is a child
1359
* of this node that has been created in this VM and not subsequently
1360
* removed. In effect, this method does a depth first traversal of the
1361
* "cached subtree" rooted at this node, calling syncSpi() on each node in
1362
* the subTree while only that node is locked. Note that syncSpi() is
1363
* invoked top-down.
1364
*
1365
* @throws BackingStoreException if this operation cannot be completed
1366
* due to a failure in the backing store, or inability to
1367
* communicate with it.
1368
* @throws IllegalStateException if this node (or an ancestor) has been
1369
* removed with the {@link #removeNode()} method.
1370
* @see #flush()
1371
*/
1372
public void sync() throws BackingStoreException {
1373
sync2();
1374
}
1375
1376
private void sync2() throws BackingStoreException {
1377
AbstractPreferences[] cachedKids;
1378
1379
synchronized(lock) {
1380
if (removed)
1381
throw new IllegalStateException("Node has been removed");
1382
syncSpi();
1383
cachedKids = cachedChildren();
1384
}
1385
1386
for (AbstractPreferences cachedKid : cachedKids)
1387
cachedKid.sync2();
1388
}
1389
1390
/**
1391
* This method is invoked with this node locked. The contract of this
1392
* method is to synchronize any cached preferences stored at this node
1393
* with any stored in the backing store. (It is perfectly possible that
1394
* this node does not exist on the backing store, either because it has
1395
* been deleted by another VM, or because it has not yet been created.)
1396
* Note that this method should <i>not</i> synchronize the preferences in
1397
* any subnodes of this node. If the backing store naturally syncs an
1398
* entire subtree at once, the implementer is encouraged to override
1399
* sync(), rather than merely overriding this method.
1400
*
1401
* <p>If this node throws a {@code BackingStoreException}, the exception
1402
* will propagate out beyond the enclosing {@link #sync()} invocation.
1403
*
1404
* @throws BackingStoreException if this operation cannot be completed
1405
* due to a failure in the backing store, or inability to
1406
* communicate with it.
1407
*/
1408
protected abstract void syncSpi() throws BackingStoreException;
1409
1410
/**
1411
* Implements the {@code flush} method as per the specification in
1412
* {@link Preferences#flush()}.
1413
*
1414
* <p>This implementation calls a recursive helper method that locks this
1415
* node, invokes flushSpi() on it, unlocks this node, and recursively
1416
* invokes this method on each "cached child." A cached child is a child
1417
* of this node that has been created in this VM and not subsequently
1418
* removed. In effect, this method does a depth first traversal of the
1419
* "cached subtree" rooted at this node, calling flushSpi() on each node in
1420
* the subTree while only that node is locked. Note that flushSpi() is
1421
* invoked top-down.
1422
*
1423
* <p> If this method is invoked on a node that has been removed with
1424
* the {@link #removeNode()} method, flushSpi() is invoked on this node,
1425
* but not on others.
1426
*
1427
* @throws BackingStoreException if this operation cannot be completed
1428
* due to a failure in the backing store, or inability to
1429
* communicate with it.
1430
* @see #flush()
1431
*/
1432
public void flush() throws BackingStoreException {
1433
flush2();
1434
}
1435
1436
private void flush2() throws BackingStoreException {
1437
AbstractPreferences[] cachedKids;
1438
1439
synchronized(lock) {
1440
flushSpi();
1441
if(removed)
1442
return;
1443
cachedKids = cachedChildren();
1444
}
1445
1446
for (AbstractPreferences cachedKid : cachedKids)
1447
cachedKid.flush2();
1448
}
1449
1450
/**
1451
* This method is invoked with this node locked. The contract of this
1452
* method is to force any cached changes in the contents of this
1453
* preference node to the backing store, guaranteeing their persistence.
1454
* (It is perfectly possible that this node does not exist on the backing
1455
* store, either because it has been deleted by another VM, or because it
1456
* has not yet been created.) Note that this method should <i>not</i>
1457
* flush the preferences in any subnodes of this node. If the backing
1458
* store naturally flushes an entire subtree at once, the implementer is
1459
* encouraged to override flush(), rather than merely overriding this
1460
* method.
1461
*
1462
* <p>If this node throws a {@code BackingStoreException}, the exception
1463
* will propagate out beyond the enclosing {@link #flush()} invocation.
1464
*
1465
* @throws BackingStoreException if this operation cannot be completed
1466
* due to a failure in the backing store, or inability to
1467
* communicate with it.
1468
*/
1469
protected abstract void flushSpi() throws BackingStoreException;
1470
1471
/**
1472
* Returns {@code true} iff this node (or an ancestor) has been
1473
* removed with the {@link #removeNode()} method. This method
1474
* locks this node prior to returning the contents of the private
1475
* field used to track this state.
1476
*
1477
* @return {@code true} iff this node (or an ancestor) has been
1478
* removed with the {@link #removeNode()} method.
1479
*/
1480
protected boolean isRemoved() {
1481
synchronized(lock) {
1482
return removed;
1483
}
1484
}
1485
1486
/**
1487
* Queue of pending notification events. When a preference or node
1488
* change event for which there are one or more listeners occurs,
1489
* it is placed on this queue and the queue is notified. A background
1490
* thread waits on this queue and delivers the events. This decouples
1491
* event delivery from preference activity, greatly simplifying
1492
* locking and reducing opportunity for deadlock.
1493
*/
1494
private static final List<EventObject> eventQueue = new LinkedList<>();
1495
1496
/**
1497
* These two classes are used to distinguish NodeChangeEvents on
1498
* eventQueue so the event dispatch thread knows whether to call
1499
* childAdded or childRemoved.
1500
*/
1501
private class NodeAddedEvent extends NodeChangeEvent {
1502
private static final long serialVersionUID = -6743557530157328528L;
1503
NodeAddedEvent(Preferences parent, Preferences child) {
1504
super(parent, child);
1505
}
1506
}
1507
private class NodeRemovedEvent extends NodeChangeEvent {
1508
private static final long serialVersionUID = 8735497392918824837L;
1509
NodeRemovedEvent(Preferences parent, Preferences child) {
1510
super(parent, child);
1511
}
1512
}
1513
1514
/**
1515
* A single background thread ("the event notification thread") monitors
1516
* the event queue and delivers events that are placed on the queue.
1517
*/
1518
private static class EventDispatchThread extends Thread {
1519
private EventDispatchThread() {
1520
super(null, null, "Event Dispatch Thread", 0, false);
1521
}
1522
1523
public void run() {
1524
while(true) {
1525
// Wait on eventQueue till an event is present
1526
EventObject event = null;
1527
synchronized(eventQueue) {
1528
try {
1529
while (eventQueue.isEmpty())
1530
eventQueue.wait();
1531
event = eventQueue.remove(0);
1532
} catch (InterruptedException e) {
1533
// XXX Log "Event dispatch thread interrupted. Exiting"
1534
return;
1535
}
1536
}
1537
1538
// Now we have event & hold no locks; deliver evt to listeners
1539
AbstractPreferences src=(AbstractPreferences)event.getSource();
1540
if (event instanceof PreferenceChangeEvent) {
1541
PreferenceChangeEvent pce = (PreferenceChangeEvent)event;
1542
PreferenceChangeListener[] listeners = src.prefListeners();
1543
for (PreferenceChangeListener listener : listeners)
1544
listener.preferenceChange(pce);
1545
} else {
1546
NodeChangeEvent nce = (NodeChangeEvent)event;
1547
NodeChangeListener[] listeners = src.nodeListeners();
1548
if (nce instanceof NodeAddedEvent) {
1549
for (NodeChangeListener listener : listeners)
1550
listener.childAdded(nce);
1551
} else {
1552
// assert nce instanceof NodeRemovedEvent;
1553
for (NodeChangeListener listener : listeners)
1554
listener.childRemoved(nce);
1555
}
1556
}
1557
}
1558
}
1559
}
1560
1561
private static Thread eventDispatchThread = null;
1562
1563
/**
1564
* This method starts the event dispatch thread the first time it
1565
* is called. The event dispatch thread will be started only
1566
* if someone registers a listener.
1567
*/
1568
private static synchronized void startEventDispatchThreadIfNecessary() {
1569
if (eventDispatchThread == null) {
1570
// XXX Log "Starting event dispatch thread"
1571
eventDispatchThread = new EventDispatchThread();
1572
eventDispatchThread.setDaemon(true);
1573
eventDispatchThread.start();
1574
}
1575
}
1576
1577
/**
1578
* Return this node's preference/node change listeners. Even though
1579
* we're using a copy-on-write lists, we use synchronized accessors to
1580
* ensure information transmission from the writing thread to the
1581
* reading thread.
1582
*/
1583
PreferenceChangeListener[] prefListeners() {
1584
synchronized(lock) {
1585
return prefListeners;
1586
}
1587
}
1588
NodeChangeListener[] nodeListeners() {
1589
synchronized(lock) {
1590
return nodeListeners;
1591
}
1592
}
1593
1594
/**
1595
* Enqueue a preference change event for delivery to registered
1596
* preference change listeners unless there are no registered
1597
* listeners. Invoked with this.lock held.
1598
*/
1599
private void enqueuePreferenceChangeEvent(String key, String newValue) {
1600
if (prefListeners.length != 0) {
1601
synchronized(eventQueue) {
1602
eventQueue.add(new PreferenceChangeEvent(this, key, newValue));
1603
eventQueue.notify();
1604
}
1605
}
1606
}
1607
1608
/**
1609
* Enqueue a "node added" event for delivery to registered node change
1610
* listeners unless there are no registered listeners. Invoked with
1611
* this.lock held.
1612
*/
1613
private void enqueueNodeAddedEvent(Preferences child) {
1614
if (nodeListeners.length != 0) {
1615
synchronized(eventQueue) {
1616
eventQueue.add(new NodeAddedEvent(this, child));
1617
eventQueue.notify();
1618
}
1619
}
1620
}
1621
1622
/**
1623
* Enqueue a "node removed" event for delivery to registered node change
1624
* listeners unless there are no registered listeners. Invoked with
1625
* this.lock held.
1626
*/
1627
private void enqueueNodeRemovedEvent(Preferences child) {
1628
if (nodeListeners.length != 0) {
1629
synchronized(eventQueue) {
1630
eventQueue.add(new NodeRemovedEvent(this, child));
1631
eventQueue.notify();
1632
}
1633
}
1634
}
1635
1636
/**
1637
* Implements the {@code exportNode} method as per the specification in
1638
* {@link Preferences#exportNode(OutputStream)}.
1639
*
1640
* @param os the output stream on which to emit the XML document.
1641
* @throws IOException if writing to the specified output stream
1642
* results in an {@code IOException}.
1643
* @throws BackingStoreException if preference data cannot be read from
1644
* backing store.
1645
*/
1646
public void exportNode(OutputStream os)
1647
throws IOException, BackingStoreException
1648
{
1649
XmlSupport.export(os, this, false);
1650
}
1651
1652
/**
1653
* Implements the {@code exportSubtree} method as per the specification in
1654
* {@link Preferences#exportSubtree(OutputStream)}.
1655
*
1656
* @param os the output stream on which to emit the XML document.
1657
* @throws IOException if writing to the specified output stream
1658
* results in an {@code IOException}.
1659
* @throws BackingStoreException if preference data cannot be read from
1660
* backing store.
1661
*/
1662
public void exportSubtree(OutputStream os)
1663
throws IOException, BackingStoreException
1664
{
1665
XmlSupport.export(os, this, true);
1666
}
1667
}
1668
1669