• 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) 2004-2016, International Business Machines
7 * Corporation and others.  All Rights Reserved.
8 **********************************************************************
9 * Author: Alan Liu
10 * Created: April 6, 2004
11 * Since: ICU 3.0
12 **********************************************************************
13 */
14 package ohos.global.icu.text;
15 
16 import java.io.IOException;
17 import java.io.InvalidObjectException;
18 import java.io.ObjectInputStream;
19 import java.text.AttributedCharacterIterator;
20 import java.text.AttributedCharacterIterator.Attribute;
21 import java.text.AttributedString;
22 import java.text.CharacterIterator;
23 import java.text.ChoiceFormat;
24 import java.text.FieldPosition;
25 import java.text.Format;
26 import java.text.ParseException;
27 import java.text.ParsePosition;
28 import java.util.ArrayList;
29 import java.util.Date;
30 import java.util.HashMap;
31 import java.util.HashSet;
32 import java.util.Iterator;
33 import java.util.List;
34 import java.util.Locale;
35 import java.util.Map;
36 import java.util.Objects;
37 import java.util.Set;
38 
39 import ohos.global.icu.impl.PatternProps;
40 import ohos.global.icu.number.NumberFormatter;
41 import ohos.global.icu.text.MessagePattern.ArgType;
42 import ohos.global.icu.text.MessagePattern.Part;
43 import ohos.global.icu.text.PluralRules.IFixedDecimal;
44 import ohos.global.icu.text.PluralRules.PluralType;
45 import ohos.global.icu.util.ICUUncheckedIOException;
46 import ohos.global.icu.util.ULocale;
47 import ohos.global.icu.util.ULocale.Category;
48 
49 /**
50  * <strong>[icu enhancement]</strong> ICU's replacement for {@link java.text.MessageFormat}.&nbsp;Methods, fields, and other functionality specific to ICU are labeled '<strong>[icu]</strong>'.
51  *
52  * <p>MessageFormat prepares strings for display to users,
53  * with optional arguments (variables/placeholders).
54  * The arguments can occur in any order, which is necessary for translation
55  * into languages with different grammars.
56  *
57  * <p>A MessageFormat is constructed from a <em>pattern</em> string
58  * with arguments in {curly braces} which will be replaced by formatted values.
59  *
60  * <p><code>MessageFormat</code> differs from the other <code>Format</code>
61  * classes in that you create a <code>MessageFormat</code> object with one
62  * of its constructors (not with a <code>getInstance</code> style factory
63  * method). Factory methods aren't necessary because <code>MessageFormat</code>
64  * itself doesn't implement locale-specific behavior. Any locale-specific
65  * behavior is defined by the pattern that you provide and the
66  * subformats used for inserted arguments.
67  *
68  * <p>Arguments can be named (using identifiers) or numbered (using small ASCII-digit integers).
69  * Some of the API methods work only with argument numbers and throw an exception
70  * if the pattern has named arguments (see {@link #usesNamedArguments()}).
71  *
72  * <p>An argument might not specify any format type. In this case,
73  * a Number value is formatted with a default (for the locale) NumberFormat,
74  * a Date value is formatted with a default (for the locale) DateFormat,
75  * and for any other value its toString() value is used.
76  *
77  * <p>An argument might specify a "simple" type for which the specified
78  * Format object is created, cached and used.
79  *
80  * <p>An argument might have a "complex" type with nested MessageFormat sub-patterns.
81  * During formatting, one of these sub-messages is selected according to the argument value
82  * and recursively formatted.
83  *
84  * <p>After construction, a custom Format object can be set for
85  * a top-level argument, overriding the default formatting and parsing behavior
86  * for that argument.
87  * However, custom formatting can be achieved more simply by writing
88  * a typeless argument in the pattern string
89  * and supplying it with a preformatted string value.
90  *
91  * <p>When formatting, MessageFormat takes a collection of argument values
92  * and writes an output string.
93  * The argument values may be passed as an array
94  * (when the pattern contains only numbered arguments)
95  * or as a Map (which works for both named and numbered arguments).
96  *
97  * <p>Each argument is matched with one of the input values by array index or map key
98  * and formatted according to its pattern specification
99  * (or using a custom Format object if one was set).
100  * A numbered pattern argument is matched with a map key that contains that number
101  * as an ASCII-decimal-digit string (without leading zero).
102  *
103  * <h3><a name="patterns">Patterns and Their Interpretation</a></h3>
104  *
105  * <code>MessageFormat</code> uses patterns of the following form:
106  * <blockquote><pre>
107  * message = messageText (argument messageText)*
108  * argument = noneArg | simpleArg | complexArg
109  * complexArg = choiceArg | pluralArg | selectArg | selectordinalArg
110  *
111  * noneArg = '{' argNameOrNumber '}'
112  * simpleArg = '{' argNameOrNumber ',' argType [',' argStyle] '}'
113  * choiceArg = '{' argNameOrNumber ',' "choice" ',' choiceStyle '}'
114  * pluralArg = '{' argNameOrNumber ',' "plural" ',' pluralStyle '}'
115  * selectArg = '{' argNameOrNumber ',' "select" ',' selectStyle '}'
116  * selectordinalArg = '{' argNameOrNumber ',' "selectordinal" ',' pluralStyle '}'
117  *
118  * choiceStyle: see {@link ChoiceFormat}
119  * pluralStyle: see {@link PluralFormat}
120  * selectStyle: see {@link SelectFormat}
121  *
122  * argNameOrNumber = argName | argNumber
123  * argName = [^[[:Pattern_Syntax:][:Pattern_White_Space:]]]+
124  * argNumber = '0' | ('1'..'9' ('0'..'9')*)
125  *
126  * argType = "number" | "date" | "time" | "spellout" | "ordinal" | "duration"
127  * argStyle = "short" | "medium" | "long" | "full" | "integer" | "currency" | "percent" | argStyleText | "::" argSkeletonText
128  * </pre></blockquote>
129  *
130  * <ul>
131  *   <li>messageText can contain quoted literal strings including syntax characters.
132  *       A quoted literal string begins with an ASCII apostrophe and a syntax character
133  *       (usually a {curly brace}) and continues until the next single apostrophe.
134  *       A double ASCII apostrohpe inside or outside of a quoted string represents
135  *       one literal apostrophe.
136  *   <li>Quotable syntax characters are the {curly braces} in all messageText parts,
137  *       plus the '#' sign in a messageText immediately inside a pluralStyle,
138  *       and the '|' symbol in a messageText immediately inside a choiceStyle.
139  *   <li>See also {@link MessagePattern.ApostropheMode}
140  *   <li>In argStyleText, every single ASCII apostrophe begins and ends quoted literal text,
141  *       and unquoted {curly braces} must occur in matched pairs.
142  * </ul>
143  *
144  * <p>Recommendation: Use the real apostrophe (single quote) character \\u2019 for
145  * human-readable text, and use the ASCII apostrophe (\\u0027 ' )
146  * only in program syntax, like quoting in MessageFormat.
147  * See the annotations for U+0027 Apostrophe in The Unicode Standard.
148  *
149  * <p>The <code>choice</code> argument type is deprecated.
150  * Use <code>plural</code> arguments for proper plural selection,
151  * and <code>select</code> arguments for simple selection among a fixed set of choices.
152  *
153  * <p>The <code>argType</code> and <code>argStyle</code> values are used to create
154  * a <code>Format</code> instance for the format element. The following
155  * table shows how the values map to Format instances. Combinations not
156  * shown in the table are illegal. Any <code>argStyleText</code> must
157  * be a valid pattern string for the Format subclass used.
158  *
159  * <table border=1>
160  *    <tr>
161  *       <th>argType
162  *       <th>argStyle
163  *       <th>resulting Format object
164  *    <tr>
165  *       <td colspan=2><i>(none)</i>
166  *       <td><code>null</code>
167  *    <tr>
168  *       <td rowspan=6><code>number</code>
169  *       <td><i>(none)</i>
170  *       <td><code>NumberFormat.getInstance(getLocale())</code>
171  *    <tr>
172  *       <td><code>integer</code>
173  *       <td><code>NumberFormat.getIntegerInstance(getLocale())</code>
174  *    <tr>
175  *       <td><code>currency</code>
176  *       <td><code>NumberFormat.getCurrencyInstance(getLocale())</code>
177  *    <tr>
178  *       <td><code>percent</code>
179  *       <td><code>NumberFormat.getPercentInstance(getLocale())</code>
180  *    <tr>
181  *       <td><i>argStyleText</i>
182  *       <td><code>new DecimalFormat(argStyleText, new DecimalFormatSymbols(getLocale()))</code>
183  *    <tr>
184  *       <td><i>argSkeletonText</i>
185  *       <td><code>NumberFormatter.forSkeleton(argSkeletonText).locale(getLocale()).toFormat()</code>
186  *    <tr>
187  *       <td rowspan=7><code>date</code>
188  *       <td><i>(none)</i>
189  *       <td><code>DateFormat.getDateInstance(DateFormat.DEFAULT, getLocale())</code>
190  *    <tr>
191  *       <td><code>short</code>
192  *       <td><code>DateFormat.getDateInstance(DateFormat.SHORT, getLocale())</code>
193  *    <tr>
194  *       <td><code>medium</code>
195  *       <td><code>DateFormat.getDateInstance(DateFormat.DEFAULT, getLocale())</code>
196  *    <tr>
197  *       <td><code>long</code>
198  *       <td><code>DateFormat.getDateInstance(DateFormat.LONG, getLocale())</code>
199  *    <tr>
200  *       <td><code>full</code>
201  *       <td><code>DateFormat.getDateInstance(DateFormat.FULL, getLocale())</code>
202  *    <tr>
203  *       <td><i>argStyleText</i>
204  *       <td><code>new SimpleDateFormat(argStyleText, getLocale())</code>
205  *    <tr>
206  *       <td><i>argSkeletonText</i>
207  *       <td><code>DateFormat.getInstanceForSkeleton(argSkeletonText, getLocale())</code>
208  *    <tr>
209  *       <td rowspan=6><code>time</code>
210  *       <td><i>(none)</i>
211  *       <td><code>DateFormat.getTimeInstance(DateFormat.DEFAULT, getLocale())</code>
212  *    <tr>
213  *       <td><code>short</code>
214  *       <td><code>DateFormat.getTimeInstance(DateFormat.SHORT, getLocale())</code>
215  *    <tr>
216  *       <td><code>medium</code>
217  *       <td><code>DateFormat.getTimeInstance(DateFormat.DEFAULT, getLocale())</code>
218  *    <tr>
219  *       <td><code>long</code>
220  *       <td><code>DateFormat.getTimeInstance(DateFormat.LONG, getLocale())</code>
221  *    <tr>
222  *       <td><code>full</code>
223  *       <td><code>DateFormat.getTimeInstance(DateFormat.FULL, getLocale())</code>
224  *    <tr>
225  *       <td><i>argStyleText</i>
226  *       <td><code>new SimpleDateFormat(argStyleText, getLocale())</code>
227  *    <tr>
228  *       <td><code>spellout</code>
229  *       <td><i>argStyleText (optional)</i>
230  *       <td><code>new RuleBasedNumberFormat(getLocale(), RuleBasedNumberFormat.SPELLOUT)
231  *           <br>&nbsp;&nbsp;&nbsp;&nbsp;.setDefaultRuleset(argStyleText);</code>
232  *    <tr>
233  *       <td><code>ordinal</code>
234  *       <td><i>argStyleText (optional)</i>
235  *       <td><code>new RuleBasedNumberFormat(getLocale(), RuleBasedNumberFormat.ORDINAL)
236  *           <br>&nbsp;&nbsp;&nbsp;&nbsp;.setDefaultRuleset(argStyleText);</code>
237  *    <tr>
238  *       <td><code>duration</code>
239  *       <td><i>argStyleText (optional)</i>
240  *       <td><code>new RuleBasedNumberFormat(getLocale(), RuleBasedNumberFormat.DURATION)
241  *           <br>&nbsp;&nbsp;&nbsp;&nbsp;.setDefaultRuleset(argStyleText);</code>
242  * </table>
243  *
244  * <h4><a name="diffsjdk">Differences from java.text.MessageFormat</a></h4>
245  *
246  * <p>The ICU MessageFormat supports both named and numbered arguments,
247  * while the JDK MessageFormat only supports numbered arguments.
248  * Named arguments make patterns more readable.
249  *
250  * <p>ICU implements a more user-friendly apostrophe quoting syntax.
251  * In message text, an apostrophe only begins quoting literal text
252  * if it immediately precedes a syntax character (mostly {curly braces}).<br>
253  * In the JDK MessageFormat, an apostrophe always begins quoting,
254  * which requires common text like "don't" and "aujourd'hui"
255  * to be written with doubled apostrophes like "don''t" and "aujourd''hui".
256  * For more details see {@link MessagePattern.ApostropheMode}.
257  *
258  * <p>ICU does not create a ChoiceFormat object for a choiceArg, pluralArg or selectArg
259  * but rather handles such arguments itself.
260  * The JDK MessageFormat does create and use a ChoiceFormat object
261  * (<code>new ChoiceFormat(argStyleText)</code>).
262  * The JDK does not support plural and select arguments at all.
263 
264  * <p>Both the ICU and the JDK <code>MessageFormat</code> can control the argument
265  * formats by using <code>argStyle</code>. But the JDK <code>MessageFormat</code> only
266  * supports predefined formats and number / date / time pattern strings (which would need
267  * to be localized).<br>
268  * ICU supports everything the JDK does, and also number / date / time <b>skeletons</b> using the
269  * <code>::</code> prefix (which automatically yield output appropriate for the
270  * <code>MessageFormat</code> locale).</p>
271  *
272  * <h4>Argument formatting</h4>
273  *
274  * <p>Arguments are formatted according to their type, using the default
275  * ICU formatters for those types, unless otherwise specified.
276  * For unknown types, <code>MessageFormat</code> will call <code>toString()</code>.</p>
277  *
278  * <p>There are also several ways to control the formatting.</p>
279  *
280  * <p>We recommend you use default styles, predefined style values, skeletons,
281  * or preformatted values, but not pattern strings or custom format objects.</p>
282  *
283  * <p>For more details, see the
284  * <a href="http://userguide.icu-project.org/formatparse/messages">ICU User Guide</a>.</p>
285  *
286  * <h4>Usage Information</h4>
287  *
288  * <p>Here are some examples of usage:
289  * <blockquote>
290  * <pre>
291  * Object[] arguments = {
292  *     7,
293  *     new Date(System.currentTimeMillis()),
294  *     "a disturbance in the Force"
295  * };
296  *
297  * String result = MessageFormat.format(
298  *     "At {1,time,::jmm} on {1,date,::dMMMM}, there was {2} on planet {0,number,integer}.",
299  *     arguments);
300  *
301  * <em>output</em>: At 4:34 PM on March 23, there was a disturbance
302  *           in the Force on planet 7.
303  *
304  * </pre>
305  * </blockquote>
306  * Typically, the message format will come from resources, and the
307  * arguments will be dynamically set at runtime.
308  *
309  * <p>Example 2:
310  * <blockquote>
311  * <pre>
312  * Object[] testArgs = { 3, "MyDisk" };
313  *
314  * MessageFormat form = new MessageFormat(
315  *     "The disk \"{1}\" contains {0} file(s).");
316  *
317  * System.out.println(form.format(testArgs));
318  *
319  * // output, with different testArgs
320  * <em>output</em>: The disk "MyDisk" contains 0 file(s).
321  * <em>output</em>: The disk "MyDisk" contains 1 file(s).
322  * <em>output</em>: The disk "MyDisk" contains 1,273 file(s).
323  * </pre>
324  * </blockquote>
325  *
326  * <p>For messages that include plural forms, you can use a plural argument:
327  * <pre>
328  * MessageFormat msgFmt = new MessageFormat(
329  *     "{num_files, plural, " +
330  *     "=0{There are no files on disk \"{disk_name}\".}" +
331  *     "=1{There is one file on disk \"{disk_name}\".}" +
332  *     "other{There are # files on disk \"{disk_name}\".}}",
333  *     ULocale.ENGLISH);
334  * Map args = new HashMap();
335  * args.put("num_files", 0);
336  * args.put("disk_name", "MyDisk");
337  * System.out.println(msgFmt.format(args));
338  * args.put("num_files", 3);
339  * System.out.println(msgFmt.format(args));
340  *
341  * <em>output</em>:
342  * There are no files on disk "MyDisk".
343  * There are 3 files on "MyDisk".
344  * </pre>
345  * See {@link PluralFormat} and {@link PluralRules} for details.
346  *
347  * <h4><a name="synchronization">Synchronization</a></h4>
348  *
349  * <p>MessageFormats are not synchronized.
350  * It is recommended to create separate format instances for each thread.
351  * If multiple threads access a format concurrently, it must be synchronized
352  * externally.
353  *
354  * @see          java.util.Locale
355  * @see          Format
356  * @see          NumberFormat
357  * @see          DecimalFormat
358  * @see          ChoiceFormat
359  * @see          PluralFormat
360  * @see          SelectFormat
361  * @author       Mark Davis
362  * @author       Markus Scherer
363  */
364 public class MessageFormat extends UFormat {
365 
366     // Incremented by 1 for ICU 4.8's new format.
367     static final long serialVersionUID = 7136212545847378652L;
368 
369     /**
370      * Constructs a MessageFormat for the default <code>FORMAT</code> locale and the
371      * specified pattern.
372      * Sets the locale and calls applyPattern(pattern).
373      *
374      * @param pattern the pattern for this message format
375      * @exception IllegalArgumentException if the pattern is invalid
376      * @see Category#FORMAT
377      */
MessageFormat(String pattern)378     public MessageFormat(String pattern) {
379         this.ulocale = ULocale.getDefault(Category.FORMAT);
380         applyPattern(pattern);
381     }
382 
383     /**
384      * Constructs a MessageFormat for the specified locale and
385      * pattern.
386      * Sets the locale and calls applyPattern(pattern).
387      *
388      * @param pattern the pattern for this message format
389      * @param locale the locale for this message format
390      * @exception IllegalArgumentException if the pattern is invalid
391      */
MessageFormat(String pattern, Locale locale)392     public MessageFormat(String pattern, Locale locale) {
393         this(pattern, ULocale.forLocale(locale));
394     }
395 
396     /**
397      * Constructs a MessageFormat for the specified locale and
398      * pattern.
399      * Sets the locale and calls applyPattern(pattern).
400      *
401      * @param pattern the pattern for this message format
402      * @param locale the locale for this message format
403      * @exception IllegalArgumentException if the pattern is invalid
404      */
MessageFormat(String pattern, ULocale locale)405     public MessageFormat(String pattern, ULocale locale) {
406         this.ulocale = locale;
407         applyPattern(pattern);
408     }
409 
410     /**
411      * Sets the locale to be used for creating argument Format objects.
412      * This affects subsequent calls to the {@link #applyPattern applyPattern}
413      * method as well as to the <code>format</code> and
414      * {@link #formatToCharacterIterator formatToCharacterIterator} methods.
415      *
416      * @param locale the locale to be used when creating or comparing subformats
417      */
setLocale(Locale locale)418     public void setLocale(Locale locale) {
419         setLocale(ULocale.forLocale(locale));
420     }
421 
422     /**
423      * Sets the locale to be used for creating argument Format objects.
424      * This affects subsequent calls to the {@link #applyPattern applyPattern}
425      * method as well as to the <code>format</code> and
426      * {@link #formatToCharacterIterator formatToCharacterIterator} methods.
427      *
428      * @param locale the locale to be used when creating or comparing subformats
429      */
setLocale(ULocale locale)430     public void setLocale(ULocale locale) {
431         /* Save the pattern, and then reapply so that */
432         /* we pick up any changes in locale specific */
433         /* elements */
434         String existingPattern = toPattern();                       /*ibm.3550*/
435         this.ulocale = locale;
436         // Invalidate all stock formatters. They are no longer valid since
437         // the locale has changed.
438         stockDateFormatter = null;
439         stockNumberFormatter = null;
440         pluralProvider = null;
441         ordinalProvider = null;
442         applyPattern(existingPattern);                              /*ibm.3550*/
443     }
444 
445     /**
446      * Returns the locale that's used when creating or comparing subformats.
447      *
448      * @return the locale used when creating or comparing subformats
449      */
getLocale()450     public Locale getLocale() {
451         return ulocale.toLocale();
452     }
453 
454     /**
455      * <strong>[icu]</strong> Returns the locale that's used when creating argument Format objects.
456      *
457      * @return the locale used when creating or comparing subformats
458      */
getULocale()459     public ULocale getULocale() {
460         return ulocale;
461     }
462 
463     /**
464      * Sets the pattern used by this message format.
465      * Parses the pattern and caches Format objects for simple argument types.
466      * Patterns and their interpretation are specified in the
467      * <a href="#patterns">class description</a>.
468      *
469      * @param pttrn the pattern for this message format
470      * @throws IllegalArgumentException if the pattern is invalid
471      */
applyPattern(String pttrn)472     public void applyPattern(String pttrn) {
473         try {
474             if (msgPattern == null) {
475                 msgPattern = new MessagePattern(pttrn);
476             } else {
477                 msgPattern.parse(pttrn);
478             }
479             // Cache the formats that are explicitly mentioned in the message pattern.
480             cacheExplicitFormats();
481         } catch(RuntimeException e) {
482             resetPattern();
483             throw e;
484         }
485     }
486 
487     /**
488      * <strong>[icu]</strong> Sets the ApostropheMode and the pattern used by this message format.
489      * Parses the pattern and caches Format objects for simple argument types.
490      * Patterns and their interpretation are specified in the
491      * <a href="#patterns">class description</a>.
492      * <p>
493      * This method is best used only once on a given object to avoid confusion about the mode,
494      * and after constructing the object with an empty pattern string to minimize overhead.
495      *
496      * @param pattern the pattern for this message format
497      * @param aposMode the new ApostropheMode
498      * @throws IllegalArgumentException if the pattern is invalid
499      * @see MessagePattern.ApostropheMode
500      */
applyPattern(String pattern, MessagePattern.ApostropheMode aposMode)501     public void applyPattern(String pattern, MessagePattern.ApostropheMode aposMode) {
502         if (msgPattern == null) {
503             msgPattern = new MessagePattern(aposMode);
504         } else if (aposMode != msgPattern.getApostropheMode()) {
505             msgPattern.clearPatternAndSetApostropheMode(aposMode);
506         }
507         applyPattern(pattern);
508     }
509 
510     /**
511      * <strong>[icu]</strong>
512      * @return this instance's ApostropheMode.
513      */
getApostropheMode()514     public MessagePattern.ApostropheMode getApostropheMode() {
515         if (msgPattern == null) {
516             msgPattern = new MessagePattern();  // Sets the default mode.
517         }
518         return msgPattern.getApostropheMode();
519     }
520 
521     /**
522      * Returns the applied pattern string.
523      * @return the pattern string
524      * @throws IllegalStateException after custom Format objects have been set
525      *         via setFormat() or similar APIs
526      */
toPattern()527     public String toPattern() {
528         // Return the original, applied pattern string, or else "".
529         // Note: This does not take into account
530         // - changes from setFormat() and similar methods, or
531         // - normalization of apostrophes and arguments, for example,
532         //   whether some date/time/number formatter was created via a pattern
533         //   but is equivalent to the "medium" default format.
534         if (customFormatArgStarts != null) {
535             throw new IllegalStateException(
536                     "toPattern() is not supported after custom Format objects "+
537                     "have been set via setFormat() or similar APIs");
538         }
539         if (msgPattern == null) {
540             return "";
541         }
542         String originalPattern = msgPattern.getPatternString();
543         return originalPattern == null ? "" : originalPattern;
544     }
545 
546     /**
547      * Returns the part index of the next ARG_START after partIndex, or -1 if there is none more.
548      * @param partIndex Part index of the previous ARG_START (initially 0).
549      */
nextTopLevelArgStart(int partIndex)550     private int nextTopLevelArgStart(int partIndex) {
551         if (partIndex != 0) {
552             partIndex = msgPattern.getLimitPartIndex(partIndex);
553         }
554         for (;;) {
555             MessagePattern.Part.Type type = msgPattern.getPartType(++partIndex);
556             if (type == MessagePattern.Part.Type.ARG_START) {
557                 return partIndex;
558             }
559             if (type == MessagePattern.Part.Type.MSG_LIMIT) {
560                 return -1;
561             }
562         }
563     }
564 
argNameMatches(int partIndex, String argName, int argNumber)565     private boolean argNameMatches(int partIndex, String argName, int argNumber) {
566         Part part = msgPattern.getPart(partIndex);
567         return part.getType() == MessagePattern.Part.Type.ARG_NAME ?
568             msgPattern.partSubstringMatches(part, argName) :
569             part.getValue() == argNumber;  // ARG_NUMBER
570     }
571 
getArgName(int partIndex)572     private String getArgName(int partIndex) {
573         Part part = msgPattern.getPart(partIndex);
574         if (part.getType() == MessagePattern.Part.Type.ARG_NAME) {
575             return msgPattern.getSubstring(part);
576         } else {
577             return Integer.toString(part.getValue());
578         }
579     }
580 
581     /**
582      * Sets the Format objects to use for the values passed into
583      * <code>format</code> methods or returned from <code>parse</code>
584      * methods. The indices of elements in <code>newFormats</code>
585      * correspond to the argument indices used in the previously set
586      * pattern string.
587      * The order of formats in <code>newFormats</code> thus corresponds to
588      * the order of elements in the <code>arguments</code> array passed
589      * to the <code>format</code> methods or the result array returned
590      * by the <code>parse</code> methods.
591      * <p>
592      * If an argument index is used for more than one format element
593      * in the pattern string, then the corresponding new format is used
594      * for all such format elements. If an argument index is not used
595      * for any format element in the pattern string, then the
596      * corresponding new format is ignored. If fewer formats are provided
597      * than needed, then only the formats for argument indices less
598      * than <code>newFormats.length</code> are replaced.
599      *
600      * This method is only supported if the format does not use
601      * named arguments, otherwise an IllegalArgumentException is thrown.
602      *
603      * @param newFormats the new formats to use
604      * @throws NullPointerException if <code>newFormats</code> is null
605      * @throws IllegalArgumentException if this formatter uses named arguments
606      */
setFormatsByArgumentIndex(Format[] newFormats)607     public void setFormatsByArgumentIndex(Format[] newFormats) {
608         if (msgPattern.hasNamedArguments()) {
609             throw new IllegalArgumentException(
610                     "This method is not available in MessageFormat objects " +
611                     "that use alphanumeric argument names.");
612         }
613         for (int partIndex = 0; (partIndex = nextTopLevelArgStart(partIndex)) >= 0;) {
614             int argNumber = msgPattern.getPart(partIndex + 1).getValue();
615             if (argNumber < newFormats.length) {
616                 setCustomArgStartFormat(partIndex, newFormats[argNumber]);
617             }
618         }
619     }
620 
621     /**
622      * <strong>[icu]</strong> Sets the Format objects to use for the values passed into
623      * <code>format</code> methods or returned from <code>parse</code>
624      * methods. The keys in <code>newFormats</code> are the argument
625      * names in the previously set pattern string, and the values
626      * are the formats.
627      * <p>
628      * Only argument names from the pattern string are considered.
629      * Extra keys in <code>newFormats</code> that do not correspond
630      * to an argument name are ignored.  Similarly, if there is no
631      * format in newFormats for an argument name, the formatter
632      * for that argument remains unchanged.
633      * <p>
634      * This may be called on formats that do not use named arguments.
635      * In this case the map will be queried for key Strings that
636      * represent argument indices, e.g. "0", "1", "2" etc.
637      *
638      * @param newFormats a map from String to Format providing new
639      *        formats for named arguments.
640      */
setFormatsByArgumentName(Map<String, Format> newFormats)641     public void setFormatsByArgumentName(Map<String, Format> newFormats) {
642         for (int partIndex = 0; (partIndex = nextTopLevelArgStart(partIndex)) >= 0;) {
643             String key = getArgName(partIndex + 1);
644             if (newFormats.containsKey(key)) {
645                 setCustomArgStartFormat(partIndex, newFormats.get(key));
646             }
647         }
648     }
649 
650     /**
651      * Sets the Format objects to use for the format elements in the
652      * previously set pattern string.
653      * The order of formats in <code>newFormats</code> corresponds to
654      * the order of format elements in the pattern string.
655      * <p>
656      * If more formats are provided than needed by the pattern string,
657      * the remaining ones are ignored. If fewer formats are provided
658      * than needed, then only the first <code>newFormats.length</code>
659      * formats are replaced.
660      * <p>
661      * Since the order of format elements in a pattern string often
662      * changes during localization, it is generally better to use the
663      * {@link #setFormatsByArgumentIndex setFormatsByArgumentIndex}
664      * method, which assumes an order of formats corresponding to the
665      * order of elements in the <code>arguments</code> array passed to
666      * the <code>format</code> methods or the result array returned by
667      * the <code>parse</code> methods.
668      *
669      * @param newFormats the new formats to use
670      * @exception NullPointerException if <code>newFormats</code> is null
671      */
setFormats(Format[] newFormats)672     public void setFormats(Format[] newFormats) {
673         int formatNumber = 0;
674         for (int partIndex = 0;
675                 formatNumber < newFormats.length &&
676                 (partIndex = nextTopLevelArgStart(partIndex)) >= 0;) {
677             setCustomArgStartFormat(partIndex, newFormats[formatNumber]);
678             ++formatNumber;
679         }
680     }
681 
682     /**
683      * Sets the Format object to use for the format elements within the
684      * previously set pattern string that use the given argument
685      * index.
686      * The argument index is part of the format element definition and
687      * represents an index into the <code>arguments</code> array passed
688      * to the <code>format</code> methods or the result array returned
689      * by the <code>parse</code> methods.
690      * <p>
691      * If the argument index is used for more than one format element
692      * in the pattern string, then the new format is used for all such
693      * format elements. If the argument index is not used for any format
694      * element in the pattern string, then the new format is ignored.
695      *
696      * This method is only supported when exclusively numbers are used for
697      * argument names. Otherwise an IllegalArgumentException is thrown.
698      *
699      * @param argumentIndex the argument index for which to use the new format
700      * @param newFormat the new format to use
701      * @throws IllegalArgumentException if this format uses named arguments
702      */
setFormatByArgumentIndex(int argumentIndex, Format newFormat)703     public void setFormatByArgumentIndex(int argumentIndex, Format newFormat) {
704         if (msgPattern.hasNamedArguments()) {
705             throw new IllegalArgumentException(
706                     "This method is not available in MessageFormat objects " +
707                     "that use alphanumeric argument names.");
708         }
709         for (int partIndex = 0; (partIndex = nextTopLevelArgStart(partIndex)) >= 0;) {
710             if (msgPattern.getPart(partIndex + 1).getValue() == argumentIndex) {
711                 setCustomArgStartFormat(partIndex, newFormat);
712             }
713         }
714     }
715 
716     /**
717      * <strong>[icu]</strong> Sets the Format object to use for the format elements within the
718      * previously set pattern string that use the given argument
719      * name.
720      * <p>
721      * If the argument name is used for more than one format element
722      * in the pattern string, then the new format is used for all such
723      * format elements. If the argument name is not used for any format
724      * element in the pattern string, then the new format is ignored.
725      * <p>
726      * This API may be used on formats that do not use named arguments.
727      * In this case <code>argumentName</code> should be a String that names
728      * an argument index, e.g. "0", "1", "2"... etc.  If it does not name
729      * a valid index, the format will be ignored.  No error is thrown.
730      *
731      * @param argumentName the name of the argument to change
732      * @param newFormat the new format to use
733      */
setFormatByArgumentName(String argumentName, Format newFormat)734     public void setFormatByArgumentName(String argumentName, Format newFormat) {
735         int argNumber = MessagePattern.validateArgumentName(argumentName);
736         if (argNumber < MessagePattern.ARG_NAME_NOT_NUMBER) {
737             return;
738         }
739         for (int partIndex = 0; (partIndex = nextTopLevelArgStart(partIndex)) >= 0;) {
740             if (argNameMatches(partIndex + 1, argumentName, argNumber)) {
741                 setCustomArgStartFormat(partIndex, newFormat);
742             }
743         }
744     }
745 
746     /**
747      * Sets the Format object to use for the format element with the given
748      * format element index within the previously set pattern string.
749      * The format element index is the zero-based number of the format
750      * element counting from the start of the pattern string.
751      * <p>
752      * Since the order of format elements in a pattern string often
753      * changes during localization, it is generally better to use the
754      * {@link #setFormatByArgumentIndex setFormatByArgumentIndex}
755      * method, which accesses format elements based on the argument
756      * index they specify.
757      *
758      * @param formatElementIndex the index of a format element within the pattern
759      * @param newFormat the format to use for the specified format element
760      * @exception ArrayIndexOutOfBoundsException if formatElementIndex is equal to or
761      *            larger than the number of format elements in the pattern string
762      */
setFormat(int formatElementIndex, Format newFormat)763     public void setFormat(int formatElementIndex, Format newFormat) {
764         int formatNumber = 0;
765         for (int partIndex = 0; (partIndex = nextTopLevelArgStart(partIndex)) >= 0;) {
766             if (formatNumber == formatElementIndex) {
767                 setCustomArgStartFormat(partIndex, newFormat);
768                 return;
769             }
770             ++formatNumber;
771         }
772         throw new ArrayIndexOutOfBoundsException(formatElementIndex);
773     }
774 
775     /**
776      * Returns the Format objects used for the values passed into
777      * <code>format</code> methods or returned from <code>parse</code>
778      * methods. The indices of elements in the returned array
779      * correspond to the argument indices used in the previously set
780      * pattern string.
781      * The order of formats in the returned array thus corresponds to
782      * the order of elements in the <code>arguments</code> array passed
783      * to the <code>format</code> methods or the result array returned
784      * by the <code>parse</code> methods.
785      * <p>
786      * If an argument index is used for more than one format element
787      * in the pattern string, then the format used for the last such
788      * format element is returned in the array. If an argument index
789      * is not used for any format element in the pattern string, then
790      * null is returned in the array.
791      *
792      * This method is only supported when exclusively numbers are used for
793      * argument names. Otherwise an IllegalArgumentException is thrown.
794      *
795      * @return the formats used for the arguments within the pattern
796      * @throws IllegalArgumentException if this format uses named arguments
797      */
getFormatsByArgumentIndex()798     public Format[] getFormatsByArgumentIndex() {
799         if (msgPattern.hasNamedArguments()) {
800             throw new IllegalArgumentException(
801                     "This method is not available in MessageFormat objects " +
802                     "that use alphanumeric argument names.");
803         }
804         ArrayList<Format> list = new ArrayList<>();
805         for (int partIndex = 0; (partIndex = nextTopLevelArgStart(partIndex)) >= 0;) {
806             int argNumber = msgPattern.getPart(partIndex + 1).getValue();
807             while (argNumber >= list.size()) {
808                 list.add(null);
809             }
810             list.set(argNumber, cachedFormatters == null ? null : cachedFormatters.get(partIndex));
811         }
812         return list.toArray(new Format[list.size()]);
813     }
814 
815     /**
816      * Returns the Format objects used for the format elements in the
817      * previously set pattern string.
818      * The order of formats in the returned array corresponds to
819      * the order of format elements in the pattern string.
820      * <p>
821      * Since the order of format elements in a pattern string often
822      * changes during localization, it's generally better to use the
823      * {@link #getFormatsByArgumentIndex()}
824      * method, which assumes an order of formats corresponding to the
825      * order of elements in the <code>arguments</code> array passed to
826      * the <code>format</code> methods or the result array returned by
827      * the <code>parse</code> methods.
828      *
829      * This method is only supported when exclusively numbers are used for
830      * argument names. Otherwise an IllegalArgumentException is thrown.
831      *
832      * @return the formats used for the format elements in the pattern
833      * @throws IllegalArgumentException if this format uses named arguments
834      */
getFormats()835     public Format[] getFormats() {
836         ArrayList<Format> list = new ArrayList<>();
837         for (int partIndex = 0; (partIndex = nextTopLevelArgStart(partIndex)) >= 0;) {
838             list.add(cachedFormatters == null ? null : cachedFormatters.get(partIndex));
839         }
840         return list.toArray(new Format[list.size()]);
841     }
842 
843     /**
844      * <strong>[icu]</strong> Returns the top-level argument names. For more details, see
845      * {@link #setFormatByArgumentName(String, Format)}.
846      * @return a Set of argument names
847      */
getArgumentNames()848     public Set<String> getArgumentNames() {
849         Set<String> result = new HashSet<>();
850         for (int partIndex = 0; (partIndex = nextTopLevelArgStart(partIndex)) >= 0;) {
851             result.add(getArgName(partIndex + 1));
852         }
853         return result;
854     }
855 
856     /**
857      * <strong>[icu]</strong> Returns the first top-level format associated with the given argument name.
858      * For more details, see {@link #setFormatByArgumentName(String, Format)}.
859      * @param argumentName The name of the desired argument.
860      * @return the Format associated with the name, or null if there isn't one.
861      */
getFormatByArgumentName(String argumentName)862     public Format getFormatByArgumentName(String argumentName) {
863         if (cachedFormatters == null) {
864             return null;
865         }
866         int argNumber = MessagePattern.validateArgumentName(argumentName);
867         if (argNumber < MessagePattern.ARG_NAME_NOT_NUMBER) {
868             return null;
869         }
870         for (int partIndex = 0; (partIndex = nextTopLevelArgStart(partIndex)) >= 0;) {
871             if (argNameMatches(partIndex + 1, argumentName, argNumber)) {
872                 return cachedFormatters.get(partIndex);
873             }
874         }
875         return null;
876     }
877 
878     /**
879      * Formats an array of objects and appends the <code>MessageFormat</code>'s
880      * pattern, with arguments replaced by the formatted objects, to the
881      * provided <code>StringBuffer</code>.
882      * <p>
883      * The text substituted for the individual format elements is derived from
884      * the current subformat of the format element and the
885      * <code>arguments</code> element at the format element's argument index
886      * as indicated by the first matching line of the following table. An
887      * argument is <i>unavailable</i> if <code>arguments</code> is
888      * <code>null</code> or has fewer than argumentIndex+1 elements.  When
889      * an argument is unavailable no substitution is performed.
890      *
891      * <table border=1>
892      *    <tr>
893      *       <th>argType or Format
894      *       <th>value object
895      *       <th>Formatted Text
896      *    <tr>
897      *       <td><i>any</i>
898      *       <td><i>unavailable</i>
899      *       <td><code>"{" + argNameOrNumber + "}"</code>
900      *    <tr>
901      *       <td><i>any</i>
902      *       <td><code>null</code>
903      *       <td><code>"null"</code>
904      *    <tr>
905      *       <td>custom Format <code>!= null</code>
906      *       <td><i>any</i>
907      *       <td><code>customFormat.format(argument)</code>
908      *    <tr>
909      *       <td>noneArg, or custom Format <code>== null</code>
910      *       <td><code>instanceof Number</code>
911      *       <td><code>NumberFormat.getInstance(getLocale()).format(argument)</code>
912      *    <tr>
913      *       <td>noneArg, or custom Format <code>== null</code>
914      *       <td><code>instanceof Date</code>
915      *       <td><code>DateFormat.getDateTimeInstance(DateFormat.SHORT,
916      *           DateFormat.SHORT, getLocale()).format(argument)</code>
917      *    <tr>
918      *       <td>noneArg, or custom Format <code>== null</code>
919      *       <td><code>instanceof String</code>
920      *       <td><code>argument</code>
921      *    <tr>
922      *       <td>noneArg, or custom Format <code>== null</code>
923      *       <td><i>any</i>
924      *       <td><code>argument.toString()</code>
925      *    <tr>
926      *       <td>complexArg
927      *       <td><i>any</i>
928      *       <td>result of recursive formatting of a selected sub-message
929      * </table>
930      * <p>
931      * If <code>pos</code> is non-null, and refers to
932      * <code>Field.ARGUMENT</code>, the location of the first formatted
933      * string will be returned.
934      *
935      * This method is only supported when the format does not use named
936      * arguments, otherwise an IllegalArgumentException is thrown.
937      *
938      * @param arguments an array of objects to be formatted and substituted.
939      * @param result where text is appended.
940      * @param pos On input: an alignment field, if desired.
941      *            On output: the offsets of the alignment field.
942      * @throws IllegalArgumentException if a value in the
943      *         <code>arguments</code> array is not of the type
944      *         expected by the corresponding argument or custom Format object.
945      * @throws IllegalArgumentException if this format uses named arguments
946      */
format(Object[] arguments, StringBuffer result, FieldPosition pos)947     public final StringBuffer format(Object[] arguments, StringBuffer result,
948                                      FieldPosition pos)
949     {
950         format(arguments, null, new AppendableWrapper(result), pos);
951         return result;
952     }
953 
954     /**
955      * Formats a map of objects and appends the <code>MessageFormat</code>'s
956      * pattern, with arguments replaced by the formatted objects, to the
957      * provided <code>StringBuffer</code>.
958      * <p>
959      * The text substituted for the individual format elements is derived from
960      * the current subformat of the format element and the
961      * <code>arguments</code> value corresopnding to the format element's
962      * argument name.
963      * <p>
964      * A numbered pattern argument is matched with a map key that contains that number
965      * as an ASCII-decimal-digit string (without leading zero).
966      * <p>
967      * An argument is <i>unavailable</i> if <code>arguments</code> is
968      * <code>null</code> or does not have a value corresponding to an argument
969      * name in the pattern.  When an argument is unavailable no substitution
970      * is performed.
971      *
972      * @param arguments a map of objects to be formatted and substituted.
973      * @param result where text is appended.
974      * @param pos On input: an alignment field, if desired.
975      *            On output: the offsets of the alignment field.
976      * @throws IllegalArgumentException if a value in the
977      *         <code>arguments</code> array is not of the type
978      *         expected by the corresponding argument or custom Format object.
979      * @return the passed-in StringBuffer
980      */
format(Map<String, Object> arguments, StringBuffer result, FieldPosition pos)981     public final StringBuffer format(Map<String, Object> arguments, StringBuffer result,
982                                      FieldPosition pos) {
983         format(null, arguments, new AppendableWrapper(result), pos);
984         return result;
985     }
986 
987     /**
988      * Creates a MessageFormat with the given pattern and uses it
989      * to format the given arguments. This is equivalent to
990      * <blockquote>
991      *     <code>(new {@link #MessageFormat(String) MessageFormat}(pattern)).{@link
992      *     #format(java.lang.Object[], java.lang.StringBuffer, java.text.FieldPosition)
993      *     format}(arguments, new StringBuffer(), null).toString()</code>
994      * </blockquote>
995      *
996      * @throws IllegalArgumentException if the pattern is invalid
997      * @throws IllegalArgumentException if a value in the
998      *         <code>arguments</code> array is not of the type
999      *         expected by the corresponding argument or custom Format object.
1000      * @throws IllegalArgumentException if this format uses named arguments
1001      */
format(String pattern, Object... arguments)1002     public static String format(String pattern, Object... arguments) {
1003         MessageFormat temp = new MessageFormat(pattern);
1004         return temp.format(arguments);
1005     }
1006 
1007     /**
1008      * Creates a MessageFormat with the given pattern and uses it to
1009      * format the given arguments.  The pattern must identifyarguments
1010      * by name instead of by number.
1011      * <p>
1012      * @throws IllegalArgumentException if the pattern is invalid
1013      * @throws IllegalArgumentException if a value in the
1014      *         <code>arguments</code> array is not of the type
1015      *         expected by the corresponding argument or custom Format object.
1016      * @see #format(Map, StringBuffer, FieldPosition)
1017      * @see #format(String, Object[])
1018      */
format(String pattern, Map<String, Object> arguments)1019     public static String format(String pattern, Map<String, Object> arguments) {
1020         MessageFormat temp = new MessageFormat(pattern);
1021         return temp.format(arguments);
1022     }
1023 
1024     /**
1025      * <strong>[icu]</strong> Returns true if this MessageFormat uses named arguments,
1026      * and false otherwise.  See class description.
1027      *
1028      * @return true if named arguments are used.
1029      */
usesNamedArguments()1030     public boolean usesNamedArguments() {
1031         return msgPattern.hasNamedArguments();
1032     }
1033 
1034     // Overrides
1035     /**
1036      * Formats a map or array of objects and appends the <code>MessageFormat</code>'s
1037      * pattern, with format elements replaced by the formatted objects, to the
1038      * provided <code>StringBuffer</code>.
1039      * This is equivalent to either of
1040      * <blockquote>
1041      *     <code>{@link #format(java.lang.Object[], java.lang.StringBuffer,
1042      *     java.text.FieldPosition) format}((Object[]) arguments, result, pos)</code>
1043      *     <code>{@link #format(java.util.Map, java.lang.StringBuffer,
1044      *     java.text.FieldPosition) format}((Map) arguments, result, pos)</code>
1045      * </blockquote>
1046      * A map must be provided if this format uses named arguments, otherwise
1047      * an IllegalArgumentException will be thrown.
1048      * @param arguments a map or array of objects to be formatted
1049      * @param result where text is appended
1050      * @param pos On input: an alignment field, if desired
1051      *            On output: the offsets of the alignment field
1052      * @throws IllegalArgumentException if an argument in
1053      *         <code>arguments</code> is not of the type
1054      *         expected by the format element(s) that use it
1055      * @throws IllegalArgumentException if <code>arguments</code> is
1056      *         an array of Object and this format uses named arguments
1057      */
1058     @Override
format(Object arguments, StringBuffer result, FieldPosition pos)1059     public final StringBuffer format(Object arguments, StringBuffer result,
1060                                      FieldPosition pos)
1061     {
1062         format(arguments, new AppendableWrapper(result), pos);
1063         return result;
1064     }
1065 
1066     /**
1067      * Formats an array of objects and inserts them into the
1068      * <code>MessageFormat</code>'s pattern, producing an
1069      * <code>AttributedCharacterIterator</code>.
1070      * You can use the returned <code>AttributedCharacterIterator</code>
1071      * to build the resulting String, as well as to determine information
1072      * about the resulting String.
1073      * <p>
1074      * The text of the returned <code>AttributedCharacterIterator</code> is
1075      * the same that would be returned by
1076      * <blockquote>
1077      *     <code>{@link #format(java.lang.Object[], java.lang.StringBuffer,
1078      *     java.text.FieldPosition) format}(arguments, new StringBuffer(), null).toString()</code>
1079      * </blockquote>
1080      * <p>
1081      * In addition, the <code>AttributedCharacterIterator</code> contains at
1082      * least attributes indicating where text was generated from an
1083      * argument in the <code>arguments</code> array. The keys of these attributes are of
1084      * type <code>MessageFormat.Field</code>, their values are
1085      * <code>Integer</code> objects indicating the index in the <code>arguments</code>
1086      * array of the argument from which the text was generated.
1087      * <p>
1088      * The attributes/value from the underlying <code>Format</code>
1089      * instances that <code>MessageFormat</code> uses will also be
1090      * placed in the resulting <code>AttributedCharacterIterator</code>.
1091      * This allows you to not only find where an argument is placed in the
1092      * resulting String, but also which fields it contains in turn.
1093      *
1094      * @param arguments an array of objects to be formatted and substituted.
1095      * @return AttributedCharacterIterator describing the formatted value.
1096      * @exception NullPointerException if <code>arguments</code> is null.
1097      * @throws IllegalArgumentException if a value in the
1098      *         <code>arguments</code> array is not of the type
1099      *         expected by the corresponding argument or custom Format object.
1100      */
1101     @Override
formatToCharacterIterator(Object arguments)1102     public AttributedCharacterIterator formatToCharacterIterator(Object arguments) {
1103         if (arguments == null) {
1104             throw new NullPointerException(
1105                    "formatToCharacterIterator must be passed non-null object");
1106         }
1107         StringBuilder result = new StringBuilder();
1108         AppendableWrapper wrapper = new AppendableWrapper(result);
1109         wrapper.useAttributes();
1110         format(arguments, wrapper, null);
1111         AttributedString as = new AttributedString(result.toString());
1112         for (AttributeAndPosition a : wrapper.attributes) {
1113             as.addAttribute(a.key, a.value, a.start, a.limit);
1114         }
1115         return as.getIterator();
1116     }
1117 
1118     /**
1119      * Parses the string.
1120      *
1121      * <p>Caveats: The parse may fail in a number of circumstances.
1122      * For example:
1123      * <ul>
1124      * <li>If one of the arguments does not occur in the pattern.
1125      * <li>If the format of an argument loses information, such as
1126      *     with a choice format where a large number formats to "many".
1127      * <li>Does not yet handle recursion (where
1128      *     the substituted strings contain {n} references.)
1129      * <li>Will not always find a match (or the correct match)
1130      *     if some part of the parse is ambiguous.
1131      *     For example, if the pattern "{1},{2}" is used with the
1132      *     string arguments {"a,b", "c"}, it will format as "a,b,c".
1133      *     When the result is parsed, it will return {"a", "b,c"}.
1134      * <li>If a single argument is parsed more than once in the string,
1135      *     then the later parse wins.
1136      * </ul>
1137      * When the parse fails, use ParsePosition.getErrorIndex() to find out
1138      * where in the string did the parsing failed. The returned error
1139      * index is the starting offset of the sub-patterns that the string
1140      * is comparing with. For example, if the parsing string "AAA {0} BBB"
1141      * is comparing against the pattern "AAD {0} BBB", the error index is
1142      * 0. When an error occurs, the call to this method will return null.
1143      * If the source is null, return an empty array.
1144      *
1145      * @throws IllegalArgumentException if this format uses named arguments
1146      */
parse(String source, ParsePosition pos)1147     public Object[] parse(String source, ParsePosition pos) {
1148         if (msgPattern.hasNamedArguments()) {
1149             throw new IllegalArgumentException(
1150                     "This method is not available in MessageFormat objects " +
1151                     "that use named argument.");
1152         }
1153 
1154         // Count how many slots we need in the array.
1155         int maxArgId = -1;
1156         for (int partIndex = 0; (partIndex = nextTopLevelArgStart(partIndex)) >= 0;) {
1157             int argNumber=msgPattern.getPart(partIndex + 1).getValue();
1158             if (argNumber > maxArgId) {
1159                 maxArgId = argNumber;
1160             }
1161         }
1162         Object[] resultArray = new Object[maxArgId + 1];
1163 
1164         int backupStartPos = pos.getIndex();
1165         parse(0, source, pos, resultArray, null);
1166         if (pos.getIndex() == backupStartPos) { // unchanged, returned object is null
1167             return null;
1168         }
1169 
1170         return resultArray;
1171     }
1172 
1173     /**
1174      * <strong>[icu]</strong> Parses the string, returning the results in a Map.
1175      * This is similar to the version that returns an array
1176      * of Object.  This supports both named and numbered
1177      * arguments-- if numbered, the keys in the map are the
1178      * corresponding ASCII-decimal-digit strings (e.g. "0", "1", "2"...).
1179      *
1180      * @param source the text to parse
1181      * @param pos the position at which to start parsing.  on return,
1182      *        contains the result of the parse.
1183      * @return a Map containing key/value pairs for each parsed argument.
1184      */
parseToMap(String source, ParsePosition pos)1185     public Map<String, Object> parseToMap(String source, ParsePosition pos)  {
1186         Map<String, Object> result = new HashMap<>();
1187         int backupStartPos = pos.getIndex();
1188         parse(0, source, pos, null, result);
1189         if (pos.getIndex() == backupStartPos) {
1190             return null;
1191         }
1192         return result;
1193     }
1194 
1195     /**
1196      * Parses text from the beginning of the given string to produce an object
1197      * array.
1198      * The method may not use the entire text of the given string.
1199      * <p>
1200      * See the {@link #parse(String, ParsePosition)} method for more information
1201      * on message parsing.
1202      *
1203      * @param source A <code>String</code> whose beginning should be parsed.
1204      * @return An <code>Object</code> array parsed from the string.
1205      * @exception ParseException if the beginning of the specified string cannot be parsed.
1206      * @exception IllegalArgumentException if this format uses named arguments
1207      */
parse(String source)1208     public Object[] parse(String source) throws ParseException {
1209         ParsePosition pos = new ParsePosition(0);
1210         Object[] result = parse(source, pos);
1211         if (pos.getIndex() == 0) // unchanged, returned object is null
1212             throw new ParseException("MessageFormat parse error!",
1213                                      pos.getErrorIndex());
1214 
1215         return result;
1216     }
1217 
1218     /**
1219      * Parses the string, filling either the Map or the Array.
1220      * This is a private method that all the public parsing methods call.
1221      * This supports both named and numbered
1222      * arguments-- if numbered, the keys in the map are the
1223      * corresponding ASCII-decimal-digit strings (e.g. "0", "1", "2"...).
1224      *
1225      * @param msgStart index in the message pattern to start from.
1226      * @param source the text to parse
1227      * @param pos the position at which to start parsing.  on return,
1228      *        contains the result of the parse.
1229      * @param args if not null, the parse results will be filled here (The pattern
1230      *        has to have numbered arguments in order for this to not be null).
1231      * @param argsMap if not null, the parse results will be filled here.
1232      */
parse(int msgStart, String source, ParsePosition pos, Object[] args, Map<String, Object> argsMap)1233     private void parse(int msgStart, String source, ParsePosition pos,
1234                        Object[] args, Map<String, Object> argsMap) {
1235         if (source == null) {
1236             return;
1237         }
1238         String msgString=msgPattern.getPatternString();
1239         int prevIndex=msgPattern.getPart(msgStart).getLimit();
1240         int sourceOffset = pos.getIndex();
1241         ParsePosition tempStatus = new ParsePosition(0);
1242 
1243         for(int i=msgStart+1; ; ++i) {
1244             Part part=msgPattern.getPart(i);
1245             Part.Type type=part.getType();
1246             int index=part.getIndex();
1247             // Make sure the literal string matches.
1248             int len = index - prevIndex;
1249             if (len == 0 || msgString.regionMatches(prevIndex, source, sourceOffset, len)) {
1250                 sourceOffset += len;
1251                 prevIndex += len;
1252             } else {
1253                 pos.setErrorIndex(sourceOffset);
1254                 return; // leave index as is to signal error
1255             }
1256             if(type==Part.Type.MSG_LIMIT) {
1257                 // Things went well! Done.
1258                 pos.setIndex(sourceOffset);
1259                 return;
1260             }
1261             if(type==Part.Type.SKIP_SYNTAX || type==Part.Type.INSERT_CHAR) {
1262                 prevIndex=part.getLimit();
1263                 continue;
1264             }
1265             // We do not support parsing Plural formats. (No REPLACE_NUMBER here.)
1266             assert type==Part.Type.ARG_START : "Unexpected Part "+part+" in parsed message.";
1267             int argLimit=msgPattern.getLimitPartIndex(i);
1268 
1269             ArgType argType=part.getArgType();
1270             part=msgPattern.getPart(++i);
1271             // Compute the argId, so we can use it as a key.
1272             Object argId=null;
1273             int argNumber = 0;
1274             String key = null;
1275             if(args!=null) {
1276                 argNumber=part.getValue();  // ARG_NUMBER
1277                 argId = Integer.valueOf(argNumber);
1278             } else {
1279                 if(part.getType()==MessagePattern.Part.Type.ARG_NAME) {
1280                     key=msgPattern.getSubstring(part);
1281                 } else /* ARG_NUMBER */ {
1282                     key=Integer.toString(part.getValue());
1283                 }
1284                 argId = key;
1285             }
1286 
1287             ++i;
1288             Format formatter = null;
1289             boolean haveArgResult = false;
1290             Object argResult = null;
1291             if(cachedFormatters!=null && (formatter=cachedFormatters.get(i - 2))!=null) {
1292                 // Just parse using the formatter.
1293                 tempStatus.setIndex(sourceOffset);
1294                 argResult = formatter.parseObject(source, tempStatus);
1295                 if (tempStatus.getIndex() == sourceOffset) {
1296                     pos.setErrorIndex(sourceOffset);
1297                     return; // leave index as is to signal error
1298                 }
1299                 haveArgResult = true;
1300                 sourceOffset = tempStatus.getIndex();
1301             } else if(
1302                     argType==ArgType.NONE ||
1303                     (cachedFormatters!=null && cachedFormatters.containsKey(i - 2))) {
1304                 // Match as a string.
1305                 // if at end, use longest possible match
1306                 // otherwise uses first match to intervening string
1307                 // does NOT recursively try all possibilities
1308                 String stringAfterArgument = getLiteralStringUntilNextArgument(argLimit);
1309                 int next;
1310                 if (stringAfterArgument.length() != 0) {
1311                     next = source.indexOf(stringAfterArgument, sourceOffset);
1312                 } else {
1313                     next = source.length();
1314                 }
1315                 if (next < 0) {
1316                     pos.setErrorIndex(sourceOffset);
1317                     return; // leave index as is to signal error
1318                 } else {
1319                     String strValue = source.substring(sourceOffset, next);
1320                     if (!strValue.equals("{" + argId.toString() + "}")) {
1321                         haveArgResult = true;
1322                         argResult = strValue;
1323                     }
1324                     sourceOffset = next;
1325                 }
1326             } else if(argType==ArgType.CHOICE) {
1327                 tempStatus.setIndex(sourceOffset);
1328                 double choiceResult = parseChoiceArgument(msgPattern, i, source, tempStatus);
1329                 if (tempStatus.getIndex() == sourceOffset) {
1330                     pos.setErrorIndex(sourceOffset);
1331                     return; // leave index as is to signal error
1332                 }
1333                 argResult = choiceResult;
1334                 haveArgResult = true;
1335                 sourceOffset = tempStatus.getIndex();
1336             } else if(argType.hasPluralStyle() || argType==ArgType.SELECT) {
1337                 // No can do!
1338                 throw new UnsupportedOperationException(
1339                         "Parsing of plural/select/selectordinal argument is not supported.");
1340             } else {
1341                 // This should never happen.
1342                 throw new IllegalStateException("unexpected argType "+argType);
1343             }
1344             if (haveArgResult) {
1345                 if (args != null) {
1346                     args[argNumber] = argResult;
1347                 } else if (argsMap != null) {
1348                     argsMap.put(key, argResult);
1349                 }
1350             }
1351             prevIndex=msgPattern.getPart(argLimit).getLimit();
1352             i=argLimit;
1353         }
1354     }
1355 
1356     /**
1357      * <strong>[icu]</strong> Parses text from the beginning of the given string to produce a map from
1358      * argument to values. The method may not use the entire text of the given string.
1359      *
1360      * <p>See the {@link #parse(String, ParsePosition)} method for more information on
1361      * message parsing.
1362      *
1363      * @param source A <code>String</code> whose beginning should be parsed.
1364      * @return A <code>Map</code> parsed from the string.
1365      * @throws ParseException if the beginning of the specified string cannot
1366      *         be parsed.
1367      * @see #parseToMap(String, ParsePosition)
1368      */
parseToMap(String source)1369     public Map<String, Object> parseToMap(String source) throws ParseException {
1370         ParsePosition pos = new ParsePosition(0);
1371         Map<String, Object> result = new HashMap<>();
1372         parse(0, source, pos, null, result);
1373         if (pos.getIndex() == 0) // unchanged, returned object is null
1374             throw new ParseException("MessageFormat parse error!",
1375                                      pos.getErrorIndex());
1376 
1377         return result;
1378     }
1379 
1380     /**
1381      * Parses text from a string to produce an object array or Map.
1382      * <p>
1383      * The method attempts to parse text starting at the index given by
1384      * <code>pos</code>.
1385      * If parsing succeeds, then the index of <code>pos</code> is updated
1386      * to the index after the last character used (parsing does not necessarily
1387      * use all characters up to the end of the string), and the parsed
1388      * object array is returned. The updated <code>pos</code> can be used to
1389      * indicate the starting point for the next call to this method.
1390      * If an error occurs, then the index of <code>pos</code> is not
1391      * changed, the error index of <code>pos</code> is set to the index of
1392      * the character where the error occurred, and null is returned.
1393      * <p>
1394      * See the {@link #parse(String, ParsePosition)} method for more information
1395      * on message parsing.
1396      *
1397      * @param source A <code>String</code>, part of which should be parsed.
1398      * @param pos A <code>ParsePosition</code> object with index and error
1399      *            index information as described above.
1400      * @return An <code>Object</code> parsed from the string, either an
1401      *         array of Object, or a Map, depending on whether named
1402      *         arguments are used.  This can be queried using <code>usesNamedArguments</code>.
1403      *         In case of error, returns null.
1404      * @throws NullPointerException if <code>pos</code> is null.
1405      */
1406     @Override
parseObject(String source, ParsePosition pos)1407     public Object parseObject(String source, ParsePosition pos) {
1408         if (!msgPattern.hasNamedArguments()) {
1409             return parse(source, pos);
1410         } else {
1411             return parseToMap(source, pos);
1412         }
1413     }
1414 
1415     /**
1416      * {@inheritDoc}
1417      */
1418     @Override
clone()1419     public Object clone() {
1420         MessageFormat other = (MessageFormat) super.clone();
1421 
1422         if (customFormatArgStarts != null) {
1423             other.customFormatArgStarts = new HashSet<>();
1424             for (Integer key : customFormatArgStarts) {
1425                 other.customFormatArgStarts.add(key);
1426             }
1427         } else {
1428             other.customFormatArgStarts = null;
1429         }
1430 
1431         if (cachedFormatters != null) {
1432             other.cachedFormatters = new HashMap<>();
1433             Iterator<Map.Entry<Integer, Format>> it = cachedFormatters.entrySet().iterator();
1434             while (it.hasNext()){
1435                 Map.Entry<Integer, Format> entry = it.next();
1436                 other.cachedFormatters.put(entry.getKey(), entry.getValue());
1437             }
1438         } else {
1439             other.cachedFormatters = null;
1440         }
1441 
1442         other.msgPattern = msgPattern == null ? null : (MessagePattern)msgPattern.clone();
1443         other.stockDateFormatter =
1444                 stockDateFormatter == null ? null : (DateFormat) stockDateFormatter.clone();
1445         other.stockNumberFormatter =
1446                 stockNumberFormatter == null ? null : (NumberFormat) stockNumberFormatter.clone();
1447 
1448         other.pluralProvider = null;
1449         other.ordinalProvider = null;
1450         return other;
1451     }
1452 
1453     /**
1454      * {@inheritDoc}
1455      */
1456     @Override
equals(Object obj)1457     public boolean equals(Object obj) {
1458         if (this == obj)                      // quick check
1459             return true;
1460         if (obj == null || getClass() != obj.getClass())
1461             return false;
1462         MessageFormat other = (MessageFormat) obj;
1463         return Objects.equals(ulocale, other.ulocale)
1464                 && Objects.equals(msgPattern, other.msgPattern)
1465                 && Objects.equals(cachedFormatters, other.cachedFormatters)
1466                 && Objects.equals(customFormatArgStarts, other.customFormatArgStarts);
1467         // Note: It might suffice to only compare custom formatters
1468         // rather than all formatters.
1469     }
1470 
1471     /**
1472      * {@inheritDoc}
1473      */
1474     @Override
hashCode()1475     public int hashCode() {
1476         return msgPattern.getPatternString().hashCode(); // enough for reasonable distribution
1477     }
1478 
1479     /**
1480      * Defines constants that are used as attribute keys in the
1481      * <code>AttributedCharacterIterator</code> returned
1482      * from <code>MessageFormat.formatToCharacterIterator</code>.
1483      */
1484     public static class Field extends Format.Field {
1485 
1486         private static final long serialVersionUID = 7510380454602616157L;
1487 
1488         /**
1489          * Create a <code>Field</code> with the specified name.
1490          *
1491          * @param name The name of the attribute
1492          */
Field(String name)1493         protected Field(String name) {
1494             super(name);
1495         }
1496 
1497         /**
1498          * Resolves instances being deserialized to the predefined constants.
1499          *
1500          * @return resolved MessageFormat.Field constant
1501          * @throws InvalidObjectException if the constant could not be resolved.
1502          */
1503         @Override
readResolve()1504         protected Object readResolve() throws InvalidObjectException {
1505             if (this.getClass() != MessageFormat.Field.class) {
1506                 throw new InvalidObjectException(
1507                     "A subclass of MessageFormat.Field must implement readResolve.");
1508             }
1509             if (this.getName().equals(ARGUMENT.getName())) {
1510                 return ARGUMENT;
1511             } else {
1512                 throw new InvalidObjectException("Unknown attribute name.");
1513             }
1514         }
1515 
1516         /**
1517          * Constant identifying a portion of a message that was generated
1518          * from an argument passed into <code>formatToCharacterIterator</code>.
1519          * The value associated with the key will be an <code>Integer</code>
1520          * indicating the index in the <code>arguments</code> array of the
1521          * argument from which the text was generated.
1522          */
1523         public static final Field ARGUMENT = new Field("message argument field");
1524     }
1525 
1526     // ===========================privates============================
1527 
1528     // *Important*: All fields must be declared *transient* so that we can fully
1529     // control serialization!
1530     // See for example Joshua Bloch's "Effective Java", chapter 10 Serialization.
1531 
1532     /**
1533      * The locale to use for formatting numbers and dates.
1534      */
1535     private transient ULocale ulocale;
1536 
1537     /**
1538      * The MessagePattern which contains the parsed structure of the pattern string.
1539      */
1540     private transient MessagePattern msgPattern;
1541     /**
1542      * Cached formatters so we can just use them whenever needed instead of creating
1543      * them from scratch every time.
1544      */
1545     private transient Map<Integer, Format> cachedFormatters;
1546     /**
1547      * Set of ARG_START part indexes where custom, user-provided Format objects
1548      * have been set via setFormat() or similar API.
1549      */
1550     private transient Set<Integer> customFormatArgStarts;
1551 
1552     /**
1553      * Stock formatters. Those are used when a format is not explicitly mentioned in
1554      * the message. The format is inferred from the argument.
1555      */
1556     private transient DateFormat stockDateFormatter;
1557     private transient NumberFormat stockNumberFormatter;
1558 
1559     private transient PluralSelectorProvider pluralProvider;
1560     private transient PluralSelectorProvider ordinalProvider;
1561 
getStockDateFormatter()1562     private DateFormat getStockDateFormatter() {
1563         if (stockDateFormatter == null) {
1564             stockDateFormatter = DateFormat.getDateTimeInstance(
1565                     DateFormat.SHORT, DateFormat.SHORT, ulocale);//fix
1566         }
1567         return stockDateFormatter;
1568     }
getStockNumberFormatter()1569     private NumberFormat getStockNumberFormatter() {
1570         if (stockNumberFormatter == null) {
1571             stockNumberFormatter = NumberFormat.getInstance(ulocale);
1572         }
1573         return stockNumberFormatter;
1574     }
1575 
1576     // *Important*: All fields must be declared *transient*.
1577     // See the longer comment above ulocale.
1578 
1579     /**
1580      * Formats the arguments and writes the result into the
1581      * AppendableWrapper, updates the field position.
1582      *
1583      * <p>Exactly one of args and argsMap must be null, the other non-null.
1584      *
1585      * @param msgStart      Index to msgPattern part to start formatting from.
1586      * @param pluralNumber  null except when formatting a plural argument sub-message
1587      *                      where a '#' is replaced by the format string for this number.
1588      * @param args          The formattable objects array. Non-null iff numbered values are used.
1589      * @param argsMap       The key-value map of formattable objects. Non-null iff named values are used.
1590      * @param dest          Output parameter to receive the result.
1591      *                      The result (string & attributes) is appended to existing contents.
1592      * @param fp            Field position status.
1593      */
format(int msgStart, PluralSelectorContext pluralNumber, Object[] args, Map<String, Object> argsMap, AppendableWrapper dest, FieldPosition fp)1594     private void format(int msgStart, PluralSelectorContext pluralNumber,
1595                         Object[] args, Map<String, Object> argsMap,
1596                         AppendableWrapper dest, FieldPosition fp) {
1597         String msgString=msgPattern.getPatternString();
1598         int prevIndex=msgPattern.getPart(msgStart).getLimit();
1599         for(int i=msgStart+1;; ++i) {
1600             Part part=msgPattern.getPart(i);
1601             Part.Type type=part.getType();
1602             int index=part.getIndex();
1603             dest.append(msgString, prevIndex, index);
1604             if(type==Part.Type.MSG_LIMIT) {
1605                 return;
1606             }
1607             prevIndex=part.getLimit();
1608             if(type==Part.Type.REPLACE_NUMBER) {
1609                 if(pluralNumber.forReplaceNumber) {
1610                     // number-offset was already formatted.
1611                     dest.formatAndAppend(pluralNumber.formatter,
1612                             pluralNumber.number, pluralNumber.numberString);
1613                 } else {
1614                     dest.formatAndAppend(getStockNumberFormatter(), pluralNumber.number);
1615                 }
1616                 continue;
1617             }
1618             if(type!=Part.Type.ARG_START) {
1619                 continue;
1620             }
1621             int argLimit=msgPattern.getLimitPartIndex(i);
1622             ArgType argType=part.getArgType();
1623             part=msgPattern.getPart(++i);
1624             Object arg;
1625             boolean noArg=false;
1626             Object argId=null;
1627             String argName=msgPattern.getSubstring(part);
1628             if(args!=null) {
1629                 int argNumber=part.getValue();  // ARG_NUMBER
1630                 if (dest.attributes != null) {
1631                     // We only need argId if we add it into the attributes.
1632                     argId = Integer.valueOf(argNumber);
1633                 }
1634                 if(0<=argNumber && argNumber<args.length) {
1635                     arg=args[argNumber];
1636                 } else {
1637                     arg=null;
1638                     noArg=true;
1639                 }
1640             } else {
1641                 argId = argName;
1642                 if(argsMap!=null && argsMap.containsKey(argName)) {
1643                     arg=argsMap.get(argName);
1644                 } else {
1645                     arg=null;
1646                     noArg=true;
1647                 }
1648             }
1649             ++i;
1650             int prevDestLength=dest.length;
1651             Format formatter = null;
1652             if (noArg) {
1653                 dest.append("{"+argName+"}");
1654             } else if (arg == null) {
1655                 dest.append("null");
1656             } else if(pluralNumber!=null && pluralNumber.numberArgIndex==(i-2)) {
1657                 if(pluralNumber.offset == 0) {
1658                     // The number was already formatted with this formatter.
1659                     dest.formatAndAppend(pluralNumber.formatter, pluralNumber.number, pluralNumber.numberString);
1660                 } else {
1661                     // Do not use the formatted (number-offset) string for a named argument
1662                     // that formats the number without subtracting the offset.
1663                     dest.formatAndAppend(pluralNumber.formatter, arg);
1664                 }
1665             } else if(cachedFormatters!=null && (formatter=cachedFormatters.get(i - 2))!=null) {
1666                 // Handles all ArgType.SIMPLE, and formatters from setFormat() and its siblings.
1667                 if (    formatter instanceof ChoiceFormat ||
1668                         formatter instanceof PluralFormat ||
1669                         formatter instanceof SelectFormat) {
1670                     // We only handle nested formats here if they were provided via setFormat() or its siblings.
1671                     // Otherwise they are not cached and instead handled below according to argType.
1672                     String subMsgString = formatter.format(arg);
1673                     if (subMsgString.indexOf('{') >= 0 ||
1674                             (subMsgString.indexOf('\'') >= 0 && !msgPattern.jdkAposMode())) {
1675                         MessageFormat subMsgFormat = new MessageFormat(subMsgString, ulocale);
1676                         subMsgFormat.format(0, null, args, argsMap, dest, null);
1677                     } else if (dest.attributes == null) {
1678                         dest.append(subMsgString);
1679                     } else {
1680                         // This formats the argument twice, once above to get the subMsgString
1681                         // and then once more here.
1682                         // It only happens in formatToCharacterIterator()
1683                         // on a complex Format set via setFormat(),
1684                         // and only when the selected subMsgString does not need further formatting.
1685                         // This imitates ICU 4.6 behavior.
1686                         dest.formatAndAppend(formatter, arg);
1687                     }
1688                 } else {
1689                     dest.formatAndAppend(formatter, arg);
1690                 }
1691             } else if(
1692                     argType==ArgType.NONE ||
1693                     (cachedFormatters!=null && cachedFormatters.containsKey(i - 2))) {
1694                 // ArgType.NONE, or
1695                 // any argument which got reset to null via setFormat() or its siblings.
1696                 if (arg instanceof Number) {
1697                     // format number if can
1698                     dest.formatAndAppend(getStockNumberFormatter(), arg);
1699                  } else if (arg instanceof Date) {
1700                     // format a Date if can
1701                     dest.formatAndAppend(getStockDateFormatter(), arg);
1702                 } else {
1703                     dest.append(arg.toString());
1704                 }
1705             } else if(argType==ArgType.CHOICE) {
1706                 if (!(arg instanceof Number)) {
1707                     throw new IllegalArgumentException("'" + arg + "' is not a Number");
1708                 }
1709                 double number = ((Number)arg).doubleValue();
1710                 int subMsgStart=findChoiceSubMessage(msgPattern, i, number);
1711                 formatComplexSubMessage(subMsgStart, null, args, argsMap, dest);
1712             } else if(argType.hasPluralStyle()) {
1713                 if (!(arg instanceof Number)) {
1714                     throw new IllegalArgumentException("'" + arg + "' is not a Number");
1715                 }
1716                 PluralSelectorProvider selector;
1717                 if(argType == ArgType.PLURAL) {
1718                     if (pluralProvider == null) {
1719                         pluralProvider = new PluralSelectorProvider(this, PluralType.CARDINAL);
1720                     }
1721                     selector = pluralProvider;
1722                 } else {
1723                     if (ordinalProvider == null) {
1724                         ordinalProvider = new PluralSelectorProvider(this, PluralType.ORDINAL);
1725                     }
1726                     selector = ordinalProvider;
1727                 }
1728                 Number number = (Number)arg;
1729                 double offset=msgPattern.getPluralOffset(i);
1730                 PluralSelectorContext context =
1731                         new PluralSelectorContext(i, argName, number, offset);
1732                 int subMsgStart=PluralFormat.findSubMessage(
1733                         msgPattern, i, selector, context, number.doubleValue());
1734                 formatComplexSubMessage(subMsgStart, context, args, argsMap, dest);
1735             } else if(argType==ArgType.SELECT) {
1736                 int subMsgStart=SelectFormat.findSubMessage(msgPattern, i, arg.toString());
1737                 formatComplexSubMessage(subMsgStart, null, args, argsMap, dest);
1738             } else {
1739                 // This should never happen.
1740                 throw new IllegalStateException("unexpected argType "+argType);
1741             }
1742             fp = updateMetaData(dest, prevDestLength, fp, argId);
1743             prevIndex=msgPattern.getPart(argLimit).getLimit();
1744             i=argLimit;
1745         }
1746     }
1747 
formatComplexSubMessage( int msgStart, PluralSelectorContext pluralNumber, Object[] args, Map<String, Object> argsMap, AppendableWrapper dest)1748     private void formatComplexSubMessage(
1749             int msgStart, PluralSelectorContext pluralNumber,
1750             Object[] args, Map<String, Object> argsMap,
1751             AppendableWrapper dest) {
1752         if (!msgPattern.jdkAposMode()) {
1753             format(msgStart, pluralNumber, args, argsMap, dest, null);
1754             return;
1755         }
1756         // JDK compatibility mode: (see JDK MessageFormat.format() API docs)
1757         // - remove SKIP_SYNTAX; that is, remove half of the apostrophes
1758         // - if the result string contains an open curly brace '{' then
1759         //   instantiate a temporary MessageFormat object and format again;
1760         //   otherwise just append the result string
1761         String msgString = msgPattern.getPatternString();
1762         String subMsgString;
1763         StringBuilder sb = null;
1764         int prevIndex = msgPattern.getPart(msgStart).getLimit();
1765         for (int i = msgStart;;) {
1766             Part part = msgPattern.getPart(++i);
1767             Part.Type type = part.getType();
1768             int index = part.getIndex();
1769             if (type == Part.Type.MSG_LIMIT) {
1770                 if (sb == null) {
1771                     subMsgString = msgString.substring(prevIndex, index);
1772                 } else {
1773                     subMsgString = sb.append(msgString, prevIndex, index).toString();
1774                 }
1775                 break;
1776             } else if (type == Part.Type.REPLACE_NUMBER || type == Part.Type.SKIP_SYNTAX) {
1777                 if (sb == null) {
1778                     sb = new StringBuilder();
1779                 }
1780                 sb.append(msgString, prevIndex, index);
1781                 if (type == Part.Type.REPLACE_NUMBER) {
1782                     if(pluralNumber.forReplaceNumber) {
1783                         // number-offset was already formatted.
1784                         sb.append(pluralNumber.numberString);
1785                     } else {
1786                         sb.append(getStockNumberFormatter().format(pluralNumber.number));
1787                     }
1788                 }
1789                 prevIndex = part.getLimit();
1790             } else if (type == Part.Type.ARG_START) {
1791                 if (sb == null) {
1792                     sb = new StringBuilder();
1793                 }
1794                 sb.append(msgString, prevIndex, index);
1795                 prevIndex = index;
1796                 i = msgPattern.getLimitPartIndex(i);
1797                 index = msgPattern.getPart(i).getLimit();
1798                 MessagePattern.appendReducedApostrophes(msgString, prevIndex, index, sb);
1799                 prevIndex = index;
1800             }
1801         }
1802         if (subMsgString.indexOf('{') >= 0) {
1803             MessageFormat subMsgFormat = new MessageFormat("", ulocale);
1804             subMsgFormat.applyPattern(subMsgString, MessagePattern.ApostropheMode.DOUBLE_REQUIRED);
1805             subMsgFormat.format(0, null, args, argsMap, dest, null);
1806         } else {
1807             dest.append(subMsgString);
1808         }
1809     }
1810 
1811     /**
1812      * Read as much literal string from the pattern string as possible. This stops
1813      * as soon as it finds an argument, or it reaches the end of the string.
1814      * @param from Index in the pattern string to start from.
1815      * @return A substring from the pattern string representing the longest possible
1816      *         substring with no arguments.
1817      */
getLiteralStringUntilNextArgument(int from)1818     private String getLiteralStringUntilNextArgument(int from) {
1819         StringBuilder b = new StringBuilder();
1820         String msgString=msgPattern.getPatternString();
1821         int prevIndex=msgPattern.getPart(from).getLimit();
1822         for(int i=from+1;; ++i) {
1823             Part part=msgPattern.getPart(i);
1824             Part.Type type=part.getType();
1825             int index=part.getIndex();
1826             b.append(msgString, prevIndex, index);
1827             if(type==Part.Type.ARG_START || type==Part.Type.MSG_LIMIT) {
1828                 return b.toString();
1829             }
1830             assert type==Part.Type.SKIP_SYNTAX || type==Part.Type.INSERT_CHAR :
1831                     "Unexpected Part "+part+" in parsed message.";
1832             prevIndex=part.getLimit();
1833         }
1834     }
1835 
updateMetaData(AppendableWrapper dest, int prevLength, FieldPosition fp, Object argId)1836     private FieldPosition updateMetaData(AppendableWrapper dest, int prevLength,
1837                                          FieldPosition fp, Object argId) {
1838         if (dest.attributes != null && prevLength < dest.length) {
1839             dest.attributes.add(new AttributeAndPosition(argId, prevLength, dest.length));
1840         }
1841         if (fp != null && Field.ARGUMENT.equals(fp.getFieldAttribute())) {
1842             fp.setBeginIndex(prevLength);
1843             fp.setEndIndex(dest.length);
1844             return null;
1845         }
1846         return fp;
1847     }
1848 
1849     // This lives here because ICU4J does not have its own ChoiceFormat class.
1850     /**
1851      * Finds the ChoiceFormat sub-message for the given number.
1852      * @param pattern A MessagePattern.
1853      * @param partIndex the index of the first ChoiceFormat argument style part.
1854      * @param number a number to be mapped to one of the ChoiceFormat argument's intervals
1855      * @return the sub-message start part index.
1856      */
findChoiceSubMessage(MessagePattern pattern, int partIndex, double number)1857     private static int findChoiceSubMessage(MessagePattern pattern, int partIndex, double number) {
1858         int count=pattern.countParts();
1859         int msgStart;
1860         // Iterate over (ARG_INT|DOUBLE, ARG_SELECTOR, message) tuples
1861         // until ARG_LIMIT or end of choice-only pattern.
1862         // Ignore the first number and selector and start the loop on the first message.
1863         partIndex+=2;
1864         for(;;) {
1865             // Skip but remember the current sub-message.
1866             msgStart=partIndex;
1867             partIndex=pattern.getLimitPartIndex(partIndex);
1868             if(++partIndex>=count) {
1869                 // Reached the end of the choice-only pattern.
1870                 // Return with the last sub-message.
1871                 break;
1872             }
1873             Part part=pattern.getPart(partIndex++);
1874             Part.Type type=part.getType();
1875             if(type==Part.Type.ARG_LIMIT) {
1876                 // Reached the end of the ChoiceFormat style.
1877                 // Return with the last sub-message.
1878                 break;
1879             }
1880             // part is an ARG_INT or ARG_DOUBLE
1881             assert type.hasNumericValue();
1882             double boundary=pattern.getNumericValue(part);
1883             // Fetch the ARG_SELECTOR character.
1884             int selectorIndex=pattern.getPatternIndex(partIndex++);
1885             char boundaryChar=pattern.getPatternString().charAt(selectorIndex);
1886             if(boundaryChar=='<' ? !(number>boundary) : !(number>=boundary)) {
1887                 // The number is in the interval between the previous boundary and the current one.
1888                 // Return with the sub-message between them.
1889                 // The !(a>b) and !(a>=b) comparisons are equivalent to
1890                 // (a<=b) and (a<b) except they "catch" NaN.
1891                 break;
1892             }
1893         }
1894         return msgStart;
1895     }
1896 
1897     // Ported from C++ ChoiceFormat::parse().
parseChoiceArgument( MessagePattern pattern, int partIndex, String source, ParsePosition pos)1898     private static double parseChoiceArgument(
1899             MessagePattern pattern, int partIndex,
1900             String source, ParsePosition pos) {
1901         // find the best number (defined as the one with the longest parse)
1902         int start = pos.getIndex();
1903         int furthest = start;
1904         double bestNumber = Double.NaN;
1905         double tempNumber = 0.0;
1906         while (pattern.getPartType(partIndex) != Part.Type.ARG_LIMIT) {
1907             tempNumber = pattern.getNumericValue(pattern.getPart(partIndex));
1908             partIndex += 2;  // skip the numeric part and ignore the ARG_SELECTOR
1909             int msgLimit = pattern.getLimitPartIndex(partIndex);
1910             int len = matchStringUntilLimitPart(pattern, partIndex, msgLimit, source, start);
1911             if (len >= 0) {
1912                 int newIndex = start + len;
1913                 if (newIndex > furthest) {
1914                     furthest = newIndex;
1915                     bestNumber = tempNumber;
1916                     if (furthest == source.length()) {
1917                         break;
1918                     }
1919                 }
1920             }
1921             partIndex = msgLimit + 1;
1922         }
1923         if (furthest == start) {
1924             pos.setErrorIndex(start);
1925         } else {
1926             pos.setIndex(furthest);
1927         }
1928         return bestNumber;
1929     }
1930 
1931     /**
1932      * Matches the pattern string from the end of the partIndex to
1933      * the beginning of the limitPartIndex,
1934      * including all syntax except SKIP_SYNTAX,
1935      * against the source string starting at sourceOffset.
1936      * If they match, returns the length of the source string match.
1937      * Otherwise returns -1.
1938      */
matchStringUntilLimitPart( MessagePattern pattern, int partIndex, int limitPartIndex, String source, int sourceOffset)1939     private static int matchStringUntilLimitPart(
1940             MessagePattern pattern, int partIndex, int limitPartIndex,
1941             String source, int sourceOffset) {
1942         int matchingSourceLength = 0;
1943         String msgString = pattern.getPatternString();
1944         int prevIndex = pattern.getPart(partIndex).getLimit();
1945         for (;;) {
1946             Part part = pattern.getPart(++partIndex);
1947             if (partIndex == limitPartIndex || part.getType() == Part.Type.SKIP_SYNTAX) {
1948                 int index = part.getIndex();
1949                 int length = index - prevIndex;
1950                 if (length != 0 && !source.regionMatches(sourceOffset, msgString, prevIndex, length)) {
1951                     return -1;  // mismatch
1952                 }
1953                 matchingSourceLength += length;
1954                 if (partIndex == limitPartIndex) {
1955                     return matchingSourceLength;
1956                 }
1957                 prevIndex = part.getLimit();  // SKIP_SYNTAX
1958             }
1959         }
1960     }
1961 
1962     /**
1963      * Finds the "other" sub-message.
1964      * @param partIndex the index of the first PluralFormat argument style part.
1965      * @return the "other" sub-message start part index.
1966      */
findOtherSubMessage(int partIndex)1967     private int findOtherSubMessage(int partIndex) {
1968         int count=msgPattern.countParts();
1969         MessagePattern.Part part=msgPattern.getPart(partIndex);
1970         if(part.getType().hasNumericValue()) {
1971             ++partIndex;
1972         }
1973         // Iterate over (ARG_SELECTOR [ARG_INT|ARG_DOUBLE] message) tuples
1974         // until ARG_LIMIT or end of plural-only pattern.
1975         do {
1976             part=msgPattern.getPart(partIndex++);
1977             MessagePattern.Part.Type type=part.getType();
1978             if(type==MessagePattern.Part.Type.ARG_LIMIT) {
1979                 break;
1980             }
1981             assert type==MessagePattern.Part.Type.ARG_SELECTOR;
1982             // part is an ARG_SELECTOR followed by an optional explicit value, and then a message
1983             if(msgPattern.partSubstringMatches(part, "other")) {
1984                 return partIndex;
1985             }
1986             if(msgPattern.getPartType(partIndex).hasNumericValue()) {
1987                 ++partIndex;  // skip the numeric-value part of "=1" etc.
1988             }
1989             partIndex=msgPattern.getLimitPartIndex(partIndex);
1990         } while(++partIndex<count);
1991         return 0;
1992     }
1993 
1994     /**
1995      * Returns the ARG_START index of the first occurrence of the plural number in a sub-message.
1996      * Returns -1 if it is a REPLACE_NUMBER.
1997      * Returns 0 if there is neither.
1998      */
findFirstPluralNumberArg(int msgStart, String argName)1999     private int findFirstPluralNumberArg(int msgStart, String argName) {
2000         for(int i=msgStart+1;; ++i) {
2001             Part part=msgPattern.getPart(i);
2002             Part.Type type=part.getType();
2003             if(type==Part.Type.MSG_LIMIT) {
2004                 return 0;
2005             }
2006             if(type==Part.Type.REPLACE_NUMBER) {
2007                 return -1;
2008             }
2009             if(type==Part.Type.ARG_START) {
2010                 ArgType argType=part.getArgType();
2011                 if(argName.length()!=0 && (argType==ArgType.NONE || argType==ArgType.SIMPLE)) {
2012                     part=msgPattern.getPart(i+1);  // ARG_NUMBER or ARG_NAME
2013                     if(msgPattern.partSubstringMatches(part, argName)) {
2014                         return i;
2015                     }
2016                 }
2017                 i=msgPattern.getLimitPartIndex(i);
2018             }
2019         }
2020     }
2021 
2022     /**
2023      * Mutable input/output values for the PluralSelectorProvider.
2024      * Separate so that it is possible to make MessageFormat Freezable.
2025      */
2026     private static final class PluralSelectorContext {
PluralSelectorContext(int start, String name, Number num, double off)2027         private PluralSelectorContext(int start, String name, Number num, double off) {
2028             startIndex = start;
2029             argName = name;
2030             // number needs to be set even when select() is not called.
2031             // Keep it as a Number/Formattable:
2032             // For format() methods, and to preserve information (e.g., BigDecimal).
2033             if(off == 0) {
2034                 number = num;
2035             } else {
2036                 number = num.doubleValue() - off;
2037             }
2038             offset = off;
2039         }
2040         @Override
toString()2041         public String toString() {
2042             throw new AssertionError("PluralSelectorContext being formatted, rather than its number");
2043         }
2044 
2045         // Input values for plural selection with decimals.
2046         int startIndex;
2047         String argName;
2048         /** argument number - plural offset */
2049         Number number;
2050         double offset;
2051         // Output values for plural selection with decimals.
2052         /** -1 if REPLACE_NUMBER, 0 arg not found, >0 ARG_START index */
2053         int numberArgIndex;
2054         Format formatter;
2055         /** formatted argument number - plural offset */
2056         String numberString;
2057         /** true if number-offset was formatted with the stock number formatter */
2058         boolean forReplaceNumber;
2059     }
2060 
2061     /**
2062      * This provider helps defer instantiation of a PluralRules object
2063      * until we actually need to select a keyword.
2064      * For example, if the number matches an explicit-value selector like "=1"
2065      * we do not need any PluralRules.
2066      */
2067     private static final class PluralSelectorProvider implements PluralFormat.PluralSelector {
PluralSelectorProvider(MessageFormat mf, PluralType type)2068         public PluralSelectorProvider(MessageFormat mf, PluralType type) {
2069             msgFormat = mf;
2070             this.type = type;
2071         }
2072         @Override
select(Object ctx, double number)2073         public String select(Object ctx, double number) {
2074             if(rules == null) {
2075                 rules = PluralRules.forLocale(msgFormat.ulocale, type);
2076             }
2077             // Select a sub-message according to how the number is formatted,
2078             // which is specified in the selected sub-message.
2079             // We avoid this circle by looking at how
2080             // the number is formatted in the "other" sub-message
2081             // which must always be present and usually contains the number.
2082             // Message authors should be consistent across sub-messages.
2083             PluralSelectorContext context = (PluralSelectorContext)ctx;
2084             int otherIndex = msgFormat.findOtherSubMessage(context.startIndex);
2085             context.numberArgIndex = msgFormat.findFirstPluralNumberArg(otherIndex, context.argName);
2086             if(context.numberArgIndex > 0 && msgFormat.cachedFormatters != null) {
2087                 context.formatter = msgFormat.cachedFormatters.get(context.numberArgIndex);
2088             }
2089             if(context.formatter == null) {
2090                 context.formatter = msgFormat.getStockNumberFormatter();
2091                 context.forReplaceNumber = true;
2092             }
2093             assert context.number.doubleValue() == number;  // argument number minus the offset
2094             context.numberString = context.formatter.format(context.number);
2095             if(context.formatter instanceof DecimalFormat) {
2096                 IFixedDecimal dec = ((DecimalFormat)context.formatter).getFixedDecimal(number);
2097                 return rules.select(dec);
2098             } else {
2099                 return rules.select(number);
2100             }
2101         }
2102         private MessageFormat msgFormat;
2103         private PluralRules rules;
2104         private PluralType type;
2105     }
2106 
2107     @SuppressWarnings("unchecked")
format(Object arguments, AppendableWrapper result, FieldPosition fp)2108     private void format(Object arguments, AppendableWrapper result, FieldPosition fp) {
2109         if ((arguments == null || arguments instanceof Map)) {
2110             format(null, (Map<String, Object>)arguments, result, fp);
2111         } else {
2112             format((Object[])arguments, null, result, fp);
2113         }
2114     }
2115 
2116     /**
2117      * Internal routine used by format.
2118      *
2119      * @throws IllegalArgumentException if an argument in the
2120      *         <code>arguments</code> map is not of the type
2121      *         expected by the format element(s) that use it.
2122      */
format(Object[] arguments, Map<String, Object> argsMap, AppendableWrapper dest, FieldPosition fp)2123     private void format(Object[] arguments, Map<String, Object> argsMap,
2124                         AppendableWrapper dest, FieldPosition fp) {
2125         if (arguments != null && msgPattern.hasNamedArguments()) {
2126             throw new IllegalArgumentException(
2127                 "This method is not available in MessageFormat objects " +
2128                 "that use alphanumeric argument names.");
2129         }
2130         format(0, null, arguments, argsMap, dest, fp);
2131     }
2132 
resetPattern()2133     private void resetPattern() {
2134         if (msgPattern != null) {
2135             msgPattern.clear();
2136         }
2137         if (cachedFormatters != null) {
2138             cachedFormatters.clear();
2139         }
2140         customFormatArgStarts = null;
2141     }
2142 
2143     private static final String[] typeList =
2144         { "number", "date", "time", "spellout", "ordinal", "duration" };
2145     private static final int
2146         TYPE_NUMBER = 0,
2147         TYPE_DATE = 1,
2148         TYPE_TIME = 2,
2149         TYPE_SPELLOUT = 3,
2150         TYPE_ORDINAL = 4,
2151         TYPE_DURATION = 5;
2152 
2153     private static final String[] modifierList =
2154         {"", "currency", "percent", "integer"};
2155 
2156     private static final int
2157         MODIFIER_EMPTY = 0,
2158         MODIFIER_CURRENCY = 1,
2159         MODIFIER_PERCENT = 2,
2160         MODIFIER_INTEGER = 3;
2161 
2162     private static final String[] dateModifierList =
2163         {"", "short", "medium", "long", "full"};
2164 
2165     private static final int
2166         DATE_MODIFIER_EMPTY = 0,
2167         DATE_MODIFIER_SHORT = 1,
2168         DATE_MODIFIER_MEDIUM = 2,
2169         DATE_MODIFIER_LONG = 3,
2170         DATE_MODIFIER_FULL = 4;
2171 
dateTimeFormatForPatternOrSkeleton(String style)2172     Format dateTimeFormatForPatternOrSkeleton(String style) {
2173         // Ignore leading whitespace when looking for "::", the skeleton signal sequence
2174         int i = PatternProps.skipWhiteSpace(style, 0);
2175         if (style.regionMatches(i, "::", 0, 2)) { // Skeleton
2176             return DateFormat.getInstanceForSkeleton(style.substring(i + 2), ulocale);
2177         } else { // Pattern
2178             return new SimpleDateFormat(style, ulocale);
2179         }
2180     }
2181 
2182     // Creates an appropriate Format object for the type and style passed.
2183     // Both arguments cannot be null.
createAppropriateFormat(String type, String style)2184     private Format createAppropriateFormat(String type, String style) {
2185         Format newFormat = null;
2186         int subformatType  = findKeyword(type, typeList);
2187         switch (subformatType){
2188         case TYPE_NUMBER:
2189             switch (findKeyword(style, modifierList)) {
2190             case MODIFIER_EMPTY:
2191                 newFormat = NumberFormat.getInstance(ulocale);
2192                 break;
2193             case MODIFIER_CURRENCY:
2194                 newFormat = NumberFormat.getCurrencyInstance(ulocale);
2195                 break;
2196             case MODIFIER_PERCENT:
2197                 newFormat = NumberFormat.getPercentInstance(ulocale);
2198                 break;
2199             case MODIFIER_INTEGER:
2200                 newFormat = NumberFormat.getIntegerInstance(ulocale);
2201                 break;
2202             default: // pattern or skeleton
2203                 // Ignore leading whitespace when looking for "::", the skeleton signal sequence
2204                 int i = PatternProps.skipWhiteSpace(style, 0);
2205                 if (style.regionMatches(i, "::", 0, 2)) {
2206                     // Skeleton
2207                     newFormat = NumberFormatter.forSkeleton(style.substring(i + 2)).locale(ulocale).toFormat();
2208                 } else {
2209                     // Pattern
2210                     newFormat = new DecimalFormat(style, new DecimalFormatSymbols(ulocale));
2211                 }
2212                 break;
2213             }
2214             break;
2215         case TYPE_DATE:
2216             switch (findKeyword(style, dateModifierList)) {
2217             case DATE_MODIFIER_EMPTY:
2218                 newFormat = DateFormat.getDateInstance(DateFormat.DEFAULT, ulocale);
2219                 break;
2220             case DATE_MODIFIER_SHORT:
2221                 newFormat = DateFormat.getDateInstance(DateFormat.SHORT, ulocale);
2222                 break;
2223             case DATE_MODIFIER_MEDIUM:
2224                 newFormat = DateFormat.getDateInstance(DateFormat.DEFAULT, ulocale);
2225                 break;
2226             case DATE_MODIFIER_LONG:
2227                 newFormat = DateFormat.getDateInstance(DateFormat.LONG, ulocale);
2228                 break;
2229             case DATE_MODIFIER_FULL:
2230                 newFormat = DateFormat.getDateInstance(DateFormat.FULL, ulocale);
2231                 break;
2232             default: // pattern or skeleton
2233                 newFormat = dateTimeFormatForPatternOrSkeleton(style);
2234                 break;
2235             }
2236             break;
2237         case TYPE_TIME:
2238             switch (findKeyword(style, dateModifierList)) {
2239             case DATE_MODIFIER_EMPTY:
2240                 newFormat = DateFormat.getTimeInstance(DateFormat.DEFAULT, ulocale);
2241                 break;
2242             case DATE_MODIFIER_SHORT:
2243                 newFormat = DateFormat.getTimeInstance(DateFormat.SHORT, ulocale);
2244                 break;
2245             case DATE_MODIFIER_MEDIUM:
2246                 newFormat = DateFormat.getTimeInstance(DateFormat.DEFAULT, ulocale);
2247                 break;
2248             case DATE_MODIFIER_LONG:
2249                 newFormat = DateFormat.getTimeInstance(DateFormat.LONG, ulocale);
2250                 break;
2251             case DATE_MODIFIER_FULL:
2252                 newFormat = DateFormat.getTimeInstance(DateFormat.FULL, ulocale);
2253                 break;
2254             default: // pattern or skeleton
2255                 newFormat = dateTimeFormatForPatternOrSkeleton(style);
2256                 break;
2257             }
2258             break;
2259         case TYPE_SPELLOUT:
2260             {
2261                 RuleBasedNumberFormat rbnf = new RuleBasedNumberFormat(ulocale,
2262                         RuleBasedNumberFormat.SPELLOUT);
2263                 String ruleset = style.trim();
2264                 if (ruleset.length() != 0) {
2265                     try {
2266                         rbnf.setDefaultRuleSet(ruleset);
2267                     }
2268                     catch (Exception e) {
2269                         // warn invalid ruleset
2270                     }
2271                 }
2272                 newFormat = rbnf;
2273             }
2274             break;
2275         case TYPE_ORDINAL:
2276             {
2277                 RuleBasedNumberFormat rbnf = new RuleBasedNumberFormat(ulocale,
2278                         RuleBasedNumberFormat.ORDINAL);
2279                 String ruleset = style.trim();
2280                 if (ruleset.length() != 0) {
2281                     try {
2282                         rbnf.setDefaultRuleSet(ruleset);
2283                     }
2284                     catch (Exception e) {
2285                         // warn invalid ruleset
2286                     }
2287                 }
2288                 newFormat = rbnf;
2289             }
2290             break;
2291         case TYPE_DURATION:
2292             {
2293                 RuleBasedNumberFormat rbnf = new RuleBasedNumberFormat(ulocale,
2294                         RuleBasedNumberFormat.DURATION);
2295                 String ruleset = style.trim();
2296                 if (ruleset.length() != 0) {
2297                     try {
2298                         rbnf.setDefaultRuleSet(ruleset);
2299                     }
2300                     catch (Exception e) {
2301                         // warn invalid ruleset
2302                     }
2303                 }
2304                 newFormat = rbnf;
2305             }
2306             break;
2307         default:
2308             throw new IllegalArgumentException("Unknown format type \"" + type + "\"");
2309         }
2310         return newFormat;
2311     }
2312 
2313     private static final Locale rootLocale = new Locale("");  // Locale.ROOT only @since 1.6
2314 
findKeyword(String s, String[] list)2315     private static final int findKeyword(String s, String[] list) {
2316         s = PatternProps.trimWhiteSpace(s).toLowerCase(rootLocale);
2317         for (int i = 0; i < list.length; ++i) {
2318             if (s.equals(list[i]))
2319                 return i;
2320         }
2321         return -1;
2322     }
2323 
2324     /**
2325      * Custom serialization, new in ICU 4.8.
2326      * We do not want to use default serialization because we only have a small
2327      * amount of persistent state which is better expressed explicitly
2328      * rather than via writing field objects.
2329      * @param out The output stream.
2330      * @serialData Writes the locale as a BCP 47 language tag string,
2331      * the MessagePattern.ApostropheMode as an object,
2332      * and the pattern string (null if none was applied).
2333      * Followed by an int with the number of (int formatIndex, Object formatter) pairs,
2334      * and that many such pairs, corresponding to previous setFormat() calls for custom formats.
2335      * Followed by an int with the number of (int, Object) pairs,
2336      * and that many such pairs, for future (post-ICU 4.8) extension of the serialization format.
2337      */
writeObject(java.io.ObjectOutputStream out)2338     private void writeObject(java.io.ObjectOutputStream out) throws IOException {
2339         out.defaultWriteObject();
2340         // ICU 4.8 custom serialization.
2341         // locale as a BCP 47 language tag
2342         out.writeObject(ulocale.toLanguageTag());
2343         // ApostropheMode
2344         if (msgPattern == null) {
2345             msgPattern = new MessagePattern();
2346         }
2347         out.writeObject(msgPattern.getApostropheMode());
2348         // message pattern string
2349         out.writeObject(msgPattern.getPatternString());
2350         // custom formatters
2351         if (customFormatArgStarts == null || customFormatArgStarts.isEmpty()) {
2352             out.writeInt(0);
2353         } else {
2354             out.writeInt(customFormatArgStarts.size());
2355             int formatIndex = 0;
2356             for (int partIndex = 0; (partIndex = nextTopLevelArgStart(partIndex)) >= 0;) {
2357                 if (customFormatArgStarts.contains(partIndex)) {
2358                     out.writeInt(formatIndex);
2359                     out.writeObject(cachedFormatters.get(partIndex));
2360                 }
2361                 ++formatIndex;
2362             }
2363         }
2364         // number of future (int, Object) pairs
2365         out.writeInt(0);
2366     }
2367 
2368     /**
2369      * Custom deserialization, new in ICU 4.8. See comments on writeObject().
2370      * @throws InvalidObjectException if the objects read from the stream is invalid.
2371      */
readObject(ObjectInputStream in)2372     private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
2373         in.defaultReadObject();
2374         // ICU 4.8 custom deserialization.
2375         String languageTag = (String)in.readObject();
2376         ulocale = ULocale.forLanguageTag(languageTag);
2377         MessagePattern.ApostropheMode aposMode = (MessagePattern.ApostropheMode)in.readObject();
2378         if (msgPattern == null || aposMode != msgPattern.getApostropheMode()) {
2379             msgPattern = new MessagePattern(aposMode);
2380         }
2381         String msg = (String)in.readObject();
2382         if (msg != null) {
2383             applyPattern(msg);
2384         }
2385         // custom formatters
2386         for (int numFormatters = in.readInt(); numFormatters > 0; --numFormatters) {
2387             int formatIndex = in.readInt();
2388             Format formatter = (Format)in.readObject();
2389             setFormat(formatIndex, formatter);
2390         }
2391         // skip future (int, Object) pairs
2392         for (int numPairs = in.readInt(); numPairs > 0; --numPairs) {
2393             in.readInt();
2394             in.readObject();
2395         }
2396     }
2397 
cacheExplicitFormats()2398     private void cacheExplicitFormats() {
2399         if (cachedFormatters != null) {
2400             cachedFormatters.clear();
2401         }
2402         customFormatArgStarts = null;
2403         // The last two "parts" can at most be ARG_LIMIT and MSG_LIMIT
2404         // which we need not examine.
2405         int limit = msgPattern.countParts() - 2;
2406         // This loop starts at part index 1 because we do need to examine
2407         // ARG_START parts. (But we can ignore the MSG_START.)
2408         for(int i=1; i < limit; ++i) {
2409             Part part = msgPattern.getPart(i);
2410             if(part.getType()!=Part.Type.ARG_START) {
2411                 continue;
2412             }
2413             ArgType argType=part.getArgType();
2414             if(argType != ArgType.SIMPLE) {
2415                 continue;
2416             }
2417             int index = i;
2418             i += 2;
2419             String explicitType = msgPattern.getSubstring(msgPattern.getPart(i++));
2420             String style = "";
2421             if ((part = msgPattern.getPart(i)).getType() == MessagePattern.Part.Type.ARG_STYLE) {
2422                 style = msgPattern.getSubstring(part);
2423                 ++i;
2424             }
2425             Format formatter = createAppropriateFormat(explicitType, style);
2426             setArgStartFormat(index, formatter);
2427         }
2428     }
2429 
2430     /**
2431      * Sets a formatter for a MessagePattern ARG_START part index.
2432      */
setArgStartFormat(int argStart, Format formatter)2433     private void setArgStartFormat(int argStart, Format formatter) {
2434         if (cachedFormatters == null) {
2435             cachedFormatters = new HashMap<>();
2436         }
2437         cachedFormatters.put(argStart, formatter);
2438     }
2439 
2440     /**
2441      * Sets a custom formatter for a MessagePattern ARG_START part index.
2442      * "Custom" formatters are provided by the user via setFormat() or similar APIs.
2443      */
setCustomArgStartFormat(int argStart, Format formatter)2444     private void setCustomArgStartFormat(int argStart, Format formatter) {
2445         setArgStartFormat(argStart, formatter);
2446         if (customFormatArgStarts == null) {
2447             customFormatArgStarts = new HashSet<>();
2448         }
2449         customFormatArgStarts.add(argStart);
2450     }
2451 
2452     private static final char SINGLE_QUOTE = '\'';
2453     private static final char CURLY_BRACE_LEFT = '{';
2454     private static final char CURLY_BRACE_RIGHT = '}';
2455 
2456     private static final int STATE_INITIAL = 0;
2457     private static final int STATE_SINGLE_QUOTE = 1;
2458     private static final int STATE_IN_QUOTE = 2;
2459     private static final int STATE_MSG_ELEMENT = 3;
2460 
2461     /**
2462      * <strong>[icu]</strong> Converts an 'apostrophe-friendly' pattern into a standard
2463      * pattern.
2464      * <em>This is obsolete for ICU 4.8 and higher MessageFormat pattern strings.</em>
2465      * It can still be useful together with {@link java.text.MessageFormat}.
2466      *
2467      * <p>See the class description for more about apostrophes and quoting,
2468      * and differences between ICU and {@link java.text.MessageFormat}.
2469      *
2470      * <p>{@link java.text.MessageFormat} and ICU 4.6 and earlier MessageFormat
2471      * treat all ASCII apostrophes as
2472      * quotes, which is problematic in some languages, e.g.
2473      * French, where apostrophe is commonly used.  This utility
2474      * assumes that only an unpaired apostrophe immediately before
2475      * a brace is a true quote.  Other unpaired apostrophes are paired,
2476      * and the resulting standard pattern string is returned.
2477      *
2478      * <p><b>Note</b>: It is not guaranteed that the returned pattern
2479      * is indeed a valid pattern.  The only effect is to convert
2480      * between patterns having different quoting semantics.
2481      *
2482      * <p><b>Note</b>: This method only works on top-level messageText,
2483      * not messageText nested inside a complexArg.
2484      *
2485      * @param pattern the 'apostrophe-friendly' pattern to convert
2486      * @return the standard equivalent of the original pattern
2487      */
autoQuoteApostrophe(String pattern)2488     public static String autoQuoteApostrophe(String pattern) {
2489         StringBuilder buf = new StringBuilder(pattern.length() * 2);
2490         int state = STATE_INITIAL;
2491         int braceCount = 0;
2492         for (int i = 0, j = pattern.length(); i < j; ++i) {
2493             char c = pattern.charAt(i);
2494             switch (state) {
2495             case STATE_INITIAL:
2496                 switch (c) {
2497                 case SINGLE_QUOTE:
2498                     state = STATE_SINGLE_QUOTE;
2499                     break;
2500                 case CURLY_BRACE_LEFT:
2501                     state = STATE_MSG_ELEMENT;
2502                     ++braceCount;
2503                     break;
2504                 }
2505                 break;
2506             case STATE_SINGLE_QUOTE:
2507                 switch (c) {
2508                 case SINGLE_QUOTE:
2509                     state = STATE_INITIAL;
2510                     break;
2511                 case CURLY_BRACE_LEFT:
2512                 case CURLY_BRACE_RIGHT:
2513                     state = STATE_IN_QUOTE;
2514                     break;
2515                 default:
2516                     buf.append(SINGLE_QUOTE);
2517                     state = STATE_INITIAL;
2518                     break;
2519                 }
2520                 break;
2521             case STATE_IN_QUOTE:
2522                 switch (c) {
2523                 case SINGLE_QUOTE:
2524                     state = STATE_INITIAL;
2525                     break;
2526                 }
2527                 break;
2528             case STATE_MSG_ELEMENT:
2529                 switch (c) {
2530                 case CURLY_BRACE_LEFT:
2531                     ++braceCount;
2532                     break;
2533                 case CURLY_BRACE_RIGHT:
2534                     if (--braceCount == 0) {
2535                         state = STATE_INITIAL;
2536                     }
2537                     break;
2538                 }
2539                 break;
2540             ///CLOVER:OFF
2541             default: // Never happens.
2542                 break;
2543             ///CLOVER:ON
2544             }
2545             buf.append(c);
2546         }
2547         // End of scan
2548         if (state == STATE_SINGLE_QUOTE || state == STATE_IN_QUOTE) {
2549             buf.append(SINGLE_QUOTE);
2550         }
2551         return new String(buf);
2552     }
2553 
2554     /**
2555      * Convenience wrapper for Appendable, tracks the result string length.
2556      * Also, Appendable throws IOException, and we turn that into a RuntimeException
2557      * so that we need no throws clauses.
2558      */
2559     private static final class AppendableWrapper {
AppendableWrapper(StringBuilder sb)2560         public AppendableWrapper(StringBuilder sb) {
2561             app = sb;
2562             length = sb.length();
2563             attributes = null;
2564         }
2565 
AppendableWrapper(StringBuffer sb)2566         public AppendableWrapper(StringBuffer sb) {
2567             app = sb;
2568             length = sb.length();
2569             attributes = null;
2570         }
2571 
useAttributes()2572         public void useAttributes() {
2573             attributes = new ArrayList<>();
2574         }
2575 
append(CharSequence s)2576         public void append(CharSequence s) {
2577             try {
2578                 app.append(s);
2579                 length += s.length();
2580             } catch(IOException e) {
2581                 throw new ICUUncheckedIOException(e);
2582             }
2583         }
2584 
append(CharSequence s, int start, int limit)2585         public void append(CharSequence s, int start, int limit) {
2586             try {
2587                 app.append(s, start, limit);
2588                 length += limit - start;
2589             } catch(IOException e) {
2590                 throw new ICUUncheckedIOException(e);
2591             }
2592         }
2593 
append(CharacterIterator iterator)2594         public void append(CharacterIterator iterator) {
2595             length += append(app, iterator);
2596         }
2597 
append(Appendable result, CharacterIterator iterator)2598         public static int append(Appendable result, CharacterIterator iterator) {
2599             try {
2600                 int start = iterator.getBeginIndex();
2601                 int limit = iterator.getEndIndex();
2602                 int length = limit - start;
2603                 if (start < limit) {
2604                     result.append(iterator.first());
2605                     while (++start < limit) {
2606                         result.append(iterator.next());
2607                     }
2608                 }
2609                 return length;
2610             } catch(IOException e) {
2611                 throw new ICUUncheckedIOException(e);
2612             }
2613         }
2614 
formatAndAppend(Format formatter, Object arg)2615         public void formatAndAppend(Format formatter, Object arg) {
2616             if (attributes == null) {
2617                 append(formatter.format(arg));
2618             } else {
2619                 AttributedCharacterIterator formattedArg = formatter.formatToCharacterIterator(arg);
2620                 int prevLength = length;
2621                 append(formattedArg);
2622                 // Copy all of the attributes from formattedArg to our attributes list.
2623                 formattedArg.first();
2624                 int start = formattedArg.getIndex();  // Should be 0 but might not be.
2625                 int limit = formattedArg.getEndIndex();  // == start + length - prevLength
2626                 int offset = prevLength - start;  // Adjust attribute indexes for the result string.
2627                 while (start < limit) {
2628                     Map<Attribute, Object> map = formattedArg.getAttributes();
2629                     int runLimit = formattedArg.getRunLimit();
2630                     if (map.size() != 0) {
2631                         for (Map.Entry<Attribute, Object> entry : map.entrySet()) {
2632                            attributes.add(
2633                                new AttributeAndPosition(
2634                                    entry.getKey(), entry.getValue(),
2635                                    offset + start, offset + runLimit));
2636                         }
2637                     }
2638                     start = runLimit;
2639                     formattedArg.setIndex(start);
2640                 }
2641             }
2642         }
2643 
formatAndAppend(Format formatter, Object arg, String argString)2644         public void formatAndAppend(Format formatter, Object arg, String argString) {
2645             if (attributes == null && argString != null) {
2646                 append(argString);
2647             } else {
2648                 formatAndAppend(formatter, arg);
2649             }
2650         }
2651 
2652         private Appendable app;
2653         private int length;
2654         private List<AttributeAndPosition> attributes;
2655     }
2656 
2657     private static final class AttributeAndPosition {
2658         /**
2659          * Defaults the field to Field.ARGUMENT.
2660          */
AttributeAndPosition(Object fieldValue, int startIndex, int limitIndex)2661         public AttributeAndPosition(Object fieldValue, int startIndex, int limitIndex) {
2662             init(Field.ARGUMENT, fieldValue, startIndex, limitIndex);
2663         }
2664 
AttributeAndPosition(Attribute field, Object fieldValue, int startIndex, int limitIndex)2665         public AttributeAndPosition(Attribute field, Object fieldValue, int startIndex, int limitIndex) {
2666             init(field, fieldValue, startIndex, limitIndex);
2667         }
2668 
init(Attribute field, Object fieldValue, int startIndex, int limitIndex)2669         public void init(Attribute field, Object fieldValue, int startIndex, int limitIndex) {
2670             key = field;
2671             value = fieldValue;
2672             start = startIndex;
2673             limit = limitIndex;
2674         }
2675 
2676         private Attribute key;
2677         private Object value;
2678         private int start;
2679         private int limit;
2680     }
2681 }
2682