Return-Path: Received: from po10.mit.edu (po10.mit.edu [18.7.21.66]) by po10.mit.edu (Cyrus v2.1.5) with LMTP; Fri, 26 Sep 2003 14:38:33 -0400 X-Sieve: CMU Sieve 2.2 Received: from fort-point-station.mit.edu by po10.mit.edu (8.12.4/4.7) id h8QIaNvR023800; Fri, 26 Sep 2003 14:38:27 -0400 (EDT) Received: from hermes.sun.com (hermes.sun.com [64.124.140.169]) by fort-point-station.mit.edu (8.12.4/8.9.2) with SMTP id h8QIVb4b022749 for ; Fri, 26 Sep 2003 14:31:38 -0400 (EDT) Date: 26 Sep 2003 09:31:18 -0800 From: "SDN - Core Java Technologies Tech Tips" To: alexp@mit.edu Message-Id: <44006826-1316630966@hermes.sun.com> Subject: Core Java Technologies Tech Tips, September 26, 2003 (Using ChoiceFormat, Component Orientation in Swing) 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 September 26, 2003    

In this Issue

Welcome to the Core Java Technologies Tech Tips, September 9, 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 ChoiceFormat for Handling Plural Messages
-Component Orientation in Swing User Interfaces

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.

.
.

USING CHOICEFORMAT FOR HANDLING PLURAL MESSAGES

The August 19, 2003 Tech Tip titled "Formatting Messages With Variable Content" described how to use the MessageFormat class of the java.text package to create internationalized error messages that contain variable content. Using the MessageFormat class is a good approach when most of the message stays the same, and you can simply "fill in in the blanks" to complete the variable part of the message. But the approach doesn't work particularly well in situations where part of the error message is a quantity, and you need to follow it with a noun that matches the quantity.

Matching nouns to quantities is tricky in English. Case in point: when the count of something is zero, the word it modifies is plural, as in "I have no cars in the garage." (Cars is plural here, even though you have zero of them.) When the count is one, the word it modifies is singular, as in "I just bought one car." And with more than one, the modified word is plural again: "I won the lottery and bought twelve cars."

This suggests that matching nouns to quantities is also tricky when you display localized error messages, especially when you want to display slightly different messages based on a quantity. However the java.text package includes a class, ChoiceFormat, that handles the mapping of quantities to messages. You use the results produced by ChoiceFormat in conjunction with a MessageFormat object to generate the appropriate quantified message text.

Let's look at an example. Here, you'll use a ChoiceFormat (together with MessageFormat) to display the correct message for a given quantity. Specifically, you'll display an English message that contains the correct noun plural for the quantities zero, one, and more than one.

Start by creating a resource bundle. To do this, create a file named SampleResources.properties, and place the necessary resources in it:

   none=I have no cars in the garage.
   one=I just bought one car.
   many=I won the lottery and bought {0} cars.

Remember that the {0} in the last line is a placeholder. It means replace this with a number used as an argument.

This example is for English only. You need to provide a similar file for every language you want to support. See the May 21, 1998 Tech Tip titled "Resource Bundles" for additional information on using a ResourceBundle.

Next, create a ChoiceFormat. A ChoiceFormat allows you to attach a format to a range of numbers. To specify the range, you create an array of ranges for the formats. In this example, you want one format for 0, another format for 1, and yet another format for 2 and beyond. You can specify these "ranges" in the following array:

   double limits[] = {0, 1, 2};

Although the array doesn't look as though it specifies ranges, it actually does. Technically, the array specification means: for ranges from 0 to just below 1, use the first format; for 1 to just below 2 use the next; and for 2 and above use the last. However, because the example uses only integer values for the value to print, you don't have to consider this technicality yet.

Next, specify the formats for each MessageFormat string to use:

   String none = resourceBundle.getString("none");
   String one = resourceBundle.getString("one");
   String many = resourceBundle.getString("many");
   String formats[] = {none, one, many};

Notice that the formats are the strings in the resource bundle.

Now you can create the ChoiceFormat:

   ChoiceFormat cf = new ChoiceFormat(limits, formats);

The ChoiceFormat class has a format method that you can use to go directly from something like an integer to a formatted string. However, because the "many" formatting string has a placeholder (the {0}), you need to go from ChoiceFormat to MessageFormat. You do this by creating a MessageFormat and telling the object to use the ChoiceFormat as its formats:

   MessageFormat mf = new MessageFormat("{0}");
   mf.setFormats(new Format[]{cf});

The first line says create a MessageFormat object, where the entire content is provided by the formats. The second argument is the array of formatting objects, that is, the single ChoiceFormat object. The {0} in the MessageFormat maps into the ChoiceFormat. The {0} in the specific ChoiceFormat selected is the argument provided to the MessageFormat's format method.

Next, add a for loop, and loop from 0 to 4:

   for (int i=0; i<5; i++) {
     Object messageArgs[] = {new Integer(i)};
     System.out.println("i: " + i + " / " +
       mf.format(messageArgs));
   }

