Java 1.2 Unleashed

Contents


- 19 -

Internationalization


Being the de facto programming language of the Web, Java is an international programming language. It is being used in every country connected to the Web, and it is increasingly being called upon to develop applications and applets for use in the native languages of these countries. Fortunately, the developers of Java anticipated its international appeal and designed it accordingly. The decision to provide comprehensive support of the Unicode character set was a major step toward Java's internationalization. (Unicode is covered in the section "Using Unicode" later in this chapter.)

In addition to Unicode support, the JDK provides classes and interfaces that simplify the process of incorporating locale-specific resources (such as text strings, dates, and currencies) within Java programs. These classes and interfaces allow locale-specific resources to be separately maintained and easily converted.

This chapter covers Java's internationalization support. You'll be introduced to the Unicode character set, and you'll learn how to use the Locale and ResourceBundle classes to maintain locale-specific information. You'll also learn how the java.text package facilitates conversion of numbers, dates, and other units. When you finish this chapter, you'll be able to develop Java programs that adjust their output to the language and customs of the locale in which they execute.

What Is Internationalization?

In an age when individuals around the world are globally connected via the Internet, programs are often required to be tailorable to the language and customs of the locales in which they execute. Programs that provide these capabilities are referred to as global programs.

Global programs can be difficult to develop, but they don't have to be. If you write a program that mixes language-dependent text strings throughout its code and displays all its output using the customs of a single country, you will have a terrible time converting it to the language and customs of another locale. If and when you do complete the conversion, you will have to maintain multiple versions of the same program--one for each locale. When you take this kind of approach, developing global programs is complex, difficult, and time-consuming.

On the other hand, if you carefully isolate locale-specific resources, such as text strings, currencies, and dates, and maintain these resources separately from locale-independent code, global programs can be developed with a minimum of additional effort. Tailoring of such programs for use in specific locales can often be reduced to providing foreign-language equivalents of native-language text strings and specifying the use of alternative format sets.

Internationalization is the process of designing and developing global programs in such a way that locale-specific information is separately and efficiently maintained. Internationalization allows global programs to be more easily localized.

The JDK supports internationalization by using a multilanguage character set (Unicode 2.0), the Locale, ResourceBundle and other classes of java.util, the format conversion classes of java.text, and the Unicode character-stream support of java.io. This chapter focuses on java.text but also discusses how the internationalization-related classes and interfaces of other packages are used to develop global Java applications and applets.

Using Unicode

Unlike most other programming languages, Java provides comprehensive support for the Unicode 2.0 character set. Unicode is a 16-bit character set, meaning that it is capable of representing 65,536 characters. This is a large character set and can be used to represent the characters used by many of the world's popular languages. The 128 characters of the popular ASCII character set are the first 128 characters of Unicode.


NOTE: ASCII stands for American Standard Code for Information Interchange.


NOTE: More information about the Unicode character set can be found at http://www.unicode.org.

Unicode characters are written in Java using Unicode escape character sequences. These sequences are of the form \uxxxx, where the four x's are replaced with hexadecimal digits. Each of the four hexadecimal digits represents four bits of a 16-bit Unicode character.

To display Unicode characters other than ASCII, you need a Unicode font. In the absence of such a font, Java displays Unicode characters using the \uxxxx notation. The Bitstream Cyberbit font is an example of a font that supports Unicode. It can be downloaded from the Bitstream Web site at http://www.bitstream.com/cyberbit/ ftpcyber.htm.

Managing Locales and Resources

The Locale class is defined in the java.util packages. This class provides internationalization support by describing geographic, political, or cultural regions. Locale objects are created by supplying the language and country arguments to the Locale() constructor or by using any of the predefined Locale constants. The access methods of Locale support the setting and retrieving of language, country, and variant-related values. The LocaleApp program, shown in Listing 19.1, illustrates the use of the Locale class in describing a locale in terms of a country and a language. Its output is as follows:

java LocaleApp

CURRENT LOCALE:

Country: United States

Language: English

OTHER LOCALES:

Country: ROC

Language: Chinese

Country: Korea

Language: Korean

Country: Italy

Language: Italian

Country: Canada

Language: English

Country: Canada

Language: French

LISTING 19.1. THE LocaleApp PROGRAM.

import java.util.*;

class LocaleApp {

