Starting with J2SE 1.4, the standard set of APIs will include the
Preferences API made up of classes and interfaces in the
java.util.prefs package. The Preferences API has been
designed to provide a platform independent interface for storing
preferences data. This article explores the Preferences API as it is
implemented in the J2SE 1.4 Beta 2. Note that some details of the
implementation may change in the final release of J2SE 1.4.
The Preferences API allows preference data to be divided into two categories, "System" preferences and "User" preferences. It is left to the application developer to categorize each preference into one of these. Generally system preferences include system wide attributes such as port numbers and other data that should be shared by all users of the system. User preferences include things like font or color preferences that may be user dependant.
Each of the two preference categories are structured as a
hierarchical tree to allow for fine grained organization of the
data. For example, under either of the two preferences trees there
might be a hierarchy like apps/JAsteroids/HighScores/
or apps/JAsteroids/ScreenPreferences/. In this case,
the "apps" node is the parent node to the "JAsteroids" node and the
"JAsteroids" node is the parent node to both the "HighScores" node
and the "ScreenPreferences" nodes.
Any node in a preference tree may contain other nodes and may contain preference data. The types of data that can be stored as preferences are...
int
float
long
double
boolean
byte[]
String
An instance of java.util.prefs.Preferences represents a
particular node in one of the two preference trees. As
Preferences is an abstract class, an instance may not
be created directly but instead should be retrieved from one of the
static methods in the Preferences class.
// retrieve a reference to the root of the user preferences tree
Preferences userPreferencesRoot = Preferences.userRoot();
// retrieve a reference to the root of the system preferences tree
Preferences sysPreferencesRoot = Preferences.systemRoot();
These methods return references to the root node of the user preference tree and the system preference tree respectively.
There are two other static methods in the Preferences
class that return a Preferences instance,
Preferences.userNodeForPackage(Object) and
Preferences.systemNodeForPackage(Object). These methods
return a Preferences instance that is associated with
the argument's package.
package com.ociweb.preferencestest;
import java.util.prefs.*;
public class PreferenceTestFrame extends JFrame {
// A reference to Preferences object
private Preferences myPreferences = null;
public PreferenceTestFrame() {
super("Preferences Test Window");
// Obtain a references to a Preferences object
myPreferences = Preferences.userNodeForPackage(this);
}
}
This PreferenceTestFrame class is defined to be in the
com.ociweb.preferencestest package. The call to
Preferences.userNodeForPackage(this) returns a
Preferences object that is associated with the
com/ociweb/preferencestest/ node in the user
preferences tree. If that node does not exist it will be created.
Two instance methods in the Preferences class that
return references to other Preferences objects are
parent() and node(String). The
parent() method returns a Preferences
object that is associated with the parent node of the node on which
the method was called. This method returns null if
called on one of the root nodes. The node(String)
method returns a Preferences object associated with
some node relative to the node the method was called on. For
example, if the Preferences object P1 is
associated with the apps/JAsteroids/ node, then a call
to P1.node("ScreenPreferences/") returns a
Preferences object associated with the
apps/JAsteroids/ScreenPreferences/ node. If the
requested node does not exist, it will be created.
Each piece of preference data stored using the Preferences API is
stored in a particular node in one of the two preference trees and
has a String key associated with it which must be
unique within that particular node of the tree. There are 7 "get"
methods and 7 "put" methods in the Preferences class,
1 method for each of the valid preference types.
int getInt(String key, int default)
float getFloat(String key, float default)
long getLong(String key, long default)
double getDouble(String key, double default)
boolean getBoolean(String key, boolean default)
byte[] getByteArray(String key, byte[] default)
String get(String key, String default)
void putInt(String key, int value)
void putFloat(String key, float value)
void putLong(String key, long value)
void putDouble(String key, double value)
void putBoolean(String key, boolean value)
void putByteArray(String key, byte[] value)
void put(String key, String value)
Each of the "get" methods accepts a default value. This value will
be returned if the specified key does not exist at that
particular node. They keys() method returns an array of
String objects that includes all of the keys stored at
that particular node.
Note the absence of support for storing arbitrary objects and
collections of objects. The API is designed to support simple data,
not complex objects. A simple collection of attributes could be
stored as a tokenized String. For example, if an
application needs to store the location and size of a window the
application could store 4 separate pieces of data (x-location,
y-location, width and height) or the application could store a
tokenized String that contains all 4 pieces of data and
that String would need to be parsed after being
retrieved.
package com.ociweb.preferencestest;
import java.awt.event.*;
import java.util.prefs.*;
import javax.swing.*;
/**
* A frame to test some basic functionality in the Preferences API
*
* @author Object Computing Inc.
*/
public class PreferenceTestFrame extends JFrame {
// A reference to a Preferences object
private Preferences myPreferences = null;
// Default values for this frame's preferences
public static final int DEFAULT_WINDOW_X = 50;
public static final int DEFAULT_WINDOW_Y = 50;
public static final int DEFAULT_WINDOW_WIDTH = 300;
public static final int DEFAULT_WINDOW_HEIGHT = 100;
// Keys for this frame's preferences
public static final String WINDOW_X_KEY = "TEST_WINDOW_X";
public static final String WINDOW_Y_KEY = "TEST_WINDOW_Y";
public static final String WINDOW_WIDTH_KEY = "TEST_WINDOW_WIDTH";
public static final String WINDOW_HEIGHT_KEY = "TEST_WINDOW_HEIGHT";
public PreferenceTestFrame() {
super("Preferences Test Window");
// Obtain a references to a Preferences object
myPreferences = Preferences.userNodeForPackage(this);
// Retrieve the location of the frame from the Preferences object
int windowX = myPreferences.getInt(WINDOW_X_KEY, DEFAULT_WINDOW_X);
int windowY = myPreferences.getInt(WINDOW_Y_KEY, DEFAULT_WINDOW_Y);
// Retrieve the size of the frame from the Preferences object
int windowWidth =
myPreferences.getInt(WINDOW_WIDTH_KEY, DEFAULT_WINDOW_WIDTH);
int windowHeight =
myPreferences.getInt(WINDOW_HEIGHT_KEY, DEFAULT_WINDOW_HEIGHT);
// Set the size and location of the frame
setBounds(windowX, windowY, windowWidth, windowHeight);
addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent we) {
exitApp();
}
});
}
private void exitApp() {
// Save the state of the window as preferences
myPreferences.putInt(WINDOW_WIDTH_KEY, getWidth());
myPreferences.putInt(WINDOW_HEIGHT_KEY, getHeight());
myPreferences.putInt(WINDOW_X_KEY, getX());
myPreferences.putInt(WINDOW_Y_KEY, getY());
System.exit(0);
}
public static void main(String[] args) {
new PreferenceTestFrame().show();
}
}
This application exercises the Preferences API by storing its window
size and location as preferences and then retrieving them each time
the application is executed so the window is restored to its
previous location. The Preferences object is retrieved
by calling Preferences.userNodeForPackage(this) so the
data will be stored at the com/ociweb/preferencestest/
node in the user preferences tree.
This implementation stores each of the window attributes as a
separate preference. Alternatively, the implementation could have
combined all of these pieces of information into a
String formed something like
<x-location>:<y-location>:<width>:<height>.
The details about where the data is actually stored may vary from
platform to platform. Each of the static methods in the
Preferences class that returns a
Preferences object are relying on a factory that
creates the Preferences object. This factory is some
concrete implementation of the
java.util.prefs.PreferencesFactory interface. The name
of the concrete factory class to use is specified by the
java.util.prefs.PreferencesFactory system property. The
documentation explicitly states that this is NOT
part of the specification and is subject to change in future
releases. On Linux, the value of this system property is
java.util.prefs.FileSystemPreferencesFactory so the
Preferences API will use an instance of that class whenever an
instance of Preferences is requested via one of the
static methods in the Preferences class. This
particular implementation stores all of the preference data in files
on the file system. On Windows 2000, the value of this system
property is java.util.prefs.WindowsPreferencesFactory.
This implementation stores all of the preference data in the Windows
Registry. Specifically, system preferences are stored under
HKEY_LOCAL_MACHINE\SOFTWARE\JavaSoft\Prefs and user
preferences are stored under
HKEY_CURRENT_USER\Software\JavaSoft\Prefs.
This screen shot shows the data stored by the
PreferenceTestFrame application shown above. Of course
the application code makes no references to the Windows Registry.
The application is written to the standard Preferences API.
There are facilities built into the Preferences API to allow preference data to be exported and imported to and from an eXtensible Markup Language (XML) file.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE preferences SYSTEM 'http://java.sun.com/dtd/preferences.dtd'>
<preferences EXTERNAL_XML_VERSION="1.0">
<root type="user">
<map />
<node name="com">
<map />
<node name="ociweb">
<map />
<node name="preferencestest">
<map>
<entry key="TEST_WINDOW_WIDTH" value="256" />
<entry key="TEST_WINDOW_HEIGHT" value="314" />
<entry key="TEST_WINDOW_X" value="540" />
<entry key="TEST_WINDOW_Y" value="63" />
</map>
</node>
</node>
</node>
</root>
</preferences>
This XML was created by calling the
exportSubtree(OutputStream) method on an instance of
Preferences that was associated with the root of the
user preference tree. These are the same preferences reflected in
the Windows Registry screenshot shown above. The
exportSubTree(OutputStream) method exports any
particular node and all of its sub-nodes in the tree. The
exportNode(OutputStream) method exports a particular
node and does not include any of its sub-nodes. Each of those
methods are instance methods as they operate on a particular node in
the tree.
The static method importPreferences(InputStream) in the
Preferences class imports preferences from an XML
source and adds those preferences to the appropriate preference
tree. If any of the nodes represented in the XML do not exist in the
preference tree, they are created.
Without the Preferences API, many applications have taken advantage
of Java Property files for storing simple data like that accounted
for by the Preferences API. In comparison to using Property files,
the Preferences API provides a more robust mechanism for organizing
the data without introducing complexity. For example, Property files
do not provide any easy way to group data hierarchically. This
flexibility is built into the Preferences model. Property files are
dependent on a file system, which may not exist on every platform
with a Java VM, for example a cellular telephone. There is nothing
about the Preferences API that depends on a file system. For
platforms without a file system, an implementation of
java.util.prefs.PreferencesFactory could be developed
that stores the data using whatever mechanisms are appropriate for
that platform.
The Preferences API provides a nice interface for storing application preferences in a platform independent way. While the details about the storage of the data vary from platform to platform, the application code is as portable as any other Java code.
This article has been written as of J2SE v1.4 beta 2. Keep an eye out for changes that may be made to the API before the final release of v1.4.