Return-Path: Received: from po10.mit.edu (po10.mit.edu [18.7.21.66]) by po10.mit.edu (Cyrus v2.1.5) with LMTP; Tue, 24 Jun 2003 20:30:35 -0400 X-Sieve: CMU Sieve 2.2 Received: from pacific-carrier-annex.mit.edu by po10.mit.edu (8.12.4/4.7) id h5P0UWNP007318; Tue, 24 Jun 2003 20:30:33 -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 h5P0UTlO010694 for ; Tue, 24 Jun 2003 20:30:30 -0400 (EDT) Date: 24 Jun 2003 15:50:32 -0800 From: "SDN - Core Java Technologies Tech Tips" To: alexp@mit.edu Message-Id: <36827312-746887773@hermes.sun.com> Subject: Core Java Technologies Tech Tips, June 24, 2003 (Generating Diagnostics, Internationalizing Dates/Times) Mime-Version: 1.0 Content-Type: text/html; charset=ISO-8859-1 Content-Transfer-Encoding: 7bit X-Mailer: SunMail 1.0 X-Spam-Score: 1.9 X-Spam-Level: * (1.9) X-Spam-Flag: NO 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 June 24, 2003    

In this Issue

Welcome to the Core Java Technologies Tech Tips, June 24, 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:

.Generating Diagnostics by Monitoring the System Event Queue
.Internationalizing Dates, Times, Months, and Days of the Week

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 John Zukowski, president of JZ Ventures, Inc.

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.

.
.

GENERATING DIAGNOSTICS BY MONITORING THE SYSTEM EVENT QUEUE

Have you ever wanted your applications to have a "hot key" for system diagnostics? You could then press the key (using a key sequence like CTRL-ALT-Z), to generate system diagnostic information. And you could use the key to generate diagnostics from anywhere in the application. You could create the function theoretically by registering a KeyListener with every component in your application space. However, the Java platform provides a simpler approach: you can implement the AWTEventListener interface, and attach it to the system event queue. This tip illustrates this simpler approach.

First, a word of caution is advised. Attaching a listener to the system event queue will slow down your application's operations. Every event sourced from a Component or MenuComponent is checked against your listener. Using this feature should be reserved for special cases such as automated testing, working with accessibility features, and other cases where the exact timing of operations can be affected without altering system behavior. Secondly, as with all event-handling operations, if the task you want to perform is not very quick, you should create a secondary thread to perform the operation. This is especially true when monitoring the system event queue with an AWTEventListener -- that's because as any blocking operations will seriously degrade the performance of your application.

With that cautionary note, let's examine AWTEventListener. The interface itself is just a single method:

    public void eventDispatched(AWTEvent event)

Once registered, an AWTEventListener implementation is notified when the events you are interested in happen. The "events you are interested in" are identified as part of the registration process. Then, after you perform your special operation, the system passes the event to its appropriate location.

You register an AWTEventListener by using the addAWTEventListener method of Toolkit (Toolkit is the abstract superclass of Abstract Window Toolkit implementations). The method signature looks like this:

   void addAWTEventListener(AWTEventListener listener, 
     long eventMask)

The eventMask parameter requires some explanation. Instead of responding to all events, an AWTEventListener is typically interested in only a specific subset of them, such as focus or key events. When setting an eventMask, you identify the event types of interest. This limits the amount of processing passes through the listener code. The AWTEvent class defines a series of constants, such as FOCUS_EVENT_MASK, MOUSE_EVENT_MASK, and KEY_EVENT_MASK. These are the masks that represent the types of events that can go through the event queue. You specify a mask for each type of event you want checked, and you combine them in a single variable to get the masks to pass into the registration call. So, if you want to create a listener to monitor the different focus events, you combine FOCUS_EVENT_MASK, for the component-oriented focus events, with WINDOW_FOCUS_EVENT_MASK! , for the window-oriented events. You combine the masks using the OR ( | ) operator, as shown here:

    long mask = 
      FOCUS_EVENT_MASK | WINDOW_FOCUS_EVENT_MASK;

After you combine the masks, you attach them to the Toolkit:

    AWTEventListener listener = ...;
    Toolkit toolkit = Toolkit.getDefaultToolkit();
    toolkit.addAWTEventListener(listener, mask);