 public static void main(String args[]) {

  Locale currentLocale = Locale.getDefault();

  Locale locales[]={Locale.TAIWAN,Locale.KOREA,

   Locale.ITALY,Locale.CANADA,Locale.CANADA_FRENCH};

  System.out.println("CURRENT LOCALE:");

  describeLocale(currentLocale);

  System.out.println("OTHER LOCALES:");

  for(int i=0;i<locales.length;++i)

   describeLocale(locales[i]);

 }

 static void describeLocale(Locale l){

  System.out.println("Country: "+l.getDisplayCountry());

  System.out.println("Language: "+l.getDisplayLanguage());

  System.out.println();

 }

}

The LocaleApp program invokes the getDefault() method of the Locale class to retrieve the current locale that is in effect. It then creates an array of sample Locale objects using the Locale constants to Taiwan, Korea, Italy, Canada, and French Canada. The describeLocale() method is then used to display the country and language associated with each Locale object.


NOTE: If you run the LocaleApp program with a default Locale other than the United States, the results will vary slightly.

The ResourceBundle Classes

The ResourceBundle class of java.util also supports internationalization. It is used to store locale-specific resources and tailor a program's appearance to the particular locale in which it is being run. The ResourceBundle class is extended by the ListResourceBundle and PropertyResourceBundle classes. The ListResourceBundle class organizes resources in terms of an array of object pairs, where the first object is a String key and the second object is the key's value. The PropertyResourceBundle class organizes locale-specific resources using a property file.

The ResourceBundleApp program in Listing 19.2 shows how resource bundles can be used to tailor a program's output. It is invoked with a two-character ISO-3166 country code and displays the names of five animals in English or Spanish, depending on the country code that was used.


NOTE: Lists of ISO-3166 country codes and ISO-639 language codes can be found at http://www.ics.uci.edu/pub/ietf/http/related/.

The following examples of program output show how the program tailors the information it displays using the methods of the Locale, ResourceBundle, and ListResourceBundle classes:

java ResourceBundleApp CA

cow

horse

cat

elephant

dog

java ResourceBundleApp US

cow

horse

cat

elephant

dog

java ResourceBundleApp GB

cow

horse

cat

elephant

dog

java ResourceBundleApp ES

vaca

caballo

gato

elefante

perro

java ResourceBundleApp MX

vaca

caballo

gato

elefante

perro


NOTE: The TextBundle and TextBundle_es classes, introduced later in this section, must be compiled before you run ResourceBundleApp.

LISTING 19.2. THE ResourceBundleApp PROGRAM.

import java.util.*;

class ResourceBundleApp {

 public static void main(String args[]) {

  if(args.length!=1){

   System.out.println("Usage: java ResourceBundleApp country_code");

   System.exit(0);

  }

  Locale mexico = new Locale("es","MX");

  Locale spain = new Locale("es","ES");

  Locale locales[] = {mexico,spain,Locale.US,Locale.CANADA,Locale.UK};

  Locale newLocale=null;

  for(int i=0;i<locales.length;++i){

   if(args[0].equals(locales[i].getCountry())){

    newLocale=locales[i];

    break;

   }

  }

  if(newLocale==null){

   System.out.println("Country not found.");

   System.exit(0);

  }

  ResourceBundle resources=ResourceBundle.getBundle("TextBundle",newLocale);

  Enumeration enum=resources.getKeys();

  while(enum.hasMoreElements()){

   String key=(String) enum.nextElement();

   System.out.println(resources.getString(key));

  }

 }

}

The ResourceBundleApp program creates Locale objects for Mexico and Spain and creates a list of supported locales, including Mexico, Spain, the United States, Canada, and Great Britain. It checks the two-character country code that is passed to the program as a command-line argument with the list of supported locales. The newLocale variable is assigned the Locale object whose country code matches the one passed via the command line. The getCountry() method of the Locale class returns the two-character country code of a Locale object.

The getBundle()method of the ResourceBundle class is invoked to retrieve the ResourceBundle object associated with the Locale object stored in newLocale. Two resource bundle classes are available: the default (English) resource bundle, shown in Listing 19.3, and the Spanish language resource bundle, shown in Listing 19.4. The getBundle() method looks for classes of the following form until it finds one that matches: ll is the two-character (lowercase) language code of the locale, and CC is the two-character (uppercase) country code of the locale. The following are examples of these file name formats:

For example, if you pass the GB country code (Great Britain) to ResourceBundleApp, getBundle() will look for resource bundle classes TextBundle_en_GB and TextBundle_en before settling on TextBundle.


NOTE: The language code for English is en. The language code for Spanish is es. The country codes for Mexico, Spain, United States, Canada, and United Kingdom are MX, ES, US, CA, and GB.

The getKeys() method of the ResourceBundle class returns an enumeration of the keys used to access locale-specific resources. The getString() method of ResourceBundle returns a String representation of the resource object associated with the key.

LISTING 19.3. THE TextBundle CLASS.

import java.util.*;

public class TextBundle extends ListResourceBundle {

 public Object[][] getContents() {

  return contents;

 }

 static final Object[][] contents = {

  {"dog","dog"},

  {"cat","cat"},

  {"horse","horse"},

  {"cow","cow"},

  {"elephant","elephant"}

 };

}

The TextBundle class is a subclass of ListResourceBundle, which is a subclass of ResourceBundle. The getContents() method of ListResourceBundle is overridden to return an array of keys and their associated language-specific resources. The getContents() method is used to generate the information returned by the getKeys() and getString() methods used in ResourceBundleApp.

The contents array is an array of two-element arrays: the first element is the key, and the second element is its value.

The TextBundle_es class is a Spanish language version of the TextBundle class. It provides a Spanish translation of the words "dog," "cat," "horse," "cow," and "elephant."

LISTING 19.4. THE TextBundle_es CLASS.

import java.util.*;

public class TextBundle_es extends ListResourceBundle {

 public Object[][] getContents() {

  return contents;

 }

 static final Object[][] contents = {

  {"dog","perro"},

  {"cat","gato"},

  {"horse","caballo"},

  {"cow","vaca"},

  {"elephant","elefante"}

 };

}

Performing Locale-Specific Format Conversions

The classes of java.text are used to provide locale-specific format conversions for use with numbers, dates, and other objects. Format is an abstract class that is extended by other classes to support parsing and format conversion. It declares the format() method to convert objects to strings and the parseObject() method to convert strings to objects. These methods are overridden by its subclasses.

The DateFormat Class

DateFormat is an abstract class used to format and parse date and time values using locale-specific customs. It supports four formatting styles defined by the FULL, LONG, MEDIUM, and SHORT constants. These styles determine the length of the formatted output. DateFormat defines other constants for identifying specific date and time fields. The getInstance(), getDateInstance(), getTimeInstance(), and getDateTimeInstance() methods return instances of DateFormat that are specific to a locale. Other methods are provided for working with objects of the Calendar, TimeZone, and other date-related classes covered in Chapter 11, "Using the Utility and Math Packages."

The SimpleDateFormat Class

The SimpleDateFormat class extends the DateFormat class to provide a default implementation of date formatting and parsing capabilities. It allows date and time formatting patterns to be used to customize formatting and parsing. The SimpleDateFormat class makes use of special date and time pattern symbols that are discussed in the class's API description.

The DateFormatApp program, shown in Listing 19.5, illustrates the use of the date- formatting capabilities of the SimpleDateFormat class. When you run the program, it produces the following type-formatted output. The date and time displayed are the current date and time of the locale in which the program is run:

java DateFormatApp

The year is 1998 AD.

The month is July.

It is 09 o'clock PM, Pacific Standard Time.


NOTE: If DateFormatApp displays an incorrect time zone value, check your user. timezone system property to make sure that it is set correctly.

Although the output may not be that impressive, the formatting capabilities provided by SimpleDateFormat are efficient and easy to use. The pattern string places single quotes around text that is not a date/time formatting pattern. The formatting patterns used in the pattern string are as follows:

The pattern is supplied as an argument to the SimpleDateFormat constructor to create a format pattern-specific object. The current date is passed to the format() method of this object to create a String object that is formatted using the current date and the format pattern.

LISTING 19.5. THE DateFormatApp PROGRAM.

import java.text.*;

import java.util.*;

class DateFormatApp {

 public static void main(String args[]) {

  String pattern="'The year is `";

  pattern +="yyyy GG";

  pattern +="'.\nThe month is `";

  pattern+="MMMMMMMMM";

  pattern+="'.\nIt is `";

  pattern+="hh";

  pattern+="' o''clock `";

  pattern+="a, zzzz";

  pattern+="'.'";

  SimpleDateFormat format = new SimpleDateFormat(pattern);

  String formattedDate = format.format(new Date());

  System.out.println(formattedDate);

 }

}