For each pass through the loop, create an Integer for the loop index, and tell the MessageFormat to format the output string. The MessageFormat then determines which ChoiceFormat object to use, and passes its argument to the ChoiceFormat for formatting of the message.

Here's what the complete example looks like:

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

   public class ChoiceFormatExample {
     public static void main (String args[]) {
       ResourceBundle resourceBundle =
         ResourceBundle.getBundle(
                         "SampleResources", Locale.US);
       double limits[] = {0, 1, 2};
       String none = resourceBundle.getString("none");
       String one = resourceBundle.getString("one");
       String many = resourceBundle.getString("many");
       String formats[] = {none, one, many};
       ChoiceFormat cf = 
                     new ChoiceFormat(limits, formats);
       MessageFormat mf = new MessageFormat("{0}");
       mf.setFormats(new Format[]{cf});
       for (int i=0; i<5; i++) {
         Object messageArgs[] = {new Integer(i)};
         System.out.println("i: " + i + " / " +
           mf.format(messageArgs));
       }
     }
   }

When you run the example program, you should see the following displayed:

   i: 0 / I have no cars in the garage.
   i: 1 / I just bought one car.
   i: 2 / I won the lottery and bought 2 cars.
   i: 3 / I won the lottery and bought 3 cars.
   i: 4 / I won the lottery and bought 4 cars.

There's more to ChoiceFormat than what's presented through this simple example. First, recall the technicality that was previously mentioned about the ranges in the limits array. Instead of specifically saying 2 for the third argument in the array, you could specify the next double after the value 1. You might think this is something like 1.00000000001, however there is a simpler way to specify the next double value. The ChoiceFormat class offers a nextDouble method for this exact purpose. Using nextDouble, you can specify the next double value as follows:

   {0, 1, ChoiceFormat.nextDouble(1)}

There is even a previousDouble method for specifying the previous double value.

In addition, the ChoiceFormat constructor allows you to specify all the formats in one string -- the string is parsed into its component pieces. Here, each limit is separated by the pipe (|) character, and each limit itself is specified by either a #, <, or > character. In other words, you could specify the previous set of resource strings in the following single line (for readability, the formats are shown below on two lines):

   0#I have no cars in the garage.|1#I just bought
   one car.|1<I won the lottery and bought {0} cars.

This translates to: for the number 0, use "I have no cars in the garage." For the number 1, use "I just bought one car." For everything greater than 1, use "I won the lottery and bought {0} cars."

Let's look at one more example. This example doesn't use a MessageFormat. Instead, it simply uses ChoiceFormat to map an array of limits to a specific string.

The limits are from 0-5, 5-10, and beyond 10:

  double limits[] = {0, 5, 10};

The strings are Low, Medium, and High:

  String formats[] = {"Low", "Medium", "High"};

Taking these two arrays, you again create the ChoiceFormat:

  ChoiceFormat cf = new ChoiceFormat(limits, formats);

Here's the complete example. It takes a number from the command line, and use the ChoiceFormat to display the appropriate message.

   import java.text.*;

   public class Choice2 {
     public static void main(String args[]) {
       if (args.length == 0) {
         System.err.println(
              "Please provide a number when starting");
         System.exit(-1);
       }
       double limits[] = {0, 5, 10};
       String formats[] = {"Low", "Medium", "High"};
       ChoiceFormat cf = 
                    new ChoiceFormat(limits, formats);
       System.out.println(cf.format(
                          Integer.parseInt(args[0])));
     }
   }

Run the example program with an argument of 3 to produce an output of Low, with 5 to display Medium, and an argument above 10 to display High.

   java Choice2 3
   Low

   java Choice2 5
   Medium

   java Choice2 13
   High

ResourceBundle, MessageFormat, and ChoiceFormat give you the capabilities you need to properly localize both variable and quantified text.

For more information on internationalizing your applications, see the Internationalization trail in the Java Tutorial.

.
.

COMPONENTORIENTATION IN SWING USER INTERFACES

One of the often-overlooked aspects of internationalization is component orientation. While English and the Western European languages have letters and words that go left-to-right and top-to-bottom, not all do. For instance, many Middle Eastern languages such as Hebrew and Arabic are right-to-left, top-to-bottom. Others such as Mongolian are top-to-bottom first, then right-to-left. And Far Eastern languages such as Japanese, Chinese, and Korean can be either like the Western European ones or more appropriately top-to-bottom, right-to-left.

Here's an aid to visualize the differences. For a 3x3 grid of characters:

Left-to-right, top-to-bottom

  A B C
  D E F
  G H I

Right-to-left, top-to-bottom

  C B A
  F E D
  I H G

Top-to-bottom, left-to-right

  A D G
  B E H
  C F I

Top-to-bottom, right-to-left

  G D A
  H E B
  I F C

What does this have to do with Swing and graphical interfaces? When targeting an international market for your programs, you should design considering their ComponentOrientation property.

