001/* Preferences -- Preference node containing key value entries and subnodes
002   Copyright (C) 2001, 2004, 2005, 2006  Free Software Foundation, Inc.
003
004This file is part of GNU Classpath.
005
006GNU Classpath is free software; you can redistribute it and/or modify
007it under the terms of the GNU General Public License as published by
008the Free Software Foundation; either version 2, or (at your option)
009any later version.
010
011GNU Classpath is distributed in the hope that it will be useful, but
012WITHOUT ANY WARRANTY; without even the implied warranty of
013MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014General Public License for more details.
015
016You should have received a copy of the GNU General Public License
017along with GNU Classpath; see the file COPYING.  If not, write to the
018Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
01902110-1301 USA.
020
021Linking this library statically or dynamically with other modules is
022making a combined work based on this library.  Thus, the terms and
023conditions of the GNU General Public License cover the whole
024combination.
025
026As a special exception, the copyright holders of this library give you
027permission to link this library with independent modules to produce an
028executable, regardless of the license terms of these independent
029modules, and to copy and distribute the resulting executable under
030terms of your choice, provided that you also meet, for each linked
031independent module, the terms and conditions of the license of that
032module.  An independent module is a module which is not derived from
033or based on this library.  If you modify this library, you may extend
034this exception to your version of the library, but you are not
035obligated to do so.  If you do not wish to do so, delete this
036exception statement from your version. */
037
038package java.util.prefs;
039
040import gnu.classpath.ServiceFactory;
041import gnu.java.util.prefs.NodeReader;
042
043import java.io.IOException;
044import java.io.InputStream;
045import java.io.OutputStream;
046import java.security.AccessController;
047import java.security.Permission;
048import java.security.PrivilegedAction;
049import 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 */
088public 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}