Path: blob/master/src/java.prefs/share/classes/java/util/prefs/AbstractPreferences.java
41159 views
/*1* Copyright (c) 2000, 2021, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package java.util.prefs;2627import java.util.*;28import java.io.*;29import java.security.AccessController;30import java.security.PrivilegedAction;31// These imports needed only as a workaround for a JavaDoc bug32import java.lang.Integer;33import java.lang.Long;34import java.lang.Float;35import java.lang.Double;3637/**38* This class provides a skeletal implementation of the {@link Preferences}39* class, greatly easing the task of implementing it.40*41* <p><strong>This class is for {@code Preferences} implementers only.42* Normal users of the {@code Preferences} facility should have no need to43* consult this documentation. The {@link Preferences} documentation44* should suffice.</strong>45*46* <p>Implementors must override the nine abstract service-provider interface47* (SPI) methods: {@link #getSpi(String)}, {@link #putSpi(String,String)},48* {@link #removeSpi(String)}, {@link #childSpi(String)}, {@link49* #removeNodeSpi()}, {@link #keysSpi()}, {@link #childrenNamesSpi()}, {@link50* #syncSpi()} and {@link #flushSpi()}. All of the concrete methods specify51* precisely how they are implemented atop these SPI methods. The implementor52* may, at his discretion, override one or more of the concrete methods if the53* default implementation is unsatisfactory for any reason, such as54* performance.55*56* <p>The SPI methods fall into three groups concerning exception57* behavior. The {@code getSpi} method should never throw exceptions, but it58* doesn't really matter, as any exception thrown by this method will be59* intercepted by {@link #get(String,String)}, which will return the specified60* default value to the caller. The {@code removeNodeSpi, keysSpi,61* childrenNamesSpi, syncSpi} and {@code flushSpi} methods are specified62* to throw {@link BackingStoreException}, and the implementation is required63* to throw this checked exception if it is unable to perform the operation.64* The exception propagates outward, causing the corresponding API method65* to fail.66*67* <p>The remaining SPI methods {@link #putSpi(String,String)}, {@link68* #removeSpi(String)} and {@link #childSpi(String)} have more complicated69* exception behavior. They are not specified to throw70* {@code BackingStoreException}, as they can generally obey their contracts71* even if the backing store is unavailable. This is true because they return72* no information and their effects are not required to become permanent until73* a subsequent call to {@link Preferences#flush()} or74* {@link Preferences#sync()}. Generally speaking, these SPI methods should not75* throw exceptions. In some implementations, there may be circumstances76* under which these calls cannot even enqueue the requested operation for77* later processing. Even under these circumstances it is generally better to78* simply ignore the invocation and return, rather than throwing an79* exception. Under these circumstances, however, subsequently invoking80* {@code flush()} or {@code sync} would not imply that all previous81* operations had successfully been made permanent.82*83* <p>There is one circumstance under which {@code putSpi, removeSpi and84* childSpi} <i>should</i> throw an exception: if the caller lacks85* sufficient privileges on the underlying operating system to perform the86* requested operation. This will, for instance, occur on most systems87* if a non-privileged user attempts to modify system preferences.88* (The required privileges will vary from implementation to89* implementation. On some implementations, they are the right to modify the90* contents of some directory in the file system; on others they are the right91* to modify contents of some key in a registry.) Under any of these92* circumstances, it would generally be undesirable to let the program93* continue executing as if these operations would become permanent at a later94* time. While implementations are not required to throw an exception under95* these circumstances, they are encouraged to do so. A {@link96* SecurityException} would be appropriate.97*98* <p>Most of the SPI methods require the implementation to read or write99* information at a preferences node. The implementor should beware of the100* fact that another VM may have concurrently deleted this node from the101* backing store. It is the implementation's responsibility to recreate the102* node if it has been deleted.103*104* <p>Implementation note: In Sun's default {@code Preferences}105* implementations, the user's identity is inherited from the underlying106* operating system and does not change for the lifetime of the virtual107* machine. It is recognized that server-side {@code Preferences}108* implementations may have the user identity change from request to request,109* implicitly passed to {@code Preferences} methods via the use of a110* static {@link ThreadLocal} instance. Authors of such implementations are111* <i>strongly</i> encouraged to determine the user at the time preferences112* are accessed (for example by the {@link #get(String,String)} or {@link113* #put(String,String)} method) rather than permanently associating a user114* with each {@code Preferences} instance. The latter behavior conflicts115* with normal {@code Preferences} usage and would lead to great confusion.116*117* @author Josh Bloch118* @see Preferences119* @since 1.4120*/121public abstract class AbstractPreferences extends Preferences {122/**123* The code point U+0000, assigned to the null control character, is the124* only character encoded in Unicode and ISO/IEC 10646 that is always125* invalid in any XML 1.0 and 1.1 document.126*/127static final int CODE_POINT_U0000 = '\u0000';128129/**130* Our name relative to parent.131*/132private final String name;133134/**135* Our absolute path name.136*/137private final String absolutePath;138139/**140* Our parent node.141*/142final AbstractPreferences parent;143144/**145* Our root node.146*/147private final AbstractPreferences root; // Relative to this node148149/**150* This field should be {@code true} if this node did not exist in the151* backing store prior to the creation of this object. The field152* is initialized to false, but may be set to true by a subclass153* constructor (and should not be modified thereafter). This field154* indicates whether a node change event should be fired when155* creation is complete.156*/157protected boolean newNode = false;158159/**160* All known unremoved children of this node. (This "cache" is consulted161* prior to calling childSpi() or getChild().162*/163private Map<String, AbstractPreferences> kidCache = new HashMap<>();164165/**166* This field is used to keep track of whether or not this node has167* been removed. Once it's set to true, it will never be reset to false.168*/169private boolean removed = false;170171/**172* Registered preference change listeners.173*/174private PreferenceChangeListener[] prefListeners =175new PreferenceChangeListener[0];176177/**178* Registered node change listeners.179*/180private NodeChangeListener[] nodeListeners = new NodeChangeListener[0];181182/**183* An object whose monitor is used to lock this node. This object184* is used in preference to the node itself to reduce the likelihood of185* intentional or unintentional denial of service due to a locked node.186* To avoid deadlock, a node is <i>never</i> locked by a thread that187* holds a lock on a descendant of that node.188*/189protected final Object lock = new Object();190191/**192* Creates a preference node with the specified parent and the specified193* name relative to its parent.194*195* @param parent the parent of this preference node, or null if this196* is the root.197* @param name the name of this preference node, relative to its parent,198* or {@code ""} if this is the root.199* @throws IllegalArgumentException if {@code name} contains a slash200* ({@code '/'}), or {@code parent} is {@code null} and201* name isn't {@code ""}.202*/203protected AbstractPreferences(AbstractPreferences parent, String name) {204if (parent==null) {205if (!name.isEmpty())206throw new IllegalArgumentException("Root name '"+name+207"' must be \"\"");208this.absolutePath = "/";209root = this;210} else {211if (name.indexOf('/') != -1)212throw new IllegalArgumentException("Name '" + name +213"' contains '/'");214if (name.isEmpty())215throw new IllegalArgumentException("Illegal name: empty string");216217root = parent.root;218absolutePath = (parent==root ? "/" + name219: parent.absolutePath() + "/" + name);220}221this.name = name;222this.parent = parent;223}224225/**226* Implements the {@code put} method as per the specification in227* {@link Preferences#put(String,String)}.228*229* <p>This implementation checks that the key and value are legal,230* obtains this preference node's lock, checks that the node231* has not been removed, invokes {@link #putSpi(String,String)}, and if232* there are any preference change listeners, enqueues a notification233* event for processing by the event dispatch thread.234*235* @param key key with which the specified value is to be associated.236* @param value value to be associated with the specified key.237* @throws NullPointerException if key or value is {@code null}.238* @throws IllegalArgumentException if {@code key.length()} exceeds239* {@code MAX_KEY_LENGTH} or if {@code value.length} exceeds240* {@code MAX_VALUE_LENGTH}.241* @throws IllegalArgumentException if either key or value contain242* the null control character, code point U+0000.243* @throws IllegalStateException if this node (or an ancestor) has been244* removed with the {@link #removeNode()} method.245*/246public void put(String key, String value) {247if (key==null || value==null)248throw new NullPointerException();249if (key.length() > MAX_KEY_LENGTH)250throw new IllegalArgumentException("Key too long: "+key);251if (value.length() > MAX_VALUE_LENGTH)252throw new IllegalArgumentException("Value too long: "+value);253if (key.indexOf(CODE_POINT_U0000) != -1)254throw new IllegalArgumentException("Key contains code point U+0000");255if (value.indexOf(CODE_POINT_U0000) != -1)256throw new IllegalArgumentException("Value contains code point U+0000");257258synchronized(lock) {259if (removed)260throw new IllegalStateException("Node has been removed.");261262putSpi(key, value);263enqueuePreferenceChangeEvent(key, value);264}265}266267/**268* Implements the {@code get} method as per the specification in269* {@link Preferences#get(String,String)}.270*271* <p>This implementation first checks to see if {@code key} is272* {@code null} throwing a {@code NullPointerException} if this is273* the case. Then it obtains this preference node's lock,274* checks that the node has not been removed, invokes {@link275* #getSpi(String)}, and returns the result, unless the {@code getSpi}276* invocation returns {@code null} or throws an exception, in which case277* this invocation returns {@code def}.278*279* @param key key whose associated value is to be returned.280* @param def the value to be returned in the event that this281* preference node has no value associated with {@code key}.282* @return the value associated with {@code key}, or {@code def}283* if no value is associated with {@code key}.284* @throws IllegalStateException if this node (or an ancestor) has been285* removed with the {@link #removeNode()} method.286* @throws NullPointerException if key is {@code null}. (A287* {@code null} default <i>is</i> permitted.)288* @throws IllegalArgumentException if key contains the null control289* character, code point U+0000.290*/291public String get(String key, String def) {292if (key==null)293throw new NullPointerException("Null key");294if (key.indexOf(CODE_POINT_U0000) != -1)295throw new IllegalArgumentException("Key contains code point U+0000");296synchronized(lock) {297if (removed)298throw new IllegalStateException("Node has been removed.");299300String result = null;301try {302result = getSpi(key);303} catch (Exception e) {304// Ignoring exception causes default to be returned305}306return (result==null ? def : result);307}308}309310/**311* Implements the {@code remove(String)} method as per the specification312* in {@link Preferences#remove(String)}.313*314* <p>This implementation obtains this preference node's lock,315* checks that the node has not been removed, invokes316* {@link #removeSpi(String)} and if there are any preference317* change listeners, enqueues a notification event for processing by the318* event dispatch thread.319*320* @param key key whose mapping is to be removed from the preference node.321* @throws IllegalStateException if this node (or an ancestor) has been322* removed with the {@link #removeNode()} method.323* @throws IllegalArgumentException if key contains the null control324* character, code point U+0000.325* @throws NullPointerException {@inheritDoc}.326*/327public void remove(String key) {328Objects.requireNonNull(key, "Specified key cannot be null");329if (key.indexOf(CODE_POINT_U0000) != -1)330throw new IllegalArgumentException("Key contains code point U+0000");331synchronized(lock) {332if (removed)333throw new IllegalStateException("Node has been removed.");334335removeSpi(key);336enqueuePreferenceChangeEvent(key, null);337}338}339340/**341* Implements the {@code clear} method as per the specification in342* {@link Preferences#clear()}.343*344* <p>This implementation obtains this preference node's lock,345* invokes {@link #keys()} to obtain an array of keys, and346* iterates over the array invoking {@link #remove(String)} on each key.347*348* @throws BackingStoreException if this operation cannot be completed349* due to a failure in the backing store, or inability to350* communicate with it.351* @throws IllegalStateException if this node (or an ancestor) has been352* removed with the {@link #removeNode()} method.353*/354public void clear() throws BackingStoreException {355synchronized(lock) {356for (String key : keys())357remove(key);358}359}360361/**362* Implements the {@code putInt} method as per the specification in363* {@link Preferences#putInt(String,int)}.364*365* <p>This implementation translates {@code value} to a string with366* {@link Integer#toString(int)} and invokes {@link #put(String,String)}367* on the result.368*369* @param key key with which the string form of value is to be associated.370* @param value value whose string form is to be associated with key.371* @throws NullPointerException if key is {@code null}.372* @throws IllegalArgumentException if {@code key.length()} exceeds373* {@code MAX_KEY_LENGTH}.374* @throws IllegalArgumentException if key contains375* the null control character, code point U+0000.376* @throws IllegalStateException if this node (or an ancestor) has been377* removed with the {@link #removeNode()} method.378*/379public void putInt(String key, int value) {380put(key, Integer.toString(value));381}382383/**384* Implements the {@code getInt} method as per the specification in385* {@link Preferences#getInt(String,int)}.386*387* <p>This implementation invokes {@link #get(String,String) get(key,388* null)}. If the return value is non-null, the implementation389* attempts to translate it to an {@code int} with390* {@link Integer#parseInt(String)}. If the attempt succeeds, the return391* value is returned by this method. Otherwise, {@code def} is returned.392*393* @param key key whose associated value is to be returned as an int.394* @param def the value to be returned in the event that this395* preference node has no value associated with {@code key}396* or the associated value cannot be interpreted as an int.397* @return the int value represented by the string associated with398* {@code key} in this preference node, or {@code def} if the399* associated value does not exist or cannot be interpreted as400* an int.401* @throws IllegalStateException if this node (or an ancestor) has been402* removed with the {@link #removeNode()} method.403* @throws NullPointerException if {@code key} is {@code null}.404* @throws IllegalArgumentException if key contains the null control405* character, code point U+0000.406*/407public int getInt(String key, int def) {408int result = def;409try {410String value = get(key, null);411if (value != null)412result = Integer.parseInt(value);413} catch (NumberFormatException e) {414// Ignoring exception causes specified default to be returned415}416417return result;418}419420/**421* Implements the {@code putLong} method as per the specification in422* {@link Preferences#putLong(String,long)}.423*424* <p>This implementation translates {@code value} to a string with425* {@link Long#toString(long)} and invokes {@link #put(String,String)}426* on the result.427*428* @param key key with which the string form of value is to be associated.429* @param value value whose string form is to be associated with key.430* @throws NullPointerException if key is {@code null}.431* @throws IllegalArgumentException if {@code key.length()} exceeds432* {@code MAX_KEY_LENGTH}.433* @throws IllegalArgumentException if key contains434* the null control character, code point U+0000.435* @throws IllegalStateException if this node (or an ancestor) has been436* removed with the {@link #removeNode()} method.437*/438public void putLong(String key, long value) {439put(key, Long.toString(value));440}441442/**443* Implements the {@code getLong} method as per the specification in444* {@link Preferences#getLong(String,long)}.445*446* <p>This implementation invokes {@link #get(String,String) get(key,447* null)}. If the return value is non-null, the implementation448* attempts to translate it to a {@code long} with449* {@link Long#parseLong(String)}. If the attempt succeeds, the return450* value is returned by this method. Otherwise, {@code def} is returned.451*452* @param key key whose associated value is to be returned as a long.453* @param def the value to be returned in the event that this454* preference node has no value associated with {@code key}455* or the associated value cannot be interpreted as a long.456* @return the long value represented by the string associated with457* {@code key} in this preference node, or {@code def} if the458* associated value does not exist or cannot be interpreted as459* a long.460* @throws IllegalStateException if this node (or an ancestor) has been461* removed with the {@link #removeNode()} method.462* @throws NullPointerException if {@code key} is {@code null}.463* @throws IllegalArgumentException if key contains the null control464* character, code point U+0000.465*/466public long getLong(String key, long def) {467long result = def;468try {469String value = get(key, null);470if (value != null)471result = Long.parseLong(value);472} catch (NumberFormatException e) {473// Ignoring exception causes specified default to be returned474}475476return result;477}478479/**480* Implements the {@code putBoolean} method as per the specification in481* {@link Preferences#putBoolean(String,boolean)}.482*483* <p>This implementation translates {@code value} to a string with484* {@link String#valueOf(boolean)} and invokes {@link #put(String,String)}485* on the result.486*487* @param key key with which the string form of value is to be associated.488* @param value value whose string form is to be associated with key.489* @throws NullPointerException if key is {@code null}.490* @throws IllegalArgumentException if {@code key.length()} exceeds491* {@code MAX_KEY_LENGTH}.492* @throws IllegalArgumentException if key contains493* the null control character, code point U+0000.494* @throws IllegalStateException if this node (or an ancestor) has been495* removed with the {@link #removeNode()} method.496*/497public void putBoolean(String key, boolean value) {498put(key, String.valueOf(value));499}500501/**502* Implements the {@code getBoolean} method as per the specification in503* {@link Preferences#getBoolean(String,boolean)}.504*505* <p>This implementation invokes {@link #get(String,String) get(key,506* null)}. If the return value is non-null, it is compared with507* {@code "true"} using {@link String#equalsIgnoreCase(String)}. If the508* comparison returns {@code true}, this invocation returns509* {@code true}. Otherwise, the original return value is compared with510* {@code "false"}, again using {@link String#equalsIgnoreCase(String)}.511* If the comparison returns {@code true}, this invocation returns512* {@code false}. Otherwise, this invocation returns {@code def}.513*514* @param key key whose associated value is to be returned as a boolean.515* @param def the value to be returned in the event that this516* preference node has no value associated with {@code key}517* or the associated value cannot be interpreted as a boolean.518* @return the boolean value represented by the string associated with519* {@code key} in this preference node, or {@code def} if the520* associated value does not exist or cannot be interpreted as521* a boolean.522* @throws IllegalStateException if this node (or an ancestor) has been523* removed with the {@link #removeNode()} method.524* @throws NullPointerException if {@code key} is {@code null}.525* @throws IllegalArgumentException if key contains the null control526* character, code point U+0000.527*/528public boolean getBoolean(String key, boolean def) {529boolean result = def;530String value = get(key, null);531if (value != null) {532if (value.equalsIgnoreCase("true"))533result = true;534else if (value.equalsIgnoreCase("false"))535result = false;536}537538return result;539}540541/**542* Implements the {@code putFloat} method as per the specification in543* {@link Preferences#putFloat(String,float)}.544*545* <p>This implementation translates {@code value} to a string with546* {@link Float#toString(float)} and invokes {@link #put(String,String)}547* on the result.548*549* @param key key with which the string form of value is to be associated.550* @param value value whose string form is to be associated with key.551* @throws NullPointerException if key is {@code null}.552* @throws IllegalArgumentException if {@code key.length()} exceeds553* {@code MAX_KEY_LENGTH}.554* @throws IllegalArgumentException if key contains555* the null control character, code point U+0000.556* @throws IllegalStateException if this node (or an ancestor) has been557* removed with the {@link #removeNode()} method.558*/559public void putFloat(String key, float value) {560put(key, Float.toString(value));561}562563/**564* Implements the {@code getFloat} method as per the specification in565* {@link Preferences#getFloat(String,float)}.566*567* <p>This implementation invokes {@link #get(String,String) get(key,568* null)}. If the return value is non-null, the implementation569* attempts to translate it to an {@code float} with570* {@link Float#parseFloat(String)}. If the attempt succeeds, the return571* value is returned by this method. Otherwise, {@code def} is returned.572*573* @param key key whose associated value is to be returned as a float.574* @param def the value to be returned in the event that this575* preference node has no value associated with {@code key}576* or the associated value cannot be interpreted as a float.577* @return the float value represented by the string associated with578* {@code key} in this preference node, or {@code def} if the579* associated value does not exist or cannot be interpreted as580* a float.581* @throws IllegalStateException if this node (or an ancestor) has been582* removed with the {@link #removeNode()} method.583* @throws NullPointerException if {@code key} is {@code null}.584* @throws IllegalArgumentException if key contains the null control585* character, code point U+0000.586*/587public float getFloat(String key, float def) {588float result = def;589try {590String value = get(key, null);591if (value != null)592result = Float.parseFloat(value);593} catch (NumberFormatException e) {594// Ignoring exception causes specified default to be returned595}596597return result;598}599600/**601* Implements the {@code putDouble} method as per the specification in602* {@link Preferences#putDouble(String,double)}.603*604* <p>This implementation translates {@code value} to a string with605* {@link Double#toString(double)} and invokes {@link #put(String,String)}606* on the result.607*608* @param key key with which the string form of value is to be associated.609* @param value value whose string form is to be associated with key.610* @throws NullPointerException if key is {@code null}.611* @throws IllegalArgumentException if {@code key.length()} exceeds612* {@code MAX_KEY_LENGTH}.613* @throws IllegalArgumentException if key contains614* the null control character, code point U+0000.615* @throws IllegalStateException if this node (or an ancestor) has been616* removed with the {@link #removeNode()} method.617*/618public void putDouble(String key, double value) {619put(key, Double.toString(value));620}621622/**623* Implements the {@code getDouble} method as per the specification in624* {@link Preferences#getDouble(String,double)}.625*626* <p>This implementation invokes {@link #get(String,String) get(key,627* null)}. If the return value is non-null, the implementation628* attempts to translate it to an {@code double} with629* {@link Double#parseDouble(String)}. If the attempt succeeds, the return630* value is returned by this method. Otherwise, {@code def} is returned.631*632* @param key key whose associated value is to be returned as a double.633* @param def the value to be returned in the event that this634* preference node has no value associated with {@code key}635* or the associated value cannot be interpreted as a double.636* @return the double value represented by the string associated with637* {@code key} in this preference node, or {@code def} if the638* associated value does not exist or cannot be interpreted as639* a double.640* @throws IllegalStateException if this node (or an ancestor) has been641* removed with the {@link #removeNode()} method.642* @throws NullPointerException if {@code key} is {@code null}.643* @throws IllegalArgumentException if key contains the null control644* character, code point U+0000.645*/646public double getDouble(String key, double def) {647double result = def;648try {649String value = get(key, null);650if (value != null)651result = Double.parseDouble(value);652} catch (NumberFormatException e) {653// Ignoring exception causes specified default to be returned654}655656return result;657}658659/**660* Implements the {@code putByteArray} method as per the specification in661* {@link Preferences#putByteArray(String,byte[])}.662*663* @param key key with which the string form of value is to be associated.664* @param value value whose string form is to be associated with key.665* @throws NullPointerException if key or value is {@code null}.666* @throws IllegalArgumentException if key.length() exceeds MAX_KEY_LENGTH667* or if value.length exceeds MAX_VALUE_LENGTH*3/4.668* @throws IllegalArgumentException if key contains669* the null control character, code point U+0000.670* @throws IllegalStateException if this node (or an ancestor) has been671* removed with the {@link #removeNode()} method.672*/673public void putByteArray(String key, byte[] value) {674put(key, Base64.byteArrayToBase64(value));675}676677/**678* Implements the {@code getByteArray} method as per the specification in679* {@link Preferences#getByteArray(String,byte[])}.680*681* @param key key whose associated value is to be returned as a byte array.682* @param def the value to be returned in the event that this683* preference node has no value associated with {@code key}684* or the associated value cannot be interpreted as a byte array.685* @return the byte array value represented by the string associated with686* {@code key} in this preference node, or {@code def} if the687* associated value does not exist or cannot be interpreted as688* a byte array.689* @throws IllegalStateException if this node (or an ancestor) has been690* removed with the {@link #removeNode()} method.691* @throws NullPointerException if {@code key} is {@code null}. (A692* {@code null} value for {@code def} <i>is</i> permitted.)693* @throws IllegalArgumentException if key contains the null control694* character, code point U+0000.695*/696public byte[] getByteArray(String key, byte[] def) {697byte[] result = def;698String value = get(key, null);699try {700if (value != null)701result = Base64.base64ToByteArray(value);702}703catch (RuntimeException e) {704// Ignoring exception causes specified default to be returned705}706707return result;708}709710/**711* Implements the {@code keys} method as per the specification in712* {@link Preferences#keys()}.713*714* <p>This implementation obtains this preference node's lock, checks that715* the node has not been removed and invokes {@link #keysSpi()}.716*717* @return an array of the keys that have an associated value in this718* preference node.719* @throws BackingStoreException if this operation cannot be completed720* due to a failure in the backing store, or inability to721* communicate with it.722* @throws IllegalStateException if this node (or an ancestor) has been723* removed with the {@link #removeNode()} method.724*/725public String[] keys() throws BackingStoreException {726synchronized(lock) {727if (removed)728throw new IllegalStateException("Node has been removed.");729730return keysSpi();731}732}733734/**735* Implements the {@code children} method as per the specification in736* {@link Preferences#childrenNames()}.737*738* <p>This implementation obtains this preference node's lock, checks that739* the node has not been removed, constructs a {@code TreeSet} initialized740* to the names of children already cached (the children in this node's741* "child-cache"), invokes {@link #childrenNamesSpi()}, and adds all of the742* returned child-names into the set. The elements of the tree set are743* dumped into a {@code String} array using the {@code toArray} method,744* and this array is returned.745*746* @return the names of the children of this preference node.747* @throws BackingStoreException if this operation cannot be completed748* due to a failure in the backing store, or inability to749* communicate with it.750* @throws IllegalStateException if this node (or an ancestor) has been751* removed with the {@link #removeNode()} method.752* @see #cachedChildren()753*/754public String[] childrenNames() throws BackingStoreException {755synchronized(lock) {756if (removed)757throw new IllegalStateException("Node has been removed.");758759Set<String> s = new TreeSet<>(kidCache.keySet());760for (String kid : childrenNamesSpi())761s.add(kid);762return s.toArray(EMPTY_STRING_ARRAY);763}764}765766private static final String[] EMPTY_STRING_ARRAY = new String[0];767768/**769* Returns all known unremoved children of this node.770*771* @return all known unremoved children of this node.772*/773protected final AbstractPreferences[] cachedChildren() {774return kidCache.values().toArray(EMPTY_ABSTRACT_PREFS_ARRAY);775}776777private static final AbstractPreferences[] EMPTY_ABSTRACT_PREFS_ARRAY778= new AbstractPreferences[0];779780/**781* Implements the {@code parent} method as per the specification in782* {@link Preferences#parent()}.783*784* <p>This implementation obtains this preference node's lock, checks that785* the node has not been removed and returns the parent value that was786* passed to this node's constructor.787*788* @return the parent of this preference node.789* @throws IllegalStateException if this node (or an ancestor) has been790* removed with the {@link #removeNode()} method.791*/792public Preferences parent() {793synchronized(lock) {794if (removed)795throw new IllegalStateException("Node has been removed.");796797return parent;798}799}800801/**802* Implements the {@code node} method as per the specification in803* {@link Preferences#node(String)}.804*805* <p>This implementation obtains this preference node's lock and checks806* that the node has not been removed. If {@code path} is {@code ""},807* this node is returned; if {@code path} is {@code "/"}, this node's808* root is returned. If the first character in {@code path} is809* not {@code '/'}, the implementation breaks {@code path} into810* tokens and recursively traverses the path from this node to the811* named node, "consuming" a name and a slash from {@code path} at812* each step of the traversal. At each step, the current node is locked813* and the node's child-cache is checked for the named node. If it is814* not found, the name is checked to make sure its length does not815* exceed {@code MAX_NAME_LENGTH}. Then the {@link #childSpi(String)}816* method is invoked, and the result stored in this node's child-cache.817* If the newly created {@code Preferences} object's {@link #newNode}818* field is {@code true} and there are any node change listeners,819* a notification event is enqueued for processing by the event dispatch820* thread.821*822* <p>When there are no more tokens, the last value found in the823* child-cache or returned by {@code childSpi} is returned by this824* method. If during the traversal, two {@code "/"} tokens occur825* consecutively, or the final token is {@code "/"} (rather than a name),826* an appropriate {@code IllegalArgumentException} is thrown.827*828* <p> If the first character of {@code path} is {@code '/'}829* (indicating an absolute path name) this preference node's830* lock is dropped prior to breaking {@code path} into tokens, and831* this method recursively traverses the path starting from the root832* (rather than starting from this node). The traversal is otherwise833* identical to the one described for relative path names. Dropping834* the lock on this node prior to commencing the traversal at the root835* node is essential to avoid the possibility of deadlock, as per the836* {@link #lock locking invariant}.837*838* @param path the path name of the preference node to return.839* @return the specified preference node.840* @throws IllegalArgumentException if the path name is invalid (i.e.,841* it contains multiple consecutive slash characters, or ends842* with a slash character and is more than one character long).843* @throws IllegalStateException if this node (or an ancestor) has been844* removed with the {@link #removeNode()} method.845*/846public Preferences node(String path) {847synchronized(lock) {848if (removed)849throw new IllegalStateException("Node has been removed.");850if (path.isEmpty())851return this;852if (path.equals("/"))853return root;854if (path.charAt(0) != '/')855return node(new StringTokenizer(path, "/", true));856}857858// Absolute path. Note that we've dropped our lock to avoid deadlock859return root.node(new StringTokenizer(path.substring(1), "/", true));860}861862/**863* tokenizer contains <name> {'/' <name>}*864*/865private Preferences node(StringTokenizer path) {866String token = path.nextToken();867if (token.equals("/")) // Check for consecutive slashes868throw new IllegalArgumentException("Consecutive slashes in path");869synchronized(lock) {870AbstractPreferences child = kidCache.get(token);871if (child == null) {872if (token.length() > MAX_NAME_LENGTH)873throw new IllegalArgumentException(874"Node name " + token + " too long");875child = childSpi(token);876if (child.newNode)877enqueueNodeAddedEvent(child);878kidCache.put(token, child);879}880if (!path.hasMoreTokens())881return child;882path.nextToken(); // Consume slash883if (!path.hasMoreTokens())884throw new IllegalArgumentException("Path ends with slash");885return child.node(path);886}887}888889/**890* Implements the {@code nodeExists} method as per the specification in891* {@link Preferences#nodeExists(String)}.892*893* <p>This implementation is very similar to {@link #node(String)},894* except that {@link #getChild(String)} is used instead of {@link895* #childSpi(String)}.896*897* @param path the path name of the node whose existence is to be checked.898* @return true if the specified node exists.899* @throws BackingStoreException if this operation cannot be completed900* due to a failure in the backing store, or inability to901* communicate with it.902* @throws IllegalArgumentException if the path name is invalid (i.e.,903* it contains multiple consecutive slash characters, or ends904* with a slash character and is more than one character long).905* @throws IllegalStateException if this node (or an ancestor) has been906* removed with the {@link #removeNode()} method and907* {@code pathname} is not the empty string ({@code ""}).908*/909public boolean nodeExists(String path)910throws BackingStoreException911{912synchronized(lock) {913if (path.isEmpty())914return !removed;915if (removed)916throw new IllegalStateException("Node has been removed.");917if (path.equals("/"))918return true;919if (path.charAt(0) != '/')920return nodeExists(new StringTokenizer(path, "/", true));921}922923// Absolute path. Note that we've dropped our lock to avoid deadlock924return root.nodeExists(new StringTokenizer(path.substring(1), "/",925true));926}927928/**929* tokenizer contains <name> {'/' <name>}*930*/931private boolean nodeExists(StringTokenizer path)932throws BackingStoreException933{934String token = path.nextToken();935if (token.equals("/")) // Check for consecutive slashes936throw new IllegalArgumentException("Consecutive slashes in path");937synchronized(lock) {938AbstractPreferences child = kidCache.get(token);939if (child == null)940child = getChild(token);941if (child==null)942return false;943if (!path.hasMoreTokens())944return true;945path.nextToken(); // Consume slash946if (!path.hasMoreTokens())947throw new IllegalArgumentException("Path ends with slash");948return child.nodeExists(path);949}950}951952/**953954* Implements the {@code removeNode()} method as per the specification in955* {@link Preferences#removeNode()}.956*957* <p>This implementation checks to see that this node is the root; if so,958* it throws an appropriate exception. Then, it locks this node's parent,959* and calls a recursive helper method that traverses the subtree rooted at960* this node. The recursive method locks the node on which it was called,961* checks that it has not already been removed, and then ensures that all962* of its children are cached: The {@link #childrenNamesSpi()} method is963* invoked and each returned child name is checked for containment in the964* child-cache. If a child is not already cached, the {@link965* #childSpi(String)} method is invoked to create a {@code Preferences}966* instance for it, and this instance is put into the child-cache. Then967* the helper method calls itself recursively on each node contained in its968* child-cache. Next, it invokes {@link #removeNodeSpi()}, marks itself969* as removed, and removes itself from its parent's child-cache. Finally,970* if there are any node change listeners, it enqueues a notification971* event for processing by the event dispatch thread.972*973* <p>Note that the helper method is always invoked with all ancestors up974* to the "closest non-removed ancestor" locked.975*976* @throws IllegalStateException if this node (or an ancestor) has already977* been removed with the {@link #removeNode()} method.978* @throws UnsupportedOperationException if this method is invoked on979* the root node.980* @throws BackingStoreException if this operation cannot be completed981* due to a failure in the backing store, or inability to982* communicate with it.983*/984public void removeNode() throws BackingStoreException {985if (this==root)986throw new UnsupportedOperationException("Can't remove the root!");987synchronized(parent.lock) {988removeNode2();989parent.kidCache.remove(name);990}991}992993/*994* Called with locks on all nodes on path from parent of "removal root"995* to this (including the former but excluding the latter).996*/997private void removeNode2() throws BackingStoreException {998synchronized(lock) {999if (removed)1000throw new IllegalStateException("Node already removed.");10011002// Ensure that all children are cached1003String[] kidNames = childrenNamesSpi();1004for (String kidName : kidNames)1005if (!kidCache.containsKey(kidName))1006kidCache.put(kidName, childSpi(kidName));10071008// Recursively remove all cached children1009for (Iterator<AbstractPreferences> i = kidCache.values().iterator();1010i.hasNext();) {1011try {1012i.next().removeNode2();1013i.remove();1014} catch (BackingStoreException x) { }1015}10161017// Now we have no descendants - it's time to die!1018removeNodeSpi();1019removed = true;1020parent.enqueueNodeRemovedEvent(this);1021}1022}10231024/**1025* Implements the {@code name} method as per the specification in1026* {@link Preferences#name()}.1027*1028* <p>This implementation merely returns the name that was1029* passed to this node's constructor.1030*1031* @return this preference node's name, relative to its parent.1032*/1033public String name() {1034return name;1035}10361037/**1038* Implements the {@code absolutePath} method as per the specification in1039* {@link Preferences#absolutePath()}.1040*1041* <p>This implementation merely returns the absolute path name that1042* was computed at the time that this node was constructed (based on1043* the name that was passed to this node's constructor, and the names1044* that were passed to this node's ancestors' constructors).1045*1046* @return this preference node's absolute path name.1047*/1048public String absolutePath() {1049return absolutePath;1050}10511052/**1053* Implements the {@code isUserNode} method as per the specification in1054* {@link Preferences#isUserNode()}.1055*1056* <p>This implementation compares this node's root node (which is stored1057* in a private field) with the value returned by1058* {@link Preferences#userRoot()}. If the two object references are1059* identical, this method returns true.1060*1061* @return {@code true} if this preference node is in the user1062* preference tree, {@code false} if it's in the system1063* preference tree.1064*/1065@SuppressWarnings("removal")1066public boolean isUserNode() {1067return AccessController.doPrivileged(1068new PrivilegedAction<Boolean>() {1069public Boolean run() {1070return root == Preferences.userRoot();1071}1072}).booleanValue();1073}10741075public void addPreferenceChangeListener(PreferenceChangeListener pcl) {1076if (pcl==null)1077throw new NullPointerException("Change listener is null.");1078synchronized(lock) {1079if (removed)1080throw new IllegalStateException("Node has been removed.");10811082// Copy-on-write1083PreferenceChangeListener[] old = prefListeners;1084prefListeners = new PreferenceChangeListener[old.length + 1];1085System.arraycopy(old, 0, prefListeners, 0, old.length);1086prefListeners[old.length] = pcl;1087}1088startEventDispatchThreadIfNecessary();1089}10901091public void removePreferenceChangeListener(PreferenceChangeListener pcl) {1092synchronized(lock) {1093if (removed)1094throw new IllegalStateException("Node has been removed.");1095if ((prefListeners == null) || (prefListeners.length == 0))1096throw new IllegalArgumentException("Listener not registered.");10971098// Copy-on-write1099PreferenceChangeListener[] newPl =1100new PreferenceChangeListener[prefListeners.length - 1];1101int i = 0;1102while (i < newPl.length && prefListeners[i] != pcl)1103newPl[i] = prefListeners[i++];11041105if (i == newPl.length && prefListeners[i] != pcl)1106throw new IllegalArgumentException("Listener not registered.");1107while (i < newPl.length)1108newPl[i] = prefListeners[++i];1109prefListeners = newPl;1110}1111}11121113public void addNodeChangeListener(NodeChangeListener ncl) {1114if (ncl==null)1115throw new NullPointerException("Change listener is null.");1116synchronized(lock) {1117if (removed)1118throw new IllegalStateException("Node has been removed.");11191120// Copy-on-write1121if (nodeListeners == null) {1122nodeListeners = new NodeChangeListener[1];1123nodeListeners[0] = ncl;1124} else {1125NodeChangeListener[] old = nodeListeners;1126nodeListeners = new NodeChangeListener[old.length + 1];1127System.arraycopy(old, 0, nodeListeners, 0, old.length);1128nodeListeners[old.length] = ncl;1129}1130}1131startEventDispatchThreadIfNecessary();1132}11331134public void removeNodeChangeListener(NodeChangeListener ncl) {1135synchronized(lock) {1136if (removed)1137throw new IllegalStateException("Node has been removed.");1138if ((nodeListeners == null) || (nodeListeners.length == 0))1139throw new IllegalArgumentException("Listener not registered.");11401141// Copy-on-write1142int i = 0;1143while (i < nodeListeners.length && nodeListeners[i] != ncl)1144i++;1145if (i == nodeListeners.length)1146throw new IllegalArgumentException("Listener not registered.");1147NodeChangeListener[] newNl =1148new NodeChangeListener[nodeListeners.length - 1];1149if (i != 0)1150System.arraycopy(nodeListeners, 0, newNl, 0, i);1151if (i != newNl.length)1152System.arraycopy(nodeListeners, i + 1,1153newNl, i, newNl.length - i);1154nodeListeners = newNl;1155}1156}11571158// "SPI" METHODS11591160/**1161* Put the given key-value association into this preference node. It is1162* guaranteed that {@code key} and {@code value} are non-null and of1163* legal length. Also, it is guaranteed that this node has not been1164* removed. (The implementor needn't check for any of these things.)1165*1166* <p>This method is invoked with the lock on this node held.1167* @param key the key1168* @param value the value1169*/1170protected abstract void putSpi(String key, String value);11711172/**1173* Return the value associated with the specified key at this preference1174* node, or {@code null} if there is no association for this key, or the1175* association cannot be determined at this time. It is guaranteed that1176* {@code key} is non-null. Also, it is guaranteed that this node has1177* not been removed. (The implementor needn't check for either of these1178* things.)1179*1180* <p> Generally speaking, this method should not throw an exception1181* under any circumstances. If, however, if it does throw an exception,1182* the exception will be intercepted and treated as a {@code null}1183* return value.1184*1185* <p>This method is invoked with the lock on this node held.1186*1187* @param key the key1188* @return the value associated with the specified key at this preference1189* node, or {@code null} if there is no association for this1190* key, or the association cannot be determined at this time.1191*/1192protected abstract String getSpi(String key);11931194/**1195* Remove the association (if any) for the specified key at this1196* preference node. It is guaranteed that {@code key} is non-null.1197* Also, it is guaranteed that this node has not been removed.1198* (The implementor needn't check for either of these things.)1199*1200* <p>This method is invoked with the lock on this node held.1201* @param key the key1202*/1203protected abstract void removeSpi(String key);12041205/**1206* Removes this preference node, invalidating it and any preferences that1207* it contains. The named child will have no descendants at the time this1208* invocation is made (i.e., the {@link Preferences#removeNode()} method1209* invokes this method repeatedly in a bottom-up fashion, removing each of1210* a node's descendants before removing the node itself).1211*1212* <p>This method is invoked with the lock held on this node and its1213* parent (and all ancestors that are being removed as a1214* result of a single invocation to {@link Preferences#removeNode()}).1215*1216* <p>The removal of a node needn't become persistent until the1217* {@code flush} method is invoked on this node (or an ancestor).1218*1219* <p>If this node throws a {@code BackingStoreException}, the exception1220* will propagate out beyond the enclosing {@link #removeNode()}1221* invocation.1222*1223* @throws BackingStoreException if this operation cannot be completed1224* due to a failure in the backing store, or inability to1225* communicate with it.1226*/1227protected abstract void removeNodeSpi() throws BackingStoreException;12281229/**1230* Returns all of the keys that have an associated value in this1231* preference node. (The returned array will be of size zero if1232* this node has no preferences.) It is guaranteed that this node has not1233* been removed.1234*1235* <p>This method is invoked with the lock on this node held.1236*1237* <p>If this node throws a {@code BackingStoreException}, the exception1238* will propagate out beyond the enclosing {@link #keys()} invocation.1239*1240* @return an array of the keys that have an associated value in this1241* preference node.1242* @throws BackingStoreException if this operation cannot be completed1243* due to a failure in the backing store, or inability to1244* communicate with it.1245*/1246protected abstract String[] keysSpi() throws BackingStoreException;12471248/**1249* Returns the names of the children of this preference node. (The1250* returned array will be of size zero if this node has no children.)1251* This method need not return the names of any nodes already cached,1252* but may do so without harm.1253*1254* <p>This method is invoked with the lock on this node held.1255*1256* <p>If this node throws a {@code BackingStoreException}, the exception1257* will propagate out beyond the enclosing {@link #childrenNames()}1258* invocation.1259*1260* @return an array containing the names of the children of this1261* preference node.1262* @throws BackingStoreException if this operation cannot be completed1263* due to a failure in the backing store, or inability to1264* communicate with it.1265*/1266protected abstract String[] childrenNamesSpi()1267throws BackingStoreException;12681269/**1270* Returns the named child if it exists, or {@code null} if it does not.1271* It is guaranteed that {@code nodeName} is non-null, non-empty,1272* does not contain the slash character ('/'), and is no longer than1273* {@link #MAX_NAME_LENGTH} characters. Also, it is guaranteed1274* that this node has not been removed. (The implementor needn't check1275* for any of these things if he chooses to override this method.)1276*1277* <p>Finally, it is guaranteed that the named node has not been returned1278* by a previous invocation of this method or {@link #childSpi} after the1279* last time that it was removed. In other words, a cached value will1280* always be used in preference to invoking this method. (The implementor1281* needn't maintain his own cache of previously returned children if he1282* chooses to override this method.)1283*1284* <p>This implementation obtains this preference node's lock, invokes1285* {@link #childrenNames()} to get an array of the names of this node's1286* children, and iterates over the array comparing the name of each child1287* with the specified node name. If a child node has the correct name,1288* the {@link #childSpi(String)} method is invoked and the resulting1289* node is returned. If the iteration completes without finding the1290* specified name, {@code null} is returned.1291*1292* @param nodeName name of the child to be searched for.1293* @return the named child if it exists, or null if it does not.1294* @throws BackingStoreException if this operation cannot be completed1295* due to a failure in the backing store, or inability to1296* communicate with it.1297*/1298protected AbstractPreferences getChild(String nodeName)1299throws BackingStoreException {1300synchronized(lock) {1301// assert kidCache.get(nodeName)==null;1302String[] kidNames = childrenNames();1303for (String kidName : kidNames)1304if (kidName.equals(nodeName))1305return childSpi(kidName);1306}1307return null;1308}13091310/**1311* Returns the named child of this preference node, creating it if it does1312* not already exist. It is guaranteed that {@code name} is non-null,1313* non-empty, does not contain the slash character ('/'), and is no longer1314* than {@link #MAX_NAME_LENGTH} characters. Also, it is guaranteed that1315* this node has not been removed. (The implementor needn't check for any1316* of these things.)1317*1318* <p>Finally, it is guaranteed that the named node has not been returned1319* by a previous invocation of this method or {@link #getChild(String)}1320* after the last time that it was removed. In other words, a cached1321* value will always be used in preference to invoking this method.1322* Subclasses need not maintain their own cache of previously returned1323* children.1324*1325* <p>The implementer must ensure that the returned node has not been1326* removed. If a like-named child of this node was previously removed, the1327* implementer must return a newly constructed {@code AbstractPreferences}1328* node; once removed, an {@code AbstractPreferences} node1329* cannot be "resuscitated."1330*1331* <p>If this method causes a node to be created, this node is not1332* guaranteed to be persistent until the {@code flush} method is1333* invoked on this node or one of its ancestors (or descendants).1334*1335* <p>This method is invoked with the lock on this node held.1336*1337* @param name The name of the child node to return, relative to1338* this preference node.1339* @return The named child node.1340*/1341protected abstract AbstractPreferences childSpi(String name);13421343/**1344* Returns the absolute path name of this preferences node.1345*/1346public String toString() {1347return (this.isUserNode() ? "User" : "System") +1348" Preference Node: " + this.absolutePath();1349}13501351/**1352* Implements the {@code sync} method as per the specification in1353* {@link Preferences#sync()}.1354*1355* <p>This implementation calls a recursive helper method that locks this1356* node, invokes syncSpi() on it, unlocks this node, and recursively1357* invokes this method on each "cached child." A cached child is a child1358* of this node that has been created in this VM and not subsequently1359* removed. In effect, this method does a depth first traversal of the1360* "cached subtree" rooted at this node, calling syncSpi() on each node in1361* the subTree while only that node is locked. Note that syncSpi() is1362* invoked top-down.1363*1364* @throws BackingStoreException if this operation cannot be completed1365* due to a failure in the backing store, or inability to1366* communicate with it.1367* @throws IllegalStateException if this node (or an ancestor) has been1368* removed with the {@link #removeNode()} method.1369* @see #flush()1370*/1371public void sync() throws BackingStoreException {1372sync2();1373}13741375private void sync2() throws BackingStoreException {1376AbstractPreferences[] cachedKids;13771378synchronized(lock) {1379if (removed)1380throw new IllegalStateException("Node has been removed");1381syncSpi();1382cachedKids = cachedChildren();1383}13841385for (AbstractPreferences cachedKid : cachedKids)1386cachedKid.sync2();1387}13881389/**1390* This method is invoked with this node locked. The contract of this1391* method is to synchronize any cached preferences stored at this node1392* with any stored in the backing store. (It is perfectly possible that1393* this node does not exist on the backing store, either because it has1394* been deleted by another VM, or because it has not yet been created.)1395* Note that this method should <i>not</i> synchronize the preferences in1396* any subnodes of this node. If the backing store naturally syncs an1397* entire subtree at once, the implementer is encouraged to override1398* sync(), rather than merely overriding this method.1399*1400* <p>If this node throws a {@code BackingStoreException}, the exception1401* will propagate out beyond the enclosing {@link #sync()} invocation.1402*1403* @throws BackingStoreException if this operation cannot be completed1404* due to a failure in the backing store, or inability to1405* communicate with it.1406*/1407protected abstract void syncSpi() throws BackingStoreException;14081409/**1410* Implements the {@code flush} method as per the specification in1411* {@link Preferences#flush()}.1412*1413* <p>This implementation calls a recursive helper method that locks this1414* node, invokes flushSpi() on it, unlocks this node, and recursively1415* invokes this method on each "cached child." A cached child is a child1416* of this node that has been created in this VM and not subsequently1417* removed. In effect, this method does a depth first traversal of the1418* "cached subtree" rooted at this node, calling flushSpi() on each node in1419* the subTree while only that node is locked. Note that flushSpi() is1420* invoked top-down.1421*1422* <p> If this method is invoked on a node that has been removed with1423* the {@link #removeNode()} method, flushSpi() is invoked on this node,1424* but not on others.1425*1426* @throws BackingStoreException if this operation cannot be completed1427* due to a failure in the backing store, or inability to1428* communicate with it.1429* @see #flush()1430*/1431public void flush() throws BackingStoreException {1432flush2();1433}14341435private void flush2() throws BackingStoreException {1436AbstractPreferences[] cachedKids;14371438synchronized(lock) {1439flushSpi();1440if(removed)1441return;1442cachedKids = cachedChildren();1443}14441445for (AbstractPreferences cachedKid : cachedKids)1446cachedKid.flush2();1447}14481449/**1450* This method is invoked with this node locked. The contract of this1451* method is to force any cached changes in the contents of this1452* preference node to the backing store, guaranteeing their persistence.1453* (It is perfectly possible that this node does not exist on the backing1454* store, either because it has been deleted by another VM, or because it1455* has not yet been created.) Note that this method should <i>not</i>1456* flush the preferences in any subnodes of this node. If the backing1457* store naturally flushes an entire subtree at once, the implementer is1458* encouraged to override flush(), rather than merely overriding this1459* method.1460*1461* <p>If this node throws a {@code BackingStoreException}, the exception1462* will propagate out beyond the enclosing {@link #flush()} invocation.1463*1464* @throws BackingStoreException if this operation cannot be completed1465* due to a failure in the backing store, or inability to1466* communicate with it.1467*/1468protected abstract void flushSpi() throws BackingStoreException;14691470/**1471* Returns {@code true} iff this node (or an ancestor) has been1472* removed with the {@link #removeNode()} method. This method1473* locks this node prior to returning the contents of the private1474* field used to track this state.1475*1476* @return {@code true} iff this node (or an ancestor) has been1477* removed with the {@link #removeNode()} method.1478*/1479protected boolean isRemoved() {1480synchronized(lock) {1481return removed;1482}1483}14841485/**1486* Queue of pending notification events. When a preference or node1487* change event for which there are one or more listeners occurs,1488* it is placed on this queue and the queue is notified. A background1489* thread waits on this queue and delivers the events. This decouples1490* event delivery from preference activity, greatly simplifying1491* locking and reducing opportunity for deadlock.1492*/1493private static final List<EventObject> eventQueue = new LinkedList<>();14941495/**1496* These two classes are used to distinguish NodeChangeEvents on1497* eventQueue so the event dispatch thread knows whether to call1498* childAdded or childRemoved.1499*/1500private class NodeAddedEvent extends NodeChangeEvent {1501private static final long serialVersionUID = -6743557530157328528L;1502NodeAddedEvent(Preferences parent, Preferences child) {1503super(parent, child);1504}1505}1506private class NodeRemovedEvent extends NodeChangeEvent {1507private static final long serialVersionUID = 8735497392918824837L;1508NodeRemovedEvent(Preferences parent, Preferences child) {1509super(parent, child);1510}1511}15121513/**1514* A single background thread ("the event notification thread") monitors1515* the event queue and delivers events that are placed on the queue.1516*/1517private static class EventDispatchThread extends Thread {1518private EventDispatchThread() {1519super(null, null, "Event Dispatch Thread", 0, false);1520}15211522public void run() {1523while(true) {1524// Wait on eventQueue till an event is present1525EventObject event = null;1526synchronized(eventQueue) {1527try {1528while (eventQueue.isEmpty())1529eventQueue.wait();1530event = eventQueue.remove(0);1531} catch (InterruptedException e) {1532// XXX Log "Event dispatch thread interrupted. Exiting"1533return;1534}1535}15361537// Now we have event & hold no locks; deliver evt to listeners1538AbstractPreferences src=(AbstractPreferences)event.getSource();1539if (event instanceof PreferenceChangeEvent) {1540PreferenceChangeEvent pce = (PreferenceChangeEvent)event;1541PreferenceChangeListener[] listeners = src.prefListeners();1542for (PreferenceChangeListener listener : listeners)1543listener.preferenceChange(pce);1544} else {1545NodeChangeEvent nce = (NodeChangeEvent)event;1546NodeChangeListener[] listeners = src.nodeListeners();1547if (nce instanceof NodeAddedEvent) {1548for (NodeChangeListener listener : listeners)1549listener.childAdded(nce);1550} else {1551// assert nce instanceof NodeRemovedEvent;1552for (NodeChangeListener listener : listeners)1553listener.childRemoved(nce);1554}1555}1556}1557}1558}15591560private static Thread eventDispatchThread = null;15611562/**1563* This method starts the event dispatch thread the first time it1564* is called. The event dispatch thread will be started only1565* if someone registers a listener.1566*/1567private static synchronized void startEventDispatchThreadIfNecessary() {1568if (eventDispatchThread == null) {1569// XXX Log "Starting event dispatch thread"1570eventDispatchThread = new EventDispatchThread();1571eventDispatchThread.setDaemon(true);1572eventDispatchThread.start();1573}1574}15751576/**1577* Return this node's preference/node change listeners. Even though1578* we're using a copy-on-write lists, we use synchronized accessors to1579* ensure information transmission from the writing thread to the1580* reading thread.1581*/1582PreferenceChangeListener[] prefListeners() {1583synchronized(lock) {1584return prefListeners;1585}1586}1587NodeChangeListener[] nodeListeners() {1588synchronized(lock) {1589return nodeListeners;1590}1591}15921593/**1594* Enqueue a preference change event for delivery to registered1595* preference change listeners unless there are no registered1596* listeners. Invoked with this.lock held.1597*/1598private void enqueuePreferenceChangeEvent(String key, String newValue) {1599if (prefListeners.length != 0) {1600synchronized(eventQueue) {1601eventQueue.add(new PreferenceChangeEvent(this, key, newValue));1602eventQueue.notify();1603}1604}1605}16061607/**1608* Enqueue a "node added" event for delivery to registered node change1609* listeners unless there are no registered listeners. Invoked with1610* this.lock held.1611*/1612private void enqueueNodeAddedEvent(Preferences child) {1613if (nodeListeners.length != 0) {1614synchronized(eventQueue) {1615eventQueue.add(new NodeAddedEvent(this, child));1616eventQueue.notify();1617}1618}1619}16201621/**1622* Enqueue a "node removed" event for delivery to registered node change1623* listeners unless there are no registered listeners. Invoked with1624* this.lock held.1625*/1626private void enqueueNodeRemovedEvent(Preferences child) {1627if (nodeListeners.length != 0) {1628synchronized(eventQueue) {1629eventQueue.add(new NodeRemovedEvent(this, child));1630eventQueue.notify();1631}1632}1633}16341635/**1636* Implements the {@code exportNode} method as per the specification in1637* {@link Preferences#exportNode(OutputStream)}.1638*1639* @param os the output stream on which to emit the XML document.1640* @throws IOException if writing to the specified output stream1641* results in an {@code IOException}.1642* @throws BackingStoreException if preference data cannot be read from1643* backing store.1644*/1645public void exportNode(OutputStream os)1646throws IOException, BackingStoreException1647{1648XmlSupport.export(os, this, false);1649}16501651/**1652* Implements the {@code exportSubtree} method as per the specification in1653* {@link Preferences#exportSubtree(OutputStream)}.1654*1655* @param os the output stream on which to emit the XML document.1656* @throws IOException if writing to the specified output stream1657* results in an {@code IOException}.1658* @throws BackingStoreException if preference data cannot be read from1659* backing store.1660*/1661public void exportSubtree(OutputStream os)1662throws IOException, BackingStoreException1663{1664XmlSupport.export(os, this, true);1665}1666}166716681669