12d2bb24f747c65578da13d5b13b82f0669690461Fredrik Roubert// © 2016 and later: Unicode, Inc. and others.
22d2bb24f747c65578da13d5b13b82f0669690461Fredrik Roubert// License & terms of use: http://www.unicode.org/copyright.html#License
37935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert/*
47935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert**********************************************************************
503f16b04d95bbaa98f702b69791b0de29ac75915Yoshito Umaoka* Copyright (c) 2004-2016, International Business Machines
67935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert* Corporation and others.  All Rights Reserved.
77935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert**********************************************************************
87935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert* Author: Alan Liu
97935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert* Created: April 6, 2004
107935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert* Since: ICU 3.0
117935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert**********************************************************************
127935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert*/
137935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertpackage com.ibm.icu.text;
147935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
157935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport java.io.IOException;
167935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport java.io.InvalidObjectException;
177935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport java.io.ObjectInputStream;
187935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport java.text.AttributedCharacterIterator;
197935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport java.text.AttributedCharacterIterator.Attribute;
207935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport java.text.AttributedString;
217935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport java.text.CharacterIterator;
227935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport java.text.ChoiceFormat;
237935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport java.text.FieldPosition;
247935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport java.text.Format;
257935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport java.text.ParseException;
267935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport java.text.ParsePosition;
277935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport java.util.ArrayList;
287935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport java.util.Date;
297935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport java.util.HashMap;
307935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport java.util.HashSet;
317935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport java.util.Iterator;
327935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport java.util.List;
337935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport java.util.Locale;
347935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport java.util.Map;
357935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport java.util.Set;
367935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
377935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport com.ibm.icu.impl.PatternProps;
387935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport com.ibm.icu.impl.Utility;
397935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport com.ibm.icu.text.MessagePattern.ArgType;
407935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport com.ibm.icu.text.MessagePattern.Part;
417935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport com.ibm.icu.text.PluralRules.FixedDecimal;
427935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport com.ibm.icu.text.PluralRules.PluralType;
437935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport com.ibm.icu.util.ICUUncheckedIOException;
447935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport com.ibm.icu.util.ULocale;
457935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport com.ibm.icu.util.ULocale.Category;
467935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
477935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert/**
487935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * {@icuenhanced java.text.MessageFormat}.{@icu _usage_}
497935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *
507935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * <p>MessageFormat prepares strings for display to users,
517935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * with optional arguments (variables/placeholders).
527935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * The arguments can occur in any order, which is necessary for translation
537935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * into languages with different grammars.
547935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *
557935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * <p>A MessageFormat is constructed from a <em>pattern</em> string
567935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * with arguments in {curly braces} which will be replaced by formatted values.
577935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *
587935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * <p><code>MessageFormat</code> differs from the other <code>Format</code>
597935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * classes in that you create a <code>MessageFormat</code> object with one
607935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * of its constructors (not with a <code>getInstance</code> style factory
617935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * method). Factory methods aren't necessary because <code>MessageFormat</code>
627935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * itself doesn't implement locale-specific behavior. Any locale-specific
637935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * behavior is defined by the pattern that you provide and the
647935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * subformats used for inserted arguments.
657935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *
667935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * <p>Arguments can be named (using identifiers) or numbered (using small ASCII-digit integers).
677935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * Some of the API methods work only with argument numbers and throw an exception
687935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * if the pattern has named arguments (see {@link #usesNamedArguments()}).
697935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *
707935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * <p>An argument might not specify any format type. In this case,
717935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * a Number value is formatted with a default (for the locale) NumberFormat,
727935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * a Date value is formatted with a default (for the locale) DateFormat,
737935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * and for any other value its toString() value is used.
747935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *
757935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * <p>An argument might specify a "simple" type for which the specified
767935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * Format object is created, cached and used.
777935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *
787935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * <p>An argument might have a "complex" type with nested MessageFormat sub-patterns.
797935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * During formatting, one of these sub-messages is selected according to the argument value
807935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * and recursively formatted.
817935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *
827935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * <p>After construction, a custom Format object can be set for
837935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * a top-level argument, overriding the default formatting and parsing behavior
847935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * for that argument.
857935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * However, custom formatting can be achieved more simply by writing
867935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * a typeless argument in the pattern string
877935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * and supplying it with a preformatted string value.
887935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *
897935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * <p>When formatting, MessageFormat takes a collection of argument values
907935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * and writes an output string.
917935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * The argument values may be passed as an array
927935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * (when the pattern contains only numbered arguments)
937935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * or as a Map (which works for both named and numbered arguments).
947935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *
957935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * <p>Each argument is matched with one of the input values by array index or map key
967935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * and formatted according to its pattern specification
977935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * (or using a custom Format object if one was set).
987935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * A numbered pattern argument is matched with a map key that contains that number
997935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * as an ASCII-decimal-digit string (without leading zero).
1007935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *
101bee65486a185907111f3be60992433e133ec0e32Scott Russell * <h3><a name="patterns">Patterns and Their Interpretation</a></h3>
1027935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *
1037935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * <code>MessageFormat</code> uses patterns of the following form:
1047935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * <blockquote><pre>
1057935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * message = messageText (argument messageText)*
1067935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * argument = noneArg | simpleArg | complexArg
1077935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * complexArg = choiceArg | pluralArg | selectArg | selectordinalArg
1087935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *
1097935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * noneArg = '{' argNameOrNumber '}'
1107935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * simpleArg = '{' argNameOrNumber ',' argType [',' argStyle] '}'
1117935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * choiceArg = '{' argNameOrNumber ',' "choice" ',' choiceStyle '}'
1127935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * pluralArg = '{' argNameOrNumber ',' "plural" ',' pluralStyle '}'
1137935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * selectArg = '{' argNameOrNumber ',' "select" ',' selectStyle '}'
1147935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * selectordinalArg = '{' argNameOrNumber ',' "selectordinal" ',' pluralStyle '}'
1157935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *
1167935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * choiceStyle: see {@link ChoiceFormat}
1177935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * pluralStyle: see {@link PluralFormat}
1187935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * selectStyle: see {@link SelectFormat}
1197935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *
1207935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * argNameOrNumber = argName | argNumber
1217935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * argName = [^[[:Pattern_Syntax:][:Pattern_White_Space:]]]+
1227935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * argNumber = '0' | ('1'..'9' ('0'..'9')*)
1237935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *
1247935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * argType = "number" | "date" | "time" | "spellout" | "ordinal" | "duration"
1257935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * argStyle = "short" | "medium" | "long" | "full" | "integer" | "currency" | "percent" | argStyleText
1267935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * </pre></blockquote>
1277935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *
1287935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * <ul>
1297935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *   <li>messageText can contain quoted literal strings including syntax characters.
1307935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *       A quoted literal string begins with an ASCII apostrophe and a syntax character
1317935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *       (usually a {curly brace}) and continues until the next single apostrophe.
1327935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *       A double ASCII apostrohpe inside or outside of a quoted string represents
1337935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *       one literal apostrophe.
1347935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *   <li>Quotable syntax characters are the {curly braces} in all messageText parts,
1357935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *       plus the '#' sign in a messageText immediately inside a pluralStyle,
1367935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *       and the '|' symbol in a messageText immediately inside a choiceStyle.
1377935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *   <li>See also {@link MessagePattern.ApostropheMode}
1387935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *   <li>In argStyleText, every single ASCII apostrophe begins and ends quoted literal text,
1397935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *       and unquoted {curly braces} must occur in matched pairs.
1407935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * </ul>
1417935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *
1427935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * <p>Recommendation: Use the real apostrophe (single quote) character \u2019 for
1437935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * human-readable text, and use the ASCII apostrophe (\u0027 ' )
1447935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * only in program syntax, like quoting in MessageFormat.
1457935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * See the annotations for U+0027 Apostrophe in The Unicode Standard.
1467935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *
1477935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * <p>The <code>choice</code> argument type is deprecated.
1487935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * Use <code>plural</code> arguments for proper plural selection,
1497935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * and <code>select</code> arguments for simple selection among a fixed set of choices.
1507935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *
1517935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * <p>The <code>argType</code> and <code>argStyle</code> values are used to create
1527935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * a <code>Format</code> instance for the format element. The following
1537935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * table shows how the values map to Format instances. Combinations not
1547935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * shown in the table are illegal. Any <code>argStyleText</code> must
1557935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * be a valid pattern string for the Format subclass used.
1567935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *
157bee65486a185907111f3be60992433e133ec0e32Scott Russell * <table border=1>
1587935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *    <tr>
1597935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *       <th>argType
1607935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *       <th>argStyle
1617935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *       <th>resulting Format object
1627935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *    <tr>
1637935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *       <td colspan=2><i>(none)</i>
1647935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *       <td><code>null</code>
1657935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *    <tr>
1667935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *       <td rowspan=5><code>number</code>
1677935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *       <td><i>(none)</i>
1687935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *       <td><code>NumberFormat.getInstance(getLocale())</code>
1697935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *    <tr>
1707935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *       <td><code>integer</code>
1717935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *       <td><code>NumberFormat.getIntegerInstance(getLocale())</code>
1727935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *    <tr>
1737935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *       <td><code>currency</code>
1747935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *       <td><code>NumberFormat.getCurrencyInstance(getLocale())</code>
1757935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *    <tr>
1767935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *       <td><code>percent</code>
1777935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *       <td><code>NumberFormat.getPercentInstance(getLocale())</code>
1787935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *    <tr>
1797935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *       <td><i>argStyleText</i>
1807935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *       <td><code>new DecimalFormat(argStyleText, new DecimalFormatSymbols(getLocale()))</code>
1817935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *    <tr>
1827935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *       <td rowspan=6><code>date</code>
1837935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *       <td><i>(none)</i>
1847935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *       <td><code>DateFormat.getDateInstance(DateFormat.DEFAULT, getLocale())</code>
1857935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *    <tr>
1867935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *       <td><code>short</code>
1877935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *       <td><code>DateFormat.getDateInstance(DateFormat.SHORT, getLocale())</code>
1887935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *    <tr>
1897935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *       <td><code>medium</code>
1907935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *       <td><code>DateFormat.getDateInstance(DateFormat.DEFAULT, getLocale())</code>
1917935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *    <tr>
1927935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *       <td><code>long</code>
1937935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *       <td><code>DateFormat.getDateInstance(DateFormat.LONG, getLocale())</code>
1947935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *    <tr>
1957935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *       <td><code>full</code>
1967935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *       <td><code>DateFormat.getDateInstance(DateFormat.FULL, getLocale())</code>
1977935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *    <tr>
1987935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *       <td><i>argStyleText</i>
199bee65486a185907111f3be60992433e133ec0e32Scott Russell *       <td><code>new SimpleDateFormat(argStyleText, getLocale())</code>
2007935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *    <tr>
2017935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *       <td rowspan=6><code>time</code>
2027935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *       <td><i>(none)</i>
2037935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *       <td><code>DateFormat.getTimeInstance(DateFormat.DEFAULT, getLocale())</code>
2047935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *    <tr>
2057935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *       <td><code>short</code>
2067935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *       <td><code>DateFormat.getTimeInstance(DateFormat.SHORT, getLocale())</code>
2077935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *    <tr>
2087935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *       <td><code>medium</code>
2097935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *       <td><code>DateFormat.getTimeInstance(DateFormat.DEFAULT, getLocale())</code>
2107935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *    <tr>
2117935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *       <td><code>long</code>
2127935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *       <td><code>DateFormat.getTimeInstance(DateFormat.LONG, getLocale())</code>
2137935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *    <tr>
2147935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *       <td><code>full</code>
2157935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *       <td><code>DateFormat.getTimeInstance(DateFormat.FULL, getLocale())</code>
2167935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *    <tr>
2177935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *       <td><i>argStyleText</i>
218bee65486a185907111f3be60992433e133ec0e32Scott Russell *       <td><code>new SimpleDateFormat(argStyleText, getLocale())</code>
2197935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *    <tr>
2207935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *       <td><code>spellout</code>
2217935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *       <td><i>argStyleText (optional)</i>
2227935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *       <td><code>new RuleBasedNumberFormat(getLocale(), RuleBasedNumberFormat.SPELLOUT)
223bee65486a185907111f3be60992433e133ec0e32Scott Russell *           <br>&nbsp;&nbsp;&nbsp;&nbsp;.setDefaultRuleset(argStyleText);</code>
2247935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *    <tr>
2257935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *       <td><code>ordinal</code>
2267935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *       <td><i>argStyleText (optional)</i>
2277935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *       <td><code>new RuleBasedNumberFormat(getLocale(), RuleBasedNumberFormat.ORDINAL)
228bee65486a185907111f3be60992433e133ec0e32Scott Russell *           <br>&nbsp;&nbsp;&nbsp;&nbsp;.setDefaultRuleset(argStyleText);</code>
2297935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *    <tr>
2307935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *       <td><code>duration</code>
2317935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *       <td><i>argStyleText (optional)</i>
2327935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *       <td><code>new RuleBasedNumberFormat(getLocale(), RuleBasedNumberFormat.DURATION)
233bee65486a185907111f3be60992433e133ec0e32Scott Russell *           <br>&nbsp;&nbsp;&nbsp;&nbsp;.setDefaultRuleset(argStyleText);</code>
2347935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * </table>
2357935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *
2367935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * <h4><a name="diffsjdk">Differences from java.text.MessageFormat</a></h4>
2377935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *
2387935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * <p>The ICU MessageFormat supports both named and numbered arguments,
2397935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * while the JDK MessageFormat only supports numbered arguments.
2407935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * Named arguments make patterns more readable.
2417935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *
2427935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * <p>ICU implements a more user-friendly apostrophe quoting syntax.
2437935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * In message text, an apostrophe only begins quoting literal text
2447935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * if it immediately precedes a syntax character (mostly {curly braces}).<br>
2457935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * In the JDK MessageFormat, an apostrophe always begins quoting,
2467935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * which requires common text like "don't" and "aujourd'hui"
2477935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * to be written with doubled apostrophes like "don''t" and "aujourd''hui".
2487935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * For more details see {@link MessagePattern.ApostropheMode}.
2497935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *
2507935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * <p>ICU does not create a ChoiceFormat object for a choiceArg, pluralArg or selectArg
2517935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * but rather handles such arguments itself.
2527935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * The JDK MessageFormat does create and use a ChoiceFormat object
2537935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * (<code>new ChoiceFormat(argStyleText)</code>).
2547935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * The JDK does not support plural and select arguments at all.
2557935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *
2567935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * <h4>Usage Information</h4>
2577935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *
2587935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * <p>Here are some examples of usage:
2597935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * <blockquote>
2607935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * <pre>
2617935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * Object[] arguments = {
2627935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *     7,
2637935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *     new Date(System.currentTimeMillis()),
2647935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *     "a disturbance in the Force"
2657935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * };
2667935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *
2677935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * String result = MessageFormat.format(
2687935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *     "At {1,time} on {1,date}, there was {2} on planet {0,number,integer}.",
2697935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *     arguments);
2707935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *
2717935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * <em>output</em>: At 12:30 PM on Jul 3, 2053, there was a disturbance
2727935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *           in the Force on planet 7.
2737935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *
2747935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * </pre>
2757935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * </blockquote>
2767935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * Typically, the message format will come from resources, and the
2777935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * arguments will be dynamically set at runtime.
2787935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *
2797935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * <p>Example 2:
2807935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * <blockquote>
2817935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * <pre>
2827935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * Object[] testArgs = { 3, "MyDisk" };
2837935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *
2847935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * MessageFormat form = new MessageFormat(
2857935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *     "The disk \"{1}\" contains {0} file(s).");
2867935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *
2877935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * System.out.println(form.format(testArgs));
2887935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *
2897935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * // output, with different testArgs
2907935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * <em>output</em>: The disk "MyDisk" contains 0 file(s).
2917935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * <em>output</em>: The disk "MyDisk" contains 1 file(s).
2927935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * <em>output</em>: The disk "MyDisk" contains 1,273 file(s).
2937935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * </pre>
2947935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * </blockquote>
2957935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *
2967935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * <p>For messages that include plural forms, you can use a plural argument:
2977935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * <pre>
2987935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * MessageFormat msgFmt = new MessageFormat(
2997935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *     "{num_files, plural, " +
3007935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *     "=0{There are no files on disk \"{disk_name}\".}" +
3017935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *     "=1{There is one file on disk \"{disk_name}\".}" +
3027935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *     "other{There are # files on disk \"{disk_name}\".}}",
3037935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *     ULocale.ENGLISH);
3047935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * Map args = new HashMap();
3057935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * args.put("num_files", 0);
3067935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * args.put("disk_name", "MyDisk");
3077935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * System.out.println(msgFmt.format(args));
3087935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * args.put("num_files", 3);
3097935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * System.out.println(msgFmt.format(args));
3102d2bb24f747c65578da13d5b13b82f0669690461Fredrik Roubert *
3117935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * <em>output</em>:
3127935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * There are no files on disk "MyDisk".
3137935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * There are 3 files on "MyDisk".
3147935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * </pre>
3157935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * See {@link PluralFormat} and {@link PluralRules} for details.
3167935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *
3177935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * <h4><a name="synchronization">Synchronization</a></h4>
3187935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *
3197935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * <p>MessageFormats are not synchronized.
3207935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * It is recommended to create separate format instances for each thread.
3217935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * If multiple threads access a format concurrently, it must be synchronized
3227935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * externally.
3237935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *
3247935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * @see          java.util.Locale
3257935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * @see          Format
3267935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * @see          NumberFormat
3277935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * @see          DecimalFormat
3287935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * @see          ChoiceFormat
3297935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * @see          PluralFormat
3307935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * @see          SelectFormat
3317935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * @author       Mark Davis
3327935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * @author       Markus Scherer
3337935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * @stable ICU 3.0
3347935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert */
3357935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertpublic class MessageFormat extends UFormat {
3367935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
3377935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    // Incremented by 1 for ICU 4.8's new format.
3387935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    static final long serialVersionUID = 7136212545847378652L;
3397935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
3407935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
3417935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Constructs a MessageFormat for the default <code>FORMAT</code> locale and the
3427935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * specified pattern.
3437935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Sets the locale and calls applyPattern(pattern).
3447935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *
3457935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param pattern the pattern for this message format
3467935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @exception IllegalArgumentException if the pattern is invalid
3477935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @see Category#FORMAT
3487935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @stable ICU 3.0
3497935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
3507935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public MessageFormat(String pattern) {
3517935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        this.ulocale = ULocale.getDefault(Category.FORMAT);
3527935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        applyPattern(pattern);
3537935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
3547935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
3557935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
3567935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Constructs a MessageFormat for the specified locale and
3577935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * pattern.
3587935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Sets the locale and calls applyPattern(pattern).
3597935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *
3607935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param pattern the pattern for this message format
3617935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param locale the locale for this message format
3627935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @exception IllegalArgumentException if the pattern is invalid
3637935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @stable ICU 3.0
3647935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
3657935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public MessageFormat(String pattern, Locale locale) {
3667935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        this(pattern, ULocale.forLocale(locale));
3677935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
3687935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
3697935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
3707935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Constructs a MessageFormat for the specified locale and
3717935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * pattern.
3727935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Sets the locale and calls applyPattern(pattern).
3737935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *
3747935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param pattern the pattern for this message format
3757935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param locale the locale for this message format
3767935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @exception IllegalArgumentException if the pattern is invalid
3777935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @stable ICU 3.2
3787935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
3797935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public MessageFormat(String pattern, ULocale locale) {
3807935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        this.ulocale = locale;
3817935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        applyPattern(pattern);
3827935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
3837935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
3847935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
3857935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Sets the locale to be used for creating argument Format objects.
3867935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * This affects subsequent calls to the {@link #applyPattern applyPattern}
3877935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * method as well as to the <code>format</code> and
3887935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * {@link #formatToCharacterIterator formatToCharacterIterator} methods.
3897935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *
3907935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param locale the locale to be used when creating or comparing subformats
3917935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @stable ICU 3.0
3927935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
3937935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public void setLocale(Locale locale) {
3947935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        setLocale(ULocale.forLocale(locale));
3957935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
3967935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
3977935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
3987935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Sets the locale to be used for creating argument Format objects.
3997935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * This affects subsequent calls to the {@link #applyPattern applyPattern}
4007935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * method as well as to the <code>format</code> and
4017935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * {@link #formatToCharacterIterator formatToCharacterIterator} methods.
4027935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *
4037935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param locale the locale to be used when creating or comparing subformats
4047935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @stable ICU 3.2
4057935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
4067935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public void setLocale(ULocale locale) {
4077935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        /* Save the pattern, and then reapply so that */
4087935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        /* we pick up any changes in locale specific */
4097935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        /* elements */
4107935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        String existingPattern = toPattern();                       /*ibm.3550*/
4117935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        this.ulocale = locale;
4127935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        // Invalidate all stock formatters. They are no longer valid since
4137935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        // the locale has changed.
4147935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        stockDateFormatter = null;
4157935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        stockNumberFormatter = null;
4167935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        pluralProvider = null;
4177935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        ordinalProvider = null;
4187935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        applyPattern(existingPattern);                              /*ibm.3550*/
4197935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
4207935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
4217935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
4227935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Returns the locale that's used when creating or comparing subformats.
4237935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *
4247935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @return the locale used when creating or comparing subformats
4257935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @stable ICU 3.0
4267935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
4277935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public Locale getLocale() {
4287935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return ulocale.toLocale();
4297935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
4307935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
4317935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
4327935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * {@icu} Returns the locale that's used when creating argument Format objects.
4337935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *
4347935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @return the locale used when creating or comparing subformats
4357935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @stable ICU 3.2
4367935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
4377935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public ULocale getULocale() {
4387935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return ulocale;
4397935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
4402d2bb24f747c65578da13d5b13b82f0669690461Fredrik Roubert
4417935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
4427935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Sets the pattern used by this message format.
4437935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Parses the pattern and caches Format objects for simple argument types.
4447935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Patterns and their interpretation are specified in the
4457935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * <a href="#patterns">class description</a>.
4467935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *
4477935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param pttrn the pattern for this message format
4487935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @throws IllegalArgumentException if the pattern is invalid
4497935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @stable ICU 3.0
4507935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
4517935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public void applyPattern(String pttrn) {
4527935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        try {
4537935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if (msgPattern == null) {
4547935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                msgPattern = new MessagePattern(pttrn);
4557935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            } else {
4567935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                msgPattern.parse(pttrn);
4577935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
4587935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            // Cache the formats that are explicitly mentioned in the message pattern.
4597935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            cacheExplicitFormats();
4607935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        } catch(RuntimeException e) {
4617935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            resetPattern();
4627935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            throw e;
4637935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
4647935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
4657935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
4667935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
4677935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * {@icu} Sets the ApostropheMode and the pattern used by this message format.
4687935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Parses the pattern and caches Format objects for simple argument types.
4697935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Patterns and their interpretation are specified in the
4707935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * <a href="#patterns">class description</a>.
4717935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * <p>
4727935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * This method is best used only once on a given object to avoid confusion about the mode,
4737935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * and after constructing the object with an empty pattern string to minimize overhead.
4747935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *
4757935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param pattern the pattern for this message format
4767935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param aposMode the new ApostropheMode
4777935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @throws IllegalArgumentException if the pattern is invalid
4787935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @see MessagePattern.ApostropheMode
4797935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @stable ICU 4.8
4807935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
4817935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public void applyPattern(String pattern, MessagePattern.ApostropheMode aposMode) {
4827935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (msgPattern == null) {
4837935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            msgPattern = new MessagePattern(aposMode);
4847935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        } else if (aposMode != msgPattern.getApostropheMode()) {
4857935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            msgPattern.clearPatternAndSetApostropheMode(aposMode);
4867935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
4877935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        applyPattern(pattern);
4887935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
4897935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
4907935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
4917935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * {@icu}
4927935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @return this instance's ApostropheMode.
4937935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @stable ICU 4.8
4947935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
4957935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public MessagePattern.ApostropheMode getApostropheMode() {
4967935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (msgPattern == null) {
4977935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            msgPattern = new MessagePattern();  // Sets the default mode.
4987935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
4997935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return msgPattern.getApostropheMode();
5007935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
5017935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
5027935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
5037935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Returns the applied pattern string.
5047935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @return the pattern string
5057935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @throws IllegalStateException after custom Format objects have been set
5067935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *         via setFormat() or similar APIs
5077935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @stable ICU 3.0
5087935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
5097935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public String toPattern() {
5107935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        // Return the original, applied pattern string, or else "".
5117935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        // Note: This does not take into account
5127935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        // - changes from setFormat() and similar methods, or
5137935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        // - normalization of apostrophes and arguments, for example,
5147935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        //   whether some date/time/number formatter was created via a pattern
5157935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        //   but is equivalent to the "medium" default format.
5167935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (customFormatArgStarts != null) {
5177935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            throw new IllegalStateException(
5187935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    "toPattern() is not supported after custom Format objects "+
5197935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    "have been set via setFormat() or similar APIs");
5207935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
5217935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (msgPattern == null) {
5227935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            return "";
5237935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
5247935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        String originalPattern = msgPattern.getPatternString();
5257935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return originalPattern == null ? "" : originalPattern;
5267935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
5277935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
5287935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
5297935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Returns the part index of the next ARG_START after partIndex, or -1 if there is none more.
5307935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param partIndex Part index of the previous ARG_START (initially 0).
5317935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
5327935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private int nextTopLevelArgStart(int partIndex) {
5337935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (partIndex != 0) {
5347935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            partIndex = msgPattern.getLimitPartIndex(partIndex);
5357935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
5367935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        for (;;) {
5377935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            MessagePattern.Part.Type type = msgPattern.getPartType(++partIndex);
5387935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if (type == MessagePattern.Part.Type.ARG_START) {
5397935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                return partIndex;
5407935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
5417935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if (type == MessagePattern.Part.Type.MSG_LIMIT) {
5427935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                return -1;
5437935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
5447935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
5457935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
5467935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
5477935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private boolean argNameMatches(int partIndex, String argName, int argNumber) {
5487935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        Part part = msgPattern.getPart(partIndex);
5497935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return part.getType() == MessagePattern.Part.Type.ARG_NAME ?
5507935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            msgPattern.partSubstringMatches(part, argName) :
5517935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            part.getValue() == argNumber;  // ARG_NUMBER
5527935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
5537935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
5547935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private String getArgName(int partIndex) {
5557935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        Part part = msgPattern.getPart(partIndex);
5567935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (part.getType() == MessagePattern.Part.Type.ARG_NAME) {
5577935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            return msgPattern.getSubstring(part);
5587935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        } else {
5597935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            return Integer.toString(part.getValue());
5607935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
5617935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
5627935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
5637935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
5647935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Sets the Format objects to use for the values passed into
5657935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * <code>format</code> methods or returned from <code>parse</code>
5667935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * methods. The indices of elements in <code>newFormats</code>
5677935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * correspond to the argument indices used in the previously set
5687935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * pattern string.
5697935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * The order of formats in <code>newFormats</code> thus corresponds to
5707935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * the order of elements in the <code>arguments</code> array passed
5717935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * to the <code>format</code> methods or the result array returned
5727935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * by the <code>parse</code> methods.
5737935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * <p>
5747935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * If an argument index is used for more than one format element
5757935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * in the pattern string, then the corresponding new format is used
5767935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * for all such format elements. If an argument index is not used
5777935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * for any format element in the pattern string, then the
5787935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * corresponding new format is ignored. If fewer formats are provided
5797935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * than needed, then only the formats for argument indices less
5807935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * than <code>newFormats.length</code> are replaced.
5817935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *
5827935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * This method is only supported if the format does not use
5837935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * named arguments, otherwise an IllegalArgumentException is thrown.
5847935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *
5857935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param newFormats the new formats to use
5867935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @throws NullPointerException if <code>newFormats</code> is null
5877935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @throws IllegalArgumentException if this formatter uses named arguments
5887935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @stable ICU 3.0
5897935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
5907935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public void setFormatsByArgumentIndex(Format[] newFormats) {
5917935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (msgPattern.hasNamedArguments()) {
5927935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            throw new IllegalArgumentException(
5937935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    "This method is not available in MessageFormat objects " +
5947935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    "that use alphanumeric argument names.");
5957935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
5967935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        for (int partIndex = 0; (partIndex = nextTopLevelArgStart(partIndex)) >= 0;) {
5977935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            int argNumber = msgPattern.getPart(partIndex + 1).getValue();
5987935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if (argNumber < newFormats.length) {
5997935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                setCustomArgStartFormat(partIndex, newFormats[argNumber]);
6007935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
6017935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
6027935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
6037935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
6047935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
6057935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * {@icu} Sets the Format objects to use for the values passed into
6067935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * <code>format</code> methods or returned from <code>parse</code>
6077935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * methods. The keys in <code>newFormats</code> are the argument
6087935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * names in the previously set pattern string, and the values
6097935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * are the formats.
6107935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * <p>
6117935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Only argument names from the pattern string are considered.
6127935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Extra keys in <code>newFormats</code> that do not correspond
6137935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * to an argument name are ignored.  Similarly, if there is no
6147935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * format in newFormats for an argument name, the formatter
6157935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * for that argument remains unchanged.
6167935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * <p>
6177935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * This may be called on formats that do not use named arguments.
6187935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * In this case the map will be queried for key Strings that
6197935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * represent argument indices, e.g. "0", "1", "2" etc.
6207935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *
6217935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param newFormats a map from String to Format providing new
6227935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *        formats for named arguments.
6237935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @stable ICU 3.8
6247935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
6257935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public void setFormatsByArgumentName(Map<String, Format> newFormats) {
6267935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        for (int partIndex = 0; (partIndex = nextTopLevelArgStart(partIndex)) >= 0;) {
6277935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            String key = getArgName(partIndex + 1);
6287935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if (newFormats.containsKey(key)) {
6297935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                setCustomArgStartFormat(partIndex, newFormats.get(key));
6307935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
6317935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
6327935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
6337935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
6347935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
6357935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Sets the Format objects to use for the format elements in the
6367935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * previously set pattern string.
6377935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * The order of formats in <code>newFormats</code> corresponds to
6387935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * the order of format elements in the pattern string.
6397935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * <p>
6407935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * If more formats are provided than needed by the pattern string,
6417935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * the remaining ones are ignored. If fewer formats are provided
6427935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * than needed, then only the first <code>newFormats.length</code>
6437935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * formats are replaced.
6447935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * <p>
6457935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Since the order of format elements in a pattern string often
6467935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * changes during localization, it is generally better to use the
6477935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * {@link #setFormatsByArgumentIndex setFormatsByArgumentIndex}
6487935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * method, which assumes an order of formats corresponding to the
6497935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * order of elements in the <code>arguments</code> array passed to
6507935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * the <code>format</code> methods or the result array returned by
6517935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * the <code>parse</code> methods.
6527935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *
6537935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param newFormats the new formats to use
6547935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @exception NullPointerException if <code>newFormats</code> is null
6557935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @stable ICU 3.0
6567935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
6577935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public void setFormats(Format[] newFormats) {
6587935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        int formatNumber = 0;
6597935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        for (int partIndex = 0;
6607935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                formatNumber < newFormats.length &&
6617935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                (partIndex = nextTopLevelArgStart(partIndex)) >= 0;) {
6627935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            setCustomArgStartFormat(partIndex, newFormats[formatNumber]);
6637935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            ++formatNumber;
6647935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
6657935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
6667935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
6677935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
6687935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Sets the Format object to use for the format elements within the
6697935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * previously set pattern string that use the given argument
6707935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * index.
6717935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * The argument index is part of the format element definition and
6727935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * represents an index into the <code>arguments</code> array passed
6737935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * to the <code>format</code> methods or the result array returned
6747935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * by the <code>parse</code> methods.
6757935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * <p>
6767935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * If the argument index is used for more than one format element
6777935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * in the pattern string, then the new format is used for all such
6787935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * format elements. If the argument index is not used for any format
6797935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * element in the pattern string, then the new format is ignored.
6807935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *
6817935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * This method is only supported when exclusively numbers are used for
6827935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * argument names. Otherwise an IllegalArgumentException is thrown.
6837935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *
6847935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param argumentIndex the argument index for which to use the new format
6857935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param newFormat the new format to use
6867935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @throws IllegalArgumentException if this format uses named arguments
6877935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @stable ICU 3.0
6887935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
6897935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public void setFormatByArgumentIndex(int argumentIndex, Format newFormat) {
6907935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (msgPattern.hasNamedArguments()) {
6917935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            throw new IllegalArgumentException(
6927935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    "This method is not available in MessageFormat objects " +
6937935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    "that use alphanumeric argument names.");
6947935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
6957935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        for (int partIndex = 0; (partIndex = nextTopLevelArgStart(partIndex)) >= 0;) {
6967935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if (msgPattern.getPart(partIndex + 1).getValue() == argumentIndex) {
6977935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                setCustomArgStartFormat(partIndex, newFormat);
6987935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
6997935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
7007935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
7017935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
7027935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
7037935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * {@icu} Sets the Format object to use for the format elements within the
7047935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * previously set pattern string that use the given argument
7057935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * name.
7067935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * <p>
7077935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * If the argument name is used for more than one format element
7087935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * in the pattern string, then the new format is used for all such
7097935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * format elements. If the argument name is not used for any format
7107935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * element in the pattern string, then the new format is ignored.
7117935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * <p>
7127935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * This API may be used on formats that do not use named arguments.
7137935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * In this case <code>argumentName</code> should be a String that names
7147935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * an argument index, e.g. "0", "1", "2"... etc.  If it does not name
7157935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * a valid index, the format will be ignored.  No error is thrown.
7167935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *
7177935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param argumentName the name of the argument to change
7187935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param newFormat the new format to use
7197935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @stable ICU 3.8
7207935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
7217935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public void setFormatByArgumentName(String argumentName, Format newFormat) {
7227935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        int argNumber = MessagePattern.validateArgumentName(argumentName);
7237935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (argNumber < MessagePattern.ARG_NAME_NOT_NUMBER) {
7247935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            return;
7257935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
7267935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        for (int partIndex = 0; (partIndex = nextTopLevelArgStart(partIndex)) >= 0;) {
7277935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if (argNameMatches(partIndex + 1, argumentName, argNumber)) {
7287935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                setCustomArgStartFormat(partIndex, newFormat);
7297935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
7307935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
7317935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
7327935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
7337935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
7347935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Sets the Format object to use for the format element with the given
7357935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * format element index within the previously set pattern string.
7367935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * The format element index is the zero-based number of the format
7377935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * element counting from the start of the pattern string.
7387935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * <p>
7397935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Since the order of format elements in a pattern string often
7407935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * changes during localization, it is generally better to use the
7417935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * {@link #setFormatByArgumentIndex setFormatByArgumentIndex}
7427935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * method, which accesses format elements based on the argument
7437935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * index they specify.
7447935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *
7457935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param formatElementIndex the index of a format element within the pattern
7467935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param newFormat the format to use for the specified format element
7477935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @exception ArrayIndexOutOfBoundsException if formatElementIndex is equal to or
7487935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *            larger than the number of format elements in the pattern string
7497935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @stable ICU 3.0
7507935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
7517935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public void setFormat(int formatElementIndex, Format newFormat) {
7527935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        int formatNumber = 0;
7537935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        for (int partIndex = 0; (partIndex = nextTopLevelArgStart(partIndex)) >= 0;) {
7547935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if (formatNumber == formatElementIndex) {
7557935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                setCustomArgStartFormat(partIndex, newFormat);
7567935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                return;
7577935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
7587935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            ++formatNumber;
7597935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
7607935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        throw new ArrayIndexOutOfBoundsException(formatElementIndex);
7617935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
7627935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
7637935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
7647935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Returns the Format objects used for the values passed into
7657935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * <code>format</code> methods or returned from <code>parse</code>
7667935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * methods. The indices of elements in the returned array
7677935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * correspond to the argument indices used in the previously set
7687935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * pattern string.
7697935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * The order of formats in the returned array thus corresponds to
7707935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * the order of elements in the <code>arguments</code> array passed
7717935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * to the <code>format</code> methods or the result array returned
7727935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * by the <code>parse</code> methods.
7737935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * <p>
7747935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * If an argument index is used for more than one format element
7757935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * in the pattern string, then the format used for the last such
7767935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * format element is returned in the array. If an argument index
7777935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * is not used for any format element in the pattern string, then
7787935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * null is returned in the array.
7797935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *
7807935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * This method is only supported when exclusively numbers are used for
7817935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * argument names. Otherwise an IllegalArgumentException is thrown.
7827935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *
7837935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @return the formats used for the arguments within the pattern
7847935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @throws IllegalArgumentException if this format uses named arguments
7857935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @stable ICU 3.0
7867935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
7877935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public Format[] getFormatsByArgumentIndex() {
7887935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (msgPattern.hasNamedArguments()) {
7897935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            throw new IllegalArgumentException(
7907935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    "This method is not available in MessageFormat objects " +
7917935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    "that use alphanumeric argument names.");
7927935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
7937935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        ArrayList<Format> list = new ArrayList<Format>();
7947935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        for (int partIndex = 0; (partIndex = nextTopLevelArgStart(partIndex)) >= 0;) {
7957935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            int argNumber = msgPattern.getPart(partIndex + 1).getValue();
7967935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            while (argNumber >= list.size()) {
7977935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                list.add(null);
7987935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
7997935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            list.set(argNumber, cachedFormatters == null ? null : cachedFormatters.get(partIndex));
8007935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
8017935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return list.toArray(new Format[list.size()]);
8027935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
8037935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
8047935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
8057935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Returns the Format objects used for the format elements in the
8067935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * previously set pattern string.
8077935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * The order of formats in the returned array corresponds to
8087935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * the order of format elements in the pattern string.
8097935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * <p>
8107935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Since the order of format elements in a pattern string often
8117935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * changes during localization, it's generally better to use the
8127935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * {@link #getFormatsByArgumentIndex()}
8137935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * method, which assumes an order of formats corresponding to the
8147935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * order of elements in the <code>arguments</code> array passed to
8157935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * the <code>format</code> methods or the result array returned by
8167935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * the <code>parse</code> methods.
8177935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *
8187935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * This method is only supported when exclusively numbers are used for
8197935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * argument names. Otherwise an IllegalArgumentException is thrown.
8207935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *
8217935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @return the formats used for the format elements in the pattern
8227935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @throws IllegalArgumentException if this format uses named arguments
8237935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @stable ICU 3.0
8247935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
8257935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public Format[] getFormats() {
8267935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        ArrayList<Format> list = new ArrayList<Format>();
8277935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        for (int partIndex = 0; (partIndex = nextTopLevelArgStart(partIndex)) >= 0;) {
8287935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            list.add(cachedFormatters == null ? null : cachedFormatters.get(partIndex));
8297935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
8307935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return list.toArray(new Format[list.size()]);
8317935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
8327935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
8337935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
8347935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * {@icu} Returns the top-level argument names. For more details, see
8357935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * {@link #setFormatByArgumentName(String, Format)}.
8367935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @return a Set of argument names
8377935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @stable ICU 4.8
8387935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
8397935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public Set<String> getArgumentNames() {
8407935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        Set<String> result = new HashSet<String>();
8417935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        for (int partIndex = 0; (partIndex = nextTopLevelArgStart(partIndex)) >= 0;) {
8427935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            result.add(getArgName(partIndex + 1));
8437935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
8447935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return result;
8457935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
8467935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
8477935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
8487935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * {@icu} Returns the first top-level format associated with the given argument name.
8497935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * For more details, see {@link #setFormatByArgumentName(String, Format)}.
8507935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param argumentName The name of the desired argument.
8517935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @return the Format associated with the name, or null if there isn't one.
8527935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @stable ICU 4.8
8537935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
8547935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public Format getFormatByArgumentName(String argumentName) {
8557935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (cachedFormatters == null) {
8567935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            return null;
8577935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
8587935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        int argNumber = MessagePattern.validateArgumentName(argumentName);
8597935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (argNumber < MessagePattern.ARG_NAME_NOT_NUMBER) {
8607935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            return null;
8617935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
8627935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        for (int partIndex = 0; (partIndex = nextTopLevelArgStart(partIndex)) >= 0;) {
8637935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if (argNameMatches(partIndex + 1, argumentName, argNumber)) {
8647935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                return cachedFormatters.get(partIndex);
8657935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
8667935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
8677935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return null;
8687935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
8697935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
8707935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
8717935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Formats an array of objects and appends the <code>MessageFormat</code>'s
8727935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * pattern, with arguments replaced by the formatted objects, to the
8737935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * provided <code>StringBuffer</code>.
8747935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * <p>
8757935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * The text substituted for the individual format elements is derived from
8767935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * the current subformat of the format element and the
8777935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * <code>arguments</code> element at the format element's argument index
8787935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * as indicated by the first matching line of the following table. An
8797935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * argument is <i>unavailable</i> if <code>arguments</code> is
8807935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * <code>null</code> or has fewer than argumentIndex+1 elements.  When
8817935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * an argument is unavailable no substitution is performed.
882bee65486a185907111f3be60992433e133ec0e32Scott Russell     *
8837935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * <table border=1>
8847935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *    <tr>
8857935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *       <th>argType or Format
8867935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *       <th>value object
8877935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *       <th>Formatted Text
8887935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *    <tr>
8897935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *       <td><i>any</i>
8907935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *       <td><i>unavailable</i>
8917935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *       <td><code>"{" + argNameOrNumber + "}"</code>
8927935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *    <tr>
8937935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *       <td><i>any</i>
8947935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *       <td><code>null</code>
8957935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *       <td><code>"null"</code>
8967935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *    <tr>
8977935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *       <td>custom Format <code>!= null</code>
8987935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *       <td><i>any</i>
8997935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *       <td><code>customFormat.format(argument)</code>
9007935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *    <tr>
9017935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *       <td>noneArg, or custom Format <code>== null</code>
9027935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *       <td><code>instanceof Number</code>
9037935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *       <td><code>NumberFormat.getInstance(getLocale()).format(argument)</code>
9047935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *    <tr>
9057935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *       <td>noneArg, or custom Format <code>== null</code>
9067935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *       <td><code>instanceof Date</code>
9077935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *       <td><code>DateFormat.getDateTimeInstance(DateFormat.SHORT,
9087935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *           DateFormat.SHORT, getLocale()).format(argument)</code>
9097935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *    <tr>
9107935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *       <td>noneArg, or custom Format <code>== null</code>
9117935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *       <td><code>instanceof String</code>
9127935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *       <td><code>argument</code>
9137935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *    <tr>
9147935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *       <td>noneArg, or custom Format <code>== null</code>
9157935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *       <td><i>any</i>
9167935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *       <td><code>argument.toString()</code>
9177935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *    <tr>
9187935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *       <td>complexArg
9197935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *       <td><i>any</i>
9207935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *       <td>result of recursive formatting of a selected sub-message
9217935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * </table>
9227935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * <p>
9237935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * If <code>pos</code> is non-null, and refers to
9247935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * <code>Field.ARGUMENT</code>, the location of the first formatted
9257935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * string will be returned.
9267935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *
9277935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * This method is only supported when the format does not use named
9287935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * arguments, otherwise an IllegalArgumentException is thrown.
9297935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *
9307935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param arguments an array of objects to be formatted and substituted.
9317935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param result where text is appended.
9327935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param pos On input: an alignment field, if desired.
9337935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *            On output: the offsets of the alignment field.
9347935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @throws IllegalArgumentException if a value in the
9357935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *         <code>arguments</code> array is not of the type
9367935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *         expected by the corresponding argument or custom Format object.
9377935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @throws IllegalArgumentException if this format uses named arguments
9387935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @stable ICU 3.0
9397935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
9407935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public final StringBuffer format(Object[] arguments, StringBuffer result,
9417935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                                     FieldPosition pos)
9427935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    {
9437935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        format(arguments, null, new AppendableWrapper(result), pos);
9447935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return result;
9457935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
9467935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
9477935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
9487935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Formats a map of objects and appends the <code>MessageFormat</code>'s
9497935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * pattern, with arguments replaced by the formatted objects, to the
9507935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * provided <code>StringBuffer</code>.
9517935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * <p>
9527935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * The text substituted for the individual format elements is derived from
9537935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * the current subformat of the format element and the
9547935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * <code>arguments</code> value corresopnding to the format element's
9557935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * argument name.
9567935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * <p>
9577935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * A numbered pattern argument is matched with a map key that contains that number
9587935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * as an ASCII-decimal-digit string (without leading zero).
9597935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * <p>
9607935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * An argument is <i>unavailable</i> if <code>arguments</code> is
9617935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * <code>null</code> or does not have a value corresponding to an argument
9627935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * name in the pattern.  When an argument is unavailable no substitution
9637935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * is performed.
9647935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *
9657935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param arguments a map of objects to be formatted and substituted.
9667935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param result where text is appended.
9677935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param pos On input: an alignment field, if desired.
9687935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *            On output: the offsets of the alignment field.
9697935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @throws IllegalArgumentException if a value in the
9707935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *         <code>arguments</code> array is not of the type
9717935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *         expected by the corresponding argument or custom Format object.
9727935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @return the passed-in StringBuffer
9737935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @stable ICU 3.8
9747935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
9757935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public final StringBuffer format(Map<String, Object> arguments, StringBuffer result,
9767935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                                     FieldPosition pos) {
9777935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        format(null, arguments, new AppendableWrapper(result), pos);
9787935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return result;
9797935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
9807935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
9817935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
9827935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Creates a MessageFormat with the given pattern and uses it
9837935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * to format the given arguments. This is equivalent to
9847935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * <blockquote>
9857935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *     <code>(new {@link #MessageFormat(String) MessageFormat}(pattern)).{@link
9867935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *     #format(java.lang.Object[], java.lang.StringBuffer, java.text.FieldPosition)
9877935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *     format}(arguments, new StringBuffer(), null).toString()</code>
9887935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * </blockquote>
9897935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *
9907935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @throws IllegalArgumentException if the pattern is invalid
9917935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @throws IllegalArgumentException if a value in the
9927935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *         <code>arguments</code> array is not of the type
9937935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *         expected by the corresponding argument or custom Format object.
9947935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @throws IllegalArgumentException if this format uses named arguments
9957935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @stable ICU 3.0
9967935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
9977935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public static String format(String pattern, Object... arguments) {
9987935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        MessageFormat temp = new MessageFormat(pattern);
9997935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return temp.format(arguments);
10007935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
10017935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
10027935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
10037935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Creates a MessageFormat with the given pattern and uses it to
10047935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * format the given arguments.  The pattern must identifyarguments
10057935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * by name instead of by number.
10067935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * <p>
10077935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @throws IllegalArgumentException if the pattern is invalid
10087935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @throws IllegalArgumentException if a value in the
10097935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *         <code>arguments</code> array is not of the type
10107935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *         expected by the corresponding argument or custom Format object.
10117935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @see #format(Map, StringBuffer, FieldPosition)
10127935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @see #format(String, Object[])
10137935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @stable ICU 3.8
10147935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
10157935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public static String format(String pattern, Map<String, Object> arguments) {
10167935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        MessageFormat temp = new MessageFormat(pattern);
10177935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return temp.format(arguments);
10187935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
10197935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
10207935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
10217935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * {@icu} Returns true if this MessageFormat uses named arguments,
10227935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * and false otherwise.  See class description.
10237935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *
10247935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @return true if named arguments are used.
10257935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @stable ICU 3.8
10267935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
10277935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public boolean usesNamedArguments() {
10287935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return msgPattern.hasNamedArguments();
10297935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
10307935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
10317935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    // Overrides
10327935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
10337935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Formats a map or array of objects and appends the <code>MessageFormat</code>'s
10347935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * pattern, with format elements replaced by the formatted objects, to the
10357935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * provided <code>StringBuffer</code>.
10367935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * This is equivalent to either of
10377935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * <blockquote>
10387935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *     <code>{@link #format(java.lang.Object[], java.lang.StringBuffer,
10397935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *     java.text.FieldPosition) format}((Object[]) arguments, result, pos)</code>
10407935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *     <code>{@link #format(java.util.Map, java.lang.StringBuffer,
10417935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *     java.text.FieldPosition) format}((Map) arguments, result, pos)</code>
10427935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * </blockquote>
10437935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * A map must be provided if this format uses named arguments, otherwise
10447935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * an IllegalArgumentException will be thrown.
10457935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param arguments a map or array of objects to be formatted
10467935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param result where text is appended
10477935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param pos On input: an alignment field, if desired
10487935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *            On output: the offsets of the alignment field
10497935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @throws IllegalArgumentException if an argument in
10507935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *         <code>arguments</code> is not of the type
10517935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *         expected by the format element(s) that use it
1052bee65486a185907111f3be60992433e133ec0e32Scott Russell     * @throws IllegalArgumentException if <code>arguments</code> is
10537935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *         an array of Object and this format uses named arguments
10547935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @stable ICU 3.0
10557935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
10562d2bb24f747c65578da13d5b13b82f0669690461Fredrik Roubert    @Override
10577935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public final StringBuffer format(Object arguments, StringBuffer result,
10587935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                                     FieldPosition pos)
10597935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    {
10607935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        format(arguments, new AppendableWrapper(result), pos);
10617935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return result;
10627935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
10637935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
10647935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
10657935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Formats an array of objects and inserts them into the
10667935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * <code>MessageFormat</code>'s pattern, producing an
10677935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * <code>AttributedCharacterIterator</code>.
10687935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * You can use the returned <code>AttributedCharacterIterator</code>
10697935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * to build the resulting String, as well as to determine information
10707935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * about the resulting String.
10717935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * <p>
10727935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * The text of the returned <code>AttributedCharacterIterator</code> is
10737935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * the same that would be returned by
10747935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * <blockquote>
10757935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *     <code>{@link #format(java.lang.Object[], java.lang.StringBuffer,
10767935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *     java.text.FieldPosition) format}(arguments, new StringBuffer(), null).toString()</code>
10777935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * </blockquote>
10787935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * <p>
10797935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * In addition, the <code>AttributedCharacterIterator</code> contains at
10807935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * least attributes indicating where text was generated from an
10817935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * argument in the <code>arguments</code> array. The keys of these attributes are of
10827935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * type <code>MessageFormat.Field</code>, their values are
10837935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * <code>Integer</code> objects indicating the index in the <code>arguments</code>
10847935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * array of the argument from which the text was generated.
10857935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * <p>
10867935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * The attributes/value from the underlying <code>Format</code>
10877935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * instances that <code>MessageFormat</code> uses will also be
10887935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * placed in the resulting <code>AttributedCharacterIterator</code>.
10897935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * This allows you to not only find where an argument is placed in the
10907935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * resulting String, but also which fields it contains in turn.
10917935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *
10927935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param arguments an array of objects to be formatted and substituted.
10937935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @return AttributedCharacterIterator describing the formatted value.
10947935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @exception NullPointerException if <code>arguments</code> is null.
10957935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @throws IllegalArgumentException if a value in the
10967935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *         <code>arguments</code> array is not of the type
10977935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *         expected by the corresponding argument or custom Format object.
10987935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @stable ICU 3.8
10997935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
11002d2bb24f747c65578da13d5b13b82f0669690461Fredrik Roubert    @Override
11017935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public AttributedCharacterIterator formatToCharacterIterator(Object arguments) {
11027935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (arguments == null) {
11037935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            throw new NullPointerException(
11047935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                   "formatToCharacterIterator must be passed non-null object");
11057935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
11067935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        StringBuilder result = new StringBuilder();
11077935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        AppendableWrapper wrapper = new AppendableWrapper(result);
11087935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        wrapper.useAttributes();
11097935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        format(arguments, wrapper, null);
11107935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        AttributedString as = new AttributedString(result.toString());
11117935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        for (AttributeAndPosition a : wrapper.attributes) {
11127935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            as.addAttribute(a.key, a.value, a.start, a.limit);
11137935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
11147935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return as.getIterator();
11157935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
11167935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
11177935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
11187935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Parses the string.
11197935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *
11207935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * <p>Caveats: The parse may fail in a number of circumstances.
11217935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * For example:
11227935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * <ul>
11237935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * <li>If one of the arguments does not occur in the pattern.
11247935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * <li>If the format of an argument loses information, such as
11257935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *     with a choice format where a large number formats to "many".
11267935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * <li>Does not yet handle recursion (where
11277935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *     the substituted strings contain {n} references.)
11287935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * <li>Will not always find a match (or the correct match)
11297935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *     if some part of the parse is ambiguous.
11307935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *     For example, if the pattern "{1},{2}" is used with the
11317935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *     string arguments {"a,b", "c"}, it will format as "a,b,c".
11327935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *     When the result is parsed, it will return {"a", "b,c"}.
11337935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * <li>If a single argument is parsed more than once in the string,
11347935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *     then the later parse wins.
11357935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * </ul>
11367935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * When the parse fails, use ParsePosition.getErrorIndex() to find out
11377935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * where in the string did the parsing failed. The returned error
11387935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * index is the starting offset of the sub-patterns that the string
11397935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * is comparing with. For example, if the parsing string "AAA {0} BBB"
11407935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * is comparing against the pattern "AAD {0} BBB", the error index is
11417935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * 0. When an error occurs, the call to this method will return null.
11427935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * If the source is null, return an empty array.
11437935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *
11447935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @throws IllegalArgumentException if this format uses named arguments
11457935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @stable ICU 3.0
11467935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
11477935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public Object[] parse(String source, ParsePosition pos) {
11487935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (msgPattern.hasNamedArguments()) {
11497935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            throw new IllegalArgumentException(
11507935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    "This method is not available in MessageFormat objects " +
11517935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    "that use named argument.");
11527935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
11532d2bb24f747c65578da13d5b13b82f0669690461Fredrik Roubert
11547935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        // Count how many slots we need in the array.
11557935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        int maxArgId = -1;
11567935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        for (int partIndex = 0; (partIndex = nextTopLevelArgStart(partIndex)) >= 0;) {
11577935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            int argNumber=msgPattern.getPart(partIndex + 1).getValue();
11587935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if (argNumber > maxArgId) {
11597935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                maxArgId = argNumber;
11607935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
11617935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
11627935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        Object[] resultArray = new Object[maxArgId + 1];
11637935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
11647935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        int backupStartPos = pos.getIndex();
11657935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        parse(0, source, pos, resultArray, null);
11667935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (pos.getIndex() == backupStartPos) { // unchanged, returned object is null
11677935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            return null;
11687935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
11697935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
11707935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return resultArray;
11717935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
11722d2bb24f747c65578da13d5b13b82f0669690461Fredrik Roubert
11737935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
11747935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * {@icu} Parses the string, returning the results in a Map.
11757935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * This is similar to the version that returns an array
11767935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * of Object.  This supports both named and numbered
11777935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * arguments-- if numbered, the keys in the map are the
11787935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * corresponding ASCII-decimal-digit strings (e.g. "0", "1", "2"...).
11797935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *
11807935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param source the text to parse
11817935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param pos the position at which to start parsing.  on return,
11827935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *        contains the result of the parse.
11837935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @return a Map containing key/value pairs for each parsed argument.
11847935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @stable ICU 3.8
11857935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
11867935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public Map<String, Object> parseToMap(String source, ParsePosition pos)  {
11877935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        Map<String, Object> result = new HashMap<String, Object>();
11887935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        int backupStartPos = pos.getIndex();
11897935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        parse(0, source, pos, null, result);
11907935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (pos.getIndex() == backupStartPos) {
11917935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            return null;
11927935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
11932d2bb24f747c65578da13d5b13b82f0669690461Fredrik Roubert        return result;
11947935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
11952d2bb24f747c65578da13d5b13b82f0669690461Fredrik Roubert
11967935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
11977935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Parses text from the beginning of the given string to produce an object
11987935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * array.
11997935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * The method may not use the entire text of the given string.
12007935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * <p>
12017935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * See the {@link #parse(String, ParsePosition)} method for more information
12027935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * on message parsing.
12037935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *
12047935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param source A <code>String</code> whose beginning should be parsed.
12057935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @return An <code>Object</code> array parsed from the string.
12067935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @exception ParseException if the beginning of the specified string cannot be parsed.
12077935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @exception IllegalArgumentException if this format uses named arguments
12087935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @stable ICU 3.0
12097935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
12107935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public Object[] parse(String source) throws ParseException {
12117935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        ParsePosition pos = new ParsePosition(0);
12127935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        Object[] result = parse(source, pos);
12137935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (pos.getIndex() == 0) // unchanged, returned object is null
12147935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            throw new ParseException("MessageFormat parse error!",
12157935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                                     pos.getErrorIndex());
12167935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
12177935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return result;
12187935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
12197935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
12207935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
12217935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Parses the string, filling either the Map or the Array.
12227935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * This is a private method that all the public parsing methods call.
12237935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * This supports both named and numbered
12247935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * arguments-- if numbered, the keys in the map are the
12257935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * corresponding ASCII-decimal-digit strings (e.g. "0", "1", "2"...).
12267935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *
12277935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param msgStart index in the message pattern to start from.
12287935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param source the text to parse
12297935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param pos the position at which to start parsing.  on return,
12307935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *        contains the result of the parse.
12317935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param args if not null, the parse results will be filled here (The pattern
12327935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *        has to have numbered arguments in order for this to not be null).
12337935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param argsMap if not null, the parse results will be filled here.
12347935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
12357935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private void parse(int msgStart, String source, ParsePosition pos,
12367935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                       Object[] args, Map<String, Object> argsMap) {
12377935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (source == null) {
12387935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            return;
12397935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
12407935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        String msgString=msgPattern.getPatternString();
12417935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        int prevIndex=msgPattern.getPart(msgStart).getLimit();
12427935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        int sourceOffset = pos.getIndex();
12437935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        ParsePosition tempStatus = new ParsePosition(0);
12447935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
12457935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        for(int i=msgStart+1; ; ++i) {
12467935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            Part part=msgPattern.getPart(i);
12477935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            Part.Type type=part.getType();
12487935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            int index=part.getIndex();
12497935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            // Make sure the literal string matches.
12507935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            int len = index - prevIndex;
12517935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if (len == 0 || msgString.regionMatches(prevIndex, source, sourceOffset, len)) {
12527935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                sourceOffset += len;
12537935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                prevIndex += len;
12547935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            } else {
12557935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                pos.setErrorIndex(sourceOffset);
12567935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                return; // leave index as is to signal error
12577935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
12587935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if(type==Part.Type.MSG_LIMIT) {
12597935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                // Things went well! Done.
12607935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                pos.setIndex(sourceOffset);
12617935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                return;
12627935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
12637935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if(type==Part.Type.SKIP_SYNTAX || type==Part.Type.INSERT_CHAR) {
12647935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                prevIndex=part.getLimit();
12657935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                continue;
12667935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
12677935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            // We do not support parsing Plural formats. (No REPLACE_NUMBER here.)
12687935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            assert type==Part.Type.ARG_START : "Unexpected Part "+part+" in parsed message.";
12697935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            int argLimit=msgPattern.getLimitPartIndex(i);
12702d2bb24f747c65578da13d5b13b82f0669690461Fredrik Roubert
12717935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            ArgType argType=part.getArgType();
12727935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            part=msgPattern.getPart(++i);
12737935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            // Compute the argId, so we can use it as a key.
12747935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            Object argId=null;
12757935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            int argNumber = 0;
12767935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            String key = null;
12777935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if(args!=null) {
12787935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                argNumber=part.getValue();  // ARG_NUMBER
12797935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                argId = Integer.valueOf(argNumber);
12807935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            } else {
12817935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                if(part.getType()==MessagePattern.Part.Type.ARG_NAME) {
12827935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    key=msgPattern.getSubstring(part);
12837935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                } else /* ARG_NUMBER */ {
12847935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    key=Integer.toString(part.getValue());
12857935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                }
12867935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                argId = key;
12877935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
12887935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
12897935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            ++i;
12907935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            Format formatter = null;
12917935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            boolean haveArgResult = false;
12927935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            Object argResult = null;
12937935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if(cachedFormatters!=null && (formatter=cachedFormatters.get(i - 2))!=null) {
12947935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                // Just parse using the formatter.
12957935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                tempStatus.setIndex(sourceOffset);
12967935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                argResult = formatter.parseObject(source, tempStatus);
12977935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                if (tempStatus.getIndex() == sourceOffset) {
12987935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    pos.setErrorIndex(sourceOffset);
12997935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    return; // leave index as is to signal error
13007935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                }
13017935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                haveArgResult = true;
13027935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                sourceOffset = tempStatus.getIndex();
13037935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            } else if(
13047935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    argType==ArgType.NONE ||
13057935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    (cachedFormatters!=null && cachedFormatters.containsKey(i - 2))) {
13067935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                // Match as a string.
13077935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                // if at end, use longest possible match
13087935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                // otherwise uses first match to intervening string
13097935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                // does NOT recursively try all possibilities
13107935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                String stringAfterArgument = getLiteralStringUntilNextArgument(argLimit);
13117935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                int next;
13127935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                if (stringAfterArgument.length() != 0) {
13137935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    next = source.indexOf(stringAfterArgument, sourceOffset);
13147935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                } else {
13157935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    next = source.length();
13167935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                }
13177935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                if (next < 0) {
13187935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    pos.setErrorIndex(sourceOffset);
13197935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    return; // leave index as is to signal error
13207935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                } else {
13217935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    String strValue = source.substring(sourceOffset, next);
13227935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    if (!strValue.equals("{" + argId.toString() + "}")) {
13237935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        haveArgResult = true;
13247935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        argResult = strValue;
13257935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    }
13267935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    sourceOffset = next;
13277935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                }
13287935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            } else if(argType==ArgType.CHOICE) {
13297935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                tempStatus.setIndex(sourceOffset);
13307935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                double choiceResult = parseChoiceArgument(msgPattern, i, source, tempStatus);
13317935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                if (tempStatus.getIndex() == sourceOffset) {
13327935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    pos.setErrorIndex(sourceOffset);
13337935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    return; // leave index as is to signal error
13347935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                }
13357935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                argResult = choiceResult;
13367935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                haveArgResult = true;
13377935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                sourceOffset = tempStatus.getIndex();
13387935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            } else if(argType.hasPluralStyle() || argType==ArgType.SELECT) {
13397935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                // No can do!
13407935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                throw new UnsupportedOperationException(
13417935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        "Parsing of plural/select/selectordinal argument is not supported.");
13427935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            } else {
13437935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                // This should never happen.
13447935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                throw new IllegalStateException("unexpected argType "+argType);
13457935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
13467935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if (haveArgResult) {
13477935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                if (args != null) {
13487935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    args[argNumber] = argResult;
13497935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                } else if (argsMap != null) {
13507935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    argsMap.put(key, argResult);
13517935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                }
13527935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
13537935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            prevIndex=msgPattern.getPart(argLimit).getLimit();
13547935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            i=argLimit;
13557935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
13567935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
13577935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
13587935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
13597935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * {@icu} Parses text from the beginning of the given string to produce a map from
13607935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * argument to values. The method may not use the entire text of the given string.
13617935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *
13627935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * <p>See the {@link #parse(String, ParsePosition)} method for more information on
13637935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * message parsing.
13647935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *
13657935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param source A <code>String</code> whose beginning should be parsed.
13667935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @return A <code>Map</code> parsed from the string.
13677935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @throws ParseException if the beginning of the specified string cannot
13687935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *         be parsed.
13697935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @see #parseToMap(String, ParsePosition)
13707935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @stable ICU 3.8
13717935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
13727935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public Map<String, Object> parseToMap(String source) throws ParseException {
13737935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        ParsePosition pos = new ParsePosition(0);
13747935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        Map<String, Object> result = new HashMap<String, Object>();
13757935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        parse(0, source, pos, null, result);
13767935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (pos.getIndex() == 0) // unchanged, returned object is null
13777935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            throw new ParseException("MessageFormat parse error!",
13787935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                                     pos.getErrorIndex());
13797935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
13807935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return result;
13817935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
13827935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
13837935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
13847935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Parses text from a string to produce an object array or Map.
13857935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * <p>
13867935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * The method attempts to parse text starting at the index given by
13877935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * <code>pos</code>.
13887935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * If parsing succeeds, then the index of <code>pos</code> is updated
13897935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * to the index after the last character used (parsing does not necessarily
13907935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * use all characters up to the end of the string), and the parsed
13917935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * object array is returned. The updated <code>pos</code> can be used to
13927935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * indicate the starting point for the next call to this method.
13937935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * If an error occurs, then the index of <code>pos</code> is not
13947935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * changed, the error index of <code>pos</code> is set to the index of
13957935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * the character where the error occurred, and null is returned.
13967935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * <p>
13977935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * See the {@link #parse(String, ParsePosition)} method for more information
13987935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * on message parsing.
13997935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *
14007935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param source A <code>String</code>, part of which should be parsed.
14017935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param pos A <code>ParsePosition</code> object with index and error
14027935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *            index information as described above.
14037935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @return An <code>Object</code> parsed from the string, either an
14047935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *         array of Object, or a Map, depending on whether named
14057935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *         arguments are used.  This can be queried using <code>usesNamedArguments</code>.
14067935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *         In case of error, returns null.
14077935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @throws NullPointerException if <code>pos</code> is null.
14087935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @stable ICU 3.0
14097935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
14102d2bb24f747c65578da13d5b13b82f0669690461Fredrik Roubert    @Override
14117935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public Object parseObject(String source, ParsePosition pos) {
14127935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (!msgPattern.hasNamedArguments()) {
14137935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            return parse(source, pos);
14147935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        } else {
14157935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            return parseToMap(source, pos);
14167935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
14177935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
14187935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
14197935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
14207935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * {@inheritDoc}
14217935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @stable ICU 3.0
14227935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
14237935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    @Override
14247935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public Object clone() {
14257935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        MessageFormat other = (MessageFormat) super.clone();
14267935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
14277935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (customFormatArgStarts != null) {
14287935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            other.customFormatArgStarts = new HashSet<Integer>();
14297935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            for (Integer key : customFormatArgStarts) {
14307935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                other.customFormatArgStarts.add(key);
14317935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
14327935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        } else {
14337935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            other.customFormatArgStarts = null;
14347935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
14352d2bb24f747c65578da13d5b13b82f0669690461Fredrik Roubert
14367935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (cachedFormatters != null) {
14377935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            other.cachedFormatters = new HashMap<Integer, Format>();
14387935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            Iterator<Map.Entry<Integer, Format>> it = cachedFormatters.entrySet().iterator();
14397935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            while (it.hasNext()){
14407935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                Map.Entry<Integer, Format> entry = it.next();
14417935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                other.cachedFormatters.put(entry.getKey(), entry.getValue());
14427935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
14437935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        } else {
14447935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            other.cachedFormatters = null;
14457935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
14462d2bb24f747c65578da13d5b13b82f0669690461Fredrik Roubert
14477935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        other.msgPattern = msgPattern == null ? null : (MessagePattern)msgPattern.clone();
14487935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        other.stockDateFormatter =
14497935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                stockDateFormatter == null ? null : (DateFormat) stockDateFormatter.clone();
14507935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        other.stockNumberFormatter =
14517935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                stockNumberFormatter == null ? null : (NumberFormat) stockNumberFormatter.clone();
14527935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
14537935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        other.pluralProvider = null;
14547935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        other.ordinalProvider = null;
14557935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return other;
14567935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
14577935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
14587935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
14597935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * {@inheritDoc}
14607935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @stable ICU 3.0
14617935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
14627935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    @Override
14637935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public boolean equals(Object obj) {
14647935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (this == obj)                      // quick check
14657935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            return true;
14667935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (obj == null || getClass() != obj.getClass())
14677935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            return false;
14687935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        MessageFormat other = (MessageFormat) obj;
14697935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return Utility.objectEquals(ulocale, other.ulocale)
14707935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                && Utility.objectEquals(msgPattern, other.msgPattern)
14717935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                && Utility.objectEquals(cachedFormatters, other.cachedFormatters)
14727935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                && Utility.objectEquals(customFormatArgStarts, other.customFormatArgStarts);
14737935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        // Note: It might suffice to only compare custom formatters
14747935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        // rather than all formatters.
14757935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
14767935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
14777935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
14787935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * {@inheritDoc}
14797935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @stable ICU 3.0
14807935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
14817935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    @Override
14827935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public int hashCode() {
14837935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return msgPattern.getPatternString().hashCode(); // enough for reasonable distribution
14847935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
14857935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
14867935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
14877935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Defines constants that are used as attribute keys in the
14887935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * <code>AttributedCharacterIterator</code> returned
14897935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * from <code>MessageFormat.formatToCharacterIterator</code>.
14907935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *
14917935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @stable ICU 3.8
14927935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
14937935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public static class Field extends Format.Field {
14947935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
14957935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        private static final long serialVersionUID = 7510380454602616157L;
14967935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
14977935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        /**
14987935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert         * Create a <code>Field</code> with the specified name.
14997935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert         *
15007935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert         * @param name The name of the attribute
15017935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert         *
15027935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert         * @stable ICU 3.8
15037935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert         */
15047935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        protected Field(String name) {
15057935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            super(name);
15067935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
15077935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
15087935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        /**
15097935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert         * Resolves instances being deserialized to the predefined constants.
15107935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert         *
15117935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert         * @return resolved MessageFormat.Field constant
15127935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert         * @throws InvalidObjectException if the constant could not be resolved.
15137935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert         *
15147935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert         * @stable ICU 3.8
15157935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert         */
15162d2bb24f747c65578da13d5b13b82f0669690461Fredrik Roubert        @Override
15177935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        protected Object readResolve() throws InvalidObjectException {
15187935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if (this.getClass() != MessageFormat.Field.class) {
15197935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                throw new InvalidObjectException(
15207935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    "A subclass of MessageFormat.Field must implement readResolve.");
15217935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
15227935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if (this.getName().equals(ARGUMENT.getName())) {
15237935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                return ARGUMENT;
15247935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            } else {
15257935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                throw new InvalidObjectException("Unknown attribute name.");
15267935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
15277935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
15287935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
15297935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        /**
15307935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert         * Constant identifying a portion of a message that was generated
15317935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert         * from an argument passed into <code>formatToCharacterIterator</code>.
15327935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert         * The value associated with the key will be an <code>Integer</code>
15337935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert         * indicating the index in the <code>arguments</code> array of the
15347935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert         * argument from which the text was generated.
15357935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert         *
15367935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert         * @stable ICU 3.8
15377935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert         */
15387935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        public static final Field ARGUMENT = new Field("message argument field");
15397935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
15407935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
15417935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    // ===========================privates============================
15427935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
15437935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    // *Important*: All fields must be declared *transient* so that we can fully
15447935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    // control serialization!
15457935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    // See for example Joshua Bloch's "Effective Java", chapter 10 Serialization.
15467935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
15477935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
15487935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * The locale to use for formatting numbers and dates.
15497935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
15507935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private transient ULocale ulocale;
15517935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
15527935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
15537935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * The MessagePattern which contains the parsed structure of the pattern string.
15547935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
15557935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private transient MessagePattern msgPattern;
15567935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
15577935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Cached formatters so we can just use them whenever needed instead of creating
15587935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * them from scratch every time.
15597935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
15607935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private transient Map<Integer, Format> cachedFormatters;
15617935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
15627935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Set of ARG_START part indexes where custom, user-provided Format objects
15637935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * have been set via setFormat() or similar API.
15647935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
15657935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private transient Set<Integer> customFormatArgStarts;
15667935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
15677935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
15687935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Stock formatters. Those are used when a format is not explicitly mentioned in
15697935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * the message. The format is inferred from the argument.
15707935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
15717935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private transient DateFormat stockDateFormatter;
15727935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private transient NumberFormat stockNumberFormatter;
15737935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
15747935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private transient PluralSelectorProvider pluralProvider;
15757935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private transient PluralSelectorProvider ordinalProvider;
15767935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
15777935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private DateFormat getStockDateFormatter() {
15787935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (stockDateFormatter == null) {
15797935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            stockDateFormatter = DateFormat.getDateTimeInstance(
15807935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    DateFormat.SHORT, DateFormat.SHORT, ulocale);//fix
15817935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
15827935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return stockDateFormatter;
15837935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
15847935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private NumberFormat getStockNumberFormatter() {
15857935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (stockNumberFormatter == null) {
15867935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            stockNumberFormatter = NumberFormat.getInstance(ulocale);
15877935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
15887935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return stockNumberFormatter;
15897935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
15907935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
15917935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    // *Important*: All fields must be declared *transient*.
15927935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    // See the longer comment above ulocale.
15937935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
15947935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
15957935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Formats the arguments and writes the result into the
15967935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * AppendableWrapper, updates the field position.
15977935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *
15987935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * <p>Exactly one of args and argsMap must be null, the other non-null.
15997935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *
16007935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param msgStart      Index to msgPattern part to start formatting from.
16017935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param pluralNumber  null except when formatting a plural argument sub-message
16027935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *                      where a '#' is replaced by the format string for this number.
16037935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param args          The formattable objects array. Non-null iff numbered values are used.
16047935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param argsMap       The key-value map of formattable objects. Non-null iff named values are used.
16057935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param dest          Output parameter to receive the result.
16067935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *                      The result (string & attributes) is appended to existing contents.
16077935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param fp            Field position status.
16087935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
16097935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private void format(int msgStart, PluralSelectorContext pluralNumber,
16107935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        Object[] args, Map<String, Object> argsMap,
16117935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        AppendableWrapper dest, FieldPosition fp) {
16127935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        String msgString=msgPattern.getPatternString();
16137935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        int prevIndex=msgPattern.getPart(msgStart).getLimit();
16147935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        for(int i=msgStart+1;; ++i) {
16157935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            Part part=msgPattern.getPart(i);
16167935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            Part.Type type=part.getType();
16177935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            int index=part.getIndex();
16187935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            dest.append(msgString, prevIndex, index);
16197935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if(type==Part.Type.MSG_LIMIT) {
16207935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                return;
16217935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
16227935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            prevIndex=part.getLimit();
16237935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if(type==Part.Type.REPLACE_NUMBER) {
16247935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                if(pluralNumber.forReplaceNumber) {
16257935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    // number-offset was already formatted.
16267935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    dest.formatAndAppend(pluralNumber.formatter,
16277935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                            pluralNumber.number, pluralNumber.numberString);
16287935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                } else {
16297935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    dest.formatAndAppend(getStockNumberFormatter(), pluralNumber.number);
16307935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                }
16317935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                continue;
16327935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
16337935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if(type!=Part.Type.ARG_START) {
16347935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                continue;
16357935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
16367935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            int argLimit=msgPattern.getLimitPartIndex(i);
16377935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            ArgType argType=part.getArgType();
16387935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            part=msgPattern.getPart(++i);
16397935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            Object arg;
16407935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            boolean noArg=false;
16417935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            Object argId=null;
16427935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            String argName=msgPattern.getSubstring(part);
16437935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if(args!=null) {
16447935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                int argNumber=part.getValue();  // ARG_NUMBER
16457935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                if (dest.attributes != null) {
16467935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    // We only need argId if we add it into the attributes.
16477935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    argId = Integer.valueOf(argNumber);
16487935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                }
16497935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                if(0<=argNumber && argNumber<args.length) {
16507935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    arg=args[argNumber];
16517935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                } else {
16527935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    arg=null;
16537935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    noArg=true;
16547935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                }
16557935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            } else {
16567935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                argId = argName;
16577935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                if(argsMap!=null && argsMap.containsKey(argName)) {
16587935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    arg=argsMap.get(argName);
16597935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                } else {
16607935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    arg=null;
16617935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    noArg=true;
16627935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                }
16637935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
16647935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            ++i;
16657935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            int prevDestLength=dest.length;
16667935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            Format formatter = null;
16677935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if (noArg) {
16687935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                dest.append("{"+argName+"}");
16697935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            } else if (arg == null) {
16707935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                dest.append("null");
16717935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            } else if(pluralNumber!=null && pluralNumber.numberArgIndex==(i-2)) {
16727935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                if(pluralNumber.offset == 0) {
16737935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    // The number was already formatted with this formatter.
16747935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    dest.formatAndAppend(pluralNumber.formatter, pluralNumber.number, pluralNumber.numberString);
16757935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                } else {
16767935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    // Do not use the formatted (number-offset) string for a named argument
16777935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    // that formats the number without subtracting the offset.
16787935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    dest.formatAndAppend(pluralNumber.formatter, arg);
16797935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                }
16807935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            } else if(cachedFormatters!=null && (formatter=cachedFormatters.get(i - 2))!=null) {
16817935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                // Handles all ArgType.SIMPLE, and formatters from setFormat() and its siblings.
16827935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                if (    formatter instanceof ChoiceFormat ||
16837935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        formatter instanceof PluralFormat ||
16847935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        formatter instanceof SelectFormat) {
16857935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    // We only handle nested formats here if they were provided via setFormat() or its siblings.
16867935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    // Otherwise they are not cached and instead handled below according to argType.
16877935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    String subMsgString = formatter.format(arg);
16887935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    if (subMsgString.indexOf('{') >= 0 ||
16897935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                            (subMsgString.indexOf('\'') >= 0 && !msgPattern.jdkAposMode())) {
16907935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        MessageFormat subMsgFormat = new MessageFormat(subMsgString, ulocale);
16917935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        subMsgFormat.format(0, null, args, argsMap, dest, null);
16927935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    } else if (dest.attributes == null) {
16937935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        dest.append(subMsgString);
16947935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    } else {
16957935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        // This formats the argument twice, once above to get the subMsgString
16967935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        // and then once more here.
16977935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        // It only happens in formatToCharacterIterator()
16987935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        // on a complex Format set via setFormat(),
16997935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        // and only when the selected subMsgString does not need further formatting.
17007935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        // This imitates ICU 4.6 behavior.
17017935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        dest.formatAndAppend(formatter, arg);
17027935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    }
17037935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                } else {
17047935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    dest.formatAndAppend(formatter, arg);
17057935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                }
17067935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            } else if(
17077935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    argType==ArgType.NONE ||
17087935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    (cachedFormatters!=null && cachedFormatters.containsKey(i - 2))) {
17097935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                // ArgType.NONE, or
17107935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                // any argument which got reset to null via setFormat() or its siblings.
17117935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                if (arg instanceof Number) {
17127935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    // format number if can
17137935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    dest.formatAndAppend(getStockNumberFormatter(), arg);
17147935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                 } else if (arg instanceof Date) {
17157935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    // format a Date if can
17167935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    dest.formatAndAppend(getStockDateFormatter(), arg);
17177935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                } else {
17187935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    dest.append(arg.toString());
17197935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                }
17207935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            } else if(argType==ArgType.CHOICE) {
17217935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                if (!(arg instanceof Number)) {
17227935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    throw new IllegalArgumentException("'" + arg + "' is not a Number");
17237935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                }
17247935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                double number = ((Number)arg).doubleValue();
17257935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                int subMsgStart=findChoiceSubMessage(msgPattern, i, number);
17267935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                formatComplexSubMessage(subMsgStart, null, args, argsMap, dest);
17277935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            } else if(argType.hasPluralStyle()) {
17287935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                if (!(arg instanceof Number)) {
17297935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    throw new IllegalArgumentException("'" + arg + "' is not a Number");
17307935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                }
17317935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                PluralSelectorProvider selector;
17327935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                if(argType == ArgType.PLURAL) {
17337935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    if (pluralProvider == null) {
17347935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        pluralProvider = new PluralSelectorProvider(this, PluralType.CARDINAL);
17357935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    }
17367935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    selector = pluralProvider;
17377935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                } else {
17387935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    if (ordinalProvider == null) {
17397935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        ordinalProvider = new PluralSelectorProvider(this, PluralType.ORDINAL);
17407935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    }
17417935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    selector = ordinalProvider;
17427935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                }
17437935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                Number number = (Number)arg;
17447935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                double offset=msgPattern.getPluralOffset(i);
17457935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                PluralSelectorContext context =
17467935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        new PluralSelectorContext(i, argName, number, offset);
17477935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                int subMsgStart=PluralFormat.findSubMessage(
17487935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        msgPattern, i, selector, context, number.doubleValue());
17497935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                formatComplexSubMessage(subMsgStart, context, args, argsMap, dest);
17507935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            } else if(argType==ArgType.SELECT) {
17517935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                int subMsgStart=SelectFormat.findSubMessage(msgPattern, i, arg.toString());
17527935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                formatComplexSubMessage(subMsgStart, null, args, argsMap, dest);
17537935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            } else {
17547935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                // This should never happen.
17557935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                throw new IllegalStateException("unexpected argType "+argType);
17567935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
17577935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            fp = updateMetaData(dest, prevDestLength, fp, argId);
17587935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            prevIndex=msgPattern.getPart(argLimit).getLimit();
17597935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            i=argLimit;
17607935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
17617935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
17627935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
17637935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private void formatComplexSubMessage(
17647935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            int msgStart, PluralSelectorContext pluralNumber,
17657935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            Object[] args, Map<String, Object> argsMap,
17667935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            AppendableWrapper dest) {
17677935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (!msgPattern.jdkAposMode()) {
17687935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            format(msgStart, pluralNumber, args, argsMap, dest, null);
17697935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            return;
17707935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
17717935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        // JDK compatibility mode: (see JDK MessageFormat.format() API docs)
17727935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        // - remove SKIP_SYNTAX; that is, remove half of the apostrophes
17737935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        // - if the result string contains an open curly brace '{' then
17747935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        //   instantiate a temporary MessageFormat object and format again;
17757935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        //   otherwise just append the result string
17767935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        String msgString = msgPattern.getPatternString();
17777935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        String subMsgString;
17787935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        StringBuilder sb = null;
17797935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        int prevIndex = msgPattern.getPart(msgStart).getLimit();
17807935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        for (int i = msgStart;;) {
17817935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            Part part = msgPattern.getPart(++i);
17827935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            Part.Type type = part.getType();
17837935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            int index = part.getIndex();
17847935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if (type == Part.Type.MSG_LIMIT) {
17857935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                if (sb == null) {
17867935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    subMsgString = msgString.substring(prevIndex, index);
17877935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                } else {
17887935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    subMsgString = sb.append(msgString, prevIndex, index).toString();
17897935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                }
17907935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                break;
17917935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            } else if (type == Part.Type.REPLACE_NUMBER || type == Part.Type.SKIP_SYNTAX) {
17927935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                if (sb == null) {
17937935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    sb = new StringBuilder();
17947935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                }
17957935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                sb.append(msgString, prevIndex, index);
17967935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                if (type == Part.Type.REPLACE_NUMBER) {
17977935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    if(pluralNumber.forReplaceNumber) {
17987935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        // number-offset was already formatted.
17997935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        sb.append(pluralNumber.numberString);
18007935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    } else {
18017935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        sb.append(getStockNumberFormatter().format(pluralNumber.number));
18027935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    }
18037935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                }
18047935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                prevIndex = part.getLimit();
18057935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            } else if (type == Part.Type.ARG_START) {
18067935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                if (sb == null) {
18077935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    sb = new StringBuilder();
18087935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                }
18097935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                sb.append(msgString, prevIndex, index);
18107935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                prevIndex = index;
18117935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                i = msgPattern.getLimitPartIndex(i);
18127935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                index = msgPattern.getPart(i).getLimit();
18137935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                MessagePattern.appendReducedApostrophes(msgString, prevIndex, index, sb);
18147935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                prevIndex = index;
18157935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
18167935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
18177935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (subMsgString.indexOf('{') >= 0) {
18187935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            MessageFormat subMsgFormat = new MessageFormat("", ulocale);
18197935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            subMsgFormat.applyPattern(subMsgString, MessagePattern.ApostropheMode.DOUBLE_REQUIRED);
18207935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            subMsgFormat.format(0, null, args, argsMap, dest, null);
18217935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        } else {
18227935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            dest.append(subMsgString);
18237935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
18247935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
18257935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
18267935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
18277935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Read as much literal string from the pattern string as possible. This stops
18287935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * as soon as it finds an argument, or it reaches the end of the string.
18297935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param from Index in the pattern string to start from.
18307935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @return A substring from the pattern string representing the longest possible
18312d2bb24f747c65578da13d5b13b82f0669690461Fredrik Roubert     *         substring with no arguments.
18327935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
18337935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private String getLiteralStringUntilNextArgument(int from) {
18347935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        StringBuilder b = new StringBuilder();
18357935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        String msgString=msgPattern.getPatternString();
18367935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        int prevIndex=msgPattern.getPart(from).getLimit();
18377935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        for(int i=from+1;; ++i) {
18387935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            Part part=msgPattern.getPart(i);
18397935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            Part.Type type=part.getType();
18407935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            int index=part.getIndex();
18417935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            b.append(msgString, prevIndex, index);
18427935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if(type==Part.Type.ARG_START || type==Part.Type.MSG_LIMIT) {
18437935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                return b.toString();
18447935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
18457935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            assert type==Part.Type.SKIP_SYNTAX || type==Part.Type.INSERT_CHAR :
18467935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    "Unexpected Part "+part+" in parsed message.";
18477935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            prevIndex=part.getLimit();
18487935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
18497935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
18507935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
18517935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private FieldPosition updateMetaData(AppendableWrapper dest, int prevLength,
18527935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                                         FieldPosition fp, Object argId) {
18537935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (dest.attributes != null && prevLength < dest.length) {
18547935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            dest.attributes.add(new AttributeAndPosition(argId, prevLength, dest.length));
18557935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
18567935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (fp != null && Field.ARGUMENT.equals(fp.getFieldAttribute())) {
18577935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            fp.setBeginIndex(prevLength);
18587935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            fp.setEndIndex(dest.length);
18597935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            return null;
18607935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
18617935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return fp;
18627935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
18637935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
18647935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    // This lives here because ICU4J does not have its own ChoiceFormat class.
18657935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
18667935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Finds the ChoiceFormat sub-message for the given number.
18677935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param pattern A MessagePattern.
18687935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param partIndex the index of the first ChoiceFormat argument style part.
18697935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param number a number to be mapped to one of the ChoiceFormat argument's intervals
18707935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @return the sub-message start part index.
18717935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
18727935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private static int findChoiceSubMessage(MessagePattern pattern, int partIndex, double number) {
18737935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        int count=pattern.countParts();
18747935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        int msgStart;
18757935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        // Iterate over (ARG_INT|DOUBLE, ARG_SELECTOR, message) tuples
18767935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        // until ARG_LIMIT or end of choice-only pattern.
18777935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        // Ignore the first number and selector and start the loop on the first message.
18787935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        partIndex+=2;
18797935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        for(;;) {
18807935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            // Skip but remember the current sub-message.
18817935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            msgStart=partIndex;
18827935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            partIndex=pattern.getLimitPartIndex(partIndex);
18837935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if(++partIndex>=count) {
18847935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                // Reached the end of the choice-only pattern.
18857935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                // Return with the last sub-message.
18867935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                break;
18877935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
18887935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            Part part=pattern.getPart(partIndex++);
18897935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            Part.Type type=part.getType();
18907935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if(type==Part.Type.ARG_LIMIT) {
18917935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                // Reached the end of the ChoiceFormat style.
18927935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                // Return with the last sub-message.
18937935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                break;
18947935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
18957935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            // part is an ARG_INT or ARG_DOUBLE
18967935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            assert type.hasNumericValue();
18977935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            double boundary=pattern.getNumericValue(part);
18987935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            // Fetch the ARG_SELECTOR character.
18997935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            int selectorIndex=pattern.getPatternIndex(partIndex++);
19007935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            char boundaryChar=pattern.getPatternString().charAt(selectorIndex);
19017935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if(boundaryChar=='<' ? !(number>boundary) : !(number>=boundary)) {
19027935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                // The number is in the interval between the previous boundary and the current one.
19037935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                // Return with the sub-message between them.
19047935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                // The !(a>b) and !(a>=b) comparisons are equivalent to
19057935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                // (a<=b) and (a<b) except they "catch" NaN.
19067935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                break;
19077935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
19087935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
19097935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return msgStart;
19107935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
19117935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
19127935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    // Ported from C++ ChoiceFormat::parse().
19137935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private static double parseChoiceArgument(
19147935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            MessagePattern pattern, int partIndex,
19157935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            String source, ParsePosition pos) {
19167935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        // find the best number (defined as the one with the longest parse)
19177935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        int start = pos.getIndex();
19187935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        int furthest = start;
19197935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        double bestNumber = Double.NaN;
19207935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        double tempNumber = 0.0;
19217935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        while (pattern.getPartType(partIndex) != Part.Type.ARG_LIMIT) {
19227935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            tempNumber = pattern.getNumericValue(pattern.getPart(partIndex));
19237935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            partIndex += 2;  // skip the numeric part and ignore the ARG_SELECTOR
19247935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            int msgLimit = pattern.getLimitPartIndex(partIndex);
19257935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            int len = matchStringUntilLimitPart(pattern, partIndex, msgLimit, source, start);
19267935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if (len >= 0) {
19277935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                int newIndex = start + len;
19287935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                if (newIndex > furthest) {
19297935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    furthest = newIndex;
19307935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    bestNumber = tempNumber;
19317935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    if (furthest == source.length()) {
19327935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        break;
19337935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    }
19347935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                }
19357935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
19367935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            partIndex = msgLimit + 1;
19377935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
19387935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (furthest == start) {
19397935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            pos.setErrorIndex(start);
19407935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        } else {
19417935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            pos.setIndex(furthest);
19427935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
19437935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return bestNumber;
19447935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
19457935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
19467935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
19477935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Matches the pattern string from the end of the partIndex to
19487935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * the beginning of the limitPartIndex,
19497935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * including all syntax except SKIP_SYNTAX,
19507935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * against the source string starting at sourceOffset.
19517935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * If they match, returns the length of the source string match.
19527935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Otherwise returns -1.
19537935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
19547935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private static int matchStringUntilLimitPart(
19557935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            MessagePattern pattern, int partIndex, int limitPartIndex,
19567935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            String source, int sourceOffset) {
19577935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        int matchingSourceLength = 0;
19587935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        String msgString = pattern.getPatternString();
19597935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        int prevIndex = pattern.getPart(partIndex).getLimit();
19607935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        for (;;) {
19617935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            Part part = pattern.getPart(++partIndex);
19627935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if (partIndex == limitPartIndex || part.getType() == Part.Type.SKIP_SYNTAX) {
19637935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                int index = part.getIndex();
19647935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                int length = index - prevIndex;
19657935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                if (length != 0 && !source.regionMatches(sourceOffset, msgString, prevIndex, length)) {
19667935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    return -1;  // mismatch
19677935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                }
19687935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                matchingSourceLength += length;
19697935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                if (partIndex == limitPartIndex) {
19707935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    return matchingSourceLength;
19717935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                }
19727935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                prevIndex = part.getLimit();  // SKIP_SYNTAX
19737935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
19747935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
19757935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
19767935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
19777935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
19787935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Finds the "other" sub-message.
19797935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param partIndex the index of the first PluralFormat argument style part.
19807935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @return the "other" sub-message start part index.
19817935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
19827935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private int findOtherSubMessage(int partIndex) {
19837935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        int count=msgPattern.countParts();
19847935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        MessagePattern.Part part=msgPattern.getPart(partIndex);
19857935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if(part.getType().hasNumericValue()) {
19867935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            ++partIndex;
19877935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
19887935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        // Iterate over (ARG_SELECTOR [ARG_INT|ARG_DOUBLE] message) tuples
19897935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        // until ARG_LIMIT or end of plural-only pattern.
19907935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        do {
19917935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            part=msgPattern.getPart(partIndex++);
19927935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            MessagePattern.Part.Type type=part.getType();
19937935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if(type==MessagePattern.Part.Type.ARG_LIMIT) {
19947935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                break;
19957935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
19967935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            assert type==MessagePattern.Part.Type.ARG_SELECTOR;
19977935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            // part is an ARG_SELECTOR followed by an optional explicit value, and then a message
19987935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if(msgPattern.partSubstringMatches(part, "other")) {
19997935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                return partIndex;
20007935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
20017935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if(msgPattern.getPartType(partIndex).hasNumericValue()) {
20027935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                ++partIndex;  // skip the numeric-value part of "=1" etc.
20037935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
20047935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            partIndex=msgPattern.getLimitPartIndex(partIndex);
20057935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        } while(++partIndex<count);
20067935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return 0;
20077935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
20087935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
20097935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
20107935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Returns the ARG_START index of the first occurrence of the plural number in a sub-message.
20117935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Returns -1 if it is a REPLACE_NUMBER.
20127935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Returns 0 if there is neither.
20137935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
20147935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private int findFirstPluralNumberArg(int msgStart, String argName) {
20157935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        for(int i=msgStart+1;; ++i) {
20167935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            Part part=msgPattern.getPart(i);
20177935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            Part.Type type=part.getType();
20187935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if(type==Part.Type.MSG_LIMIT) {
20197935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                return 0;
20207935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
20217935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if(type==Part.Type.REPLACE_NUMBER) {
20227935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                return -1;
20237935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
20247935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if(type==Part.Type.ARG_START) {
20257935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                ArgType argType=part.getArgType();
20267935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                if(argName.length()!=0 && (argType==ArgType.NONE || argType==ArgType.SIMPLE)) {
20277935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    part=msgPattern.getPart(i+1);  // ARG_NUMBER or ARG_NAME
20287935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    if(msgPattern.partSubstringMatches(part, argName)) {
20297935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        return i;
20307935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    }
20317935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                }
20327935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                i=msgPattern.getLimitPartIndex(i);
20337935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
20347935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
20357935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
20367935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
20377935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
20387935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Mutable input/output values for the PluralSelectorProvider.
20397935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Separate so that it is possible to make MessageFormat Freezable.
20407935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
20417935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private static final class PluralSelectorContext {
20427935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        private PluralSelectorContext(int start, String name, Number num, double off) {
20437935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            startIndex = start;
20447935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            argName = name;
20457935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            // number needs to be set even when select() is not called.
20467935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            // Keep it as a Number/Formattable:
20477935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            // For format() methods, and to preserve information (e.g., BigDecimal).
20487935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if(off == 0) {
20497935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                number = num;
20507935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            } else {
20517935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                number = num.doubleValue() - off;
20527935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
20537935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            offset = off;
20547935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
20557935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        @Override
20567935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        public String toString() {
20577935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            throw new AssertionError("PluralSelectorContext being formatted, rather than its number");
20587935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
20597935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
20607935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        // Input values for plural selection with decimals.
20617935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        int startIndex;
20627935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        String argName;
20637935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        /** argument number - plural offset */
20647935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        Number number;
20657935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        double offset;
20667935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        // Output values for plural selection with decimals.
20677935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        /** -1 if REPLACE_NUMBER, 0 arg not found, >0 ARG_START index */
20687935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        int numberArgIndex;
20697935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        Format formatter;
20707935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        /** formatted argument number - plural offset */
20717935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        String numberString;
20727935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        /** true if number-offset was formatted with the stock number formatter */
20737935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        boolean forReplaceNumber;
20747935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
20757935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
20767935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
20777935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * This provider helps defer instantiation of a PluralRules object
20787935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * until we actually need to select a keyword.
20797935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * For example, if the number matches an explicit-value selector like "=1"
20807935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * we do not need any PluralRules.
20817935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
20827935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private static final class PluralSelectorProvider implements PluralFormat.PluralSelector {
20837935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        public PluralSelectorProvider(MessageFormat mf, PluralType type) {
20847935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            msgFormat = mf;
20857935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            this.type = type;
20867935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
20872d2bb24f747c65578da13d5b13b82f0669690461Fredrik Roubert        @Override
20887935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        public String select(Object ctx, double number) {
20897935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if(rules == null) {
20907935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                rules = PluralRules.forLocale(msgFormat.ulocale, type);
20917935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
20927935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            // Select a sub-message according to how the number is formatted,
20937935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            // which is specified in the selected sub-message.
20947935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            // We avoid this circle by looking at how
20957935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            // the number is formatted in the "other" sub-message
20967935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            // which must always be present and usually contains the number.
20977935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            // Message authors should be consistent across sub-messages.
20987935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            PluralSelectorContext context = (PluralSelectorContext)ctx;
20997935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            int otherIndex = msgFormat.findOtherSubMessage(context.startIndex);
21007935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            context.numberArgIndex = msgFormat.findFirstPluralNumberArg(otherIndex, context.argName);
21017935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if(context.numberArgIndex > 0 && msgFormat.cachedFormatters != null) {
21027935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                context.formatter = msgFormat.cachedFormatters.get(context.numberArgIndex);
21037935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
21047935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if(context.formatter == null) {
21057935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                context.formatter = msgFormat.getStockNumberFormatter();
21067935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                context.forReplaceNumber = true;
21077935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
21087935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            assert context.number.doubleValue() == number;  // argument number minus the offset
21097935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            context.numberString = context.formatter.format(context.number);
21107935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if(context.formatter instanceof DecimalFormat) {
21117935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                FixedDecimal dec = ((DecimalFormat)context.formatter).getFixedDecimal(number);
21127935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                return rules.select(dec);
21137935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            } else {
21147935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                return rules.select(number);
21157935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
21167935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
21177935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        private MessageFormat msgFormat;
21187935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        private PluralRules rules;
21197935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        private PluralType type;
21207935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
21217935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
21227935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    @SuppressWarnings("unchecked")
21237935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private void format(Object arguments, AppendableWrapper result, FieldPosition fp) {
21247935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if ((arguments == null || arguments instanceof Map)) {
21257935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            format(null, (Map<String, Object>)arguments, result, fp);
21267935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        } else {
21277935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            format((Object[])arguments, null, result, fp);
21287935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
21297935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
21307935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
21317935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
21327935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Internal routine used by format.
21337935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *
21347935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @throws IllegalArgumentException if an argument in the
21357935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *         <code>arguments</code> map is not of the type
21367935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *         expected by the format element(s) that use it.
21377935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
21387935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private void format(Object[] arguments, Map<String, Object> argsMap,
21397935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        AppendableWrapper dest, FieldPosition fp) {
21407935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (arguments != null && msgPattern.hasNamedArguments()) {
21417935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            throw new IllegalArgumentException(
21427935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                "This method is not available in MessageFormat objects " +
21437935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                "that use alphanumeric argument names.");
21447935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
21457935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        format(0, null, arguments, argsMap, dest, fp);
21467935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
21477935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
21487935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private void resetPattern() {
21497935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (msgPattern != null) {
21507935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            msgPattern.clear();
21517935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
21527935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (cachedFormatters != null) {
21537935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            cachedFormatters.clear();
21547935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
21557935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        customFormatArgStarts = null;
21567935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
21577935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
21587935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private static final String[] typeList =
21597935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        { "number", "date", "time", "spellout", "ordinal", "duration" };
21607935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private static final int
21617935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        TYPE_NUMBER = 0,
21627935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        TYPE_DATE = 1,
21637935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        TYPE_TIME = 2,
21647935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        TYPE_SPELLOUT = 3,
21657935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        TYPE_ORDINAL = 4,
21667935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        TYPE_DURATION = 5;
21677935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
21687935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private static final String[] modifierList =
21697935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        {"", "currency", "percent", "integer"};
21707935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
21717935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private static final int
21727935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        MODIFIER_EMPTY = 0,
21737935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        MODIFIER_CURRENCY = 1,
21747935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        MODIFIER_PERCENT = 2,
21757935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        MODIFIER_INTEGER = 3;
21767935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
21777935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private static final String[] dateModifierList =
21787935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        {"", "short", "medium", "long", "full"};
21797935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
21807935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private static final int
21817935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        DATE_MODIFIER_EMPTY = 0,
21827935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        DATE_MODIFIER_SHORT = 1,
21837935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        DATE_MODIFIER_MEDIUM = 2,
21847935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        DATE_MODIFIER_LONG = 3,
21857935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        DATE_MODIFIER_FULL = 4;
21867935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
21877935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    // Creates an appropriate Format object for the type and style passed.
21887935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    // Both arguments cannot be null.
21897935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private Format createAppropriateFormat(String type, String style) {
21907935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        Format newFormat = null;
21917935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        int subformatType  = findKeyword(type, typeList);
21927935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        switch (subformatType){
21937935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        case TYPE_NUMBER:
21947935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            switch (findKeyword(style, modifierList)) {
21957935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            case MODIFIER_EMPTY:
21967935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                newFormat = NumberFormat.getInstance(ulocale);
21977935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                break;
21987935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            case MODIFIER_CURRENCY:
21997935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                newFormat = NumberFormat.getCurrencyInstance(ulocale);
22007935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                break;
22017935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            case MODIFIER_PERCENT:
22027935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                newFormat = NumberFormat.getPercentInstance(ulocale);
22037935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                break;
22047935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            case MODIFIER_INTEGER:
22057935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                newFormat = NumberFormat.getIntegerInstance(ulocale);
22067935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                break;
22077935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            default: // pattern
22087935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                newFormat = new DecimalFormat(style,
22097935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        new DecimalFormatSymbols(ulocale));
22107935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                break;
22117935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
22127935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            break;
22137935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        case TYPE_DATE:
22147935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            switch (findKeyword(style, dateModifierList)) {
22157935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            case DATE_MODIFIER_EMPTY:
22167935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                newFormat = DateFormat.getDateInstance(DateFormat.DEFAULT, ulocale);
22177935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                break;
22187935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            case DATE_MODIFIER_SHORT:
22197935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                newFormat = DateFormat.getDateInstance(DateFormat.SHORT, ulocale);
22207935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                break;
22217935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            case DATE_MODIFIER_MEDIUM:
22227935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                newFormat = DateFormat.getDateInstance(DateFormat.DEFAULT, ulocale);
22237935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                break;
22247935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            case DATE_MODIFIER_LONG:
22257935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                newFormat = DateFormat.getDateInstance(DateFormat.LONG, ulocale);
22267935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                break;
22277935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            case DATE_MODIFIER_FULL:
22287935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                newFormat = DateFormat.getDateInstance(DateFormat.FULL, ulocale);
22297935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                break;
22307935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            default:
22317935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                newFormat = new SimpleDateFormat(style, ulocale);
22327935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                break;
22337935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
22347935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            break;
22357935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        case TYPE_TIME:
22367935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            switch (findKeyword(style, dateModifierList)) {
22377935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            case DATE_MODIFIER_EMPTY:
22387935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                newFormat = DateFormat.getTimeInstance(DateFormat.DEFAULT, ulocale);
22397935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                break;
22407935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            case DATE_MODIFIER_SHORT:
22417935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                newFormat = DateFormat.getTimeInstance(DateFormat.SHORT, ulocale);
22427935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                break;
22437935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            case DATE_MODIFIER_MEDIUM:
22447935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                newFormat = DateFormat.getTimeInstance(DateFormat.DEFAULT, ulocale);
22457935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                break;
22467935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            case DATE_MODIFIER_LONG:
22477935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                newFormat = DateFormat.getTimeInstance(DateFormat.LONG, ulocale);
22487935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                break;
22497935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            case DATE_MODIFIER_FULL:
22507935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                newFormat = DateFormat.getTimeInstance(DateFormat.FULL, ulocale);
22517935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                break;
22527935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            default:
22537935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                newFormat = new SimpleDateFormat(style, ulocale);
22547935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                break;
22557935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
22567935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            break;
22577935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        case TYPE_SPELLOUT:
22587935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            {
22597935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                RuleBasedNumberFormat rbnf = new RuleBasedNumberFormat(ulocale,
22607935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        RuleBasedNumberFormat.SPELLOUT);
22617935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                String ruleset = style.trim();
22627935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                if (ruleset.length() != 0) {
22637935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    try {
22647935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        rbnf.setDefaultRuleSet(ruleset);
22657935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    }
22667935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    catch (Exception e) {
22677935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        // warn invalid ruleset
22687935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    }
22697935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                }
22707935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                newFormat = rbnf;
22717935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
22727935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            break;
22737935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        case TYPE_ORDINAL:
22747935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            {
22757935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                RuleBasedNumberFormat rbnf = new RuleBasedNumberFormat(ulocale,
22767935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        RuleBasedNumberFormat.ORDINAL);
22777935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                String ruleset = style.trim();
22787935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                if (ruleset.length() != 0) {
22797935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    try {
22807935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        rbnf.setDefaultRuleSet(ruleset);
22817935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    }
22827935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    catch (Exception e) {
22837935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        // warn invalid ruleset
22847935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    }
22857935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                }
22867935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                newFormat = rbnf;
22877935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
22887935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            break;
22897935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        case TYPE_DURATION:
22907935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            {
22917935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                RuleBasedNumberFormat rbnf = new RuleBasedNumberFormat(ulocale,
22927935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        RuleBasedNumberFormat.DURATION);
22937935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                String ruleset = style.trim();
22947935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                if (ruleset.length() != 0) {
22957935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    try {
22967935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        rbnf.setDefaultRuleSet(ruleset);
22977935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    }
22987935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    catch (Exception e) {
22997935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        // warn invalid ruleset
23007935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    }
23017935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                }
23027935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                newFormat = rbnf;
23037935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
23047935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            break;
23057935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        default:
23067935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            throw new IllegalArgumentException("Unknown format type \"" + type + "\"");
23077935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
23087935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return newFormat;
23097935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
23107935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
23117935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private static final Locale rootLocale = new Locale("");  // Locale.ROOT only @since 1.6
23127935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
23137935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private static final int findKeyword(String s, String[] list) {
23147935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        s = PatternProps.trimWhiteSpace(s).toLowerCase(rootLocale);
23157935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        for (int i = 0; i < list.length; ++i) {
23167935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if (s.equals(list[i]))
23177935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                return i;
23187935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
23197935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return -1;
23207935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
23217935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
23227935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
23237935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Custom serialization, new in ICU 4.8.
23247935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * We do not want to use default serialization because we only have a small
23257935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * amount of persistent state which is better expressed explicitly
23267935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * rather than via writing field objects.
23277935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param out The output stream.
23287935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @serialData Writes the locale as a BCP 47 language tag string,
23297935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * the MessagePattern.ApostropheMode as an object,
23307935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * and the pattern string (null if none was applied).
23317935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Followed by an int with the number of (int formatIndex, Object formatter) pairs,
23327935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * and that many such pairs, corresponding to previous setFormat() calls for custom formats.
23337935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Followed by an int with the number of (int, Object) pairs,
23347935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * and that many such pairs, for future (post-ICU 4.8) extension of the serialization format.
23357935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
23367935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private void writeObject(java.io.ObjectOutputStream out) throws IOException {
23377935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        out.defaultWriteObject();
23387935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        // ICU 4.8 custom serialization.
23397935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        // locale as a BCP 47 language tag
23407935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        out.writeObject(ulocale.toLanguageTag());
23417935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        // ApostropheMode
23427935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (msgPattern == null) {
23437935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            msgPattern = new MessagePattern();
23447935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
23457935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        out.writeObject(msgPattern.getApostropheMode());
23467935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        // message pattern string
23477935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        out.writeObject(msgPattern.getPatternString());
23487935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        // custom formatters
23497935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (customFormatArgStarts == null || customFormatArgStarts.isEmpty()) {
23507935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            out.writeInt(0);
23517935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        } else {
23527935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            out.writeInt(customFormatArgStarts.size());
23537935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            int formatIndex = 0;
23547935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            for (int partIndex = 0; (partIndex = nextTopLevelArgStart(partIndex)) >= 0;) {
23557935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                if (customFormatArgStarts.contains(partIndex)) {
23567935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    out.writeInt(formatIndex);
23577935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    out.writeObject(cachedFormatters.get(partIndex));
23587935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                }
23597935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                ++formatIndex;
23607935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
23617935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
23627935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        // number of future (int, Object) pairs
23637935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        out.writeInt(0);
23647935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
23657935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
23667935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
23677935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Custom deserialization, new in ICU 4.8. See comments on writeObject().
23687935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @throws InvalidObjectException if the objects read from the stream is invalid.
23697935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
23707935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
23717935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        in.defaultReadObject();
23727935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        // ICU 4.8 custom deserialization.
23737935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        String languageTag = (String)in.readObject();
23747935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        ulocale = ULocale.forLanguageTag(languageTag);
23757935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        MessagePattern.ApostropheMode aposMode = (MessagePattern.ApostropheMode)in.readObject();
23767935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (msgPattern == null || aposMode != msgPattern.getApostropheMode()) {
23777935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            msgPattern = new MessagePattern(aposMode);
23787935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
23797935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        String msg = (String)in.readObject();
23807935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (msg != null) {
23817935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            applyPattern(msg);
23827935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
23837935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        // custom formatters
23847935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        for (int numFormatters = in.readInt(); numFormatters > 0; --numFormatters) {
23857935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            int formatIndex = in.readInt();
23867935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            Format formatter = (Format)in.readObject();
23877935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            setFormat(formatIndex, formatter);
23887935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
23897935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        // skip future (int, Object) pairs
23907935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        for (int numPairs = in.readInt(); numPairs > 0; --numPairs) {
23917935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            in.readInt();
23927935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            in.readObject();
23937935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
23947935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
23957935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
23967935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private void cacheExplicitFormats() {
23977935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (cachedFormatters != null) {
23987935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            cachedFormatters.clear();
23997935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
24007935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        customFormatArgStarts = null;
24017935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        // The last two "parts" can at most be ARG_LIMIT and MSG_LIMIT
24027935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        // which we need not examine.
24037935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        int limit = msgPattern.countParts() - 2;
24047935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        // This loop starts at part index 1 because we do need to examine
24057935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        // ARG_START parts. (But we can ignore the MSG_START.)
24067935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        for(int i=1; i < limit; ++i) {
24077935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            Part part = msgPattern.getPart(i);
24087935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if(part.getType()!=Part.Type.ARG_START) {
24097935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                continue;
24107935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
24117935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            ArgType argType=part.getArgType();
24127935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if(argType != ArgType.SIMPLE) {
24137935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                continue;
24147935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
24157935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            int index = i;
24167935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            i += 2;
24177935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            String explicitType = msgPattern.getSubstring(msgPattern.getPart(i++));
24187935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            String style = "";
24197935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if ((part = msgPattern.getPart(i)).getType() == MessagePattern.Part.Type.ARG_STYLE) {
24207935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                style = msgPattern.getSubstring(part);
24217935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                ++i;
24227935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
24237935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            Format formatter = createAppropriateFormat(explicitType, style);
24247935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            setArgStartFormat(index, formatter);
24257935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
24267935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
24277935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
24287935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
24297935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Sets a formatter for a MessagePattern ARG_START part index.
24307935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
24317935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private void setArgStartFormat(int argStart, Format formatter) {
24327935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (cachedFormatters == null) {
24337935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            cachedFormatters = new HashMap<Integer, Format>();
24347935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
24357935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        cachedFormatters.put(argStart, formatter);
24367935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
24377935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
24387935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
24397935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Sets a custom formatter for a MessagePattern ARG_START part index.
24407935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * "Custom" formatters are provided by the user via setFormat() or similar APIs.
24417935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
24427935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private void setCustomArgStartFormat(int argStart, Format formatter) {
24437935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        setArgStartFormat(argStart, formatter);
24447935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (customFormatArgStarts == null) {
24457935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            customFormatArgStarts = new HashSet<Integer>();
24467935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
24477935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        customFormatArgStarts.add(argStart);
24487935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
24497935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
24507935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private static final char SINGLE_QUOTE = '\'';
24517935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private static final char CURLY_BRACE_LEFT = '{';
24527935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private static final char CURLY_BRACE_RIGHT = '}';
24537935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
24547935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private static final int STATE_INITIAL = 0;
24557935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private static final int STATE_SINGLE_QUOTE = 1;
24567935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private static final int STATE_IN_QUOTE = 2;
24577935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private static final int STATE_MSG_ELEMENT = 3;
24587935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
24597935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
24607935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * {@icu} Converts an 'apostrophe-friendly' pattern into a standard
24617935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * pattern.
24627935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * <em>This is obsolete for ICU 4.8 and higher MessageFormat pattern strings.</em>
246303f16b04d95bbaa98f702b69791b0de29ac75915Yoshito Umaoka     * It can still be useful together with {@link java.text.MessageFormat}.
24647935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *
24657935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * <p>See the class description for more about apostrophes and quoting,
246603f16b04d95bbaa98f702b69791b0de29ac75915Yoshito Umaoka     * and differences between ICU and {@link java.text.MessageFormat}.
24677935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *
246803f16b04d95bbaa98f702b69791b0de29ac75915Yoshito Umaoka     * <p>{@link java.text.MessageFormat} and ICU 4.6 and earlier MessageFormat
24697935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * treat all ASCII apostrophes as
24707935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * quotes, which is problematic in some languages, e.g.
24717935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * French, where apostrophe is commonly used.  This utility
24727935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * assumes that only an unpaired apostrophe immediately before
24737935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * a brace is a true quote.  Other unpaired apostrophes are paired,
24747935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * and the resulting standard pattern string is returned.
24757935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *
24767935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * <p><b>Note</b>: It is not guaranteed that the returned pattern
24777935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * is indeed a valid pattern.  The only effect is to convert
24787935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * between patterns having different quoting semantics.
24797935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *
24807935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * <p><b>Note</b>: This method only works on top-level messageText,
24817935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * not messageText nested inside a complexArg.
24827935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     *
24837935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param pattern the 'apostrophe-friendly' pattern to convert
24847935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @return the standard equivalent of the original pattern
24857935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @stable ICU 3.4
24867935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
24877935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public static String autoQuoteApostrophe(String pattern) {
24887935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        StringBuilder buf = new StringBuilder(pattern.length() * 2);
24897935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        int state = STATE_INITIAL;
24907935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        int braceCount = 0;
24917935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        for (int i = 0, j = pattern.length(); i < j; ++i) {
24927935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            char c = pattern.charAt(i);
24937935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            switch (state) {
24947935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            case STATE_INITIAL:
24957935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                switch (c) {
24967935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                case SINGLE_QUOTE:
24977935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    state = STATE_SINGLE_QUOTE;
24987935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    break;
24997935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                case CURLY_BRACE_LEFT:
25007935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    state = STATE_MSG_ELEMENT;
25017935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    ++braceCount;
25027935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    break;
25037935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                }
25047935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                break;
25057935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            case STATE_SINGLE_QUOTE:
25067935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                switch (c) {
25077935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                case SINGLE_QUOTE:
25087935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    state = STATE_INITIAL;
25097935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    break;
25107935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                case CURLY_BRACE_LEFT:
25117935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                case CURLY_BRACE_RIGHT:
25127935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    state = STATE_IN_QUOTE;
25137935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    break;
25147935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                default:
25157935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    buf.append(SINGLE_QUOTE);
25167935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    state = STATE_INITIAL;
25177935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    break;
25187935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                }
25197935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                break;
25207935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            case STATE_IN_QUOTE:
25217935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                switch (c) {
25227935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                case SINGLE_QUOTE:
25237935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    state = STATE_INITIAL;
25247935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    break;
25257935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                }
25267935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                break;
25277935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            case STATE_MSG_ELEMENT:
25287935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                switch (c) {
25297935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                case CURLY_BRACE_LEFT:
25307935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    ++braceCount;
25317935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    break;
25327935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                case CURLY_BRACE_RIGHT:
25337935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    if (--braceCount == 0) {
25347935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        state = STATE_INITIAL;
25357935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    }
25367935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    break;
25377935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                }
25387935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                break;
25397935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            ///CLOVER:OFF
25407935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            default: // Never happens.
25417935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                break;
25427935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            ///CLOVER:ON
25437935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
25447935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            buf.append(c);
25457935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
25467935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        // End of scan
25477935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (state == STATE_SINGLE_QUOTE || state == STATE_IN_QUOTE) {
25487935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            buf.append(SINGLE_QUOTE);
25497935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
25507935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return new String(buf);
25517935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
25527935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
25537935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
25547935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Convenience wrapper for Appendable, tracks the result string length.
25557935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Also, Appendable throws IOException, and we turn that into a RuntimeException
25567935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * so that we need no throws clauses.
25577935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
25587935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private static final class AppendableWrapper {
25597935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        public AppendableWrapper(StringBuilder sb) {
25607935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            app = sb;
25617935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            length = sb.length();
25627935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            attributes = null;
25637935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
25647935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
25657935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        public AppendableWrapper(StringBuffer sb) {
25667935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            app = sb;
25677935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            length = sb.length();
25687935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            attributes = null;
25697935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
25707935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
25717935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        public void useAttributes() {
25727935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            attributes = new ArrayList<AttributeAndPosition>();
25737935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
25747935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
25757935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        public void append(CharSequence s) {
25767935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            try {
25777935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                app.append(s);
25787935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                length += s.length();
25797935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            } catch(IOException e) {
25807935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                throw new ICUUncheckedIOException(e);
25817935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
25827935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
25837935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
25847935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        public void append(CharSequence s, int start, int limit) {
25857935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            try {
25867935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                app.append(s, start, limit);
25877935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                length += limit - start;
25887935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            } catch(IOException e) {
25897935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                throw new ICUUncheckedIOException(e);
25907935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
25917935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
25927935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
25937935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        public void append(CharacterIterator iterator) {
25947935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            length += append(app, iterator);
25957935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
25967935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
25977935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        public static int append(Appendable result, CharacterIterator iterator) {
25987935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            try {
25997935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                int start = iterator.getBeginIndex();
26007935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                int limit = iterator.getEndIndex();
26017935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                int length = limit - start;
26027935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                if (start < limit) {
26037935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    result.append(iterator.first());
26047935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    while (++start < limit) {
26057935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        result.append(iterator.next());
26067935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    }
26077935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                }
26087935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                return length;
26097935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            } catch(IOException e) {
26107935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                throw new ICUUncheckedIOException(e);
26117935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
26127935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
26137935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
26147935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        public void formatAndAppend(Format formatter, Object arg) {
26157935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if (attributes == null) {
26167935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                append(formatter.format(arg));
26177935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            } else {
26187935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                AttributedCharacterIterator formattedArg = formatter.formatToCharacterIterator(arg);
26197935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                int prevLength = length;
26207935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                append(formattedArg);
26217935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                // Copy all of the attributes from formattedArg to our attributes list.
26227935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                formattedArg.first();
26237935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                int start = formattedArg.getIndex();  // Should be 0 but might not be.
26247935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                int limit = formattedArg.getEndIndex();  // == start + length - prevLength
26257935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                int offset = prevLength - start;  // Adjust attribute indexes for the result string.
26267935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                while (start < limit) {
26277935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    Map<Attribute, Object> map = formattedArg.getAttributes();
26287935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    int runLimit = formattedArg.getRunLimit();
26297935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    if (map.size() != 0) {
26307935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        for (Map.Entry<Attribute, Object> entry : map.entrySet()) {
26317935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                           attributes.add(
26327935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                               new AttributeAndPosition(
26337935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                                   entry.getKey(), entry.getValue(),
26347935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                                   offset + start, offset + runLimit));
26357935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        }
26367935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    }
26377935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    start = runLimit;
26387935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    formattedArg.setIndex(start);
26397935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                }
26407935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
26417935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
26427935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
26437935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        public void formatAndAppend(Format formatter, Object arg, String argString) {
26447935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if (attributes == null && argString != null) {
26457935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                append(argString);
26467935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            } else {
26477935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                formatAndAppend(formatter, arg);
26487935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
26497935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
26507935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
26517935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        private Appendable app;
26527935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        private int length;
26537935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        private List<AttributeAndPosition> attributes;
26547935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
26557935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
26567935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private static final class AttributeAndPosition {
26577935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        /**
26587935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert         * Defaults the field to Field.ARGUMENT.
26597935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert         */
26607935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        public AttributeAndPosition(Object fieldValue, int startIndex, int limitIndex) {
26617935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            init(Field.ARGUMENT, fieldValue, startIndex, limitIndex);
26627935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
26637935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
26647935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        public AttributeAndPosition(Attribute field, Object fieldValue, int startIndex, int limitIndex) {
26657935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            init(field, fieldValue, startIndex, limitIndex);
26667935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
26677935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
26687935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        public void init(Attribute field, Object fieldValue, int startIndex, int limitIndex) {
26697935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            key = field;
26707935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            value = fieldValue;
26717935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            start = startIndex;
26727935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            limit = limitIndex;
26737935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
26747935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
26757935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        private Attribute key;
26767935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        private Object value;
26777935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        private int start;
26787935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        private int limit;
26797935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
26807935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert}
2681