15d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath/*
25d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath*******************************************************************************
38d05787d6a4b5762d790ccd2a9ed9dc8885986efNarayan Kamath*   Copyright (C) 2010-2014, International Business Machines
45d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath*   Corporation and others.  All Rights Reserved.
55d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath*******************************************************************************
65d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath*   created on: 2010aug21
75d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath*   created by: Markus W. Scherer
85d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath*/
95d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
105d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamathpackage com.ibm.icu.text;
115d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
125d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamathimport java.util.ArrayList;
135d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamathimport java.util.Locale;
145d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
155d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamathimport com.ibm.icu.impl.ICUConfig;
165d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamathimport com.ibm.icu.impl.PatternProps;
175d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamathimport com.ibm.icu.util.Freezable;
188d05787d6a4b5762d790ccd2a9ed9dc8885986efNarayan Kamathimport com.ibm.icu.util.ICUCloneNotSupportedException;
195d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
205d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath//Note: Minimize ICU dependencies, only use a very small part of the ICU core.
215d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath//In particular, do not depend on *Format classes.
225d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
235d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath/**
245d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * Parses and represents ICU MessageFormat patterns.
255d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * Also handles patterns for ChoiceFormat, PluralFormat and SelectFormat.
265d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * Used in the implementations of those classes as well as in tools
275d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * for message validation, translation and format conversion.
285d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * <p>
295d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * The parser handles all syntax relevant for identifying message arguments.
305d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * This includes "complex" arguments whose style strings contain
315d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * nested MessageFormat pattern substrings.
325d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * For "simple" arguments (with no nested MessageFormat pattern substrings),
335d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * the argument style is not parsed any further.
345d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * <p>
355d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * The parser handles named and numbered message arguments and allows both in one message.
365d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * <p>
375d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * Once a pattern has been parsed successfully, iterate through the parsed data
385d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * with countParts(), getPart() and related methods.
395d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * <p>
405d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * The data logically represents a parse tree, but is stored and accessed
415d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * as a list of "parts" for fast and simple parsing and to minimize object allocations.
425d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * Arguments and nested messages are best handled via recursion.
435d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * For every _START "part", {@link #getLimitPartIndex(int)} efficiently returns
445d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * the index of the corresponding _LIMIT "part".
455d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * <p>
465d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * List of "parts":
475d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * <pre>
485d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * message = MSG_START (SKIP_SYNTAX | INSERT_CHAR | REPLACE_NUMBER | argument)* MSG_LIMIT
495d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * argument = noneArg | simpleArg | complexArg
505d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * complexArg = choiceArg | pluralArg | selectArg
515d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *
525d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * noneArg = ARG_START.NONE (ARG_NAME | ARG_NUMBER) ARG_LIMIT.NONE
535d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * simpleArg = ARG_START.SIMPLE (ARG_NAME | ARG_NUMBER) ARG_TYPE [ARG_STYLE] ARG_LIMIT.SIMPLE
545d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * choiceArg = ARG_START.CHOICE (ARG_NAME | ARG_NUMBER) choiceStyle ARG_LIMIT.CHOICE
555d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * pluralArg = ARG_START.PLURAL (ARG_NAME | ARG_NUMBER) pluralStyle ARG_LIMIT.PLURAL
565d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * selectArg = ARG_START.SELECT (ARG_NAME | ARG_NUMBER) selectStyle ARG_LIMIT.SELECT
575d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *
585d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * choiceStyle = ((ARG_INT | ARG_DOUBLE) ARG_SELECTOR message)+
595d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * pluralStyle = [ARG_INT | ARG_DOUBLE] (ARG_SELECTOR [ARG_INT | ARG_DOUBLE] message)+
605d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * selectStyle = (ARG_SELECTOR message)+
615d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * </pre>
625d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * <ul>
635d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *   <li>Literal output text is not represented directly by "parts" but accessed
645d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *       between parts of a message, from one part's getLimit() to the next part's getIndex().
655d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *   <li><code>ARG_START.CHOICE</code> stands for an ARG_START Part with ArgType CHOICE.
665d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *   <li>In the choiceStyle, the ARG_SELECTOR has the '<', the '#' or
675d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *       the less-than-or-equal-to sign (U+2264).
685d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *   <li>In the pluralStyle, the first, optional numeric Part has the "offset:" value.
695d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *       The optional numeric Part between each (ARG_SELECTOR, message) pair
705d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *       is the value of an explicit-number selector like "=2",
715d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *       otherwise the selector is a non-numeric identifier.
725d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *   <li>The REPLACE_NUMBER Part can occur only in an immediate sub-message of the pluralStyle.
735d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * <p>
745d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * This class is not intended for public subclassing.
755d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath *
765d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * @stable ICU 4.8
775d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath * @author Markus Scherer
785d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath */
795d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamathpublic final class MessagePattern implements Cloneable, Freezable<MessagePattern> {
805d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    /**
815d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * Mode for when an apostrophe starts quoted literal text for MessageFormat output.
825d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * The default is DOUBLE_OPTIONAL unless overridden via ICUConfig
835d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * (/com/ibm/icu/ICUConfig.properties).
845d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * <p>
855d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * A pair of adjacent apostrophes always results in a single apostrophe in the output,
865d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * even when the pair is between two single, text-quoting apostrophes.
875d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * <p>
885d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * The following table shows examples of desired MessageFormat.format() output
895d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * with the pattern strings that yield that output.
905d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * <p>
915d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * <table>
925d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *   <tr>
935d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *     <th>Desired output</th>
945d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *     <th>DOUBLE_OPTIONAL</th>
955d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *     <th>DOUBLE_REQUIRED</th>
965d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *   </tr>
975d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *   <tr>
985d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *     <td>I see {many}</td>
995d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *     <td>I see '{many}'</td>
1005d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *     <td>(same)</td>
1015d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *   </tr>
1025d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *   <tr>
1035d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *     <td>I said {'Wow!'}</td>
1045d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *     <td>I said '{''Wow!''}'</td>
1055d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *     <td>(same)</td>
1065d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *   </tr>
1075d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *   <tr>
1085d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *     <td>I don't know</td>
1095d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *     <td>I don't know OR<br> I don''t know</td>
1105d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *     <td>I don''t know</td>
1115d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *   </tr>
1125d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * </table>
1135d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @stable ICU 4.8
1145d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     */
1155d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    public enum ApostropheMode {
1165d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        /**
1175d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath         * A literal apostrophe is represented by
1185d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath         * either a single or a double apostrophe pattern character.
1195d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath         * Within a MessageFormat pattern, a single apostrophe only starts quoted literal text
1205d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath         * if it immediately precedes a curly brace {},
1215d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath         * or a pipe symbol | if inside a choice format,
1225d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath         * or a pound symbol # if inside a plural format.
1235d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath         * <p>
1245d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath         * This is the default behavior starting with ICU 4.8.
1255d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath         * @stable ICU 4.8
1265d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath         */
1275d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        DOUBLE_OPTIONAL,
1285d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        /**
1295d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath         * A literal apostrophe must be represented by
1305d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath         * a double apostrophe pattern character.
1315d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath         * A single apostrophe always starts quoted literal text.
1325d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath         * <p>
1335d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath         * This is the behavior of ICU 4.6 and earlier, and of the JDK.
1345d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath         * @stable ICU 4.8
1355d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath         */
1365d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        DOUBLE_REQUIRED
1375d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    }
1385d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
1395d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    /**
1405d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * Constructs an empty MessagePattern with default ApostropheMode.
1415d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @stable ICU 4.8
1425d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     */
1435d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    public MessagePattern() {
1445d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        aposMode=defaultAposMode;
1455d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    }
1465d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
1475d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    /**
1485d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * Constructs an empty MessagePattern.
1495d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @param mode Explicit ApostropheMode.
1505d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @stable ICU 4.8
1515d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     */
1525d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    public MessagePattern(ApostropheMode mode) {
1535d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        aposMode=mode;
1545d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    }
1555d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
1565d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    /**
1575d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * Constructs a MessagePattern with default ApostropheMode and
1585d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * parses the MessageFormat pattern string.
1595d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @param pattern a MessageFormat pattern string
1605d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @throws IllegalArgumentException for syntax errors in the pattern string
1615d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @throws IndexOutOfBoundsException if certain limits are exceeded
1625d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *         (e.g., argument number too high, argument name too long, etc.)
1635d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @throws NumberFormatException if a number could not be parsed
1645d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @stable ICU 4.8
1655d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     */
1665d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    public MessagePattern(String pattern) {
1675d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        aposMode=defaultAposMode;
1685d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        parse(pattern);
1695d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    }
1705d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
1715d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    /**
1725d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * Parses a MessageFormat pattern string.
1735d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @param pattern a MessageFormat pattern string
1745d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @return this
1755d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @throws IllegalArgumentException for syntax errors in the pattern string
1765d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @throws IndexOutOfBoundsException if certain limits are exceeded
1775d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *         (e.g., argument number too high, argument name too long, etc.)
1785d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @throws NumberFormatException if a number could not be parsed
1795d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @stable ICU 4.8
1805d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     */
1815d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    public MessagePattern parse(String pattern) {
1825d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        preParse(pattern);
1835d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        parseMessage(0, 0, 0, ArgType.NONE);
1845d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        postParse();
1855d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        return this;
1865d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    }
1875d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
1885d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    /**
1895d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * Parses a ChoiceFormat pattern string.
1905d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @param pattern a ChoiceFormat pattern string
1915d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @return this
1925d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @throws IllegalArgumentException for syntax errors in the pattern string
1935d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @throws IndexOutOfBoundsException if certain limits are exceeded
1945d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *         (e.g., argument number too high, argument name too long, etc.)
1955d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @throws NumberFormatException if a number could not be parsed
1965d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @stable ICU 4.8
1975d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     */
1985d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    public MessagePattern parseChoiceStyle(String pattern) {
1995d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        preParse(pattern);
2005d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        parseChoiceStyle(0, 0);
2015d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        postParse();
2025d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        return this;
2035d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    }
2045d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
2055d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    /**
2065d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * Parses a PluralFormat pattern string.
2075d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @param pattern a PluralFormat pattern string
2085d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @return this
2095d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @throws IllegalArgumentException for syntax errors in the pattern string
2105d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @throws IndexOutOfBoundsException if certain limits are exceeded
2115d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *         (e.g., argument number too high, argument name too long, etc.)
2125d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @throws NumberFormatException if a number could not be parsed
2135d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @stable ICU 4.8
2145d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     */
2155d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    public MessagePattern parsePluralStyle(String pattern) {
2165d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        preParse(pattern);
2175d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        parsePluralOrSelectStyle(ArgType.PLURAL, 0, 0);
2185d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        postParse();
2195d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        return this;
2205d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    }
2215d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
2225d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    /**
2235d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * Parses a SelectFormat pattern string.
2245d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @param pattern a SelectFormat pattern string
2255d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @return this
2265d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @throws IllegalArgumentException for syntax errors in the pattern string
2275d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @throws IndexOutOfBoundsException if certain limits are exceeded
2285d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *         (e.g., argument number too high, argument name too long, etc.)
2295d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @throws NumberFormatException if a number could not be parsed
2305d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @stable ICU 4.8
2315d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     */
2325d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    public MessagePattern parseSelectStyle(String pattern) {
2335d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        preParse(pattern);
2345d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        parsePluralOrSelectStyle(ArgType.SELECT, 0, 0);
2355d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        postParse();
2365d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        return this;
2375d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    }
2385d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
2395d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    /**
2405d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * Clears this MessagePattern.
2415d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * countParts() will return 0.
2425d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @stable ICU 4.8
2435d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     */
2445d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    public void clear() {
2455d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        // Mostly the same as preParse().
2465d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        if(isFrozen()) {
2475d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            throw new UnsupportedOperationException(
2485d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                "Attempt to clear() a frozen MessagePattern instance.");
2495d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        }
2505d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        msg=null;
2515d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        hasArgNames=hasArgNumbers=false;
2525d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        needsAutoQuoting=false;
2535d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        parts.clear();
2545d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        if(numericValues!=null) {
2555d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            numericValues.clear();
2565d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        }
2575d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    }
2585d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
2595d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    /**
2605d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * Clears this MessagePattern and sets the ApostropheMode.
2615d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * countParts() will return 0.
2625d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @param mode The new ApostropheMode.
2635d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @stable ICU 4.8
2645d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     */
2655d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    public void clearPatternAndSetApostropheMode(ApostropheMode mode) {
2665d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        clear();
2675d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        aposMode=mode;
2685d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    }
2695d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
2705d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    /**
2715d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @param other another object to compare with.
2725d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @return true if this object is equivalent to the other one.
2735d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @stable ICU 4.8
2745d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     */
2755d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    @Override
2765d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    public boolean equals(Object other) {
2775d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        if(this==other) {
2785d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            return true;
2795d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        }
2805d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        if(other==null || getClass()!=other.getClass()) {
2815d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            return false;
2825d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        }
2835d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        MessagePattern o=(MessagePattern)other;
2845d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        return
2855d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            aposMode.equals(o.aposMode) &&
2865d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            (msg==null ? o.msg==null : msg.equals(o.msg)) &&
2875d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            parts.equals(o.parts);
2885d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        // No need to compare numericValues if msg and parts are the same.
2895d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    }
2905d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
2915d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    /**
2925d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * {@inheritDoc}
2935d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @stable ICU 4.8
2945d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     */
2955d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    @Override
2965d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    public int hashCode() {
2975d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        return (aposMode.hashCode()*37+(msg!=null ? msg.hashCode() : 0))*37+parts.hashCode();
2985d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    }
2995d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
3005d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    /**
3015d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @return this instance's ApostropheMode.
3025d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @stable ICU 4.8
3035d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     */
3045d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    public ApostropheMode getApostropheMode() {
3055d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        return aposMode;
3065d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    }
3075d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
3085d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    /**
3095d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @return true if getApostropheMode() == ApostropheMode.DOUBLE_REQUIRED
3105d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @internal
3115d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     */
3128d05787d6a4b5762d790ccd2a9ed9dc8885986efNarayan Kamath    public boolean jdkAposMode() {
3135d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        return aposMode == ApostropheMode.DOUBLE_REQUIRED;
3145d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    }
3155d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
3165d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    /**
3175d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @return the parsed pattern string (null if none was parsed).
3185d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @stable ICU 4.8
3195d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     */
3205d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    public String getPatternString() {
3215d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        return msg;
3225d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    }
3235d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
3245d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    /**
3255d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * Does the parsed pattern have named arguments like {first_name}?
3265d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @return true if the parsed pattern has at least one named argument.
3275d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @stable ICU 4.8
3285d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     */
3295d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    public boolean hasNamedArguments() {
3305d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        return hasArgNames;
3315d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    }
3325d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
3335d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    /**
3345d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * Does the parsed pattern have numbered arguments like {2}?
3355d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @return true if the parsed pattern has at least one numbered argument.
3365d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @stable ICU 4.8
3375d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     */
3385d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    public boolean hasNumberedArguments() {
3395d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        return hasArgNumbers;
3405d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    }
3415d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
3425d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    /**
3435d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * {@inheritDoc}
3445d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @stable ICU 4.8
3455d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     */
3465d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    @Override
3475d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    public String toString() {
3485d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        return msg;
3495d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    }
3505d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
3515d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    /**
3525d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * Validates and parses an argument name or argument number string.
3535d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * An argument name must be a "pattern identifier", that is, it must contain
3545d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * no Unicode Pattern_Syntax or Pattern_White_Space characters.
3555d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * If it only contains ASCII digits, then it must be a small integer with no leading zero.
3565d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @param name Input string.
3575d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @return &gt;=0 if the name is a valid number,
3585d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *         ARG_NAME_NOT_NUMBER (-1) if it is a "pattern identifier" but not all ASCII digits,
3595d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *         ARG_NAME_NOT_VALID (-2) if it is neither.
3605d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @stable ICU 4.8
3615d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     */
3625d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    public static int validateArgumentName(String name) {
3635d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        if(!PatternProps.isIdentifier(name)) {
3645d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            return ARG_NAME_NOT_VALID;
3655d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        }
3665d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        return parseArgNumber(name, 0, name.length());
3675d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    }
3685d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
3695d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    /**
3705d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * Return value from {@link #validateArgumentName(String)} for when
3715d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * the string is a valid "pattern identifier" but not a number.
3725d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @stable ICU 4.8
3735d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     */
3745d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    public static final int ARG_NAME_NOT_NUMBER=-1;
3755d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
3765d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    /**
3775d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * Return value from {@link #validateArgumentName(String)} for when
3785d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * the string is invalid.
3795d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * It might not be a valid "pattern identifier",
3805d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * or it have only ASCII digits but there is a leading zero or the number is too large.
3815d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @stable ICU 4.8
3825d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     */
3835d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    public static final int ARG_NAME_NOT_VALID=-2;
3845d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
3855d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    /**
3865d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * Returns a version of the parsed pattern string where each ASCII apostrophe
3875d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * is doubled (escaped) if it is not already, and if it is not interpreted as quoting syntax.
3885d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * <p>
3895d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * For example, this turns "I don't '{know}' {gender,select,female{h''er}other{h'im}}."
3905d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * into "I don''t '{know}' {gender,select,female{h''er}other{h''im}}."
3915d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @return the deep-auto-quoted version of the parsed pattern string.
3925d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @see MessageFormat#autoQuoteApostrophe(String)
3935d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @stable ICU 4.8
3945d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     */
3955d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    public String autoQuoteApostropheDeep() {
3965d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        if(!needsAutoQuoting) {
3975d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            return msg;
3985d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        }
3995d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        StringBuilder modified=null;
4005d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        // Iterate backward so that the insertion indexes do not change.
4015d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        int count=countParts();
4025d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        for(int i=count; i>0;) {
4035d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            Part part;
4045d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            if((part=getPart(--i)).getType()==Part.Type.INSERT_CHAR) {
4055d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                if(modified==null) {
4065d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    modified=new StringBuilder(msg.length()+10).append(msg);
4075d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                }
4085d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                modified.insert(part.index, (char)part.value);
4095d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            }
4105d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        }
4115d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        if(modified==null) {
4125d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            return msg;
4135d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        } else {
4145d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            return modified.toString();
4155d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        }
4165d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    }
4175d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
4185d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    /**
4195d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * Returns the number of "parts" created by parsing the pattern string.
4205d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * Returns 0 if no pattern has been parsed or clear() was called.
4215d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @return the number of pattern parts.
4225d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @stable ICU 4.8
4235d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     */
4245d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    public int countParts() {
4255d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        return parts.size();
4265d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    }
4275d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
4285d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    /**
4295d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * Gets the i-th pattern "part".
4305d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @param i The index of the Part data. (0..countParts()-1)
4315d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @return the i-th pattern "part".
4325d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @throws IndexOutOfBoundsException if i is outside the (0..countParts()-1) range
4335d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @stable ICU 4.8
4345d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     */
4355d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    public Part getPart(int i) {
4365d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        return parts.get(i);
4375d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    }
4385d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
4395d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    /**
4405d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * Returns the Part.Type of the i-th pattern "part".
4415d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * Convenience method for getPart(i).getType().
4425d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @param i The index of the Part data. (0..countParts()-1)
4435d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @return The Part.Type of the i-th Part.
4445d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @throws IndexOutOfBoundsException if i is outside the (0..countParts()-1) range
4455d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @stable ICU 4.8
4465d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     */
4475d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    public Part.Type getPartType(int i) {
4485d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        return parts.get(i).type;
4495d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    }
4505d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
4515d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    /**
4525d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * Returns the pattern index of the specified pattern "part".
4535d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * Convenience method for getPart(partIndex).getIndex().
4545d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @param partIndex The index of the Part data. (0..countParts()-1)
4555d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @return The pattern index of this Part.
4565d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @throws IndexOutOfBoundsException if partIndex is outside the (0..countParts()-1) range
4575d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @stable ICU 4.8
4585d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     */
4595d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    public int getPatternIndex(int partIndex) {
4605d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        return parts.get(partIndex).index;
4615d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    }
4625d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
4635d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    /**
4645d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * Returns the substring of the pattern string indicated by the Part.
4655d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * Convenience method for getPatternString().substring(part.getIndex(), part.getLimit()).
4665d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @param part a part of this MessagePattern.
4675d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @return the substring associated with part.
4685d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @stable ICU 4.8
4695d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     */
4705d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    public String getSubstring(Part part) {
4715d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        int index=part.index;
4725d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        return msg.substring(index, index+part.length);
4735d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    }
4745d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
4755d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    /**
4765d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * Compares the part's substring with the input string s.
4775d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @param part a part of this MessagePattern.
4785d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @param s a string.
4795d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @return true if getSubstring(part).equals(s).
4805d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @stable ICU 4.8
4815d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     */
4825d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    public boolean partSubstringMatches(Part part, String s) {
4835d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        return msg.regionMatches(part.index, s, 0, part.length);
4845d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    }
4855d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
4865d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    /**
4875d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * Returns the numeric value associated with an ARG_INT or ARG_DOUBLE.
4885d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @param part a part of this MessagePattern.
4895d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @return the part's numeric value, or NO_NUMERIC_VALUE if this is not a numeric part.
4905d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @stable ICU 4.8
4915d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     */
4925d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    public double getNumericValue(Part part) {
4935d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        Part.Type type=part.type;
4945d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        if(type==Part.Type.ARG_INT) {
4955d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            return part.value;
4965d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        } else if(type==Part.Type.ARG_DOUBLE) {
4975d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            return numericValues.get(part.value);
4985d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        } else {
4995d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            return NO_NUMERIC_VALUE;
5005d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        }
5015d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    }
5025d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
5035d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    /**
5045d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * Special value that is returned by getNumericValue(Part) when no
5055d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * numeric value is defined for a part.
5065d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @see #getNumericValue
5075d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @stable ICU 4.8
5085d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     */
5095d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    public static final double NO_NUMERIC_VALUE=-123456789;
5105d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
5115d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    /**
5125d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * Returns the "offset:" value of a PluralFormat argument, or 0 if none is specified.
5135d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @param pluralStart the index of the first PluralFormat argument style part. (0..countParts()-1)
5145d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @return the "offset:" value.
5155d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @throws IndexOutOfBoundsException if pluralStart is outside the (0..countParts()-1) range
5165d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @stable ICU 4.8
5175d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     */
5185d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    public double getPluralOffset(int pluralStart) {
5195d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        Part part=parts.get(pluralStart);
5205d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        if(part.type.hasNumericValue()) {
5215d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            return getNumericValue(part);
5225d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        } else {
5235d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            return 0;
5245d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        }
5255d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    }
5265d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
5275d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    /**
5285d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * Returns the index of the ARG|MSG_LIMIT part corresponding to the ARG|MSG_START at start.
5295d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @param start The index of some Part data (0..countParts()-1);
5305d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *        this Part should be of Type ARG_START or MSG_START.
5315d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @return The first i>start where getPart(i).getType()==ARG|MSG_LIMIT at the same nesting level,
5325d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *         or start itself if getPartType(msgStart)!=ARG|MSG_START.
5335d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @throws IndexOutOfBoundsException if start is outside the (0..countParts()-1) range
5345d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @stable ICU 4.8
5355d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     */
5365d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    public int getLimitPartIndex(int start) {
5375d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        int limit=parts.get(start).limitPartIndex;
5385d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        if(limit<start) {
5395d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            return start;
5405d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        }
5415d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        return limit;
5425d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    }
5435d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
5445d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    /**
5455d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * A message pattern "part", representing a pattern parsing event.
5465d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * There is a part for the start and end of a message or argument,
5475d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * for quoting and escaping of and with ASCII apostrophes,
5485d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * and for syntax elements of "complex" arguments.
5495d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @stable ICU 4.8
5505d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     */
5515d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    public static final class Part {
5525d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        private Part(Type t, int i, int l, int v) {
5535d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            type=t;
5545d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            index=i;
5555d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            length=(char)l;
5565d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            value=(short)v;
5575d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        }
5585d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
5595d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        /**
5605d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath         * Returns the type of this part.
5615d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath         * @return the part type.
5625d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath         * @stable ICU 4.8
5635d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath         */
5645d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        public Type getType() {
5655d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            return type;
5665d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        }
5675d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
5685d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        /**
5695d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath         * Returns the pattern string index associated with this Part.
5705d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath         * @return this part's pattern string index.
5715d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath         * @stable ICU 4.8
5725d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath         */
5735d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        public int getIndex() {
5745d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            return index;
5755d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        }
5765d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
5775d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        /**
5785d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath         * Returns the length of the pattern substring associated with this Part.
5795d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath         * This is 0 for some parts.
5805d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath         * @return this part's pattern substring length.
5815d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath         * @stable ICU 4.8
5825d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath         */
5835d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        public int getLength() {
5845d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            return length;
5855d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        }
5865d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
5875d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        /**
5885d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath         * Returns the pattern string limit (exclusive-end) index associated with this Part.
5895d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath         * Convenience method for getIndex()+getLength().
5905d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath         * @return this part's pattern string limit index, same as getIndex()+getLength().
5915d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath         * @stable ICU 4.8
5925d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath         */
5935d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        public int getLimit() {
5945d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            return index+length;
5955d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        }
5965d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
5975d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        /**
5985d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath         * Returns a value associated with this part.
5995d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath         * See the documentation of each part type for details.
6005d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath         * @return the part value.
6015d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath         * @stable ICU 4.8
6025d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath         */
6035d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        public int getValue() {
6045d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            return value;
6055d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        }
6065d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
6075d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        /**
6085d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath         * Returns the argument type if this part is of type ARG_START or ARG_LIMIT,
6095d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath         * otherwise ArgType.NONE.
6105d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath         * @return the argument type for this part.
6115d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath         * @stable ICU 4.8
6125d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath         */
6135d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        public ArgType getArgType() {
6145d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            Type type=getType();
6155d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            if(type==Type.ARG_START || type==Type.ARG_LIMIT) {
6165d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                return argTypes[value];
6175d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            } else {
6185d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                return ArgType.NONE;
6195d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            }
6205d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        }
6215d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
6225d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        /**
6235d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath         * Part type constants.
6245d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath         * @stable ICU 4.8
6255d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath         */
6265d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        public enum Type {
6275d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            /**
6285d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath             * Start of a message pattern (main or nested).
6295d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath             * The length is 0 for the top-level message
6305d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath             * and for a choice argument sub-message, otherwise 1 for the '{'.
6315d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath             * The value indicates the nesting level, starting with 0 for the main message.
6325d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath             * <p>
6335d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath             * There is always a later MSG_LIMIT part.
6345d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath             * @stable ICU 4.8
6355d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath             */
6365d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            MSG_START,
6375d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            /**
6385d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath             * End of a message pattern (main or nested).
6395d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath             * The length is 0 for the top-level message and
6405d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath             * the last sub-message of a choice argument,
6415d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath             * otherwise 1 for the '}' or (in a choice argument style) the '|'.
6425d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath             * The value indicates the nesting level, starting with 0 for the main message.
6435d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath             * @stable ICU 4.8
6445d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath             */
6455d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            MSG_LIMIT,
6465d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            /**
6475d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath             * Indicates a substring of the pattern string which is to be skipped when formatting.
6485d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath             * For example, an apostrophe that begins or ends quoted text
6495d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath             * would be indicated with such a part.
6505d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath             * The value is undefined and currently always 0.
6515d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath             * @stable ICU 4.8
6525d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath             */
6535d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            SKIP_SYNTAX,
6545d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            /**
6555d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath             * Indicates that a syntax character needs to be inserted for auto-quoting.
6565d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath             * The length is 0.
6575d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath             * The value is the character code of the insertion character. (U+0027=APOSTROPHE)
6585d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath             * @stable ICU 4.8
6595d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath             */
6605d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            INSERT_CHAR,
6615d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            /**
6625d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath             * Indicates a syntactic (non-escaped) # symbol in a plural variant.
6635d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath             * When formatting, replace this part's substring with the
6645d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath             * (value-offset) for the plural argument value.
6655d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath             * The value is undefined and currently always 0.
6665d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath             * @stable ICU 4.8
6675d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath             */
6685d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            REPLACE_NUMBER,
6695d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            /**
6705d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath             * Start of an argument.
6715d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath             * The length is 1 for the '{'.
6725d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath             * The value is the ordinal value of the ArgType. Use getArgType().
6735d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath             * <p>
6745d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath             * This part is followed by either an ARG_NUMBER or ARG_NAME,
6755d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath             * followed by optional argument sub-parts (see ArgType constants)
6765d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath             * and finally an ARG_LIMIT part.
6775d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath             * @stable ICU 4.8
6785d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath             */
6795d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            ARG_START,
6805d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            /**
6815d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath             * End of an argument.
6825d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath             * The length is 1 for the '}'.
6835d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath             * The value is the ordinal value of the ArgType. Use getArgType().
6845d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath             * @stable ICU 4.8
6855d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath             */
6865d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            ARG_LIMIT,
6875d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            /**
6885d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath             * The argument number, provided by the value.
6895d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath             * @stable ICU 4.8
6905d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath             */
6915d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            ARG_NUMBER,
6925d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            /**
6935d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath             * The argument name.
6945d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath             * The value is undefined and currently always 0.
6955d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath             * @stable ICU 4.8
6965d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath             */
6975d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            ARG_NAME,
6985d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            /**
6995d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath             * The argument type.
7005d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath             * The value is undefined and currently always 0.
7015d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath             * @stable ICU 4.8
7025d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath             */
7035d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            ARG_TYPE,
7045d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            /**
7055d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath             * The argument style text.
7065d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath             * The value is undefined and currently always 0.
7075d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath             * @stable ICU 4.8
7085d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath             */
7095d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            ARG_STYLE,
7105d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            /**
7115d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath             * A selector substring in a "complex" argument style.
7125d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath             * The value is undefined and currently always 0.
7135d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath             * @stable ICU 4.8
7145d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath             */
7155d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            ARG_SELECTOR,
7165d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            /**
7175d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath             * An integer value, for example the offset or an explicit selector value
7185d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath             * in a PluralFormat style.
7195d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath             * The part value is the integer value.
7205d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath             * @stable ICU 4.8
7215d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath             */
7225d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            ARG_INT,
7235d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            /**
7245d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath             * A numeric value, for example the offset or an explicit selector value
7255d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath             * in a PluralFormat style.
7265d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath             * The part value is an index into an internal array of numeric values;
7275d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath             * use getNumericValue().
7285d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath             * @stable ICU 4.8
7295d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath             */
7305d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            ARG_DOUBLE;
7315d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
7325d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            /**
7335d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath             * Indicates whether this part has a numeric value.
7345d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath             * If so, then that numeric value can be retrieved via {@link MessagePattern#getNumericValue(Part)}.
7355d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath             * @return true if this part has a numeric value.
7365d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath             * @stable ICU 4.8
7375d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath             */
7385d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            public boolean hasNumericValue() {
7395d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                return this==ARG_INT || this==ARG_DOUBLE;
7405d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            }
7415d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        }
7425d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
7435d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        /**
7445d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath         * @return a string representation of this part.
7455d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath         * @stable ICU 4.8
7465d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath         */
7475d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        @Override
7485d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        public String toString() {
7495d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            String valueString=(type==Type.ARG_START || type==Type.ARG_LIMIT) ?
7505d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                getArgType().name() : Integer.toString(value);
7515d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            return type.name()+"("+valueString+")@"+index;
7525d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        }
7535d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
7545d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        /**
7555d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath         * @param other another object to compare with.
7565d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath         * @return true if this object is equivalent to the other one.
7575d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath         * @stable ICU 4.8
7585d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath         */
7595d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        @Override
7605d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        public boolean equals(Object other) {
7615d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            if(this==other) {
7625d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                return true;
7635d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            }
7645d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            if(other==null || getClass()!=other.getClass()) {
7655d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                return false;
7665d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            }
7675d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            Part o=(Part)other;
7685d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            return
7695d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                type.equals(o.type) &&
7705d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                index==o.index &&
7715d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                length==o.length &&
7725d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                value==o.value &&
7735d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                limitPartIndex==o.limitPartIndex;
7745d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        }
7755d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
7765d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        /**
7775d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath         * {@inheritDoc}
7785d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath         * @stable ICU 4.8
7795d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath         */
7805d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        @Override
7815d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        public int hashCode() {
7825d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            return ((type.hashCode()*37+index)*37+length)*37+value;
7835d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        }
7845d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
7855d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        private static final int MAX_LENGTH=0xffff;
7865d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        private static final int MAX_VALUE=Short.MAX_VALUE;
7875d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
7885d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        // Some fields are not final because they are modified during pattern parsing.
7895d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        // After pattern parsing, the parts are effectively immutable.
7905d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        private final Type type;
7915d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        private final int index;
7925d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        private final char length;
7935d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        private short value;
7945d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        private int limitPartIndex;
7955d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    }
7965d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
7975d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    /**
7985d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * Argument type constants.
7995d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * Returned by Part.getArgType() for ARG_START and ARG_LIMIT parts.
8005d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *
8015d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * Messages nested inside an argument are each delimited by MSG_START and MSG_LIMIT,
8025d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * with a nesting level one greater than the surrounding message.
8035d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @stable ICU 4.8
8045d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     */
8055d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    public enum ArgType {
8065d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        /**
8075d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath         * The argument has no specified type.
8085d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath         * @stable ICU 4.8
8095d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath         */
8105d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        NONE,
8115d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        /**
8125d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath         * The argument has a "simple" type which is provided by the ARG_TYPE part.
8135d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath         * An ARG_STYLE part might follow that.
8145d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath         * @stable ICU 4.8
8155d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath         */
8165d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        SIMPLE,
8175d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        /**
8185d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath         * The argument is a ChoiceFormat with one or more
8195d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath         * ((ARG_INT | ARG_DOUBLE), ARG_SELECTOR, message) tuples.
8205d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath         * @stable ICU 4.8
8215d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath         */
8225d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        CHOICE,
8235d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        /**
8245d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath         * The argument is a cardinal-number PluralFormat with an optional ARG_INT or ARG_DOUBLE offset
8255d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath         * (e.g., offset:1)
8265d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath         * and one or more (ARG_SELECTOR [explicit-value] message) tuples.
8275d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath         * If the selector has an explicit value (e.g., =2), then
8285d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath         * that value is provided by the ARG_INT or ARG_DOUBLE part preceding the message.
8295d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath         * Otherwise the message immediately follows the ARG_SELECTOR.
8305d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath         * @stable ICU 4.8
8315d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath         */
8325d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        PLURAL,
8335d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        /**
8345d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath         * The argument is a SelectFormat with one or more (ARG_SELECTOR, message) pairs.
8355d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath         * @stable ICU 4.8
8365d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath         */
8375d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        SELECT,
8385d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        /**
8395d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath         * The argument is an ordinal-number PluralFormat
8405d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath         * with the same style parts sequence and semantics as {@link ArgType#PLURAL}.
8415d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath         * @stable ICU 50
8425d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath         */
8435d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        SELECTORDINAL;
8445d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
8455d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        /**
8465d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath         * @return true if the argument type has a plural style part sequence and semantics,
8475d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath         * for example {@link ArgType#PLURAL} and {@link ArgType#SELECTORDINAL}.
8485d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath         * @stable ICU 50
8495d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath         */
8505d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        public boolean hasPluralStyle() {
8515d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            return this == PLURAL || this == SELECTORDINAL;
8525d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        }
8535d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    }
8545d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
8555d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    /**
8565d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * Creates and returns a copy of this object.
8575d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @return a copy of this object (or itself if frozen).
8585d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @stable ICU 4.8
8595d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     */
8605d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    @Override
8615d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    public Object clone() {
8625d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        if(isFrozen()) {
8635d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            return this;
8645d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        } else {
8655d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            return cloneAsThawed();
8665d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        }
8675d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    }
8685d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
8695d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    /**
8705d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * Creates and returns an unfrozen copy of this object.
8715d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @return a copy of this object.
8725d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @stable ICU 4.8
8735d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     */
8745d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    @SuppressWarnings("unchecked")
8755d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    public MessagePattern cloneAsThawed() {
8765d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        MessagePattern newMsg;
8775d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        try {
8785d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            newMsg=(MessagePattern)super.clone();
8795d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        } catch (CloneNotSupportedException e) {
8808d05787d6a4b5762d790ccd2a9ed9dc8885986efNarayan Kamath            throw new ICUCloneNotSupportedException(e);
8815d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        }
8825d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        newMsg.parts=(ArrayList<Part>)parts.clone();
8835d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        if(numericValues!=null) {
8845d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            newMsg.numericValues=(ArrayList<Double>)numericValues.clone();
8855d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        }
8865d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        newMsg.frozen=false;
8875d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        return newMsg;
8885d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    }
8895d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
8905d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    /**
8915d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * Freezes this object, making it immutable and thread-safe.
8925d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @return this
8935d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @stable ICU 4.8
8945d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     */
8955d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    public MessagePattern freeze() {
8965d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        frozen=true;
8975d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        return this;
8985d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    }
8995d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
9005d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    /**
9015d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * Determines whether this object is frozen (immutable) or not.
9025d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @return true if this object is frozen.
9035d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @stable ICU 4.8
9045d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     */
9055d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    public boolean isFrozen() {
9065d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        return frozen;
9075d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    }
9085d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
9095d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    private void preParse(String pattern) {
9105d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        if(isFrozen()) {
9115d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            throw new UnsupportedOperationException(
9125d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                "Attempt to parse("+prefix(pattern)+") on frozen MessagePattern instance.");
9135d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        }
9145d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        msg=pattern;
9155d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        hasArgNames=hasArgNumbers=false;
9165d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        needsAutoQuoting=false;
9175d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        parts.clear();
9185d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        if(numericValues!=null) {
9195d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            numericValues.clear();
9205d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        }
9215d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    }
9225d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
9235d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    private void postParse() {
9245d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        // Nothing to be done currently.
9255d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    }
9265d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
9275d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    private int parseMessage(int index, int msgStartLength, int nestingLevel, ArgType parentType) {
9285d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        if(nestingLevel>Part.MAX_VALUE) {
9295d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            throw new IndexOutOfBoundsException();
9305d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        }
9315d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        int msgStart=parts.size();
9325d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        addPart(Part.Type.MSG_START, index, msgStartLength, nestingLevel);
9335d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        index+=msgStartLength;
9345d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        while(index<msg.length()) {
9355d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            char c=msg.charAt(index++);
9365d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            if(c=='\'') {
9375d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                if(index==msg.length()) {
9385d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    // The apostrophe is the last character in the pattern.
9395d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    // Add a Part for auto-quoting.
9405d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    addPart(Part.Type.INSERT_CHAR, index, 0, '\'');  // value=char to be inserted
9415d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    needsAutoQuoting=true;
9425d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                } else {
9435d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    c=msg.charAt(index);
9445d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    if(c=='\'') {
9455d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                        // double apostrophe, skip the second one
9465d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                        addPart(Part.Type.SKIP_SYNTAX, index++, 1, 0);
9475d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    } else if(
9485d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                        aposMode==ApostropheMode.DOUBLE_REQUIRED ||
9495d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                        c=='{' || c=='}' ||
9505d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                        (parentType==ArgType.CHOICE && c=='|') ||
9515d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                        (parentType.hasPluralStyle() && c=='#')
9525d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    ) {
9535d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                        // skip the quote-starting apostrophe
9545d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                        addPart(Part.Type.SKIP_SYNTAX, index-1, 1, 0);
9555d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                        // find the end of the quoted literal text
9565d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                        for(;;) {
9575d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                            index=msg.indexOf('\'', index+1);
9585d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                            if(index>=0) {
9595d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                                if((index+1)<msg.length() && msg.charAt(index+1)=='\'') {
9605d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                                    // double apostrophe inside quoted literal text
9615d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                                    // still encodes a single apostrophe, skip the second one
9625d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                                    addPart(Part.Type.SKIP_SYNTAX, ++index, 1, 0);
9635d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                                } else {
9645d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                                    // skip the quote-ending apostrophe
9655d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                                    addPart(Part.Type.SKIP_SYNTAX, index++, 1, 0);
9665d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                                    break;
9675d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                                }
9685d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                            } else {
9695d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                                // The quoted text reaches to the end of the of the message.
9705d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                                index=msg.length();
9715d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                                // Add a Part for auto-quoting.
9725d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                                addPart(Part.Type.INSERT_CHAR, index, 0, '\'');  // value=char to be inserted
9735d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                                needsAutoQuoting=true;
9745d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                                break;
9755d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                            }
9765d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                        }
9775d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    } else {
9785d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                        // Interpret the apostrophe as literal text.
9795d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                        // Add a Part for auto-quoting.
9805d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                        addPart(Part.Type.INSERT_CHAR, index, 0, '\'');  // value=char to be inserted
9815d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                        needsAutoQuoting=true;
9825d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    }
9835d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                }
9845d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            } else if(parentType.hasPluralStyle() && c=='#') {
9855d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                // The unquoted # in a plural message fragment will be replaced
9865d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                // with the (number-offset).
9875d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                addPart(Part.Type.REPLACE_NUMBER, index-1, 1, 0);
9885d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            } else if(c=='{') {
9895d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                index=parseArg(index-1, 1, nestingLevel);
9905d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            } else if((nestingLevel>0 && c=='}') || (parentType==ArgType.CHOICE && c=='|')) {
9915d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                // Finish the message before the terminator.
9925d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                // In a choice style, report the "}" substring only for the following ARG_LIMIT,
9935d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                // not for this MSG_LIMIT.
9945d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                int limitLength=(parentType==ArgType.CHOICE && c=='}') ? 0 : 1;
9955d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                addLimitPart(msgStart, Part.Type.MSG_LIMIT, index-1, limitLength, nestingLevel);
9965d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                if(parentType==ArgType.CHOICE) {
9975d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    // Let the choice style parser see the '}' or '|'.
9985d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    return index-1;
9995d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                } else {
10005d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    // continue parsing after the '}'
10015d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    return index;
10025d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                }
10035d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            }  // else: c is part of literal text
10045d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        }
10055d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        if(nestingLevel>0 && !inTopLevelChoiceMessage(nestingLevel, parentType)) {
10065d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            throw new IllegalArgumentException(
10075d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                "Unmatched '{' braces in message "+prefix());
10085d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        }
10095d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        addLimitPart(msgStart, Part.Type.MSG_LIMIT, index, 0, nestingLevel);
10105d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        return index;
10115d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    }
10125d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
10135d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    private int parseArg(int index, int argStartLength, int nestingLevel) {
10145d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        int argStart=parts.size();
10155d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        ArgType argType=ArgType.NONE;
10165d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        addPart(Part.Type.ARG_START, index, argStartLength, argType.ordinal());
10175d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        int nameIndex=index=skipWhiteSpace(index+argStartLength);
10185d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        if(index==msg.length()) {
10195d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            throw new IllegalArgumentException(
10205d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                "Unmatched '{' braces in message "+prefix());
10215d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        }
10225d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        // parse argument name or number
10235d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        index=skipIdentifier(index);
10245d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        int number=parseArgNumber(nameIndex, index);
10255d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        if(number>=0) {
10265d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            int length=index-nameIndex;
10275d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            if(length>Part.MAX_LENGTH || number>Part.MAX_VALUE) {
10285d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                throw new IndexOutOfBoundsException(
10295d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    "Argument number too large: "+prefix(nameIndex));
10305d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            }
10315d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            hasArgNumbers=true;
10325d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            addPart(Part.Type.ARG_NUMBER, nameIndex, length, number);
10335d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        } else if(number==ARG_NAME_NOT_NUMBER) {
10345d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            int length=index-nameIndex;
10355d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            if(length>Part.MAX_LENGTH) {
10365d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                throw new IndexOutOfBoundsException(
10375d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    "Argument name too long: "+prefix(nameIndex));
10385d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            }
10395d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            hasArgNames=true;
10405d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            addPart(Part.Type.ARG_NAME, nameIndex, length, 0);
10415d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        } else {  // number<-1 (ARG_NAME_NOT_VALID)
10425d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            throw new IllegalArgumentException("Bad argument syntax: "+prefix(nameIndex));
10435d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        }
10445d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        index=skipWhiteSpace(index);
10455d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        if(index==msg.length()) {
10465d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            throw new IllegalArgumentException(
10475d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                "Unmatched '{' braces in message "+prefix());
10485d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        }
10495d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        char c=msg.charAt(index);
10505d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        if(c=='}') {
10515d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            // all done
10525d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        } else if(c!=',') {
10535d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            throw new IllegalArgumentException("Bad argument syntax: "+prefix(nameIndex));
10545d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        } else /* ',' */ {
10555d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            // parse argument type: case-sensitive a-zA-Z
10565d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            int typeIndex=index=skipWhiteSpace(index+1);
10575d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            while(index<msg.length() && isArgTypeChar(msg.charAt(index))) {
10585d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                ++index;
10595d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            }
10605d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            int length=index-typeIndex;
10615d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            index=skipWhiteSpace(index);
10625d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            if(index==msg.length()) {
10635d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                throw new IllegalArgumentException(
10645d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    "Unmatched '{' braces in message "+prefix());
10655d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            }
10665d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            if(length==0 || ((c=msg.charAt(index))!=',' && c!='}')) {
10675d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                throw new IllegalArgumentException("Bad argument syntax: "+prefix(nameIndex));
10685d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            }
10695d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            if(length>Part.MAX_LENGTH) {
10705d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                throw new IndexOutOfBoundsException(
10715d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    "Argument type name too long: "+prefix(nameIndex));
10725d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            }
10735d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            argType=ArgType.SIMPLE;
10745d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            if(length==6) {
10755d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                // case-insensitive comparisons for complex-type names
10765d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                if(isChoice(typeIndex)) {
10775d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    argType=ArgType.CHOICE;
10785d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                } else if(isPlural(typeIndex)) {
10795d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    argType=ArgType.PLURAL;
10805d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                } else if(isSelect(typeIndex)) {
10815d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    argType=ArgType.SELECT;
10825d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                }
10835d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            } else if(length==13) {
10845d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                if(isSelect(typeIndex) && isOrdinal(typeIndex+6)) {
10855d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    argType=ArgType.SELECTORDINAL;
10865d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                }
10875d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            }
10885d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            // change the ARG_START type from NONE to argType
10895d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            parts.get(argStart).value=(short)argType.ordinal();
10905d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            if(argType==ArgType.SIMPLE) {
10915d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                addPart(Part.Type.ARG_TYPE, typeIndex, length, 0);
10925d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            }
10935d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            // look for an argument style (pattern)
10945d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            if(c=='}') {
10955d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                if(argType!=ArgType.SIMPLE) {
10965d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    throw new IllegalArgumentException(
10975d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                        "No style field for complex argument: "+prefix(nameIndex));
10985d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                }
10995d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            } else /* ',' */ {
11005d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                ++index;
11015d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                if(argType==ArgType.SIMPLE) {
11025d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    index=parseSimpleStyle(index);
11035d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                } else if(argType==ArgType.CHOICE) {
11045d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    index=parseChoiceStyle(index, nestingLevel);
11055d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                } else {
11065d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    index=parsePluralOrSelectStyle(argType, index, nestingLevel);
11075d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                }
11085d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            }
11095d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        }
11105d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        // Argument parsing stopped on the '}'.
11115d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        addLimitPart(argStart, Part.Type.ARG_LIMIT, index, 1, argType.ordinal());
11125d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        return index+1;
11135d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    }
11145d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
11155d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    private int parseSimpleStyle(int index) {
11165d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        int start=index;
11175d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        int nestedBraces=0;
11185d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        while(index<msg.length()) {
11195d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            char c=msg.charAt(index++);
11205d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            if(c=='\'') {
11215d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                // Treat apostrophe as quoting but include it in the style part.
11225d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                // Find the end of the quoted literal text.
11235d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                index=msg.indexOf('\'', index);
11245d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                if(index<0) {
11255d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    throw new IllegalArgumentException(
11265d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                        "Quoted literal argument style text reaches to the end of the message: "+
11275d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                        prefix(start));
11285d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                }
11295d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                // skip the quote-ending apostrophe
11305d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                ++index;
11315d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            } else if(c=='{') {
11325d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                ++nestedBraces;
11335d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            } else if(c=='}') {
11345d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                if(nestedBraces>0) {
11355d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    --nestedBraces;
11365d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                } else {
11375d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    int length=--index-start;
11385d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    if(length>Part.MAX_LENGTH) {
11395d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                        throw new IndexOutOfBoundsException(
11405d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                            "Argument style text too long: "+prefix(start));
11415d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    }
11425d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    addPart(Part.Type.ARG_STYLE, start, length, 0);
11435d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    return index;
11445d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                }
11455d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            }  // c is part of literal text
11465d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        }
11475d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        throw new IllegalArgumentException(
11485d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            "Unmatched '{' braces in message "+prefix());
11495d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    }
11505d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
11515d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    private int parseChoiceStyle(int index, int nestingLevel) {
11525d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        int start=index;
11535d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        index=skipWhiteSpace(index);
11545d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        if(index==msg.length() || msg.charAt(index)=='}') {
11555d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            throw new IllegalArgumentException(
11565d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                "Missing choice argument pattern in "+prefix());
11575d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        }
11585d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        for(;;) {
11595d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            // The choice argument style contains |-separated (number, separator, message) triples.
11605d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            // Parse the number.
11615d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            int numberIndex=index;
11625d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            index=skipDouble(index);
11635d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            int length=index-numberIndex;
11645d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            if(length==0) {
11655d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                throw new IllegalArgumentException("Bad choice pattern syntax: "+prefix(start));
11665d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            }
11675d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            if(length>Part.MAX_LENGTH) {
11685d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                throw new IndexOutOfBoundsException(
11695d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    "Choice number too long: "+prefix(numberIndex));
11705d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            }
11715d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            parseDouble(numberIndex, index, true);  // adds ARG_INT or ARG_DOUBLE
11725d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            // Parse the separator.
11735d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            index=skipWhiteSpace(index);
11745d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            if(index==msg.length()) {
11755d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                throw new IllegalArgumentException("Bad choice pattern syntax: "+prefix(start));
11765d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            }
11775d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            char c=msg.charAt(index);
11785d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            if(!(c=='#' || c=='<' || c=='\u2264')) {  // U+2264 is <=
11795d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                throw new IllegalArgumentException(
11805d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    "Expected choice separator (#<\u2264) instead of '"+c+
11815d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    "' in choice pattern "+prefix(start));
11825d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            }
11835d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            addPart(Part.Type.ARG_SELECTOR, index, 1, 0);
11845d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            // Parse the message fragment.
11855d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            index=parseMessage(++index, 0, nestingLevel+1, ArgType.CHOICE);
11865d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            // parseMessage(..., CHOICE) returns the index of the terminator, or msg.length().
11875d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            if(index==msg.length()) {
11885d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                return index;
11895d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            }
11905d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            if(msg.charAt(index)=='}') {
11915d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                if(!inMessageFormatPattern(nestingLevel)) {
11925d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    throw new IllegalArgumentException(
11935d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                        "Bad choice pattern syntax: "+prefix(start));
11945d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                }
11955d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                return index;
11965d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            }  // else the terminator is '|'
11975d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            index=skipWhiteSpace(index+1);
11985d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        }
11995d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    }
12005d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
12015d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    private int parsePluralOrSelectStyle(ArgType argType, int index, int nestingLevel) {
12025d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        int start=index;
12035d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        boolean isEmpty=true;
12045d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        boolean hasOther=false;
12055d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        for(;;) {
12065d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            // First, collect the selector looking for a small set of terminators.
12075d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            // It would be a little faster to consider the syntax of each possible
12085d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            // token right here, but that makes the code too complicated.
12095d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            index=skipWhiteSpace(index);
12105d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            boolean eos=index==msg.length();
12115d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            if(eos || msg.charAt(index)=='}') {
12125d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                if(eos==inMessageFormatPattern(nestingLevel)) {
12135d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    throw new IllegalArgumentException(
12145d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                        "Bad "+
12155d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                        argType.toString().toLowerCase(Locale.ENGLISH)+
12165d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                        " pattern syntax: "+prefix(start));
12175d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                }
12185d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                if(!hasOther) {
12195d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    throw new IllegalArgumentException(
12205d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                        "Missing 'other' keyword in "+
12215d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                        argType.toString().toLowerCase(Locale.ENGLISH)+
12225d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                        " pattern in "+prefix());
12235d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                }
12245d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                return index;
12255d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            }
12265d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            int selectorIndex=index;
12275d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            if(argType.hasPluralStyle() && msg.charAt(selectorIndex)=='=') {
12285d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                // explicit-value plural selector: =double
12295d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                index=skipDouble(index+1);
12305d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                int length=index-selectorIndex;
12315d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                if(length==1) {
12325d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    throw new IllegalArgumentException(
12335d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                        "Bad "+
12345d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                        argType.toString().toLowerCase(Locale.ENGLISH)+
12355d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                        " pattern syntax: "+prefix(start));
12365d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                }
12375d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                if(length>Part.MAX_LENGTH) {
12385d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    throw new IndexOutOfBoundsException(
12395d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                        "Argument selector too long: "+prefix(selectorIndex));
12405d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                }
12415d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                addPart(Part.Type.ARG_SELECTOR, selectorIndex, length, 0);
12425d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                parseDouble(selectorIndex+1, index, false);  // adds ARG_INT or ARG_DOUBLE
12435d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            } else {
12445d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                index=skipIdentifier(index);
12455d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                int length=index-selectorIndex;
12465d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                if(length==0) {
12475d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    throw new IllegalArgumentException(
12485d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                        "Bad "+
12495d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                        argType.toString().toLowerCase(Locale.ENGLISH)+
12505d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                        " pattern syntax: "+prefix(start));
12515d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                }
12525d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                // Note: The ':' in "offset:" is just beyond the skipIdentifier() range.
12535d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                if( argType.hasPluralStyle() && length==6 && index<msg.length() &&
12545d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    msg.regionMatches(selectorIndex, "offset:", 0, 7)
12555d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                ) {
12565d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    // plural offset, not a selector
12575d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    if(!isEmpty) {
12585d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                        throw new IllegalArgumentException(
12595d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                            "Plural argument 'offset:' (if present) must precede key-message pairs: "+
12605d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                            prefix(start));
12615d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    }
12625d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    // allow whitespace between offset: and its value
12635d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    int valueIndex=skipWhiteSpace(index+1);  // The ':' is at index.
12645d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    index=skipDouble(valueIndex);
12655d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    if(index==valueIndex) {
12665d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                        throw new IllegalArgumentException(
12675d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                            "Missing value for plural 'offset:' "+prefix(start));
12685d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    }
12695d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    if((index-valueIndex)>Part.MAX_LENGTH) {
12705d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                        throw new IndexOutOfBoundsException(
12715d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                            "Plural offset value too long: "+prefix(valueIndex));
12725d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    }
12735d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    parseDouble(valueIndex, index, false);  // adds ARG_INT or ARG_DOUBLE
12745d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    isEmpty=false;
12755d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    continue;  // no message fragment after the offset
12765d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                } else {
12775d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    // normal selector word
12785d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    if(length>Part.MAX_LENGTH) {
12795d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                        throw new IndexOutOfBoundsException(
12805d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                            "Argument selector too long: "+prefix(selectorIndex));
12815d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    }
12825d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    addPart(Part.Type.ARG_SELECTOR, selectorIndex, length, 0);
12835d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    if(msg.regionMatches(selectorIndex, "other", 0, length)) {
12845d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                        hasOther=true;
12855d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    }
12865d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                }
12875d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            }
12885d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
12895d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            // parse the message fragment following the selector
12905d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            index=skipWhiteSpace(index);
12915d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            if(index==msg.length() || msg.charAt(index)!='{') {
12925d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                throw new IllegalArgumentException(
12935d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    "No message fragment after "+
12945d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    argType.toString().toLowerCase(Locale.ENGLISH)+
12955d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    " selector: "+prefix(selectorIndex));
12965d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            }
12975d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            index=parseMessage(index, 1, nestingLevel+1, argType);
12985d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            isEmpty=false;
12995d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        }
13005d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    }
13015d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
13025d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    /**
13035d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * Validates and parses an argument name or argument number string.
13045d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * This internal method assumes that the input substring is a "pattern identifier".
13055d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @return &gt;=0 if the name is a valid number,
13065d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *         ARG_NAME_NOT_NUMBER (-1) if it is a "pattern identifier" but not all ASCII digits,
13075d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *         ARG_NAME_NOT_VALID (-2) if it is neither.
13085d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @see #validateArgumentName(String)
13095d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     */
13105d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    private static int parseArgNumber(CharSequence s, int start, int limit) {
13115d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        // If the identifier contains only ASCII digits, then it is an argument _number_
13125d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        // and must not have leading zeros (except "0" itself).
13135d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        // Otherwise it is an argument _name_.
13145d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        if(start>=limit) {
13155d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            return ARG_NAME_NOT_VALID;
13165d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        }
13175d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        int number;
13185d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        // Defer numeric errors until we know there are only digits.
13195d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        boolean badNumber;
13205d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        char c=s.charAt(start++);
13215d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        if(c=='0') {
13225d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            if(start==limit) {
13235d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                return 0;
13245d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            } else {
13255d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                number=0;
13265d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                badNumber=true;  // leading zero
13275d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            }
13285d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        } else if('1'<=c && c<='9') {
13295d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            number=c-'0';
13305d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            badNumber=false;
13315d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        } else {
13325d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            return ARG_NAME_NOT_NUMBER;
13335d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        }
13345d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        while(start<limit) {
13355d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            c=s.charAt(start++);
13365d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            if('0'<=c && c<='9') {
13375d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                if(number>=Integer.MAX_VALUE/10) {
13385d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    badNumber=true;  // overflow
13395d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                }
13405d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                number=number*10+(c-'0');
13415d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            } else {
13425d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                return ARG_NAME_NOT_NUMBER;
13435d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            }
13445d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        }
13455d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        // There are only ASCII digits.
13465d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        if(badNumber) {
13475d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            return ARG_NAME_NOT_VALID;
13485d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        } else {
13495d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            return number;
13505d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        }
13515d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    }
13525d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
13535d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    private int parseArgNumber(int start, int limit) {
13545d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        return parseArgNumber(msg, start, limit);
13555d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    }
13565d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
13575d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    /**
13585d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * Parses a number from the specified message substring.
13595d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @param start start index into the message string
13605d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @param limit limit index into the message string, must be start<limit
13615d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @param allowInfinity true if U+221E is allowed (for ChoiceFormat)
13625d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     */
13635d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    private void parseDouble(int start, int limit, boolean allowInfinity) {
13645d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        assert start<limit;
13655d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        // fake loop for easy exit and single throw statement
13665d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        for(;;) {
13675d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            // fast path for small integers and infinity
13685d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            int value=0;
13695d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            int isNegative=0;  // not boolean so that we can easily add it to value
13705d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            int index=start;
13715d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            char c=msg.charAt(index++);
13725d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            if(c=='-') {
13735d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                isNegative=1;
13745d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                if(index==limit) {
13755d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    break;  // no number
13765d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                }
13775d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                c=msg.charAt(index++);
13785d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            } else if(c=='+') {
13795d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                if(index==limit) {
13805d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    break;  // no number
13815d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                }
13825d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                c=msg.charAt(index++);
13835d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            }
13845d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            if(c==0x221e) {  // infinity
13855d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                if(allowInfinity && index==limit) {
13865d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    addArgDoublePart(
13875d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                        isNegative!=0 ? Double.NEGATIVE_INFINITY : Double.POSITIVE_INFINITY,
13885d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                        start, limit-start);
13895d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    return;
13905d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                } else {
13915d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    break;
13925d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                }
13935d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            }
13945d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            // try to parse the number as a small integer but fall back to a double
13955d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            while('0'<=c && c<='9') {
13965d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                value=value*10+(c-'0');
13975d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                if(value>(Part.MAX_VALUE+isNegative)) {
13985d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    break;  // not a small-enough integer
13995d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                }
14005d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                if(index==limit) {
14015d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    addPart(Part.Type.ARG_INT, start, limit-start, isNegative!=0 ? -value : value);
14025d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                    return;
14035d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                }
14045d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                c=msg.charAt(index++);
14055d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            }
14065d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            // Let Double.parseDouble() throw a NumberFormatException.
14075d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            double numericValue=Double.parseDouble(msg.substring(start, limit));
14085d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            addArgDoublePart(numericValue, start, limit-start);
14095d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            return;
14105d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        }
14115d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        throw new NumberFormatException(
14125d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            "Bad syntax for numeric value: "+msg.substring(start, limit));
14135d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    }
14145d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
14155d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    /**
14165d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * Appends the s[start, limit[ substring to sb, but with only half of the apostrophes
14175d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * according to JDK pattern behavior.
14185d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @internal
14195d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     */
14205d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    /* package */ static void appendReducedApostrophes(String s, int start, int limit,
14215d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                                                       StringBuilder sb) {
14225d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        int doubleApos=-1;
14235d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        for(;;) {
14245d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            int i=s.indexOf('\'', start);
14255d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            if(i<0 || i>=limit) {
14265d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                sb.append(s, start, limit);
14275d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                break;
14285d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            }
14295d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            if(i==doubleApos) {
14305d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                // Double apostrophe at start-1 and start==i, append one.
14315d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                sb.append('\'');
14325d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                ++start;
14335d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                doubleApos=-1;
14345d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            } else {
14355d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                // Append text between apostrophes and skip this one.
14365d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                sb.append(s, start, i);
14375d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                doubleApos=start=i+1;
14385d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            }
14395d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        }
14405d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    }
14415d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
14425d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    private int skipWhiteSpace(int index) {
14435d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        return PatternProps.skipWhiteSpace(msg, index);
14445d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    }
14455d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
14465d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    private int skipIdentifier(int index) {
14475d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        return PatternProps.skipIdentifier(msg, index);
14485d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    }
14495d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
14505d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    /**
14515d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * Skips a sequence of characters that could occur in a double value.
14525d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * Does not fully parse or validate the value.
14535d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     */
14545d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    private int skipDouble(int index) {
14555d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        while(index<msg.length()) {
14565d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            char c=msg.charAt(index);
14575d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            // U+221E: Allow the infinity symbol, for ChoiceFormat patterns.
14585d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            if((c<'0' && "+-.".indexOf(c)<0) || (c>'9' && c!='e' && c!='E' && c!=0x221e)) {
14595d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                break;
14605d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            }
14615d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            ++index;
14625d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        }
14635d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        return index;
14645d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    }
14655d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
14665d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    private static boolean isArgTypeChar(int c) {
14675d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        return ('a'<=c && c<='z') || ('A'<=c && c<='Z');
14685d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    }
14695d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
14705d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    private boolean isChoice(int index) {
14715d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        char c;
14725d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        return
14735d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            ((c=msg.charAt(index++))=='c' || c=='C') &&
14745d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            ((c=msg.charAt(index++))=='h' || c=='H') &&
14755d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            ((c=msg.charAt(index++))=='o' || c=='O') &&
14765d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            ((c=msg.charAt(index++))=='i' || c=='I') &&
14775d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            ((c=msg.charAt(index++))=='c' || c=='C') &&
14785d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            ((c=msg.charAt(index))=='e' || c=='E');
14795d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    }
14805d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
14815d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    private boolean isPlural(int index) {
14825d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        char c;
14835d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        return
14845d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            ((c=msg.charAt(index++))=='p' || c=='P') &&
14855d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            ((c=msg.charAt(index++))=='l' || c=='L') &&
14865d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            ((c=msg.charAt(index++))=='u' || c=='U') &&
14875d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            ((c=msg.charAt(index++))=='r' || c=='R') &&
14885d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            ((c=msg.charAt(index++))=='a' || c=='A') &&
14895d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            ((c=msg.charAt(index))=='l' || c=='L');
14905d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    }
14915d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
14925d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    private boolean isSelect(int index) {
14935d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        char c;
14945d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        return
14955d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            ((c=msg.charAt(index++))=='s' || c=='S') &&
14965d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            ((c=msg.charAt(index++))=='e' || c=='E') &&
14975d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            ((c=msg.charAt(index++))=='l' || c=='L') &&
14985d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            ((c=msg.charAt(index++))=='e' || c=='E') &&
14995d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            ((c=msg.charAt(index++))=='c' || c=='C') &&
15005d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            ((c=msg.charAt(index))=='t' || c=='T');
15015d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    }
15025d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
15035d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    private boolean isOrdinal(int index) {
15045d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        char c;
15055d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        return
15065d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            ((c=msg.charAt(index++))=='o' || c=='O') &&
15075d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            ((c=msg.charAt(index++))=='r' || c=='R') &&
15085d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            ((c=msg.charAt(index++))=='d' || c=='D') &&
15095d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            ((c=msg.charAt(index++))=='i' || c=='I') &&
15105d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            ((c=msg.charAt(index++))=='n' || c=='N') &&
15115d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            ((c=msg.charAt(index++))=='a' || c=='A') &&
15125d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            ((c=msg.charAt(index))=='l' || c=='L');
15135d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    }
15145d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
15155d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    /**
15165d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @return true if we are inside a MessageFormat (sub-)pattern,
15175d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *         as opposed to inside a top-level choice/plural/select pattern.
15185d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     */
15195d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    private boolean inMessageFormatPattern(int nestingLevel) {
15205d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        return nestingLevel>0 || parts.get(0).type==Part.Type.MSG_START;
15215d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    }
15225d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
15235d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    /**
15245d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @return true if we are in a MessageFormat sub-pattern
15255d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     *         of a top-level ChoiceFormat pattern.
15265d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     */
15275d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    private boolean inTopLevelChoiceMessage(int nestingLevel, ArgType parentType) {
15285d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        return
15295d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            nestingLevel==1 &&
15305d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            parentType==ArgType.CHOICE &&
15315d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            parts.get(0).type!=Part.Type.MSG_START;
15325d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    }
15335d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
15345d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    private void addPart(Part.Type type, int index, int length, int value) {
15355d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        parts.add(new Part(type, index, length, value));
15365d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    }
15375d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
15385d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    private void addLimitPart(int start, Part.Type type, int index, int length, int value) {
15395d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        parts.get(start).limitPartIndex=parts.size();
15405d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        addPart(type, index, length, value);
15415d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    }
15425d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
15435d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    private void addArgDoublePart(double numericValue, int start, int length) {
15445d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        int numericIndex;
15455d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        if(numericValues==null) {
15465d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            numericValues=new ArrayList<Double>();
15475d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            numericIndex=0;
15485d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        } else {
15495d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            numericIndex=numericValues.size();
15505d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            if(numericIndex>Part.MAX_VALUE) {
15515d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                throw new IndexOutOfBoundsException("Too many numeric values");
15525d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            }
15535d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        }
15545d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        numericValues.add(numericValue);
15555d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        addPart(Part.Type.ARG_DOUBLE, start, length, numericIndex);
15565d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    }
15575d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
15585d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    private static final int MAX_PREFIX_LENGTH=24;
15595d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
15605d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    /**
15615d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * Returns a prefix of s.substring(start). Used for Exception messages.
15625d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @param s
15635d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @param start start index in s
15645d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     * @return s.substring(start) or a prefix of that
15655d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath     */
15665d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    private static String prefix(String s, int start) {
15675d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        StringBuilder prefix=new StringBuilder(MAX_PREFIX_LENGTH+20);
15685d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        if(start==0) {
15695d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            prefix.append("\"");
15705d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        } else {
15715d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            prefix.append("[at pattern index ").append(start).append("] \"");
15725d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        }
15735d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        int substringLength=s.length()-start;
15745d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        if(substringLength<=MAX_PREFIX_LENGTH) {
15755d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            prefix.append(start==0 ? s : s.substring(start));
15765d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        } else {
15775d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            int limit=start+MAX_PREFIX_LENGTH-4;
15785d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            if(Character.isHighSurrogate(s.charAt(limit-1))) {
15795d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                // remove lead surrogate from the end of the prefix
15805d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath                --limit;
15815d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            }
15825d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            prefix.append(s, start, limit).append(" ...");
15835d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        }
15845d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        return prefix.append("\"").toString();
15855d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    }
15865d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
15875d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    private static String prefix(String s) {
15885d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        return prefix(s, 0);
15895d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    }
15905d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
15915d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    private String prefix(int start) {
15925d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        return prefix(msg, start);
15935d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    }
15945d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
15955d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    private String prefix() {
15965d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        return prefix(msg, 0);
15975d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    }
15985d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
15995d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    private ApostropheMode aposMode;
16005d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    private String msg;
16015d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    private ArrayList<Part> parts=new ArrayList<Part>();
16025d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    private ArrayList<Double> numericValues;
16035d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    private boolean hasArgNames;
16045d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    private boolean hasArgNumbers;
16055d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    private boolean needsAutoQuoting;
16065d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    private boolean frozen;
16075d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
16085d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    private static final ApostropheMode defaultAposMode=
16095d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath        ApostropheMode.valueOf(
16105d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath            ICUConfig.get("com.ibm.icu.text.MessagePattern.ApostropheMode", "DOUBLE_OPTIONAL"));
16115d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath
16125d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath    private static final ArgType[] argTypes=ArgType.values();
16135d1b0c9f348c25f01c6f2f5be04d2409409c08feNarayan Kamath}
1614