| ## Java 1.1 Unleashed # -15- The Text Package by Eric Burke IN THIS CHAPTER Formats Collators Iterators This chapter describes the classes in the `java.text` package, new to the Java Development Kit 1.1. The text package contains a number of classes that allow the programmer to create internationalized and localized programs that do not contain location-dependent code. In my experience, localization is one of the most overlooked aspects of released software. This problem is compounded by the global characteristics of the World Wide Web. Any Web application--based on Java or not--has to be understood by many users who speak many different languages. When used properly, the classes contained in `java.text` allow the developer to cleanly and correctly implement a localized program to be used the world over. Table 15.1 shows the classes and interfaces that are part of the `java.text` package. #### Table 15.1. Classes and interfaces available in the text package.
Class | Description
`BreakIterator` | Finds boundary locations in text
`CharacterIterator` | Interface for bidirectional text iteration
`ChoiceFormat` | Attaches a format to a range of numbers
`CollationElementIterator` | Walks through each character of an international string
`CollationKey` | Compares strings that are part of a `Collator` class
`Collator` | Abstract class that provides Unicode text-comparison services
`DateFormat` | Abstract base class for date and time formatting
`DateFormatSymbols` | Encapsulates date and time formatting functionality for changes across languages and countries
`DecimalFormat` | Formats decimal numbers
`DecimalFormatSymbols` | Represents symbols such as decimal separators and grouping separators required by `DecimalFormat` when formatting`` numbers
`FieldPosition` | Aligns columns of formatted text
`Format` | Abstract base class for all formats
`MessageFormat` | Formats localizable concatenated messages
`NumberFormat` | Abstract base class for all number formats
`ParsePosition` | Records the parsing position for formatted strings
`SimpleDateFormat` | Formats and parses dates in a localized way
`StringCharacterIterator` | Implements the `CharacterIterator` interface for strings
`RuleBasedCollator` | Simple `Collator` implementation
`SimpleDateFormat` | Concrete class for parsing and formatting dates
`BreakIterator` | Iterates through word and line boundaries in a`stringParseException`; thrown when an error occurs while parsing or formatting
## Formats Formats provide a way for the programmer to easily handle the formatting of text, numbers, dates, times, and so on, in a localized way. For example, in the United States, the number `4.00` written as a monetary amount is `$4.00`, but in Germany, it is `DM4,00`. By using classes derived from `java.text.Format`, you are spared the details of how a particular locale writes its numbers or strings for any representation such as money, time, and so on. Instead, you can concentrate on writing the application at hand. The JDK 1.1 provides classes you can use to format numbers, dates and times, and text messages. Formats rely heavily on the use of `java.util.Locale`, so let's take a look at that before continuing. Table 15.2 lists the important methods supported by the `Format` class. #### Table 15.2. Methods in the Format class.
Method | Description
`format()` | Formats the given object
`parseObject()` | Parses the given s`tring` using the current f`ormat`
### The NumberFormat Class The `java.text.NumberFormat` class is the abstract base class for all classes that support number formatting and parsing. Code that uses a `NumberFormat`-derived class can be written to be completely independent of the current locale with respect to number conventions (that is, decimal sign, percent sign, separator for thousands, and so on). The `NumberFormat` class provides a few static methods that return an appropriate number, currency, or percent format for a specific locale:
    NumberFormat defFormat = NumberFormat.getInstance();
    NumberFormat defCurrFmt = NumberFormat.getCurrencyInstance();
    NumberFormat defPctFmt = NumberFormat.getPercentInstance();
    NumberFormat frFormat = NumberFormat.getInstance( Locale.FRENCH );
    NumberFormat usCurrFmt = NumberFormat.getCurrencyInstance( Locale.US );
    getPercentInstanceNumberFormat usPctFmt = NumberFormat.getPercentInstance( Locale.US );