Found in the java.awt package, the ComponentOrientation class allows you to discover the current orientation for a Locale, and design screens accordingly. You ask for the ComponentOrientation of a Locale with the getOrientation(Locale) method. Then, you can ask if that orientation is horizontal with the isHorizontal method (horizontal corresponds to both the Left-to-right, top-to-bottom, and Right-to-left, top-to-bottom grids shown above). You can also ask if the orientation is left-to-right with the isLeftToRight method (left-to-right corresponds to both the Left-to-right and Top-to-bottom, left-to-right grids above).

Why do you need to know the direction? One common reason is for building screens. The BorderLayout class is commonly used to create screens. Using the NORTH, SOUTH, EAST, and WEST constants, you can place components on the top, bottom, left, or right sides. And, you can place a component in the center with CENTER. But, many people don't know, or at least don't bother using, another set of constants: PAGE_START, PAGE_END, LINE_START, and LINE_END. (Note that these names were simplified in J2SE 1.4 from the earlier names that were added in the J2SE 1.2: BEFORE_FIRST_LINE (PAGE_START), AFTER_LAST_LINE (PAGE_END), BEFORE_LINE_BEGINS (LINE_START), and AFTER_LINE_ENDS (LINE_END).) Although EAST and WEST might make perfect sen! se for a Roman language, they don't necessarily make sense for other languages. For example, if you need to place a component at the beginning of the line, that component would need to be on the WEST side for a right-to-left language, but the EAST side for a left-to-right language. Instead of adding that component with an orientation of EAST or WEST, you should add it with a constraint of LINE_START. This will ensure the correct internationalized behavior.

To illustrate the point, run the following program, BorderOrientation. It will display two screens, one for a US-English locale and another for an Israel-Hebrew locale.

comporntUS

comporntIsr

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

   public class BorderOrientation {
      public static void main(String args[]) {
        JFrame frame1 = new JFrame("US");
        frame1.setDefaultCloseOperation(
                                 JFrame.EXIT_ON_CLOSE);
        ComponentOrientation usOrientation =
          ComponentOrientation.getOrientation(
                                            Locale.US);
        Container contentPane1 = 
                               frame1.getContentPane();
        contentPane1.setComponentOrientation(
                                        usOrientation);
        contentPane1.add(new JButton(
                    "LineEnd"), BorderLayout.LINE_END);
        contentPane1.add(new JButton(
                "LineStart"), BorderLayout.LINE_START);
        contentPane1.add(new JButton(
                "PageStart"), BorderLayout.PAGE_START);
        contentPane1.add(new JButton(
                    "PageEnd"), BorderLayout.PAGE_END);
        frame1.pack();
        frame1.setLocation(100, 100);
        frame1.show();

        JFrame frame2 = new JFrame("Israel");
        frame2.setDefaultCloseOperation(
                                 JFrame.EXIT_ON_CLOSE);
        Locale israel = new Locale("he", "IL");
        ComponentOrientation hebrewOrientation =
          ComponentOrientation.getOrientation(israel);
        Container contentPane2 = 
                              frame2.getContentPane();
        contentPane2.setComponentOrientation(
                                    hebrewOrientation);
        contentPane2.add(new JButton(
                      "LineEnd"), BorderLayout.LINE_END);
        contentPane2.add(new JButton(
                  "LineStart"), BorderLayout.LINE_START);
        contentPane2.add(new JButton(
                  "PageStart"), BorderLayout.PAGE_START);
        contentPane2.add(new JButton(
                      "PageEnd"), BorderLayout.PAGE_END);
        frame2.pack();
        frame2.setLocation(200, 200);
        frame2.show();
      }
   }

For simplicity, the previous example used hard-coded strings for button labels. Remember that in "real" internationalized programs, button labels should come from resource bundles.

You don't always have to build screens differently based on the locale and its associated component orientation. Containers using FlowLayout, for instance, will draw their components in the reverse order when the orientation is right-to-left. Some components, such as the text components, will lay out their contents accordingly, based on their orientation setting. Other components, such as JTree, reorient the tree to the other side of the display when the orientation is right-to-left. For example, run the following program, TreeSample. Notice that for the right-to-left component orientation, the JTree displays a "flipped" tree.

fliptree

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

   public class TreeSample {
      public static void main (String args[]) {
        JFrame f = new JFrame("Flipped");
        f.setDefaultCloseOperation(
                                 JFrame.EXIT_ON_CLOSE);
        Container content = f.getContentPane();
        JTree tree = new JTree();
        tree.setComponentOrientation(
                   ComponentOrientation.RIGHT_TO_LEFT);
        JScrollPane scrollPane = new JScrollPane(tree);
        content.add(scrollPane, BorderLayout.CENTER);
        f.setSize (300, 200);
        f.show();
      }
   }

Other AWT and Swing components also reorient their content based on the current component orientation setting. For a JTable, column order is reversed when the orientation is right-to-left. With a JMenuBar, the menus are reversed. For instance, the File menu, typically found on the far left, is on the other side when reversed. As a developer, you don't have to do anything differently to manipulate these components, you just need to be aware that not everything will appear the way you expect it to. JOptionPane is probably one of the exceptions -- its definition states the location of its various elements.

For more information on ComponentOrientation in Swing see the article "Component Orientation in Swing: How JFC Components Support 'BIDI' Text".

.
.
.

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.