0001 /*
0002 * Copyright 2000-2006 Sun Microsystems, Inc. All Rights Reserved.
0003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
0004 *
0005 * This code is free software; you can redistribute it and/or modify it
0006 * under the terms of the GNU General Public License version 2 only, as
0007 * published by the Free Software Foundation. Sun designates this
0008 * particular file as subject to the "Classpath" exception as provided
0009 * by Sun in the LICENSE file that accompanied this code.
0010 *
0011 * This code is distributed in the hope that it will be useful, but WITHOUT
0012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
0013 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
0014 * version 2 for more details (a copy is included in the LICENSE file that
0015 * accompanied this code).
0016 *
0017 * You should have received a copy of the GNU General Public License version
0018 * 2 along with this work; if not, write to the Free Software Foundation,
0019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
0020 *
0021 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
0022 * CA 95054 USA or visit www.sun.com if you need additional information or
0023 * have any questions.
0024 */
0025
0026 package java.util.prefs;
0027
0028 import java.util.*;
0029 import java.io.*;
0030 import java.security.AccessController;
0031 import java.security.PrivilegedAction; // These imports needed only as a workaround for a JavaDoc bug
0032 import java.lang.Integer;
0033 import java.lang.Long;
0034 import java.lang.Float;
0035 import java.lang.Double;
0036
0037 /**
0038 * This class provides a skeletal implementation of the {@link Preferences}
0039 * class, greatly easing the task of implementing it.
0040 *
0041 * <p><strong>This class is for <tt>Preferences</tt> implementers only.
0042 * Normal users of the <tt>Preferences</tt> facility should have no need to
0043 * consult this documentation. The {@link Preferences} documentation
0044 * should suffice.</strong>
0045 *
0046 * <p>Implementors must override the nine abstract service-provider interface
0047 * (SPI) methods: {@link #getSpi(String)}, {@link #putSpi(String,String)},
0048 * {@link #removeSpi(String)}, {@link #childSpi(String)}, {@link
0049 * #removeNodeSpi()}, {@link #keysSpi()}, {@link #childrenNamesSpi()}, {@link
0050 * #syncSpi()} and {@link #flushSpi()}. All of the concrete methods specify
0051 * precisely how they are implemented atop these SPI methods. The implementor
0052 * may, at his discretion, override one or more of the concrete methods if the
0053 * default implementation is unsatisfactory for any reason, such as
0054 * performance.
0055 *
0056 * <p>The SPI methods fall into three groups concerning exception
0057 * behavior. The <tt>getSpi</tt> method should never throw exceptions, but it
0058 * doesn't really matter, as any exception thrown by this method will be
0059 * intercepted by {@link #get(String,String)}, which will return the specified
0060 * default value to the caller. The <tt>removeNodeSpi, keysSpi,
0061 * childrenNamesSpi, syncSpi</tt> and <tt>flushSpi</tt> methods are specified
0062 * to throw {@link BackingStoreException}, and the implementation is required
0063 * to throw this checked exception if it is unable to perform the operation.
0064 * The exception propagates outward, causing the corresponding API method
0065 * to fail.
0066 *
0067 * <p>The remaining SPI methods {@link #putSpi(String,String)}, {@link
0068 * #removeSpi(String)} and {@link #childSpi(String)} have more complicated
0069 * exception behavior. They are not specified to throw
0070 * <tt>BackingStoreException</tt>, as they can generally obey their contracts
0071 * even if the backing store is unavailable. This is true because they return
0072 * no information and their effects are not required to become permanent until
0073 * a subsequent call to {@link Preferences#flush()} or
0074 * {@link Preferences#sync()}. Generally speaking, these SPI methods should not
0075 * throw exceptions. In some implementations, there may be circumstances
0076 * under which these calls cannot even enqueue the requested operation for
0077 * later processing. Even under these circumstances it is generally better to
0078 * simply ignore the invocation and return, rather than throwing an
0079 * exception. Under these circumstances, however, all subsequent invocations
0080 * of <tt>flush()</tt> and <tt>sync</tt> should return <tt>false</tt>, as
0081 * returning <tt>true</tt> would imply that all previous operations had
0082 * successfully been made permanent.
0083 *
0084 * <p>There is one circumstance under which <tt>putSpi, removeSpi and
0085 * childSpi</tt> <i>should</i> throw an exception: if the caller lacks
0086 * sufficient privileges on the underlying operating system to perform the
0087 * requested operation. This will, for instance, occur on most systems
0088 * if a non-privileged user attempts to modify system preferences.
0089 * (The required privileges will vary from implementation to
0090 * implementation. On some implementations, they are the right to modify the
0091 * contents of some directory in the file system; on others they are the right
0092 * to modify contents of some key in a registry.) Under any of these
0093 * circumstances, it would generally be undesirable to let the program
0094 * continue executing as if these operations would become permanent at a later
0095 * time. While implementations are not required to throw an exception under
0096 * these circumstances, they are encouraged to do so. A {@link
0097 * SecurityException} would be appropriate.
0098 *
0099 * <p>Most of the SPI methods require the implementation to read or write
0100 * information at a preferences node. The implementor should beware of the
0101 * fact that another VM may have concurrently deleted this node from the
0102 * backing store. It is the implementation's responsibility to recreate the
0103 * node if it has been deleted.
0104 *
0105 * <p>Implementation note: In Sun's default <tt>Preferences</tt>
0106 * implementations, the user's identity is inherited from the underlying
0107 * operating system and does not change for the lifetime of the virtual
0108 * machine. It is recognized that server-side <tt>Preferences</tt>
0109 * implementations may have the user identity change from request to request,
0110 * implicitly passed to <tt>Preferences</tt> methods via the use of a
0111 * static {@link ThreadLocal} instance. Authors of such implementations are
0112 * <i>strongly</i> encouraged to determine the user at the time preferences
0113 * are accessed (for example by the {@link #get(String,String)} or {@link
0114 * #put(String,String)} method) rather than permanently associating a user
0115 * with each <tt>Preferences</tt> instance. The latter behavior conflicts
0116 * with normal <tt>Preferences</tt> usage and would lead to great confusion.
0117 *
0118 * @author Josh Bloch
0119 * @version 1.32, 05/05/07
0120 * @see Preferences
0121 * @since 1.4
0122 */
0123 public abstract class AbstractPreferences extends Preferences {
0124 /**
0125 * Our name relative to parent.
0126 */
0127 private final String name;
0128
0129 /**
0130 * Our absolute path name.
0131 */
0132 private final String absolutePath;
0133
0134 /**
0135 * Our parent node.
0136 */
0137 final AbstractPreferences parent;
0138
0139 /**
0140 * Our root node.
0141 */
0142 private final AbstractPreferences root; // Relative to this node
0143
0144 /**
0145 * This field should be <tt>true</tt> if this node did not exist in the
0146 * backing store prior to the creation of this object. The field
0147 * is initialized to false, but may be set to true by a subclass
0148 * constructor (and should not be modified thereafter). This field
0149 * indicates whether a node change event should be fired when
0150 * creation is complete.
0151 */
0152 protected boolean newNode = false;
0153
0154 /**
0155 * All known unremoved children of this node. (This "cache" is consulted
0156 * prior to calling childSpi() or getChild().
0157 */
0158 private Map kidCache = new HashMap();
0159
0160 /**
0161 * This field is used to keep track of whether or not this node has
0162 * been removed. Once it's set to true, it will never be reset to false.
0163 */
0164 private boolean removed = false;
0165
0166 /**
0167 * Registered preference change listeners.
0168 */
0169 private PreferenceChangeListener[] prefListeners = new PreferenceChangeListener[0];
0170
0171 /**
0172 * Registered node change listeners.
0173 */
0174 private NodeChangeListener[] nodeListeners = new NodeChangeListener[0];
0175
0176 /**
0177 * An object whose monitor is used to lock this node. This object
0178 * is used in preference to the node itself to reduce the likelihood of
0179 * intentional or unintentional denial of service due to a locked node.
0180 * To avoid deadlock, a node is <i>never</i> locked by a thread that
0181 * holds a lock on a descendant of that node.
0182 */
0183 protected final Object lock = new Object();
0184
0185 /**
0186 * Creates a preference node with the specified parent and the specified
0187 * name relative to its parent.
0188 *
0189 * @param parent the parent of this preference node, or null if this
0190 * is the root.
0191 * @param name the name of this preference node, relative to its parent,
0192 * or <tt>""</tt> if this is the root.
0193 * @throws IllegalArgumentException if <tt>name</tt> contains a slash
0194 * (<tt>'/'</tt>), or <tt>parent</tt> is <tt>null</tt> and
0195 * name isn't <tt>""</tt>.
0196 */
0197 protected AbstractPreferences(AbstractPreferences parent,
0198 String name) {
0199 if (parent == null) {
0200 if (!name.equals(""))
0201 throw new IllegalArgumentException("Root name '" + name
0202 + "' must be \"\"");
0203 this .absolutePath = "/";
0204 root = this ;
0205 } else {
0206 if (name.indexOf('/') != -1)
0207 throw new IllegalArgumentException("Name '" + name
0208 + "' contains '/'");
0209 if (name.equals(""))
0210 throw new IllegalArgumentException(
0211 "Illegal name: empty string");
0212
0213 root = parent.root;
0214 absolutePath = (parent == root ? "/" + name : parent
0215 .absolutePath()
0216 + "/" + name);
0217 }
0218 this .name = name;
0219 this .parent = parent;
0220 }
0221
0222 /**
0223 * Implements the <tt>put</tt> method as per the specification in
0224 * {@link Preferences#put(String,String)}.
0225 *
0226 * <p>This implementation checks that the key and value are legal,
0227 * obtains this preference node's lock, checks that the node
0228 * has not been removed, invokes {@link #putSpi(String,String)}, and if
0229 * there are any preference change listeners, enqueues a notification
0230 * event for processing by the event dispatch thread.
0231 *
0232 * @param key key with which the specified value is to be associated.
0233 * @param value value to be associated with the specified key.
0234 * @throws NullPointerException if key or value is <tt>null</tt>.
0235 * @throws IllegalArgumentException if <tt>key.length()</tt> exceeds
0236 * <tt>MAX_KEY_LENGTH</tt> or if <tt>value.length</tt> exceeds
0237 * <tt>MAX_VALUE_LENGTH</tt>.
0238 * @throws IllegalStateException if this node (or an ancestor) has been
0239 * removed with the {@link #removeNode()} method.
0240 */
0241 public void put(String key, String value) {
0242 if (key == null || value == null)
0243 throw new NullPointerException();
0244 if (key.length() > MAX_KEY_LENGTH)
0245 throw new IllegalArgumentException("Key too long: " + key);
0246 if (value.length() > MAX_VALUE_LENGTH)
0247 throw new IllegalArgumentException("Value too long: "
0248 + value);
0249
0250 synchronized (lock) {
0251 if (removed)
0252 throw new IllegalStateException(
0253 "Node has been removed.");
0254
0255 putSpi(key, value);
0256 enqueuePreferenceChangeEvent(key, value);
0257 }
0258 }
0259
0260 /**
0261 * Implements the <tt>get</tt> method as per the specification in
0262 * {@link Preferences#get(String,String)}.
0263 *
0264 * <p>This implementation first checks to see if <tt>key</tt> is
0265 * <tt>null</tt> throwing a <tt>NullPointerException</tt> if this is
0266 * the case. Then it obtains this preference node's lock,
0267 * checks that the node has not been removed, invokes {@link
0268 * #getSpi(String)}, and returns the result, unless the <tt>getSpi</tt>
0269 * invocation returns <tt>null</tt> or throws an exception, in which case
0270 * this invocation returns <tt>def</tt>.
0271 *
0272 * @param key key whose associated value is to be returned.
0273 * @param def the value to be returned in the event that this
0274 * preference node has no value associated with <tt>key</tt>.
0275 * @return the value associated with <tt>key</tt>, or <tt>def</tt>
0276 * if no value is associated with <tt>key</tt>.
0277 * @throws IllegalStateException if this node (or an ancestor) has been
0278 * removed with the {@link #removeNode()} method.
0279 * @throws NullPointerException if key is <tt>null</tt>. (A
0280 * <tt>null</tt> default <i>is</i> permitted.)
0281 */
0282 public String get(String key, String def) {
0283 if (key == null)
0284 throw new NullPointerException("Null key");
0285 synchronized (lock) {
0286 if (removed)
0287 throw new IllegalStateException(
0288 "Node has been removed.");
0289
0290 String result = null;
0291 try {
0292 result = getSpi(key);
0293 } catch (Exception e) {
0294 // Ignoring exception causes default to be returned
0295 }
0296 return (result == null ? def : result);
0297 }
0298 }
0299
0300 /**
0301 * Implements the <tt>remove(String)</tt> method as per the specification
0302 * in {@link Preferences#remove(String)}.
0303 *
0304 * <p>This implementation obtains this preference node's lock,
0305 * checks that the node has not been removed, invokes
0306 * {@link #removeSpi(String)} and if there are any preference
0307 * change listeners, enqueues a notification event for processing by the
0308 * event dispatch thread.
0309 *
0310 * @param key key whose mapping is to be removed from the preference node.
0311 * @throws IllegalStateException if this node (or an ancestor) has been
0312 * removed with the {@link #removeNode()} method.
0313 */
0314 public void remove(String key) {
0315 synchronized (lock) {
0316 if (removed)
0317 throw new IllegalStateException(
0318 "Node has been removed.");
0319
0320 removeSpi(key);
0321 enqueuePreferenceChangeEvent(key, null);
0322 }
0323 }
0324
0325 /**
0326 * Implements the <tt>clear</tt> method as per the specification in
0327 * {@link Preferences#clear()}.
0328 *
0329 * <p>This implementation obtains this preference node's lock,
0330 * invokes {@link #keys()} to obtain an array of keys, and
0331 * iterates over the array invoking {@link #remove(String)} on each key.
0332 *
0333 * @throws BackingStoreException if this operation cannot be completed
0334 * due to a failure in the backing store, or inability to
0335 * communicate with it.
0336 * @throws IllegalStateException if this node (or an ancestor) has been
0337 * removed with the {@link #removeNode()} method.
0338 */
0339 public void clear() throws BackingStoreException {
0340 synchronized (lock) {
0341 String[] keys = keys();
0342 for (int i = 0; i < keys.length; i++)
0343 remove(keys[i]);
0344 }
0345 }
0346
0347 /**
0348 * Implements the <tt>putInt</tt> method as per the specification in
0349 * {@link Preferences#putInt(String,int)}.
0350 *
0351 * <p>This implementation translates <tt>value</tt> to a string with
0352 * {@link Integer#toString(int)} and invokes {@link #put(String,String)}
0353 * on the result.
0354 *
0355 * @param key key with which the string form of value is to be associated.
0356 * @param value value whose string form is to be associated with key.
0357 * @throws NullPointerException if key is <tt>null</tt>.
0358 * @throws IllegalArgumentException if <tt>key.length()</tt> exceeds
0359 * <tt>MAX_KEY_LENGTH</tt>.
0360 * @throws IllegalStateException if this node (or an ancestor) has been
0361 * removed with the {@link #removeNode()} method.
0362 */
0363 public void putInt(String key, int value) {
0364 put(key, Integer.toString(value));
0365 }
0366
0367 /**
0368 * Implements the <tt>getInt</tt> method as per the specification in
0369 * {@link Preferences#getInt(String,int)}.
0370 *
0371 * <p>This implementation invokes {@link #get(String,String) <tt>get(key,
0372 * null)</tt>}. If the return value is non-null, the implementation
0373 * attempts to translate it to an <tt>int</tt> with
0374 * {@link Integer#parseInt(String)}. If the attempt succeeds, the return
0375 * value is returned by this method. Otherwise, <tt>def</tt> is returned.
0376 *
0377 * @param key key whose associated value is to be returned as an int.
0378 * @param def the value to be returned in the event that this
0379 * preference node has no value associated with <tt>key</tt>
0380 * or the associated value cannot be interpreted as an int.
0381 * @return the int value represented by the string associated with
0382 * <tt>key</tt> in this preference node, or <tt>def</tt> if the
0383 * associated value does not exist or cannot be interpreted as
0384 * an int.
0385 * @throws IllegalStateException if this node (or an ancestor) has been
0386 * removed with the {@link #removeNode()} method.
0387 * @throws NullPointerException if <tt>key</tt> is <tt>null</tt>.
0388 */
0389 public int getInt(String key, int def) {
0390 int result = def;
0391 try {
0392 String value = get(key, null);
0393 if (value != null)
0394 result = Integer.parseInt(value);
0395 } catch (NumberFormatException e) {
0396 // Ignoring exception causes specified default to be returned
0397 }
0398
0399 return result;
0400 }
0401
0402 /**
0403 * Implements the <tt>putLong</tt> method as per the specification in
0404 * {@link Preferences#putLong(String,long)}.
0405 *
0406 * <p>This implementation translates <tt>value</tt> to a string with
0407 * {@link Long#toString(long)} and invokes {@link #put(String,String)}
0408 * on the result.
0409 *
0410 * @param key key with which the string form of value is to be associated.
0411 * @param value value whose string form is to be associated with key.
0412 * @throws NullPointerException if key is <tt>null</tt>.
0413 * @throws IllegalArgumentException if <tt>key.length()</tt> exceeds
0414 * <tt>MAX_KEY_LENGTH</tt>.
0415 * @throws IllegalStateException if this node (or an ancestor) has been
0416 * removed with the {@link #removeNode()} method.
0417 */
0418 public void putLong(String key, long value) {
0419 put(key, Long.toString(value));
0420 }
0421
0422 /**
0423 * Implements the <tt>getLong</tt> method as per the specification in
0424 * {@link Preferences#getLong(String,long)}.
0425 *
0426 * <p>This implementation invokes {@link #get(String,String) <tt>get(key,
0427 * null)</tt>}. If the return value is non-null, the implementation
0428 * attempts to translate it to a <tt>long</tt> with
0429 * {@link Long#parseLong(String)}. If the attempt succeeds, the return
0430 * value is returned by this method. Otherwise, <tt>def</tt> is returned.
0431 *
0432 * @param key key whose associated value is to be returned as a long.
0433 * @param def the value to be returned in the event that this
0434 * preference node has no value associated with <tt>key</tt>
0435 * or the associated value cannot be interpreted as a long.
0436 * @return the long value represented by the string associated with
0437 * <tt>key</tt> in this preference node, or <tt>def</tt> if the
0438 * associated value does not exist or cannot be interpreted as
0439 * a long.
0440 * @throws IllegalStateException if this node (or an ancestor) has been
0441 * removed with the {@link #removeNode()} method.
0442 * @throws NullPointerException if <tt>key</tt> is <tt>null</tt>.
0443 */
0444 public long getLong(String key, long def) {
0445 long result = def;
0446 try {
0447 String value = get(key, null);
0448 if (value != null)
0449 result = Long.parseLong(value);
0450 } catch (NumberFormatException e) {
0451 // Ignoring exception causes specified default to be returned
0452 }
0453
0454 return result;
0455 }
0456
0457 /**
0458 * Implements the <tt>putBoolean</tt> method as per the specification in
0459 * {@link Preferences#putBoolean(String,boolean)}.
0460 *
0461 * <p>This implementation translates <tt>value</tt> to a string with
0462 * {@link String#valueOf(boolean)} and invokes {@link #put(String,String)}
0463 * on the result.
0464 *
0465 * @param key key with which the string form of value is to be associated.
0466 * @param value value whose string form is to be associated with key.
0467 * @throws NullPointerException if key is <tt>null</tt>.
0468 * @throws IllegalArgumentException if <tt>key.length()</tt> exceeds
0469 * <tt>MAX_KEY_LENGTH</tt>.
0470 * @throws IllegalStateException if this node (or an ancestor) has been
0471 * removed with the {@link #removeNode()} method.
0472 */
0473 public void putBoolean(String key, boolean value) {
0474 put(key, String.valueOf(value));
0475 }
0476
0477 /**
0478 * Implements the <tt>getBoolean</tt> method as per the specification in
0479 * {@link Preferences#getBoolean(String,boolean)}.
0480 *
0481 * <p>This implementation invokes {@link #get(String,String) <tt>get(key,
0482 * null)</tt>}. If the return value is non-null, it is compared with
0483 * <tt>"true"</tt> using {@link String#equalsIgnoreCase(String)}. If the
0484 * comparison returns <tt>true</tt>, this invocation returns
0485 * <tt>true</tt>. Otherwise, the original return value is compared with
0486 * <tt>"false"</tt>, again using {@link String#equalsIgnoreCase(String)}.
0487 * If the comparison returns <tt>true</tt>, this invocation returns
0488 * <tt>false</tt>. Otherwise, this invocation returns <tt>def</tt>.
0489 *
0490 * @param key key whose associated value is to be returned as a boolean.
0491 * @param def the value to be returned in the event that this
0492 * preference node has no value associated with <tt>key</tt>
0493 * or the associated value cannot be interpreted as a boolean.
0494 * @return the boolean value represented by the string associated with
0495 * <tt>key</tt> in this preference node, or <tt>def</tt> if the
0496 * associated value does not exist or cannot be interpreted as
0497 * a boolean.
0498 * @throws IllegalStateException if this node (or an ancestor) has been
0499 * removed with the {@link #removeNode()} method.
0500 * @throws NullPointerException if <tt>key</tt> is <tt>null</tt>.
0501 */
0502 public boolean getBoolean(String key, boolean def) {
0503 boolean result = def;
0504 String value = get(key, null);
0505 if (value != null) {
0506 if (value.equalsIgnoreCase("true"))
0507 result = true;
0508 else if (value.equalsIgnoreCase("false"))
0509 result = false;
0510 }
0511
0512 return result;
0513 }
0514
0515 /**
0516 * Implements the <tt>putFloat</tt> method as per the specification in
0517 * {@link Preferences#putFloat(String,float)}.
0518 *
0519 * <p>This implementation translates <tt>value</tt> to a string with
0520 * {@link Float#toString(float)} and invokes {@link #put(String,String)}
0521 * on the result.
0522 *
0523 * @param key key with which the string form of value is to be associated.
0524 * @param value value whose string form is to be associated with key.
0525 * @throws NullPointerException if key is <tt>null</tt>.
0526 * @throws IllegalArgumentException if <tt>key.length()</tt> exceeds
0527 * <tt>MAX_KEY_LENGTH</tt>.
0528 * @throws IllegalStateException if this node (or an ancestor) has been
0529 * removed with the {@link #removeNode()} method.
0530 */
0531 public void putFloat(String key, float value) {
0532 put(key, Float.toString(value));
0533 }
0534
0535 /**
0536 * Implements the <tt>getFloat</tt> method as per the specification in
0537 * {@link Preferences#getFloat(String,float)}.
0538 *
0539 * <p>This implementation invokes {@link #get(String,String) <tt>get(key,
0540 * null)</tt>}. If the return value is non-null, the implementation
0541 * attempts to translate it to an <tt>float</tt> with
0542 * {@link Float#parseFloat(String)}. If the attempt succeeds, the return
0543 * value is returned by this method. Otherwise, <tt>def</tt> is returned.
0544 *
0545 * @param key key whose associated value is to be returned as a float.
0546 * @param def the value to be returned in the event that this
0547 * preference node has no value associated with <tt>key</tt>
0548 * or the associated value cannot be interpreted as a float.
0549 * @return the float value represented by the string associated with
0550 * <tt>key</tt> in this preference node, or <tt>def</tt> if the
0551 * associated value does not exist or cannot be interpreted as
0552 * a float.
0553 * @throws IllegalStateException if this node (or an ancestor) has been
0554 * removed with the {@link #removeNode()} method.
0555 * @throws NullPointerException if <tt>key</tt> is <tt>null</tt>.
0556 */
0557 public float getFloat(String key, float def) {
0558 float result = def;
0559 try {
0560 String value = get(key, null);
0561 if (value != null)
0562 result = Float.parseFloat(value);
0563 } catch (NumberFormatException e) {
0564 // Ignoring exception causes specified default to be returned
0565 }
0566
0567 return result;
0568 }
0569
0570 /**
0571 * Implements the <tt>putDouble</tt> method as per the specification in
0572 * {@link Preferences#putDouble(String,double)}.
0573 *
0574 * <p>This implementation translates <tt>value</tt> to a string with
0575 * {@link Double#toString(double)} and invokes {@link #put(String,String)}
0576 * on the result.
0577 *
0578 * @param key key with which the string form of value is to be associated.
0579 * @param value value whose string form is to be associated with key.
0580 * @throws NullPointerException if key is <tt>null</tt>.
0581 * @throws IllegalArgumentException if <tt>key.length()</tt> exceeds
0582 * <tt>MAX_KEY_LENGTH</tt>.
0583 * @throws IllegalStateException if this node (or an ancestor) has been
0584 * removed with the {@link #removeNode()} method.
0585 */
0586 public void putDouble(String key, double value) {
0587 put(key, Double.toString(value));
0588 }
0589
0590 /**
0591 * Implements the <tt>getDouble</tt> method as per the specification in
0592 * {@link Preferences#getDouble(String,double)}.
0593 *
0594 * <p>This implementation invokes {@link #get(String,String) <tt>get(key,
0595 * null)</tt>}. If the return value is non-null, the implementation
0596 * attempts to translate it to an <tt>double</tt> with
0597 * {@link Double#parseDouble(String)}. If the attempt succeeds, the return
0598 * value is returned by this method. Otherwise, <tt>def</tt> is returned.
0599 *
0600 * @param key key whose associated value is to be returned as a double.
0601 * @param def the value to be returned in the event that this
0602 * preference node has no value associated with <tt>key</tt>
0603 * or the associated value cannot be interpreted as a double.
0604 * @return the double value represented by the string associated with
0605 * <tt>key</tt> in this preference node, or <tt>def</tt> if the
0606 * associated value does not exist or cannot be interpreted as
0607 * a double.
0608 * @throws IllegalStateException if this node (or an ancestor) has been
0609 * removed with the {@link #removeNode()} method.
0610 * @throws NullPointerException if <tt>key</tt> is <tt>null</tt>.
0611 */
0612 public double getDouble(String key, double def) {
0613 double result = def;
0614 try {
0615 String value = get(key, null);
0616 if (value != null)
0617 result = Double.parseDouble(value);
0618 } catch (NumberFormatException e) {
0619 // Ignoring exception causes specified default to be returned
0620 }
0621
0622 return result;
0623 }
0624
0625 /**
0626 * Implements the <tt>putByteArray</tt> method as per the specification in
0627 * {@link Preferences#putByteArray(String,byte[])}.
0628 *
0629 * @param key key with which the string form of value is to be associated.
0630 * @param value value whose string form is to be associated with key.
0631 * @throws NullPointerException if key or value is <tt>null</tt>.
0632 * @throws IllegalArgumentException if key.length() exceeds MAX_KEY_LENGTH
0633 * or if value.length exceeds MAX_VALUE_LENGTH*3/4.
0634 * @throws IllegalStateException if this node (or an ancestor) has been
0635 * removed with the {@link #removeNode()} method.
0636 */
0637 public void putByteArray(String key, byte[] value) {
0638 put(key, Base64.byteArrayToBase64(value));
0639 }
0640
0641 /**
0642 * Implements the <tt>getByteArray</tt> method as per the specification in
0643 * {@link Preferences#getByteArray(String,byte[])}.
0644 *
0645 * @param key key whose associated value is to be returned as a byte array.
0646 * @param def the value to be returned in the event that this
0647 * preference node has no value associated with <tt>key</tt>
0648 * or the associated value cannot be interpreted as a byte array.
0649 * @return the byte array value represented by the string associated with
0650 * <tt>key</tt> in this preference node, or <tt>def</tt> if the
0651 * associated value does not exist or cannot be interpreted as
0652 * a byte array.
0653 * @throws IllegalStateException if this node (or an ancestor) has been
0654 * removed with the {@link #removeNode()} method.
0655 * @throws NullPointerException if <tt>key</tt> is <tt>null</tt>. (A
0656 * <tt>null</tt> value for <tt>def</tt> <i>is</i> permitted.)
0657 */
0658 public byte[] getByteArray(String key, byte[] def) {
0659 byte[] result = def;
0660 String value = get(key, null);
0661 try {
0662 if (value != null)
0663 result = Base64.base64ToByteArray(value);
0664 } catch (RuntimeException e) {
0665 // Ignoring exception causes specified default to be returned
0666 }
0667
0668 return result;
0669 }
0670
0671 /**
0672 * Implements the <tt>keys</tt> method as per the specification in
0673 * {@link Preferences#keys()}.
0674 *
0675 * <p>This implementation obtains this preference node's lock, checks that
0676 * the node has not been removed and invokes {@link #keysSpi()}.
0677 *
0678 * @return an array of the keys that have an associated value in this
0679 * preference node.
0680 * @throws BackingStoreException if this operation cannot be completed
0681 * due to a failure in the backing store, or inability to
0682 * communicate with it.
0683 * @throws IllegalStateException if this node (or an ancestor) has been
0684 * removed with the {@link #removeNode()} method.
0685 */
0686 public String[] keys() throws BackingStoreException {
0687 synchronized (lock) {
0688 if (removed)
0689 throw new IllegalStateException(
0690 "Node has been removed.");
0691
0692 return keysSpi();
0693 }
0694 }
0695
0696 /**
0697 * Implements the <tt>children</tt> method as per the specification in
0698 * {@link Preferences#childrenNames()}.
0699 *
0700 * <p>This implementation obtains this preference node's lock, checks that
0701 * the node has not been removed, constructs a <tt>TreeSet</tt> initialized
0702 * to the names of children already cached (the children in this node's
0703 * "child-cache"), invokes {@link #childrenNamesSpi()}, and adds all of the
0704 * returned child-names into the set. The elements of the tree set are
0705 * dumped into a <tt>String</tt> array using the <tt>toArray</tt> method,
0706 * and this array is returned.
0707 *
0708 * @return the names of the children of this preference node.
0709 * @throws BackingStoreException if this operation cannot be completed
0710 * due to a failure in the backing store, or inability to
0711 * communicate with it.
0712 * @throws IllegalStateException if this node (or an ancestor) has been
0713 * removed with the {@link #removeNode()} method.
0714 * @see #cachedChildren()
0715 */
0716 public String[] childrenNames() throws BackingStoreException {
0717 synchronized (lock) {
0718 if (removed)
0719 throw new IllegalStateException(
0720 "Node has been removed.");
0721
0722 Set s = new TreeSet(kidCache.keySet());
0723 String[] kids = childrenNamesSpi();
0724 for (int i = 0; i < kids.length; i++)
0725 s.add(kids[i]);
0726 return (String[]) s.toArray(EMPTY_STRING_ARRAY);
0727 }
0728 }
0729
0730 private static final String[] EMPTY_STRING_ARRAY = new String[0];
0731
0732 /**
0733 * Returns all known unremoved children of this node.
0734 *
0735 * @return all known unremoved children of this node.
0736 */
0737 protected final AbstractPreferences[] cachedChildren() {
0738 return (AbstractPreferences[]) kidCache.values().toArray(
0739 EMPTY_ABSTRACT_PREFS_ARRAY);
0740 }
0741
0742 private static final AbstractPreferences[] EMPTY_ABSTRACT_PREFS_ARRAY = new AbstractPreferences[0];
0743
0744 /**
0745 * Implements the <tt>parent</tt> method as per the specification in
0746 * {@link Preferences#parent()}.
0747 *
0748 * <p>This implementation obtains this preference node's lock, checks that
0749 * the node has not been removed and returns the parent value that was
0750 * passed to this node's constructor.
0751 *
0752 * @return the parent of this preference node.
0753 * @throws IllegalStateException if this node (or an ancestor) has been
0754 * removed with the {@link #removeNode()} method.
0755 */
0756 public Preferences parent() {
0757 synchronized (lock) {
0758 if (removed)
0759 throw new IllegalStateException(
0760 "Node has been removed.");
0761
0762 return parent;
0763 }
0764 }
0765
0766 /**
0767 * Implements the <tt>node</tt> method as per the specification in
0768 * {@link Preferences#node(String)}.
0769 *
0770 * <p>This implementation obtains this preference node's lock and checks
0771 * that the node has not been removed. If <tt>path</tt> is <tt>""</tt>,
0772 * this node is returned; if <tt>path</tt> is <tt>"/"</tt>, this node's
0773 * root is returned. If the first character in <tt>path</tt> is
0774 * not <tt>'/'</tt>, the implementation breaks <tt>path</tt> into
0775 * tokens and recursively traverses the path from this node to the
0776 * named node, "consuming" a name and a slash from <tt>path</tt> at
0777 * each step of the traversal. At each step, the current node is locked
0778 * and the node's child-cache is checked for the named node. If it is
0779 * not found, the name is checked to make sure its length does not
0780 * exceed <tt>MAX_NAME_LENGTH</tt>. Then the {@link #childSpi(String)}
0781 * method is invoked, and the result stored in this node's child-cache.
0782 * If the newly created <tt>Preferences</tt> object's {@link #newNode}
0783 * field is <tt>true</tt> and there are any node change listeners,
0784 * a notification event is enqueued for processing by the event dispatch
0785 * thread.
0786 *
0787 * <p>When there are no more tokens, the last value found in the
0788 * child-cache or returned by <tt>childSpi</tt> is returned by this
0789 * method. If during the traversal, two <tt>"/"</tt> tokens occur
0790 * consecutively, or the final token is <tt>"/"</tt> (rather than a name),
0791 * an appropriate <tt>IllegalArgumentException</tt> is thrown.
0792 *
0793 * <p> If the first character of <tt>path</tt> is <tt>'/'</tt>
0794 * (indicating an absolute path name) this preference node's
0795 * lock is dropped prior to breaking <tt>path</tt> into tokens, and
0796 * this method recursively traverses the path starting from the root
0797 * (rather than starting from this node). The traversal is otherwise
0798 * identical to the one described for relative path names. Dropping
0799 * the lock on this node prior to commencing the traversal at the root
0800 * node is essential to avoid the possibility of deadlock, as per the
0801 * {@link #lock locking invariant}.
0802 *
0803 * @param path the path name of the preference node to return.
0804 * @return the specified preference node.
0805 * @throws IllegalArgumentException if the path name is invalid (i.e.,
0806 * it contains multiple consecutive slash characters, or ends
0807 * with a slash character and is more than one character long).
0808 * @throws IllegalStateException if this node (or an ancestor) has been
0809 * removed with the {@link #removeNode()} method.
0810 */
0811 public Preferences node(String path) {
0812 synchronized (lock) {
0813 if (removed)
0814 throw new IllegalStateException(
0815 "Node has been removed.");
0816 if (path.equals(""))
0817 return this ;
0818 if (path.equals("/"))
0819 return root;
0820 if (path.charAt(0) != '/')
0821 return node(new StringTokenizer(path, "/", true));
0822 }
0823
0824 // Absolute path. Note that we've dropped our lock to avoid deadlock
0825 return root.node(new StringTokenizer(path.substring(1), "/",
0826 true));
0827 }
0828
0829 /**
0830 * tokenizer contains <name> {'/' <name>}*
0831 */
0832 private Preferences node(StringTokenizer path) {
0833 String token = path.nextToken();
0834 if (token.equals("/")) // Check for consecutive slashes
0835 throw new IllegalArgumentException(
0836 "Consecutive slashes in path");
0837 synchronized (lock) {
0838 AbstractPreferences child = (AbstractPreferences) kidCache
0839 .get(token);
0840 if (child == null) {
0841 if (token.length() > MAX_NAME_LENGTH)
0842 throw new IllegalArgumentException("Node name "
0843 + token + " too long");
0844 child = childSpi(token);
0845 if (child.newNode)
0846 enqueueNodeAddedEvent(child);
0847 kidCache.put(token, child);
0848 }
0849 if (!path.hasMoreTokens())
0850 return child;
0851 path.nextToken(); // Consume slash
0852 if (!path.hasMoreTokens())
0853 throw new IllegalArgumentException(
0854 "Path ends with slash");
0855 return child.node(path);
0856 }
0857 }
0858
0859 /**
0860 * Implements the <tt>nodeExists</tt> method as per the specification in
0861 * {@link Preferences#nodeExists(String)}.
0862 *
0863 * <p>This implementation is very similar to {@link #node(String)},
0864 * except that {@link #getChild(String)} is used instead of {@link
0865 * #childSpi(String)}.
0866 *
0867 * @param path the path name of the node whose existence is to be checked.
0868 * @return true if the specified node exists.
0869 * @throws BackingStoreException if this operation cannot be completed
0870 * due to a failure in the backing store, or inability to
0871 * communicate with it.
0872 * @throws IllegalArgumentException if the path name is invalid (i.e.,
0873 * it contains multiple consecutive slash characters, or ends
0874 * with a slash character and is more than one character long).
0875 * @throws IllegalStateException if this node (or an ancestor) has been
0876 * removed with the {@link #removeNode()} method and
0877 * <tt>pathname</tt> is not the empty string (<tt>""</tt>).
0878 */
0879 public boolean nodeExists(String path) throws BackingStoreException {
0880 synchronized (lock) {
0881 if (path.equals(""))
0882 return !removed;
0883 if (removed)
0884 throw new IllegalStateException(
0885 "Node has been removed.");
0886 if (path.equals("/"))
0887 return true;
0888 if (path.charAt(0) != '/')
0889 return nodeExists(new StringTokenizer(path, "/", true));
0890 }
0891
0892 // Absolute path. Note that we've dropped our lock to avoid deadlock
0893 return root.nodeExists(new StringTokenizer(path.substring(1),
0894 "/", true));
0895 }
0896
0897 /**
0898 * tokenizer contains <name> {'/' <name>}*
0899 */
0900 private boolean nodeExists(StringTokenizer path)
0901 throws BackingStoreException {
0902 String token = path.nextToken();
0903 if (token.equals("/")) // Check for consecutive slashes
0904 throw new IllegalArgumentException(
0905 "Consecutive slashes in path");
0906 synchronized (lock) {
0907 AbstractPreferences child = (AbstractPreferences) kidCache
0908 .get(token);
0909 if (child == null)
0910 child = getChild(token);
0911 if (child == null)
0912 return false;
0913 if (!path.hasMoreTokens())
0914 return true;
0915 path.nextToken(); // Consume slash
0916 if (!path.hasMoreTokens())
0917 throw new IllegalArgumentException(
0918 "Path ends with slash");
0919 return child.nodeExists(path);
0920 }
0921 }
0922
0923 /**
0924
0925 * Implements the <tt>removeNode()</tt> method as per the specification in
0926 * {@link Preferences#removeNode()}.
0927 *
0928 * <p>This implementation checks to see that this node is the root; if so,
0929 * it throws an appropriate exception. Then, it locks this node's parent,
0930 * and calls a recursive helper method that traverses the subtree rooted at
0931 * this node. The recursive method locks the node on which it was called,
0932 * checks that it has not already been removed, and then ensures that all
0933 * of its children are cached: The {@link #childrenNamesSpi()} method is
0934 * invoked and each returned child name is checked for containment in the
0935 * child-cache. If a child is not already cached, the {@link
0936 * #childSpi(String)} method is invoked to create a <tt>Preferences</tt>
0937 * instance for it, and this instance is put into the child-cache. Then
0938 * the helper method calls itself recursively on each node contained in its
0939 * child-cache. Next, it invokes {@link #removeNodeSpi()}, marks itself
0940 * as removed, and removes itself from its parent's child-cache. Finally,
0941 * if there are any node change listeners, it enqueues a notification
0942 * event for processing by the event dispatch thread.
0943 *
0944 * <p>Note that the helper method is always invoked with all ancestors up
0945 * to the "closest non-removed ancestor" locked.
0946 *
0947 * @throws IllegalStateException if this node (or an ancestor) has already
0948 * been removed with the {@link #removeNode()} method.
0949 * @throws UnsupportedOperationException if this method is invoked on
0950 * the root node.
0951 * @throws BackingStoreException if this operation cannot be completed
0952 * due to a failure in the backing store, or inability to
0953 * communicate with it.
0954 */
0955 public void removeNode() throws BackingStoreException {
0956 if (this == root)
0957 throw new UnsupportedOperationException(
0958 "Can't remove the root!");
0959 synchronized (parent.lock) {
0960 removeNode2();
0961 parent.kidCache.remove(name);
0962 }
0963 }
0964
0965 /*
0966 * Called with locks on all nodes on path from parent of "removal root"
0967 * to this (including the former but excluding the latter).
0968 */
0969 private void removeNode2() throws BackingStoreException {
0970 synchronized (lock) {
0971 if (removed)
0972 throw new IllegalStateException("Node already removed.");
0973
0974 // Ensure that all children are cached
0975 String[] kidNames = childrenNamesSpi();
0976 for (int i = 0; i < kidNames.length; i++)
0977 if (!kidCache.containsKey(kidNames[i]))
0978 kidCache.put(kidNames[i], childSpi(kidNames[i]));
0979
0980 // Recursively remove all cached children
0981 for (Iterator i = kidCache.values().iterator(); i.hasNext();) {
0982 try {
0983 ((AbstractPreferences) i.next()).removeNode2();
0984 i.remove();
0985 } catch (BackingStoreException x) {
0986 }
0987 }
0988
0989 // Now we have no descendants - it's time to die!
0990 removeNodeSpi();
0991 removed = true;
0992 parent.enqueueNodeRemovedEvent(this );
0993 }
0994 }
0995
0996 /**
0997 * Implements the <tt>name</tt> method as per the specification in
0998 * {@link Preferences#name()}.
0999 *
1000 * <p>This implementation merely returns the name that was
1001 * passed to this node's constructor.
1002 *
1003 * @return this preference node's name, relative to its parent.
1004 */
1005 public String name() {
1006 return name;
1007 }
1008
1009 /**
1010 * Implements the <tt>absolutePath</tt> method as per the specification in
1011 * {@link Preferences#absolutePath()}.
1012 *
1013 * <p>This implementation merely returns the absolute path name that
1014 * was computed at the time that this node was constructed (based on
1015 * the name that was passed to this node's constructor, and the names
1016 * that were passed to this node's ancestors' constructors).
1017 *
1018 * @return this preference node's absolute path name.
1019 */
1020 public String absolutePath() {
1021 return absolutePath;
1022 }
1023
1024 /**
1025 * Implements the <tt>isUserNode</tt> method as per the specification in
1026 * {@link Preferences#isUserNode()}.
1027 *
1028 * <p>This implementation compares this node's root node (which is stored
1029 * in a private field) with the value returned by
1030 * {@link Preferences#userRoot()}. If the two object references are
1031 * identical, this method returns true.
1032 *
1033 * @return <tt>true</tt> if this preference node is in the user
1034 * preference tree, <tt>false</tt> if it's in the system
1035 * preference tree.
1036 */
1037 public boolean isUserNode() {
1038 Boolean result = (Boolean) AccessController
1039 .doPrivileged(new PrivilegedAction() {
1040 public Object run() {
1041 return Boolean.valueOf(root == Preferences
1042 .userRoot());
1043 }
1044 });
1045 return result.booleanValue();
1046 }
1047
1048 public void addPreferenceChangeListener(PreferenceChangeListener pcl) {
1049 if (pcl == null)
1050 throw new NullPointerException("Change listener is null.");
1051 synchronized (lock) {
1052 if (removed)
1053 throw new IllegalStateException(
1054 "Node has been removed.");
1055
1056 // Copy-on-write
1057 PreferenceChangeListener[] old = prefListeners;
1058 prefListeners = new PreferenceChangeListener[old.length + 1];
1059 System.arraycopy(old, 0, prefListeners, 0, old.length);
1060 prefListeners[old.length] = pcl;
1061 }
1062 startEventDispatchThreadIfNecessary();
1063 }
1064
1065 public void removePreferenceChangeListener(
1066 PreferenceChangeListener pcl) {
1067 synchronized (lock) {
1068 if (removed)
1069 throw new IllegalStateException(
1070 "Node has been removed.");
1071 if ((prefListeners == null) || (prefListeners.length == 0))
1072 throw new IllegalArgumentException(
1073 "Listener not registered.");
1074
1075 // Copy-on-write
1076 PreferenceChangeListener[] newPl = new PreferenceChangeListener[prefListeners.length - 1];
1077 int i = 0;
1078 while (i < newPl.length && prefListeners[i] != pcl)
1079 newPl[i] = prefListeners[i++];
1080
1081 if (i == newPl.length && prefListeners[i] != pcl)
1082 throw new IllegalArgumentException(
1083 "Listener not registered.");
1084 while (i < newPl.length)
1085 newPl[i] = prefListeners[++i];
1086 prefListeners = newPl;
1087 }
1088 }
1089
1090 public void addNodeChangeListener(NodeChangeListener ncl) {
1091 if (ncl == null)
1092 throw new NullPointerException("Change listener is null.");
1093 synchronized (lock) {
1094 if (removed)
1095 throw new IllegalStateException(
1096 "Node has been removed.");
1097
1098 // Copy-on-write
1099 if (nodeListeners == null) {
1100 nodeListeners = new NodeChangeListener[1];
1101 nodeListeners[0] = ncl;
1102 } else {
1103 NodeChangeListener[] old = nodeListeners;
1104 nodeListeners = new NodeChangeListener[old.length + 1];
1105 System.arraycopy(old, 0, nodeListeners, 0, old.length);
1106 nodeListeners[old.length] = ncl;
1107 }
1108 }
1109 startEventDispatchThreadIfNecessary();
1110 }
1111
1112 public void removeNodeChangeListener(NodeChangeListener ncl) {
1113 synchronized (lock) {
1114 if (removed)
1115 throw new IllegalStateException(
1116 "Node has been removed.");
1117 if ((nodeListeners == null) || (nodeListeners.length == 0))
1118 throw new IllegalArgumentException(
1119 "Listener not registered.");
1120
1121 // Copy-on-write
1122 int i = 0;
1123 while (i < nodeListeners.length && nodeListeners[i] != ncl)
1124 i++;
1125 if (i == nodeListeners.length)
1126 throw new IllegalArgumentException(
1127 "Listener not registered.");
1128 NodeChangeListener[] newNl = new NodeChangeListener[nodeListeners.length - 1];
1129 if (i != 0)
1130 System.arraycopy(nodeListeners, 0, newNl, 0, i);
1131 if (i != newNl.length)
1132 System.arraycopy(nodeListeners, i + 1, newNl, i,
1133 newNl.length - i);
1134 nodeListeners = newNl;
1135 }
1136 }
1137
1138 // "SPI" METHODS
1139
1140 /**
1141 * Put the given key-value association into this preference node. It is
1142 * guaranteed that <tt>key</tt> and <tt>value</tt> are non-null and of
1143 * legal length. Also, it is guaranteed that this node has not been
1144 * removed. (The implementor needn't check for any of these things.)
1145 *
1146 * <p>This method is invoked with the lock on this node held.
1147 */
1148 protected abstract void putSpi(String key, String value);
1149
1150 /**
1151 * Return the value associated with the specified key at this preference
1152 * node, or <tt>null</tt> if there is no association for this key, or the
1153 * association cannot be determined at this time. It is guaranteed that
1154 * <tt>key</tt> is non-null. Also, it is guaranteed that this node has
1155 * not been removed. (The implementor needn't check for either of these
1156 * things.)
1157 *
1158 * <p> Generally speaking, this method should not throw an exception
1159 * under any circumstances. If, however, if it does throw an exception,
1160 * the exception will be intercepted and treated as a <tt>null</tt>
1161 * return value.
1162 *
1163 * <p>This method is invoked with the lock on this node held.
1164 *
1165 * @return the value associated with the specified key at this preference
1166 * node, or <tt>null</tt> if there is no association for this
1167 * key, or the association cannot be determined at this time.
1168 */
1169 protected abstract String getSpi(String key);
1170
1171 /**
1172 * Remove the association (if any) for the specified key at this
1173 * preference node. It is guaranteed that <tt>key</tt> is non-null.
1174 * Also, it is guaranteed that this node has not been removed.
1175 * (The implementor needn't check for either of these things.)
1176 *
1177 * <p>This method is invoked with the lock on this node held.
1178 */
1179 protected abstract void removeSpi(String key);
1180
1181 /**
1182 * Removes this preference node, invalidating it and any preferences that
1183 * it contains. The named child will have no descendants at the time this
1184 * invocation is made (i.e., the {@link Preferences#removeNode()} method
1185 * invokes this method repeatedly in a bottom-up fashion, removing each of
1186 * a node's descendants before removing the node itself).
1187 *
1188 * <p>This method is invoked with the lock held on this node and its
1189 * parent (and all ancestors that are being removed as a
1190 * result of a single invocation to {@link Preferences#removeNode()}).
1191 *
1192 * <p>The removal of a node needn't become persistent until the
1193 * <tt>flush</tt> method is invoked on this node (or an ancestor).
1194 *
1195 * <p>If this node throws a <tt>BackingStoreException</tt>, the exception
1196 * will propagate out beyond the enclosing {@link #removeNode()}
1197 * invocation.
1198 *
1199 * @throws BackingStoreException if this operation cannot be completed
1200 * due to a failure in the backing store, or inability to
1201 * communicate with it.
1202 */
1203 protected abstract void removeNodeSpi()
1204 throws BackingStoreException;
1205
1206 /**
1207 * Returns all of the keys that have an associated value in this
1208 * preference node. (The returned array will be of size zero if
1209 * this node has no preferences.) It is guaranteed that this node has not
1210 * been removed.
1211 *
1212 * <p>This method is invoked with the lock on this node held.
1213 *
1214 * <p>If this node throws a <tt>BackingStoreException</tt>, the exception
1215 * will propagate out beyond the enclosing {@link #keys()} invocation.
1216 *
1217 * @return an array of the keys that have an associated value in this
1218 * preference node.
1219 * @throws BackingStoreException if this operation cannot be completed
1220 * due to a failure in the backing store, or inability to
1221 * communicate with it.
1222 */
1223 protected abstract String[] keysSpi() throws BackingStoreException;
1224
1225 /**
1226 * Returns the names of the children of this preference node. (The
1227 * returned array will be of size zero if this node has no children.)
1228 * This method need not return the names of any nodes already cached,
1229 * but may do so without harm.
1230 *
1231 * <p>This method is invoked with the lock on this node held.
1232 *
1233 * <p>If this node throws a <tt>BackingStoreException</tt>, the exception
1234 * will propagate out beyond the enclosing {@link #childrenNames()}
1235 * invocation.
1236 *
1237 * @return an array containing the names of the children of this
1238 * preference node.
1239 * @throws BackingStoreException if this operation cannot be completed
1240 * due to a failure in the backing store, or inability to
1241 * communicate with it.
1242 */
1243 protected abstract String[] childrenNamesSpi()
1244 throws BackingStoreException;
1245
1246 /**
1247 * Returns the named child if it exists, or <tt>null</tt> if it does not.
1248 * It is guaranteed that <tt>nodeName</tt> is non-null, non-empty,
1249 * does not contain the slash character ('/'), and is no longer than
1250 * {@link #MAX_NAME_LENGTH} characters. Also, it is guaranteed
1251 * that this node has not been removed. (The implementor needn't check
1252 * for any of these things if he chooses to override this method.)
1253 *
1254 * <p>Finally, it is guaranteed that the named node has not been returned
1255 * by a previous invocation of this method or {@link #childSpi} after the
1256 * last time that it was removed. In other words, a cached value will
1257 * always be used in preference to invoking this method. (The implementor
1258 * needn't maintain his own cache of previously returned children if he
1259 * chooses to override this method.)
1260 *
1261 * <p>This implementation obtains this preference node's lock, invokes
1262 * {@link #childrenNames()} to get an array of the names of this node's
1263 * children, and iterates over the array comparing the name of each child
1264 * with the specified node name. If a child node has the correct name,
1265 * the {@link #childSpi(String)} method is invoked and the resulting
1266 * node is returned. If the iteration completes without finding the
1267 * specified name, <tt>null</tt> is returned.
1268 *
1269 * @param nodeName name of the child to be searched for.
1270 * @return the named child if it exists, or null if it does not.
1271 * @throws BackingStoreException if this operation cannot be completed
1272 * due to a failure in the backing store, or inability to
1273 * communicate with it.
1274 */
1275 protected AbstractPreferences getChild(String nodeName)
1276 throws BackingStoreException {
1277 synchronized (lock) {
1278 // assert kidCache.get(nodeName)==null;
1279 String[] kidNames = childrenNames();
1280 for (int i = 0; i < kidNames.length; i++)
1281 if (kidNames[i].equals(nodeName))
1282 return childSpi(kidNames[i]);
1283 }
1284 return null;
1285 }
1286
1287 /**
1288 * Returns the named child of this preference node, creating it if it does
1289 * not already exist. It is guaranteed that <tt>name</tt> is non-null,
1290 * non-empty, does not contain the slash character ('/'), and is no longer
1291 * than {@link #MAX_NAME_LENGTH} characters. Also, it is guaranteed that
1292 * this node has not been removed. (The implementor needn't check for any
1293 * of these things.)
1294 *
1295 * <p>Finally, it is guaranteed that the named node has not been returned
1296 * by a previous invocation of this method or {@link #getChild(String)}
1297 * after the last time that it was removed. In other words, a cached
1298 * value will always be used in preference to invoking this method.
1299 * Subclasses need not maintain their own cache of previously returned
1300 * children.
1301 *
1302 * <p>The implementer must ensure that the returned node has not been
1303 * removed. If a like-named child of this node was previously removed, the
1304 * implementer must return a newly constructed <tt>AbstractPreferences</tt>
1305 * node; once removed, an <tt>AbstractPreferences</tt> node
1306 * cannot be "resuscitated."
1307 *
1308 * <p>If this method causes a node to be created, this node is not
1309 * guaranteed to be persistent until the <tt>flush</tt> method is
1310 * invoked on this node or one of its ancestors (or descendants).
1311 *
1312 * <p>This method is invoked with the lock on this node held.
1313 *
1314 * @param name The name of the child node to return, relative to
1315 * this preference node.
1316 * @return The named child node.
1317 */
1318 protected abstract AbstractPreferences childSpi(String name);
1319
1320 /**
1321 * Returns the absolute path name of this preferences node.
1322 */
1323 public String toString() {
1324 return (this .isUserNode() ? "User" : "System")
1325 + " Preference Node: " + this .absolutePath();
1326 }
1327
1328 /**
1329 * Implements the <tt>sync</tt> method as per the specification in
1330 * {@link Preferences#sync()}.
1331 *
1332 * <p>This implementation calls a recursive helper method that locks this
1333 * node, invokes syncSpi() on it, unlocks this node, and recursively
1334 * invokes this method on each "cached child." A cached child is a child
1335 * of this node that has been created in this VM and not subsequently
1336 * removed. In effect, this method does a depth first traversal of the
1337 * "cached subtree" rooted at this node, calling syncSpi() on each node in
1338 * the subTree while only that node is locked. Note that syncSpi() is
1339 * invoked top-down.
1340 *
1341 * @throws BackingStoreException if this operation cannot be completed
1342 * due to a failure in the backing store, or inability to
1343 * communicate with it.
1344 * @throws IllegalStateException if this node (or an ancestor) has been
1345 * removed with the {@link #removeNode()} method.
1346 * @see #flush()
1347 */
1348 public void sync() throws BackingStoreException {
1349 sync2();
1350 }
1351
1352 private void sync2() throws BackingStoreException {
1353 AbstractPreferences[] cachedKids;
1354
1355 synchronized (lock) {
1356 if (removed)
1357 throw new IllegalStateException("Node has been removed");
1358 syncSpi();
1359 cachedKids = cachedChildren();
1360 }
1361
1362 for (int i = 0; i < cachedKids.length; i++)
1363 cachedKids[i].sync2();
1364 }
1365
1366 /**
1367 * This method is invoked with this node locked. The contract of this
1368 * method is to synchronize any cached preferences stored at this node
1369 * with any stored in the backing store. (It is perfectly possible that
1370 * this node does not exist on the backing store, either because it has
1371 * been deleted by another VM, or because it has not yet been created.)
1372 * Note that this method should <i>not</i> synchronize the preferences in
1373 * any subnodes of this node. If the backing store naturally syncs an
1374 * entire subtree at once, the implementer is encouraged to override
1375 * sync(), rather than merely overriding this method.
1376 *
1377 * <p>If this node throws a <tt>BackingStoreException</tt>, the exception
1378 * will propagate out beyond the enclosing {@link #sync()} invocation.
1379 *
1380 * @throws BackingStoreException if this operation cannot be completed
1381 * due to a failure in the backing store, or inability to
1382 * communicate with it.
1383 */
1384 protected abstract void syncSpi() throws BackingStoreException;
1385
1386 /**
1387 * Implements the <tt>flush</tt> method as per the specification in
1388 * {@link Preferences#flush()}.
1389 *
1390 * <p>This implementation calls a recursive helper method that locks this
1391 * node, invokes flushSpi() on it, unlocks this node, and recursively
1392 * invokes this method on each "cached child." A cached child is a child
1393 * of this node that has been created in this VM and not subsequently
1394 * removed. In effect, this method does a depth first traversal of the
1395 * "cached subtree" rooted at this node, calling flushSpi() on each node in
1396 * the subTree while only that node is locked. Note that flushSpi() is
1397 * invoked top-down.
1398 *
1399 * <p> If this method is invoked on a node that has been removed with
1400 * the {@link #removeNode()} method, flushSpi() is invoked on this node,
1401 * but not on others.
1402 *
1403 * @throws BackingStoreException if this operation cannot be completed
1404 * due to a failure in the backing store, or inability to
1405 * communicate with it.
1406 * @see #flush()
1407 */
1408 public void flush() throws BackingStoreException {
1409 flush2();
1410 }
1411
1412 private void flush2() throws BackingStoreException {
1413 AbstractPreferences[] cachedKids;
1414
1415 synchronized (lock) {
1416 flushSpi();
1417 if (removed)
1418 return;
1419 cachedKids = cachedChildren();
1420 }
1421
1422 for (int i = 0; i < cachedKids.length; i++)
1423 cachedKids[i].flush2();
1424 }
1425
1426 /**
1427 * This method is invoked with this node locked. The contract of this
1428 * method is to force any cached changes in the contents of this
1429 * preference node to the backing store, guaranteeing their persistence.
1430 * (It is perfectly possible that this node does not exist on the backing
1431 * store, either because it has been deleted by another VM, or because it
1432 * has not yet been created.) Note that this method should <i>not</i>
1433 * flush the preferences in any subnodes of this node. If the backing
1434 * store naturally flushes an entire subtree at once, the implementer is
1435 * encouraged to override flush(), rather than merely overriding this
1436 * method.
1437 *
1438 * <p>If this node throws a <tt>BackingStoreException</tt>, the exception
1439 * will propagate out beyond the enclosing {@link #flush()} invocation.
1440 *
1441 * @throws BackingStoreException if this operation cannot be completed
1442 * due to a failure in the backing store, or inability to
1443 * communicate with it.
1444 */
1445 protected abstract void flushSpi() throws BackingStoreException;
1446
1447 /**
1448 * Returns <tt>true</tt> iff this node (or an ancestor) has been
1449 * removed with the {@link #removeNode()} method. This method
1450 * locks this node prior to returning the contents of the private
1451 * field used to track this state.
1452 *
1453 * @return <tt>true</tt> iff this node (or an ancestor) has been
1454 * removed with the {@link #removeNode()} method.
1455 */
1456 protected boolean isRemoved() {
1457 synchronized (lock) {
1458 return removed;
1459 }
1460 }
1461
1462 /**
1463 * Queue of pending notification events. When a preference or node
1464 * change event for which there are one or more listeners occurs,
1465 * it is placed on this queue and the queue is notified. A background
1466 * thread waits on this queue and delivers the events. This decouples
1467 * event delivery from preference activity, greatly simplifying
1468 * locking and reducing opportunity for deadlock.
1469 */
1470 private static final List eventQueue = new LinkedList();
1471
1472 /**
1473 * These two classes are used to distinguish NodeChangeEvents on
1474 * eventQueue so the event dispatch thread knows whether to call
1475 * childAdded or childRemoved.
1476 */
1477 private class NodeAddedEvent extends NodeChangeEvent {
1478 private static final long serialVersionUID = -6743557530157328528L;
1479
1480 NodeAddedEvent(Preferences parent, Preferences child) {
1481 super (parent, child);
1482 }
1483 }
1484
1485 private class NodeRemovedEvent extends NodeChangeEvent {
1486 private static final long serialVersionUID = 8735497392918824837L;
1487
1488 NodeRemovedEvent(Preferences parent, Preferences child) {
1489 super (parent, child);
1490 }
1491 }
1492
1493 /**
1494 * A single background thread ("the event notification thread") monitors
1495 * the event queue and delivers events that are placed on the queue.
1496 */
1497 private static class EventDispatchThread extends Thread {
1498 public void run() {
1499 while (true) {
1500 // Wait on eventQueue till an event is present
1501 EventObject event = null;
1502 synchronized (eventQueue) {
1503 try {
1504 while (eventQueue.isEmpty())
1505 eventQueue.wait();
1506 event = (EventObject) eventQueue.remove(0);
1507 } catch (InterruptedException e) {
1508 // XXX Log "Event dispatch thread interrupted. Exiting"
1509 return;
1510 }
1511 }
1512
1513 // Now we have event & hold no locks; deliver evt to listeners
1514 AbstractPreferences src = (AbstractPreferences) event
1515 .getSource();
1516 if (event instanceof PreferenceChangeEvent) {
1517 PreferenceChangeEvent pce = (PreferenceChangeEvent) event;
1518 PreferenceChangeListener[] listeners = src
1519 .prefListeners();
1520 for (int i = 0; i < listeners.length; i++)
1521 listeners[i].preferenceChange(pce);
1522 } else {
1523 NodeChangeEvent nce = (NodeChangeEvent) event;
1524 NodeChangeListener[] listeners = src
1525 .nodeListeners();
1526 if (nce instanceof NodeAddedEvent) {
1527 for (int i = 0; i < listeners.length; i++)
1528 listeners[i].childAdded(nce);
1529 } else {
1530 // assert nce instanceof NodeRemovedEvent;
1531 for (int i = 0; i < listeners.length; i++)
1532 listeners[i].childRemoved(nce);
1533 }
1534 }
1535 }
1536 }
1537 }
1538
1539 private static Thread eventDispatchThread = null;
1540
1541 /**
1542 * This method starts the event dispatch thread the first time it
1543 * is called. The event dispatch thread will be started only
1544 * if someone registers a listener.
1545 */
1546 private static synchronized void startEventDispatchThreadIfNecessary() {
1547 if (eventDispatchThread == null) {
1548 // XXX Log "Starting event dispatch thread"
1549 eventDispatchThread = new EventDispatchThread();
1550 eventDispatchThread.setDaemon(true);
1551 eventDispatchThread.start();
1552 }
1553 }
1554
1555 /**
1556 * Return this node's preference/node change listeners. Even though
1557 * we're using a copy-on-write lists, we use synchronized accessors to
1558 * ensure information transmission from the writing thread to the
1559 * reading thread.
1560 */
1561 PreferenceChangeListener[] prefListeners() {
1562 synchronized (lock) {
1563 return prefListeners;
1564 }
1565 }
1566
1567 NodeChangeListener[] nodeListeners() {
1568 synchronized (lock) {
1569 return nodeListeners;
1570 }
1571 }
1572
1573 /**
1574 * Enqueue a preference change event for delivery to registered
1575 * preference change listeners unless there are no registered
1576 * listeners. Invoked with this.lock held.
1577 */
1578 private void enqueuePreferenceChangeEvent(String key,
1579 String newValue) {
1580 if (prefListeners.length != 0) {
1581 synchronized (eventQueue) {
1582 eventQueue.add(new PreferenceChangeEvent(this , key,
1583 newValue));
1584 eventQueue.notify();
1585 }
1586 }
1587 }
1588
1589 /**
1590 * Enqueue a "node added" event for delivery to registered node change
1591 * listeners unless there are no registered listeners. Invoked with
1592 * this.lock held.
1593 */
1594 private void enqueueNodeAddedEvent(Preferences child) {
1595 if (nodeListeners.length != 0) {
1596 synchronized (eventQueue) {
1597 eventQueue.add(new NodeAddedEvent(this , child));
1598 eventQueue.notify();
1599 }
1600 }
1601 }
1602
1603 /**
1604 * Enqueue a "node removed" event for delivery to registered node change
1605 * listeners unless there are no registered listeners. Invoked with
1606 * this.lock held.
1607 */
1608 private void enqueueNodeRemovedEvent(Preferences child) {
1609 if (nodeListeners.length != 0) {
1610 synchronized (eventQueue) {
1611 eventQueue.add(new NodeRemovedEvent(this , child));
1612 eventQueue.notify();
1613 }
1614 }
1615 }
1616
1617 /**
1618 * Implements the <tt>exportNode</tt> method as per the specification in
1619 * {@link Preferences#exportNode(OutputStream)}.
1620 *
1621 * @param os the output stream on which to emit the XML document.
1622 * @throws IOException if writing to the specified output stream
1623 * results in an <tt>IOException</tt>.
1624 * @throws BackingStoreException if preference data cannot be read from
1625 * backing store.
1626 */
1627 public void exportNode(OutputStream os) throws IOException,
1628 BackingStoreException {
1629 XmlSupport.export(os, this , false);
1630 }
1631
1632 /**
1633 * Implements the <tt>exportSubtree</tt> method as per the specification in
1634 * {@link Preferences#exportSubtree(OutputStream)}.
1635 *
1636 * @param os the output stream on which to emit the XML document.
1637 * @throws IOException if writing to the specified output stream
1638 * results in an <tt>IOException</tt>.
1639 * @throws BackingStoreException if preference data cannot be read from
1640 * backing store.
1641 */
1642 public void exportSubtree(OutputStream os) throws IOException,
1643 BackingStoreException {
1644 XmlSupport.export(os, this , true);
1645 }
1646 }
|