001    /* Preferences -- Preference node containing key value entries and subnodes
002       Copyright (C) 2001, 2004, 2005, 2006  Free Software Foundation, Inc.
003    
004    This file is part of GNU Classpath.
005    
006    GNU Classpath is free software; you can redistribute it and/or modify
007    it under the terms of the GNU General Public License as published by
008    the Free Software Foundation; either version 2, or (at your option)
009    any later version.
010    
011    GNU Classpath is distributed in the hope that it will be useful, but
012    WITHOUT ANY WARRANTY; without even the implied warranty of
013    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014    General Public License for more details.
015    
016    You should have received a copy of the GNU General Public License
017    along with GNU Classpath; see the file COPYING.  If not, write to the
018    Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
019    02110-1301 USA.
020    
021    Linking this library statically or dynamically with other modules is
022    making a combined work based on this library.  Thus, the terms and
023    conditions of the GNU General Public License cover the whole
024    combination.
025    
026    As a special exception, the copyright holders of this library give you
027    permission to link this library with independent modules to produce an
028    executable, regardless of the license terms of these independent
029    modules, and to copy and distribute the resulting executable under
030    terms of your choice, provided that you also meet, for each linked
031    independent module, the terms and conditions of the license of that
032    module.  An independent module is a module which is not derived from
033    or based on this library.  If you modify this library, you may extend
034    this exception to your version of the library, but you are not
035    obligated to do so.  If you do not wish to do so, delete this
036    exception statement from your version. */
037    
038    package java.util.prefs;
039    
040    import gnu.classpath.ServiceFactory;
041    import gnu.java.util.prefs.NodeReader;
042    
043    import java.io.IOException;
044    import java.io.InputStream;
045    import java.io.OutputStream;
046    import java.security.AccessController;
047    import java.security.Permission;
048    import java.security.PrivilegedAction;
049    import java.util.Iterator;
050    
051    /**
052     * Preference node containing key value entries and subnodes.
053     * <p>
054     * There are two preference node trees, a system tree which can be accessed
055     * by calling <code>systemRoot()</code> containing system preferences usefull
056     * for all users, and a user tree that can be accessed by calling
057     * <code>userRoot()</code> containing preferences that can differ between
058     * different users. How different users are identified is implementation
059     * depended. It can be determined by Thread, Access Control Context or Subject.
060     * <p>
061     * This implementation uses the "java.util.prefs.PreferencesFactory" system
062     * property to find a class that implement <code>PreferencesFactory</code>
063     * and initialized that class (if it has a public no arguments contructor)
064     * to get at the actual system or user root. If the system property is not set,
065     * or the class cannot be initialized it uses the default implementation
066     * <code>gnu.java.util.prefs.FileBasedFactory</code>.
067     * <p>
068     * Besides the two static method above to get the roots of the system and user
069     * preference node trees there are also two convenience methods to access the
070     * default preference node for a particular package an object is in. These are
071     * <code>userNodeForPackage()</code> and <code>systemNodeForPackage()</code>.
072     * Both methods take an Object as an argument so accessing preferences values
073     * can be as easy as calling <code>Preferences.userNodeForPackage(this)</code>.
074     * <p>
075     * Note that if a security manager is installed all static methods check for
076     * <code>RuntimePermission("preferences")</code>. But if this permission is
077     * given to the code then it can access and change all (user) preference nodes
078     * and entries. So you should be carefull not to store to sensitive information
079     * or make security decissions based on preference values since there is no
080     * more fine grained control over what preference values can be changed once
081     * code has been given the correct runtime permission.
082     * <p>
083     * XXX
084     *
085     * @since 1.4
086     * @author Mark Wielaard (mark@klomp.org)
087     */
088    public abstract class Preferences {
089    
090        // Static Fields
091    
092        /**
093         * Default PreferencesFactory class used when the system property
094         * "java.util.prefs.PreferencesFactory" is not set.
095         */
096        private static final String defaultFactoryClass
097            = "gnu.java.util.prefs.FileBasedFactory";
098    
099        /** Permission needed to access system or user root. */
100        private static final Permission prefsPermission
101            = new RuntimePermission("preferences");
102    
103        /**
104         * The preferences factory object that supplies the system and user root.
105         * Set and returned by the getFactory() method.
106         */
107        private static PreferencesFactory factory;
108    
109        /** Maximum node name length. 80 characters. */
110        public static final int MAX_NAME_LENGTH = 80;
111    
112        /** Maximum entry key length. 80 characters. */
113        public static final int MAX_KEY_LENGTH = 80;
114    
115        /** Maximum entry value length. 8192 characters. */
116        public static final int MAX_VALUE_LENGTH = 8192;
117    
118        // Constructors
119    
120        /**
121         * Creates a new Preferences node. Can only be used by subclasses.
122         * Empty implementation.
123         */
124        protected Preferences() {}
125    
126        // Static methods
127    
128        /**
129         * Returns the system preferences root node containing usefull preferences
130         * for all users. It is save to cache this value since it should always
131         * return the same preference node.
132         *
133         * @return the root system preference node
134         * @exception SecurityException when a security manager is installed and
135         * the caller does not have <code>RuntimePermission("preferences")</code>.
136         */
137        public static Preferences systemRoot() throws SecurityException {
138            // Get the preferences factory and check for permission
139            PreferencesFactory factory = getFactory();
140    
141            return factory.systemRoot();
142        }
143    
144        /**
145         * Returns the user preferences root node containing preferences for the
146         * the current user. How different users are identified is implementation
147         * depended. It can be determined by Thread, Access Control Context or
148         * Subject.
149         *
150         * @return the root user preference node
151         * @exception SecurityException when a security manager is installed and
152         * the caller does not have <code>RuntimePermission("preferences")</code>.
153         */
154        public static Preferences userRoot() throws SecurityException {
155            // Get the preferences factory and check for permission
156            PreferencesFactory factory = getFactory();
157            return factory.userRoot();
158        }
159    
160        /**
161         * Private helper method for <code>systemRoot()</code> and
162         * <code>userRoot()</code>. Checks security permission and instantiates the
163         * correct factory if it has not yet been set.
164         * <p>
165         * When the preferences factory has not yet been set this method first
166         * tries to get the system propery "java.util.prefs.PreferencesFactory"
167         * and tries to initializes that class. If the system property is not set
168         * or initialization fails it returns an instance of the default factory
169         * <code>gnu.java.util.prefs.FileBasedPreferencesFactory</code>.
170         *
171         * @return the preferences factory to use
172         * @exception SecurityException when a security manager is installed and
173         * the caller does not have <code>RuntimePermission("preferences")</code>.
174         */
175        private static PreferencesFactory getFactory() throws SecurityException {
176    
177            // First check for permission
178            SecurityManager sm = System.getSecurityManager();
179            if (sm != null) {
180                sm.checkPermission(prefsPermission);
181            }
182    
183            // Get the factory
184            if (factory == null) {
185                // Caller might not have enough permissions
186                factory = AccessController.doPrivileged(
187                            new PrivilegedAction<PreferencesFactory>() {
188                                public PreferencesFactory run() {
189                                    PreferencesFactory pf = null;
190                                    String className = System.getProperty
191                                        ("java.util.prefs.PreferencesFactory");
192                                    if (className != null) {
193                                        try {
194                                            Class fc = Class.forName(className);
195                                            Object o = fc.newInstance();
196                                            pf = (PreferencesFactory) o;
197                                        } catch (ClassNotFoundException cnfe)
198                                            {/*ignore*/}
199                                        catch (InstantiationException ie)
200                                            {/*ignore*/}
201                                        catch (IllegalAccessException iae)
202                                            {/*ignore*/}
203                                        catch (ClassCastException cce)
204                                            {/*ignore*/}
205                                    }
206                                    return pf;
207                                }
208                            });
209    
210                // Still no factory? Try to see if we have one defined
211                // as a System Preference
212                if (factory == null)
213                  {
214                    Iterator iter = ServiceFactory.lookupProviders
215                        (PreferencesFactory.class, null);
216    
217                    if (iter != null && iter.hasNext())
218                      factory = (PreferencesFactory) iter.next();
219                  }
220    
221                // Still no factory? Use our default.
222                if (factory == null)
223                  {
224                    try
225                      {
226                        Class cls = Class.forName (defaultFactoryClass);
227                        factory = (PreferencesFactory) cls.newInstance();
228                      }
229                    catch (Exception e)
230                      {
231                        throw new RuntimeException ("Couldn't load default factory"
232                            + " '"+ defaultFactoryClass +"'", e);
233                      }
234                  }
235    
236            }
237    
238            return factory;
239        }
240    
241        /**
242         * Returns the system preferences node for the package of a class.
243         * The package node name of the class is determined by dropping the
244         * final component of the fully qualified class name and
245         * changing all '.' to '/' in the package name. If the class of the
246         * object has no package then the package node name is "&lt;unnamed&gt;".
247         * The returned node is <code>systemRoot().node(packageNodeName)</code>.
248         *
249         * @param c Object whose default system preference node is requested
250         * @returns system preferences node that should be used by class c
251         * @exception SecurityException when a security manager is installed and
252         * the caller does not have <code>RuntimePermission("preferences")</code>.
253         */
254        public static Preferences systemNodeForPackage(Class<?> c)
255                throws SecurityException
256        {
257            return nodeForPackage(c, systemRoot());
258        }
259    
260        /**
261         * Returns the user preferences node for the package of a class.
262         * The package node name of the class is determined by dropping the
263         * final component of the fully qualified class name and
264         * changing all '.' to '/' in the package name. If the class of the
265         * object has no package then the package node name is "&lt;unnamed&gt;".
266         * The returned node is <code>userRoot().node(packageNodeName)</code>.
267         *
268         * @param c Object whose default userpreference node is requested
269         * @returns userpreferences node that should be used by class c
270         * @exception SecurityException when a security manager is installed and
271         * the caller does not have <code>RuntimePermission("preferences")</code>.
272         */
273        public static Preferences userNodeForPackage(Class<?> c)
274                throws SecurityException
275        {
276            return nodeForPackage(c, userRoot());
277        }
278    
279        /**
280         * Private helper method for <code>systemNodeForPackage()</code> and
281         * <code>userNodeForPackage()</code>. Given the correct system or user
282         * root it returns the correct Preference node for the package node name
283         * of the given object.
284         */
285        private static Preferences nodeForPackage(Class c, Preferences root) {
286            // Get the package path
287            String className = c.getName();
288            String packagePath;
289            int index = className.lastIndexOf('.');
290            if(index == -1) {
291                packagePath = "<unnamed>";
292            } else {
293                packagePath = className.substring(0,index).replace('.','/');
294            }
295    
296            return root.node(packagePath);
297        }
298    
299        /**
300         * Import preferences from the given input stream.  This expects
301         * preferences to be represented in XML as emitted by
302         * {@link #exportNode(OutputStream)} and
303         * {@link #exportSubtree(OutputStream)}.
304         * @throws IOException if there is an error while reading
305         * @throws InvalidPreferencesFormatException if the XML is not properly
306         * formatted
307         */
308        public static void importPreferences(InputStream is)
309                                        throws InvalidPreferencesFormatException,
310                                               IOException
311        {
312            PreferencesFactory factory = getFactory();
313            NodeReader reader = new NodeReader(is, factory);
314            reader.importPreferences();
315        }
316    
317        // abstract methods (identification)
318    
319        /**
320         * Returns the absolute path name of this preference node.
321         * The absolute path name of a node is the path name of its parent node
322         * plus a '/' plus its own name. If the node is the root node and has no
323         * parent then its name is "" and its absolute path name is "/".
324         */
325        public abstract String absolutePath();
326    
327        /**
328         * Returns true if this node comes from the user preferences tree, false
329         * if it comes from the system preferences tree.
330         */
331        public abstract boolean isUserNode();
332    
333        /**
334         * Returns the name of this preferences node. The name of the node cannot
335         * be null, can be mostly 80 characters and cannot contain any '/'
336         * characters. The root node has as name "".
337         */
338        public abstract String name();
339    
340        /**
341         * Returns the String given by
342         * <code>
343         * (isUserNode() ? "User":"System") + " Preference Node: " + absolutePath()
344         * </code>
345         */
346        public abstract String toString();
347    
348        // abstract methods (navigation)
349    
350        /**
351         * Returns all the direct sub nodes of this preferences node.
352         * Needs access to the backing store to give a meaningfull answer.
353         *
354         * @exception BackingStoreException when the backing store cannot be
355         *            reached
356         * @exception IllegalStateException when this node has been removed
357         */
358        public abstract String[] childrenNames() throws BackingStoreException;
359    
360        /**
361         * Returns a sub node of this preferences node if the given path is
362         * relative (does not start with a '/') or a sub node of the root
363         * if the path is absolute (does start with a '/').
364         *
365         * @exception IllegalStateException if this node has been removed
366         * @exception IllegalArgumentException if the path contains two or more
367         * consecutive '/' characters, ends with a '/' charactor and is not the
368         * string "/" (indicating the root node) or any name on the path is more
369         * then 80 characters long
370         */
371        public abstract Preferences node(String path);
372    
373        /**
374         * Returns true if the node that the path points to exists in memory or
375         * in the backing store. Otherwise it returns false or an exception is
376         * thrown. When this node is removed the only valid parameter is the
377         * empty string (indicating this node), the return value in that case
378         * will be false.
379         *
380         * @exception BackingStoreException when the backing store cannot be
381         *            reached
382         * @exception IllegalStateException if this node has been removed
383         *            and the path is not the empty string (indicating this node)
384         * @exception IllegalArgumentException if the path contains two or more
385         * consecutive '/' characters, ends with a '/' charactor and is not the
386         * string "/" (indicating the root node) or any name on the path is more
387         * then 80 characters long
388         */
389        public abstract boolean nodeExists(String path)
390                                    throws BackingStoreException;
391    
392        /**
393         * Returns the parent preferences node of this node or null if this is
394         * the root of the preferences tree.
395         *
396         * @exception IllegalStateException if this node has been removed
397         */
398        public abstract Preferences parent();
399    
400        // abstract methods (export)
401    
402        /**
403         * Export this node, but not its descendants, as XML to the
404         * indicated output stream.  The XML will be encoded using UTF-8
405         * and will use a specified document type:<br>
406         * <code>&lt;!DOCTYPE preferences SYSTEM "http://java.sun.com/dtd/preferences.dtd"&gt;</code><br>
407         * @param os the output stream to which the XML is sent
408         * @throws BackingStoreException if preference data cannot be read
409         * @throws IOException if an error occurs while writing the XML
410         * @throws IllegalStateException if this node or an ancestor has been removed
411         */
412        public abstract void exportNode(OutputStream os)
413                                    throws BackingStoreException,
414                                           IOException;
415    
416        /**
417         * Export this node and all its descendants as XML to the
418         * indicated output stream.  The XML will be encoded using UTF-8
419         * and will use a specified document type:<br>
420         * <code>&lt;!DOCTYPE preferences SYSTEM "http://java.sun.com/dtd/preferences.dtd"&gt;</code><br>
421         * @param os the output stream to which the XML is sent
422         * @throws BackingStoreException if preference data cannot be read
423         * @throws IOException if an error occurs while writing the XML
424         * @throws IllegalStateException if this node or an ancestor has been removed
425         */
426        public abstract void exportSubtree(OutputStream os)
427                                    throws BackingStoreException,
428                                           IOException;
429    
430        // abstract methods (preference entry manipulation)
431    
432        /**
433         * Returns an (possibly empty) array with all the keys of the preference
434         * entries of this node.
435         *
436         * @exception BackingStoreException when the backing store cannot be
437         *            reached
438         * @exception IllegalStateException if this node has been removed
439         */
440        public abstract String[] keys() throws BackingStoreException;
441    
442        /**
443         * Returns the value associated with the key in this preferences node. If
444         * the default value of the key cannot be found in the preferences node
445         * entries or something goes wrong with the backing store the supplied
446         * default value is returned.
447         *
448         * @exception IllegalArgumentException if key is larger then 80 characters
449         * @exception IllegalStateException if this node has been removed
450         * @exception NullPointerException if key is null
451         */
452        public abstract String get(String key, String defaultVal);
453    
454        /**
455         * Convenience method for getting the given entry as a boolean.
456         * When the string representation of the requested entry is either
457         * "true" or "false" (ignoring case) then that value is returned,
458         * otherwise the given default boolean value is returned.
459         *
460         * @exception IllegalArgumentException if key is larger then 80 characters
461         * @exception IllegalStateException if this node has been removed
462         * @exception NullPointerException if key is null
463         */
464        public abstract boolean getBoolean(String key, boolean defaultVal);
465    
466        /**
467         * Convenience method for getting the given entry as a byte array.
468         * When the string representation of the requested entry is a valid
469         * Base64 encoded string (without any other characters, such as newlines)
470         * then the decoded Base64 string is returned as byte array,
471         * otherwise the given default byte array value is returned.
472         *
473         * @exception IllegalArgumentException if key is larger then 80 characters
474         * @exception IllegalStateException if this node has been removed
475         * @exception NullPointerException if key is null
476         */
477        public abstract byte[] getByteArray(String key, byte[] defaultVal);
478    
479        /**
480         * Convenience method for getting the given entry as a double.
481         * When the string representation of the requested entry can be decoded
482         * with <code>Double.parseDouble()</code> then that double is returned,
483         * otherwise the given default double value is returned.
484         *
485         * @exception IllegalArgumentException if key is larger then 80 characters
486         * @exception IllegalStateException if this node has been removed
487         * @exception NullPointerException if key is null
488         */
489        public abstract double getDouble(String key, double defaultVal);
490    
491        /**
492         * Convenience method for getting the given entry as a float.
493         * When the string representation of the requested entry can be decoded
494         * with <code>Float.parseFloat()</code> then that float is returned,
495         * otherwise the given default float value is returned.
496         *
497         * @exception IllegalArgumentException if key is larger then 80 characters
498         * @exception IllegalStateException if this node has been removed
499         * @exception NullPointerException if key is null
500         */
501        public abstract float getFloat(String key, float defaultVal);
502    
503        /**
504         * Convenience method for getting the given entry as an integer.
505         * When the string representation of the requested entry can be decoded
506         * with <code>Integer.parseInt()</code> then that integer is returned,
507         * otherwise the given default integer value is returned.
508         *
509         * @exception IllegalArgumentException if key is larger then 80 characters
510         * @exception IllegalStateException if this node has been removed
511         * @exception NullPointerException if key is null
512         */
513        public abstract int getInt(String key, int defaultVal);
514    
515        /**
516         * Convenience method for getting the given entry as a long.
517         * When the string representation of the requested entry can be decoded
518         * with <code>Long.parseLong()</code> then that long is returned,
519         * otherwise the given default long value is returned.
520         *
521         * @exception IllegalArgumentException if key is larger then 80 characters
522         * @exception IllegalStateException if this node has been removed
523         * @exception NullPointerException if key is null
524         */
525        public abstract long getLong(String key, long defaultVal);
526    
527        /**
528         * Sets the value of the given preferences entry for this node.
529         * Key and value cannot be null, the key cannot exceed 80 characters
530         * and the value cannot exceed 8192 characters.
531         * <p>
532         * The result will be immediatly visible in this VM, but may not be
533         * immediatly written to the backing store.
534         *
535         * @exception NullPointerException if either key or value are null
536         * @exception IllegalArgumentException if either key or value are to large
537         * @exception IllegalStateException when this node has been removed
538         */
539        public abstract void put(String key, String value);
540    
541        /**
542         * Convenience method for setting the given entry as a boolean.
543         * The boolean is converted with <code>Boolean.toString(value)</code>
544         * and then stored in the preference entry as that string.
545         *
546         * @exception NullPointerException if key is null
547         * @exception IllegalArgumentException if the key length is to large
548         * @exception IllegalStateException when this node has been removed
549         */
550        public abstract void putBoolean(String key, boolean value);
551    
552        /**
553         * Convenience method for setting the given entry as an array of bytes.
554         * The byte array is converted to a Base64 encoded string
555         * and then stored in the preference entry as that string.
556         * <p>
557         * Note that a byte array encoded as a Base64 string will be about 1.3
558         * times larger then the original length of the byte array, which means
559         * that the byte array may not be larger about 6 KB.
560         *
561         * @exception NullPointerException if either key or value are null
562         * @exception IllegalArgumentException if either key or value are to large
563         * @exception IllegalStateException when this node has been removed
564         */
565        public abstract void putByteArray(String key, byte[] value);
566    
567        /**
568         * Convenience method for setting the given entry as a double.
569         * The double is converted with <code>Double.toString(double)</code>
570         * and then stored in the preference entry as that string.
571         *
572         * @exception NullPointerException if the key is null
573         * @exception IllegalArgumentException if the key length is to large
574         * @exception IllegalStateException when this node has been removed
575         */
576        public abstract void putDouble(String key, double value);
577    
578        /**
579         * Convenience method for setting the given entry as a float.
580         * The float is converted with <code>Float.toString(float)</code>
581         * and then stored in the preference entry as that string.
582         *
583         * @exception NullPointerException if the key is null
584         * @exception IllegalArgumentException if the key length is to large
585         * @exception IllegalStateException when this node has been removed
586         */
587        public abstract void putFloat(String key, float value);
588    
589        /**
590         * Convenience method for setting the given entry as an integer.
591         * The integer is converted with <code>Integer.toString(int)</code>
592         * and then stored in the preference entry as that string.
593         *
594         * @exception NullPointerException if the key is null
595         * @exception IllegalArgumentException if the key length is to large
596         * @exception IllegalStateException when this node has been removed
597         */
598        public abstract void putInt(String key, int value);
599    
600        /**
601         * Convenience method for setting the given entry as a long.
602         * The long is converted with <code>Long.toString(long)</code>
603         * and then stored in the preference entry as that string.
604         *
605         * @exception NullPointerException if the key is null
606         * @exception IllegalArgumentException if the key length is to large
607         * @exception IllegalStateException when this node has been removed
608         */
609        public abstract void putLong(String key, long value);
610    
611        /**
612         * Removes the preferences entry from this preferences node.
613         * <p>
614         * The result will be immediatly visible in this VM, but may not be
615         * immediatly written to the backing store.
616         *
617         * @exception NullPointerException if the key is null
618         * @exception IllegalArgumentException if the key length is to large
619         * @exception IllegalStateException when this node has been removed
620         */
621        public abstract void remove(String key);
622    
623        // abstract methods (preference node manipulation)
624    
625        /**
626         * Removes all entries from this preferences node. May need access to the
627         * backing store to get and clear all entries.
628         * <p>
629         * The result will be immediatly visible in this VM, but may not be
630         * immediatly written to the backing store.
631         *
632         * @exception BackingStoreException when the backing store cannot be
633         *            reached
634         * @exception IllegalStateException if this node has been removed
635         */
636        public abstract void clear() throws BackingStoreException;
637    
638        /**
639         * Writes all preference changes on this and any subnode that have not
640         * yet been written to the backing store. This has no effect on the
641         * preference entries in this VM, but it makes sure that all changes
642         * are visible to other programs (other VMs might need to call the
643         * <code>sync()</code> method to actually see the changes to the backing
644         * store.
645         *
646         * @exception BackingStoreException when the backing store cannot be
647         *            reached
648         * @exception IllegalStateException if this node has been removed
649         */
650        public abstract void flush() throws BackingStoreException;
651    
652        /**
653         * Writes and reads all preference changes to and from this and any
654         * subnodes. This makes sure that all local changes are written to the
655         * backing store and that all changes to the backing store are visible
656         * in this preference node (and all subnodes).
657         *
658         * @exception BackingStoreException when the backing store cannot be
659         *            reached
660         * @exception IllegalStateException if this node has been removed
661         */
662        public abstract void sync() throws BackingStoreException;
663    
664        /**
665         * Removes this and all subnodes from the backing store and clears all
666         * entries. After removal this instance will not be useable (except for
667         * a few methods that don't throw a <code>InvalidStateException</code>),
668         * even when a new node with the same path name is created this instance
669         * will not be usable again. The root (system or user) may never be removed.
670         * <p>
671         * Note that according to the specification an implementation may delay
672         * removal of the node from the backing store till the <code>flush()</code>
673         * method is called. But the <code>flush()</code> method may throw a
674         * <code>IllegalStateException</code> when the node has been removed.
675         * So most implementations will actually remove the node and any subnodes
676         * from the backing store immediatly.
677         *
678         * @exception BackingStoreException when the backing store cannot be
679         *            reached
680         * @exception IllegalStateException if this node has already been removed
681         * @exception UnsupportedOperationException if this is a root node
682         */
683        public abstract void removeNode() throws BackingStoreException;
684    
685        // abstract methods (listeners)
686    
687        public abstract void addNodeChangeListener(NodeChangeListener listener);
688    
689        public abstract void addPreferenceChangeListener
690                                (PreferenceChangeListener listener);
691    
692        public abstract void removeNodeChangeListener(NodeChangeListener listener);
693    
694        public abstract void removePreferenceChangeListener
695                                (PreferenceChangeListener listener);
696    }