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