At that point, your program is watching for the masked events -- in this case, component-oriented focus events and window-oriented events.

Here's an example. The following program displays two screens (one on top of the other), each with its own text field. The "monitoring" task records every key typed into either frame's text field. When either frame is closed, the recorded keystrokes are displayed to the console.

    import javax.swing.*;
    import java.awt.*;
    import java.awt.event.*;

    public class Monitor {
      static StringBuffer buffer = 
        new StringBuffer(100);
      static class TextFrame extends JFrame {
        TextFrame() {
          setDefaultCloseOperation(EXIT_ON_CLOSE);
          Container pane = getContentPane();
          JTextField tf = new JTextField();
          pane.add(tf, BorderLayout.NORTH);
          setSize(100, 50);
          show();
          WindowListener listener = 
            new WindowAdapter() {
              public void windowClosing(WindowEvent e) {
                done();
              }
          };
          addWindowListener(listener);
        } 
      }

      public static void done() {
        if (buffer.length() > 0) {
          System.out.println(buffer.toString());
          buffer.setLength(0);
        }
      }

      public static void main(String args[]) {
        AWTEventListener listener = 
          new AWTEventListener() {
            public void eventDispatched(AWTEvent event) {
              KeyEvent ke = (KeyEvent)event;
              if (ke.getID() == KeyEvent.KEY_TYPED) {
                buffer.append(ke.getKeyChar());
            }
          }
        };
        Toolkit toolkit = Toolkit.getDefaultToolkit();
        toolkit.addAWTEventListener(
          listener, AWTEvent.KEY_EVENT_MASK);
        new TextFrame();
        new TextFrame();
      }
    }

One aspect of the program is worth mentioning. Because the buffer variable is only accessed from the system event thread (within eventDispatched specifically), access to the variable does not need to be synchronized. If the variable was accessed from outside the event thread, in addition to inside, the variable would need to be synchronized.

Here's a second example. The following program demonstrates how to watch for a special key sequence: in this case, CTRL-ALT-Z. In a program like this, the eventDispatched method can be called frequently, so it's important to minimize the creation of objects within the listener. Otherwise, the garbage collector will be kept quite busy freeing the objects created for each pass. Much of the program is the same as for the first example. The major changes relate to actions taken in response to the triggering event (pressing CTRL-ALT-Z). Instead of capturing keystrokes, the program reports the amount of free memory in the Java virtual machine*.

    import javax.swing.*;
    import java.awt.*;
    import java.awt.event.*;

    public class GetMem {
      static class TextFrame extends JFrame {
        TextFrame() {
          setDefaultCloseOperation(EXIT_ON_CLOSE);
          Container pane = getContentPane();
          JTextField tf = new JTextField();
          pane.add(tf, BorderLayout.NORTH);
          setSize(100, 50);
          show();
        } 
      }

      public static void main(String args[]) {
        AWTEventListener listener = 
                               new AWTEventListener() {
          int keyId = KeyEvent.KEY_PRESSED;
          int keyCode = KeyEvent.VK_Z;
          int keyModifiers =
           (InputEvent.CTRL_MASK | InputEvent.ALT_MASK);
          public void eventDispatched(AWTEvent event) {
            KeyEvent ke = (KeyEvent)event;
            if ((ke.getID() == keyId) &&
                (ke.getKeyCode() == keyCode) &&
                (ke.getModifiers() == keyModifiers)) {
              System.out.println(
                  Runtime.getRuntime().freeMemory());
            }
          }
        };
        Toolkit toolkit = Toolkit.getDefaultToolkit();
        toolkit.addAWTEventListener(
            listener, AWTEvent.KEY_EVENT_MASK);
        new TextFrame();
        new TextFrame();
      }
    }

The activate key combination you select must be sensitive to platform and application-specific options. Be sure to select a combination that is unique.

Obviously, much more complicated operations could happen in response to the triggering sequence. However, no matter what the actions are, the way you create and register the sequence is the same:

  • Create the AWTEventListener
  • Determine the appropriate mask
  • Register the listener with the Toolkit

When you use an AWTEventListener, you typically assign the sequence to the event queue for the life of the application. However to monitor a situation, it might be necessary to only temporarily associate the listener with the queue. In this case, remember to remove the listener, with the removeAWTEventListener method of Toolkit, when the monitoring operation completes.

