Return-Path: Received: from po10.mit.edu (po10.mit.edu [18.7.21.66]) (authenticated user=env_37792749-1992895016@hermes.sun.com bits=0) by po10.mit.edu (Cyrus v2.1.5) with LMTP; Tue, 15 Jul 2003 15:06:49 -0400 X-Sieve: CMU Sieve 2.2 Received: from po10.mit.edu by po10.mit.edu (8.12.4/4.7) id h6FJ6nNP005815; Tue, 15 Jul 2003 15:06:49 -0400 (EDT) Received: (from mdefang@localhost) by po10.mit.edu (8.12.4/8.12.4/Submit) id h6FJ6m0U005809 for ; Tue, 15 Jul 2003 15:06:48 -0400 (EDT) Received: from hermes.sun.com (hermes.sun.com [64.124.140.169]) by pacific-carrier-annex.mit.edu (8.12.4/8.9.2) with SMTP id h6FJ6j6G019750 for ; Tue, 15 Jul 2003 15:06:45 -0400 (EDT) Date: 15 Jul 2003 10:20:17 -0800 From: "SDN - Core Java Technologies Tech Tips" To: alexp@mit.edu Message-Id: <37792749-1992895016@hermes.sun.com> Subject: Core Java Technologies Tech Tips, July 15, 2003 (Preferences API, Interfaces and Constants) Mime-Version: 1.0 Content-Type: text/html; charset=ISO-8859-1 Content-Transfer-Encoding: 7bit X-Mailer: SunMail 1.0 X-Spam-Score: 2.6 X-Spam-Level: ** (2.6) X-Spam-Flag: YES X-Scanned-By: MIMEDefang 2.28 (www . roaringpenguin . com / mimedefang) Core Java Technologies Technical Tips
.
.
Core Java Technologies Technical Tips
.
   View this issue as simple text July 15, 2003    

In this Issue

Welcome to the Core Java Technologies Tech Tips, July 15, 2003. Here you'll get tips on using core Java technologies and APIs, such as those in Java 2 Platform, Standard Edition (J2SE).

This issue covers:

.Using the Preferences API
.Interfaces and Constants

These tips were developed using Java 2 SDK, Standard Edition, v 1.4.

This issue of the Core Java Technologies Tech Tips is written by Daniel Steinberg, Director of Java Offerings for Dim Sum Thinking, Inc, and editor-in-chief for java.net.

See the Subscribe/Unsubscribe note at the end of this newsletter to subscribe to Tech Tips that focus on technologies and products in other Java platforms.

.
.

USING THE PREFERENCES API

There are many times when you need to persist configuration information for an application. In the past, you might have used java.util.Properties to save details about the preferred date format for an application, the preferred window size or location, or the previous time that a user logged into an application. The Properties class required you to write to and read from streams. This, in turn, meant that you had to manage where the properties files were stored in your file system. In other words, much of your effort was spent managing details that had little to do with the information being managed.

The Preferences API introduced in J2SE 1.4 provides a convenient framework for storing and recalling data about user-specific preferences or system-wide preferences. In this tip, you'll learn how to use the Preferences API to store and recall user-specific preferences.

First, here's a little background about how preferences data is organized. Data about user and system preferences is organized into hierarchical collections called nodes. Multiple nodes exist in a tree. There are two types of trees: one for user preferences and one for system preferences. There is a separate preference tree for each user. But there is only one system preference tree for the entire system. Note that even though data about user-specific preferences and system-wide preferences are in different trees, the techniques for storing and recalling system preferences are the same as those shown in this tip.

This first step in using the Preferences API is to get a handle to a Preferences object for this class. To do this, pass the factory method useNodeForPackage() an object of type Class.

   import java.util.prefs.Preferences;
   import java.awt.Dimension;

   public class UserPreferences {
      private Preferences userPrefs;

      public UserPreferences(){
        userPrefs = Preferences.userNodeForPackage(
                                UserPreferences.class);
   }
}