The DateFormatSymbols Class

The DateFormatSymbols class is used to provide access to locale-specific date symbols, such as the names of days, months, and other date and time units. DateFormatSymbols objects are created using the getDateFormatSymbols method of the SimpleDateFormat class or via the DateFormatSymbols() constructor. Several get methods allow locale-specific formatting strings to be retrieved.

The NumberFormat Class

The NumberFormat class is an abstract class that supports the locale-specific formatting and parsing of numbers. The INTEGER_FIELD and FRACTION_FIELD constants are used to identify fields within decimal numbers. The format() and parse() methods support number formatting and parsing. Other methods are provided to access the number of digits to the left and right of the decimal point, to work with locales, and to access other number-formatting attributes.

The NumberFormatApp program, shown in Listing 19.6, illustrates the use of the NumberFormat class in supporting locale-specific currency formatting. It prints out a value of 1,000,000 currency units using the locale-specific currency value, as shown in the following:

java NumberFormatApp

$1,000,000.00

LISTING 19.6. THE NumberFormatApP PROGRAM.

import java.text.*;

import java.util.*;

class NumberFormatApp {

 public static void main(String args[]) {

  NumberFormat format = NumberFormat.getCurrencyInstance(

   Locale.getDefault());

  String formattedCurrency = format.format(1000000);

  System.out.println(formattedCurrency);

 }

}

The DecimalFormat Class

The DecimalFormat class extends the NumberFormat class to support the formatting of decimal numbers using locale-specific customs. The class supports the specification and use of custom formatting patterns. The format() and parse() methods are used to perform formatting and parsing. A number of get and set methods are provided to access specific formatting parameters. The DecimalFormat class makes use of special number formatting pattern symbols that are discussed in the class's API description.

The DecimalFormatSymbols Class

The DecimalFormatSymbols class provides access to the locale-specific symbols used in formatting numbers. These symbols include decimal separators, grouping separators, and others used by objects of the DecimalFormat class. Instances of DecimalFormatSymbols are created using the getDecimalFormatSymbols() method of the DecimalFormat class and the DecimalFormatSymbols() constructors. Several methods are provided to set and retrieve formatting information, such as the decimal separator character, grouping separators, the minus sign, the infinity symbol, the not-a-number symbol, and the percentage sign.

The ChoiceFormat Class

The ChoiceFormat class extends the NumberFormat class to identify strings that serve as labels for numbers within specific intervals. The ChoiceFormat constructor takes an array of double values used to specify numeric intervals and an array of String objects that identify the labels associated with those intervals. The double values are referred to as limits. The methods of the ChoiceFormat class support the formatting and parsing of strings based on the limits and their labels.

The ChoiceFormatApp program in Listing 19.7 illustrates the use of the ChoiceFormat class. It uses a random number generator to predict the likelihood of rain. Its output varies every time you run it. Sample output follows:

java ChoiceFormatApp

The likelihood of rain today is very low (0.05002163005399529).

java ChoiceFormatApp

The likelihood of rain today is high (0.954441890602895).

java ChoiceFormatApp

The likelihood of rain today is moderate (0.3000466839384621).

The ChoiceFormatApp program defines four limits: 0.0, 0.1, 0.3, and 0.7. It defines four labels that correspond to intervals defined by these limits:

An object of the ChoiceFormat class is created using the limits and labels arrays. A random number between 0 and 1 is fed into the format() method of the ChoiceFormat object to select the label associated with the interval in which the random number generator falls.

LISTING 19.7. THE ChoiceFormatApp PROGRAM.

import java.text.*;

import java.util.*;

class ChoiceFormatApp {

 public static void main(String args[]) {

  double limits[] = {0.0,0.1,0.3,0.7};

  String labels[] = {"very low","low","moderate","high"};

  ChoiceFormat format = new ChoiceFormat(limits,labels);

  String prediction = "The likelihood of rain today is ";

  double r = Math.random();

  prediction += format.format(r)+" ("+r+").";

  System.out.println(prediction);

 }

}

The MessageFormat Class

