The Preferences API

The Java Preferences API accommodates the need to store both system
and per-user configuration data persistently across executions of the Java
VM. The Preferences API is like a portable version of the Windows
registry, a mini-database in which you can keep small amounts of
information, accessible to all applications. Entries are stored as
name/value pairs, where the values may be of several standard types
including strings, numbers, Booleans, and even short byte arrays. We
should stress that the Preferences API is not intended to be used as a
true database and you can’t store large amounts of data in it.

Preferences are stored logically in a tree. A preferences object is
a node in the tree located by a unique path. You can think of preferences
as files in a directory structure; within the file are stored one or more
name/value pairs. To store or retrieve items, you ask for a preferences
object for the correct path. Here is an example; we’ll explain the node
lookup shortly:

In addition to the String and
int type accessors, there are the
following get methods for other types: getLong(), getFloat(), getDouble(), getByteArray(), and getBoolean(). Each of these get methods takes a
key name and default value to be used if no value is defined. And, of
course, for each get method, there is a corresponding “put” method that
takes the name and a value of the corresponding type. Providing defaults
in the get methods is mandatory. The intent is for applications to
function even if there is no preference information or if the storage for
it is not available, as we’ll discuss later.

Preferences are stored in two separate trees: system
preferences and user preferences. System preferences
are shared by all users of the Java installation. But user
preferences are maintained separately for each user; each user
sees his or her own preference information. In our example, we used the
static method userRoot() to fetch the
root node (preference object) for the user preferences tree. We then asked
that node to find the child node at the path
oreilly/learningjava, using the node() method. The
corresponding systemRoot() method
provides the system root node.

The node() method accepts either
a relative or an absolute path. A relative path asks the node to find the
path relative to itself as a base. We also could have gotten our node this
way:

But node() also accepts an
absolute path, in which case the base node serves only to designate the
tree that the path is in. We could use the absolute path
/oreilly/learningjava as the argument to any node() method and reach our preferences
object.

Preferences for Classes

Java is an object-oriented language, and so it’s natural
to wish to associate preference data with classes. In Chapter 12, we’ll see that Java provides special
facilities for loading resource files associated with class files. The
Preferences API follows this pattern by associating a node with each
Java package. Its convention is simple: the node path is just the
package name with the dots (.) converted to slashes (/). All classes in
the package share the same node.

You can get the preference object node for a class using the
static Preferences.userNodeForPackage() or Preferences.systemNodeForPackage() methods,
which take a Class as an argument and
return the corresponding package node for the user and system trees,
respectively. For example:

Here, we’ve used the .class
construct to refer to the Class
object for the Date class in the
system tree and to our own MyClass
class in the user tree. The Date
class is in the java.util package, so
we’ll get the node /java/util in that case. You can
get the Class for any object instance
using the getClass() method.

Preferences Storage

There is no need to “create” nodes. When you ask for a
node, you get a preferences object for that path in the tree. If you
write something to it, that data is eventually placed in persistent
storage, called the backing store. The backing
store is the implementation-dependent storage mechanism used
to hold the preference data. All the put methods return immediately, and
no guarantees are made as to when the data is actually stored. You can
force data to the backing store explicitly using the flush() method of the
Preferences class.
Conversely, you can use the sync() method to
guarantee that a preferences object is up-to-date with respect to
changes placed into the backing store by other applications or threads.
Both flush() and sync() throw a BackingStoreException if data cannot be read
or written for some reason.

You don’t have to create nodes, but you can test for the existence
of a data node with the nodeExists() method,
and you can remove a node and all its children with the removeNode() method. To remove a data item from
a node, use the remove() method,
specifying the key; or you can remove all the data from a node with the
clear() method (which
is not the same as removing the node).

Although the details of the backing store are
implementation-dependent, the Preferences API provides a simple
import/export facility that can read and write parts of a preference
tree to an XML file. (The format for the file is available at
http://java.sun.com/dtd/.) A
preference object can be written to an output stream with the exportNode() method.
The exportSubtree() method
writes the node and all its children. Going the other way, the static
Preferences.importPreferences() method can
read the XML file and populate the appropriate tree with its data. The
XML file records whether it is user or system preferences, but user data
is always placed into the current user’s tree, regardless of who
generated it.

It’s interesting to note that because the import mechanism writes
directly to the tree, you can’t use this as a general data-to-XML
storage mechanism (other APIs play that role). Also, although we said
that the implementation details are not specified, it’s interesting how
things really work in the current implementation. On some systems, Java
creates a directory hierarchy for each tree at
$JAVA_HOME/jre/.systemPrefs and
$HOME/.java/.userPrefs, respectively. In each
directory, there is an XML file called prefs.xml
corresponding to that node.

Change Notification

Often your application should be notified if changes are
made to the preferences while it’s running. You can get updates on
preference changes using the PreferenceChangeListener and NodeChangeListener
interfaces. These interfaces are examples of event
listener interfaces, and we’ll see many examples of these in
Chapters 16 through 18. We’ll
also talk about the general pattern later in this chapter in the section
Observers and Observables. For now, we’ll just say
that by registering an object that implements PreferenceChangeListener with a node, you can
receive updates on added, removed, and changed preference data for that
node. The NodeChangeListener allows
you to be told when child nodes are added to or removed from a specific
node. Here is a snippet that prints all the data changes affecting our
/oreilly/learningjava node: