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:
Preferences
prefs
=
Preferences
.
userRoot
().
node
(
"oreilly/learningjava"
);
prefs
.
put
(
"author"
,
"Niemeyer"
);
prefs
.
putInt
(
"edition"
,
4
);
String
author
=
prefs
.
get
(
"author"
,
"unknown"
);
int
edition
=
prefs
.
getInt
(
"edition"
,
-
1
);
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:
Preferences
prefs
=
Preferences
.
userRoot
().
node
(
"oreilly"
).
node
(
"learningjava"
);
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.
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:
Preferences
datePrefs
=
Preferences
.
systemNodeForPackage
(
Date
.
class
);
Preferences
myPrefs
=
Preferences
.
userNodeForPackage
(
MyClass
.
class
);
Preferences
morePrefs
=
Preferences
.
userNodeForPackage
(
myObject
.
getClass
()
);
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.
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 remove
Node()
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.
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:
Preferences
prefs
=
Preferences
.
userRoot
().
node
(
"/oreilly/learningjava"
);
prefs
.
addPreferenceChangeListener
(
new
PreferenceChangeListener
()
{
public
void
preferenceChange
(
PreferenceChangeEvent
e
)
{
System
.
out
.
println
(
"Value: "
+
e
.
getKey
()
+
" changed to "
+
e
.
getNewValue
()
);
}
}
);
In brief, this example listens for changes to preferences and prints them. If this example isn’t immediately clear, it should be after you’ve read about events in Chapter 16 and beyond.
Get Learning Java, 4th Edition now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.