This gives you an instance of a Preferences object that you can use to read from and write to the stored data. For this tip, let's use the Preferences instance to store and retrieve the dimensions of a JFrame. This is shown in the following code:

   import java.util.prefs.Preferences;
   import java.awt.Dimension;
   import javax.swing.JFrame;

   public class UserPreferences extends JFrame{
      private Preferences userPrefs;

      public UserPreferences(){
        userPrefs = Preferences.userNodeForPackage(
                               UserPreferences.class);
        setToPreferredSize();
      }
 
      public void setToPreferredSize(){
        int width = userPrefs.getInt("width", 100);
        int height = userPrefs.getInt("height", 200);
        setSize(width, height);
      }

      public Dimension getDimensions(){
        int width = userPrefs.getInt("width", 100);
        int height = userPrefs.getInt("height", 200);
        return new Dimension(width, height);
      }

      public void putDimensions( Dimension dimension){
        userPrefs.putInt(
                   "width", (int)dimension.getWidth());
        userPrefs.putInt(
                 "height", (int)dimension.getHeight());
      }

   }

In this class, the setToPreferredSize() method is used to retrieve the height and width, and set the JFrame's size to these dimensions. The putDimensions() method is used to persist int values for the height and width. These methods, in turn, call the getInt() and putInt() methods of the Preferences class.

The getInt() method in the Preferences class takes two arguments. The first is a String that acts as the key for the property being stored. The second argument is an int that is used as the default value. If the property has not yet been set, then the default value is used. Similarly the putInt() method takes two arguments. The first is the key, and the second is the value of the property being set.

Now let's exercise the code. The following program creates a JFrame, setting the size based on data read from the Preferences object. The dimensions in the Preferences object are initially width 100 and height 200. In the program, they're reset to width 200 and height 300. A print statement and a call to setVisible() have been added so you can see the effects.

   import java.util.prefs.Preferences;
   import java.awt.Dimension;
   import javax.swing.JFrame;

   public class UserPreferences extends JFrame{
      private Preferences userPrefs;

      public UserPreferences(){
        userPrefs = Preferences.userNodeForPackage(
                               UserPreferences.class);
        setToPreferredSize();
        setVisible(true);
      }
 
      public void setToPreferredSize(){
        int width = userPrefs.getInt("width", 100);
        int height = userPrefs.getInt("height", 200);
        setSize(width, height);
        System.out.println("Width = "+ getWidth() +
                         " Height = " + getHeight());
      }

      public Dimension getDimensions(){
        int width = userPrefs.getInt("width", 100);
        int height = userPrefs.getInt("height", 200);
        return new Dimension(width, height);
      }

      public void putDimensions( Dimension dimension){
        userPrefs.putInt(
                   "width", (int)dimension.getWidth());
        userPrefs.putInt(
                 "height", (int)dimension.getHeight());
      }

      public static void main(String[] args){
        new UserPreferences().putDimensions(
                               new Dimension(200,300));
      }
   }

Run this program, and you should get the following results:

   Width = 100 Height = 200

Run the program a second time, and you should get the following results:

   Width = 200 Height = 300

The width and height have been successfully saved, and so the values are saved between runs of the program.

If you want to add the ability to clear the values from memory, add the following method to UserPreferences.java.

   public void clearPreferences(){
      try {
        userPrefs.clear();
      } catch (BackingStoreException e) {
        e.printStackTrace();
      }
  }

Also add the import statement for BackingStoreException. A call to the clearPreferences() method will result in the use of the default values when you call the setToPreferredSize() method.

You can also implement a listener, specifically PreferenceChangeListener, for changes in preferences. For example, let's set up the JFrame to implement PreferenceChangeListener, and so respond to changes in the preferences. In addition to extending JFrame, UserPreferences can also implement PreferenceChangeListener. The only requirement is that you implement PreferenceChangeListener's one method: preferenceChange(). When a change to the preferred dimensions is discovered, the appropriate action is to change the dimensions of the JFrame by calling setToPreferredSize().

  public void preferenceChange(PreferenceChangeEvent e){
      setToPreferredSize();
  }

