]> gitweb.fperrin.net Git - Dictionary.git/blob - jars/icu4j-52_1/main/classes/core/src/com/ibm/icu/text/MessagePattern.java
Added flags.
[Dictionary.git] / jars / icu4j-52_1 / main / classes / core / src / com / ibm / icu / text / MessagePattern.java
1 /*
2 *******************************************************************************
3 *   Copyright (C) 2010-2013, International Business Machines
4 *   Corporation and others.  All Rights Reserved.
5 *******************************************************************************
6 *   created on: 2010aug21
7 *   created by: Markus W. Scherer
8 */
9
10 package com.ibm.icu.text;
11
12 import java.util.ArrayList;
13 import java.util.Locale;
14
15 import com.ibm.icu.impl.ICUConfig;
16 import com.ibm.icu.impl.PatternProps;
17 import com.ibm.icu.util.Freezable;
18
19 //Note: Minimize ICU dependencies, only use a very small part of the ICU core.
20 //In particular, do not depend on *Format classes.
21
22 /**
23  * Parses and represents ICU MessageFormat patterns.
24  * Also handles patterns for ChoiceFormat, PluralFormat and SelectFormat.
25  * Used in the implementations of those classes as well as in tools
26  * for message validation, translation and format conversion.
27  * <p>
28  * The parser handles all syntax relevant for identifying message arguments.
29  * This includes "complex" arguments whose style strings contain
30  * nested MessageFormat pattern substrings.
31  * For "simple" arguments (with no nested MessageFormat pattern substrings),
32  * the argument style is not parsed any further.
33  * <p>
34  * The parser handles named and numbered message arguments and allows both in one message.
35  * <p>
36  * Once a pattern has been parsed successfully, iterate through the parsed data
37  * with countParts(), getPart() and related methods.
38  * <p>
39  * The data logically represents a parse tree, but is stored and accessed
40  * as a list of "parts" for fast and simple parsing and to minimize object allocations.
41  * Arguments and nested messages are best handled via recursion.
42  * For every _START "part", {@link #getLimitPartIndex(int)} efficiently returns
43  * the index of the corresponding _LIMIT "part".
44  * <p>
45  * List of "parts":
46  * <pre>
47  * message = MSG_START (SKIP_SYNTAX | INSERT_CHAR | REPLACE_NUMBER | argument)* MSG_LIMIT
48  * argument = noneArg | simpleArg | complexArg
49  * complexArg = choiceArg | pluralArg | selectArg
50  *
51  * noneArg = ARG_START.NONE (ARG_NAME | ARG_NUMBER) ARG_LIMIT.NONE
52  * simpleArg = ARG_START.SIMPLE (ARG_NAME | ARG_NUMBER) ARG_TYPE [ARG_STYLE] ARG_LIMIT.SIMPLE
53  * choiceArg = ARG_START.CHOICE (ARG_NAME | ARG_NUMBER) choiceStyle ARG_LIMIT.CHOICE
54  * pluralArg = ARG_START.PLURAL (ARG_NAME | ARG_NUMBER) pluralStyle ARG_LIMIT.PLURAL
55  * selectArg = ARG_START.SELECT (ARG_NAME | ARG_NUMBER) selectStyle ARG_LIMIT.SELECT
56  *
57  * choiceStyle = ((ARG_INT | ARG_DOUBLE) ARG_SELECTOR message)+
58  * pluralStyle = [ARG_INT | ARG_DOUBLE] (ARG_SELECTOR [ARG_INT | ARG_DOUBLE] message)+
59  * selectStyle = (ARG_SELECTOR message)+
60  * </pre>
61  * <ul>
62  *   <li>Literal output text is not represented directly by "parts" but accessed
63  *       between parts of a message, from one part's getLimit() to the next part's getIndex().
64  *   <li><code>ARG_START.CHOICE</code> stands for an ARG_START Part with ArgType CHOICE.
65  *   <li>In the choiceStyle, the ARG_SELECTOR has the '<', the '#' or
66  *       the less-than-or-equal-to sign (U+2264).
67  *   <li>In the pluralStyle, the first, optional numeric Part has the "offset:" value.
68  *       The optional numeric Part between each (ARG_SELECTOR, message) pair
69  *       is the value of an explicit-number selector like "=2",
70  *       otherwise the selector is a non-numeric identifier.
71  *   <li>The REPLACE_NUMBER Part can occur only in an immediate sub-message of the pluralStyle.
72  * <p>
73  * This class is not intended for public subclassing.
74  *
75  * @stable ICU 4.8
76  * @author Markus Scherer
77  */
78 public final class MessagePattern implements Cloneable, Freezable<MessagePattern> {
79     /**
80      * Mode for when an apostrophe starts quoted literal text for MessageFormat output.
81      * The default is DOUBLE_OPTIONAL unless overridden via ICUConfig
82      * (/com/ibm/icu/ICUConfig.properties).
83      * <p>
84      * A pair of adjacent apostrophes always results in a single apostrophe in the output,
85      * even when the pair is between two single, text-quoting apostrophes.
86      * <p>
87      * The following table shows examples of desired MessageFormat.format() output
88      * with the pattern strings that yield that output.
89      * <p>
90      * <table>
91      *   <tr>
92      *     <th>Desired output</th>
93      *     <th>DOUBLE_OPTIONAL</th>
94      *     <th>DOUBLE_REQUIRED</th>
95      *   </tr>
96      *   <tr>
97      *     <td>I see {many}</td>
98      *     <td>I see '{many}'</td>
99      *     <td>(same)</td>
100      *   </tr>
101      *   <tr>
102      *     <td>I said {'Wow!'}</td>
103      *     <td>I said '{''Wow!''}'</td>
104      *     <td>(same)</td>
105      *   </tr>
106      *   <tr>
107      *     <td>I don't know</td>
108      *     <td>I don't know OR<br> I don''t know</td>
109      *     <td>I don''t know</td>
110      *   </tr>
111      * </table>
112      * @stable ICU 4.8
113      */
114     public enum ApostropheMode {
115         /**
116          * A literal apostrophe is represented by
117          * either a single or a double apostrophe pattern character.
118          * Within a MessageFormat pattern, a single apostrophe only starts quoted literal text
119          * if it immediately precedes a curly brace {},
120          * or a pipe symbol | if inside a choice format,
121          * or a pound symbol # if inside a plural format.
122          * <p>
123          * This is the default behavior starting with ICU 4.8.
124          * @stable ICU 4.8
125          */
126         DOUBLE_OPTIONAL,
127         /**
128          * A literal apostrophe must be represented by
129          * a double apostrophe pattern character.
130          * A single apostrophe always starts quoted literal text.
131          * <p>
132          * This is the behavior of ICU 4.6 and earlier, and of the JDK.
133          * @stable ICU 4.8
134          */
135         DOUBLE_REQUIRED
136     }
137
138     /**
139      * Constructs an empty MessagePattern with default ApostropheMode.
140      * @stable ICU 4.8
141      */
142     public MessagePattern() {
143         aposMode=defaultAposMode;
144     }
145
146     /**
147      * Constructs an empty MessagePattern.
148      * @param mode Explicit ApostropheMode.
149      * @stable ICU 4.8
150      */
151     public MessagePattern(ApostropheMode mode) {
152         aposMode=mode;
153     }
154
155     /**
156      * Constructs a MessagePattern with default ApostropheMode and
157      * parses the MessageFormat pattern string.
158      * @param pattern a MessageFormat pattern string
159      * @throws IllegalArgumentException for syntax errors in the pattern string
160      * @throws IndexOutOfBoundsException if certain limits are exceeded
161      *         (e.g., argument number too high, argument name too long, etc.)
162      * @throws NumberFormatException if a number could not be parsed
163      * @stable ICU 4.8
164      */
165     public MessagePattern(String pattern) {
166         aposMode=defaultAposMode;
167         parse(pattern);
168     }
169
170     /**
171      * Parses a MessageFormat pattern string.
172      * @param pattern a MessageFormat pattern string
173      * @return this
174      * @throws IllegalArgumentException for syntax errors in the pattern string
175      * @throws IndexOutOfBoundsException if certain limits are exceeded
176      *         (e.g., argument number too high, argument name too long, etc.)
177      * @throws NumberFormatException if a number could not be parsed
178      * @stable ICU 4.8
179      */
180     public MessagePattern parse(String pattern) {
181         preParse(pattern);
182         parseMessage(0, 0, 0, ArgType.NONE);
183         postParse();
184         return this;
185     }
186
187     /**
188      * Parses a ChoiceFormat pattern string.
189      * @param pattern a ChoiceFormat pattern string
190      * @return this
191      * @throws IllegalArgumentException for syntax errors in the pattern string
192      * @throws IndexOutOfBoundsException if certain limits are exceeded
193      *         (e.g., argument number too high, argument name too long, etc.)
194      * @throws NumberFormatException if a number could not be parsed
195      * @stable ICU 4.8
196      */
197     public MessagePattern parseChoiceStyle(String pattern) {
198         preParse(pattern);
199         parseChoiceStyle(0, 0);
200         postParse();
201         return this;
202     }
203
204     /**
205      * Parses a PluralFormat pattern string.
206      * @param pattern a PluralFormat pattern string
207      * @return this
208      * @throws IllegalArgumentException for syntax errors in the pattern string
209      * @throws IndexOutOfBoundsException if certain limits are exceeded
210      *         (e.g., argument number too high, argument name too long, etc.)
211      * @throws NumberFormatException if a number could not be parsed
212      * @stable ICU 4.8
213      */
214     public MessagePattern parsePluralStyle(String pattern) {
215         preParse(pattern);
216         parsePluralOrSelectStyle(ArgType.PLURAL, 0, 0);
217         postParse();
218         return this;
219     }
220
221     /**
222      * Parses a SelectFormat pattern string.
223      * @param pattern a SelectFormat pattern string
224      * @return this
225      * @throws IllegalArgumentException for syntax errors in the pattern string
226      * @throws IndexOutOfBoundsException if certain limits are exceeded
227      *         (e.g., argument number too high, argument name too long, etc.)
228      * @throws NumberFormatException if a number could not be parsed
229      * @stable ICU 4.8
230      */
231     public MessagePattern parseSelectStyle(String pattern) {
232         preParse(pattern);
233         parsePluralOrSelectStyle(ArgType.SELECT, 0, 0);
234         postParse();
235         return this;
236     }
237
238     /**
239      * Clears this MessagePattern.
240      * countParts() will return 0.
241      * @stable ICU 4.8
242      */
243     public void clear() {
244         // Mostly the same as preParse().
245         if(isFrozen()) {
246             throw new UnsupportedOperationException(
247                 "Attempt to clear() a frozen MessagePattern instance.");
248         }
249         msg=null;
250         hasArgNames=hasArgNumbers=false;
251         needsAutoQuoting=false;
252         parts.clear();
253         if(numericValues!=null) {
254             numericValues.clear();
255         }
256     }
257
258     /**
259      * Clears this MessagePattern and sets the ApostropheMode.
260      * countParts() will return 0.
261      * @param mode The new ApostropheMode.
262      * @stable ICU 4.8
263      */
264     public void clearPatternAndSetApostropheMode(ApostropheMode mode) {
265         clear();
266         aposMode=mode;
267     }
268
269     /**
270      * @param other another object to compare with.
271      * @return true if this object is equivalent to the other one.
272      * @stable ICU 4.8
273      */
274     @Override
275     public boolean equals(Object other) {
276         if(this==other) {
277             return true;
278         }
279         if(other==null || getClass()!=other.getClass()) {
280             return false;
281         }
282         MessagePattern o=(MessagePattern)other;
283         return
284             aposMode.equals(o.aposMode) &&
285             (msg==null ? o.msg==null : msg.equals(o.msg)) &&
286             parts.equals(o.parts);
287         // No need to compare numericValues if msg and parts are the same.
288     }
289
290     /**
291      * {@inheritDoc}
292      * @stable ICU 4.8
293      */
294     @Override
295     public int hashCode() {
296         return (aposMode.hashCode()*37+(msg!=null ? msg.hashCode() : 0))*37+parts.hashCode();
297     }
298
299     /**
300      * @return this instance's ApostropheMode.
301      * @stable ICU 4.8
302      */
303     public ApostropheMode getApostropheMode() {
304         return aposMode;
305     }
306
307     /**
308      * @return true if getApostropheMode() == ApostropheMode.DOUBLE_REQUIRED
309      * @internal
310      */
311     /* package */ boolean jdkAposMode() {
312         return aposMode == ApostropheMode.DOUBLE_REQUIRED;
313     }
314
315     /**
316      * @return the parsed pattern string (null if none was parsed).
317      * @stable ICU 4.8
318      */
319     public String getPatternString() {
320         return msg;
321     }
322
323     /**
324      * Does the parsed pattern have named arguments like {first_name}?
325      * @return true if the parsed pattern has at least one named argument.
326      * @stable ICU 4.8
327      */
328     public boolean hasNamedArguments() {
329         return hasArgNames;
330     }
331
332     /**
333      * Does the parsed pattern have numbered arguments like {2}?
334      * @return true if the parsed pattern has at least one numbered argument.
335      * @stable ICU 4.8
336      */
337     public boolean hasNumberedArguments() {
338         return hasArgNumbers;
339     }
340
341     /**
342      * {@inheritDoc}
343      * @stable ICU 4.8
344      */
345     @Override
346     public String toString() {
347         return msg;
348     }
349
350     /**
351      * Validates and parses an argument name or argument number string.
352      * An argument name must be a "pattern identifier", that is, it must contain
353      * no Unicode Pattern_Syntax or Pattern_White_Space characters.
354      * If it only contains ASCII digits, then it must be a small integer with no leading zero.
355      * @param name Input string.
356      * @return &gt;=0 if the name is a valid number,
357      *         ARG_NAME_NOT_NUMBER (-1) if it is a "pattern identifier" but not all ASCII digits,
358      *         ARG_NAME_NOT_VALID (-2) if it is neither.
359      * @stable ICU 4.8
360      */
361     public static int validateArgumentName(String name) {
362         if(!PatternProps.isIdentifier(name)) {
363             return ARG_NAME_NOT_VALID;
364         }
365         return parseArgNumber(name, 0, name.length());
366     }
367
368     /**
369      * Return value from {@link #validateArgumentName(String)} for when
370      * the string is a valid "pattern identifier" but not a number.
371      * @stable ICU 4.8
372      */
373     public static final int ARG_NAME_NOT_NUMBER=-1;
374
375     /**
376      * Return value from {@link #validateArgumentName(String)} for when
377      * the string is invalid.
378      * It might not be a valid "pattern identifier",
379      * or it have only ASCII digits but there is a leading zero or the number is too large.
380      * @stable ICU 4.8
381      */
382     public static final int ARG_NAME_NOT_VALID=-2;
383
384     /**
385      * Returns a version of the parsed pattern string where each ASCII apostrophe
386      * is doubled (escaped) if it is not already, and if it is not interpreted as quoting syntax.
387      * <p>
388      * For example, this turns "I don't '{know}' {gender,select,female{h''er}other{h'im}}."
389      * into "I don''t '{know}' {gender,select,female{h''er}other{h''im}}."
390      * @return the deep-auto-quoted version of the parsed pattern string.
391      * @see MessageFormat#autoQuoteApostrophe(String)
392      * @stable ICU 4.8
393      */
394     public String autoQuoteApostropheDeep() {
395         if(!needsAutoQuoting) {
396             return msg;
397         }
398         StringBuilder modified=null;
399         // Iterate backward so that the insertion indexes do not change.
400         int count=countParts();
401         for(int i=count; i>0;) {
402             Part part;
403             if((part=getPart(--i)).getType()==Part.Type.INSERT_CHAR) {
404                 if(modified==null) {
405                     modified=new StringBuilder(msg.length()+10).append(msg);
406                 }
407                 modified.insert(part.index, (char)part.value);
408             }
409         }
410         if(modified==null) {
411             return msg;
412         } else {
413             return modified.toString();
414         }
415     }
416
417     /**
418      * Returns the number of "parts" created by parsing the pattern string.
419      * Returns 0 if no pattern has been parsed or clear() was called.
420      * @return the number of pattern parts.
421      * @stable ICU 4.8
422      */
423     public int countParts() {
424         return parts.size();
425     }
426
427     /**
428      * Gets the i-th pattern "part".
429      * @param i The index of the Part data. (0..countParts()-1)
430      * @return the i-th pattern "part".
431      * @throws IndexOutOfBoundsException if i is outside the (0..countParts()-1) range
432      * @stable ICU 4.8
433      */
434     public Part getPart(int i) {
435         return parts.get(i);
436     }
437
438     /**
439      * Returns the Part.Type of the i-th pattern "part".
440      * Convenience method for getPart(i).getType().
441      * @param i The index of the Part data. (0..countParts()-1)
442      * @return The Part.Type of the i-th Part.
443      * @throws IndexOutOfBoundsException if i is outside the (0..countParts()-1) range
444      * @stable ICU 4.8
445      */
446     public Part.Type getPartType(int i) {
447         return parts.get(i).type;
448     }
449
450     /**
451      * Returns the pattern index of the specified pattern "part".
452      * Convenience method for getPart(partIndex).getIndex().
453      * @param partIndex The index of the Part data. (0..countParts()-1)
454      * @return The pattern index of this Part.
455      * @throws IndexOutOfBoundsException if partIndex is outside the (0..countParts()-1) range
456      * @stable ICU 4.8
457      */
458     public int getPatternIndex(int partIndex) {
459         return parts.get(partIndex).index;
460     }
461
462     /**
463      * Returns the substring of the pattern string indicated by the Part.
464      * Convenience method for getPatternString().substring(part.getIndex(), part.getLimit()).
465      * @param part a part of this MessagePattern.
466      * @return the substring associated with part.
467      * @stable ICU 4.8
468      */
469     public String getSubstring(Part part) {
470         int index=part.index;
471         return msg.substring(index, index+part.length);
472     }
473
474     /**
475      * Compares the part's substring with the input string s.
476      * @param part a part of this MessagePattern.
477      * @param s a string.
478      * @return true if getSubstring(part).equals(s).
479      * @stable ICU 4.8
480      */
481     public boolean partSubstringMatches(Part part, String s) {
482         return msg.regionMatches(part.index, s, 0, part.length);
483     }
484
485     /**
486      * Returns the numeric value associated with an ARG_INT or ARG_DOUBLE.
487      * @param part a part of this MessagePattern.
488      * @return the part's numeric value, or NO_NUMERIC_VALUE if this is not a numeric part.
489      * @stable ICU 4.8
490      */
491     public double getNumericValue(Part part) {
492         Part.Type type=part.type;
493         if(type==Part.Type.ARG_INT) {
494             return part.value;
495         } else if(type==Part.Type.ARG_DOUBLE) {
496             return numericValues.get(part.value);
497         } else {
498             return NO_NUMERIC_VALUE;
499         }
500     }
501
502     /**
503      * Special value that is returned by getNumericValue(Part) when no
504      * numeric value is defined for a part.
505      * @see #getNumericValue
506      * @stable ICU 4.8
507      */
508     public static final double NO_NUMERIC_VALUE=-123456789;
509
510     /**
511      * Returns the "offset:" value of a PluralFormat argument, or 0 if none is specified.
512      * @param pluralStart the index of the first PluralFormat argument style part. (0..countParts()-1)
513      * @return the "offset:" value.
514      * @throws IndexOutOfBoundsException if pluralStart is outside the (0..countParts()-1) range
515      * @stable ICU 4.8
516      */
517     public double getPluralOffset(int pluralStart) {
518         Part part=parts.get(pluralStart);
519         if(part.type.hasNumericValue()) {
520             return getNumericValue(part);
521         } else {
522             return 0;
523         }
524     }
525
526     /**
527      * Returns the index of the ARG|MSG_LIMIT part corresponding to the ARG|MSG_START at start.
528      * @param start The index of some Part data (0..countParts()-1);
529      *        this Part should be of Type ARG_START or MSG_START.
530      * @return The first i>start where getPart(i).getType()==ARG|MSG_LIMIT at the same nesting level,
531      *         or start itself if getPartType(msgStart)!=ARG|MSG_START.
532      * @throws IndexOutOfBoundsException if start is outside the (0..countParts()-1) range
533      * @stable ICU 4.8
534      */
535     public int getLimitPartIndex(int start) {
536         int limit=parts.get(start).limitPartIndex;
537         if(limit<start) {
538             return start;
539         }
540         return limit;
541     }
542
543     /**
544      * A message pattern "part", representing a pattern parsing event.
545      * There is a part for the start and end of a message or argument,
546      * for quoting and escaping of and with ASCII apostrophes,
547      * and for syntax elements of "complex" arguments.
548      * @stable ICU 4.8
549      */
550     public static final class Part {
551         private Part(Type t, int i, int l, int v) {
552             type=t;
553             index=i;
554             length=(char)l;
555             value=(short)v;
556         }
557
558         /**
559          * Returns the type of this part.
560          * @return the part type.
561          * @stable ICU 4.8
562          */
563         public Type getType() {
564             return type;
565         }
566
567         /**
568          * Returns the pattern string index associated with this Part.
569          * @return this part's pattern string index.
570          * @stable ICU 4.8
571          */
572         public int getIndex() {
573             return index;
574         }
575
576         /**
577          * Returns the length of the pattern substring associated with this Part.
578          * This is 0 for some parts.
579          * @return this part's pattern substring length.
580          * @stable ICU 4.8
581          */
582         public int getLength() {
583             return length;
584         }
585
586         /**
587          * Returns the pattern string limit (exclusive-end) index associated with this Part.
588          * Convenience method for getIndex()+getLength().
589          * @return this part's pattern string limit index, same as getIndex()+getLength().
590          * @stable ICU 4.8
591          */
592         public int getLimit() {
593             return index+length;
594         }
595
596         /**
597          * Returns a value associated with this part.
598          * See the documentation of each part type for details.
599          * @return the part value.
600          * @stable ICU 4.8
601          */
602         public int getValue() {
603             return value;
604         }
605
606         /**
607          * Returns the argument type if this part is of type ARG_START or ARG_LIMIT,
608          * otherwise ArgType.NONE.
609          * @return the argument type for this part.
610          * @stable ICU 4.8
611          */
612         public ArgType getArgType() {
613             Type type=getType();
614             if(type==Type.ARG_START || type==Type.ARG_LIMIT) {
615                 return argTypes[value];
616             } else {
617                 return ArgType.NONE;
618             }
619         }
620
621         /**
622          * Part type constants.
623          * @stable ICU 4.8
624          */
625         public enum Type {
626             /**
627              * Start of a message pattern (main or nested).
628              * The length is 0 for the top-level message
629              * and for a choice argument sub-message, otherwise 1 for the '{'.
630              * The value indicates the nesting level, starting with 0 for the main message.
631              * <p>
632              * There is always a later MSG_LIMIT part.
633              * @stable ICU 4.8
634              */
635             MSG_START,
636             /**
637              * End of a message pattern (main or nested).
638              * The length is 0 for the top-level message and
639              * the last sub-message of a choice argument,
640              * otherwise 1 for the '}' or (in a choice argument style) the '|'.
641              * The value indicates the nesting level, starting with 0 for the main message.
642              * @stable ICU 4.8
643              */
644             MSG_LIMIT,
645             /**
646              * Indicates a substring of the pattern string which is to be skipped when formatting.
647              * For example, an apostrophe that begins or ends quoted text
648              * would be indicated with such a part.
649              * The value is undefined and currently always 0.
650              * @stable ICU 4.8
651              */
652             SKIP_SYNTAX,
653             /**
654              * Indicates that a syntax character needs to be inserted for auto-quoting.
655              * The length is 0.
656              * The value is the character code of the insertion character. (U+0027=APOSTROPHE)
657              * @stable ICU 4.8
658              */
659             INSERT_CHAR,
660             /**
661              * Indicates a syntactic (non-escaped) # symbol in a plural variant.
662              * When formatting, replace this part's substring with the
663              * (value-offset) for the plural argument value.
664              * The value is undefined and currently always 0.
665              * @stable ICU 4.8
666              */
667             REPLACE_NUMBER,
668             /**
669              * Start of an argument.
670              * The length is 1 for the '{'.
671              * The value is the ordinal value of the ArgType. Use getArgType().
672              * <p>
673              * This part is followed by either an ARG_NUMBER or ARG_NAME,
674              * followed by optional argument sub-parts (see ArgType constants)
675              * and finally an ARG_LIMIT part.
676              * @stable ICU 4.8
677              */
678             ARG_START,
679             /**
680              * End of an argument.
681              * The length is 1 for the '}'.
682              * The value is the ordinal value of the ArgType. Use getArgType().
683              * @stable ICU 4.8
684              */
685             ARG_LIMIT,
686             /**
687              * The argument number, provided by the value.
688              * @stable ICU 4.8
689              */
690             ARG_NUMBER,
691             /**
692              * The argument name.
693              * The value is undefined and currently always 0.
694              * @stable ICU 4.8
695              */
696             ARG_NAME,
697             /**
698              * The argument type.
699              * The value is undefined and currently always 0.
700              * @stable ICU 4.8
701              */
702             ARG_TYPE,
703             /**
704              * The argument style text.
705              * The value is undefined and currently always 0.
706              * @stable ICU 4.8
707              */
708             ARG_STYLE,
709             /**
710              * A selector substring in a "complex" argument style.
711              * The value is undefined and currently always 0.
712              * @stable ICU 4.8
713              */
714             ARG_SELECTOR,
715             /**
716              * An integer value, for example the offset or an explicit selector value
717              * in a PluralFormat style.
718              * The part value is the integer value.
719              * @stable ICU 4.8
720              */
721             ARG_INT,
722             /**
723              * A numeric value, for example the offset or an explicit selector value
724              * in a PluralFormat style.
725              * The part value is an index into an internal array of numeric values;
726              * use getNumericValue().
727              * @stable ICU 4.8
728              */
729             ARG_DOUBLE;
730
731             /**
732              * Indicates whether this part has a numeric value.
733              * If so, then that numeric value can be retrieved via {@link MessagePattern#getNumericValue(Part)}.
734              * @return true if this part has a numeric value.
735              * @stable ICU 4.8
736              */
737             public boolean hasNumericValue() {
738                 return this==ARG_INT || this==ARG_DOUBLE;
739             }
740         }
741
742         /**
743          * @return a string representation of this part.
744          * @stable ICU 4.8
745          */
746         @Override
747         public String toString() {
748             String valueString=(type==Type.ARG_START || type==Type.ARG_LIMIT) ?
749                 getArgType().name() : Integer.toString(value);
750             return type.name()+"("+valueString+")@"+index;
751         }
752
753         /**
754          * @param other another object to compare with.
755          * @return true if this object is equivalent to the other one.
756          * @stable ICU 4.8
757          */
758         @Override
759         public boolean equals(Object other) {
760             if(this==other) {
761                 return true;
762             }
763             if(other==null || getClass()!=other.getClass()) {
764                 return false;
765             }
766             Part o=(Part)other;
767             return
768                 type.equals(o.type) &&
769                 index==o.index &&
770                 length==o.length &&
771                 value==o.value &&
772                 limitPartIndex==o.limitPartIndex;
773         }
774
775         /**
776          * {@inheritDoc}
777          * @stable ICU 4.8
778          */
779         @Override
780         public int hashCode() {
781             return ((type.hashCode()*37+index)*37+length)*37+value;
782         }
783
784         private static final int MAX_LENGTH=0xffff;
785         private static final int MAX_VALUE=Short.MAX_VALUE;
786
787         // Some fields are not final because they are modified during pattern parsing.
788         // After pattern parsing, the parts are effectively immutable.
789         private final Type type;
790         private final int index;
791         private final char length;
792         private short value;
793         private int limitPartIndex;
794     }
795
796     /**
797      * Argument type constants.
798      * Returned by Part.getArgType() for ARG_START and ARG_LIMIT parts.
799      *
800      * Messages nested inside an argument are each delimited by MSG_START and MSG_LIMIT,
801      * with a nesting level one greater than the surrounding message.
802      * @stable ICU 4.8
803      */
804     public enum ArgType {
805         /**
806          * The argument has no specified type.
807          * @stable ICU 4.8
808          */
809         NONE,
810         /**
811          * The argument has a "simple" type which is provided by the ARG_TYPE part.
812          * An ARG_STYLE part might follow that.
813          * @stable ICU 4.8
814          */
815         SIMPLE,
816         /**
817          * The argument is a ChoiceFormat with one or more
818          * ((ARG_INT | ARG_DOUBLE), ARG_SELECTOR, message) tuples.
819          * @stable ICU 4.8
820          */
821         CHOICE,
822         /**
823          * The argument is a cardinal-number PluralFormat with an optional ARG_INT or ARG_DOUBLE offset
824          * (e.g., offset:1)
825          * and one or more (ARG_SELECTOR [explicit-value] message) tuples.
826          * If the selector has an explicit value (e.g., =2), then
827          * that value is provided by the ARG_INT or ARG_DOUBLE part preceding the message.
828          * Otherwise the message immediately follows the ARG_SELECTOR.
829          * @stable ICU 4.8
830          */
831         PLURAL,
832         /**
833          * The argument is a SelectFormat with one or more (ARG_SELECTOR, message) pairs.
834          * @stable ICU 4.8
835          */
836         SELECT,
837         /**
838          * The argument is an ordinal-number PluralFormat
839          * with the same style parts sequence and semantics as {@link ArgType#PLURAL}.
840          * @stable ICU 50
841          */
842         SELECTORDINAL;
843
844         /**
845          * @return true if the argument type has a plural style part sequence and semantics,
846          * for example {@link ArgType#PLURAL} and {@link ArgType#SELECTORDINAL}.
847          * @stable ICU 50
848          */
849         public boolean hasPluralStyle() {
850             return this == PLURAL || this == SELECTORDINAL;
851         }
852     }
853
854     /**
855      * Creates and returns a copy of this object.
856      * @return a copy of this object (or itself if frozen).
857      * @stable ICU 4.8
858      */
859     @Override
860     public Object clone() {
861         if(isFrozen()) {
862             return this;
863         } else {
864             return cloneAsThawed();
865         }
866     }
867
868     /**
869      * Creates and returns an unfrozen copy of this object.
870      * @return a copy of this object.
871      * @stable ICU 4.8
872      */
873     @SuppressWarnings("unchecked")
874     public MessagePattern cloneAsThawed() {
875         MessagePattern newMsg;
876         try {
877             newMsg=(MessagePattern)super.clone();
878         } catch (CloneNotSupportedException e) {
879             throw new RuntimeException(e);
880         }
881         newMsg.parts=(ArrayList<Part>)parts.clone();
882         if(numericValues!=null) {
883             newMsg.numericValues=(ArrayList<Double>)numericValues.clone();
884         }
885         newMsg.frozen=false;
886         return newMsg;
887     }
888
889     /**
890      * Freezes this object, making it immutable and thread-safe.
891      * @return this 
892      * @stable ICU 4.8
893      */
894     public MessagePattern freeze() {
895         frozen=true;
896         return this;
897     }
898
899     /**
900      * Determines whether this object is frozen (immutable) or not.
901      * @return true if this object is frozen.
902      * @stable ICU 4.8
903      */
904     public boolean isFrozen() {
905         return frozen;
906     }
907
908     private void preParse(String pattern) {
909         if(isFrozen()) {
910             throw new UnsupportedOperationException(
911                 "Attempt to parse("+prefix(pattern)+") on frozen MessagePattern instance.");
912         }
913         msg=pattern;
914         hasArgNames=hasArgNumbers=false;
915         needsAutoQuoting=false;
916         parts.clear();
917         if(numericValues!=null) {
918             numericValues.clear();
919         }
920     }
921
922     private void postParse() {
923         // Nothing to be done currently.
924     }
925
926     private int parseMessage(int index, int msgStartLength, int nestingLevel, ArgType parentType) {
927         if(nestingLevel>Part.MAX_VALUE) {
928             throw new IndexOutOfBoundsException();
929         }
930         int msgStart=parts.size();
931         addPart(Part.Type.MSG_START, index, msgStartLength, nestingLevel);
932         index+=msgStartLength;
933         while(index<msg.length()) {
934             char c=msg.charAt(index++);
935             if(c=='\'') {
936                 if(index==msg.length()) {
937                     // The apostrophe is the last character in the pattern. 
938                     // Add a Part for auto-quoting.
939                     addPart(Part.Type.INSERT_CHAR, index, 0, '\'');  // value=char to be inserted
940                     needsAutoQuoting=true;
941                 } else {
942                     c=msg.charAt(index);
943                     if(c=='\'') {
944                         // double apostrophe, skip the second one
945                         addPart(Part.Type.SKIP_SYNTAX, index++, 1, 0);
946                     } else if(
947                         aposMode==ApostropheMode.DOUBLE_REQUIRED ||
948                         c=='{' || c=='}' ||
949                         (parentType==ArgType.CHOICE && c=='|') ||
950                         (parentType.hasPluralStyle() && c=='#')
951                     ) {
952                         // skip the quote-starting apostrophe
953                         addPart(Part.Type.SKIP_SYNTAX, index-1, 1, 0);
954                         // find the end of the quoted literal text
955                         for(;;) {
956                             index=msg.indexOf('\'', index+1);
957                             if(index>=0) {
958                                 if((index+1)<msg.length() && msg.charAt(index+1)=='\'') {
959                                     // double apostrophe inside quoted literal text
960                                     // still encodes a single apostrophe, skip the second one
961                                     addPart(Part.Type.SKIP_SYNTAX, ++index, 1, 0);
962                                 } else {
963                                     // skip the quote-ending apostrophe
964                                     addPart(Part.Type.SKIP_SYNTAX, index++, 1, 0);
965                                     break;
966                                 }
967                             } else {
968                                 // The quoted text reaches to the end of the of the message.
969                                 index=msg.length();
970                                 // Add a Part for auto-quoting.
971                                 addPart(Part.Type.INSERT_CHAR, index, 0, '\'');  // value=char to be inserted
972                                 needsAutoQuoting=true;
973                                 break;
974                             }
975                         }
976                     } else {
977                         // Interpret the apostrophe as literal text.
978                         // Add a Part for auto-quoting.
979                         addPart(Part.Type.INSERT_CHAR, index, 0, '\'');  // value=char to be inserted
980                         needsAutoQuoting=true;
981                     }
982                 }
983             } else if(parentType.hasPluralStyle() && c=='#') {
984                 // The unquoted # in a plural message fragment will be replaced
985                 // with the (number-offset).
986                 addPart(Part.Type.REPLACE_NUMBER, index-1, 1, 0);
987             } else if(c=='{') {
988                 index=parseArg(index-1, 1, nestingLevel);
989             } else if((nestingLevel>0 && c=='}') || (parentType==ArgType.CHOICE && c=='|')) {
990                 // Finish the message before the terminator.
991                 // In a choice style, report the "}" substring only for the following ARG_LIMIT,
992                 // not for this MSG_LIMIT.
993                 int limitLength=(parentType==ArgType.CHOICE && c=='}') ? 0 : 1;
994                 addLimitPart(msgStart, Part.Type.MSG_LIMIT, index-1, limitLength, nestingLevel);
995                 if(parentType==ArgType.CHOICE) {
996                     // Let the choice style parser see the '}' or '|'.
997                     return index-1;
998                 } else {
999                     // continue parsing after the '}'
1000                     return index;
1001                 }
1002             }  // else: c is part of literal text
1003         }
1004         if(nestingLevel>0 && !inTopLevelChoiceMessage(nestingLevel, parentType)) {
1005             throw new IllegalArgumentException(
1006                 "Unmatched '{' braces in message "+prefix());
1007         }
1008         addLimitPart(msgStart, Part.Type.MSG_LIMIT, index, 0, nestingLevel);
1009         return index;
1010     }
1011
1012     private int parseArg(int index, int argStartLength, int nestingLevel) {
1013         int argStart=parts.size();
1014         ArgType argType=ArgType.NONE;
1015         addPart(Part.Type.ARG_START, index, argStartLength, argType.ordinal());
1016         int nameIndex=index=skipWhiteSpace(index+argStartLength);
1017         if(index==msg.length()) {
1018             throw new IllegalArgumentException(
1019                 "Unmatched '{' braces in message "+prefix());
1020         }
1021         // parse argument name or number
1022         index=skipIdentifier(index);
1023         int number=parseArgNumber(nameIndex, index);
1024         if(number>=0) {
1025             int length=index-nameIndex;
1026             if(length>Part.MAX_LENGTH || number>Part.MAX_VALUE) {
1027                 throw new IndexOutOfBoundsException(
1028                     "Argument number too large: "+prefix(nameIndex));
1029             }
1030             hasArgNumbers=true;
1031             addPart(Part.Type.ARG_NUMBER, nameIndex, length, number);
1032         } else if(number==ARG_NAME_NOT_NUMBER) {
1033             int length=index-nameIndex;
1034             if(length>Part.MAX_LENGTH) {
1035                 throw new IndexOutOfBoundsException(
1036                     "Argument name too long: "+prefix(nameIndex));
1037             }
1038             hasArgNames=true;
1039             addPart(Part.Type.ARG_NAME, nameIndex, length, 0);
1040         } else {  // number<-1 (ARG_NAME_NOT_VALID)
1041             throw new IllegalArgumentException("Bad argument syntax: "+prefix(nameIndex));
1042         }
1043         index=skipWhiteSpace(index);
1044         if(index==msg.length()) {
1045             throw new IllegalArgumentException(
1046                 "Unmatched '{' braces in message "+prefix());
1047         }
1048         char c=msg.charAt(index);
1049         if(c=='}') {
1050             // all done
1051         } else if(c!=',') {
1052             throw new IllegalArgumentException("Bad argument syntax: "+prefix(nameIndex));
1053         } else /* ',' */ {
1054             // parse argument type: case-sensitive a-zA-Z
1055             int typeIndex=index=skipWhiteSpace(index+1);
1056             while(index<msg.length() && isArgTypeChar(msg.charAt(index))) {
1057                 ++index;
1058             }
1059             int length=index-typeIndex;
1060             index=skipWhiteSpace(index);
1061             if(index==msg.length()) {
1062                 throw new IllegalArgumentException(
1063                     "Unmatched '{' braces in message "+prefix());
1064             }
1065             if(length==0 || ((c=msg.charAt(index))!=',' && c!='}')) {
1066                 throw new IllegalArgumentException("Bad argument syntax: "+prefix(nameIndex));
1067             }
1068             if(length>Part.MAX_LENGTH) {
1069                 throw new IndexOutOfBoundsException(
1070                     "Argument type name too long: "+prefix(nameIndex));
1071             }
1072             argType=ArgType.SIMPLE;
1073             if(length==6) {
1074                 // case-insensitive comparisons for complex-type names
1075                 if(isChoice(typeIndex)) {
1076                     argType=ArgType.CHOICE;
1077                 } else if(isPlural(typeIndex)) {
1078                     argType=ArgType.PLURAL;
1079                 } else if(isSelect(typeIndex)) {
1080                     argType=ArgType.SELECT;
1081                 }
1082             } else if(length==13) {
1083                 if(isSelect(typeIndex) && isOrdinal(typeIndex+6)) {
1084                     argType=ArgType.SELECTORDINAL;
1085                 }
1086             }
1087             // change the ARG_START type from NONE to argType
1088             parts.get(argStart).value=(short)argType.ordinal();
1089             if(argType==ArgType.SIMPLE) {
1090                 addPart(Part.Type.ARG_TYPE, typeIndex, length, 0);
1091             }
1092             // look for an argument style (pattern)
1093             if(c=='}') {
1094                 if(argType!=ArgType.SIMPLE) {
1095                     throw new IllegalArgumentException(
1096                         "No style field for complex argument: "+prefix(nameIndex));
1097                 }
1098             } else /* ',' */ {
1099                 ++index;
1100                 if(argType==ArgType.SIMPLE) {
1101                     index=parseSimpleStyle(index);
1102                 } else if(argType==ArgType.CHOICE) {
1103                     index=parseChoiceStyle(index, nestingLevel);
1104                 } else {
1105                     index=parsePluralOrSelectStyle(argType, index, nestingLevel);
1106                 }
1107             }
1108         }
1109         // Argument parsing stopped on the '}'.
1110         addLimitPart(argStart, Part.Type.ARG_LIMIT, index, 1, argType.ordinal());
1111         return index+1;
1112     }
1113
1114     private int parseSimpleStyle(int index) {
1115         int start=index;
1116         int nestedBraces=0;
1117         while(index<msg.length()) {
1118             char c=msg.charAt(index++);
1119             if(c=='\'') {
1120                 // Treat apostrophe as quoting but include it in the style part.
1121                 // Find the end of the quoted literal text.
1122                 index=msg.indexOf('\'', index);
1123                 if(index<0) {
1124                     throw new IllegalArgumentException(
1125                         "Quoted literal argument style text reaches to the end of the message: "+
1126                         prefix(start));
1127                 }
1128                 // skip the quote-ending apostrophe
1129                 ++index;
1130             } else if(c=='{') {
1131                 ++nestedBraces;
1132             } else if(c=='}') {
1133                 if(nestedBraces>0) {
1134                     --nestedBraces;
1135                 } else {
1136                     int length=--index-start;
1137                     if(length>Part.MAX_LENGTH) {
1138                         throw new IndexOutOfBoundsException(
1139                             "Argument style text too long: "+prefix(start));
1140                     }
1141                     addPart(Part.Type.ARG_STYLE, start, length, 0);
1142                     return index;
1143                 }
1144             }  // c is part of literal text
1145         }
1146         throw new IllegalArgumentException(
1147             "Unmatched '{' braces in message "+prefix());
1148     }
1149
1150     private int parseChoiceStyle(int index, int nestingLevel) {
1151         int start=index;
1152         index=skipWhiteSpace(index);
1153         if(index==msg.length() || msg.charAt(index)=='}') {
1154             throw new IllegalArgumentException(
1155                 "Missing choice argument pattern in "+prefix());
1156         }
1157         for(;;) {
1158             // The choice argument style contains |-separated (number, separator, message) triples.
1159             // Parse the number.
1160             int numberIndex=index;
1161             index=skipDouble(index);
1162             int length=index-numberIndex;
1163             if(length==0) {
1164                 throw new IllegalArgumentException("Bad choice pattern syntax: "+prefix(start));
1165             }
1166             if(length>Part.MAX_LENGTH) {
1167                 throw new IndexOutOfBoundsException(
1168                     "Choice number too long: "+prefix(numberIndex));
1169             }
1170             parseDouble(numberIndex, index, true);  // adds ARG_INT or ARG_DOUBLE
1171             // Parse the separator.
1172             index=skipWhiteSpace(index);
1173             if(index==msg.length()) {
1174                 throw new IllegalArgumentException("Bad choice pattern syntax: "+prefix(start));
1175             }
1176             char c=msg.charAt(index);
1177             if(!(c=='#' || c=='<' || c=='\u2264')) {  // U+2264 is <=
1178                 throw new IllegalArgumentException(
1179                     "Expected choice separator (#<\u2264) instead of '"+c+
1180                     "' in choice pattern "+prefix(start));
1181             }
1182             addPart(Part.Type.ARG_SELECTOR, index, 1, 0);
1183             // Parse the message fragment.
1184             index=parseMessage(++index, 0, nestingLevel+1, ArgType.CHOICE);
1185             // parseMessage(..., CHOICE) returns the index of the terminator, or msg.length().
1186             if(index==msg.length()) {
1187                 return index;
1188             }
1189             if(msg.charAt(index)=='}') {
1190                 if(!inMessageFormatPattern(nestingLevel)) {
1191                     throw new IllegalArgumentException(
1192                         "Bad choice pattern syntax: "+prefix(start));
1193                 }
1194                 return index;
1195             }  // else the terminator is '|'
1196             index=skipWhiteSpace(index+1);
1197         }
1198     }
1199
1200     private int parsePluralOrSelectStyle(ArgType argType, int index, int nestingLevel) {
1201         int start=index;
1202         boolean isEmpty=true;
1203         boolean hasOther=false;
1204         for(;;) {
1205             // First, collect the selector looking for a small set of terminators.
1206             // It would be a little faster to consider the syntax of each possible
1207             // token right here, but that makes the code too complicated.
1208             index=skipWhiteSpace(index);
1209             boolean eos=index==msg.length();
1210             if(eos || msg.charAt(index)=='}') {
1211                 if(eos==inMessageFormatPattern(nestingLevel)) {
1212                     throw new IllegalArgumentException(
1213                         "Bad "+
1214                         argType.toString().toLowerCase(Locale.ENGLISH)+
1215                         " pattern syntax: "+prefix(start));
1216                 }
1217                 if(!hasOther) {
1218                     throw new IllegalArgumentException(
1219                         "Missing 'other' keyword in "+
1220                         argType.toString().toLowerCase(Locale.ENGLISH)+
1221                         " pattern in "+prefix());
1222                 }
1223                 return index;
1224             }
1225             int selectorIndex=index;
1226             if(argType.hasPluralStyle() && msg.charAt(selectorIndex)=='=') {
1227                 // explicit-value plural selector: =double
1228                 index=skipDouble(index+1);
1229                 int length=index-selectorIndex;
1230                 if(length==1) {
1231                     throw new IllegalArgumentException(
1232                         "Bad "+
1233                         argType.toString().toLowerCase(Locale.ENGLISH)+
1234                         " pattern syntax: "+prefix(start));
1235                 }
1236                 if(length>Part.MAX_LENGTH) {
1237                     throw new IndexOutOfBoundsException(
1238                         "Argument selector too long: "+prefix(selectorIndex));
1239                 }
1240                 addPart(Part.Type.ARG_SELECTOR, selectorIndex, length, 0);
1241                 parseDouble(selectorIndex+1, index, false);  // adds ARG_INT or ARG_DOUBLE
1242             } else {
1243                 index=skipIdentifier(index);
1244                 int length=index-selectorIndex;
1245                 if(length==0) {
1246                     throw new IllegalArgumentException(
1247                         "Bad "+
1248                         argType.toString().toLowerCase(Locale.ENGLISH)+
1249                         " pattern syntax: "+prefix(start));
1250                 }
1251                 // Note: The ':' in "offset:" is just beyond the skipIdentifier() range.
1252                 if( argType.hasPluralStyle() && length==6 && index<msg.length() &&
1253                     msg.regionMatches(selectorIndex, "offset:", 0, 7)
1254                 ) {
1255                     // plural offset, not a selector
1256                     if(!isEmpty) {
1257                         throw new IllegalArgumentException(
1258                             "Plural argument 'offset:' (if present) must precede key-message pairs: "+
1259                             prefix(start));
1260                     }
1261                     // allow whitespace between offset: and its value
1262                     int valueIndex=skipWhiteSpace(index+1);  // The ':' is at index.
1263                     index=skipDouble(valueIndex);
1264                     if(index==valueIndex) {
1265                         throw new IllegalArgumentException(
1266                             "Missing value for plural 'offset:' "+prefix(start));
1267                     }
1268                     if((index-valueIndex)>Part.MAX_LENGTH) {
1269                         throw new IndexOutOfBoundsException(
1270                             "Plural offset value too long: "+prefix(valueIndex));
1271                     }
1272                     parseDouble(valueIndex, index, false);  // adds ARG_INT or ARG_DOUBLE
1273                     isEmpty=false;
1274                     continue;  // no message fragment after the offset
1275                 } else {
1276                     // normal selector word
1277                     if(length>Part.MAX_LENGTH) {
1278                         throw new IndexOutOfBoundsException(
1279                             "Argument selector too long: "+prefix(selectorIndex));
1280                     }
1281                     addPart(Part.Type.ARG_SELECTOR, selectorIndex, length, 0);
1282                     if(msg.regionMatches(selectorIndex, "other", 0, length)) {
1283                         hasOther=true;
1284                     }
1285                 }
1286             }
1287
1288             // parse the message fragment following the selector
1289             index=skipWhiteSpace(index);
1290             if(index==msg.length() || msg.charAt(index)!='{') {
1291                 throw new IllegalArgumentException(
1292                     "No message fragment after "+
1293                     argType.toString().toLowerCase(Locale.ENGLISH)+
1294                     " selector: "+prefix(selectorIndex));
1295             }
1296             index=parseMessage(index, 1, nestingLevel+1, argType);
1297             isEmpty=false;
1298         }
1299     }
1300
1301     /**
1302      * Validates and parses an argument name or argument number string.
1303      * This internal method assumes that the input substring is a "pattern identifier".
1304      * @return &gt;=0 if the name is a valid number,
1305      *         ARG_NAME_NOT_NUMBER (-1) if it is a "pattern identifier" but not all ASCII digits,
1306      *         ARG_NAME_NOT_VALID (-2) if it is neither.
1307      * @see #validateArgumentName(String)
1308      */
1309     private static int parseArgNumber(CharSequence s, int start, int limit) {
1310         // If the identifier contains only ASCII digits, then it is an argument _number_
1311         // and must not have leading zeros (except "0" itself).
1312         // Otherwise it is an argument _name_.
1313         if(start>=limit) {
1314             return ARG_NAME_NOT_VALID;
1315         }
1316         int number;
1317         // Defer numeric errors until we know there are only digits.
1318         boolean badNumber;
1319         char c=s.charAt(start++);
1320         if(c=='0') {
1321             if(start==limit) {
1322                 return 0;
1323             } else {
1324                 number=0;
1325                 badNumber=true;  // leading zero
1326             }
1327         } else if('1'<=c && c<='9') {
1328             number=c-'0';
1329             badNumber=false;
1330         } else {
1331             return ARG_NAME_NOT_NUMBER;
1332         }
1333         while(start<limit) {
1334             c=s.charAt(start++);
1335             if('0'<=c && c<='9') {
1336                 if(number>=Integer.MAX_VALUE/10) {
1337                     badNumber=true;  // overflow
1338                 }
1339                 number=number*10+(c-'0');
1340             } else {
1341                 return ARG_NAME_NOT_NUMBER;
1342             }
1343         }
1344         // There are only ASCII digits.
1345         if(badNumber) {
1346             return ARG_NAME_NOT_VALID;
1347         } else {
1348             return number;
1349         }
1350     }
1351
1352     private int parseArgNumber(int start, int limit) {
1353         return parseArgNumber(msg, start, limit);
1354     }
1355
1356     /**
1357      * Parses a number from the specified message substring.
1358      * @param start start index into the message string
1359      * @param limit limit index into the message string, must be start<limit
1360      * @param allowInfinity true if U+221E is allowed (for ChoiceFormat)
1361      */
1362     private void parseDouble(int start, int limit, boolean allowInfinity) {
1363         assert start<limit;
1364         // fake loop for easy exit and single throw statement
1365         for(;;) {
1366             // fast path for small integers and infinity
1367             int value=0;
1368             int isNegative=0;  // not boolean so that we can easily add it to value
1369             int index=start;
1370             char c=msg.charAt(index++);
1371             if(c=='-') {
1372                 isNegative=1;
1373                 if(index==limit) {
1374                     break;  // no number
1375                 }
1376                 c=msg.charAt(index++);
1377             } else if(c=='+') {
1378                 if(index==limit) {
1379                     break;  // no number
1380                 }
1381                 c=msg.charAt(index++);
1382             }
1383             if(c==0x221e) {  // infinity
1384                 if(allowInfinity && index==limit) {
1385                     addArgDoublePart(
1386                         isNegative!=0 ? Double.NEGATIVE_INFINITY : Double.POSITIVE_INFINITY,
1387                         start, limit-start);
1388                     return;
1389                 } else {
1390                     break;
1391                 }
1392             }
1393             // try to parse the number as a small integer but fall back to a double
1394             while('0'<=c && c<='9') {
1395                 value=value*10+(c-'0');
1396                 if(value>(Part.MAX_VALUE+isNegative)) {
1397                     break;  // not a small-enough integer
1398                 }
1399                 if(index==limit) {
1400                     addPart(Part.Type.ARG_INT, start, limit-start, isNegative!=0 ? -value : value);
1401                     return;
1402                 }
1403                 c=msg.charAt(index++);
1404             }
1405             // Let Double.parseDouble() throw a NumberFormatException.
1406             double numericValue=Double.parseDouble(msg.substring(start, limit));
1407             addArgDoublePart(numericValue, start, limit-start);
1408             return;
1409         }
1410         throw new NumberFormatException(
1411             "Bad syntax for numeric value: "+msg.substring(start, limit));
1412     }
1413
1414     /**
1415      * Appends the s[start, limit[ substring to sb, but with only half of the apostrophes
1416      * according to JDK pattern behavior.
1417      * @internal
1418      */
1419     /* package */ static void appendReducedApostrophes(String s, int start, int limit,
1420                                                        StringBuilder sb) {
1421         int doubleApos=-1;
1422         for(;;) {
1423             int i=s.indexOf('\'', start);
1424             if(i<0 || i>=limit) {
1425                 sb.append(s, start, limit);
1426                 break;
1427             }
1428             if(i==doubleApos) {
1429                 // Double apostrophe at start-1 and start==i, append one.
1430                 sb.append('\'');
1431                 ++start;
1432                 doubleApos=-1;
1433             } else {
1434                 // Append text between apostrophes and skip this one.
1435                 sb.append(s, start, i);
1436                 doubleApos=start=i+1;
1437             }
1438         }
1439     }
1440
1441     private int skipWhiteSpace(int index) {
1442         return PatternProps.skipWhiteSpace(msg, index);
1443     }
1444
1445     private int skipIdentifier(int index) {
1446         return PatternProps.skipIdentifier(msg, index);
1447     }
1448
1449     /**
1450      * Skips a sequence of characters that could occur in a double value.
1451      * Does not fully parse or validate the value.
1452      */
1453     private int skipDouble(int index) {
1454         while(index<msg.length()) {
1455             char c=msg.charAt(index);
1456             // U+221E: Allow the infinity symbol, for ChoiceFormat patterns.
1457             if((c<'0' && "+-.".indexOf(c)<0) || (c>'9' && c!='e' && c!='E' && c!=0x221e)) {
1458                 break;
1459             }
1460             ++index;
1461         }
1462         return index;
1463     }
1464
1465     private static boolean isArgTypeChar(int c) {
1466         return ('a'<=c && c<='z') || ('A'<=c && c<='Z');
1467     }
1468
1469     private boolean isChoice(int index) {
1470         char c;
1471         return
1472             ((c=msg.charAt(index++))=='c' || c=='C') &&
1473             ((c=msg.charAt(index++))=='h' || c=='H') &&
1474             ((c=msg.charAt(index++))=='o' || c=='O') &&
1475             ((c=msg.charAt(index++))=='i' || c=='I') &&
1476             ((c=msg.charAt(index++))=='c' || c=='C') &&
1477             ((c=msg.charAt(index))=='e' || c=='E');
1478     }
1479
1480     private boolean isPlural(int index) {
1481         char c;
1482         return
1483             ((c=msg.charAt(index++))=='p' || c=='P') &&
1484             ((c=msg.charAt(index++))=='l' || c=='L') &&
1485             ((c=msg.charAt(index++))=='u' || c=='U') &&
1486             ((c=msg.charAt(index++))=='r' || c=='R') &&
1487             ((c=msg.charAt(index++))=='a' || c=='A') &&
1488             ((c=msg.charAt(index))=='l' || c=='L');
1489     }
1490
1491     private boolean isSelect(int index) {
1492         char c;
1493         return
1494             ((c=msg.charAt(index++))=='s' || c=='S') &&
1495             ((c=msg.charAt(index++))=='e' || c=='E') &&
1496             ((c=msg.charAt(index++))=='l' || c=='L') &&
1497             ((c=msg.charAt(index++))=='e' || c=='E') &&
1498             ((c=msg.charAt(index++))=='c' || c=='C') &&
1499             ((c=msg.charAt(index))=='t' || c=='T');
1500     }
1501
1502     private boolean isOrdinal(int index) {
1503         char c;
1504         return
1505             ((c=msg.charAt(index++))=='o' || c=='O') &&
1506             ((c=msg.charAt(index++))=='r' || c=='R') &&
1507             ((c=msg.charAt(index++))=='d' || c=='D') &&
1508             ((c=msg.charAt(index++))=='i' || c=='I') &&
1509             ((c=msg.charAt(index++))=='n' || c=='N') &&
1510             ((c=msg.charAt(index++))=='a' || c=='A') &&
1511             ((c=msg.charAt(index))=='l' || c=='L');
1512     }
1513
1514     /**
1515      * @return true if we are inside a MessageFormat (sub-)pattern,
1516      *         as opposed to inside a top-level choice/plural/select pattern.
1517      */
1518     private boolean inMessageFormatPattern(int nestingLevel) {
1519         return nestingLevel>0 || parts.get(0).type==Part.Type.MSG_START;
1520     }
1521
1522     /**
1523      * @return true if we are in a MessageFormat sub-pattern
1524      *         of a top-level ChoiceFormat pattern.
1525      */
1526     private boolean inTopLevelChoiceMessage(int nestingLevel, ArgType parentType) {
1527         return
1528             nestingLevel==1 &&
1529             parentType==ArgType.CHOICE &&
1530             parts.get(0).type!=Part.Type.MSG_START;
1531     }
1532
1533     private void addPart(Part.Type type, int index, int length, int value) {
1534         parts.add(new Part(type, index, length, value));
1535     }
1536
1537     private void addLimitPart(int start, Part.Type type, int index, int length, int value) {
1538         parts.get(start).limitPartIndex=parts.size();
1539         addPart(type, index, length, value);
1540     }
1541
1542     private void addArgDoublePart(double numericValue, int start, int length) {
1543         int numericIndex;
1544         if(numericValues==null) {
1545             numericValues=new ArrayList<Double>();
1546             numericIndex=0;
1547         } else {
1548             numericIndex=numericValues.size();
1549             if(numericIndex>Part.MAX_VALUE) {
1550                 throw new IndexOutOfBoundsException("Too many numeric values");
1551             }
1552         }
1553         numericValues.add(numericValue);
1554         addPart(Part.Type.ARG_DOUBLE, start, length, numericIndex);
1555     }
1556
1557     private static final int MAX_PREFIX_LENGTH=24;
1558
1559     /**
1560      * Returns a prefix of s.substring(start). Used for Exception messages.
1561      * @param s
1562      * @param start start index in s
1563      * @return s.substring(start) or a prefix of that
1564      */
1565     private static String prefix(String s, int start) {
1566         StringBuilder prefix=new StringBuilder(MAX_PREFIX_LENGTH+20);
1567         if(start==0) {
1568             prefix.append("\"");
1569         } else {
1570             prefix.append("[at pattern index ").append(start).append("] \"");
1571         }
1572         int substringLength=s.length()-start;
1573         if(substringLength<=MAX_PREFIX_LENGTH) {
1574             prefix.append(start==0 ? s : s.substring(start));
1575         } else {
1576             int limit=start+MAX_PREFIX_LENGTH-4;
1577             if(Character.isHighSurrogate(s.charAt(limit-1))) {
1578                 // remove lead surrogate from the end of the prefix
1579                 --limit;
1580             }
1581             prefix.append(s, start, limit).append(" ...");
1582         }
1583         return prefix.append("\"").toString();
1584     }
1585
1586     private static String prefix(String s) {
1587         return prefix(s, 0);
1588     }
1589
1590     private String prefix(int start) {
1591         return prefix(msg, start);
1592     }
1593
1594     private String prefix() {
1595         return prefix(msg, 0);
1596     }
1597
1598     private ApostropheMode aposMode;
1599     private String msg;
1600     private ArrayList<Part> parts=new ArrayList<Part>();
1601     private ArrayList<Double> numericValues;
1602     private boolean hasArgNames;
1603     private boolean hasArgNumbers;
1604     private boolean needsAutoQuoting;
1605     private boolean frozen;
1606
1607     private static final ApostropheMode defaultAposMode=
1608         ApostropheMode.valueOf(
1609             ICUConfig.get("com.ibm.icu.text.MessagePattern.ApostropheMode", "DOUBLE_OPTIONAL"));
1610
1611     private static final ArgType[] argTypes=ArgType.values();
1612 }