getChild()
or childSpi()
* invocations and that have not been removed.
*/
private HashMap
* (isUserNode() ? "User":"System") + " Preference Node: " + absolutePath()
*
*/
public String toString() {
return (isUserNode() ? "User":"System")
+ " Preference Node: "
+ absolutePath();
}
/**
* Returns all known unremoved children of this node.
*
* @return All known unremoved children of this node
*/
protected final AbstractPreferences[] cachedChildren()
{
Collection
* This implementation locks this node, checks if the node has not yet
* been removed and throws an
* This method first locks this node and checks if the node has not been
* removed, if it has been removed it throws an exception. Then if the
* path is relative (does not start with a '/') it checks if the path is
* legal (does not end with a '/' and has no consecutive '/' characters).
* Then it recursively gets a name from the path, gets the child node
* from the child-cache of this node or calls the
* Gets the lock on this node, calls
* Gets the lock on this node and then returns a boolean field set by
*
* Gets the lock on this node, checks that the node has not been removed
* and returns the parent given to the constructor.
*
* @exception IllegalStateException if this node has been removed
*/
public Preferences parent() {
synchronized(lock) {
if (isRemoved())
throw new IllegalStateException("Node removed");
return parent;
}
}
// export methods
// Inherit javadoc.
public void exportNode(OutputStream os)
throws BackingStoreException,
IOException
{
NodeWriter nodeWriter = new NodeWriter(this, os);
nodeWriter.writePrefs();
}
// Inherit javadoc.
public void exportSubtree(OutputStream os)
throws BackingStoreException,
IOException
{
NodeWriter nodeWriter = new NodeWriter(this, os);
nodeWriter.writePrefsTree();
}
// preference entry manipulation methods
/**
* Returns an (possibly empty) array with all the keys of the preference
* entries of this node.
*
* This method locks this node and checks if the node has not been
* removed, if it has been removed it throws an exception, then it returns
* the result of calling
* Checks that key is not null and not larger then 80 characters,
* locks this node, and checks that the node has not been removed.
* Then it calls
* The result will be immediately visible in this VM, but may not be
* immediately written to the backing store.
*
* Checks that key and value are valid, locks this node, and checks that
* the node has not been removed. Then it calls
* Note that a byte array encoded as a Base64 string will be about 1.3
* times larger then the original length of the byte array, which means
* that the byte array may not be larger about 6 KB.
*
* @exception NullPointerException if either key or value are null
* @exception IllegalArgumentException if either key or value are to large
* @exception IllegalStateException when this node has been removed
*/
public void putByteArray(String key, byte[] value) {
put(key, encode64(value));
}
/**
* Helper method for encoding an array of bytes as a Base64 String.
*/
private static String encode64(byte[] b) {
CPStringBuilder sb = new CPStringBuilder((b.length/3)*4);
int i = 0;
int remaining = b.length;
char c[] = new char[4];
while (remaining > 0) {
// Three input bytes are encoded as four chars (6 bits) as
// 00000011 11112222 22333333
c[0] = (char) ((b[i] & 0xFC) >> 2);
c[1] = (char) ((b[i] & 0x03) << 4);
if (remaining >= 2) {
c[1] += (char) ((b[i+1] & 0xF0) >> 4);
c[2] = (char) ((b[i+1] & 0x0F) << 2);
if (remaining >= 3) {
c[2] += (char) ((b[i+2] & 0xC0) >> 6);
c[3] = (char) (b[i+2] & 0x3F);
} else {
c[3] = 64;
}
} else {
c[2] = 64;
c[3] = 64;
}
// Convert to base64 chars
for(int j = 0; j < 4; j++) {
if (c[j] < 26) {
c[j] += 'A';
} else if (c[j] < 52) {
c[j] = (char) (c[j] - 26 + 'a');
} else if (c[j] < 62) {
c[j] = (char) (c[j] - 52 + '0');
} else if (c[j] == 62) {
c[j] = '+';
} else if (c[j] == 63) {
c[j] = '/';
} else {
c[j] = '=';
}
}
sb.append(c);
i += 3;
remaining -= 3;
}
return sb.toString();
}
/**
* Convenience method for setting the given entry as a double.
* The double is converted with
* The result will be immediately visible in this VM, but may not be
* immediately written to the backing store.
*
* This implementation checks that the key is not larger then 80
* characters, gets the lock of this node, checks that the node has
* not been removed and calls
* The result will be immediately visible in this VM, but may not be
* immediatly written to the backing store.
*
* This implementation locks this node, checks that the node has not been
* removed and calls
* Locks this node, calls the
* Checks that this node is not removed, locks this node, calls the
*
* Called by either
* Checks that this is not a root node. If not it locks the parent node,
* then locks this node and checks that the node has not yet been removed.
* Then it makes sure that all subnodes of this node are in the child cache,
* by calling
* Makes sure that all subnodes of this node are in the child cache,
* by calling
* Called by
* Note that this method should even return a non-null child node if the
* backing store is not available since it may not throw a
*
* Called by
* Called by
* Called by
* Called by
* Called (indirectly) by
* Called (indirectly) by
* Called (indirectly) by IllegalStateException
when it
* has been. Then it creates a new TreeSet
and adds any
* already cached child nodes names. To get any uncached names it calls
* childrenNamesSpi()
and adds the result to the set. Finally
* it calls toArray()
on the created set. When the call to
* childrenNamesSpi
thows an BackingStoreException
* this method will not catch that exception but propagate the exception
* to the caller.
*
* @exception BackingStoreException when the backing store cannot be
* reached
* @exception IllegalStateException when this node has been removed
*/
public String[] childrenNames() throws BackingStoreException {
synchronized(lock) {
if (isRemoved())
throw new IllegalStateException("Node removed");
TreeSetchildSpi()
* method to create a new child sub node. This is done recursively on the
* newly created sub node with the rest of the path till the path is empty.
* If the path is absolute (starts with a '/') the lock on this node is
* droped and this method is called on the root of the preferences tree
* with as argument the complete path minus the first '/'.
*
* @exception IllegalStateException if this node has been removed
* @exception IllegalArgumentException if the path contains two or more
* consecutive '/' characters, ends with a '/' charactor and is not the
* string "/" (indicating the root node) or any name on the path is more
* than 80 characters long
*/
public Preferences node(String path) {
synchronized(lock) {
if (isRemoved())
throw new IllegalStateException("Node removed");
// Is it a relative path?
if (!path.startsWith("/")) {
// Check if it is a valid path
if (path.indexOf("//") != -1 || path.endsWith("/"))
throw new IllegalArgumentException(path);
return getNode(path);
}
}
// path started with a '/' so it is absolute
// we drop the lock and start from the root (omitting the first '/')
Preferences root = isUserNode() ? userRoot() : systemRoot();
return root.node(path.substring(1));
}
/**
* Private helper method for node()
. Called with this node
* locked. Returns this node when path is the empty string, if it is not
* empty the next node name is taken from the path (all chars till the
* next '/' or end of path string) and the node is either taken from the
* child-cache of this node or the childSpi()
method is called
* on this node with the name as argument. Then this method is called
* recursively on the just constructed child node with the rest of the
* path.
*
* @param path should not end with a '/' character and should not contain
* consecutive '/' characters
* @exception IllegalArgumentException if path begins with a name that is
* larger then 80 characters.
*/
private Preferences getNode(String path) {
// if mark is dom then goto end
// Empty String "" indicates this node
if (path.length() == 0)
return this;
// Calculate child name and rest of path
String childName;
String childPath;
int nextSlash = path.indexOf('/');
if (nextSlash == -1) {
childName = path;
childPath = "";
} else {
childName = path.substring(0, nextSlash);
childPath = path.substring(nextSlash+1);
}
// Get the child node
AbstractPreferences child;
child = (AbstractPreferences)childCache.get(childName);
if (child == null) {
if (childName.length() > MAX_NAME_LENGTH)
throw new IllegalArgumentException(childName);
// Not in childCache yet so create a new sub node
child = childSpi(childName);
childCache.put(childName, child);
if (child.newNode && nodeListeners != null)
fire(new NodeChangeEvent(this, child), true);
}
// Lock the child and go down
synchronized(child.lock) {
return child.getNode(childPath);
}
}
/**
* Returns true if the node that the path points to exists in memory or
* in the backing store. Otherwise it returns false or an exception is
* thrown. When this node is removed the only valid parameter is the
* empty string (indicating this node), the return value in that case
* will be false.
*
* @exception BackingStoreException when the backing store cannot be
* reached
* @exception IllegalStateException if this node has been removed
* and the path is not the empty string (indicating this node)
* @exception IllegalArgumentException if the path contains two or more
* consecutive '/' characters, ends with a '/' charactor and is not the
* string "/" (indicating the root node) or any name on the path is more
* then 80 characters long
*/
public boolean nodeExists(String path) throws BackingStoreException {
synchronized(lock) {
if (isRemoved() && path.length() != 0)
throw new IllegalStateException("Node removed");
// Is it a relative path?
if (!path.startsWith("/")) {
// Check if it is a valid path
if (path.indexOf("//") != -1 || path.endsWith("/"))
throw new IllegalArgumentException(path);
return existsNode(path);
}
}
// path started with a '/' so it is absolute
// we drop the lock and start from the root (omitting the first '/')
Preferences root = isUserNode() ? userRoot() : systemRoot();
return root.nodeExists(path.substring(1));
}
private boolean existsNode(String path) throws BackingStoreException {
// Empty String "" indicates this node
if (path.length() == 0)
return(!isRemoved());
// Calculate child name and rest of path
String childName;
String childPath;
int nextSlash = path.indexOf('/');
if (nextSlash == -1) {
childName = path;
childPath = "";
} else {
childName = path.substring(0, nextSlash);
childPath = path.substring(nextSlash+1);
}
// Get the child node
AbstractPreferences child;
child = (AbstractPreferences)childCache.get(childName);
if (child == null) {
if (childName.length() > MAX_NAME_LENGTH)
throw new IllegalArgumentException(childName);
// Not in childCache yet so create a new sub node
child = getChild(childName);
if (child == null)
return false;
childCache.put(childName, child);
}
// Lock the child and go down
synchronized(child.lock) {
return child.existsNode(childPath);
}
}
/**
* Returns the child sub node if it exists in the backing store or null
* if it does not exist. Called (indirectly) by nodeExists()
* when a child node name can not be found in the cache.
* childrenNamesSpi()
to
* get an array of all (possibly uncached) children and compares the
* given name with the names in the array. If the name is found in the
* array childSpi()
is called to get an instance, otherwise
* null is returned.
*
* @exception BackingStoreException when the backing store cannot be
* reached
*/
protected AbstractPreferences getChild(String name)
throws BackingStoreException
{
synchronized(lock) {
// Get all the names (not yet in the cache)
String[] names = childrenNamesSpi();
for (int i=0; i < names.length; i++)
if (name.equals(names[i]))
return childSpi(name);
// No child with that name found
return null;
}
}
/**
* Returns true if this node has been removed with the
* removeNode()
method, false otherwise.
* removeNode
methods.
*/
protected boolean isRemoved() {
synchronized(lock) {
return removed;
}
}
/**
* Returns the parent preferences node of this node or null if this is
* the root of the preferences tree.
* keysSpi()
.
*
* @exception BackingStoreException when the backing store cannot be
* reached
* @exception IllegalStateException if this node has been removed
*/
public String[] keys() throws BackingStoreException {
synchronized(lock) {
if (isRemoved())
throw new IllegalStateException("Node removed");
return keysSpi();
}
}
/**
* Returns the value associated with the key in this preferences node. If
* the default value of the key cannot be found in the preferences node
* entries or something goes wrong with the backing store the supplied
* default value is returned.
* keySpi()
and returns
* the result of that method or the given default value if it returned
* null or throwed an exception.
*
* @exception IllegalArgumentException if key is larger then 80 characters
* @exception IllegalStateException if this node has been removed
* @exception NullPointerException if key is null
*/
public String get(String key, String defaultVal) {
if (key.length() > MAX_KEY_LENGTH)
throw new IllegalArgumentException(key);
synchronized(lock) {
if (isRemoved())
throw new IllegalStateException("Node removed");
String value;
try {
value = getSpi(key);
} catch (ThreadDeath death) {
throw death;
} catch (Throwable t) {
value = null;
}
if (value != null) {
return value;
} else {
return defaultVal;
}
}
}
/**
* Convenience method for getting the given entry as a boolean.
* When the string representation of the requested entry is either
* "true" or "false" (ignoring case) then that value is returned,
* otherwise the given default boolean value is returned.
*
* @exception IllegalArgumentException if key is larger then 80 characters
* @exception IllegalStateException if this node has been removed
* @exception NullPointerException if key is null
*/
public boolean getBoolean(String key, boolean defaultVal) {
String value = get(key, null);
if ("true".equalsIgnoreCase(value))
return true;
if ("false".equalsIgnoreCase(value))
return false;
return defaultVal;
}
/**
* Convenience method for getting the given entry as a byte array.
* When the string representation of the requested entry is a valid
* Base64 encoded string (without any other characters, such as newlines)
* then the decoded Base64 string is returned as byte array,
* otherwise the given default byte array value is returned.
*
* @exception IllegalArgumentException if key is larger then 80 characters
* @exception IllegalStateException if this node has been removed
* @exception NullPointerException if key is null
*/
public byte[] getByteArray(String key, byte[] defaultVal) {
String value = get(key, null);
byte[] b = null;
if (value != null) {
b = decode64(value);
}
if (b != null)
return b;
else
return defaultVal;
}
/**
* Helper method for decoding a Base64 string as an byte array.
* Returns null on encoding error. This method does not allow any other
* characters present in the string then the 65 special base64 chars.
*/
private static byte[] decode64(String s) {
ByteArrayOutputStream bs = new ByteArrayOutputStream((s.length()/4)*3);
char[] c = new char[s.length()];
s.getChars(0, s.length(), c, 0);
// Convert from base64 chars
int endchar = -1;
for(int j = 0; j < c.length && endchar == -1; j++) {
if (c[j] >= 'A' && c[j] <= 'Z') {
c[j] -= 'A';
} else if (c[j] >= 'a' && c[j] <= 'z') {
c[j] = (char) (c[j] + 26 - 'a');
} else if (c[j] >= '0' && c[j] <= '9') {
c[j] = (char) (c[j] + 52 - '0');
} else if (c[j] == '+') {
c[j] = 62;
} else if (c[j] == '/') {
c[j] = 63;
} else if (c[j] == '=') {
endchar = j;
} else {
return null; // encoding exception
}
}
int remaining = endchar == -1 ? c.length : endchar;
int i = 0;
while (remaining > 0) {
// Four input chars (6 bits) are decoded as three bytes as
// 000000 001111 111122 222222
byte b0 = (byte) (c[i] << 2);
if (remaining >= 2) {
b0 += (c[i+1] & 0x30) >> 4;
}
bs.write(b0);
if (remaining >= 3) {
byte b1 = (byte) ((c[i+1] & 0x0F) << 4);
b1 += (byte) ((c[i+2] & 0x3C) >> 2);
bs.write(b1);
}
if (remaining >= 4) {
byte b2 = (byte) ((c[i+2] & 0x03) << 6);
b2 += c[i+3];
bs.write(b2);
}
i += 4;
remaining -= 4;
}
return bs.toByteArray();
}
/**
* Convenience method for getting the given entry as a double.
* When the string representation of the requested entry can be decoded
* with Double.parseDouble()
then that double is returned,
* otherwise the given default double value is returned.
*
* @exception IllegalArgumentException if key is larger then 80 characters
* @exception IllegalStateException if this node has been removed
* @exception NullPointerException if key is null
*/
public double getDouble(String key, double defaultVal) {
String value = get(key, null);
if (value != null) {
try {
return Double.parseDouble(value);
} catch (NumberFormatException nfe) { /* ignore */ }
}
return defaultVal;
}
/**
* Convenience method for getting the given entry as a float.
* When the string representation of the requested entry can be decoded
* with Float.parseFloat()
then that float is returned,
* otherwise the given default float value is returned.
*
* @exception IllegalArgumentException if key is larger then 80 characters
* @exception IllegalStateException if this node has been removed
* @exception NullPointerException if key is null
*/
public float getFloat(String key, float defaultVal) {
String value = get(key, null);
if (value != null) {
try {
return Float.parseFloat(value);
} catch (NumberFormatException nfe) { /* ignore */ }
}
return defaultVal;
}
/**
* Convenience method for getting the given entry as an integer.
* When the string representation of the requested entry can be decoded
* with Integer.parseInt()
then that integer is returned,
* otherwise the given default integer value is returned.
*
* @exception IllegalArgumentException if key is larger then 80 characters
* @exception IllegalStateException if this node has been removed
* @exception NullPointerException if key is null
*/
public int getInt(String key, int defaultVal) {
String value = get(key, null);
if (value != null) {
try {
return Integer.parseInt(value);
} catch (NumberFormatException nfe) { /* ignore */ }
}
return defaultVal;
}
/**
* Convenience method for getting the given entry as a long.
* When the string representation of the requested entry can be decoded
* with Long.parseLong()
then that long is returned,
* otherwise the given default long value is returned.
*
* @exception IllegalArgumentException if key is larger then 80 characters
* @exception IllegalStateException if this node has been removed
* @exception NullPointerException if key is null
*/
public long getLong(String key, long defaultVal) {
String value = get(key, null);
if (value != null) {
try {
return Long.parseLong(value);
} catch (NumberFormatException nfe) { /* ignore */ }
}
return defaultVal;
}
/**
* Sets the value of the given preferences entry for this node.
* Key and value cannot be null, the key cannot exceed 80 characters
* and the value cannot exceed 8192 characters.
* putSpi()
.
*
* @exception NullPointerException if either key or value are null
* @exception IllegalArgumentException if either key or value are to large
* @exception IllegalStateException when this node has been removed
*/
public void put(String key, String value) {
if (key.length() > MAX_KEY_LENGTH
|| value.length() > MAX_VALUE_LENGTH)
throw new IllegalArgumentException("key ("
+ key.length() + ")"
+ " or value ("
+ value.length() + ")"
+ " to large");
synchronized(lock) {
if (isRemoved())
throw new IllegalStateException("Node removed");
putSpi(key, value);
if (preferenceListeners != null)
fire(new PreferenceChangeEvent(this, key, value));
}
}
/**
* Convenience method for setting the given entry as a boolean.
* The boolean is converted with Boolean.toString(value)
* and then stored in the preference entry as that string.
*
* @exception NullPointerException if key is null
* @exception IllegalArgumentException if the key length is to large
* @exception IllegalStateException when this node has been removed
*/
public void putBoolean(String key, boolean value) {
put(key, Boolean.toString(value));
}
/**
* Convenience method for setting the given entry as an array of bytes.
* The byte array is converted to a Base64 encoded string
* and then stored in the preference entry as that string.
* Double.toString(double)
* and then stored in the preference entry as that string.
*
* @exception NullPointerException if the key is null
* @exception IllegalArgumentException if the key length is to large
* @exception IllegalStateException when this node has been removed
*/
public void putDouble(String key, double value) {
put(key, Double.toString(value));
}
/**
* Convenience method for setting the given entry as a float.
* The float is converted with Float.toString(float)
* and then stored in the preference entry as that string.
*
* @exception NullPointerException if the key is null
* @exception IllegalArgumentException if the key length is to large
* @exception IllegalStateException when this node has been removed
*/
public void putFloat(String key, float value) {
put(key, Float.toString(value));
}
/**
* Convenience method for setting the given entry as an integer.
* The integer is converted with Integer.toString(int)
* and then stored in the preference entry as that string.
*
* @exception NullPointerException if the key is null
* @exception IllegalArgumentException if the key length is to large
* @exception IllegalStateException when this node has been removed
*/
public void putInt(String key, int value) {
put(key, Integer.toString(value));
}
/**
* Convenience method for setting the given entry as a long.
* The long is converted with Long.toString(long)
* and then stored in the preference entry as that string.
*
* @exception NullPointerException if the key is null
* @exception IllegalArgumentException if the key length is to large
* @exception IllegalStateException when this node has been removed
*/
public void putLong(String key, long value) {
put(key, Long.toString(value));
}
/**
* Removes the preferences entry from this preferences node.
* removeSpi
with the given key.
*
* @exception NullPointerException if the key is null
* @exception IllegalArgumentException if the key length is to large
* @exception IllegalStateException when this node has been removed
*/
public void remove(String key) {
if (key.length() > MAX_KEY_LENGTH)
throw new IllegalArgumentException(key);
synchronized(lock) {
if (isRemoved())
throw new IllegalStateException("Node removed");
removeSpi(key);
if (preferenceListeners != null)
fire(new PreferenceChangeEvent(this, key, null));
}
}
/**
* Removes all entries from this preferences node. May need access to the
* backing store to get and clear all entries.
* keys()
to get a complete array of keys
* for this node. For every key found removeSpi()
is called.
*
* @exception BackingStoreException when the backing store cannot be
* reached
* @exception IllegalStateException if this node has been removed
*/
public void clear() throws BackingStoreException {
synchronized(lock) {
if (isRemoved())
throw new IllegalStateException("Node Removed");
String[] keys = keys();
for (int i = 0; i < keys.length; i++) {
removeSpi(keys[i]);
}
}
}
/**
* Writes all preference changes on this and any subnode that have not
* yet been written to the backing store. This has no effect on the
* preference entries in this VM, but it makes sure that all changes
* are visible to other programs (other VMs might need to call the
* sync()
method to actually see the changes to the backing
* store.
* flushSpi()
method, gets all
* the (cached - already existing in this VM) subnodes and then calls
* flushSpi()
on every subnode with this node unlocked and
* only that particular subnode locked.
*
* @exception BackingStoreException when the backing store cannot be
* reached
*/
public void flush() throws BackingStoreException {
flushNode(false);
}
/**
* Writes and reads all preference changes to and from this and any
* subnodes. This makes sure that all local changes are written to the
* backing store and that all changes to the backing store are visible
* in this preference node (and all subnodes).
* syncSpi()
method, gets all the subnodes and then calls
* syncSpi()
on every subnode with this node unlocked and
* only that particular subnode locked.
*
* @exception BackingStoreException when the backing store cannot be
* reached
* @exception IllegalStateException if this node has been removed
*/
public void sync() throws BackingStoreException {
flushNode(true);
}
/**
* Private helper method that locks this node and calls either
* flushSpi()
if sync
is false, or
* flushSpi()
if sync
is true. Then it gets all
* the currently cached subnodes. For every subnode it calls this method
* recursively with this node no longer locked.
* flush()
or sync()
*/
private void flushNode(boolean sync) throws BackingStoreException {
String[] keys = null;
synchronized(lock) {
if (sync) {
syncSpi();
} else {
flushSpi();
}
keys = (String[]) childCache.keySet().toArray(new String[]{});
}
if (keys != null) {
for (int i = 0; i < keys.length; i++) {
// Have to lock this node again to access the childCache
AbstractPreferences subNode;
synchronized(lock) {
subNode = (AbstractPreferences) childCache.get(keys[i]);
}
// The child could already have been removed from the cache
if (subNode != null) {
subNode.flushNode(sync);
}
}
}
}
/**
* Removes this and all subnodes from the backing store and clears all
* entries. After removal this instance will not be useable (except for
* a few methods that don't throw a InvalidStateException
),
* even when a new node with the same path name is created this instance
* will not be usable again.
* childSpi()
on any children not yet in the cache.
* Then for all children it locks the subnode and removes it. After all
* subnodes have been purged the child cache is cleared, this nodes removed
* flag is set and any listeners are called. Finally this node is removed
* from the child cache of the parent node.
*
* @exception BackingStoreException when the backing store cannot be
* reached
* @exception IllegalStateException if this node has already been removed
* @exception UnsupportedOperationException if this is a root node
*/
public void removeNode() throws BackingStoreException {
// Check if it is a root node
if (parent == null)
throw new UnsupportedOperationException("Cannot remove root node");
synchronized (parent.lock) {
synchronized(this.lock) {
if (isRemoved())
throw new IllegalStateException("Node Removed");
purge();
}
parent.childCache.remove(name);
}
}
/**
* Private helper method used to completely remove this node.
* Called by removeNode
with the parent node and this node
* locked.
* childSpi()
on any children not yet in the
* cache. Then for all children it locks the subnode and calls this method
* on that node. After all subnodes have been purged the child cache is
* cleared, this nodes removed flag is set and any listeners are called.
*/
private void purge() throws BackingStoreException
{
// Make sure all children have an AbstractPreferences node in cache
String children[] = childrenNamesSpi();
for (int i = 0; i < children.length; i++) {
if (childCache.get(children[i]) == null)
childCache.put(children[i], childSpi(children[i]));
}
// purge all children
Iterator i = childCache.values().iterator();
while (i.hasNext()) {
AbstractPreferences node = (AbstractPreferences) i.next();
synchronized(node.lock) {
node.purge();
}
}
// Cache is empty now
childCache.clear();
// remove this node
removeNodeSpi();
removed = true;
if (nodeListeners != null)
fire(new NodeChangeEvent(parent, this), false);
}
// listener methods
/**
* Add a listener which is notified when a sub-node of this node
* is added or removed.
* @param listener the listener to add
*/
public void addNodeChangeListener(NodeChangeListener listener)
{
synchronized (lock)
{
if (isRemoved())
throw new IllegalStateException("node has been removed");
if (listener == null)
throw new NullPointerException("listener is null");
if (nodeListeners == null)
nodeListeners = new ArrayListchildrenNames()
with this node locked.
*/
protected abstract String[] childrenNamesSpi() throws BackingStoreException;
/**
* Returns a child note with the given name.
* This method is called by the node()
method (indirectly
* through the getNode()
helper method) with this node locked
* if a sub node with this name does not already exist in the child cache.
* If the child node did not aleady exist in the backing store the boolean
* field newNode
of the returned node should be set.
* BackingStoreException
.
*/
protected abstract AbstractPreferences childSpi(String name);
/**
* Returns an (possibly empty) array with all the keys of the preference
* entries of this node.
* keys()
with this node locked if this node has
* not been removed. May throw an exception when the backing store cannot
* be accessed.
*
* @exception BackingStoreException when the backing store cannot be
* reached
*/
protected abstract String[] keysSpi() throws BackingStoreException;
/**
* Returns the value associated with the key in this preferences node or
* null when the key does not exist in this preferences node.
* key()
with this node locked after checking that
* key is valid, not null and that the node has not been removed.
* key()
will catch any exceptions that this method throws.
*/
protected abstract String getSpi(String key);
/**
* Sets the value of the given preferences entry for this node.
* The implementation is not required to propagate the change to the
* backing store immediately. It may not throw an exception when it tries
* to write to the backing store and that operation fails, the failure
* should be registered so a later invocation of flush()
* or sync()
can signal the failure.
* put()
with this node locked after checking that
* key and value are valid and non-null.
*/
protected abstract void putSpi(String key, String value);
/**
* Removes the given key entry from this preferences node.
* The implementation is not required to propagate the change to the
* backing store immediately. It may not throw an exception when it tries
* to write to the backing store and that operation fails, the failure
* should be registered so a later invocation of flush()
* or sync()
can signal the failure.
* remove()
with this node locked after checking
* that the key is valid and non-null.
*/
protected abstract void removeSpi(String key);
/**
* Writes all entries of this preferences node that have not yet been
* written to the backing store and possibly creates this node in the
* backing store, if it does not yet exist. Should only write changes to
* this node and not write changes to any subnodes.
* Note that the node can be already removed in this VM. To check if
* that is the case the implementation can call isRemoved()
.
* flush()
with this node locked.
*/
protected abstract void flushSpi() throws BackingStoreException;
/**
* Writes all entries of this preferences node that have not yet been
* written to the backing store and reads any entries that have changed
* in the backing store but that are not yet visible in this VM.
* Should only sync this node and not change any of the subnodes.
* Note that the node can be already removed in this VM. To check if
* that is the case the implementation can call isRemoved()
.
* sync()
with this node locked.
*/
protected abstract void syncSpi() throws BackingStoreException;
/**
* Clears this node from this VM and removes it from the backing store.
* After this method has been called the node is marked as removed.
* removeNode()
with this node locked
* after all the sub nodes of this node have already been removed.
*/
protected abstract void removeNodeSpi() throws BackingStoreException;
}