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