Other changes you can make to the code include adding methods to select and set the random ints that are stored as preferences, and adding a call to the resetManyTimes() method in main(). The revised code looks like this.

   import javax.swing.JFrame;
   import java.util.prefs.Preferences;
   import java.util.prefs.PreferenceChangeListener;
   import java.util.prefs.PreferenceChangeEvent;

   public class UserPreferences extends JFrame 
                implements PreferenceChangeListener{
     private Preferences userPrefs;

     public UserPreferences(){
       userPrefs = Preferences.userNodeForPackage(
                              UserPreferences.class);
       userPrefs.addPreferenceChangeListener(this);
       setToPreferredSize();
       setVisible(true);
     }

     public void setToPreferredSize(){
       int width = userPrefs.getInt("width", 100);
       int height = userPrefs.getInt("height", 200);
       setSize(width, height);
       System.out.println("Width = "+ getWidth() +
           " Height = "+ getHeight());
     }

       public void resetDimensionsManyTimes(){
       for (int i = 0; i<10;i++){
         putRandomDimensions();
         try {
           Thread.sleep(1000);
         } catch (InterruptedException e) {
           e.printStackTrace();
         }
       }
     }

     private void putRandomDimensions(){
       userPrefs.putInt("width", getRandomInt());
       userPrefs.putInt("height", getRandomInt());
     }

     private int getRandomInt(){
       return (int)(Math.random()*300+100);
     }

     public void preferenceChange(PreferenceChangeEvent e){
         setToPreferredSize();
     }

     public static void main(String[] args){
       new UserPreferences().resetDimensionsManyTimes();
     }

   }

Run the application and you should see the JFrame change size ten times.

For more information on the Preferences APIs, see the its description in the J2SE 1.4.1 documentation.

.
.

INTERFACES AND CONSTANTS

When you want to use global constants in an application, it is often tempting to define the constants in an interface and then implement the interface in the class using the constants. For example, suppose you have the following interface that contains approximations for the numbers pi and e.

   public interface TranscendentalConstants {
      public static final double PI = 3.14159;
      public static final double E = 2.71828;
   }

Suppose too that you have a second collection of constants that contains approximations for the square roots of two and three.

   public interface IrrationalConstants {
      public static final double SQRT_TWO = 1.414;
      public static final double SQRT_THREE = 1.732;
   }

A common strategy for using these constants is to create a class like the following.

   public class BadUseOfConstants implements 
        TranscendentalConstants, IrrationalConstants {

      public double sinPiOverFour(){
        return SQRT_TWO / 2;
      }

      public double sinPiOverThree(){
        return SQRT_THREE / 2;
      }

      private void outputResults() {
        System.out.println("Pi is approximately " + PI);
        System.out.println(
                   "The sin of Pi/4 is approximately " + 
                   sinPiOverFour());
        System.out.println(
                   "The sin of Pi/3 is approximately " + 
                   sinPiOverThree());
      }

      public static void main(String[] args) {
        new BadUseOfConstants().outputResults();
      }
  }

Even though this code runs, it is an improper use of interfaces. That's because BadUseOfConstants is not an extension of the type TranscendentalConstants. Instead, BadUseOfConstants is a consumer of TranscendentalConstants. There are two reasons why using interfaces in this way is so attractive. First, you can easily use constants defined in two different interfaces. If the constants were used in different classes instead, multiple inheritance would be required to perform the same task. Second, by implementing the interfaces, you can refer to the constants without qualifying them as SQRT_TWO instead of as IrrationalConstants.SQRT_TWO.

