Source Code Cross Referenced for AbstractPreferences.java in  » 6.0-JDK-Core » Collections-Jar-Zip-Logging-regex » java » util » prefs » Java Source Code / Java DocumentationJava Source Code and Java Documentation

Home
Java Source Code / Java Documentation
1.6.0 JDK Core
2.6.0 JDK Modules
3.6.0 JDK Modules com.sun
4.6.0 JDK Modules com.sun.java
5.6.0 JDK Modules sun
6.6.0 JDK Platform
7.Ajax
8.Apache Harmony Java SE
9.Aspect oriented
10.Authentication Authorization
11.Blogger System
12.Build
13.Byte Code
14.Cache
15.Chart
16.Chat
17.Code Analyzer
18.Collaboration
19.Content Management System
20.Database Client
21.Database DBMS
22.Database JDBC Connection Pool
23.Database ORM
24.Development
25.EJB Server
26.ERP CRM Financial
27.ESB
28.Forum
29.Game
30.GIS
31.Graphic 3D
32.Graphic Library
33.Groupware
34.HTML Parser
35.IDE
36.IDE Eclipse
37.IDE Netbeans
38.Installer
39.Internationalization Localization
40.Inversion of Control
41.Issue Tracking
42.J2EE
43.J2ME
44.JBoss
45.JMS
46.JMX
47.Library
48.Mail Clients
49.Music
50.Net
51.Parser
52.PDF
53.Portal
54.Profiler
55.Project Management
56.Report
57.RSS RDF
58.Rule Engine
59.Science
60.Scripting
61.Search Engine
62.Security
63.Sevlet Container
64.Source Control
65.Swing Library
66.Template Engine
67.Test Coverage
68.Testing
69.UML
70.Web Crawler
71.Web Framework
72.Web Mail
73.Web Server
74.Web Services
75.Web Services apache cxf 2.2.6
76.Web Services AXIS2
77.Wiki Engine
78.Workflow Engines
79.XML
80.XML UI
Java Source Code / Java Documentation » 6.0 JDK Core » Collections Jar Zip Logging regex » java.util.prefs 
Source Cross Referenced  Class Diagram Java Document (Java Doc) 


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