For more information on ways to debug an AWT application, including using AWTEventListener, see Chapter 7 of Advanced Programming for the Java 2 Platform.

.
.

INTERNATIONALIZING DATES, TIMES, MONTHS, AND DAYS OF THE WEEK

Would you like to port your programs to other locations around the world, but want to minimize the costs involved in internationalizing your programs? The standard Java programming libraries give ways to do that for several languages. This tip covers one aspect of internationalization -- internationalizing dates and times.

The first step in internationalizing an application, is to localize the messages for the program. This means identifying a language and country for the messages. You do this through the java.util.Locale class. You might ask, why specify both a language and country -- English is English isn't it? And French is French? Well, with English, for instance, the United States and Great Britain have some linguistic differences. Similarly, there are differences between French as used in France, and French as used in Quebec.

The purpose of this tip, though, isn't to discuss the differences between dialects or whether the proper spelling of a performance hall is theater or theatre. Instead, this tip focuses on how to display information related to dates and times in a format that's internationalized. With this format, a user won't have to ask if 01/02/03 is January 2nd or the first of February.

As previously mentioned, the Locale class allows you to specify the "where" part of internationalization. The "how" part is covered by the DateFormat class, for both dates and times, with the DateFormatSymbols class playing a supporting role.

Although DateFormatSymbols plays a supporting role, providing indirect access through the DateFormat class's factory methods: getTimeInstance, getDateInstance, and getDateTimeInstance, let's look at it first. The DateFormatSymbols class is a holding place for translations for month names, day-of-the-week names, and even am/pm strings. Instead of having to find a translator for every language you need to translate your dates to, simply use DateFormatSymbols -- that is, if the locale you are interested in is one of the approximately fifty that has been translated for you. See the list of supported locales. If the locale of interest is supported, you get both short and long forms of the month and day-of-the-week names translated.

To demonstrate, the following program displays the month names for the language (and region) you want -- assuming that the language is supported. You specify the language as an argument to the program. The program accepts zero, one, or two arguments. Called with no argument, the program displays the names for the default Locale. If called with one argument, the program accepts that argument as the two-letter language code from ISO-639. Some examples of these two-letter codes are: de for German, fr for French, and it for Italian. If you specify two arguments, the program accepts the first as the two-letter language code, and the second as a country code from ISO-3166.

    import java.text.*;
    import java.util.*;

    public class MonthNames {
      public static void main(String args[]) {
        Locale locale;
        if (args.length == 1) {
          locale = new Locale(args[0]);
        } else if (args.length == 2) {
          locale = new Locale(args[0], args[1]);
        } else {
          locale = Locale.getDefault();
        }
        DateFormatSymbols symbols = 
          new DateFormatSymbols(locale);
        String months[] = symbols.getMonths();
        for (int i=0; i < months.length; i++) {
          System.out.println(months[i]);
        }
      }
    }

Running the program for Spanish months with

    java MonthNames es

produces the following results:

    enero
    febrero
    marzo
    abril
    mayo
    junio
    julio
    agosto
    septiembre
    octubre
    noviembre
    diciembre
	

Note that the last line of the result is empty (that is, the result includes a "thirteenth" month). The reason for this might not be obvious, but is necessary for lunar calendars, and ignored for Gregorian calendars. As you shouldn't use DateFormatSymbols directly, working with dates through DateFormat will fill in the correct one.

There's one thing you should note about printing internationalized text to the console. Internally, characters are stored in 16-bit Unicode format. When printed to the console, they get converted to something the console can handle. In most systems, this is not Unicode. Rather it is a locale-specific encoding. If you run your target machine in the French locale and you print out the French version of February (février), you will see what you expect. With a US locale, you typically won't. Consider displaying output to a JTextArea if the console doesn't provide the support you need.

Another application of localization has to do with the way a date is displayed, that is, the issue mentioned earlier regarding the interpretation of 01/02/03. This is where the DateFormat class is important.

The DateFormat class offers an easy way to ask for the format used to display dates at a Locale. (A Locale is an object that represents a specific geography or region.) To determine how dates are displayed in a particular country, you simply request the DateFormat for that Locale.

The DateFormat class offers four formats per Locale:

  • SHORT
  • MEDIUM
  • LONG
  • FULL

