15d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath/*
25d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath**********************************************************************
38d05787d6a4b5762d790ccd2a9ed9dc8885986efNarayan Kamath* Copyright (c) 2004-2014, International Business Machines
45d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath* Corporation and others.  All Rights Reserved.
55d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath**********************************************************************
65d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath* Author: Alan Liu
75d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath* Created: April 6, 2004
85d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath* Since: ICU 3.0
95d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath**********************************************************************
105d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath*/
118d05787d6a4b5762d790ccd2a9ed9dc8885986efNarayan Kamathpackage com.ibm.icu.simple;
125d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
135d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamathimport java.io.IOException;
145d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamathimport java.io.InvalidObjectException;
155d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamathimport java.text.AttributedCharacterIterator;
165d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamathimport java.text.AttributedCharacterIterator.Attribute;
175d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamathimport java.text.AttributedString;
185d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamathimport java.text.CharacterIterator;
195d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamathimport java.text.ChoiceFormat;
208d05787d6a4b5762d790ccd2a9ed9dc8885986efNarayan Kamathimport java.text.DateFormat;
218d05787d6a4b5762d790ccd2a9ed9dc8885986efNarayan Kamathimport java.text.DecimalFormat;
228d05787d6a4b5762d790ccd2a9ed9dc8885986efNarayan Kamathimport java.text.DecimalFormatSymbols;
235d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamathimport java.text.FieldPosition;
245d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamathimport java.text.Format;
258d05787d6a4b5762d790ccd2a9ed9dc8885986efNarayan Kamathimport java.text.NumberFormat;
265d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamathimport java.text.ParseException;
275d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamathimport java.text.ParsePosition;
288d05787d6a4b5762d790ccd2a9ed9dc8885986efNarayan Kamathimport java.text.SimpleDateFormat;
295d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamathimport java.util.ArrayList;
305d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamathimport java.util.Date;
315d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamathimport java.util.HashMap;
325d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamathimport java.util.HashSet;
335d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamathimport java.util.List;
345d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamathimport java.util.Locale;
355d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamathimport java.util.Map;
365d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamathimport java.util.Set;
375d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
385d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamathimport com.ibm.icu.impl.PatternProps;
398d05787d6a4b5762d790ccd2a9ed9dc8885986efNarayan Kamathimport com.ibm.icu.simple.PluralRules.PluralType;
408d05787d6a4b5762d790ccd2a9ed9dc8885986efNarayan Kamathimport com.ibm.icu.text.MessagePattern;
415d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamathimport com.ibm.icu.text.MessagePattern.ArgType;
425d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamathimport com.ibm.icu.text.MessagePattern.Part;
438d05787d6a4b5762d790ccd2a9ed9dc8885986efNarayan Kamathimport com.ibm.icu.text.SelectFormat;
448d05787d6a4b5762d790ccd2a9ed9dc8885986efNarayan Kamathimport com.ibm.icu.util.ICUUncheckedIOException;
455d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
465d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath/**
475d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * {@icuenhanced java.text.MessageFormat}.{@icu _usage_}
485d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *
495d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * <p>MessageFormat prepares strings for display to users,
505d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * with optional arguments (variables/placeholders).
515d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * The arguments can occur in any order, which is necessary for translation
525d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * into languages with different grammars.
535d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *
545d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * <p>A MessageFormat is constructed from a <em>pattern</em> string
555d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * with arguments in {curly braces} which will be replaced by formatted values.
565d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *
575d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * <p><code>MessageFormat</code> differs from the other <code>Format</code>
585d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * classes in that you create a <code>MessageFormat</code> object with one
595d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * of its constructors (not with a <code>getInstance</code> style factory
605d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * method). Factory methods aren't necessary because <code>MessageFormat</code>
615d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * itself doesn't implement locale-specific behavior. Any locale-specific
625d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * behavior is defined by the pattern that you provide and the
635d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * subformats used for inserted arguments.
645d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *
655d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * <p>Arguments can be named (using identifiers) or numbered (using small ASCII-digit integers).
665d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * Some of the API methods work only with argument numbers and throw an exception
675d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * if the pattern has named arguments (see {@link #usesNamedArguments()}).
685d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *
695d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * <p>An argument might not specify any format type. In this case,
705d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * a Number value is formatted with a default (for the locale) NumberFormat,
715d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * a Date value is formatted with a default (for the locale) DateFormat,
725d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * and for any other value its toString() value is used.
735d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *
745d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * <p>An argument might specify a "simple" type for which the specified
755d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * Format object is created, cached and used.
765d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *
775d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * <p>An argument might have a "complex" type with nested MessageFormat sub-patterns.
785d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * During formatting, one of these sub-messages is selected according to the argument value
795d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * and recursively formatted.
805d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *
815d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * <p>After construction, a custom Format object can be set for
825d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * a top-level argument, overriding the default formatting and parsing behavior
835d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * for that argument.
845d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * However, custom formatting can be achieved more simply by writing
855d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * a typeless argument in the pattern string
865d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * and supplying it with a preformatted string value.
875d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *
885d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * <p>When formatting, MessageFormat takes a collection of argument values
895d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * and writes an output string.
905d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * The argument values may be passed as an array
915d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * (when the pattern contains only numbered arguments)
925d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * or as a Map (which works for both named and numbered arguments).
935d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *
945d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * <p>Each argument is matched with one of the input values by array index or map key
955d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * and formatted according to its pattern specification
965d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * (or using a custom Format object if one was set).
975d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * A numbered pattern argument is matched with a map key that contains that number
985d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * as an ASCII-decimal-digit string (without leading zero).
995d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *
1005d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * <h4><a name="patterns">Patterns and Their Interpretation</a></h4>
1015d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *
1025d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * <code>MessageFormat</code> uses patterns of the following form:
1035d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * <blockquote><pre>
1045d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * message = messageText (argument messageText)*
1055d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * argument = noneArg | simpleArg | complexArg
1065d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * complexArg = choiceArg | pluralArg | selectArg | selectordinalArg
1075d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *
1085d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * noneArg = '{' argNameOrNumber '}'
1095d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * simpleArg = '{' argNameOrNumber ',' argType [',' argStyle] '}'
1105d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * choiceArg = '{' argNameOrNumber ',' "choice" ',' choiceStyle '}'
1115d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * pluralArg = '{' argNameOrNumber ',' "plural" ',' pluralStyle '}'
1125d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * selectArg = '{' argNameOrNumber ',' "select" ',' selectStyle '}'
1135d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * selectordinalArg = '{' argNameOrNumber ',' "selectordinal" ',' pluralStyle '}'
1145d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *
1155d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * choiceStyle: see {@link ChoiceFormat}
1165d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * pluralStyle: see {@link PluralFormat}
1175d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * selectStyle: see {@link SelectFormat}
1185d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *
1195d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * argNameOrNumber = argName | argNumber
1205d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * argName = [^[[:Pattern_Syntax:][:Pattern_White_Space:]]]+
1215d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * argNumber = '0' | ('1'..'9' ('0'..'9')*)
1225d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *
1235d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * argType = "number" | "date" | "time" | "spellout" | "ordinal" | "duration"
1245d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * argStyle = "short" | "medium" | "long" | "full" | "integer" | "currency" | "percent" | argStyleText
1255d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * </pre></blockquote>
1265d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *
1275d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * <ul>
1285d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *   <li>messageText can contain quoted literal strings including syntax characters.
1295d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *       A quoted literal string begins with an ASCII apostrophe and a syntax character
1305d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *       (usually a {curly brace}) and continues until the next single apostrophe.
1315d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *       A double ASCII apostrohpe inside or outside of a quoted string represents
1325d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *       one literal apostrophe.
1335d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *   <li>Quotable syntax characters are the {curly braces} in all messageText parts,
1345d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *       plus the '#' sign in a messageText immediately inside a pluralStyle,
1355d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *       and the '|' symbol in a messageText immediately inside a choiceStyle.
1365d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *   <li>See also {@link MessagePattern.ApostropheMode}
1375d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *   <li>In argStyleText, every single ASCII apostrophe begins and ends quoted literal text,
1385d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *       and unquoted {curly braces} must occur in matched pairs.
1395d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * </ul>
1405d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *
1415d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * <p>Recommendation: Use the real apostrophe (single quote) character \u2019 for
1425d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * human-readable text, and use the ASCII apostrophe (\u0027 ' )
1435d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * only in program syntax, like quoting in MessageFormat.
1445d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * See the annotations for U+0027 Apostrophe in The Unicode Standard.
1455d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *
1465d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * <p>The <code>choice</code> argument type is deprecated.
1475d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * Use <code>plural</code> arguments for proper plural selection,
1485d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * and <code>select</code> arguments for simple selection among a fixed set of choices.
1495d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *
1505d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * <p>The <code>argType</code> and <code>argStyle</code> values are used to create
1515d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * a <code>Format</code> instance for the format element. The following
1525d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * table shows how the values map to Format instances. Combinations not
1535d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * shown in the table are illegal. Any <code>argStyleText</code> must
1545d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * be a valid pattern string for the Format subclass used.
1555d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *
1565d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * <p><table border=1>
1575d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *    <tr>
1585d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *       <th>argType
1595d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *       <th>argStyle
1605d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *       <th>resulting Format object
1615d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *    <tr>
1625d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *       <td colspan=2><i>(none)</i>
1635d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *       <td><code>null</code>
1645d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *    <tr>
1655d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *       <td rowspan=5><code>number</code>
1665d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *       <td><i>(none)</i>
1675d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *       <td><code>NumberFormat.getInstance(getLocale())</code>
1685d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *    <tr>
1695d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *       <td><code>integer</code>
1705d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *       <td><code>NumberFormat.getIntegerInstance(getLocale())</code>
1715d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *    <tr>
1725d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *       <td><code>currency</code>
1735d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *       <td><code>NumberFormat.getCurrencyInstance(getLocale())</code>
1745d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *    <tr>
1755d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *       <td><code>percent</code>
1765d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *       <td><code>NumberFormat.getPercentInstance(getLocale())</code>
1775d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *    <tr>
1785d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *       <td><i>argStyleText</i>
1795d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *       <td><code>new DecimalFormat(argStyleText, new DecimalFormatSymbols(getLocale()))</code>
1805d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *    <tr>
1815d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *       <td rowspan=6><code>date</code>
1825d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *       <td><i>(none)</i>
1835d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *       <td><code>DateFormat.getDateInstance(DateFormat.DEFAULT, getLocale())</code>
1845d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *    <tr>
1855d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *       <td><code>short</code>
1865d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *       <td><code>DateFormat.getDateInstance(DateFormat.SHORT, getLocale())</code>
1875d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *    <tr>
1885d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *       <td><code>medium</code>
1895d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *       <td><code>DateFormat.getDateInstance(DateFormat.DEFAULT, getLocale())</code>
1905d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *    <tr>
1915d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *       <td><code>long</code>
1925d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *       <td><code>DateFormat.getDateInstance(DateFormat.LONG, getLocale())</code>
1935d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *    <tr>
1945d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *       <td><code>full</code>
1955d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *       <td><code>DateFormat.getDateInstance(DateFormat.FULL, getLocale())</code>
1965d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *    <tr>
1975d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *       <td><i>argStyleText</i>
1985d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *       <td><code>new SimpleDateFormat(argStyleText, getLocale())
1995d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *    <tr>
2005d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *       <td rowspan=6><code>time</code>
2015d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *       <td><i>(none)</i>
2025d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *       <td><code>DateFormat.getTimeInstance(DateFormat.DEFAULT, getLocale())</code>
2035d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *    <tr>
2045d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *       <td><code>short</code>
2055d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *       <td><code>DateFormat.getTimeInstance(DateFormat.SHORT, getLocale())</code>
2065d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *    <tr>
2075d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *       <td><code>medium</code>
2085d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *       <td><code>DateFormat.getTimeInstance(DateFormat.DEFAULT, getLocale())</code>
2095d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *    <tr>
2105d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *       <td><code>long</code>
2115d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *       <td><code>DateFormat.getTimeInstance(DateFormat.LONG, getLocale())</code>
2125d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *    <tr>
2135d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *       <td><code>full</code>
2145d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *       <td><code>DateFormat.getTimeInstance(DateFormat.FULL, getLocale())</code>
2155d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *    <tr>
2165d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *       <td><i>argStyleText</i>
2175d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *       <td><code>new SimpleDateFormat(argStyleText, getLocale())
2185d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *    <tr>
2195d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *       <td><code>spellout</code>
2205d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *       <td><i>argStyleText (optional)</i>
2215d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *       <td><code>new RuleBasedNumberFormat(getLocale(), RuleBasedNumberFormat.SPELLOUT)
2225d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *           <br/>&nbsp;&nbsp;&nbsp;&nbsp;.setDefaultRuleset(argStyleText);</code>
2235d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *    <tr>
2245d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *       <td><code>ordinal</code>
2255d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *       <td><i>argStyleText (optional)</i>
2265d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *       <td><code>new RuleBasedNumberFormat(getLocale(), RuleBasedNumberFormat.ORDINAL)
2275d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *           <br/>&nbsp;&nbsp;&nbsp;&nbsp;.setDefaultRuleset(argStyleText);</code>
2285d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *    <tr>
2295d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *       <td><code>duration</code>
2305d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *       <td><i>argStyleText (optional)</i>
2315d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *       <td><code>new RuleBasedNumberFormat(getLocale(), RuleBasedNumberFormat.DURATION)
2325d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *           <br/>&nbsp;&nbsp;&nbsp;&nbsp;.setDefaultRuleset(argStyleText);</code>
2335d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * </table>
2345d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * <p>
2355d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *
2365d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * <h4><a name="diffsjdk">Differences from java.text.MessageFormat</a></h4>
2375d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *
2385d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * <p>The ICU MessageFormat supports both named and numbered arguments,
2395d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * while the JDK MessageFormat only supports numbered arguments.
2405d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * Named arguments make patterns more readable.
2415d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *
2425d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * <p>ICU implements a more user-friendly apostrophe quoting syntax.
2435d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * In message text, an apostrophe only begins quoting literal text
2445d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * if it immediately precedes a syntax character (mostly {curly braces}).<br>
2455d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * In the JDK MessageFormat, an apostrophe always begins quoting,
2465d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * which requires common text like "don't" and "aujourd'hui"
2475d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * to be written with doubled apostrophes like "don''t" and "aujourd''hui".
2485d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * For more details see {@link MessagePattern.ApostropheMode}.
2495d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *
2505d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * <p>ICU does not create a ChoiceFormat object for a choiceArg, pluralArg or selectArg
2515d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * but rather handles such arguments itself.
2525d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * The JDK MessageFormat does create and use a ChoiceFormat object
2535d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * (<code>new ChoiceFormat(argStyleText)</code>).
2545d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * The JDK does not support plural and select arguments at all.
2555d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *
2565d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * <h4>Usage Information</h4>
2575d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *
2585d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * <p>Here are some examples of usage:
2595d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * <blockquote>
2605d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * <pre>
2615d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * Object[] arguments = {
2625d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *     7,
2635d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *     new Date(System.currentTimeMillis()),
2645d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *     "a disturbance in the Force"
2655d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * };
2665d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *
2675d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * String result = MessageFormat.format(
2685d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *     "At {1,time} on {1,date}, there was {2} on planet {0,number,integer}.",
2695d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *     arguments);
2705d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *
2715d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * <em>output</em>: At 12:30 PM on Jul 3, 2053, there was a disturbance
2725d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *           in the Force on planet 7.
2735d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *
2745d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * </pre>
2755d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * </blockquote>
2765d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * Typically, the message format will come from resources, and the
2775d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * arguments will be dynamically set at runtime.
2785d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *
2795d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * <p>Example 2:
2805d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * <blockquote>
2815d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * <pre>
2825d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * Object[] testArgs = { 3, "MyDisk" };
2835d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *
2845d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * MessageFormat form = new MessageFormat(
2855d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *     "The disk \"{1}\" contains {0} file(s).");
2865d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *
2875d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * System.out.println(form.format(testArgs));
2885d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *
2895d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * // output, with different testArgs
2905d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * <em>output</em>: The disk "MyDisk" contains 0 file(s).
2915d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * <em>output</em>: The disk "MyDisk" contains 1 file(s).
2925d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * <em>output</em>: The disk "MyDisk" contains 1,273 file(s).
2935d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * </pre>
2945d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * </blockquote>
2955d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *
2965d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * <p>For messages that include plural forms, you can use a plural argument:
2975d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * <pre>
2985d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * MessageFormat msgFmt = new MessageFormat(
2995d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *     "{num_files, plural, " +
3005d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *     "=0{There are no files on disk \"{disk_name}\".}" +
3015d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *     "=1{There is one file on disk \"{disk_name}\".}" +
3025d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *     "other{There are # files on disk \"{disk_name}\".}}",
3035d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *     ULocale.ENGLISH);
3045d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * Map args = new HashMap();
3055d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * args.put("num_files", 0);
3065d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * args.put("disk_name", "MyDisk");
3075d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * System.out.println(msgFmt.format(args));
3085d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * args.put("num_files", 3);
3095d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * System.out.println(msgFmt.format(args));
3105d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *
3115d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * <em>output</em>:
3125d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * There are no files on disk "MyDisk".
3135d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * There are 3 files on "MyDisk".
3145d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * </pre>
3155d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * See {@link PluralFormat} and {@link PluralRules} for details.
3165d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *
3175d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * <h4><a name="synchronization">Synchronization</a></h4>
3185d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *
3195d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * <p>MessageFormats are not synchronized.
3205d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * It is recommended to create separate format instances for each thread.
3215d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * If multiple threads access a format concurrently, it must be synchronized
3225d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * externally.
3235d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *
3245d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * @see          java.util.Locale
3255d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * @see          Format
3265d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * @see          NumberFormat
3275d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * @see          DecimalFormat
3285d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * @see          ChoiceFormat
3295d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * @see          PluralFormat
3305d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * @see          SelectFormat
3315d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * @author       Mark Davis
3325d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * @author       Markus Scherer
3335d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * @stable ICU 3.0
3345d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath */
3358d05787d6a4b5762d790ccd2a9ed9dc8885986efNarayan Kamathpublic class MessageFormat extends Format {
3365d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
3375d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    // Incremented by 1 for ICU 4.8's new format.
3385d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    static final long serialVersionUID = 7136212545847378652L;
3395d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
3405d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    /**
3418d05787d6a4b5762d790ccd2a9ed9dc8885986efNarayan Kamath     * Formats a message pattern string with a variable number of name/value pair arguments.
3428d05787d6a4b5762d790ccd2a9ed9dc8885986efNarayan Kamath     * Creates an ICU MessageFormat for the locale and pattern,
3438d05787d6a4b5762d790ccd2a9ed9dc8885986efNarayan Kamath     * and formats with the arguments.
3448d05787d6a4b5762d790ccd2a9ed9dc8885986efNarayan Kamath     *
3458d05787d6a4b5762d790ccd2a9ed9dc8885986efNarayan Kamath     * @param locale Locale for number formatting and plural selection etc.
3468d05787d6a4b5762d790ccd2a9ed9dc8885986efNarayan Kamath     * @param msg an ICU-MessageFormat-syntax string
3478d05787d6a4b5762d790ccd2a9ed9dc8885986efNarayan Kamath     * @param nameValuePairs (argument name, argument value) pairs
3488d05787d6a4b5762d790ccd2a9ed9dc8885986efNarayan Kamath     */
3498d05787d6a4b5762d790ccd2a9ed9dc8885986efNarayan Kamath    public static final String formatNamedArgs(Locale locale, String msg, Object... nameValuePairs) {
3508d05787d6a4b5762d790ccd2a9ed9dc8885986efNarayan Kamath        StringBuilder result = new StringBuilder(msg.length());
3518d05787d6a4b5762d790ccd2a9ed9dc8885986efNarayan Kamath        new MessageFormat(msg, locale).format(0, null, null, null, nameValuePairs,
3528d05787d6a4b5762d790ccd2a9ed9dc8885986efNarayan Kamath                new AppendableWrapper(result), null);
3538d05787d6a4b5762d790ccd2a9ed9dc8885986efNarayan Kamath        return result.toString();
3548d05787d6a4b5762d790ccd2a9ed9dc8885986efNarayan Kamath    }
3558d05787d6a4b5762d790ccd2a9ed9dc8885986efNarayan Kamath
3568d05787d6a4b5762d790ccd2a9ed9dc8885986efNarayan Kamath    /**
3575d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * Constructs a MessageFormat for the default <code>FORMAT</code> locale and the
3585d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * specified pattern.
3595d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * Sets the locale and calls applyPattern(pattern).
3605d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *
3615d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @param pattern the pattern for this message format
3625d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @exception IllegalArgumentException if the pattern is invalid
3635d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @see Category#FORMAT
3645d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @stable ICU 3.0
3655d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     */
3665d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    public MessageFormat(String pattern) {
3678d05787d6a4b5762d790ccd2a9ed9dc8885986efNarayan Kamath        locale_ = Locale.getDefault();  // Category.FORMAT
3685d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        applyPattern(pattern);
3695d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    }
3705d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
3715d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    /**
3725d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * Constructs a MessageFormat for the specified locale and
3735d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * pattern.
3745d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * Sets the locale and calls applyPattern(pattern).
3755d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *
3765d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @param pattern the pattern for this message format
3775d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @param locale the locale for this message format
3785d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @exception IllegalArgumentException if the pattern is invalid
3795d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @stable ICU 3.0
3805d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     */
3815d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    public MessageFormat(String pattern, Locale locale) {
3828d05787d6a4b5762d790ccd2a9ed9dc8885986efNarayan Kamath        locale_ = locale;
3835d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        applyPattern(pattern);
3845d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    }
3855d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
3865d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    /**
3875d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * Returns the locale that's used when creating or comparing subformats.
3885d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *
3895d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @return the locale used when creating or comparing subformats
3905d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @stable ICU 3.0
3915d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     */
3925d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    public Locale getLocale() {
3938d05787d6a4b5762d790ccd2a9ed9dc8885986efNarayan Kamath        return locale_;
3945d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    }
3955d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
3965d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    /**
3975d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * Sets the pattern used by this message format.
3985d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * Parses the pattern and caches Format objects for simple argument types.
3995d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * Patterns and their interpretation are specified in the
4005d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * <a href="#patterns">class description</a>.
4015d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *
4025d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @param pttrn the pattern for this message format
4035d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @throws IllegalArgumentException if the pattern is invalid
4045d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @stable ICU 3.0
4055d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     */
4065d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    public void applyPattern(String pttrn) {
4075d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        try {
4085d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            if (msgPattern == null) {
4095d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                msgPattern = new MessagePattern(pttrn);
4105d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            } else {
4115d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                msgPattern.parse(pttrn);
4125d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            }
4135d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            // Cache the formats that are explicitly mentioned in the message pattern.
4145d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            cacheExplicitFormats();
4155d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        } catch(RuntimeException e) {
4165d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            resetPattern();
4175d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            throw e;
4185d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        }
4195d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    }
4205d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
4215d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    /**
4225d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * {@icu} Sets the ApostropheMode and the pattern used by this message format.
4235d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * Parses the pattern and caches Format objects for simple argument types.
4245d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * Patterns and their interpretation are specified in the
4255d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * <a href="#patterns">class description</a>.
4265d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * <p>
4275d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * This method is best used only once on a given object to avoid confusion about the mode,
4285d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * and after constructing the object with an empty pattern string to minimize overhead.
4295d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *
4305d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @param pattern the pattern for this message format
4315d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @param aposMode the new ApostropheMode
4325d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @throws IllegalArgumentException if the pattern is invalid
4335d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @see MessagePattern.ApostropheMode
4345d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @stable ICU 4.8
4355d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     */
4365d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    public void applyPattern(String pattern, MessagePattern.ApostropheMode aposMode) {
4375d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        if (msgPattern == null) {
4385d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            msgPattern = new MessagePattern(aposMode);
4395d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        } else if (aposMode != msgPattern.getApostropheMode()) {
4405d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            msgPattern.clearPatternAndSetApostropheMode(aposMode);
4415d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        }
4425d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        applyPattern(pattern);
4435d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    }
4445d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
4455d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    /**
4465d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * {@icu}
4475d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @return this instance's ApostropheMode.
4485d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @stable ICU 4.8
4495d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     */
4505d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    public MessagePattern.ApostropheMode getApostropheMode() {
4515d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        if (msgPattern == null) {
4525d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            msgPattern = new MessagePattern();  // Sets the default mode.
4535d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        }
4545d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        return msgPattern.getApostropheMode();
4555d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    }
4565d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
4575d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    /**
4585d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * Returns the applied pattern string.
4595d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @return the pattern string
4605d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @throws IllegalStateException after custom Format objects have been set
4615d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *         via setFormat() or similar APIs
4625d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @stable ICU 3.0
4635d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     */
4645d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    public String toPattern() {
4655d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        // Return the original, applied pattern string, or else "".
4665d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        // Note: This does not take into account
4675d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        // - changes from setFormat() and similar methods, or
4685d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        // - normalization of apostrophes and arguments, for example,
4695d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        //   whether some date/time/number formatter was created via a pattern
4705d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        //   but is equivalent to the "medium" default format.
4715d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        if (customFormatArgStarts != null) {
4725d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            throw new IllegalStateException(
4735d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    "toPattern() is not supported after custom Format objects "+
4745d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    "have been set via setFormat() or similar APIs");
4755d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        }
4765d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        if (msgPattern == null) {
4775d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            return "";
4785d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        }
4795d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        String originalPattern = msgPattern.getPatternString();
4805d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        return originalPattern == null ? "" : originalPattern;
4815d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    }
4825d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
4835d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    /**
4845d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * Returns the part index of the next ARG_START after partIndex, or -1 if there is none more.
4855d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @param partIndex Part index of the previous ARG_START (initially 0).
4865d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     */
4875d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    private int nextTopLevelArgStart(int partIndex) {
4885d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        if (partIndex != 0) {
4895d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            partIndex = msgPattern.getLimitPartIndex(partIndex);
4905d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        }
4915d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        for (;;) {
4925d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            MessagePattern.Part.Type type = msgPattern.getPartType(++partIndex);
4935d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            if (type == MessagePattern.Part.Type.ARG_START) {
4945d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                return partIndex;
4955d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            }
4965d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            if (type == MessagePattern.Part.Type.MSG_LIMIT) {
4975d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                return -1;
4985d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            }
4995d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        }
5005d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    }
5015d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
5025d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    private boolean argNameMatches(int partIndex, String argName, int argNumber) {
5035d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        Part part = msgPattern.getPart(partIndex);
5045d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        return part.getType() == MessagePattern.Part.Type.ARG_NAME ?
5055d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            msgPattern.partSubstringMatches(part, argName) :
5065d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            part.getValue() == argNumber;  // ARG_NUMBER
5075d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    }
5085d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
5095d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    private String getArgName(int partIndex) {
5105d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        Part part = msgPattern.getPart(partIndex);
5115d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        if (part.getType() == MessagePattern.Part.Type.ARG_NAME) {
5125d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            return msgPattern.getSubstring(part);
5135d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        } else {
5145d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            return Integer.toString(part.getValue());
5155d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        }
5165d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    }
5175d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
5185d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    /**
5195d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * Sets the Format objects to use for the values passed into
5205d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * <code>format</code> methods or returned from <code>parse</code>
5215d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * methods. The indices of elements in <code>newFormats</code>
5225d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * correspond to the argument indices used in the previously set
5235d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * pattern string.
5245d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * The order of formats in <code>newFormats</code> thus corresponds to
5255d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * the order of elements in the <code>arguments</code> array passed
5265d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * to the <code>format</code> methods or the result array returned
5275d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * by the <code>parse</code> methods.
5285d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * <p>
5295d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * If an argument index is used for more than one format element
5305d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * in the pattern string, then the corresponding new format is used
5315d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * for all such format elements. If an argument index is not used
5325d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * for any format element in the pattern string, then the
5335d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * corresponding new format is ignored. If fewer formats are provided
5345d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * than needed, then only the formats for argument indices less
5355d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * than <code>newFormats.length</code> are replaced.
5365d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *
5375d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * This method is only supported if the format does not use
5385d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * named arguments, otherwise an IllegalArgumentException is thrown.
5395d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *
5405d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @param newFormats the new formats to use
5415d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @throws NullPointerException if <code>newFormats</code> is null
5425d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @throws IllegalArgumentException if this formatter uses named arguments
5435d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @stable ICU 3.0
5445d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     */
5455d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    public void setFormatsByArgumentIndex(Format[] newFormats) {
5465d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        if (msgPattern.hasNamedArguments()) {
5475d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            throw new IllegalArgumentException(
5485d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    "This method is not available in MessageFormat objects " +
5495d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    "that use alphanumeric argument names.");
5505d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        }
5515d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        for (int partIndex = 0; (partIndex = nextTopLevelArgStart(partIndex)) >= 0;) {
5525d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            int argNumber = msgPattern.getPart(partIndex + 1).getValue();
5535d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            if (argNumber < newFormats.length) {
5545d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                setCustomArgStartFormat(partIndex, newFormats[argNumber]);
5555d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            }
5565d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        }
5575d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    }
5585d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
5595d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    /**
5605d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * {@icu} Sets the Format objects to use for the values passed into
5615d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * <code>format</code> methods or returned from <code>parse</code>
5625d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * methods. The keys in <code>newFormats</code> are the argument
5635d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * names in the previously set pattern string, and the values
5645d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * are the formats.
5655d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * <p>
5665d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * Only argument names from the pattern string are considered.
5675d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * Extra keys in <code>newFormats</code> that do not correspond
5685d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * to an argument name are ignored.  Similarly, if there is no
5695d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * format in newFormats for an argument name, the formatter
5705d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * for that argument remains unchanged.
5715d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * <p>
5725d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * This may be called on formats that do not use named arguments.
5735d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * In this case the map will be queried for key Strings that
5745d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * represent argument indices, e.g. "0", "1", "2" etc.
5755d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *
5765d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @param newFormats a map from String to Format providing new
5775d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *        formats for named arguments.
5785d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @stable ICU 3.8
5795d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     */
5805d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    public void setFormatsByArgumentName(Map<String, Format> newFormats) {
5815d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        for (int partIndex = 0; (partIndex = nextTopLevelArgStart(partIndex)) >= 0;) {
5825d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            String key = getArgName(partIndex + 1);
5835d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            if (newFormats.containsKey(key)) {
5845d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                setCustomArgStartFormat(partIndex, newFormats.get(key));
5855d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            }
5865d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        }
5875d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    }
5885d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
5895d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    /**
5905d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * Sets the Format objects to use for the format elements in the
5915d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * previously set pattern string.
5925d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * The order of formats in <code>newFormats</code> corresponds to
5935d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * the order of format elements in the pattern string.
5945d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * <p>
5955d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * If more formats are provided than needed by the pattern string,
5965d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * the remaining ones are ignored. If fewer formats are provided
5975d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * than needed, then only the first <code>newFormats.length</code>
5985d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * formats are replaced.
5995d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * <p>
6005d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * Since the order of format elements in a pattern string often
6015d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * changes during localization, it is generally better to use the
6025d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * {@link #setFormatsByArgumentIndex setFormatsByArgumentIndex}
6035d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * method, which assumes an order of formats corresponding to the
6045d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * order of elements in the <code>arguments</code> array passed to
6055d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * the <code>format</code> methods or the result array returned by
6065d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * the <code>parse</code> methods.
6075d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *
6085d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @param newFormats the new formats to use
6095d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @exception NullPointerException if <code>newFormats</code> is null
6105d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @stable ICU 3.0
6115d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     */
6125d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    public void setFormats(Format[] newFormats) {
6135d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        int formatNumber = 0;
6145d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        for (int partIndex = 0;
6155d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                formatNumber < newFormats.length &&
6165d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                (partIndex = nextTopLevelArgStart(partIndex)) >= 0;) {
6175d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            setCustomArgStartFormat(partIndex, newFormats[formatNumber]);
6185d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            ++formatNumber;
6195d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        }
6205d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    }
6215d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
6225d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    /**
6235d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * Sets the Format object to use for the format elements within the
6245d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * previously set pattern string that use the given argument
6255d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * index.
6265d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * The argument index is part of the format element definition and
6275d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * represents an index into the <code>arguments</code> array passed
6285d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * to the <code>format</code> methods or the result array returned
6295d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * by the <code>parse</code> methods.
6305d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * <p>
6315d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * If the argument index is used for more than one format element
6325d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * in the pattern string, then the new format is used for all such
6335d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * format elements. If the argument index is not used for any format
6345d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * element in the pattern string, then the new format is ignored.
6355d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *
6365d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * This method is only supported when exclusively numbers are used for
6375d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * argument names. Otherwise an IllegalArgumentException is thrown.
6385d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *
6395d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @param argumentIndex the argument index for which to use the new format
6405d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @param newFormat the new format to use
6415d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @throws IllegalArgumentException if this format uses named arguments
6425d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @stable ICU 3.0
6435d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     */
6445d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    public void setFormatByArgumentIndex(int argumentIndex, Format newFormat) {
6455d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        if (msgPattern.hasNamedArguments()) {
6465d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            throw new IllegalArgumentException(
6475d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    "This method is not available in MessageFormat objects " +
6485d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    "that use alphanumeric argument names.");
6495d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        }
6505d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        for (int partIndex = 0; (partIndex = nextTopLevelArgStart(partIndex)) >= 0;) {
6515d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            if (msgPattern.getPart(partIndex + 1).getValue() == argumentIndex) {
6525d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                setCustomArgStartFormat(partIndex, newFormat);
6535d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            }
6545d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        }
6555d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    }
6565d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
6575d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    /**
6585d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * {@icu} Sets the Format object to use for the format elements within the
6595d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * previously set pattern string that use the given argument
6605d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * name.
6615d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * <p>
6625d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * If the argument name is used for more than one format element
6635d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * in the pattern string, then the new format is used for all such
6645d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * format elements. If the argument name is not used for any format
6655d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * element in the pattern string, then the new format is ignored.
6665d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * <p>
6675d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * This API may be used on formats that do not use named arguments.
6685d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * In this case <code>argumentName</code> should be a String that names
6695d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * an argument index, e.g. "0", "1", "2"... etc.  If it does not name
6705d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * a valid index, the format will be ignored.  No error is thrown.
6715d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *
6725d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @param argumentName the name of the argument to change
6735d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @param newFormat the new format to use
6745d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @stable ICU 3.8
6755d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     */
6765d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    public void setFormatByArgumentName(String argumentName, Format newFormat) {
6775d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        int argNumber = MessagePattern.validateArgumentName(argumentName);
6785d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        if (argNumber < MessagePattern.ARG_NAME_NOT_NUMBER) {
6795d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            return;
6805d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        }
6815d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        for (int partIndex = 0; (partIndex = nextTopLevelArgStart(partIndex)) >= 0;) {
6825d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            if (argNameMatches(partIndex + 1, argumentName, argNumber)) {
6835d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                setCustomArgStartFormat(partIndex, newFormat);
6845d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            }
6855d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        }
6865d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    }
6875d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
6885d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    /**
6895d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * Sets the Format object to use for the format element with the given
6905d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * format element index within the previously set pattern string.
6915d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * The format element index is the zero-based number of the format
6925d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * element counting from the start of the pattern string.
6935d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * <p>
6945d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * Since the order of format elements in a pattern string often
6955d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * changes during localization, it is generally better to use the
6965d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * {@link #setFormatByArgumentIndex setFormatByArgumentIndex}
6975d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * method, which accesses format elements based on the argument
6985d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * index they specify.
6995d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *
7005d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @param formatElementIndex the index of a format element within the pattern
7015d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @param newFormat the format to use for the specified format element
7025d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @exception ArrayIndexOutOfBoundsException if formatElementIndex is equal to or
7035d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *            larger than the number of format elements in the pattern string
7045d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @stable ICU 3.0
7055d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     */
7065d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    public void setFormat(int formatElementIndex, Format newFormat) {
7075d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        int formatNumber = 0;
7085d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        for (int partIndex = 0; (partIndex = nextTopLevelArgStart(partIndex)) >= 0;) {
7095d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            if (formatNumber == formatElementIndex) {
7105d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                setCustomArgStartFormat(partIndex, newFormat);
7115d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                return;
7125d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            }
7135d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            ++formatNumber;
7145d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        }
7155d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        throw new ArrayIndexOutOfBoundsException(formatElementIndex);
7165d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    }
7175d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
7185d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    /**
7195d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * Returns the Format objects used for the values passed into
7205d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * <code>format</code> methods or returned from <code>parse</code>
7215d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * methods. The indices of elements in the returned array
7225d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * correspond to the argument indices used in the previously set
7235d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * pattern string.
7245d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * The order of formats in the returned array thus corresponds to
7255d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * the order of elements in the <code>arguments</code> array passed
7265d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * to the <code>format</code> methods or the result array returned
7275d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * by the <code>parse</code> methods.
7285d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * <p>
7295d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * If an argument index is used for more than one format element
7305d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * in the pattern string, then the format used for the last such
7315d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * format element is returned in the array. If an argument index
7325d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * is not used for any format element in the pattern string, then
7335d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * null is returned in the array.
7345d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *
7355d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * This method is only supported when exclusively numbers are used for
7365d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * argument names. Otherwise an IllegalArgumentException is thrown.
7375d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *
7385d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @return the formats used for the arguments within the pattern
7395d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @throws IllegalArgumentException if this format uses named arguments
7405d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @stable ICU 3.0
7415d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     */
7425d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    public Format[] getFormatsByArgumentIndex() {
7435d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        if (msgPattern.hasNamedArguments()) {
7445d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            throw new IllegalArgumentException(
7455d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    "This method is not available in MessageFormat objects " +
7465d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    "that use alphanumeric argument names.");
7475d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        }
7485d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        ArrayList<Format> list = new ArrayList<Format>();
7495d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        for (int partIndex = 0; (partIndex = nextTopLevelArgStart(partIndex)) >= 0;) {
7505d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            int argNumber = msgPattern.getPart(partIndex + 1).getValue();
7515d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            while (argNumber >= list.size()) {
7525d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                list.add(null);
7535d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            }
7545d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            list.set(argNumber, cachedFormatters == null ? null : cachedFormatters.get(partIndex));
7555d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        }
7565d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        return list.toArray(new Format[list.size()]);
7575d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    }
7585d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
7595d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    /**
7605d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * Returns the Format objects used for the format elements in the
7615d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * previously set pattern string.
7625d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * The order of formats in the returned array corresponds to
7635d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * the order of format elements in the pattern string.
7645d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * <p>
7655d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * Since the order of format elements in a pattern string often
7665d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * changes during localization, it's generally better to use the
7675d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * {@link #getFormatsByArgumentIndex()}
7685d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * method, which assumes an order of formats corresponding to the
7695d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * order of elements in the <code>arguments</code> array passed to
7705d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * the <code>format</code> methods or the result array returned by
7715d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * the <code>parse</code> methods.
7725d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *
7735d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * This method is only supported when exclusively numbers are used for
7745d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * argument names. Otherwise an IllegalArgumentException is thrown.
7755d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *
7765d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @return the formats used for the format elements in the pattern
7775d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @throws IllegalArgumentException if this format uses named arguments
7785d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @stable ICU 3.0
7795d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     */
7805d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    public Format[] getFormats() {
7815d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        ArrayList<Format> list = new ArrayList<Format>();
7825d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        for (int partIndex = 0; (partIndex = nextTopLevelArgStart(partIndex)) >= 0;) {
7835d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            list.add(cachedFormatters == null ? null : cachedFormatters.get(partIndex));
7845d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        }
7855d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        return list.toArray(new Format[list.size()]);
7865d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    }
7875d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
7885d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    /**
7895d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * {@icu} Returns the top-level argument names. For more details, see
7905d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * {@link #setFormatByArgumentName(String, Format)}.
7915d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @return a Set of argument names
7925d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @stable ICU 4.8
7935d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     */
7945d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    public Set<String> getArgumentNames() {
7955d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        Set<String> result = new HashSet<String>();
7965d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        for (int partIndex = 0; (partIndex = nextTopLevelArgStart(partIndex)) >= 0;) {
7975d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            result.add(getArgName(partIndex + 1));
7985d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        }
7995d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        return result;
8005d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    }
8015d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
8025d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    /**
8035d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * {@icu} Returns the first top-level format associated with the given argument name.
8045d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * For more details, see {@link #setFormatByArgumentName(String, Format)}.
8055d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @param argumentName The name of the desired argument.
8065d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @return the Format associated with the name, or null if there isn't one.
8075d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @stable ICU 4.8
8085d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     */
8095d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    public Format getFormatByArgumentName(String argumentName) {
8105d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        if (cachedFormatters == null) {
8115d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            return null;
8125d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        }
8135d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        int argNumber = MessagePattern.validateArgumentName(argumentName);
8145d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        if (argNumber < MessagePattern.ARG_NAME_NOT_NUMBER) {
8155d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            return null;
8165d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        }
8175d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        for (int partIndex = 0; (partIndex = nextTopLevelArgStart(partIndex)) >= 0;) {
8185d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            if (argNameMatches(partIndex + 1, argumentName, argNumber)) {
8195d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                return cachedFormatters.get(partIndex);
8205d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            }
8215d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        }
8225d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        return null;
8235d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    }
8245d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
8255d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    /**
8265d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * Formats an array of objects and appends the <code>MessageFormat</code>'s
8275d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * pattern, with arguments replaced by the formatted objects, to the
8285d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * provided <code>StringBuffer</code>.
8295d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * <p>
8305d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * The text substituted for the individual format elements is derived from
8315d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * the current subformat of the format element and the
8325d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * <code>arguments</code> element at the format element's argument index
8335d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * as indicated by the first matching line of the following table. An
8345d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * argument is <i>unavailable</i> if <code>arguments</code> is
8355d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * <code>null</code> or has fewer than argumentIndex+1 elements.  When
8365d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * an argument is unavailable no substitution is performed.
8375d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * <p>
8385d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * <table border=1>
8395d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *    <tr>
8405d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *       <th>argType or Format
8415d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *       <th>value object
8425d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *       <th>Formatted Text
8435d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *    <tr>
8445d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *       <td><i>any</i>
8455d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *       <td><i>unavailable</i>
8465d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *       <td><code>"{" + argNameOrNumber + "}"</code>
8475d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *    <tr>
8485d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *       <td><i>any</i>
8495d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *       <td><code>null</code>
8505d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *       <td><code>"null"</code>
8515d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *    <tr>
8525d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *       <td>custom Format <code>!= null</code>
8535d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *       <td><i>any</i>
8545d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *       <td><code>customFormat.format(argument)</code>
8555d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *    <tr>
8565d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *       <td>noneArg, or custom Format <code>== null</code>
8575d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *       <td><code>instanceof Number</code>
8585d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *       <td><code>NumberFormat.getInstance(getLocale()).format(argument)</code>
8595d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *    <tr>
8605d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *       <td>noneArg, or custom Format <code>== null</code>
8615d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *       <td><code>instanceof Date</code>
8625d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *       <td><code>DateFormat.getDateTimeInstance(DateFormat.SHORT,
8635d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *           DateFormat.SHORT, getLocale()).format(argument)</code>
8645d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *    <tr>
8655d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *       <td>noneArg, or custom Format <code>== null</code>
8665d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *       <td><code>instanceof String</code>
8675d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *       <td><code>argument</code>
8685d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *    <tr>
8695d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *       <td>noneArg, or custom Format <code>== null</code>
8705d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *       <td><i>any</i>
8715d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *       <td><code>argument.toString()</code>
8725d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *    <tr>
8735d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *       <td>complexArg
8745d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *       <td><i>any</i>
8755d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *       <td>result of recursive formatting of a selected sub-message
8765d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * </table>
8775d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * <p>
8785d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * If <code>pos</code> is non-null, and refers to
8795d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * <code>Field.ARGUMENT</code>, the location of the first formatted
8805d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * string will be returned.
8815d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *
8825d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * This method is only supported when the format does not use named
8835d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * arguments, otherwise an IllegalArgumentException is thrown.
8845d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *
8855d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @param arguments an array of objects to be formatted and substituted.
8865d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @param result where text is appended.
8875d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @param pos On input: an alignment field, if desired.
8885d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *            On output: the offsets of the alignment field.
8895d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @throws IllegalArgumentException if a value in the
8905d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *         <code>arguments</code> array is not of the type
8915d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *         expected by the corresponding argument or custom Format object.
8925d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @throws IllegalArgumentException if this format uses named arguments
8935d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @stable ICU 3.0
8945d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     */
8955d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    public final StringBuffer format(Object[] arguments, StringBuffer result,
8965d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                                     FieldPosition pos)
8975d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    {
8985d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        format(arguments, null, new AppendableWrapper(result), pos);
8995d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        return result;
9005d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    }
9015d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
9025d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    /**
9035d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * Formats a map of objects and appends the <code>MessageFormat</code>'s
9045d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * pattern, with arguments replaced by the formatted objects, to the
9055d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * provided <code>StringBuffer</code>.
9065d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * <p>
9075d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * The text substituted for the individual format elements is derived from
9085d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * the current subformat of the format element and the
9095d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * <code>arguments</code> value corresopnding to the format element's
9105d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * argument name.
9115d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * <p>
9125d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * A numbered pattern argument is matched with a map key that contains that number
9135d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * as an ASCII-decimal-digit string (without leading zero).
9145d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * <p>
9155d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * An argument is <i>unavailable</i> if <code>arguments</code> is
9165d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * <code>null</code> or does not have a value corresponding to an argument
9175d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * name in the pattern.  When an argument is unavailable no substitution
9185d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * is performed.
9195d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *
9205d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @param arguments a map of objects to be formatted and substituted.
9215d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @param result where text is appended.
9225d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @param pos On input: an alignment field, if desired.
9235d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *            On output: the offsets of the alignment field.
9245d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @throws IllegalArgumentException if a value in the
9255d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *         <code>arguments</code> array is not of the type
9265d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *         expected by the corresponding argument or custom Format object.
9275d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @return the passed-in StringBuffer
9285d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @stable ICU 3.8
9295d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     */
9305d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    public final StringBuffer format(Map<String, Object> arguments, StringBuffer result,
9315d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                                     FieldPosition pos) {
9325d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        format(null, arguments, new AppendableWrapper(result), pos);
9335d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        return result;
9345d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    }
9355d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
9365d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    /**
9375d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * Creates a MessageFormat with the given pattern and uses it
9385d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * to format the given arguments. This is equivalent to
9395d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * <blockquote>
9405d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *     <code>(new {@link #MessageFormat(String) MessageFormat}(pattern)).{@link
9415d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *     #format(java.lang.Object[], java.lang.StringBuffer, java.text.FieldPosition)
9425d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *     format}(arguments, new StringBuffer(), null).toString()</code>
9435d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * </blockquote>
9445d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *
9455d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @throws IllegalArgumentException if the pattern is invalid
9465d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @throws IllegalArgumentException if a value in the
9475d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *         <code>arguments</code> array is not of the type
9485d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *         expected by the corresponding argument or custom Format object.
9495d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @throws IllegalArgumentException if this format uses named arguments
9505d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @stable ICU 3.0
9515d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     */
9525d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    public static String format(String pattern, Object... arguments) {
9535d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        MessageFormat temp = new MessageFormat(pattern);
9545d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        return temp.format(arguments);
9555d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    }
9565d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
9575d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    /**
9585d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * Creates a MessageFormat with the given pattern and uses it to
9595d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * format the given arguments.  The pattern must identifyarguments
9605d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * by name instead of by number.
9615d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * <p>
9625d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @throws IllegalArgumentException if the pattern is invalid
9635d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @throws IllegalArgumentException if a value in the
9645d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *         <code>arguments</code> array is not of the type
9655d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *         expected by the corresponding argument or custom Format object.
9665d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @see #format(Map, StringBuffer, FieldPosition)
9675d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @see #format(String, Object[])
9685d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @stable ICU 3.8
9695d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     */
9705d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    public static String format(String pattern, Map<String, Object> arguments) {
9715d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        MessageFormat temp = new MessageFormat(pattern);
9725d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        return temp.format(arguments);
9735d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    }
9745d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
9755d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    /**
9765d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * {@icu} Returns true if this MessageFormat uses named arguments,
9775d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * and false otherwise.  See class description.
9785d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *
9795d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @return true if named arguments are used.
9805d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @stable ICU 3.8
9815d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     */
9825d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    public boolean usesNamedArguments() {
9835d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        return msgPattern.hasNamedArguments();
9845d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    }
9855d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
9865d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    // Overrides
9875d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    /**
9885d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * Formats a map or array of objects and appends the <code>MessageFormat</code>'s
9895d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * pattern, with format elements replaced by the formatted objects, to the
9905d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * provided <code>StringBuffer</code>.
9915d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * This is equivalent to either of
9925d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * <blockquote>
9935d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *     <code>{@link #format(java.lang.Object[], java.lang.StringBuffer,
9945d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *     java.text.FieldPosition) format}((Object[]) arguments, result, pos)</code>
9955d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *     <code>{@link #format(java.util.Map, java.lang.StringBuffer,
9965d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *     java.text.FieldPosition) format}((Map) arguments, result, pos)</code>
9975d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * </blockquote>
9985d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * A map must be provided if this format uses named arguments, otherwise
9995d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * an IllegalArgumentException will be thrown.
10005d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @param arguments a map or array of objects to be formatted
10015d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @param result where text is appended
10025d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @param pos On input: an alignment field, if desired
10035d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *            On output: the offsets of the alignment field
10045d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @throws IllegalArgumentException if an argument in
10055d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *         <code>arguments</code> is not of the type
10065d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *         expected by the format element(s) that use it
10075d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @throws IllegalArgumentException if <code>arguments<code> is
10085d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *         an array of Object and this format uses named arguments
10095d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @stable ICU 3.0
10105d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     */
10115d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    public final StringBuffer format(Object arguments, StringBuffer result,
10125d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                                     FieldPosition pos)
10135d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    {
10145d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        format(arguments, new AppendableWrapper(result), pos);
10155d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        return result;
10165d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    }
10175d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
10185d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    /**
10195d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * Formats an array of objects and inserts them into the
10205d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * <code>MessageFormat</code>'s pattern, producing an
10215d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * <code>AttributedCharacterIterator</code>.
10225d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * You can use the returned <code>AttributedCharacterIterator</code>
10235d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * to build the resulting String, as well as to determine information
10245d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * about the resulting String.
10255d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * <p>
10265d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * The text of the returned <code>AttributedCharacterIterator</code> is
10275d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * the same that would be returned by
10285d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * <blockquote>
10295d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *     <code>{@link #format(java.lang.Object[], java.lang.StringBuffer,
10305d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *     java.text.FieldPosition) format}(arguments, new StringBuffer(), null).toString()</code>
10315d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * </blockquote>
10325d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * <p>
10335d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * In addition, the <code>AttributedCharacterIterator</code> contains at
10345d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * least attributes indicating where text was generated from an
10355d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * argument in the <code>arguments</code> array. The keys of these attributes are of
10365d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * type <code>MessageFormat.Field</code>, their values are
10375d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * <code>Integer</code> objects indicating the index in the <code>arguments</code>
10385d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * array of the argument from which the text was generated.
10395d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * <p>
10405d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * The attributes/value from the underlying <code>Format</code>
10415d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * instances that <code>MessageFormat</code> uses will also be
10425d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * placed in the resulting <code>AttributedCharacterIterator</code>.
10435d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * This allows you to not only find where an argument is placed in the
10445d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * resulting String, but also which fields it contains in turn.
10455d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *
10465d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @param arguments an array of objects to be formatted and substituted.
10475d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @return AttributedCharacterIterator describing the formatted value.
10485d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @exception NullPointerException if <code>arguments</code> is null.
10495d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @throws IllegalArgumentException if a value in the
10505d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *         <code>arguments</code> array is not of the type
10515d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *         expected by the corresponding argument or custom Format object.
10525d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @stable ICU 3.8
10535d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     */
10545d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    public AttributedCharacterIterator formatToCharacterIterator(Object arguments) {
10555d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        if (arguments == null) {
10565d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            throw new NullPointerException(
10575d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                   "formatToCharacterIterator must be passed non-null object");
10585d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        }
10595d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        StringBuilder result = new StringBuilder();
10605d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        AppendableWrapper wrapper = new AppendableWrapper(result);
10615d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        wrapper.useAttributes();
10625d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        format(arguments, wrapper, null);
10635d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        AttributedString as = new AttributedString(result.toString());
10645d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        for (AttributeAndPosition a : wrapper.attributes) {
10655d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            as.addAttribute(a.key, a.value, a.start, a.limit);
10665d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        }
10675d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        return as.getIterator();
10685d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    }
10695d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
10705d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    /**
10715d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * Parses the string.
10725d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *
10735d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * <p>Caveats: The parse may fail in a number of circumstances.
10745d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * For example:
10755d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * <ul>
10765d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * <li>If one of the arguments does not occur in the pattern.
10775d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * <li>If the format of an argument loses information, such as
10785d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *     with a choice format where a large number formats to "many".
10795d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * <li>Does not yet handle recursion (where
10805d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *     the substituted strings contain {n} references.)
10815d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * <li>Will not always find a match (or the correct match)
10825d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *     if some part of the parse is ambiguous.
10835d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *     For example, if the pattern "{1},{2}" is used with the
10845d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *     string arguments {"a,b", "c"}, it will format as "a,b,c".
10855d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *     When the result is parsed, it will return {"a", "b,c"}.
10865d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * <li>If a single argument is parsed more than once in the string,
10875d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *     then the later parse wins.
10885d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * </ul>
10895d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * When the parse fails, use ParsePosition.getErrorIndex() to find out
10905d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * where in the string did the parsing failed. The returned error
10915d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * index is the starting offset of the sub-patterns that the string
10925d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * is comparing with. For example, if the parsing string "AAA {0} BBB"
10935d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * is comparing against the pattern "AAD {0} BBB", the error index is
10945d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * 0. When an error occurs, the call to this method will return null.
10955d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * If the source is null, return an empty array.
10965d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *
10975d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @throws IllegalArgumentException if this format uses named arguments
10985d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @stable ICU 3.0
10995d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     */
11005d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    public Object[] parse(String source, ParsePosition pos) {
11015d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        if (msgPattern.hasNamedArguments()) {
11025d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            throw new IllegalArgumentException(
11035d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    "This method is not available in MessageFormat objects " +
11045d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    "that use named argument.");
11055d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        }
11065d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
11075d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        // Count how many slots we need in the array.
11085d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        int maxArgId = -1;
11095d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        for (int partIndex = 0; (partIndex = nextTopLevelArgStart(partIndex)) >= 0;) {
11105d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            int argNumber=msgPattern.getPart(partIndex + 1).getValue();
11115d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            if (argNumber > maxArgId) {
11125d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                maxArgId = argNumber;
11135d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            }
11145d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        }
11155d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        Object[] resultArray = new Object[maxArgId + 1];
11165d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
11175d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        int backupStartPos = pos.getIndex();
11185d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        parse(0, source, pos, resultArray, null);
11195d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        if (pos.getIndex() == backupStartPos) { // unchanged, returned object is null
11205d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            return null;
11215d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        }
11225d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
11235d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        return resultArray;
11245d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    }
11255d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
11265d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    /**
11275d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * {@icu} Parses the string, returning the results in a Map.
11285d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * This is similar to the version that returns an array
11295d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * of Object.  This supports both named and numbered
11305d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * arguments-- if numbered, the keys in the map are the
11315d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * corresponding ASCII-decimal-digit strings (e.g. "0", "1", "2"...).
11325d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *
11335d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @param source the text to parse
11345d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @param pos the position at which to start parsing.  on return,
11355d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *        contains the result of the parse.
11365d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @return a Map containing key/value pairs for each parsed argument.
11375d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @stable ICU 3.8
11385d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     */
11395d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    public Map<String, Object> parseToMap(String source, ParsePosition pos)  {
11405d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        Map<String, Object> result = new HashMap<String, Object>();
11415d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        int backupStartPos = pos.getIndex();
11425d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        parse(0, source, pos, null, result);
11435d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        if (pos.getIndex() == backupStartPos) {
11445d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            return null;
11455d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        }
11465d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        return result;
11475d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    }
11485d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
11495d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    /**
11505d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * Parses text from the beginning of the given string to produce an object
11515d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * array.
11525d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * The method may not use the entire text of the given string.
11535d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * <p>
11545d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * See the {@link #parse(String, ParsePosition)} method for more information
11555d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * on message parsing.
11565d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *
11575d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @param source A <code>String</code> whose beginning should be parsed.
11585d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @return An <code>Object</code> array parsed from the string.
11595d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @exception ParseException if the beginning of the specified string cannot be parsed.
11605d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @exception IllegalArgumentException if this format uses named arguments
11615d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @stable ICU 3.0
11625d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     */
11635d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    public Object[] parse(String source) throws ParseException {
11645d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        ParsePosition pos = new ParsePosition(0);
11655d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        Object[] result = parse(source, pos);
11665d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        if (pos.getIndex() == 0) // unchanged, returned object is null
11675d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            throw new ParseException("MessageFormat parse error!",
11685d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                                     pos.getErrorIndex());
11695d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
11705d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        return result;
11715d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    }
11725d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
11735d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    /**
11745d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * Parses the string, filling either the Map or the Array.
11755d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * This is a private method that all the public parsing methods call.
11765d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * This supports both named and numbered
11775d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * arguments-- if numbered, the keys in the map are the
11785d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * corresponding ASCII-decimal-digit strings (e.g. "0", "1", "2"...).
11795d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *
11805d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @param msgStart index in the message pattern to start from.
11815d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @param source the text to parse
11825d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @param pos the position at which to start parsing.  on return,
11835d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *        contains the result of the parse.
11845d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @param args if not null, the parse results will be filled here (The pattern
11855d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *        has to have numbered arguments in order for this to not be null).
11865d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @param argsMap if not null, the parse results will be filled here.
11875d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     */
11885d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    private void parse(int msgStart, String source, ParsePosition pos,
11895d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                       Object[] args, Map<String, Object> argsMap) {
11905d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        if (source == null) {
11915d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            return;
11925d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        }
11935d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        String msgString=msgPattern.getPatternString();
11945d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        int prevIndex=msgPattern.getPart(msgStart).getLimit();
11955d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        int sourceOffset = pos.getIndex();
11965d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        ParsePosition tempStatus = new ParsePosition(0);
11975d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
11985d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        for(int i=msgStart+1; ; ++i) {
11995d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            Part part=msgPattern.getPart(i);
12005d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            Part.Type type=part.getType();
12015d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            int index=part.getIndex();
12025d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            // Make sure the literal string matches.
12035d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            int len = index - prevIndex;
12045d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            if (len == 0 || msgString.regionMatches(prevIndex, source, sourceOffset, len)) {
12055d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                sourceOffset += len;
12065d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                prevIndex += len;
12075d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            } else {
12085d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                pos.setErrorIndex(sourceOffset);
12095d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                return; // leave index as is to signal error
12105d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            }
12115d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            if(type==Part.Type.MSG_LIMIT) {
12125d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                // Things went well! Done.
12135d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                pos.setIndex(sourceOffset);
12145d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                return;
12155d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            }
12165d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            if(type==Part.Type.SKIP_SYNTAX || type==Part.Type.INSERT_CHAR) {
12175d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                prevIndex=part.getLimit();
12185d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                continue;
12195d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            }
12205d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            // We do not support parsing Plural formats. (No REPLACE_NUMBER here.)
12215d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            assert type==Part.Type.ARG_START : "Unexpected Part "+part+" in parsed message.";
12225d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            int argLimit=msgPattern.getLimitPartIndex(i);
12235d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
12245d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            ArgType argType=part.getArgType();
12255d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            part=msgPattern.getPart(++i);
12265d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            // Compute the argId, so we can use it as a key.
12275d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            Object argId=null;
12285d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            int argNumber = 0;
12295d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            String key = null;
12305d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            if(args!=null) {
12315d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                argNumber=part.getValue();  // ARG_NUMBER
12325d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                argId = Integer.valueOf(argNumber);
12335d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            } else {
12345d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                if(part.getType()==MessagePattern.Part.Type.ARG_NAME) {
12355d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    key=msgPattern.getSubstring(part);
12365d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                } else /* ARG_NUMBER */ {
12375d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    key=Integer.toString(part.getValue());
12385d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                }
12395d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                argId = key;
12405d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            }
12415d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
12425d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            ++i;
12435d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            Format formatter = null;
12445d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            boolean haveArgResult = false;
12455d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            Object argResult = null;
12465d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            if(cachedFormatters!=null && (formatter=cachedFormatters.get(i - 2))!=null) {
12475d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                // Just parse using the formatter.
12485d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                tempStatus.setIndex(sourceOffset);
12495d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                argResult = formatter.parseObject(source, tempStatus);
12505d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                if (tempStatus.getIndex() == sourceOffset) {
12515d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    pos.setErrorIndex(sourceOffset);
12525d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    return; // leave index as is to signal error
12535d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                }
12545d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                haveArgResult = true;
12555d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                sourceOffset = tempStatus.getIndex();
12565d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            } else if(
12575d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    argType==ArgType.NONE ||
12585d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    (cachedFormatters!=null && cachedFormatters.containsKey(i - 2))) {
12595d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                // Match as a string.
12605d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                // if at end, use longest possible match
12615d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                // otherwise uses first match to intervening string
12625d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                // does NOT recursively try all possibilities
12635d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                String stringAfterArgument = getLiteralStringUntilNextArgument(argLimit);
12645d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                int next;
12655d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                if (stringAfterArgument.length() != 0) {
12665d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    next = source.indexOf(stringAfterArgument, sourceOffset);
12675d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                } else {
12685d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    next = source.length();
12695d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                }
12705d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                if (next < 0) {
12715d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    pos.setErrorIndex(sourceOffset);
12725d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    return; // leave index as is to signal error
12735d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                } else {
12745d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    String strValue = source.substring(sourceOffset, next);
12755d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    if (!strValue.equals("{" + argId.toString() + "}")) {
12765d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                        haveArgResult = true;
12775d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                        argResult = strValue;
12785d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    }
12795d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    sourceOffset = next;
12805d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                }
12815d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            } else if(argType==ArgType.CHOICE) {
12825d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                tempStatus.setIndex(sourceOffset);
12835d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                double choiceResult = parseChoiceArgument(msgPattern, i, source, tempStatus);
12845d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                if (tempStatus.getIndex() == sourceOffset) {
12855d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    pos.setErrorIndex(sourceOffset);
12865d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    return; // leave index as is to signal error
12875d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                }
12885d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                argResult = choiceResult;
12895d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                haveArgResult = true;
12905d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                sourceOffset = tempStatus.getIndex();
12915d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            } else if(argType.hasPluralStyle() || argType==ArgType.SELECT) {
12925d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                // No can do!
12935d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                throw new UnsupportedOperationException(
12945d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                        "Parsing of plural/select/selectordinal argument is not supported.");
12955d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            } else {
12965d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                // This should never happen.
12975d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                throw new IllegalStateException("unexpected argType "+argType);
12985d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            }
12995d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            if (haveArgResult) {
13005d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                if (args != null) {
13015d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    args[argNumber] = argResult;
13025d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                } else if (argsMap != null) {
13035d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    argsMap.put(key, argResult);
13045d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                }
13055d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            }
13065d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            prevIndex=msgPattern.getPart(argLimit).getLimit();
13075d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            i=argLimit;
13085d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        }
13095d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    }
13105d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
13115d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    /**
13125d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * {@icu} Parses text from the beginning of the given string to produce a map from
13135d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * argument to values. The method may not use the entire text of the given string.
13145d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *
13155d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * <p>See the {@link #parse(String, ParsePosition)} method for more information on
13165d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * message parsing.
13175d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *
13185d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @param source A <code>String</code> whose beginning should be parsed.
13195d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @return A <code>Map</code> parsed from the string.
13205d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @throws ParseException if the beginning of the specified string cannot
13215d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *         be parsed.
13225d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @see #parseToMap(String, ParsePosition)
13235d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @stable ICU 3.8
13245d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     */
13255d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    public Map<String, Object> parseToMap(String source) throws ParseException {
13265d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        ParsePosition pos = new ParsePosition(0);
13275d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        Map<String, Object> result = new HashMap<String, Object>();
13285d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        parse(0, source, pos, null, result);
13295d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        if (pos.getIndex() == 0) // unchanged, returned object is null
13305d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            throw new ParseException("MessageFormat parse error!",
13315d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                                     pos.getErrorIndex());
13325d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
13335d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        return result;
13345d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    }
13355d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
13365d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    /**
13375d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * Parses text from a string to produce an object array or Map.
13385d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * <p>
13395d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * The method attempts to parse text starting at the index given by
13405d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * <code>pos</code>.
13415d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * If parsing succeeds, then the index of <code>pos</code> is updated
13425d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * to the index after the last character used (parsing does not necessarily
13435d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * use all characters up to the end of the string), and the parsed
13445d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * object array is returned. The updated <code>pos</code> can be used to
13455d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * indicate the starting point for the next call to this method.
13465d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * If an error occurs, then the index of <code>pos</code> is not
13475d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * changed, the error index of <code>pos</code> is set to the index of
13485d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * the character where the error occurred, and null is returned.
13495d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * <p>
13505d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * See the {@link #parse(String, ParsePosition)} method for more information
13515d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * on message parsing.
13525d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *
13535d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @param source A <code>String</code>, part of which should be parsed.
13545d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @param pos A <code>ParsePosition</code> object with index and error
13555d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *            index information as described above.
13565d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @return An <code>Object</code> parsed from the string, either an
13575d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *         array of Object, or a Map, depending on whether named
13585d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *         arguments are used.  This can be queried using <code>usesNamedArguments</code>.
13595d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *         In case of error, returns null.
13605d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @throws NullPointerException if <code>pos</code> is null.
13615d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @stable ICU 3.0
13625d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     */
13635d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    public Object parseObject(String source, ParsePosition pos) {
13645d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        if (!msgPattern.hasNamedArguments()) {
13655d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            return parse(source, pos);
13665d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        } else {
13675d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            return parseToMap(source, pos);
13685d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        }
13695d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    }
13705d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
13715d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    /**
13725d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * {@inheritDoc}
13735d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @stable ICU 3.0
13745d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    @Override
13755d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    public boolean equals(Object obj) {
13765d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        if (this == obj)                      // quick check
13775d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            return true;
13785d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        if (obj == null || getClass() != obj.getClass())
13795d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            return false;
13805d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        MessageFormat other = (MessageFormat) obj;
13815d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        return Utility.objectEquals(ulocale, other.ulocale)
13825d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                && Utility.objectEquals(msgPattern, other.msgPattern)
13835d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                && Utility.objectEquals(cachedFormatters, other.cachedFormatters)
13845d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                && Utility.objectEquals(customFormatArgStarts, other.customFormatArgStarts);
13855d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        // Note: It might suffice to only compare custom formatters
13865d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        // rather than all formatters.
13875d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    }
13888d05787d6a4b5762d790ccd2a9ed9dc8885986efNarayan Kamath     */
13895d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
13905d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    /**
13915d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * {@inheritDoc}
13925d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @stable ICU 3.0
13935d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     */
13945d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    @Override
13955d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    public int hashCode() {
13965d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        return msgPattern.getPatternString().hashCode(); // enough for reasonable distribution
13975d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    }
13985d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
13995d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    /**
14005d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * Defines constants that are used as attribute keys in the
14015d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * <code>AttributedCharacterIterator</code> returned
14025d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * from <code>MessageFormat.formatToCharacterIterator</code>.
14035d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *
14045d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @stable ICU 3.8
14055d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     */
14065d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    public static class Field extends Format.Field {
14075d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
14085d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        private static final long serialVersionUID = 7510380454602616157L;
14095d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
14105d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        /**
14115d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath         * Create a <code>Field</code> with the specified name.
14125d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath         *
14135d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath         * @param name The name of the attribute
14145d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath         *
14155d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath         * @stable ICU 3.8
14165d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath         */
14175d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        protected Field(String name) {
14185d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            super(name);
14195d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        }
14205d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
14215d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        /**
14225d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath         * Resolves instances being deserialized to the predefined constants.
14235d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath         *
14245d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath         * @return resolved MessageFormat.Field constant
14255d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath         * @throws InvalidObjectException if the constant could not be resolved.
14265d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath         *
14275d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath         * @stable ICU 3.8
14285d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath         */
14295d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        protected Object readResolve() throws InvalidObjectException {
14305d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            if (this.getClass() != MessageFormat.Field.class) {
14315d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                throw new InvalidObjectException(
14325d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    "A subclass of MessageFormat.Field must implement readResolve.");
14335d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            }
14345d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            if (this.getName().equals(ARGUMENT.getName())) {
14355d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                return ARGUMENT;
14365d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            } else {
14375d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                throw new InvalidObjectException("Unknown attribute name.");
14385d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            }
14395d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        }
14405d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
14415d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        /**
14425d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath         * Constant identifying a portion of a message that was generated
14435d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath         * from an argument passed into <code>formatToCharacterIterator</code>.
14445d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath         * The value associated with the key will be an <code>Integer</code>
14455d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath         * indicating the index in the <code>arguments</code> array of the
14465d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath         * argument from which the text was generated.
14475d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath         *
14485d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath         * @stable ICU 3.8
14495d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath         */
14505d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        public static final Field ARGUMENT = new Field("message argument field");
14515d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    }
14525d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
14535d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    // ===========================privates============================
14545d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
14555d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    // *Important*: All fields must be declared *transient* so that we can fully
14565d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    // control serialization!
14575d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    // See for example Joshua Bloch's "Effective Java", chapter 10 Serialization.
14585d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
14595d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    /**
14605d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * The locale to use for formatting numbers and dates.
14615d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     */
14628d05787d6a4b5762d790ccd2a9ed9dc8885986efNarayan Kamath    private transient Locale locale_;
14635d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
14645d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    /**
14655d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * The MessagePattern which contains the parsed structure of the pattern string.
14665d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     */
14675d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    private transient MessagePattern msgPattern;
14685d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    /**
14695d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * Cached formatters so we can just use them whenever needed instead of creating
14705d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * them from scratch every time.
14715d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     */
14725d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    private transient Map<Integer, Format> cachedFormatters;
14735d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    /**
14745d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * Set of ARG_START part indexes where custom, user-provided Format objects
14755d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * have been set via setFormat() or similar API.
14765d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     */
14775d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    private transient Set<Integer> customFormatArgStarts;
14785d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
14795d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    /**
14805d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * Stock formatters. Those are used when a format is not explicitly mentioned in
14815d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * the message. The format is inferred from the argument.
14825d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     */
14835d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    private transient DateFormat stockDateFormatter;
14845d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    private transient NumberFormat stockNumberFormatter;
14855d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
14865d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    private transient PluralSelectorProvider pluralProvider;
14875d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    private transient PluralSelectorProvider ordinalProvider;
14885d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
14895d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    private DateFormat getStockDateFormatter() {
14905d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        if (stockDateFormatter == null) {
14915d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            stockDateFormatter = DateFormat.getDateTimeInstance(
14928d05787d6a4b5762d790ccd2a9ed9dc8885986efNarayan Kamath                    DateFormat.SHORT, DateFormat.SHORT, locale_);//fix
14935d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        }
14945d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        return stockDateFormatter;
14955d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    }
14965d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    private NumberFormat getStockNumberFormatter() {
14975d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        if (stockNumberFormatter == null) {
14988d05787d6a4b5762d790ccd2a9ed9dc8885986efNarayan Kamath            stockNumberFormatter = NumberFormat.getInstance(locale_);
14995d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        }
15005d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        return stockNumberFormatter;
15015d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    }
15025d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
15035d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    // *Important*: All fields must be declared *transient*.
15045d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    // See the longer comment above ulocale.
15055d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
15065d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    /**
15075d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * Formats the arguments and writes the result into the
15085d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * AppendableWrapper, updates the field position.
15095d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *
15105d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * <p>Exactly one of args and argsMap must be null, the other non-null.
15115d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *
15125d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @param msgStart      Index to msgPattern part to start formatting from.
15135d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @param pluralNumber  null except when formatting a plural argument sub-message
15145d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *                      where a '#' is replaced by the format string for this number.
15155d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @param args          The formattable objects array. Non-null iff numbered values are used.
15165d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @param argsMap       The key-value map of formattable objects. Non-null iff named values are used.
15175d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @param dest          Output parameter to receive the result.
15185d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *                      The result (string & attributes) is appended to existing contents.
15195d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @param fp            Field position status.
15205d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     */
15215d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    private void format(int msgStart, PluralSelectorContext pluralNumber,
15228d05787d6a4b5762d790ccd2a9ed9dc8885986efNarayan Kamath                        Object[] args, Map<String, Object> argsMap, Object[] nameValuePairs,
15235d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                        AppendableWrapper dest, FieldPosition fp) {
15245d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        String msgString=msgPattern.getPatternString();
15255d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        int prevIndex=msgPattern.getPart(msgStart).getLimit();
15265d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        for(int i=msgStart+1;; ++i) {
15275d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            Part part=msgPattern.getPart(i);
15285d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            Part.Type type=part.getType();
15295d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            int index=part.getIndex();
15305d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            dest.append(msgString, prevIndex, index);
15315d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            if(type==Part.Type.MSG_LIMIT) {
15325d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                return;
15335d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            }
15345d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            prevIndex=part.getLimit();
15355d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            if(type==Part.Type.REPLACE_NUMBER) {
15365d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                if(pluralNumber.forReplaceNumber) {
15375d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    // number-offset was already formatted.
15385d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    dest.formatAndAppend(pluralNumber.formatter,
15395d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                            pluralNumber.number, pluralNumber.numberString);
15405d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                } else {
15415d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    dest.formatAndAppend(getStockNumberFormatter(), pluralNumber.number);
15425d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                }
15435d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                continue;
15445d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            }
15455d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            if(type!=Part.Type.ARG_START) {
15465d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                continue;
15475d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            }
15485d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            int argLimit=msgPattern.getLimitPartIndex(i);
15495d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            ArgType argType=part.getArgType();
15505d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            part=msgPattern.getPart(++i);
15515d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            Object arg;
15525d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            boolean noArg=false;
15535d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            Object argId=null;
15545d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            String argName=msgPattern.getSubstring(part);
15555d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            if(args!=null) {
15565d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                int argNumber=part.getValue();  // ARG_NUMBER
15575d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                if (dest.attributes != null) {
15585d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    // We only need argId if we add it into the attributes.
15595d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    argId = Integer.valueOf(argNumber);
15605d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                }
15615d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                if(0<=argNumber && argNumber<args.length) {
15625d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    arg=args[argNumber];
15635d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                } else {
15645d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    arg=null;
15655d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    noArg=true;
15665d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                }
15678d05787d6a4b5762d790ccd2a9ed9dc8885986efNarayan Kamath            } else if(nameValuePairs!=null) {
15688d05787d6a4b5762d790ccd2a9ed9dc8885986efNarayan Kamath                argId = argName;
15698d05787d6a4b5762d790ccd2a9ed9dc8885986efNarayan Kamath                for(int nvIndex=0;; nvIndex+=2) {
15708d05787d6a4b5762d790ccd2a9ed9dc8885986efNarayan Kamath                    if(nvIndex<nameValuePairs.length) {
15718d05787d6a4b5762d790ccd2a9ed9dc8885986efNarayan Kamath                        if(argName.equals(nameValuePairs[nvIndex].toString())) {
15728d05787d6a4b5762d790ccd2a9ed9dc8885986efNarayan Kamath                            arg=nameValuePairs[nvIndex+1];
15738d05787d6a4b5762d790ccd2a9ed9dc8885986efNarayan Kamath                            break;
15748d05787d6a4b5762d790ccd2a9ed9dc8885986efNarayan Kamath                        }
15758d05787d6a4b5762d790ccd2a9ed9dc8885986efNarayan Kamath                    } else {
15768d05787d6a4b5762d790ccd2a9ed9dc8885986efNarayan Kamath                        arg=null;
15778d05787d6a4b5762d790ccd2a9ed9dc8885986efNarayan Kamath                        noArg=true;
15788d05787d6a4b5762d790ccd2a9ed9dc8885986efNarayan Kamath                        break;
15798d05787d6a4b5762d790ccd2a9ed9dc8885986efNarayan Kamath                    }
15808d05787d6a4b5762d790ccd2a9ed9dc8885986efNarayan Kamath                }
15815d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            } else {
15825d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                argId = argName;
15835d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                if(argsMap!=null && argsMap.containsKey(argName)) {
15845d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    arg=argsMap.get(argName);
15855d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                } else {
15865d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    arg=null;
15875d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    noArg=true;
15885d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                }
15895d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            }
15905d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            ++i;
15915d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            int prevDestLength=dest.length;
15925d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            Format formatter = null;
15935d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            if (noArg) {
15945d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                dest.append("{"+argName+"}");
15955d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            } else if (arg == null) {
15965d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                dest.append("null");
15975d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            } else if(pluralNumber!=null && pluralNumber.numberArgIndex==(i-2)) {
15985d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                if(pluralNumber.offset == 0) {
15995d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    // The number was already formatted with this formatter.
16005d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    dest.formatAndAppend(pluralNumber.formatter, pluralNumber.number, pluralNumber.numberString);
16015d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                } else {
16025d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    // Do not use the formatted (number-offset) string for a named argument
16035d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    // that formats the number without subtracting the offset.
16045d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    dest.formatAndAppend(pluralNumber.formatter, arg);
16055d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                }
16065d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            } else if(cachedFormatters!=null && (formatter=cachedFormatters.get(i - 2))!=null) {
16075d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                // Handles all ArgType.SIMPLE, and formatters from setFormat() and its siblings.
16088d05787d6a4b5762d790ccd2a9ed9dc8885986efNarayan Kamath                {
16095d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    dest.formatAndAppend(formatter, arg);
16105d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                }
16115d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            } else if(
16125d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    argType==ArgType.NONE ||
16135d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    (cachedFormatters!=null && cachedFormatters.containsKey(i - 2))) {
16145d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                // ArgType.NONE, or
16155d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                // any argument which got reset to null via setFormat() or its siblings.
16165d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                if (arg instanceof Number) {
16175d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    // format number if can
16185d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    dest.formatAndAppend(getStockNumberFormatter(), arg);
16195d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                 } else if (arg instanceof Date) {
16205d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    // format a Date if can
16215d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    dest.formatAndAppend(getStockDateFormatter(), arg);
16225d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                } else {
16235d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    dest.append(arg.toString());
16245d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                }
16255d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            } else if(argType==ArgType.CHOICE) {
16265d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                if (!(arg instanceof Number)) {
16275d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    throw new IllegalArgumentException("'" + arg + "' is not a Number");
16285d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                }
16295d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                double number = ((Number)arg).doubleValue();
16305d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                int subMsgStart=findChoiceSubMessage(msgPattern, i, number);
16318d05787d6a4b5762d790ccd2a9ed9dc8885986efNarayan Kamath                formatComplexSubMessage(subMsgStart, null, args, argsMap, nameValuePairs, dest);
16325d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            } else if(argType.hasPluralStyle()) {
16335d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                if (!(arg instanceof Number)) {
16345d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    throw new IllegalArgumentException("'" + arg + "' is not a Number");
16355d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                }
16365d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                PluralSelectorProvider selector;
16375d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                if(argType == ArgType.PLURAL) {
16385d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    if (pluralProvider == null) {
16395d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                        pluralProvider = new PluralSelectorProvider(this, PluralType.CARDINAL);
16405d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    }
16415d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    selector = pluralProvider;
16425d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                } else {
16435d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    if (ordinalProvider == null) {
16445d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                        ordinalProvider = new PluralSelectorProvider(this, PluralType.ORDINAL);
16455d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    }
16465d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    selector = ordinalProvider;
16475d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                }
16485d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                Number number = (Number)arg;
16495d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                double offset=msgPattern.getPluralOffset(i);
16505d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                PluralSelectorContext context =
16515d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                        new PluralSelectorContext(i, argName, number, offset);
16525d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                int subMsgStart=PluralFormat.findSubMessage(
16535d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                        msgPattern, i, selector, context, number.doubleValue());
16548d05787d6a4b5762d790ccd2a9ed9dc8885986efNarayan Kamath                formatComplexSubMessage(subMsgStart, context, args, argsMap, nameValuePairs, dest);
16555d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            } else if(argType==ArgType.SELECT) {
16565d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                int subMsgStart=SelectFormat.findSubMessage(msgPattern, i, arg.toString());
16578d05787d6a4b5762d790ccd2a9ed9dc8885986efNarayan Kamath                formatComplexSubMessage(subMsgStart, null, args, argsMap, nameValuePairs, dest);
16585d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            } else {
16595d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                // This should never happen.
16605d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                throw new IllegalStateException("unexpected argType "+argType);
16615d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            }
16625d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            fp = updateMetaData(dest, prevDestLength, fp, argId);
16635d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            prevIndex=msgPattern.getPart(argLimit).getLimit();
16645d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            i=argLimit;
16655d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        }
16665d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    }
16675d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
16685d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    private void formatComplexSubMessage(
16695d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            int msgStart, PluralSelectorContext pluralNumber,
16708d05787d6a4b5762d790ccd2a9ed9dc8885986efNarayan Kamath            Object[] args, Map<String, Object> argsMap, Object[] nameValuePairs,
16715d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            AppendableWrapper dest) {
16725d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        if (!msgPattern.jdkAposMode()) {
16738d05787d6a4b5762d790ccd2a9ed9dc8885986efNarayan Kamath            format(msgStart, pluralNumber, args, argsMap, nameValuePairs, dest, null);
16745d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            return;
16755d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        }
16765d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        // JDK compatibility mode: (see JDK MessageFormat.format() API docs)
16778d05787d6a4b5762d790ccd2a9ed9dc8885986efNarayan Kamath        throw new UnsupportedOperationException("JDK apostrophe mode not supported");
16788d05787d6a4b5762d790ccd2a9ed9dc8885986efNarayan Kamath        /*
16795d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        // - remove SKIP_SYNTAX; that is, remove half of the apostrophes
16805d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        // - if the result string contains an open curly brace '{' then
16815d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        //   instantiate a temporary MessageFormat object and format again;
16825d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        //   otherwise just append the result string
16835d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        String msgString = msgPattern.getPatternString();
16845d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        String subMsgString;
16855d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        StringBuilder sb = null;
16865d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        int prevIndex = msgPattern.getPart(msgStart).getLimit();
16875d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        for (int i = msgStart;;) {
16885d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            Part part = msgPattern.getPart(++i);
16895d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            Part.Type type = part.getType();
16905d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            int index = part.getIndex();
16915d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            if (type == Part.Type.MSG_LIMIT) {
16925d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                if (sb == null) {
16935d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    subMsgString = msgString.substring(prevIndex, index);
16945d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                } else {
16955d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    subMsgString = sb.append(msgString, prevIndex, index).toString();
16965d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                }
16975d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                break;
16985d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            } else if (type == Part.Type.REPLACE_NUMBER || type == Part.Type.SKIP_SYNTAX) {
16995d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                if (sb == null) {
17005d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    sb = new StringBuilder();
17015d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                }
17025d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                sb.append(msgString, prevIndex, index);
17035d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                if (type == Part.Type.REPLACE_NUMBER) {
17045d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    if(pluralNumber.forReplaceNumber) {
17055d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                        // number-offset was already formatted.
17065d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                        sb.append(pluralNumber.numberString);
17075d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    } else {
17085d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                        sb.append(getStockNumberFormatter().format(pluralNumber.number));
17095d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    }
17105d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                }
17115d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                prevIndex = part.getLimit();
17125d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            } else if (type == Part.Type.ARG_START) {
17135d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                if (sb == null) {
17145d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    sb = new StringBuilder();
17155d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                }
17165d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                sb.append(msgString, prevIndex, index);
17175d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                prevIndex = index;
17185d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                i = msgPattern.getLimitPartIndex(i);
17195d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                index = msgPattern.getPart(i).getLimit();
17205d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                MessagePattern.appendReducedApostrophes(msgString, prevIndex, index, sb);
17215d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                prevIndex = index;
17225d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            }
17235d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        }
17245d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        if (subMsgString.indexOf('{') >= 0) {
17255d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            MessageFormat subMsgFormat = new MessageFormat("", ulocale);
17265d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            subMsgFormat.applyPattern(subMsgString, MessagePattern.ApostropheMode.DOUBLE_REQUIRED);
17275d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            subMsgFormat.format(0, null, args, argsMap, dest, null);
17285d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        } else {
17295d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            dest.append(subMsgString);
17305d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        }
17318d05787d6a4b5762d790ccd2a9ed9dc8885986efNarayan Kamath        */
17325d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    }
17335d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
17345d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    /**
17355d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * Read as much literal string from the pattern string as possible. This stops
17365d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * as soon as it finds an argument, or it reaches the end of the string.
17375d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @param from Index in the pattern string to start from.
17385d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @return A substring from the pattern string representing the longest possible
17395d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *         substring with no arguments.
17405d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     */
17415d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    private String getLiteralStringUntilNextArgument(int from) {
17425d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        StringBuilder b = new StringBuilder();
17435d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        String msgString=msgPattern.getPatternString();
17445d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        int prevIndex=msgPattern.getPart(from).getLimit();
17455d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        for(int i=from+1;; ++i) {
17465d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            Part part=msgPattern.getPart(i);
17475d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            Part.Type type=part.getType();
17485d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            int index=part.getIndex();
17495d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            b.append(msgString, prevIndex, index);
17505d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            if(type==Part.Type.ARG_START || type==Part.Type.MSG_LIMIT) {
17515d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                return b.toString();
17525d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            }
17535d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            assert type==Part.Type.SKIP_SYNTAX || type==Part.Type.INSERT_CHAR :
17545d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    "Unexpected Part "+part+" in parsed message.";
17555d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            prevIndex=part.getLimit();
17565d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        }
17575d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    }
17585d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
17595d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    private FieldPosition updateMetaData(AppendableWrapper dest, int prevLength,
17605d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                                         FieldPosition fp, Object argId) {
17615d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        if (dest.attributes != null && prevLength < dest.length) {
17625d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            dest.attributes.add(new AttributeAndPosition(argId, prevLength, dest.length));
17635d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        }
17645d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        if (fp != null && Field.ARGUMENT.equals(fp.getFieldAttribute())) {
17655d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            fp.setBeginIndex(prevLength);
17665d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            fp.setEndIndex(dest.length);
17675d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            return null;
17685d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        }
17695d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        return fp;
17705d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    }
17715d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
17725d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    // This lives here because ICU4J does not have its own ChoiceFormat class.
17735d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    /**
17745d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * Finds the ChoiceFormat sub-message for the given number.
17755d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @param pattern A MessagePattern.
17765d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @param partIndex the index of the first ChoiceFormat argument style part.
17775d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @param number a number to be mapped to one of the ChoiceFormat argument's intervals
17785d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @return the sub-message start part index.
17795d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     */
17805d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    private static int findChoiceSubMessage(MessagePattern pattern, int partIndex, double number) {
17815d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        int count=pattern.countParts();
17825d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        int msgStart;
17835d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        // Iterate over (ARG_INT|DOUBLE, ARG_SELECTOR, message) tuples
17845d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        // until ARG_LIMIT or end of choice-only pattern.
17855d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        // Ignore the first number and selector and start the loop on the first message.
17865d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        partIndex+=2;
17875d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        for(;;) {
17885d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            // Skip but remember the current sub-message.
17895d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            msgStart=partIndex;
17905d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            partIndex=pattern.getLimitPartIndex(partIndex);
17915d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            if(++partIndex>=count) {
17925d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                // Reached the end of the choice-only pattern.
17935d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                // Return with the last sub-message.
17945d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                break;
17955d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            }
17965d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            Part part=pattern.getPart(partIndex++);
17975d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            Part.Type type=part.getType();
17985d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            if(type==Part.Type.ARG_LIMIT) {
17995d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                // Reached the end of the ChoiceFormat style.
18005d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                // Return with the last sub-message.
18015d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                break;
18025d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            }
18035d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            // part is an ARG_INT or ARG_DOUBLE
18045d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            assert type.hasNumericValue();
18055d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            double boundary=pattern.getNumericValue(part);
18065d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            // Fetch the ARG_SELECTOR character.
18075d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            int selectorIndex=pattern.getPatternIndex(partIndex++);
18085d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            char boundaryChar=pattern.getPatternString().charAt(selectorIndex);
18095d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            if(boundaryChar=='<' ? !(number>boundary) : !(number>=boundary)) {
18105d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                // The number is in the interval between the previous boundary and the current one.
18115d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                // Return with the sub-message between them.
18125d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                // The !(a>b) and !(a>=b) comparisons are equivalent to
18135d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                // (a<=b) and (a<b) except they "catch" NaN.
18145d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                break;
18155d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            }
18165d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        }
18175d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        return msgStart;
18185d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    }
18195d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
18205d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    // Ported from C++ ChoiceFormat::parse().
18215d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    private static double parseChoiceArgument(
18225d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            MessagePattern pattern, int partIndex,
18235d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            String source, ParsePosition pos) {
18245d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        // find the best number (defined as the one with the longest parse)
18255d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        int start = pos.getIndex();
18265d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        int furthest = start;
18275d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        double bestNumber = Double.NaN;
18285d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        double tempNumber = 0.0;
18295d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        while (pattern.getPartType(partIndex) != Part.Type.ARG_LIMIT) {
18305d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            tempNumber = pattern.getNumericValue(pattern.getPart(partIndex));
18315d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            partIndex += 2;  // skip the numeric part and ignore the ARG_SELECTOR
18325d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            int msgLimit = pattern.getLimitPartIndex(partIndex);
18335d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            int len = matchStringUntilLimitPart(pattern, partIndex, msgLimit, source, start);
18345d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            if (len >= 0) {
18355d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                int newIndex = start + len;
18365d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                if (newIndex > furthest) {
18375d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    furthest = newIndex;
18385d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    bestNumber = tempNumber;
18395d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    if (furthest == source.length()) {
18405d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                        break;
18415d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    }
18425d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                }
18435d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            }
18445d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            partIndex = msgLimit + 1;
18455d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        }
18465d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        if (furthest == start) {
18475d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            pos.setErrorIndex(start);
18485d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        } else {
18495d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            pos.setIndex(furthest);
18505d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        }
18515d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        return bestNumber;
18525d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    }
18535d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
18545d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    /**
18555d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * Matches the pattern string from the end of the partIndex to
18565d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * the beginning of the limitPartIndex,
18575d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * including all syntax except SKIP_SYNTAX,
18585d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * against the source string starting at sourceOffset.
18595d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * If they match, returns the length of the source string match.
18605d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * Otherwise returns -1.
18615d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     */
18625d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    private static int matchStringUntilLimitPart(
18635d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            MessagePattern pattern, int partIndex, int limitPartIndex,
18645d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            String source, int sourceOffset) {
18655d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        int matchingSourceLength = 0;
18665d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        String msgString = pattern.getPatternString();
18675d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        int prevIndex = pattern.getPart(partIndex).getLimit();
18685d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        for (;;) {
18695d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            Part part = pattern.getPart(++partIndex);
18705d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            if (partIndex == limitPartIndex || part.getType() == Part.Type.SKIP_SYNTAX) {
18715d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                int index = part.getIndex();
18725d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                int length = index - prevIndex;
18735d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                if (length != 0 && !source.regionMatches(sourceOffset, msgString, prevIndex, length)) {
18745d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    return -1;  // mismatch
18755d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                }
18765d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                matchingSourceLength += length;
18775d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                if (partIndex == limitPartIndex) {
18785d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    return matchingSourceLength;
18795d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                }
18805d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                prevIndex = part.getLimit();  // SKIP_SYNTAX
18815d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            }
18825d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        }
18835d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    }
18845d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
18855d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    /**
18865d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * Finds the "other" sub-message.
18875d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @param partIndex the index of the first PluralFormat argument style part.
18885d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @return the "other" sub-message start part index.
18895d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     */
18905d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    private int findOtherSubMessage(int partIndex) {
18915d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        int count=msgPattern.countParts();
18925d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        MessagePattern.Part part=msgPattern.getPart(partIndex);
18935d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        if(part.getType().hasNumericValue()) {
18945d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            ++partIndex;
18955d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        }
18965d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        // Iterate over (ARG_SELECTOR [ARG_INT|ARG_DOUBLE] message) tuples
18975d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        // until ARG_LIMIT or end of plural-only pattern.
18985d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        do {
18995d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            part=msgPattern.getPart(partIndex++);
19005d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            MessagePattern.Part.Type type=part.getType();
19015d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            if(type==MessagePattern.Part.Type.ARG_LIMIT) {
19025d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                break;
19035d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            }
19045d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            assert type==MessagePattern.Part.Type.ARG_SELECTOR;
19055d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            // part is an ARG_SELECTOR followed by an optional explicit value, and then a message
19065d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            if(msgPattern.partSubstringMatches(part, "other")) {
19075d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                return partIndex;
19085d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            }
19095d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            if(msgPattern.getPartType(partIndex).hasNumericValue()) {
19105d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                ++partIndex;  // skip the numeric-value part of "=1" etc.
19115d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            }
19125d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            partIndex=msgPattern.getLimitPartIndex(partIndex);
19135d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        } while(++partIndex<count);
19145d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        return 0;
19155d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    }
19165d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
19175d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    /**
19185d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * Returns the ARG_START index of the first occurrence of the plural number in a sub-message.
19195d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * Returns -1 if it is a REPLACE_NUMBER.
19205d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * Returns 0 if there is neither.
19215d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     */
19225d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    private int findFirstPluralNumberArg(int msgStart, String argName) {
19235d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        for(int i=msgStart+1;; ++i) {
19245d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            Part part=msgPattern.getPart(i);
19255d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            Part.Type type=part.getType();
19265d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            if(type==Part.Type.MSG_LIMIT) {
19275d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                return 0;
19285d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            }
19295d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            if(type==Part.Type.REPLACE_NUMBER) {
19305d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                return -1;
19315d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            }
19325d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            if(type==Part.Type.ARG_START) {
19335d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                ArgType argType=part.getArgType();
19345d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                if(argName.length()!=0 && (argType==ArgType.NONE || argType==ArgType.SIMPLE)) {
19355d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    part=msgPattern.getPart(i+1);  // ARG_NUMBER or ARG_NAME
19365d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    if(msgPattern.partSubstringMatches(part, argName)) {
19375d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                        return i;
19385d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    }
19395d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                }
19405d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                i=msgPattern.getLimitPartIndex(i);
19415d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            }
19425d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        }
19435d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    }
19445d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
19455d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    /**
19465d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * Mutable input/output values for the PluralSelectorProvider.
19475d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * Separate so that it is possible to make MessageFormat Freezable.
19485d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     */
19495d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    private static final class PluralSelectorContext {
19505d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        private PluralSelectorContext(int start, String name, Number num, double off) {
19515d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            startIndex = start;
19525d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            argName = name;
19535d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            // number needs to be set even when select() is not called.
19545d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            // Keep it as a Number/Formattable:
19555d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            // For format() methods, and to preserve information (e.g., BigDecimal).
19565d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            if(off == 0) {
19575d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                number = num;
19585d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            } else {
19595d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                number = num.doubleValue() - off;
19605d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            }
19615d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            offset = off;
19625d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        }
19635d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        @Override
19645d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        public String toString() {
19655d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            throw new AssertionError("PluralSelectorContext being formatted, rather than its number");
19665d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        }
19675d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
19685d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        // Input values for plural selection with decimals.
19695d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        int startIndex;
19705d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        String argName;
19715d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        /** argument number - plural offset */
19725d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        Number number;
19735d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        double offset;
19745d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        // Output values for plural selection with decimals.
19755d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        /** -1 if REPLACE_NUMBER, 0 arg not found, >0 ARG_START index */
19765d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        int numberArgIndex;
19775d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        Format formatter;
19785d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        /** formatted argument number - plural offset */
19795d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        String numberString;
19805d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        /** true if number-offset was formatted with the stock number formatter */
19815d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        boolean forReplaceNumber;
19825d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    }
19835d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
19845d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    /**
19855d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * This provider helps defer instantiation of a PluralRules object
19865d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * until we actually need to select a keyword.
19875d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * For example, if the number matches an explicit-value selector like "=1"
19885d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * we do not need any PluralRules.
19895d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     */
19905d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    private static final class PluralSelectorProvider implements PluralFormat.PluralSelector {
19915d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        public PluralSelectorProvider(MessageFormat mf, PluralType type) {
19925d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            msgFormat = mf;
19935d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            this.type = type;
19945d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        }
19955d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        public String select(Object ctx, double number) {
19965d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            if(rules == null) {
19978d05787d6a4b5762d790ccd2a9ed9dc8885986efNarayan Kamath                rules = PluralRules.forLocale(msgFormat.locale_, type);
19985d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            }
19995d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            // Select a sub-message according to how the number is formatted,
20005d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            // which is specified in the selected sub-message.
20015d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            // We avoid this circle by looking at how
20025d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            // the number is formatted in the "other" sub-message
20035d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            // which must always be present and usually contains the number.
20045d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            // Message authors should be consistent across sub-messages.
20055d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            PluralSelectorContext context = (PluralSelectorContext)ctx;
20065d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            int otherIndex = msgFormat.findOtherSubMessage(context.startIndex);
20075d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            context.numberArgIndex = msgFormat.findFirstPluralNumberArg(otherIndex, context.argName);
20085d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            if(context.numberArgIndex > 0 && msgFormat.cachedFormatters != null) {
20095d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                context.formatter = msgFormat.cachedFormatters.get(context.numberArgIndex);
20105d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            }
20115d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            if(context.formatter == null) {
20125d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                context.formatter = msgFormat.getStockNumberFormatter();
20135d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                context.forReplaceNumber = true;
20145d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            }
20155d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            assert context.number.doubleValue() == number;  // argument number minus the offset
20165d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            context.numberString = context.formatter.format(context.number);
20178d05787d6a4b5762d790ccd2a9ed9dc8885986efNarayan Kamath            /* TODO: Try to get FixedDecimal from formatted string.
20185d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            if(context.formatter instanceof DecimalFormat) {
20195d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                FixedDecimal dec = ((DecimalFormat)context.formatter).getFixedDecimal(number);
20205d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                return rules.select(dec);
20218d05787d6a4b5762d790ccd2a9ed9dc8885986efNarayan Kamath            } else */ {
20225d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                return rules.select(number);
20235d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            }
20245d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        }
20255d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        private MessageFormat msgFormat;
20265d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        private PluralRules rules;
20275d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        private PluralType type;
20285d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    }
20295d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
20305d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    @SuppressWarnings("unchecked")
20315d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    private void format(Object arguments, AppendableWrapper result, FieldPosition fp) {
20325d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        if ((arguments == null || arguments instanceof Map)) {
20335d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            format(null, (Map<String, Object>)arguments, result, fp);
20345d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        } else {
20355d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            format((Object[])arguments, null, result, fp);
20365d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        }
20375d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    }
20385d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
20395d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    /**
20405d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * Internal routine used by format.
20415d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *
20425d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @throws IllegalArgumentException if an argument in the
20435d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *         <code>arguments</code> map is not of the type
20445d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *         expected by the format element(s) that use it.
20455d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     */
20465d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    private void format(Object[] arguments, Map<String, Object> argsMap,
20475d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                        AppendableWrapper dest, FieldPosition fp) {
20485d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        if (arguments != null && msgPattern.hasNamedArguments()) {
20495d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            throw new IllegalArgumentException(
20505d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                "This method is not available in MessageFormat objects " +
20515d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                "that use alphanumeric argument names.");
20525d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        }
20538d05787d6a4b5762d790ccd2a9ed9dc8885986efNarayan Kamath        format(0, null, arguments, argsMap, null, dest, fp);
20545d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    }
20555d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
20565d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    private void resetPattern() {
20575d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        if (msgPattern != null) {
20585d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            msgPattern.clear();
20595d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        }
20605d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        if (cachedFormatters != null) {
20615d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            cachedFormatters.clear();
20625d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        }
20635d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        customFormatArgStarts = null;
20645d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    }
20655d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
20665d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    private static final String[] typeList =
20675d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        { "number", "date", "time", "spellout", "ordinal", "duration" };
20685d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    private static final int
20695d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        TYPE_NUMBER = 0,
20705d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        TYPE_DATE = 1,
20715d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        TYPE_TIME = 2,
20725d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        TYPE_SPELLOUT = 3,
20735d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        TYPE_ORDINAL = 4,
20745d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        TYPE_DURATION = 5;
20755d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
20765d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    private static final String[] modifierList =
20775d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        {"", "currency", "percent", "integer"};
20785d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
20795d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    private static final int
20805d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        MODIFIER_EMPTY = 0,
20815d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        MODIFIER_CURRENCY = 1,
20825d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        MODIFIER_PERCENT = 2,
20835d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        MODIFIER_INTEGER = 3;
20845d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
20855d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    private static final String[] dateModifierList =
20865d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        {"", "short", "medium", "long", "full"};
20875d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
20885d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    private static final int
20895d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        DATE_MODIFIER_EMPTY = 0,
20905d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        DATE_MODIFIER_SHORT = 1,
20915d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        DATE_MODIFIER_MEDIUM = 2,
20925d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        DATE_MODIFIER_LONG = 3,
20935d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        DATE_MODIFIER_FULL = 4;
20945d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
20955d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    // Creates an appropriate Format object for the type and style passed.
20965d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    // Both arguments cannot be null.
20975d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    private Format createAppropriateFormat(String type, String style) {
20985d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        Format newFormat = null;
20995d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        int subformatType  = findKeyword(type, typeList);
21005d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        switch (subformatType){
21015d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        case TYPE_NUMBER:
21025d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            switch (findKeyword(style, modifierList)) {
21035d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            case MODIFIER_EMPTY:
21048d05787d6a4b5762d790ccd2a9ed9dc8885986efNarayan Kamath                newFormat = NumberFormat.getInstance(locale_);
21055d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                break;
21065d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            case MODIFIER_CURRENCY:
21078d05787d6a4b5762d790ccd2a9ed9dc8885986efNarayan Kamath                newFormat = NumberFormat.getCurrencyInstance(locale_);
21085d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                break;
21095d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            case MODIFIER_PERCENT:
21108d05787d6a4b5762d790ccd2a9ed9dc8885986efNarayan Kamath                newFormat = NumberFormat.getPercentInstance(locale_);
21115d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                break;
21125d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            case MODIFIER_INTEGER:
21138d05787d6a4b5762d790ccd2a9ed9dc8885986efNarayan Kamath                newFormat = NumberFormat.getIntegerInstance(locale_);
21145d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                break;
21155d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            default: // pattern
21165d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                newFormat = new DecimalFormat(style,
21178d05787d6a4b5762d790ccd2a9ed9dc8885986efNarayan Kamath                        new DecimalFormatSymbols(locale_));
21185d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                break;
21195d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            }
21205d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            break;
21215d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        case TYPE_DATE:
21225d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            switch (findKeyword(style, dateModifierList)) {
21235d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            case DATE_MODIFIER_EMPTY:
21248d05787d6a4b5762d790ccd2a9ed9dc8885986efNarayan Kamath                newFormat = DateFormat.getDateInstance(DateFormat.DEFAULT, locale_);
21255d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                break;
21265d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            case DATE_MODIFIER_SHORT:
21278d05787d6a4b5762d790ccd2a9ed9dc8885986efNarayan Kamath                newFormat = DateFormat.getDateInstance(DateFormat.SHORT, locale_);
21285d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                break;
21295d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            case DATE_MODIFIER_MEDIUM:
21308d05787d6a4b5762d790ccd2a9ed9dc8885986efNarayan Kamath                newFormat = DateFormat.getDateInstance(DateFormat.DEFAULT, locale_);
21315d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                break;
21325d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            case DATE_MODIFIER_LONG:
21338d05787d6a4b5762d790ccd2a9ed9dc8885986efNarayan Kamath                newFormat = DateFormat.getDateInstance(DateFormat.LONG, locale_);
21345d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                break;
21355d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            case DATE_MODIFIER_FULL:
21368d05787d6a4b5762d790ccd2a9ed9dc8885986efNarayan Kamath                newFormat = DateFormat.getDateInstance(DateFormat.FULL, locale_);
21375d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                break;
21385d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            default:
21398d05787d6a4b5762d790ccd2a9ed9dc8885986efNarayan Kamath                newFormat = new SimpleDateFormat(style, locale_);
21405d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                break;
21415d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            }
21425d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            break;
21435d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        case TYPE_TIME:
21445d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            switch (findKeyword(style, dateModifierList)) {
21455d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            case DATE_MODIFIER_EMPTY:
21468d05787d6a4b5762d790ccd2a9ed9dc8885986efNarayan Kamath                newFormat = DateFormat.getTimeInstance(DateFormat.DEFAULT, locale_);
21475d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                break;
21485d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            case DATE_MODIFIER_SHORT:
21498d05787d6a4b5762d790ccd2a9ed9dc8885986efNarayan Kamath                newFormat = DateFormat.getTimeInstance(DateFormat.SHORT, locale_);
21505d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                break;
21515d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            case DATE_MODIFIER_MEDIUM:
21528d05787d6a4b5762d790ccd2a9ed9dc8885986efNarayan Kamath                newFormat = DateFormat.getTimeInstance(DateFormat.DEFAULT, locale_);
21535d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                break;
21545d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            case DATE_MODIFIER_LONG:
21558d05787d6a4b5762d790ccd2a9ed9dc8885986efNarayan Kamath                newFormat = DateFormat.getTimeInstance(DateFormat.LONG, locale_);
21565d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                break;
21575d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            case DATE_MODIFIER_FULL:
21588d05787d6a4b5762d790ccd2a9ed9dc8885986efNarayan Kamath                newFormat = DateFormat.getTimeInstance(DateFormat.FULL, locale_);
21595d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                break;
21605d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            default:
21618d05787d6a4b5762d790ccd2a9ed9dc8885986efNarayan Kamath                newFormat = new SimpleDateFormat(style, locale_);
21625d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                break;
21635d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            }
21645d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            break;
21658d05787d6a4b5762d790ccd2a9ed9dc8885986efNarayan Kamath        /* There is no java.text.RuleBasedNumberFormat --
21665d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        case TYPE_SPELLOUT:
21675d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            {
21685d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                RuleBasedNumberFormat rbnf = new RuleBasedNumberFormat(ulocale,
21695d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                        RuleBasedNumberFormat.SPELLOUT);
21705d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                String ruleset = style.trim();
21715d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                if (ruleset.length() != 0) {
21725d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    try {
21735d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                        rbnf.setDefaultRuleSet(ruleset);
21745d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    }
21755d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    catch (Exception e) {
21765d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                        // warn invalid ruleset
21775d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    }
21785d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                }
21795d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                newFormat = rbnf;
21805d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            }
21815d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            break;
21825d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        case TYPE_ORDINAL:
21835d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            {
21845d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                RuleBasedNumberFormat rbnf = new RuleBasedNumberFormat(ulocale,
21855d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                        RuleBasedNumberFormat.ORDINAL);
21865d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                String ruleset = style.trim();
21875d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                if (ruleset.length() != 0) {
21885d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    try {
21895d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                        rbnf.setDefaultRuleSet(ruleset);
21905d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    }
21915d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    catch (Exception e) {
21925d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                        // warn invalid ruleset
21935d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    }
21945d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                }
21955d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                newFormat = rbnf;
21965d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            }
21975d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            break;
21985d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        case TYPE_DURATION:
21995d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            {
22005d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                RuleBasedNumberFormat rbnf = new RuleBasedNumberFormat(ulocale,
22015d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                        RuleBasedNumberFormat.DURATION);
22025d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                String ruleset = style.trim();
22035d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                if (ruleset.length() != 0) {
22045d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    try {
22055d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                        rbnf.setDefaultRuleSet(ruleset);
22065d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    }
22075d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    catch (Exception e) {
22085d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                        // warn invalid ruleset
22095d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    }
22105d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                }
22115d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                newFormat = rbnf;
22125d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            }
22135d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            break;
22148d05787d6a4b5762d790ccd2a9ed9dc8885986efNarayan Kamath        */
22155d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        default:
22165d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            throw new IllegalArgumentException("Unknown format type \"" + type + "\"");
22175d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        }
22185d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        return newFormat;
22195d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    }
22205d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
22215d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    private static final Locale rootLocale = new Locale("");  // Locale.ROOT only @since 1.6
22225d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
22235d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    private static final int findKeyword(String s, String[] list) {
22245d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        s = PatternProps.trimWhiteSpace(s).toLowerCase(rootLocale);
22255d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        for (int i = 0; i < list.length; ++i) {
22265d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            if (s.equals(list[i]))
22275d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                return i;
22285d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        }
22295d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        return -1;
22305d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    }
22315d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
22325d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    private void cacheExplicitFormats() {
22335d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        if (cachedFormatters != null) {
22345d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            cachedFormatters.clear();
22355d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        }
22365d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        customFormatArgStarts = null;
22375d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        // The last two "parts" can at most be ARG_LIMIT and MSG_LIMIT
22385d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        // which we need not examine.
22395d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        int limit = msgPattern.countParts() - 2;
22405d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        // This loop starts at part index 1 because we do need to examine
22415d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        // ARG_START parts. (But we can ignore the MSG_START.)
22425d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        for(int i=1; i < limit; ++i) {
22435d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            Part part = msgPattern.getPart(i);
22445d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            if(part.getType()!=Part.Type.ARG_START) {
22455d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                continue;
22465d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            }
22475d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            ArgType argType=part.getArgType();
22485d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            if(argType != ArgType.SIMPLE) {
22495d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                continue;
22505d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            }
22515d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            int index = i;
22525d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            i += 2;
22535d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            String explicitType = msgPattern.getSubstring(msgPattern.getPart(i++));
22545d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            String style = "";
22555d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            if ((part = msgPattern.getPart(i)).getType() == MessagePattern.Part.Type.ARG_STYLE) {
22565d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                style = msgPattern.getSubstring(part);
22575d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                ++i;
22585d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            }
22595d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            Format formatter = createAppropriateFormat(explicitType, style);
22605d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            setArgStartFormat(index, formatter);
22615d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        }
22625d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    }
22635d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
22645d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    /**
22655d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * Sets a formatter for a MessagePattern ARG_START part index.
22665d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     */
22675d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    private void setArgStartFormat(int argStart, Format formatter) {
22685d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        if (cachedFormatters == null) {
22695d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            cachedFormatters = new HashMap<Integer, Format>();
22705d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        }
22715d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        cachedFormatters.put(argStart, formatter);
22725d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    }
22735d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
22745d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    /**
22755d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * Sets a custom formatter for a MessagePattern ARG_START part index.
22765d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * "Custom" formatters are provided by the user via setFormat() or similar APIs.
22775d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     */
22785d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    private void setCustomArgStartFormat(int argStart, Format formatter) {
22795d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        setArgStartFormat(argStart, formatter);
22805d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        if (customFormatArgStarts == null) {
22815d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            customFormatArgStarts = new HashSet<Integer>();
22825d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        }
22835d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        customFormatArgStarts.add(argStart);
22845d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    }
22855d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
22865d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    private static final char SINGLE_QUOTE = '\'';
22875d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    private static final char CURLY_BRACE_LEFT = '{';
22885d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    private static final char CURLY_BRACE_RIGHT = '}';
22895d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
22905d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    private static final int STATE_INITIAL = 0;
22915d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    private static final int STATE_SINGLE_QUOTE = 1;
22925d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    private static final int STATE_IN_QUOTE = 2;
22935d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    private static final int STATE_MSG_ELEMENT = 3;
22945d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
22955d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    /**
22965d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * {@icu} Converts an 'apostrophe-friendly' pattern into a standard
22975d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * pattern.
22985d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * <em>This is obsolete for ICU 4.8 and higher MessageFormat pattern strings.</em>
22995d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * It can still be useful together with the JDK MessageFormat.
23005d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *
23015d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * <p>See the class description for more about apostrophes and quoting,
23025d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * and differences between ICU and the JDK.
23035d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *
23045d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * <p>The JDK MessageFormat and ICU 4.6 and earlier MessageFormat
23055d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * treat all ASCII apostrophes as
23065d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * quotes, which is problematic in some languages, e.g.
23075d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * French, where apostrophe is commonly used.  This utility
23085d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * assumes that only an unpaired apostrophe immediately before
23095d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * a brace is a true quote.  Other unpaired apostrophes are paired,
23105d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * and the resulting standard pattern string is returned.
23115d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *
23125d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * <p><b>Note</b>: It is not guaranteed that the returned pattern
23135d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * is indeed a valid pattern.  The only effect is to convert
23145d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * between patterns having different quoting semantics.
23155d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *
23165d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * <p><b>Note</b>: This method only works on top-level messageText,
23175d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * not messageText nested inside a complexArg.
23185d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *
23195d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @param pattern the 'apostrophe-friendly' pattern to convert
23205d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @return the standard equivalent of the original pattern
23215d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @stable ICU 3.4
23225d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     */
23235d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    public static String autoQuoteApostrophe(String pattern) {
23245d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        StringBuilder buf = new StringBuilder(pattern.length() * 2);
23255d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        int state = STATE_INITIAL;
23265d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        int braceCount = 0;
23275d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        for (int i = 0, j = pattern.length(); i < j; ++i) {
23285d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            char c = pattern.charAt(i);
23295d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            switch (state) {
23305d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            case STATE_INITIAL:
23315d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                switch (c) {
23325d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                case SINGLE_QUOTE:
23335d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    state = STATE_SINGLE_QUOTE;
23345d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    break;
23355d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                case CURLY_BRACE_LEFT:
23365d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    state = STATE_MSG_ELEMENT;
23375d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    ++braceCount;
23385d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    break;
23395d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                }
23405d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                break;
23415d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            case STATE_SINGLE_QUOTE:
23425d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                switch (c) {
23435d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                case SINGLE_QUOTE:
23445d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    state = STATE_INITIAL;
23455d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    break;
23465d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                case CURLY_BRACE_LEFT:
23475d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                case CURLY_BRACE_RIGHT:
23485d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    state = STATE_IN_QUOTE;
23495d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    break;
23505d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                default:
23515d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    buf.append(SINGLE_QUOTE);
23525d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    state = STATE_INITIAL;
23535d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    break;
23545d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                }
23555d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                break;
23565d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            case STATE_IN_QUOTE:
23575d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                switch (c) {
23585d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                case SINGLE_QUOTE:
23595d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    state = STATE_INITIAL;
23605d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    break;
23615d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                }
23625d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                break;
23635d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            case STATE_MSG_ELEMENT:
23645d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                switch (c) {
23655d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                case CURLY_BRACE_LEFT:
23665d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    ++braceCount;
23675d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    break;
23685d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                case CURLY_BRACE_RIGHT:
23695d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    if (--braceCount == 0) {
23705d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                        state = STATE_INITIAL;
23715d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    }
23725d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    break;
23735d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                }
23745d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                break;
23755d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            ///CLOVER:OFF
23765d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            default: // Never happens.
23775d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                break;
23785d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            ///CLOVER:ON
23795d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            }
23805d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            buf.append(c);
23815d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        }
23825d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        // End of scan
23835d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        if (state == STATE_SINGLE_QUOTE || state == STATE_IN_QUOTE) {
23845d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            buf.append(SINGLE_QUOTE);
23855d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        }
23865d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        return new String(buf);
23875d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    }
23885d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
23895d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    /**
23905d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * Convenience wrapper for Appendable, tracks the result string length.
23915d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * Also, Appendable throws IOException, and we turn that into a RuntimeException
23925d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * so that we need no throws clauses.
23935d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     */
23945d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    private static final class AppendableWrapper {
23955d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        public AppendableWrapper(StringBuilder sb) {
23965d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            app = sb;
23975d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            length = sb.length();
23985d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            attributes = null;
23995d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        }
24005d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
24015d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        public AppendableWrapper(StringBuffer sb) {
24025d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            app = sb;
24035d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            length = sb.length();
24045d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            attributes = null;
24055d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        }
24065d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
24075d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        public void useAttributes() {
24085d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            attributes = new ArrayList<AttributeAndPosition>();
24095d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        }
24105d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
24115d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        public void append(CharSequence s) {
24125d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            try {
24135d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                app.append(s);
24145d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                length += s.length();
24155d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            } catch(IOException e) {
24168d05787d6a4b5762d790ccd2a9ed9dc8885986efNarayan Kamath                throw new ICUUncheckedIOException(e);
24175d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            }
24185d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        }
24195d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
24205d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        public void append(CharSequence s, int start, int limit) {
24215d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            try {
24225d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                app.append(s, start, limit);
24235d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                length += limit - start;
24245d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            } catch(IOException e) {
24258d05787d6a4b5762d790ccd2a9ed9dc8885986efNarayan Kamath                throw new ICUUncheckedIOException(e);
24265d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            }
24275d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        }
24285d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
24295d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        public void append(CharacterIterator iterator) {
24305d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            length += append(app, iterator);
24315d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        }
24325d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
24335d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        public static int append(Appendable result, CharacterIterator iterator) {
24345d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            try {
24355d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                int start = iterator.getBeginIndex();
24365d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                int limit = iterator.getEndIndex();
24375d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                int length = limit - start;
24385d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                if (start < limit) {
24395d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    result.append(iterator.first());
24405d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    while (++start < limit) {
24415d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                        result.append(iterator.next());
24425d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    }
24435d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                }
24445d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                return length;
24455d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            } catch(IOException e) {
24468d05787d6a4b5762d790ccd2a9ed9dc8885986efNarayan Kamath                throw new ICUUncheckedIOException(e);
24475d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            }
24485d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        }
24495d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
24505d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        public void formatAndAppend(Format formatter, Object arg) {
24515d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            if (attributes == null) {
24525d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                append(formatter.format(arg));
24535d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            } else {
24545d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                AttributedCharacterIterator formattedArg = formatter.formatToCharacterIterator(arg);
24555d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                int prevLength = length;
24565d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                append(formattedArg);
24575d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                // Copy all of the attributes from formattedArg to our attributes list.
24585d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                formattedArg.first();
24595d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                int start = formattedArg.getIndex();  // Should be 0 but might not be.
24605d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                int limit = formattedArg.getEndIndex();  // == start + length - prevLength
24615d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                int offset = prevLength - start;  // Adjust attribute indexes for the result string.
24625d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                while (start < limit) {
24635d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    Map<Attribute, Object> map = formattedArg.getAttributes();
24645d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    int runLimit = formattedArg.getRunLimit();
24655d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    if (map.size() != 0) {
24665d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                        for (Map.Entry<Attribute, Object> entry : map.entrySet()) {
24675d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                           attributes.add(
24685d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                               new AttributeAndPosition(
24695d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                                   entry.getKey(), entry.getValue(),
24705d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                                   offset + start, offset + runLimit));
24715d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                        }
24725d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    }
24735d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    start = runLimit;
24745d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    formattedArg.setIndex(start);
24755d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                }
24765d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            }
24775d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        }
24785d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
24795d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        public void formatAndAppend(Format formatter, Object arg, String argString) {
24805d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            if (attributes == null && argString != null) {
24815d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                append(argString);
24825d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            } else {
24835d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                formatAndAppend(formatter, arg);
24845d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            }
24855d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        }
24865d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
24875d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        private Appendable app;
24885d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        private int length;
24895d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        private List<AttributeAndPosition> attributes;
24905d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    }
24915d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
24925d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    private static final class AttributeAndPosition {
24935d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        /**
24945d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath         * Defaults the field to Field.ARGUMENT.
24955d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath         */
24965d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        public AttributeAndPosition(Object fieldValue, int startIndex, int limitIndex) {
24975d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            init(Field.ARGUMENT, fieldValue, startIndex, limitIndex);
24985d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        }
24995d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
25005d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        public AttributeAndPosition(Attribute field, Object fieldValue, int startIndex, int limitIndex) {
25015d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            init(field, fieldValue, startIndex, limitIndex);
25025d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        }
25035d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
25045d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        public void init(Attribute field, Object fieldValue, int startIndex, int limitIndex) {
25055d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            key = field;
25065d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            value = fieldValue;
25075d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            start = startIndex;
25085d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            limit = limitIndex;
25095d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        }
25105d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
25115d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        private Attribute key;
25125d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        private Object value;
25135d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        private int start;
25145d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        private int limit;
25155d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    }
25165d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath}
2517