You can address this second problem of having to qualify constants by caching the variables locally like this.

   public class OKUseOfConstants {

     private double PI = TranscendentalConstants.PI;
     private double SQRT_TWO = 
                          IrrationalConstants.SQRT_TWO;
     private double SQRT_THREE = 
                        IrrationalConstants.SQRT_THREE;

     public double sinPiOverFour() {
       return SQRT_TWO / 2;
     }

     public double sinPiOverThree() {
       return SQRT_THREE / 2;
     }

     public void outputResults() {
       System.out.println("Pi is approximately " + PI);
       System.out.println(
           "The sin of Pi/4 is approximately " +
           sinPiOverFour());
       System.out.println(
           "The sin of Pi/3 is approximately " +
           sinPiOverThree());
     }

     public static void main(String[] args) {
       new OKUseOfConstants().outputResults();
     }
   }

When J2SE 1.5 is released, a new mechanism for importing specific constants will be introduced that will address both of these issues. In the meantime, you might want to introduce an inner class or a different class that collects all of the interfaces containing the constants you want to use. Here's an example. In the BetterUseOfConstants program that follows, the inner class ConstantCollector is used to collect the constants declared in TranscendentalConstants and IrrationalConstants.

   public class BetterUseOfConstants {

      private ConstantCollector constants = 
                               new ConstantCollector();

      public double sinPiOverFour(){
        return constants.SQRT_TWO / 2;
      }

      public double sinPiOverThree(){
        return constants.SQRT_THREE / 2;
      }
      
      private void outputResults() {
        System.out.println(
                               "Pi is approximately " + 
                               constants.PI);
        System.out.println(
                  "The sin of Pi/4 is approximately " + 
                  sinPiOverFour());
        System.out.println(
                  "The sin of Pi/3 is approximately " + 
                  sinPiOverThree());
      }

      public static void main(String[] args) {
        new BetterUseOfConstants().outputResults();
      }
      
      private class ConstantCollector implements 
       TranscendentalConstants, IrrationalConstants{}
      }

Running this code should produce the following results:

   Pi is approximately 3.14159
   The sin of Pi/4 is approximately 0.707
   The sin of Pi/3 is approximately 0.866      

In this case ConstantCollector is a type extension of the two interfaces.

.
.
.

Reader Feedback

  Very worth reading    Worth reading    Not worth reading 

If you have other comments or ideas for future technical tips, please type them here:

 

Have a question about Java programming? Use Java Online Support.

.
.

IMPORTANT: Please read our Terms of Use, Privacy, and Licensing policies:
http://www.sun.com/share/text/termsofuse.html
http://www.sun.com/privacy/
http://developer.java.sun.com/berkeley_license.html


Comments? Send your feedback on the Core Java Technologies Tech Tips to: http://developers.sun.com/contact/feedback.jsp?category=sdn

Subscribe to other Java developer Tech Tips:

- Enterprise Java Technologies Tech Tips. Get tips on using enterprise Java technologies and APIs, such as those in the Java 2 Platform, Enterprise Edition (J2EE).
- Wireless Developer Tech Tips. Get tips on using wireless Java technologies and APIs, such as those in the Java 2 Platform, Micro Edition (J2ME).

To subscribe to these and other JDC publications:
- Go to the JDC Newsletters and Publications page, choose the newsletters you want to subscribe to and click "Update".
- To unsubscribe, go to the subscriptions page, uncheck the appropriate checkbox, and click "Update".


ARCHIVES: You'll find the Core Java Technologies Tech Tips archives at:
http://java.sun.com/jdc/TechTips/index.html


Copyright 2003 Sun Microsystems, Inc. All rights reserved.
4150 Network Circle, Santa Clara, CA 95054 USA.


This document is protected by copyright. For more information, see:
http://java.sun.com/jdc/copyright.html


Java, J2SE, J2EE, J2ME, and all Java-based marks are trademarks or registered trademarks (http://www.sun.com/suntrademarks/) of Sun Microsystems, Inc. in the United States and other countries.

Sun Microsystems, Inc.
.
.


Please unsubscribe me from this newsletter.