There is also a DEFAULT format, which lets you use the appropriate default for the locale.

Here's a demonstration of DateFormat to display dates. The following program displays today's date in the format for a requested Locale. When you run the program, you specify Locale arguments. The arguments to DisplayDate are like those for the MonthNames program: no argument means use the default Locale; one argument is the two-letter ISO-639 language code; and two arguments means the two-letter language code plus the ISO-3166 country code.

    import java.text.*;
    import java.util.*;

    public class DisplayDates {
      public static void main(String args[]) {
        Locale locale;
        if (args.length == 1) {
          locale = new Locale(args[0]);
        } else if (args.length == 2) {
          locale = new Locale(args[0], args[1]);
        } else {
          locale = Locale.getDefault();
        }
        Date now = new Date();
        DateFormat format = DateFormat.getDateInstance(
          DateFormat.SHORT, locale);
        System.out.println("Short: " + 
          format.format(now));
        format = DateFormat.getDateInstance(
          DateFormat.MEDIUM, locale);
        System.out.println("Medium: " + 
          format.format(now));
        format = DateFormat.getDateInstance(
          DateFormat.LONG, locale);
        System.out.println("Long: " + 
          format.format(now));
        format = DateFormat.getDateInstance(
          DateFormat.FULL, locale);
        System.out.println("Full: " + 
          format.format(now));
        format = DateFormat.getDateInstance(
          DateFormat.DEFAULT, locale);
        System.out.println("Default: " + 
          format.format(now));
      }
    }

Suppose that today's date is June 24, 2003. If you run the program today for the United Kingdom:

    java DisplayDates en GB

it produces the following results:

    Short: 24/06/03
    Medium: 24-Jun-2003
    Long: 24 June 2003
    Full: 24 June 2003
    Default: 24-Jun-2003

For Germany:

    java DisplayDates de DE

Short: 24.06.03
Medium: 24.06.2003
Long: 24. Juni 2003
Full: Dienstag, 24. Juni 2003
Default: 24.06.2003

Notice that in some languages, even the separator is different.

DateFormat isn't just for output formats. It is also for input formats. This is how you can find out if 01/02/03 is really January or February. If you ask the DateFormat class to parse an input string, it works in the reverse of the format method (which formats an output string). Parsing creates a Date object. So, if you want to see what the underlying date is, you need to go back to the DateFormat to format the output. (Or, as the following example demonstrates, you can simply rely on the toString method of Date to format it for you.)

Here's a program that demonstrates input parsing. The program requires that the first argument be the date string to parse. The remaining arguments identify the Locale (as in the previous examples). Using DateFormat in this manner allows you to request input from users in the date format they're accustomed to using.

    import java.text.*;
    import java.util.*;

    public class ParseDate {
      public static void main(String args[]) {
        Locale locale;
        if (args.length == 0) {
          System.err.println(
            "Please provide a date string to parse");
          return;
        } else if (args.length == 2) {
          locale = new Locale(args[1]);
        } else if (args.length == 3) {
          locale = new Locale(args[1], args[2]);
        } else {
          locale = Locale.getDefault();
        }
        String dateString = args[0];

        DateFormat format = DateFormat.getDateInstance(
          DateFormat.SHORT, locale);
        try {
          System.out.println(format.parse(dateString));
        } catch (ParseException e) {
          System.err.println("Bad input format");
        }
      }
    }

Running the program for the United States locale:

    java ParseDate 01/02/03 en US

produces the following results in the Eastern Time Zone:

    Thu Jan 02 00:00:00 EST 2003

However, if you run the program with a locale of Spain:

    java ParseDate 01/02/03 es ES

you get a different result:

    Sat Feb 01 00:00:00 EST 2003

What if you give the wrong input format for a particular locale? In that case, the program throws a ParseException:

    java ParseDate 01/02/03 de

    Bad input format

In addition to formatting date and time strings, the standard libraries can be used to display localized messages. You do this using a ResourceBundle. How to use these bundles can be found in an earlier Tech Tip titled "Resource Bundles". You can also learn more about internationalization in the Internationalization trail of the Java Tutorial.

.
.
.

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.

* As used in this document, the terms "Java virtual machine" or "JVM" mean a virtual machine for the Java platform.

Sun Microsystems, Inc.
.
.


Please unsubscribe me from this newsletter.