Java ScreenShot
     

Screenshot Core Java 2: Volume I - Fundamentals

Table of Contents
 10.  Deploying Applets and apps


Storing app Preferences

Most programs can be configured by their users. The programs must then save the user preferences and restore them when the app starts again. You already saw how a Java Web Start app can use a persistent store for that purpose. However, SDK 1.4 introduced a different and more powerful mechanism for local apps. We will describe that mechanism, but first we will cover the much simpler approach for storing configuration information that Java apps have traditionally taken.

Property Sets

A property set is a data structure that stores key/value pairs. Property sets are often used for storing configuration information. Property sets have three particular characteristics:

  • The keys and values are strings.
  • The set can easily be saved to a file and loaded from a file.
  • There is a secondary table for defaults.

The Java platform class that implements a property set is called Properties. Property sets are useful in specifying configuration options for programs. For example,

Properties settings = new Properties();
settings.put("font", "Courier");
settings.put("size", "10");
settings.put("message", "Hello, World");


Use the store method to save this list of properties to a file. Here, we just print the property set to the standard output. The second argument is a comment that is included in the file.

FileOutputStream out
 = new FileOutputStream("Myprog.properties");
settings.store(out, "Myprog Properties");


The sample set gives the following output.

#Myprog Properties
#Sun Jul 14 07:22:52 2002
font=Courier size=10
message=Hello, World


To load the properties from a file, use

FileInputStream in
 = new FileInputStream("Myprog.properties");
settings.load(in);


We'll put this technique to work so that your users can customize the NotHelloWorld program to their hearts' content. We'll allow them to specify the following in the configuration file CustomWorld.properties:

  • Window size;
  • Font;
  • Point size;
  • Background color;
  • Message string.

If the user doesn't specify some of the settings, we will provide defaults. The Properties class has two mechanisms for providing defaults. First, whenever you look up the value of a string, you can specify a default that should be used automatically when the key is not present.

String font = settings.getProperty("font", "Courier");


If there is a "font" property in the property table, then font is set to that string. Otherwise, font is set to "Courier". If you find it too tedious to specify the default in every call to getProperty, then you can pack all the defaults into a secondary property set and supply that in the constructor of your lookup table.

Properties defaultSettings = new Properties();
defaultSettings.put("font", "Courier");
defaultSettings.put("size", "10");
defaultSettings.put("color.red", "0");
. . .
Properties settings = new Properties(defaultSettings);
FileInputStream in
 = new FileInputStream("CustomWorld.properties");
settings.load(in);
. . .


Yes, you can even specify defaults to defaults if you give another property set parameter to the defaultSettings constructor, but it is not something one would normally do. Screenshot-17 is the customizable "Hello, Custom World" program. Just edit the .properties file to change the program's appearance to the way you want (see Screenshot-17).

Screenshot-17. The customized Hello World program

Java graphics 10fig17.gif


Java graphics notes_icon.gif

Properties are simple table without a hierarchical structure. It is common to introduce a fake hierarchy with key names such as color.red, color.green, and so on. But there are no methods in the Properties class that help organize such a hierarchy. If you store complex configuration information, you should use the Preferences class instead—see the next section.

Java graphics notes_icon.gif

The Properties class extends the Hashtable class. That means that all methods of Hashtable are available to Properties objects. Some methods are useful. For example, size returns the number of possible properties (well, it isn't that nice—it doesn't count the defaults). Similarly, keys returns an enumeration of all keys, except for the defaults. There is also a second function, called propertyNames, that returns all keys. The put function is downright dangerous. It doesn't check that you put strings into the table. Does the is-a rule for using inheritance apply here? Is every property set a hash table? Not really. That these are true is really just an implementation detail. Maybe it is better to think of a property set as having a hash table. But then the hash table should be a private instance variable. Actually, in this case, a property set uses two hash tables: one for the defaults and one for the nondefault values. We think a better design would be the following:

class Properties
{
 public String getProperty(String) { . . . }
 public void put(String, String) { . . . }
 . . .
 private Hashtable nonDefaults;
 private Hashtable defaults;
}


We don't want to tell you to avoid the Properties class in the Java library. Provided you are careful to put nothing but strings in it, it works just fine. But think twice before using "quick and dirty" inheritance in your own programs.

