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