The MessageFormat class extends the Format class to format objects as messages that are inserted into a String object. The MessageFormat constructor takes a String argument that specifies a message-formatting pattern. This pattern contains formatting elements where time, date, number, and choice objects may be inserted. Consult the API documentation of the MessageFormat class for a description of the syntax used to create formatting patterns. The format() method is used to insert objects into a message formatting pattern. The parse() method is used to parse the objects contained in a string according to a message-formatting pattern.

The MessageFormatApp program, shown in Listing 19.8, provides a simple introduction to the use of the MessageFormat class. It generates output in the following form:

java MessageFormatApp

The time is 4:16:05 PM and your lucky number is 620.

MessageFormatApp creates a format pattern with time and number fields and uses this pattern to construct a MessageFormat object. It creates an array of two objects: the current time (as a Date object) and a random Integer object. It then invokes the format() method of the MessageFormat object to produce the formatted output that is displayed to the console window.

LISTING 19.8. THE MessageFormatApp CLASS.

import java.text.*;

import java.util.*;

class MessageFormatApp {

 public static void main(String args[]) {

  String pattern = "The time is {0,time} and ";

  pattern += "your lucky number is {1,number}.";

  MessageFormat format = new MessageFormat(pattern);

  Object objects[] = {new Date(),

   new Integer((int)(Math.random()*1000))};

  String formattedOutput=format.format(objects);

  System.out.println(formattedOutput);

 }

}

The FieldPosition and ParsePosition Classes

The FieldPosition class is used to identify fields in formatted output. It keeps track of the field's position within the formatted output. The FieldPosition() constructor takes an integer value that is used to identify the field. Its methods are used to retrieve the indices of the beginning and end of the field and the field's identifier.

The ParsePosition class is similar to the FieldPosition class. While FieldPosition is used for formatting, ParsePosition is used for parsing. Its constructor takes an integer that identifies its index within the string being parsed. The getIndex() and setIndex() methods are used to change this index.

Collation

Different languages have different alphabets and unique ways of sorting text strings written in those languages. Collation, as it applies to java.text, is the process of sorting or arranging text strings according to locale-specific customs. The java.text package supports collation through the Collator, RuleBasedCollator, CollationKey, and CollationElementIterator classes.

The Collator class is an abstract class that is used to compare String objects using locale-specific customs. It is subclassed to provide implement-specific collation algorithms. The getInstance() method is used to retrieve a locale-specific Collation instance. Some languages recognize different strengths in determining whether letters are identical or different; this is common to languages that support accented characters. The setStrength() of Collator may be used to set different collation strength levels. The compare() method compares two strings and returns an int value indicating the results of the comparison. The other methods of Collator support the decomposition of composite characters (for example, accented characters) and the creation of CollationKey objects.

The CollationKey class provides a compact representation of a String object according to the collation rules of a Collator object. CollationKey objects are optimized to support fast String comparisons and are preferred over the compare() method of the Collator class for extensive comparisons, such as those found in sorting algorithms. CollationKey objects are generated using the getCollationKey() method of the Collator class. The compareTo() and equals() methods of CollationKey are used to perform comparisons. The toByteArray() method can be used to convert a CollationKey object to a byte array. The getSourceString() method returns the String object from which a CollationKey object was generated.

The RuleBasedCollator class extends Collator to provide a concrete collator implementation. It allows you to define your own collation rules. However, in most cases, you'll want to use the predefined rules that are specific to your locale. The getRules() method may be used to retrieve the collation rules that are in effect for a RuleBasedCollator object. The getCollationKey() method overrides that of the Collator class. The getCollationElementIterator() method returns a CollationElementIterator object for the collator. Iterator classes are covered in the next section.

The CollateApp program, shown in Listing 19.9, shows how the RulesBasedCollator and CollationKey classes can be used to sort a file. The CollateApp program takes a filename as a command-line argument and produces a sorted version of the file's contents as its output. The following output shows how the file CollateApp.java is sorted. Note how the default collation rules treat blanks that appear at the beginning of a string:

java CollateApp CollateApp.java

}

 }

 }

  }

  }

  }

   }

    }

}catch(Exception ex){

  boolean changes=true;

   BufferedReader in = new BufferedReader(new FileReader(args[0]));

   changes=false;

     changes=true; 

class CollateApp {

  CollationKey keys[]=new CollationKey[keyVector.size()];

     CollationKey temp=keys[i];

   Collator.getInstance(defaultLocale);

  for(int i=0;i<keys.length;++i)

  for(int i=0;i<keys.length;++i)

   for(int i=0;i<keys.length-1;++i){

  if(args.length!=1){

    if(compare>0){

import java.io.*;

import java.text.*; 

import java.util.*;

   in.close();

    int compare=keys[i].compareTo(keys[i+1]);

   keys[i]=(CollationKey) keyVector.elementAt(i);

     keys[i]=keys[i+1];

     keys[i+1]=temp;

  keys=sort(keys);

    keyVector.addElement(collator.getCollationKey(line));

  Locale defaultLocale = Locale.getDefault();

 public static void main(String args[]) {

  return keys;

  RuleBasedCollator collator = (RuleBasedCollator)

 static CollationKey[] sort(CollationKey keys[]){

   String line;

   System.exit(0);

   System.exit(0);

   System.out.println("Usage: java CollateApp file");

   System.out.println(ex);

   System.out.println(keys[i].getSourceString());

  try {

  Vector keyVector = new Vector();

   while((line=in.readLine())!=null)

  while(changes){ 

LISTING 19.9. THE CollateApp PROGRAM.

import java.text.*;

import java.util.*;

import java.io.*;

class CollateApp {

 public static void main(String args[]) {

  if(args.length!=1){

   System.out.println("Usage: java CollateApp file");

   System.exit(0);

  }

  Locale defaultLocale = Locale.getDefault();

  RuleBasedCollator collator = (RuleBasedCollator)

   Collator.getInstance(defaultLocale);

  Vector keyVector = new Vector();

  try {

   BufferedReader in = new BufferedReader(new FileReader(args[0]));

   String line;

   while((line=in.readLine())!=null)

    keyVector.addElement(collator.getCollationKey(line));

   in.close();

  }catch(Exception ex){

   System.out.println(ex);

   System.exit(0);

  }

  CollationKey keys[]=new CollationKey[keyVector.size()];

  for(int i=0;i<keys.length;++i)

   keys[i]=(CollationKey) keyVector.elementAt(i); 

  keys=sort(keys);

  for(int i=0;i<keys.length;++i)

   System.out.println(keys[i].getSourceString());

 }

 static CollationKey[] sort(CollationKey keys[]){

  boolean changes=true;

  while(changes){

   changes=false;

   for(int i=0;i<keys.length-1;++i){

    int compare=keys[i].compareTo(keys[i+1]);

    if(compare>0){

     changes=true;

     CollationKey temp=keys[i];

     keys[i]=keys[i+1];

     keys[i+1]=temp;

    }

   }

  }

  return keys;

}

}

The CollateApp program creates a RulesBasedCollator object using the getInstance() method of the Collator class. It selects the collator corresponding to the default locale. It reads in each line of the file, creates a CollationKey object corresponding to the input line, and stores the line in a Vector object. It then converts the vector to an array to simplify the sorting process. The sort() method is invoked to sort the CollationKey array. The String objects corresponding to the sorted CollationKey objects are retrieved via the getSourceString() method. These String objects are then printed.

The sort() method sorts the CollationKey array using the CollationKey compareTo() method. This method returns a positive integer if the CollationKey object being compared is greater than the one it is being compared to. It returns 0 if they are equal and a negative integer if the CollationKey object being compared is less than the one it is being compared to.

The Iterator Classes and Interfaces of java.text

The CharacterIterator interface defines methods that are implemented by classes that provide the capability to step through (iterate) a sequence of characters. These methods allow you to set your position within the text, move to other positions, and return the character at a specific position. The StringCharacterIterator class implements CharacterIterator to support string iteration and parsing.

The BreakIterator class is used to find text boundaries. It provides useful static methods for locale-specific parsing of character sequences by word, line, or sentence.

The CollationElementIterator class supports string iteration and returns information used to collate strings using locale-specific customs.

Summary

In this chapter you were introduced to Java's internationalization support. You learned about the Unicode character set and learned how to use the Locale and ResourceBundle classes to maintain locale-specific information. You also learned how the classes of the java.text package facilitate the conversion of numbers, dates, and other units. In the next chapter you'll be introduced to the new multimedia capabilities provided by JDK 1.2.


Contents

© Copyright 1998, Macmillan Publishing. All rights reserved.