Example 10-14 CustomWorld.java
 1. import java.awt.*;
 2. import java.awt.event.*;
 3. import java.util.*;
 4. import java.io.*;
 5. import javax.swing.*;
 6.
 7. /**
 8. This program demonstrates how to customize a "Hello, World"
 9. program with a properties file.
10. */
11. public class CustomWorld
12. {
13. public static void main(String[] args)
14. {
15. CustomWorldFrame frame = new CustomWorldFrame();
16. frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
17. frame.show();
18. }
19. }
20.
21. /**
22. This frame displays a message. The frame size, message text,
23. font, and color are set in a properties file.
24. */
25. class CustomWorldFrame extends JFrame
26. {
27. public CustomWorldFrame()
28. {
29. Properties defaultSettings = new Properties();
30. defaultSettings.put("font", "Monospaced");
31. defaultSettings.put("width", "300");
32. defaultSettings.put("height", "200");
33. defaultSettings.put("message", "Hello, World");
34. defaultSettings.put("color.red", "0 50 50");
35. defaultSettings.put("color.green", "50");
36. defaultSettings.put("color.blue", "50");
37. defaultSettings.put("ptsize", "12");
38.
39. Properties settings = new Properties(defaultSettings);
40. try
41. {
42. FileInputStream in
43. = new FileInputStream("CustomWorld.properties");
44. settings.load(in);
45. }
46. catch (IOException exception)
47. {
48. exception.printStackTrace();
49. }
50.
51. int red = Integer.parseInt(
52. settings.getProperty("color.red"));
53. int green = Integer.parseInt(
54. settings.getProperty("color.green"));
55. int blue = Integer.parseInt(
56. settings.getProperty("color.blue"));
57.
58. Color foreground = new Color(red, green, blue);
59.
60. String name = settings.getProperty("font");
61. int ptsize = Integer.parseInt(
62. settings.getProperty("ptsize"));
63. Font f = new Font(name, Font.BOLD, ptsize);
64.
65. int hsize = Integer.parseInt(
66. settings.getProperty("width"));
67. int vsize = Integer.parseInt(
68. settings.getProperty("height"));
69. setSize(hsize, vsize);
70. setTitle(settings.getProperty("message"));
71.
72. JLabel label
73. = new JLabel(settings.getProperty("message"),
74. SwingConstants.CENTER);
75. label.setFont(f);
76. label.setForeground(foreground);
77. getContentPane().add(label);
78. }
79. }


java.util.Properties 1.0

Java graphics api_icon.gif
  • Properties()

    creates an empty property list.

  • Properties(Properties defaults)

    creates an empty property list with a set of defaults.

    Parameters:

    defaults

    the defaults to use for lookups

  • String getProperty(String key)

    gets a property association; returns the string associated with the key, or the string associated with the key in the default table if it wasn't present in the table, or null if the key wasn't present in the default table either.

    Parameters:

    key

    the key whose associated string to get

  • String getProperty(String key, String defaultValue)

    gets a property with a default value if the key is not found; returns the string associated with the key, or the default string if it wasn't present in the table.

    Parameters:

    key

    the key whose associated string to get

     

    defaultValue

    the string to return if the key is not present

  • void load(InputStream in) throws IOException

    loads a property set from an input stream.

    Parameters:

    in

    the input stream

  • void store(OutputStream out, String header) 1.2

    saves a property set to an output stream.

    Parameters:

    out

    the output stream

     

    header

    the header in the first line of the stored file

System information

Here's another example of the ubiquity of the Properties set. Information about your system is stored in a Properties object that is returned by the static getProperties method of the System class:

Properties sysprops = System.getProperties();


To access a single property, call

String value = System.getProperty(key);


apps that run without a security manager have complete access to this information, but applets and other untrusted programs can only access the following keys:

java.version java.vendor java.vendor.url java.class.version os.name os.version os.arch file.separator path.separator line.separator java.specification.version java.vm.specification.version java.vm.specification.vendor java.vm.specification.name java.vm.version java.vm.vendor java.vm.name


If an applet attempts to read another key (or all properties), then a security exception is thrown.

Java graphics notes_icon.gif

You can find the names of the freely accessible system properties in the file security/java.policy in the directory of the Java runtime.

