diff options
Diffstat (limited to 'libjava/classpath/java/util/prefs/AbstractPreferences.java')
-rw-r--r-- | libjava/classpath/java/util/prefs/AbstractPreferences.java | 1392 |
1 files changed, 0 insertions, 1392 deletions
diff --git a/libjava/classpath/java/util/prefs/AbstractPreferences.java b/libjava/classpath/java/util/prefs/AbstractPreferences.java deleted file mode 100644 index 14fed38..0000000 --- a/libjava/classpath/java/util/prefs/AbstractPreferences.java +++ /dev/null @@ -1,1392 +0,0 @@ -/* AbstractPreferences -- Partial implementation of a Preference node - Copyright (C) 2001, 2003, 2004, 2006 Free Software Foundation, Inc. - -This file is part of GNU Classpath. - -GNU Classpath is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2, or (at your option) -any later version. - -GNU Classpath is distributed in the hope that it will be useful, but -WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -General Public License for more details. - -You should have received a copy of the GNU General Public License -along with GNU Classpath; see the file COPYING. If not, write to the -Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA -02110-1301 USA. - -Linking this library statically or dynamically with other modules is -making a combined work based on this library. Thus, the terms and -conditions of the GNU General Public License cover the whole -combination. - -As a special exception, the copyright holders of this library give you -permission to link this library with independent modules to produce an -executable, regardless of the license terms of these independent -modules, and to copy and distribute the resulting executable under -terms of your choice, provided that you also meet, for each linked -independent module, the terms and conditions of the license of that -module. An independent module is a module which is not derived from -or based on this library. If you modify this library, you may extend -this exception to your version of the library, but you are not -obligated to do so. If you do not wish to do so, delete this -exception statement from your version. */ - - -package java.util.prefs; - -import gnu.classpath.toolkit.DefaultDaemonThreadFactory; -import gnu.java.lang.CPStringBuilder; -import gnu.java.util.prefs.NodeWriter; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.Iterator; -import java.util.TreeSet; -import java.util.concurrent.Executor; -import java.util.concurrent.Executors; - -/** - * Partial implementation of a Preference node. - * - * @since 1.4 - * @author Mark Wielaard (mark@klomp.org) - */ -public abstract class AbstractPreferences extends Preferences { - - // protected fields - - /** - * Object used to lock this preference node. Any thread only locks nodes - * downwards when it has the lock on the current node. No method should - * synchronize on the lock of any of its parent nodes while holding the - * lock on the current node. - */ - protected final Object lock = new Object(); - - /** - * Set to true in the contructor if the node did not exist in the backing - * store when this preference node object was created. Should be set in - * the constructor of a subclass. Defaults to false. Used to fire node - * changed events. - */ - protected boolean newNode = false; - - // private fields - - /** - * The parent preferences node or null when this is the root node. - */ - private final AbstractPreferences parent; - - /** - * The name of this node. - * Only when this is a root node (parent == null) the name is empty. - * It has a maximum of 80 characters and cannot contain any '/' characters. - */ - private final String name; - - /** True when this node has been remove, false otherwise. */ - private boolean removed = false; - - /** - * Holds all the child names and nodes of this node that have been - * accessed by earlier <code>getChild()</code> or <code>childSpi()</code> - * invocations and that have not been removed. - */ - private HashMap<String, AbstractPreferences> childCache - = new HashMap<String, AbstractPreferences>(); - - /** - * A list of all the registered NodeChangeListener objects. - */ - private ArrayList<NodeChangeListener> nodeListeners; - - /** - * A list of all the registered PreferenceChangeListener objects. - */ - private ArrayList<PreferenceChangeListener> preferenceListeners; - - // constructor - - /** - * Creates a new AbstractPreferences node with the given parent and name. - * - * @param parent the parent of this node or null when this is the root node - * @param name the name of this node, can not be null, only 80 characters - * maximum, must be empty when parent is null and cannot - * contain any '/' characters - * @exception IllegalArgumentException when name is null, greater then 80 - * characters, not the empty string but parent is null or - * contains a '/' character - */ - protected AbstractPreferences(AbstractPreferences parent, String name) { - if ( (name == null) // name should be given - || (name.length() > MAX_NAME_LENGTH) // 80 characters max - || (parent == null && name.length() != 0) // root has no name - || (parent != null && name.length() == 0) // all other nodes do - || (name.indexOf('/') != -1)) // must not contain '/' - throw new IllegalArgumentException("Illegal name argument '" - + name - + "' (parent is " - + (parent == null ? "" : "not ") - + "null)"); - this.parent = parent; - this.name = name; - } - - // identification methods - - /** - * Returns the absolute path name of this preference node. - * The absolute path name of a node is the path name of its parent node - * plus a '/' plus its own name. If the node is the root node and has no - * parent then its path name is "" and its absolute path name is "/". - */ - public String absolutePath() { - if (parent == null) - return "/"; - else - return parent.path() + '/' + name; - } - - /** - * Private helper method for absolutePath. Returns the empty string for a - * root node and otherwise the parentPath of its parent plus a '/'. - */ - private String path() { - if (parent == null) - return ""; - else - return parent.path() + '/' + name; - } - - /** - * Returns true if this node comes from the user preferences tree, false - * if it comes from the system preferences tree. - */ - public boolean isUserNode() { - AbstractPreferences root = this; - while (root.parent != null) - root = root.parent; - return root == Preferences.userRoot(); - } - - /** - * Returns the name of this preferences node. The name of the node cannot - * be null, can be mostly 80 characters and cannot contain any '/' - * characters. The root node has as name "". - */ - public String name() { - return name; - } - - /** - * Returns the String given by - * <code> - * (isUserNode() ? "User":"System") + " Preference Node: " + absolutePath() - * </code> - */ - 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<AbstractPreferences> vals = childCache.values(); - return vals.toArray(new AbstractPreferences[vals.size()]); - } - - /** - * Returns all the direct sub nodes of this preferences node. - * Needs access to the backing store to give a meaningfull answer. - * <p> - * This implementation locks this node, checks if the node has not yet - * been removed and throws an <code>IllegalStateException</code> when it - * has been. Then it creates a new <code>TreeSet</code> and adds any - * already cached child nodes names. To get any uncached names it calls - * <code>childrenNamesSpi()</code> and adds the result to the set. Finally - * it calls <code>toArray()</code> on the created set. When the call to - * <code>childrenNamesSpi</code> thows an <code>BackingStoreException</code> - * 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"); - - TreeSet<String> childrenNames = new TreeSet<String>(); - - // First get all cached node names - childrenNames.addAll(childCache.keySet()); - - // Then add any others - String names[] = childrenNamesSpi(); - for (int i = 0; i < names.length; i++) { - childrenNames.add(names[i]); - } - - // And return the array of names - String[] children = new String[childrenNames.size()]; - childrenNames.toArray(children); - return children; - - } - } - - /** - * Returns a sub node of this preferences node if the given path is - * relative (does not start with a '/') or a sub node of the root - * if the path is absolute (does start with a '/'). - * <p> - * 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 <code>childSpi()</code> - * 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 <code>node()</code>. 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 <code>childSpi()</code> 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 <code>nodeExists()</code> - * when a child node name can not be found in the cache. - * <p> - * Gets the lock on this node, calls <code>childrenNamesSpi()</code> 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 <code>childSpi()</code> 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 - * <code>removeNode()</code> method, false otherwise. - * <p> - * Gets the lock on this node and then returns a boolean field set by - * <code>removeNode</code> 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. - * <p> - * 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. - * <p> - * 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 <code>keysSpi()</code>. - * - * @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. - * <p> - * 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 <code>keySpi()</code> 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 <code>Double.parseDouble()</code> 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 <code>Float.parseFloat()</code> 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 <code>Integer.parseInt()</code> 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 <code>Long.parseLong()</code> 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. - * <p> - * The result will be immediately visible in this VM, but may not be - * immediately written to the backing store. - * <p> - * Checks that key and value are valid, locks this node, and checks that - * the node has not been removed. Then it calls <code>putSpi()</code>. - * - * @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 <code>Boolean.toString(value)</code> - * 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. - * <p> - * 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 <code>Double.toString(double)</code> - * 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 <code>Float.toString(float)</code> - * 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 <code>Integer.toString(int)</code> - * 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 <code>Long.toString(long)</code> - * 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. - * <p> - * The result will be immediately visible in this VM, but may not be - * immediately written to the backing store. - * <p> - * 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 <code>removeSpi</code> 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. - * <p> - * The result will be immediately visible in this VM, but may not be - * immediatly written to the backing store. - * <p> - * This implementation locks this node, checks that the node has not been - * removed and calls <code>keys()</code> to get a complete array of keys - * for this node. For every key found <code>removeSpi()</code> 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 - * <code>sync()</code> method to actually see the changes to the backing - * store. - * <p> - * Locks this node, calls the <code>flushSpi()</code> method, gets all - * the (cached - already existing in this VM) subnodes and then calls - * <code>flushSpi()</code> 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). - * <p> - * Checks that this node is not removed, locks this node, calls the - * <code>syncSpi()</code> method, gets all the subnodes and then calls - * <code>syncSpi()</code> 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 - * <code>flushSpi()</code> if <code>sync</code> is false, or - * <code>flushSpi()</code> if <code>sync</code> is true. Then it gets all - * the currently cached subnodes. For every subnode it calls this method - * recursively with this node no longer locked. - * <p> - * Called by either <code>flush()</code> or <code>sync()</code> - */ - 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 <code>InvalidStateException</code>), - * even when a new node with the same path name is created this instance - * will not be usable again. - * <p> - * 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 <code>childSpi()</code> 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 <code>removeNode</code> with the parent node and this node - * locked. - * <p> - * Makes sure that all subnodes of this node are in the child cache, - * by calling <code>childSpi()</code> 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 ArrayList<NodeChangeListener>(); - nodeListeners.add(listener); - } - } - - /** - * Add a listener which is notified when a value in this node - * is added, changed, or removed. - * @param listener the listener to add - */ - public void addPreferenceChangeListener(PreferenceChangeListener listener) - { - synchronized (lock) - { - if (isRemoved()) - throw new IllegalStateException("node has been removed"); - if (listener == null) - throw new NullPointerException("listener is null"); - if (preferenceListeners == null) - preferenceListeners = new ArrayList<PreferenceChangeListener>(); - preferenceListeners.add(listener); - } - } - - /** - * Remove the indicated node change listener from the list of - * listeners to notify. - * @param listener the listener to remove - */ - public void removeNodeChangeListener(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.remove(listener); - } - } - - /** - * Remove the indicated preference change listener from the list of - * listeners to notify. - * @param listener the listener to remove - */ - public void removePreferenceChangeListener (PreferenceChangeListener listener) - { - synchronized (lock) - { - if (isRemoved()) - throw new IllegalStateException("node has been removed"); - if (listener == null) - throw new NullPointerException("listener is null"); - if (preferenceListeners != null) - preferenceListeners.remove(listener); - } - } - - /** - * Send a preference change event to all listeners. Note that - * the caller is responsible for holding the node's lock, and - * for checking that the list of listeners is not null. - * @param event the event to send - */ - private void fire(final PreferenceChangeEvent event) - { - for (final PreferenceChangeListener listener : preferenceListeners) - { - Runnable dispatcher = new Runnable() { - public void run() - { - listener.preferenceChange(event); - } - }; - - Executor executor = - Executors.newSingleThreadExecutor(new DefaultDaemonThreadFactory()); - executor.execute(dispatcher); - } - } - - /** - * Send a node change event to all listeners. Note that - * the caller is responsible for holding the node's lock, and - * for checking that the list of listeners is not null. - * @param event the event to send - */ - private void fire(final NodeChangeEvent event, final boolean added) - { - for (final NodeChangeListener listener : nodeListeners) - { - Runnable dispatcher = new Runnable() { - public void run() - { - if (added) - listener.childAdded(event); - else - listener.childRemoved(event); - } - }; - - Executor executor = - Executors.newSingleThreadExecutor(new DefaultDaemonThreadFactory()); - executor.execute(dispatcher); - } - } - - // abstract spi methods - - /** - * Returns the names of the sub nodes of this preference node. - * This method only has to return any not yet cached child names, - * but may return all names if that is easier. It must not return - * null when there are no children, it has to return an empty array - * in that case. Since this method must consult the backing store to - * get all the sub node names it may throw a BackingStoreException. - * <p> - * Called by <code>childrenNames()</code> with this node locked. - */ - protected abstract String[] childrenNamesSpi() throws BackingStoreException; - - /** - * Returns a child note with the given name. - * This method is called by the <code>node()</code> method (indirectly - * through the <code>getNode()</code> 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 <code>newNode</code> of the returned node should be set. - * <p> - * 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 - * <code>BackingStoreException</code>. - */ - protected abstract AbstractPreferences childSpi(String name); - - /** - * Returns an (possibly empty) array with all the keys of the preference - * entries of this node. - * <p> - * Called by <code>keys()</code> 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. - * <p> - * Called by <code>key()</code> with this node locked after checking that - * key is valid, not null and that the node has not been removed. - * <code>key()</code> 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 <code>flush()</code> - * or <code>sync()</code> can signal the failure. - * <p> - * Called by <code>put()</code> 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 <code>flush()</code> - * or <code>sync()</code> can signal the failure. - * <p> - * Called by <code>remove()</code> 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 <code>isRemoved()</code>. - * <p> - * Called (indirectly) by <code>flush()</code> 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 <code>isRemoved()</code>. - * <p> - * Called (indirectly) by <code>sync()</code> 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. - * <p> - * Called (indirectly) by <code>removeNode()</code> with this node locked - * after all the sub nodes of this node have already been removed. - */ - protected abstract void removeNodeSpi() throws BackingStoreException; -} |