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