The program in Example 10-15 prints out the key/value pairs in the Properties object that stores the system properties. Here is an example of what you would see when you run the program. You can see all the values stored in this Properties object. (What you would get will, of course, reflect your machine's settings.)
[View full width]
java.runtime.name=Java(TM) 2 Runtime Environment, Standard version sun.boot.library.path=/usr/local/j2sdk1.4.0/jre/lib/i386
java.vm.version=1.4.0
java.vm.vendor=Oracle Inc.
java.vendor.url=http://java.oracle.com/
path.separator=:
java.vm.name=Java HotSpot(TM) Client VM file.encoding.pkg=sun.io sun.os.patch.level=unknown java.vm.specification.name=Java Virtual Machine Specification user.dir=/home/cay java.runtime.version=1.4.0
java.awt.graphicsenv=sun.awt.X11GraphicsEnvironment os.arch=i386
java.io.tmpdir=/tmp line.separator=
java.vm.specification.vendor=Oracle Inc.
java.awt.fonts=
os.name=Linux java.library.path=/usr/local/j2sdk1.4.0/jre/lib/i386:/usr/local/j2sdk1.4.0/jre/lib/i386/
Java graphics ccc.gifnative_threads/:/usr/local/j2sdk1.4.0/jre/lib/i386/client:/usr/local/j2sdk1.4.0/jre/../
Java graphics ccc.giflib/i386
java.specification.name=Java Platform API Specification java.class.version=48.0
java.util.prefs.PreferencesFactory=java.util.prefs.FileSystemPreferencesFactory os.version=2.4.3-20mdk user.home=/home/cay user.timezone=
java.awt.printerjob=sun.print.PSPrinterJob file.encoding=ISO-8859-1
java.specification.version=1.4
user.name=cay java.class.path=.
java.vm.specification.version=1.0
sun.arch.data.model=32
java.home=/usr/local/j2sdk1.4.0/jre java.specification.vendor=Oracle Inc.
user.language=en java.vm.info=mixed mode java.version=1.4.0-beta java.ext.dirs=/usr/local/j2sdk1.4.0/jre/lib/ext sun.boot.class.path=/usr/local/j2sdk1.4.0/jre/lib/rt.jar:/usr/local/j2sdk1.4.0/jre/lib/
Java graphics ccc.gifi18n.jar:/usr/local/j2sdk1.4.0/jre/lib/sunrsasign.jar:/usr/local/j2sdk1.4.0/jre/lib/jsse.
Java graphics ccc.gifjar:/usr/local/j2sdk1.4.0/jre/lib/jce.jar:/usr/local/j2sdk1.4.0/jre/lib/charsets.jar:/usr/
Java graphics ccc.giflocal/j2sdk1.4.0/jre/classes java.vendor=Oracle Inc.
file.separator=/
java.vendor.url.bug=http://java.oracle.com/cgi-bin/bugreport.cgi sun.cpu.endian=little sun.io.unicode.encoding=UnicodeLittle user.region=US sun.cpu.isalist=


Example 10-15 SystemInfo.java
 1. import java.applet.*;
 2. import java.io.*;
 3. import java.util.*;
 4.
 5. /**
 6. This program prints out all system properties.
 7. */
 8. public class SystemInfo
 9. {
10. public static void main(String args[])
11. {
12. try
13. {
14. Properties sysprops = System.getProperties();
15. sysprops.store(System.out, "System Properties");
16. }
17. catch (IOException exception)
18. {
19. exception.printStackTrace();
20. }
21. }
22. }


java.lang.System 1.0

Java graphics api_icon.gif
  • Properties getProperties()

    retrieves all system properties. The app must have permission to retrieve all properties, or a security exception is thrown.

  • String getProperty(String key)

    retrieves the system property with the given key name. The app must have permission to retrieve the property, or a security exception is thrown.

The Preferences API

As you have seen, the Properties class makes it simple to load and save configuration information. However, using property files has a number of disadvantages.

  • The configuration files cannot always be stored in the same location as the program since that location may not be writable. For example, it may be a read-only directory, or a JAR file.
  • Multiple users may want to configure the same app in different ways.
  • Configuration files cannot always be stored in the user's home directory. Some operating systems (such as Windows 9x) have no concept of a home directory.
  • There is no standard convention for naming configuration files, increasing the likelihood of name clashes as users install multiple Java apps.

Some operating systems have a central repository for configuration information. The best-known example is the registry in Microsoft Windows. The Preferences class of SDK 1.4 provides such a central repository in a platform-independent manner. In Windows, the Preferences class uses the registry for storage; on Linux, the information is stored in the local file system instead. Of course, the repository implementation is transparent to the programmer using the Preferences class. The Preferences repository has a tree structure, with node path names such as /com/mycompany/myapp. As with package names, name clashes are avoided as long as programmers start the paths with reversed domain names. In fact, the designers of the API suggest that the configuration node paths match the package names in your program. Each node in the repository has a separate table of key/value pairs that you can use to store numbers, strings, or byte arrays. There is no provision for storing serializable objects. The API designers felt that the serialization format is too fragile for long-term storage. Of course, if you disagree, you can save serialized objects in byte arrays. For additional flexibility, there are multiple parallel trees. Each program user has one tree, and there is an additional tree, called the system tree, for settings that are common to all users. The Preferences class uses the operating system notion of the "current user" for accessing the appropriate user tree. To access a node in the tree, start with the user or system root:

Preferences root = Preferences.userRoot();


or

Preferences root = Preferences.systemRoot();


Then access the node. You can simply provide a node path name:

Preferences node = root.node("/com/mycompany/myapp");


There is a convenient shortcut to get a node whose path name equals the package name of a class. Simply take an object of that class and call

Preferences node = Preferences.userNodeForPackage(obj);


or

Preferences node = Preferences.systemNodeForPackage(obj);


Typically, obj will be the this reference. Once you have a node, you can access the key/value table with methods

String get(String key, String defval)
int getInt(String key, int defval)
long getLong(String key, long defval)
float getFloat(String key, float defval)
double getDouble(String key, double defval)
boolean getBoolean(String key, boolean defval)
byte[] getByteArray(String key, byte[] defval)


Note that you must specify a default value when reading the information, in case the repository data is not available. Defaults are required for several reasons. The data might be missing because the user never specified a preference. Certain resource-constrained platforms might not have a repository, and mobile devices might be temporarily disconnected from the repository. Conversely, you can write data to the repository with put methods such as

put(String key, String value)
putInt(String key, int value)


and so on. You can enumerate all keys stored in a node with the method

String[] keys


But there is currently no way to find out the type of the value of a particular key. Central repositories such as the Windows registry traditionally suffer from two problems.

  • They turn into a "dumping ground," filled with obsolete information.
  • Configuration data gets entangled into the repository, making it difficult to move preferences to a new platform.

The Preferences class has a solution for the second problem. You can export the preferences of a subtree (or, less commonly, a single node) by calling the methods

void exportSubtree(OutputStream out)
void exportNode(OutputStream out)


The data is saved in XML format. It can be imported into another repository by calling

void importPreferences(InputStream in)


Here is a sample file:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE preferences
 SYSTEM 'http://java.oracle.com/dtd/preferences.dtd'>
<preferences EXTERNAL_XML_VERSION="1.0">
 <root type="user">
 <map />
 <node >
 <map />
 <node >
 <map />
 <node >
 <map>
 <entry key="left" value="11" />
 <entry key="top" value="9" />
 <entry key="width" value="453" />
 <entry key="height" value="365" />
 </map>
 </node>
 </node>
 </node>
 </root>
</preferences>


If your program uses preferences, you should give your users the opportunity of exporting and importing them, so they can easily migrate their settings from one computer to another. The program in Example 10-16 demonstrates this technique. The program simply saves the position and size of the main window. Try resizing the window, then exit and restart the app. The window will be just like you left it when you exited.

Example 10-16 PreferencesTest.java
 1. import java.awt.*;
 2. import java.awt.event.*;
 3. import java.io.*;
 4. import java.util.logging.*;
 5. import java.util.prefs.*;
 6. import javax.swing.*;
 7. import javax.swing.event.*;
 8.
 9. /**
 10. A program to test preference settings. The program
 11. remembers the frame position and size.
 12. */
 13. public class PreferencesTest
 14. {
 15. public static void main(String[] args)
 16. {
 17. PreferencesFrame frame = new PreferencesFrame();
 18. frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
 19. frame.show();
 20. }
 21. }
 22.
 23. /**
 24. A frame that restores position and size from user
 25. preferences and updates the preferences upon exit.
 26. */
 27. class PreferencesFrame extends JFrame
 28. {
 29. public PreferencesFrame()
 30. {
 31. setTitle("PreferencesTest");
 32.
 33. // get position, size from preferences
 34.
 35. Preferences root = Preferences.userRoot();
 36. final Preferences node
 37. = root.node("/com/horstmann/corejava");
 38. int left = node.getInt("left", 0);
 39. int top = node.getInt("top", 0);
 40. int width = node.getInt("width", DEFAULT_WIDTH);
 41. int height = node.getInt("height", DEFAULT_HEIGHT);
 42. setBounds(left, top, width, height);
 43.
 44. // set up file chooser that shows XML files
 45.
 46. final JFileChooser chooser = new JFileChooser();
 47. chooser.setCurrentDirectory(new File("."));
 48.
 49. // accept all files ending with .xml
 50. chooser.setFileFilter(new
 51. javax.swing.filechooser.FileFilter()
 52. {
 53. public boolean accept(File f)
 54. {
 55. return f.getName().toLowerCase()
 56. .endsWith(".xml")
 57. || f.isDirectory();
 58. }
 59. public String getDescription()
 60. {
 61. return "XML files";
 62. }
 63. });
 64.
 65.
 66. // set up menus
 67. JMenuBar menuBar = new JMenuBar();
 68. setJMenuBar(menuBar);
 69. JMenu menu = new JMenu("File");
 70. menuBar.add(menu);
 71.
 72. JMenuItem exportItem = new JMenuItem("Export preferences");
 73. menu.add(exportItem);
 74. exportItem.addActionListener(new
 75. ActionListener()
 76. {
 77. public void actionPerformed(ActionEvent event)
 78. {
 79. if(chooser.showSaveDialog(PreferencesFrame.this)
 80. == JFileChooser.APPROVE_OPTION)
 81. {
 82. try
 83. {
 84. OutputStream out = new FileOutputStream(
 85. chooser.getSelectedFile());
 86. node.exportSubtree(out);
 87. out.close();
 88. }
 89. catch (Exception exception)
 90. {
 91. Logger.getLogger("global").log(Level.INFO,
 92. "Export preferences", exception);
 93. }
 94. }
 95. }
 96. });
 97.
 98. JMenuItem importItem = new JMenuItem("Import preferences");
 99. menu.add(importItem);
100. importItem.addActionListener(new
101. ActionListener()
102. {
103. public void actionPerformed(ActionEvent event)
104. {
105. if(chooser.showOpenDialog(PreferencesFrame.this)
106. == JFileChooser.APPROVE_OPTION)
107. {
108. try
109. {
110. InputStream in = new FileInputStream(
111. chooser.getSelectedFile());
112. node.importPreferences(in);
113. in.close();
114. }
115. catch (Exception exception)
116. {
117. Logger.getLogger("global").log(Level.INFO,
118. "Import preferences", exception);
119. }
120. }
121. }
122. });
123.
124. JMenuItem exitItem = new JMenuItem("Exit");
125. menu.add(exitItem);
126. exitItem.addActionListener(new
127. ActionListener()
128. {
129. public void actionPerformed(ActionEvent event)
130. {
131. node.putInt("left", getX());
132. node.putInt("top", getY());
133. node.putInt("width", getWidth());
134. node.putInt("height", getHeight());
135. System.exit(0);
136. }
137. });
138. }
139. public static final int DEFAULT_WIDTH = 300;
140. public static final int DEFAULT_HEIGHT = 200;
141. }


java.util.prefs.Preferences 1.4

Java graphics api_icon.gif
  • Preferences userRoot()

    returns the root preferences node of the user of the calling program.

  • Preferences systemRoot()

    returns the system-wide root preferences node.

  • Preferences node(String path)

    returns a node that can be reached from the current node by the given path. If path is absolute (that is, starts with a /), then the node is located starting from the root of the tree containing this preference node. If there isn't a node with the given path, it is created.

  • Preferences userNodeForPackage(Class cl)
  • Preferences systemNodeForPackage(Class cl)

    return a node in the current user's tree or the system tree whose absolute node path corresponds to the package name of the class cl.

  • String[] keys()

    returns all keys belonging to this node.

  • String get(String key, String defval)
  • int getInt(String key, int defval)
  • long getLong(String key, long defval)
  • float getFloat(String key, float defval)
  • double getDouble(String key, double defval)
  • boolean getBoolean(String key, boolean defval)
  • byte[] getByteArray(String key, byte[] defval)

    These methods return the value associated with the given key, or the supplied default value if there is no value associated with the key, or the associated value is not of the correct type, or the preferences store is unavailable.

  • void put(String key, String value)
  • void putInt(String key, int value)
  • void putLong(String key, long value)
  • void putFloat(String key, float value)
  • void putDouble(String key, double value)
  • void putBoolean(String key, boolean value)
  • void putByteArray(String key, byte[] value)

    These methods store a key/value pair with this node.

  • void exportSubtree(OutputStream out)

    writes the preferences of this node and its children to the specified stream.

  • void exportSubtree(OutputStream out)

    writes the preferences of this node (but not its children) to the specified stream.

  • void importPreferences(InputStream in)

    imports the preferences contained in the specified stream.

This concludes our discussion of Java software deployment. In the next chapter, you will learn how to use exceptions to tell your programs what to do when problems arise at runtime. We'll also give you tips and techniques for testing and debugging, so that hopefully not too many things will go wrong when your programs run.Screenshot

Java ScreenShot
     
Top
 

Comments