The first three methods in this list return the `NumberFormat` objects for the default `Locale` for numbers, currencies, and percents, respectively. The second three methods also return `NumberFormat` objects for numbers, currencies, and percents; however, these methods also accept a `Locale` object (in these examples, `Locale.FRENCH` and`Locale.US`) as a parameter and use that as the `Locale` whose `NumberFormat` should be returned. Once you retrieve a `NumberFormat` object, you can use it to generate a properly formatted number. The following line creates the string for `4.00` monetary units in the default locale: `String moneyString = NumberFormat.getCurrencyInstance().format( 4.00 );` You can also use the `NumberFormat` class to parse a string that you know is a representation of a number in the current locale. For example, the following statement parses the string `"$4.00"` and finds the value `4.00` to store in the variable `myFour`: `Number myFour = NumberFormat.getCurrencyInstance(Locale.US).parse( "$4.00" );` Finally, you can use the `NumberFormat` class in conjunction with the `FieldPosition` class to provide a simple way of aligning numbers on different fields, such as the decimal sign, the percent sign, and so on. Table 15.3 lists the important methods in the `NumberFormat` class. #### Table 15.3. Methods in the NumberFormat class.
Method | Description
`format()` | Formats the given object into a string; overrides the `Format` class
`parseObject()` | Parses a string and creates an `Object` object
`parse()` | Parses a string and returns a `Number` object
`isParseIntegerOnly()` | Returns `true` if this parser stops when it hits a decimal point
`setParseIntegerOnly()` | Specifies whether the parser should read past the decimal point
`getInstance()` | Gets the default `NumberFormat` object for a locale
`getNumberInstance()` | Gets a general-purpose formatter for a locale
`getCurrencyInstance()` | Gets a currency formatter for a locale
`getPercentInstance()` | Gets a percent formatter for a locale
`getAvailableLocales()` | Returns all locales supported by the `NumberFormat` class
`isGroupingUsed()` | Returns `true` if grouping is used; example: The number `12345` would be `12,345` with grouping turned on
`setGroupingUsed()` | Specifies whether grouping should be used
`get/setMaximumIntegerDigits()` | Gets or sets the maximum number of integer digits to be used
`get/setMinimumIntegerDigits()` | Gets or sets the minimum number of integer digits to be used
`get/setMaximumFractionDigits()` | Gets or sets the maximum number of fraction digits to be used
`get/setMinimumFractionDigits()` | Gets or sets the minimum number of fraction digits to be used
Listing 15.1 shows how to use the `NumberFormat` class. You can find this code on the CD-ROM that accompanies this book. #### Listing 15.1. NumberFormatExample.java: A sample NumberFormat program.
    import java.text.*;
    import java.util.*;

    public class NumberFormatExample {

        public static void main( String args[] ) {

            try {
                double averages[] = { 0.456, 0.78, 0.3, 1.0, .25, .345 };
                String spaces = "                    ";

                System.out.println( "Available Locales for NumberFormat" );
                Locale availLocs[] = NumberFormat.getAvailableLocales();

                for( int i=0; i<availLocs.length; i++ ) {
                    System.out.println( "\t" + availLocs[i].getDisplayName() );
                    DecimalFormat fmt = (DecimalFormat) NumberFormat.getInstance( availLocs[i] );
                    String pattern = fmt.toPattern();
                    int len = pattern.length();
                    String newPattern = pattern.substring(0,len-4) + ".000";
                    fmt = new DecimalFormat( newPattern, new DecimalFormatSymbols(availLocs[i]) );
                    FieldPosition status = new FieldPosition( NumberFormat.FRACTION_FIELD );

                    for( int j=0; j<averages.length; j++ ) {
                        StringBuffer sb = new StringBuffer();
                        fmt.format( averages[j], sb, status );
                        System.out.println( spaces.substring(0, 20-status.getEndIndex()) + sb.toString() );
                    }

                    System.out.println("");
                }

            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
The output from this program looks like this:
    Available Locales for NumberFormat
        Belorussian (Belarus)
                   0,456
                   0,780
                   0,300
                   1,000
                   0,250
                   0,345

        Bulgarian (Bulgaria)
                   0,456
                   0,780
                   0,300
                   1,000
                   0,250
                   0,345
As you can see, the same code block produces different output strings depending on the locale. ### The DateFormat Class The `java.text.DateFormat` class is an abstract base class for all classes that parse and format dates and times in a localized manner. Like the `NumberFormat` class, the `DateFormat` class also provides a number of static functions that retrieve default formats for dates and times:
    DateFormat fmt = DateFormat.getDateInstance();
    DateFormat fmt = DateFormat.getDateInstance( DateFormat.SHORT );
    DateFormat fmt = DateFormat.getDateInstance( DateFormat.LONG, Locale.FRENCH );
    DateFormat fmt = DateFormat.getDateTimeInstance();
    DateFormat fmt = DateFormat.getDateInstance( DateFormat.LONG, DateFormat.SHORT );
    DateFormat fmt = DateFormat.getDateInstance( DateFormat.LONG, DateFormat.SHORT, Locale.US );
    DateFormat fmt = DateFormat.getTimeInstance();
    DateFormat fmt = DateFormat.getTimeInstance( DateFormat.SHORT );
    DateFormat fmt = DateFormat.getTimeInstance( DateFormat.LONG, Locale.FRENCH );
The `getDateFormat()` method returns the default date format for the default locale. The second version of this method, `getDateFormat(DateFormat.SHORT)`, returns the default date format for the given style for the default locale. The style attribute can be `SHORT`, `MEDIUM`, `LONG`, `FULL`, or `DEFAULT`. The third form of this method, `getDateFormat(DateFormat.LONG, Locale.FRENCH)`, returns the default date format for the given style in the given locale. The `getDateTimeFormat()` method returns the default date and time format for the default locale. The `getDateFormat(DateFormat.LONG, DateFormat.SHORT)` method returns the default date and time format for the given date and time formatting styles for the default locale. The next version of this method, `getDateFormat(DateFormat.LONG, DateFormat.SHORT, Locale.US)`, returns the default date and time format for the given date and time formatting styles for the given locale. The `getTimeFormat()` method returns the default time format for the default locale. The second version of this method, `getTimeFormat(DateFormat.SHORT)`, returns the default time format with the given style for the default locale. The style attribute for the time methods can have the same value as the style attribute for the date methods: `SHORT`, `LONG`, `FULL`, or `DEFAULT`. The third version of this method, `getTimeFormat(DateFormat.LONG, Locale.FRENCH)`, returns the default time format with the given style in the given locale. Here are some examples of the `DateFormat` attributes:
Attribute | Example
`SHORT` | `4/2/97` (completely numeric)
`MEDIUM` | `Apr 2, 1997`
`LONG` | `April 2, 1997`
`FULL` | `Wednesday, April 2, 1997 AD`
The object returned by these functions is usually of type `java.text.SimpleDateFormat`, which provides a concrete implementation of the abstract `DateFormat` class. Once you have retrieved a `DateFormat` object, you can use it to properly format a date or parse a string to find a date. These field constants are also available in the `DateFormat` class: `AM_PM_FIELD`, `DATE_FIELD`, and `DAY_OF_WEEK_FIELD`, among others. These field constants are used with the `FieldPosition` class to help properly align strings formatted with a `DateFormat` object. For example, if the formatted date is `Friday, April 4, 1997`, and the `FieldPosition` class is using the `DAY_OF_WEEK_FIELD` for alignment, the `DateFormat` object determines that the day of the week begins in `string[0]` and ends in `string[5]`; the `FieldPosition` object sets its `getBeginIndex()` and `getEndIndex()` methods to `0` and `5`, respectively. Table 15.4 lists the important methods in the `DateFormat` class. #### Table 15.4. Methods in the DateFormat class.
Method | Description
`format()` | Formats the given object into a string; overrides class `Format`
`parseObject()` | Parses a string and creates an `Object` object
`parse()` | Parses a string and returns a `Number` object
`getTimeInstance()` | Gets a time formatter for a locale
`getDateInstance()` | Gets a date formatter for a locale
`getDateTimeInstance()` | Gets a date and time formatter for a locale
`getInstance()` | Gets the default `DateFormat` for a locale
`getAvailableLocales()` | Gets all the locales supported by `DateFormat`
`get/setCalendar()` | Gets or sets the `Calendar` to be used by the `DateFormat` object
`get/setNumberFormat()` | Gets or sets the `NumberFormat` to be used by the `DateFormat` object
`get/setTimeZone()` | Gets or sets the `TimeZone` object for the calendar of this `DateFormat` object
`is/setLenient()` | Gets or sets whether this object uses lenient parsing; if lenient parsing is used, the parser will use heuristics to interpret the input; if lenient parsing is off, the parser will use strict parsing rules
Listing 15.2 shows some examples of the use of the `DateFormat` class. You can find this code on the accompanying CD-ROM. #### Listing 15.2. DateFormatExample.java: A sample DateFormat program.
    import java.text.*;
    import java.util.*;

    public class DateFormatExample {

        public static void main( String args[] ) {

            try {
                System.out.println( "Available Locales for DateFormat" );
                Locale availLocs[] = DateFormat.getAvailableLocales();
                for( int i=0; i<availLocs.length; i++ ) {
                    System.out.println( "\t" + availLocs[i].getDisplayName() );
                }

                SimpleDateFormat fmt =
                    new SimpleDateFormat( "'It is now' H:mm 'on' EEEE',' MMMM d',' yyyy" );
                FieldPosition status = new FieldPosition( DateFormat.DAY_OF_WEEK_FIELD );

                // format today's date
                Date today = new Date();
                StringBuffer sbToday = new StringBuffer();
                fmt.format( today, sbToday, status );
                int todayOffset = status.getEndIndex();

                // format tomorrow's date
                Date tomorrow = new Date( today.getTime() + 86400000 );
                StringBuffer sbTmw = new StringBuffer();
                fmt.format( tomorrow, sbTmw, status );
                int tmwOffset = status.getEndIndex();

                // format tomorrow+1
                Date tp1 = new Date( tomorrow.getTime() + 86400000 );
                StringBuffer sbTp1 = new StringBuffer();
                fmt.format( tp1, sbTp1, status );
                int tp1Offset = status.getEndIndex();

                // align all dates in column 40 of the screen using the DAY_OF_WEEK
                String spaces = "                    ";
                System.out.println("Dates");
                System.out.print( spaces.substring(0, 40-todayOffset) );
                System.out.println( sbToday.toString() );
                System.out.print( spaces.substring(0, 40-tmwOffset) );
                System.out.println( sbTmw.toString() );
                System.out.print( spaces.substring(0, 40-tp1Offset) );
                System.out.println( sbTp1.toString() );

                // parse a date from a string (reverse-formatting)
                String dateStr = "It is now 16:26 on Tuesday, February 4, 1997";
                Date date = fmt.parse( dateStr );
                System.out.println("Parsing");
                System.out.println( "\t" + date.toString() );

            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
The output of this program looks something like the following. The actual output from your system may vary depending on which `locales` are installed.
    Available Locales for DateFormat
        Arabic (Egypt)
        Belorussian (Belarus)
        Bulgarian (Bulgaria)
        Catalan (Spain)
        Czech (Czech Republic)
        Danish (Denmark)
        German (Germany)
        <more locales here>
    Dates
                   It is now 14:37 on Monday, April 7, 1997
                  It is now 14:37 on Tuesday, April 8, 1997
                It is now 14:37 on Wednesday, April 9, 1997
    Parsing
        Tue Feb 04 16:26:00 PST 1997
As you can see, the dates are aligned on the day of the week, and the string we made up is parsed into a `java.util.Date` object, just as we expected! ### The ChoiceFormat Class The `java.text.ChoiceFormat` class is a formatter that allows you to attach a pattern to a range of numbers (of type `double`). Its most common use is in conjunction with the `MessageFormat` class to handle cases with plurals (for example, "zero objects," "one object," and "many objects"), although it is certainly not limited to such use. A `ChoiceFormat` object is specified with a list of ascending numbers (`double`s) that determine the limits to be used. A number `X` falls into a given interval between `list[j]` and `list[j+1]` if and only if `list[j] <= X < list[j+1]`. If `X < list[0]`, then `list[0]` is used. Similarly, if `X > list[N-1]` (where there are `N` items in the list), `list[N-1]` is used. For example, if the list is {`1.0, 2.0, 3.0}`, then the following are true:
    0.5 maps to list[0] because 0.5 is less than 1.0 (list[0])
    1.5 also maps to list[0] because 1.0 <= 1.5 < 2.0
    2.5 maps to list[1] because 2.0 <= 2.5 < 3.0
    3.5 maps to list[2] because 3.0 <= 3.5
Along with the list of numbers that determine the limits is a list of objects. The list of objects has the same number of items as the list of limits and contains the items to be used as the formats for the corresponding limits. Although it sounds confusing, it is really very simple. Table 15.5 lists the important methods in the `ChoiceFormat` class. #### Table 15.5. Methods in the ChoiceFormat class.
Method | Description
`applyPattern()` | Sets the pattern for a `ChoiceFormat` object
`toPattern()` | Gets the pattern for a `ChoiceFormat` object
`setChoices()` | Sets the limits for a `ChoiceFormat` object
`getLimits()` | Gets the limits for a `ChoiceFormat` object
`getFormats()` | Gets the formats for a `ChoiceFormat` object
`format()` | Formats an object into a string
`parse()` | Parses a string and creates a `Number` object
`nextDouble()` | Finds the next double greater than or equal to a given value
`previousDouble()` | Finds the next double less than or equal to a given value
Listing 15.3 shows a simple example. You can also find this example on the CD-ROM that accompanies this book. #### Listing 15.3. SimpleChoiceFormatExample.java: A sample ChoiceFormat program.
    import java.text.*;
    import java.util.*;

    public class SimpleChoiceFormatExample {

        public static void main( String args[] ) {

            try {
                double[] limits = { 1, 4, 7, 10 };
                String[] seasons = { "Winter", "Spring", "Summer", "Autumn" };
                ChoiceFormat fmt = new ChoiceFormat( limits, seasons );

                for (int i = 1; i <= 12; ++i) {
                    System.out.println( "Month number " + i + " falls in " + fmt.format(i) );
                }

            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
This program prints the following output:
    Month number 1 falls in Winter
    Month number 2 falls in Winter
    Month number 3 falls in Winter
    Month number 4 falls in Spring
    Month number 5 falls in Spring
    Month number 6 falls in Spring
    Month number 7 falls in Summer
    Month number 8 falls in Summer
    Month number 9 falls in Summer
    Month number 10 falls in Autumn
    Month number 11 falls in Autumn
    Month number 12 falls in Autumn
By letting the formatter do the work, you save the effort of doing many comparisons to determine the season in which a particular month falls. The next section makes it clearer why the `ChoiceFormat` class is useful. ### The MessageFormat Class The `java.text.MessageFormat` class provides a simple way to get concatenated messages in a language-neutral (localized) way. A `MessageFormat` object has a specified pattern and, optionally, a list of `Format` objects associated with it. A `MessageFormat` object's specification is of the following form: `MessageFormat fmt = new MessageFormat( "The incoming fax from {0} has a total of {1} pages.");` The string passed in is called the pattern. The pattern is used by the `MessageFormat` object when formatting and is subject to the following set of rules: 1. The syntax `{N} (0 <= N <= 9)` indicates that the `N`th argument in the list of arguments passed to `format()` should be formatted using format `N` (which is specified by calling `setFormat()` ). An example pattern for this rule is `"The person's name is {0}"`. 2. The optional syntax `{N, <`elementType`>}` indicates that the `N`th argument should be formatted with the `N`th format, subject to the constraints set by `<`elementType`>`. The valid element types are `time`, `date`, `number`, and `choice`. If an element type is provided, the formatter assumes that the argument is of the type indicated and throws an exception if it is not. If no element type is provided (as in rule 1 just listed), it is assumed to be a string. A sample pattern is `"It is now {0,time} on {1,date}, and I am {3,number} years old."` The formatter assumes that argument `0` represents a time, argument `1` represents a date, and argument `3` represents a number. 3. The elementType can have a style. Valid styles for dates and times are `SHORT`, `MEDIUM`, `LONG`, and `FULL`. Valid styles for numbers are `currency`, `percent`, and `integer`. Table 15.6 lists the important methods found in the `MessageFormat` class. #### Table 15.6. Methods in the MessageFormat class.
Method | Description
`get/setLocale()` | Gets or sets the locale for the `MessageFormat` object
`applyPattern()` | Sets the pattern for the object
`toPattern()` | Gets the pattern for the object
`setFormats()` | Sets all the formats to be used by the object
`setFormat()` | Sets an individual format to be used by the object
`getFormats()` | Gets all the formats for the object
`format()` | Formats an object and returns a string
`parse()` | Parses a string and returns an array of objects
`parseObject()` | Parses a string and returns the next object
Listing 15.4 shows a simple example of how to use `MessageFormat` objects. You can find the code on the CD-ROM that accompanies this book. #### Listing 15.4. MessageFormatExample.java: A sample MessageFormat program.
    import java.text.*;
    import java.util.*;

    public class MessageFormatExample {

        public static void main( String args[] ) {
            try {
                MessageFormat fmt = new MessageFormat( "The fax from {1} has {0} pages." );
                Object fmtArgs[] = { new Long(5), "Joe Schmo" };
                System.out.println( fmt.toPattern() + "; " + fmt.format(fmtArgs) );

            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
The output of this short code snippet is as follows:
    The fax from {1} has {0} pages.; The fax from Joe Schmo has 5 pages.
Because we did not specify any formats for the particular arguments, the `MessageFormat` object simply substituted `fmtArgs[0]` for `{0}` and `fmtArgs[1]` for `{1}`. Listing 15.5 uses a `ChoiceFormat` object in conjunction with a `MessageFormat` object to create a formatted string. You can find this file on the CD-ROM that accompanies this book. #### Listing 15.5. MessageFormatExample2.java: A more complex MessageFormat program.
    import java.text.*;
    public class MessageFormatExample2 {
        public static void main( String args[] ) {
            try {
                // the limits to use for ChoiceFormat
                double[] limits = { 0, 1, 2};

                // strings for 0, 1, and >1 pages
                String[] pages = { "no pages", "one page", "{1,number} pages" };

                // a ChoiceFormat object based on the given limits
                ChoiceFormat chFmt = new ChoiceFormat( limits, pages );

                // senders of faxes
                String[] senders = { "Joe", "Fred", "Mary" };

                // formats to use for the arguments in the MessageFormat
                Format[] testFormats = { null, chFmt };

                MessageFormat messFmt =
                        new MessageFormat( "The fax from {0} has {1}." );

                messFmt.setFormat( 1, chFmt );

                for (int i = 0; i < 3; ++i) {

                    // an array of Objects to pass to the MessageFormat
                    Object[] testArgs = { senders[i], new Long(i) };

                    // format the arguments and print out the resulting string
                    System.out.println( messFmt.toPattern() + " -> " +
                    messFmt.format(testArgs) );
                }

            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
The output of this code is as follows: `The fax from {0} has {1,choice,0.0#no pages|1.0#one page|2.0#{1,number} pages}. -> The fax from Joe has no pages. The fax from {0} has {1,choice,0.0#no pages|1.0#one page|2.0#{1,number} pages}. -> The fax from Fred has one page. The fax from {0} has {1,choice,0.0#no pages|1.0#one page|2.0#{1,number} pages}. -> The fax from Mary has 2 pages.` ## Collators The `java.text.Collator` class is an abstract class that provides a common interface for the language-sensitive comparison of strings, text searches, and alphabetical sorting. The `Collator` class hides from the developer the nuances of any individual locale so that you can use the same code in any local setting. Table 15.7 lists the important methods used in the `Collator` class. #### Table 15.7. Methods in the Collator class.
Method | Description
`getInstance()` | Gets a `Collator` for a locale
`compare()` | Compares two strings according to the rules for a `Collator`
`getCollationKey()` | Transforms a string into a set of bits that can be
compared (bitwise) to other `CollationKeys` from the same `Collator` |
`get/setStrength()` | Gets or sets the strength of the `Collator`; legal values are `PRIMARY`, `SECONDARY`, and `TERTIARY`
`get/setDecomposition()` | Gets or sets the decomposition mode for the `Collator`; legal values are `NO_DECOMPOSITION`, `CANONICAL_DECOMPOSITION`, and `FULL_DECOMPOSITION`
`getAvailableLocales()` | Gets a list of locales supported by the `Collator` class
### Basic Collation Languages throughout the world differ with respect to both the characters they use and the way they treat those characters when comparing and sorting them. There are four areas that apply to correct string comparison and sorting: ordering characters, grouping characters, expanding characters, and ignoring characters. The following sections explain these areas in more detail. NOTE: Java uses the Unicode representation of strings instead of using multibyte representation. #### Ordering There are three types of orderings: primary, secondary, and tertiary. When you compare two strings, you first do so by comparing characters at the same positions in each string. The first difference in this primary ordering determines the order of the strings, regardless of the remaining characters. For example, `"deed"` is less than `"definition"`. The first primary difference is in the third character, where `"e"` is less than `"f"`. In languages such as English and German, the primary ordering is in base letters. For example, `"a"` is different than `"b"` but is not different from `"A"`. Remember that Java uses the Unicode representation of characters, which is actually a superset of the ASCII character set. Punctuation such as spaces and quotation marks precede numbers, which precede letters in the ordering. If the primary ordering shows that the strings are identical, the comparison then moves to secondary ordering. In English, the secondary ordering is in the case of the characters. Thus, `"apple"` is less than `"Apple"`. In this example, the primary ordering of the characters is the same, but the first characters have a secondary difference. In languages such as Czech and French, the secondary ordering is in accents. Finally, if all secondary orderings are also identical, the comparison moves to tertiary ordering. For example, in Czech, the secondary ordering is based on accent marks (`"e"` is less than `"è"`) and the tertiary ordering is based on case. #### Groups of Characters Some languages stipulate that a certain sequence of characters should be treated as a single character. For example, in Spanish, `"c"` is less than `"ch"` which is less than `"d"`, because `"ch"` is treated as a base character in itself and is placed in the ordering between the characters `"c"` and `"d"`. Note that the language normally determines when a set of characters should be treated as a single character (as in the Spanish `"ch"` example), but the programmer can also insert his or her own grouped characters, as we will see later in this chapter with the `RuleBasedCollator` example. #### Expanding Characters Some languages stipulate that a single character be treated as a sequence of characters. For example, in German, `"s"` is less than `"ß"` which is less than `"t"`. In this case, the character `"ß"` is treated as though it is the characters `"ss"` for ordering purposes. #### Ignorable Characters Most languages have certain characters that can be ignored when you compare and sort strings. That is, some characters are not significant unless there are no differences in the remainder of the string. In English, one such character is the dash (-). For example, `"foobar"` is less than `"foo-bar"` which is less than `"foobars"`. For any given collation operation, you can specify a strength (`PRIMARY`, `SECONDARY`, or `TERTIARY`). The strength of a collation is the highest level at which comparisons are made; differences in levels beyond the specified strength are ignored. For example, if you set the strength of a collation to `SECONDARY`, any characters that have tertiary differences are reported as being equal. The strength of a collator can be set using `Collator.setStrength().` ### Comparing Strings Using the CollationKey Class It is simple to compare two strings using the `Collator.compare()` method. However, the comparison algorithm used by `Collator.compare()` is very complex. If you are sorting long lists of strings, the operation may be very slow because `compare()` repetitively compares the same strings. As an alternative, you can use the `java.text.CollationKey` class, which is a key representing a given string in a collation. You can generate `CollationKey` objects for all your strings and cache them for use in all comparisons instead of using the strings themselves. Because they are bit-ordered, `CollationKey` objects allow you to do bitwise comparisons; in addition, once the keys are generated, comparisons are faster than direct comparisons of the two strings. Table 15.8 lists the important methods in the `CollationKey` class. #### Table 15.8. Methods in the CollationKey class.
Method | Description
`compareTo()` | Compares the `CollationKey` to another `CollationKey` object from the same `Collator`
`getSourceString()` | Returns a reference to the actual string which maps to the `CollationKey` under the given `Collator`
`toByteArray()` | Converts the `CollationKey` to a sequence of bits; used for bitwise comparison of keys
TIP: Here is a good rule of thumb when deciding whether to use direct comparison or comparisons using `CollationKeys`: If you compare the strings more than once, use `CollationKeys`; if you compare the strings only once, use the direct comparison. Also note that you cannot compare `CollationKey` objects from different `Collator` objects. ### Decomposition Modes Decomposing characters is another way of saying preparing characters for sorting. Decomposing characters involves the four attributes discussed earlier: ordering, grouping, expanding, and ignoring characters. Decomposing characters is the process of actually applying a language's rules to a given set of characters. When you are dealing with Unicode characters, there are three decomposition modes to consider: No decomposition. Accented characters are not sorted correctly. You should use this mode only if you can guarantee that the source text has absolutely no accented characters. This mode is not recommended. Canonical decompositio Characters that are canonical variants under Unicode 2.0 (such as accents) are decomposed when collated. This is the default mode, and must be used if accents and other canonical variants are to be correctly collated. Canonical variants are letters such as `"e"` and `"è"`, which differ only in that one has an accent mark. Full decompositio Both canonical variants and compatibility variants are decomposed. A compatibility variant is a character that has a special format to be sorted with its normalized form, such as half-width and full-width ASCII and Katakana characters. NOTE: Although `NO_DECOMPOSITION` is the fastest decomposition mode, it is not correct if accented characters appear. For correctness, it is recommended that you use either `CANONICAL_DECOMPOSITION` or `FULL DECOMPOSITION`. To set the decomposition mode for a collator, use the `setDecomposition()` method. ### The RuleBasedCollator Class The `java.text.RuleBasedCollator` class is a concrete class that provides a very simple `Collator` implementation using tables (hence the name). This class uses a set of collation rules to determine the result of comparisons. The rules can take on three different forms: `<`modifier`>` `<`relation`> <`text argument`>` `<`reset`> <`text argument`>` The definitions for each component of these rules are as follows:
Component | Description
modifier | There is only one modifier, `"@"`, which indicates that allUcondary cdeg.[partialdiff]ferences are ordered in reverse.
relation | There are four relations. The first three--`"<"`, `";"`, and `","`--mean "greater than" for primary, secondary, and tertiary differences, respectively. The fourth relation is `"="`, which means "equal."
reset | There is only one reset, `"&"`, which specifies that the next rule follows the position in which the reset argument would be sorted. The reset argument follows the `"&"`, as in `"a < b & a < c"`. In this case, `"a"` is the reset argument and `"c"` is placed in the list after text argument `"a"`, yielding `"a < c < b"`.
Any sequence of characters, excluding "special" characters (those characters contained in whitespace or used as modifiers, relations, and resets). To use a special character within a string, place it in single quotation marks (for example, ``&'`).
Here are some simple examples of rules:
Example | Comment
`a < b < c` |
`a < c & a < b` | Equivalent to `a < b < c`
`a < b & b < c` | Equivalent to `a < b < c`
`- < a < b < c` | ``-'` can be ignored because it preceded the first relation
Here are some rules that create errors (and thus throw `FormatException` exceptions):
Example | Comment
`a < b & c < d` | `c` has not been put into the rules and thus cannot be used as a reset argument
`w < x y < z` | There is no relation between `x` and `y`
`w <, x` | There is no text argument between the relations `<` and `,`
NOTE: The `RuleBasedCollator` class has a few restrictions for efficiency: It uses the secondary ordering rules for the French language for the entire object Any unmentioned Unicode characters come at the end of the collation order Private-use characters (that is, Unicode characters `0xe800` through `0xf8ff`) are all treated as identical These restrictions should be important only to advanced users of the `RuleBasedCollator` class; they are mentioned here for completeness. Table 15.9 lists the important methods in the `RuleBasedCollator` class. #### Table 15.9. Methods in the RuleBasedCollator class.
Method | Description
`getRules()` | Returns a string representation of the rules for the object
`getCollationElementIterator()` | Gets a `CollationElementIterator` object for a given string under the `RuleBasedCollator`
`compare()` | Compares two strings based on the rules in the `RuleBasedCollator`
`getCollationKey()` | Gets a `CollationKey` for a given string under the rules for the `RuleBasedCollator`
To explain the use of the `RuleBasedCollator` class, we need a useful example. Many companies assign a "grade level" to their employees; this grade level consists of a letter followed by a number. Our fictional company uses this ordering system: A1, A2, B1, A3, B2, and B3. Additionally, for the A grades, a lowercase a represents someone who is in training to become that given grade, but is not quite there. Thus, we define a tertiary difference between a lowercase a and an uppercase A grade. Listing 15.6 augments the basic United States `RuleBasedCollator` rules with our rules and sorts a list of grade levels using these rules (the code is also located on the CD-ROM that accompanies this book). Note that each of the grade levels is considered by the `Collator` class to be a single group character, not a string of multiple characters. #### Listing 15.6. RuleBasedCollatorExample.java: A sample RuleBasedCollator program.
    import java.text.*;
    import java.util.*;

    public class RuleBasedCollatorExample {

        public static void main( String args[] ) {

            try {
                // make a collation with rules from the US
                RuleBasedCollator collUS = (RuleBasedCollator) Collator.getInstance(Locale.US);

                // provide the ordering for levels
                // no need to do the C's because they will have a primary difference

                String newRules = "< B1 < a1, A1 < a2, A2 < a3, A3 < B2 < B3";
                String sampleInput[] = { "B1", "a1", "A3", "A1", "B3", "B2", "a2", "A2", "B1" };

                RuleBasedCollator newColl = new RuleBasedCollator( newRules );
                newColl.setStrength( Collator.TERTIARY );

                CollationKey keys[] = new CollationKey[sampleInput.length];

                // print the original list
                for( int i=0; i<sampleInput.length; i++ ) {
                    System.out.print( sampleInput[i] + " " );
                    keys[i] = newColl.getCollationKey( sampleInput[i] );
                }
                System.out.println("");

            // sort the list
                for( int i=0; i<sampleInput.length-1; i++ ) {
                    for (int j=i+1; j<sampleInput.length; j++ ) {

                        if( keys[i].compareTo(keys[j]) > 0 ) {
                            CollationKey tmpkey = keys[i];
                            keys[i] = keys[j];
                            keys[j] = tmpkey;

                            String tmp = sampleInput[i];
                            sampleInput[i] = sampleInput[j];
                            sampleInput[j] = tmp;
                        }
                    }
                }

                // print the sorted list
                for( int i=0; i<sampleInput.length; i++ ) {
                    System.out.print( sampleInput[i] + " " );
                }
                System.out.println("");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
The output of the program is as follows:
    B1 a1 A3 A1 B3 B2 a2 A2 B1
    B1 B1 a1 A1 a2 A2 A3 B2 B3
## Iterators There are two classes of iterators in the `java.text` package: the `CollationElementIterator`, which is used to iterate through each character of an international string; and `StringCharacterIterator`, which implements the `CharacterIterator` interface and is used for bidirectional iteration over a given string. ### The CollationElementIterator Class The `java.text.CollationElementIterator` class allows you to go through each character of an international string and return the ordering priority of the positioned character. The "key" of a character is an integer that comprises the primary, secondary, and tertiary orders for the character. The primary order is of type `short` (16 bits); the secondary and tertiary orders are of type `byte` (8 bits). This integer is formed internally to the iterator based on other characters in the string. NOTE: Java strictly defines the size and sign of the types `short` and `byte`. To ensure that the key value for a given character is correct, the function `primaryOrder()` returns an `int` (not a `short`) because we need a true, unsigned, 16-bit number. Similarly, the functions `secondaryOrder()` and `tertiaryOrder()` return values of type `short` (and not `byte`). Table 15.10 lists the important methods in the `CollationElementIterator` class. #### Table 15.10. Methods in the CollationElementIterator class.
Method | Description
`reset()` | Resets the iterator's marker to the beginning of the string
`next()` | Gets the next character from the iterator
`primaryOrder()` | Gets the primary order of the given character
`secondaryOrder()` | Gets the secondary order of the given character
`tertiaryOrder()` | Gets the tertiary order of the given character
Listing 15.7 provides an example using the `CollationElementIterator` class. It is similar to Listing 15.6 in that it uses the same `RuleBasedCollator` object. As usual, you can find this code on the CD-ROM that accompanies this book. #### Listing 15.7. CollationElementIteratorExample.java: A sample CollationElementIterator program.
    import java.text.*;
    import java.util.*;

    public class CollationElementIteratorExample {

        public static void main( String args[] ) {
            try {
                // make a collation with rules from the US
                RuleBasedCollator collUS = (RuleBasedCollator) Collator.getInstance(Locale.US);

                // provide the ordering for levels
                String newRules = "< B1 < a1, A1 < a2, A2 < a3, A3 < B2 < B3";

                // sample list
                String sampleInput[] = { "B1a1A3A1B3B2a2A2B1" };
                RuleBasedCollator newColl = new RuleBasedCollator( collUS.getRules() + newRules );

                // sort the list
                for( int i=0; i<sampleInput.length; i++ ) {
                    CollationElementIterator iter =
                    newColl.getCollationElementIterator( sampleInput[i] );
                    int next;
                    int count = 0;
                    while( (next = iter.next()) != CollationElementIterator.NULLORDER ) {
                        int pri = CollationElementIterator.primaryOrder( next );
                        int sec = CollationElementIterator.secondaryOrder( next );
                        int ter = CollationElementIterator.tertiaryOrder( next );
                        System.out.println( "orderings for character " + count +
                                            " of string " + i + " are " +
                                            pri + "," + sec + "," + ter );
                        count++;
                    }
                    System.out.println("");
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
The output of this program is as follows:
    orderings for character 0 of string 0 are 96,0,0
    orderings for character 1 of string 0 are 97,0,0
    orderings for character 2 of string 0 are 99,0,1
    orderings for character 3 of string 0 are 97,0,1
    orderings for character 4 of string 0 are 101,0,0
    orderings for character 5 of string 0 are 100,0,0
    orderings for character 6 of string 0 are 98,0,0
    orderings for character 7 of string 0 are 98,0,1
    orderings for character 8 of string 0 are 96,0,0
Notice that characters `6` and `7` have the same primary value (the value `98`); this is because `a2` and `A2` are the same as far as primary comparisons go. However, there is a case difference between `a2` and `A2` as our rules are written; thus, there is a tertiary difference (the third value; character `6` has value `0` and character `7` has value `1`). ### The StringCharacterIterator Class The `StringCharacterIterator` class implements the `CharacterIterator` interface for strings. The `CharacterIterator` interface specifies a protocol for the bidirectional iteration over text, on a range of character positions bounded by `startIndex` and `endIndex-1`. Table 15.11 shows four methods from this class which allow you to manipulate and query the indices of this iterator. #### Table 15.11. Methods from the StringCharacterIterator class.
Method | Description
`startIndex()` | Retrieves the starting index for the given iterator
`endIndex()` | Retrieves the ending index
`getIndex()` | Retrieves the index of the character currently being used by the iterator
`setIndex()` | Changes the current index
Three methods allow you to retrieve the actual characters from the text stored in the iterator:
Method | Description
`current()` | Returns the character at the current index
`previous()` | Decrements the index by `1` and returns the character at the new index
`next()` | Increments the index by `1` and returns the character at the new index
With these seven methods, you can easily find a combination that enables you to iterate through any given string in any manner you choose. In Listing 15.9, we start at position 7 of a given string and iterate through the string in two directions: from position 7 to the end (forward) and from position 7 to the beginning (backward). Finally, we use the `java.text.BreakIterator` class to break up the string at each word. You can find this code on the CD-ROM that accompanies this book. #### Listing 15.9. StringCharacterIteratorExample.java: A sample StringCharacterIterator program.
    import java.text.*;
    import java.util.*;

    public class StringCharacterIteratorExample {

        public static void main( String args[] ) {
            try {
                String source = "This is my string!  Hey there.";
                StringCharacterIterator iter =
                    new StringCharacterIterator( source );

                int pos = 7;
                System.out.println( "Starting from position " + pos );
                System.out.print( "\t" );
                for (char c = iter.setIndex(pos);
                     c != CharacterIterator.DONE && iter.getIndex() <=
                          iter.getEndIndex();
                     c = iter.next()) {
                    System.out.print( c );
                }
                System.out.print( "\n" );
                System.out.print( "\t" );
                for (char c = iter.setIndex(pos);
                     c != CharacterIterator.DONE && iter.getIndex() >=
                          iter.getBeginIndex();
                     c = iter.previous()) {
                    System.out.print( c );
                }
                System.out.print( "\n" );

                BreakIterator bd = BreakIterator.getWordInstance();
                bd.setText( iter );

                int start = bd.first();
                for ( int end = bd.next();
                      end != BreakIterator.DONE;
                      start = end, end = bd.next() ) {
                    System.out.println( source.substring(start, end) );
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
The output of this program is as follows:
    Starting from position 7
            my string!  Hey there.
            si sihT
    This
    is
    my
    string
    !
    Hey
    there.
## Summary Writing inherently international code is not easy; many large software houses spend a lot of money in their efforts to make localization simpler. What the JDK 1.1 provides in the `java.text` package is a huge first step toward helping all Java developers create clean, international code. Clean, international code, in turn, will help make Java the global language we all want it to be. As the Web grows, we can no longer assume anything about the demographics of those who use what we publish. This chapter has given you some insight not only into how to use the package, but also into what it means to make a program international and localizable. As a developer, you must shift your thought processes when you write international code; as you do that, the ideas behind the `java.text` package will become clearer and you will fully understand how to use all the classes described in this chapter. Good luck! Viel Glück! Bonne chance! Chuk li ho wan! Kou-unn wo Negattemasu! Bueno suerte!
| ©Copyright, Macmillan Computer Publishing. All rights reserved.