]> gitweb.fperrin.net Git - Dictionary.git/blob - jars/icu4j-4_2_1-src/src/com/ibm/icu/text/MessageFormat.java
icu4jsrc
[Dictionary.git] / jars / icu4j-4_2_1-src / src / com / ibm / icu / text / MessageFormat.java
1 //##header\r
2 /*\r
3 **********************************************************************\r
4 * Copyright (c) 2004-2009, International Business Machines\r
5 * Corporation and others.  All Rights Reserved.\r
6 **********************************************************************\r
7 * Author: Alan Liu\r
8 * Created: April 6, 2004\r
9 * Since: ICU 3.0\r
10 **********************************************************************\r
11 */\r
12 package com.ibm.icu.text;\r
13 \r
14 import java.io.IOException;\r
15 import java.io.InvalidObjectException;\r
16 import java.io.ObjectInputStream;\r
17 import java.text.ChoiceFormat;\r
18 import java.text.FieldPosition;\r
19 import java.text.Format;\r
20 import java.text.ParseException;\r
21 import java.text.ParsePosition;\r
22 import java.util.Date;\r
23 import java.util.HashSet;\r
24 import java.util.Iterator;\r
25 import java.util.List;\r
26 import java.util.Locale;\r
27 import java.util.Map;\r
28 import java.util.HashMap;\r
29 import java.util.Set;\r
30 //#if defined(FOUNDATION10) || defined(J2SE13)\r
31 //#else\r
32 import java.text.AttributedCharacterIterator;\r
33 import java.text.AttributedString;\r
34 import java.util.ArrayList;\r
35 import java.text.CharacterIterator;\r
36 //#endif\r
37 \r
38 import com.ibm.icu.impl.Utility;\r
39 import com.ibm.icu.text.UnicodeSet;\r
40 import com.ibm.icu.util.ULocale;\r
41 \r
42 /**\r
43  * <code>MessageFormat</code> provides a means to produce concatenated\r
44  * messages in language-neutral way. Use this to construct messages\r
45  * displayed for end users.\r
46  *\r
47  * <p>\r
48  * <code>MessageFormat</code> takes a set of objects, formats them, then\r
49  * inserts the formatted strings into the pattern at the appropriate places.\r
50  *\r
51  * <p>\r
52  * <strong>Note:</strong>\r
53  * <code>MessageFormat</code> differs from the other <code>Format</code>\r
54  * classes in that you create a <code>MessageFormat</code> object with one\r
55  * of its constructors (not with a <code>getInstance</code> style factory\r
56  * method). The factory methods aren't necessary because <code>MessageFormat</code>\r
57  * itself doesn't implement locale specific behavior. Any locale specific\r
58  * behavior is defined by the pattern that you provide as well as the\r
59  * subformats used for inserted arguments.\r
60  *\r
61  * <p>\r
62  * <strong>Note:</strong>\r
63  * In ICU 3.8 MessageFormat supports named arguments.  If a named argument\r
64  * is used, all arguments must be named.  Names start with a character in\r
65  * <code>:ID_START:</code> and continue with characters in <code>:ID_CONTINUE:</code>,\r
66  * in particular they do not start with a digit.  If named arguments\r
67  * are used, {@link #usesNamedArguments()} will return true.\r
68  * <p>\r
69  * The other new APIs supporting named arguments are\r
70  * {@link #setFormatsByArgumentName(Map)},\r
71  * {@link #setFormatByArgumentName(String, Format)},\r
72  * {@link #format(Map, StringBuffer, FieldPosition)},\r
73  * {@link #format(String, Map)}, {@link #parseToMap(String, ParsePosition)},\r
74  * and {@link #parseToMap(String)}.  These APIs are all compatible\r
75  * with patterns that do not used named arguments-- in these cases\r
76  * the keys in the input or output <code>Map</code>s use\r
77  * <code>String</code>s that name the argument indices, e.g. "0",\r
78  * "1", "2"... etc.\r
79  * <p>\r
80  * When named arguments are used, certain APIs on Message that take or\r
81  * return arrays will throw an exception, since it is not possible to\r
82  * identify positions in an array using a name.  These APIs are {@link\r
83  * #setFormatsByArgumentIndex(Format[])}, {@link #getFormatsByArgumentIndex()},\r
84  * {@link #format(Object[], StringBuffer, FieldPosition)},\r
85  * {@link #format(String, Object[])},{@link #parse(String, ParsePosition)},\r
86  * and {@link #parse(String)}.\r
87  * These APIs all have corresponding new versions as listed above.\r
88  * <p>\r
89 \r
90  * The API {@link #format(Object, StringBuffer, FieldPosition)} has\r
91  * been modified so that the <code>Object</code> argument can be\r
92  * either an <code>Object</code> array or a <code>Map</code>.  If this\r
93  * format uses named arguments, this argument must not be an\r
94  * <code>Object</code> array otherwise an exception will be thrown.\r
95  * If the argument is a <code>Map</code> it can be used with Strings that\r
96  * represent indices as described above.\r
97  *\r
98  * <h4><a name="patterns">Patterns and Their Interpretation</a></h4>\r
99  *\r
100  * <code>MessageFormat</code> uses patterns of the following form:\r
101  * <blockquote><pre>\r
102  * <i>MessageFormatPattern:</i>\r
103  *         <i>String</i>\r
104  *         <i>MessageFormatPattern</i> <i>FormatElement</i> <i>String</i>\r
105  *\r
106  * <i>FormatElement:</i>\r
107  *         { <i>ArgumentIndexOrName</i> }\r
108  *         { <i>ArgumentIndexOrName</i> , <i>FormatType</i> }\r
109  *         { <i>ArgumentIndexOrName</i> , <i>FormatType</i> , <i>FormatStyle</i> }\r
110  *\r
111  * <i>ArgumentIndexOrName: one of </i>\r
112  *         ['0'-'9']+\r
113  *         [:ID_START:][:ID_CONTINUE:]*\r
114  *\r
115  * <i>FormatType: one of </i>\r
116  *         number date time choice\r
117  *\r
118  * <i>FormatStyle:</i>\r
119  *         short\r
120  *         medium\r
121  *         long\r
122  *         full\r
123  *         integer\r
124  *         currency\r
125  *         percent\r
126  *         <i>SubformatPattern</i>\r
127  *\r
128  * <i>String:</i>\r
129  *         <i>StringPart<sub>opt</sub></i>\r
130  *         <i>String</i> <i>StringPart</i>\r
131  *\r
132  * <i>StringPart:</i>\r
133  *         ''\r
134  *         ' <i>QuotedString</i> '\r
135  *         <i>UnquotedString</i>\r
136  *\r
137  * <i>SubformatPattern:</i>\r
138  *         <i>SubformatPatternPart<sub>opt</sub></i>\r
139  *         <i>SubformatPattern</i> <i>SubformatPatternPart</i>\r
140  *\r
141  * <i>SubFormatPatternPart:</i>\r
142  *         ' <i>QuotedPattern</i> '\r
143  *         <i>UnquotedPattern</i>\r
144  * </pre></blockquote>\r
145  *\r
146  * <p>\r
147  * Within a <i>String</i>, <code>"''"</code> represents a single\r
148  * quote. A <i>QuotedString</i> can contain arbitrary characters\r
149  * except single quotes; the surrounding single quotes are removed.\r
150  * An <i>UnquotedString</i> can contain arbitrary characters\r
151  * except single quotes and left curly brackets. Thus, a string that\r
152  * should result in the formatted message "'{0}'" can be written as\r
153  * <code>"'''{'0}''"</code> or <code>"'''{0}'''"</code>.\r
154  * <p>\r
155  * Within a <i>SubformatPattern</i>, different rules apply.\r
156  * A <i>QuotedPattern</i> can contain arbitrary characters\r
157  * except single quotes; but the surrounding single quotes are\r
158  * <strong>not</strong> removed, so they may be interpreted by the\r
159  * subformat. For example, <code>"{1,number,$'#',##}"</code> will\r
160  * produce a number format with the pound-sign quoted, with a result\r
161  * such as: "$#31,45".\r
162  * An <i>UnquotedPattern</i> can contain arbitrary characters\r
163  * except single quotes, but curly braces within it must be balanced.\r
164  * For example, <code>"ab {0} de"</code> and <code>"ab '}' de"</code>\r
165  * are valid subformat patterns, but <code>"ab {0'}' de"</code> and\r
166  * <code>"ab } de"</code> are not.\r
167  * <p>\r
168  * <dl><dt><b>Warning:</b><dd>The rules for using quotes within message\r
169  * format patterns unfortunately have shown to be somewhat confusing.\r
170  * In particular, it isn't always obvious to localizers whether single\r
171  * quotes need to be doubled or not. Make sure to inform localizers about\r
172  * the rules, and tell them (for example, by using comments in resource\r
173  * bundle source files) which strings will be processed by MessageFormat.\r
174  * Note that localizers may need to use single quotes in translated\r
175  * strings where the original version doesn't have them.\r
176  * <br>Note also that the simplest way to avoid the problem is to\r
177  * use the real apostrophe (single quote) character \u2019 (') for\r
178  * human-readable text, and to use the ASCII apostrophe (\u0027 ' )\r
179  * only in program syntax, like quoting in MessageFormat.\r
180  * See the annotations for U+0027 Apostrophe in The Unicode Standard.</p>\r
181  * </dl>\r
182  * <p>\r
183  * The <i>ArgumentIndex</i> value is a non-negative integer written\r
184  * using the digits '0' through '9', and represents an index into the\r
185  * <code>arguments</code> array passed to the <code>format</code> methods\r
186  * or the result array returned by the <code>parse</code> methods.\r
187  * <p>\r
188  * The <i>FormatType</i> and <i>FormatStyle</i> values are used to create\r
189  * a <code>Format</code> instance for the format element. The following\r
190  * table shows how the values map to Format instances. Combinations not\r
191  * shown in the table are illegal. A <i>SubformatPattern</i> must\r
192  * be a valid pattern string for the Format subclass used.\r
193  * <p>\r
194  * <table border=1>\r
195  *    <tr>\r
196  *       <th>Format Type\r
197  *       <th>Format Style\r
198  *       <th>Subformat Created\r
199  *    <tr>\r
200  *       <td colspan=2><i>(none)</i>\r
201  *       <td><code>null</code>\r
202  *    <tr>\r
203  *       <td rowspan=5><code>number</code>\r
204  *       <td><i>(none)</i>\r
205  *       <td><code>NumberFormat.getInstance(getLocale())</code>\r
206  *    <tr>\r
207  *       <td><code>integer</code>\r
208  *       <td><code>NumberFormat.getIntegerInstance(getLocale())</code>\r
209  *    <tr>\r
210  *       <td><code>currency</code>\r
211  *       <td><code>NumberFormat.getCurrencyInstance(getLocale())</code>\r
212  *    <tr>\r
213  *       <td><code>percent</code>\r
214  *       <td><code>NumberFormat.getPercentInstance(getLocale())</code>\r
215  *    <tr>\r
216  *       <td><i>SubformatPattern</i>\r
217  *       <td><code>new DecimalFormat(subformatPattern, new DecimalFormatSymbols(getLocale()))</code>\r
218  *    <tr>\r
219  *       <td rowspan=6><code>date</code>\r
220  *       <td><i>(none)</i>\r
221  *       <td><code>DateFormat.getDateInstance(DateFormat.DEFAULT, getLocale())</code>\r
222  *    <tr>\r
223  *       <td><code>short</code>\r
224  *       <td><code>DateFormat.getDateInstance(DateFormat.SHORT, getLocale())</code>\r
225  *    <tr>\r
226  *       <td><code>medium</code>\r
227  *       <td><code>DateFormat.getDateInstance(DateFormat.DEFAULT, getLocale())</code>\r
228  *    <tr>\r
229  *       <td><code>long</code>\r
230  *       <td><code>DateFormat.getDateInstance(DateFormat.LONG, getLocale())</code>\r
231  *    <tr>\r
232  *       <td><code>full</code>\r
233  *       <td><code>DateFormat.getDateInstance(DateFormat.FULL, getLocale())</code>\r
234  *    <tr>\r
235  *       <td><i>SubformatPattern</i>\r
236  *       <td><code>new SimpleDateFormat(subformatPattern, getLocale())\r
237  *    <tr>\r
238  *       <td rowspan=6><code>time</code>\r
239  *       <td><i>(none)</i>\r
240  *       <td><code>DateFormat.getTimeInstance(DateFormat.DEFAULT, getLocale())</code>\r
241  *    <tr>\r
242  *       <td><code>short</code>\r
243  *       <td><code>DateFormat.getTimeInstance(DateFormat.SHORT, getLocale())</code>\r
244  *    <tr>\r
245  *       <td><code>medium</code>\r
246  *       <td><code>DateFormat.getTimeInstance(DateFormat.DEFAULT, getLocale())</code>\r
247  *    <tr>\r
248  *       <td><code>long</code>\r
249  *       <td><code>DateFormat.getTimeInstance(DateFormat.LONG, getLocale())</code>\r
250  *    <tr>\r
251  *       <td><code>full</code>\r
252  *       <td><code>DateFormat.getTimeInstance(DateFormat.FULL, getLocale())</code>\r
253  *    <tr>\r
254  *       <td><i>SubformatPattern</i>\r
255  *       <td><code>new SimpleDateFormat(subformatPattern, getLocale())\r
256  *    <tr>\r
257  *       <td><code>choice</code>\r
258  *       <td><i>SubformatPattern</i>\r
259  *       <td><code>new ChoiceFormat(subformatPattern)</code>\r
260  *    <tr>\r
261  *       <td><code>spellout</code>\r
262  *       <td><i>Ruleset name (optional)</i>\r
263  *       <td><code>new RuleBasedNumberFormat(getLocale(), RuleBasedNumberFormat.SPELLOUT)<br/>&nbsp;&nbsp;&nbsp;&nbsp;.setDefaultRuleset(ruleset);</code>\r
264  *    <tr>\r
265  *       <td><code>ordinal</code>\r
266  *       <td><i>Ruleset name (optional)</i>\r
267  *       <td><code>new RuleBasedNumberFormat(getLocale(), RuleBasedNumberFormat.ORDINAL)<br/>&nbsp;&nbsp;&nbsp;&nbsp;.setDefaultRuleset(ruleset);</code>\r
268  *    <tr>\r
269  *       <td><code>duration</code>\r
270  *       <td><i>Ruleset name (optional)</i>\r
271  *       <td><code>new RuleBasedNumberFormat(getLocale(), RuleBasedNumberFormat.DURATION)<br/>&nbsp;&nbsp;&nbsp;&nbsp;.setDefaultRuleset(ruleset);</code>\r
272  *    <tr>\r
273  *       <td><code>plural</code>\r
274  *       <td><i>SubformatPattern</i>\r
275  *       <td><code>new PluralFormat(subformatPattern)</code>\r
276  * </table>\r
277  * <p>\r
278  *\r
279  * <h4>Usage Information</h4>\r
280  *\r
281  * <p>\r
282  * Here are some examples of usage:\r
283  * <blockquote>\r
284  * <pre>\r
285  * Object[] arguments = {\r
286  *     new Integer(7),\r
287  *     new Date(System.currentTimeMillis()),\r
288  *     "a disturbance in the Force"\r
289  * };\r
290  *\r
291  * String result = MessageFormat.format(\r
292  *     "At {1,time} on {1,date}, there was {2} on planet {0,number,integer}.",\r
293  *     arguments);\r
294  *\r
295  * <em>output</em>: At 12:30 PM on Jul 3, 2053, there was a disturbance\r
296  *           in the Force on planet 7.\r
297  *\r
298  * </pre>\r
299  * </blockquote>\r
300  * Typically, the message format will come from resources, and the\r
301  * arguments will be dynamically set at runtime.\r
302  *\r
303  * <p>\r
304  * Example 2:\r
305  * <blockquote>\r
306  * <pre>\r
307  * Object[] testArgs = {new Long(3), "MyDisk"};\r
308  *\r
309  * MessageFormat form = new MessageFormat(\r
310  *     "The disk \"{1}\" contains {0} file(s).");\r
311  *\r
312  * System.out.println(form.format(testArgs));\r
313  *\r
314  * // output, with different testArgs\r
315  * <em>output</em>: The disk "MyDisk" contains 0 file(s).\r
316  * <em>output</em>: The disk "MyDisk" contains 1 file(s).\r
317  * <em>output</em>: The disk "MyDisk" contains 1,273 file(s).\r
318  * </pre>\r
319  * </blockquote>\r
320  *\r
321  * <p>\r
322  * For more sophisticated patterns, you can use a <code>ChoiceFormat</code> to get\r
323  * output such as:\r
324  * <blockquote>\r
325  * <pre>\r
326  * MessageFormat form = new MessageFormat("The disk \"{1}\" contains {0}.");\r
327  * double[] filelimits = {0,1,2};\r
328  * String[] filepart = {"no files","one file","{0,number} files"};\r
329  * ChoiceFormat fileform = new ChoiceFormat(filelimits, filepart);\r
330  * form.setFormatByArgumentIndex(0, fileform);\r
331  *\r
332  * Object[] testArgs = {new Long(12373), "MyDisk"};\r
333  *\r
334  * System.out.println(form.format(testArgs));\r
335  *\r
336  * // output, with different testArgs\r
337  * output: The disk "MyDisk" contains no files.\r
338  * output: The disk "MyDisk" contains one file.\r
339  * output: The disk "MyDisk" contains 1,273 files.\r
340  * </pre>\r
341  * </blockquote>\r
342  * You can either do this programmatically, as in the above example,\r
343  * or by using a pattern (see\r
344  * {@link ChoiceFormat}\r
345  * for more information) as in:\r
346  * <blockquote>\r
347  * <pre>\r
348  * form.applyPattern(\r
349  *    "There {0,choice,0#are no files|1#is one file|1&lt;are {0,number,integer} files}.");\r
350  * </pre>\r
351  * </blockquote>\r
352  * <p>\r
353  * <strong>Note:</strong> As we see above, the string produced\r
354  * by a <code>ChoiceFormat</code> in <code>MessageFormat</code> is treated specially;\r
355  * occurances of '{' are used to indicated subformats, and cause recursion.\r
356  * If you create both a <code>MessageFormat</code> and <code>ChoiceFormat</code>\r
357  * programmatically (instead of using the string patterns), then be careful not to\r
358  * produce a format that recurses on itself, which will cause an infinite loop.\r
359  * <p>\r
360  * When a single argument is parsed more than once in the string, the last match\r
361  * will be the final result of the parsing.  For example,\r
362  * <pre>\r
363  * MessageFormat mf = new MessageFormat("{0,number,#.##}, {0,number,#.#}");\r
364  * Object[] objs = {new Double(3.1415)};\r
365  * String result = mf.format( objs );\r
366  * // result now equals "3.14, 3.1"\r
367  * objs = null;\r
368  * objs = mf.parse(result, new ParsePosition(0));\r
369  * // objs now equals {new Double(3.1)}\r
370  * </pre>\r
371  * <p>\r
372  * Likewise, parsing with a MessageFormat object using patterns containing\r
373  * multiple occurances of the same argument would return the last match.  For\r
374  * example,\r
375  * <pre>\r
376  * MessageFormat mf = new MessageFormat("{0}, {0}, {0}");\r
377  * String forParsing = "x, y, z";\r
378  * Object[] objs = mf.parse(forParsing, new ParsePosition(0));\r
379  * // result now equals {new String("z")}\r
380  * </pre>\r
381  *\r
382  * <h4><a name="synchronization">Synchronization</a></h4>\r
383  *\r
384  * <p>\r
385  * Message formats are not synchronized.\r
386  * It is recommended to create separate format instances for each thread.\r
387  * If multiple threads access a format concurrently, it must be synchronized\r
388  * externally.\r
389  *\r
390  * @see          java.util.Locale\r
391  * @see          Format\r
392  * @see          NumberFormat\r
393  * @see          DecimalFormat\r
394  * @see          ChoiceFormat\r
395  * @author       Mark Davis\r
396  * @stable ICU 3.0\r
397  */\r
398  // TODO: Update JavaDoc class description with regards to named arguments.\r
399  // TODO: Update JavaDoc class description with regards to PluralFormat\r
400  //       integration.\r
401 public class MessageFormat extends UFormat {\r
402 \r
403     // Generated by serialver from JDK 1.4.1_01\r
404     static final long serialVersionUID = 7136212545847378651L;\r
405 \r
406     /**\r
407      * Constructs a MessageFormat for the default locale and the\r
408      * specified pattern.\r
409      * The constructor first sets the locale, then parses the pattern and\r
410      * creates a list of subformats for the format elements contained in it.\r
411      * Patterns and their interpretation are specified in the\r
412      * <a href="#patterns">class description</a>.\r
413      *\r
414      * @param pattern the pattern for this message format\r
415      * @exception IllegalArgumentException if the pattern is invalid\r
416      * @stable ICU 3.0\r
417      */\r
418     public MessageFormat(String pattern) {\r
419         this.ulocale = ULocale.getDefault();\r
420         applyPattern(pattern);\r
421     }\r
422 \r
423     /**\r
424      * Constructs a MessageFormat for the specified locale and\r
425      * pattern.\r
426      * The constructor first sets the locale, then parses the pattern and\r
427      * creates a list of subformats for the format elements contained in it.\r
428      * Patterns and their interpretation are specified in the\r
429      * <a href="#patterns">class description</a>.\r
430      *\r
431      * @param pattern the pattern for this message format\r
432      * @param locale the locale for this message format\r
433      * @exception IllegalArgumentException if the pattern is invalid\r
434      * @stable ICU 3.0\r
435      */\r
436     public MessageFormat(String pattern, Locale locale) {\r
437         this(pattern, ULocale.forLocale(locale));\r
438     }\r
439 \r
440     /**\r
441      * Constructs a MessageFormat for the specified locale and\r
442      * pattern.\r
443      * The constructor first sets the locale, then parses the pattern and\r
444      * creates a list of subformats for the format elements contained in it.\r
445      * Patterns and their interpretation are specified in the\r
446      * <a href="#patterns">class description</a>.\r
447      *\r
448      * @param pattern the pattern for this message format\r
449      * @param locale the locale for this message format\r
450      * @exception IllegalArgumentException if the pattern is invalid\r
451      * @stable ICU 3.2\r
452      */\r
453     public MessageFormat(String pattern, ULocale locale) {\r
454         this.ulocale = locale;\r
455         applyPattern(pattern);\r
456     }\r
457 \r
458     /**\r
459      * Sets the locale to be used when creating or comparing subformats.\r
460      * This affects subsequent calls to the {@link #applyPattern applyPattern}\r
461      * and {@link #toPattern toPattern} methods as well as to the\r
462      * <code>format</code> and\r
463      * {@link #formatToCharacterIterator formatToCharacterIterator} methods.\r
464      *\r
465      * @param locale the locale to be used when creating or comparing subformats\r
466      * @stable ICU 3.0\r
467      */\r
468     public void setLocale(Locale locale) {\r
469         setLocale(ULocale.forLocale(locale));\r
470     }\r
471 \r
472     /**\r
473      * Sets the locale to be used when creating or comparing subformats.\r
474      * This affects subsequent calls to the {@link #applyPattern applyPattern}\r
475      * and {@link #toPattern toPattern} methods as well as to the\r
476      * <code>format</code> and\r
477      * {@link #formatToCharacterIterator formatToCharacterIterator} methods.\r
478      *\r
479      * @param locale the locale to be used when creating or comparing subformats\r
480      * @stable ICU 3.2\r
481      */\r
482     public void setLocale(ULocale locale) {\r
483         /* Save the pattern, and then reapply so that */\r
484         /* we pick up any changes in locale specific */\r
485         /* elements */\r
486         String existingPattern = toPattern();                       /*ibm.3550*/\r
487         this.ulocale = locale;\r
488         applyPattern(existingPattern);                              /*ibm.3550*/\r
489     }\r
490 \r
491     /**\r
492      * Gets the locale that's used when creating or comparing subformats.\r
493      *\r
494      * @return the locale used when creating or comparing subformats\r
495      * @stable ICU 3.0\r
496      */\r
497     public Locale getLocale() {\r
498         return ulocale.toLocale();\r
499     }\r
500 \r
501     /**\r
502      * Gets the locale that's used when creating or comparing subformats.\r
503      *\r
504      * @return the locale used when creating or comparing subformats\r
505      * @stable ICU 3.2\r
506      */\r
507     public ULocale getULocale() {\r
508         return ulocale;\r
509     }\r
510 \r
511     /**\r
512      * Sets the pattern used by this message format.\r
513      * The method parses the pattern and creates a list of subformats\r
514      * for the format elements contained in it.\r
515      * Patterns and their interpretation are specified in the\r
516      * <a href="#patterns">class description</a>.\r
517      * <p>\r
518      * The pattern must contain only named or only numeric arguments,\r
519      * mixing them is not allowed.\r
520      *\r
521      * @param pttrn the pattern for this message format\r
522      * @throws IllegalArgumentException if the pattern is invalid\r
523      * @stable ICU 3.0\r
524      */\r
525     public void applyPattern(String pttrn) {\r
526         StringBuffer[] segments = new StringBuffer[4];\r
527         for (int i = 0; i < segments.length; ++i) {\r
528             segments[i] = new StringBuffer();\r
529         }\r
530         int part = 0;\r
531         int formatNumber = 0;\r
532         boolean inQuote = false;\r
533         int braceStack = 0;\r
534         maxOffset = -1;\r
535         for (int i = 0; i < pttrn.length(); ++i) {\r
536             char ch = pttrn.charAt(i);\r
537             if (part == 0) {\r
538                 if (ch == '\'') {\r
539                     if (i + 1 < pttrn.length()\r
540                         && pttrn.charAt(i+1) == '\'') {\r
541                         segments[part].append(ch);  // handle doubles\r
542                         ++i;\r
543                     } else {\r
544                         inQuote = !inQuote;\r
545                     }\r
546                 } else if (ch == '{' && !inQuote) {\r
547                     part = 1;\r
548                 } else {\r
549                     segments[part].append(ch);\r
550                 }\r
551             } else  if (inQuote) {  // just copy quotes in parts\r
552                 segments[part].append(ch);\r
553                 if (ch == '\'') {\r
554                     inQuote = false;\r
555                 }\r
556             } else {\r
557                 switch (ch) {\r
558                 case ',':\r
559                     if (part < 3)\r
560                         part += 1;\r
561                     else\r
562                         segments[part].append(ch);\r
563                     break;\r
564                 case '{':\r
565                     ++braceStack;\r
566                     segments[part].append(ch);\r
567                     break;\r
568                 case '}':\r
569                     if (braceStack == 0) {\r
570                         part = 0;\r
571                         makeFormat(i, formatNumber, segments);\r
572                         formatNumber++;\r
573                     } else {\r
574                         --braceStack;\r
575                         segments[part].append(ch);\r
576                     }\r
577                     break;\r
578                 case '\'':\r
579                     inQuote = true;\r
580                     // fall through, so we keep quotes in other parts\r
581                 default:\r
582                     segments[part].append(ch);\r
583                     break;\r
584                 }\r
585             }\r
586         }\r
587         if (braceStack == 0 && part != 0) {\r
588             maxOffset = -1;\r
589             throw new IllegalArgumentException("Unmatched braces in the pattern.");\r
590         }\r
591         this.pattern = segments[0].toString();\r
592     }\r
593 \r
594 \r
595     /**\r
596      * Returns a pattern representing the current state of the message format.\r
597      * The string is constructed from internal information and therefore\r
598      * does not necessarily equal the previously applied pattern.\r
599      *\r
600      * @return a pattern representing the current state of the message format\r
601      * @stable ICU 3.0\r
602      */\r
603     public String toPattern() {\r
604         // later, make this more extensible\r
605         int lastOffset = 0;\r
606         StringBuffer result = new StringBuffer();\r
607         for (int i = 0; i <= maxOffset; ++i) {\r
608             copyAndFixQuotes(pattern, lastOffset, offsets[i],result);\r
609             lastOffset = offsets[i];\r
610             result.append('{');\r
611             result.append(argumentNames[i]);\r
612             if (formats[i] == null) {\r
613                 // do nothing, string format\r
614             } else if (formats[i] instanceof DecimalFormat) {\r
615                 if (formats[i].equals(NumberFormat.getInstance(ulocale))) {\r
616                     result.append(",number");\r
617                 } else if (formats[i].equals(NumberFormat.getCurrencyInstance(ulocale))) {\r
618                     result.append(",number,currency");\r
619                 } else if (formats[i].equals(NumberFormat.getPercentInstance(ulocale))) {\r
620                     result.append(",number,percent");\r
621                 } else if (formats[i].equals(NumberFormat.getIntegerInstance(ulocale))) {\r
622                     result.append(",number,integer");\r
623                 } else {\r
624                     result.append(",number," +\r
625                                   ((DecimalFormat)formats[i]).toPattern());\r
626                 }\r
627             } else if (formats[i] instanceof SimpleDateFormat) {\r
628                 if (formats[i].equals(DateFormat.getDateInstance(DateFormat.DEFAULT,ulocale))) {\r
629                     result.append(",date");\r
630                 } else if (formats[i].equals(DateFormat.getDateInstance(DateFormat.SHORT,ulocale))) {\r
631                     result.append(",date,short");\r
632 // This code will never be executed [alan]\r
633 //                } else if (formats[i].equals(DateFormat.getDateInstance(DateFormat.DEFAULT,ulocale))) {\r
634 //                    result.append(",date,medium");\r
635                 } else if (formats[i].equals(DateFormat.getDateInstance(DateFormat.LONG,ulocale))) {\r
636                     result.append(",date,long");\r
637                 } else if (formats[i].equals(DateFormat.getDateInstance(DateFormat.FULL,ulocale))) {\r
638                     result.append(",date,full");\r
639                 } else if (formats[i].equals(DateFormat.getTimeInstance(DateFormat.DEFAULT,ulocale))) {\r
640                     result.append(",time");\r
641                 } else if (formats[i].equals(DateFormat.getTimeInstance(DateFormat.SHORT,ulocale))) {\r
642                     result.append(",time,short");\r
643 // This code will never be executed [alan]\r
644 //                } else if (formats[i].equals(DateFormat.getTimeInstance(DateFormat.DEFAULT,ulocale))) {\r
645 //                    result.append(",time,medium");\r
646                 } else if (formats[i].equals(DateFormat.getTimeInstance(DateFormat.LONG,ulocale))) {\r
647                     result.append(",time,long");\r
648                 } else if (formats[i].equals(DateFormat.getTimeInstance(DateFormat.FULL,ulocale))) {\r
649                     result.append(",time,full");\r
650                 } else {\r
651                     result.append(",date," + ((SimpleDateFormat)formats[i]).toPattern());\r
652                 }\r
653             } else if (formats[i] instanceof ChoiceFormat) {\r
654                 result.append(",choice,"\r
655                         + ((ChoiceFormat) formats[i]).toPattern());\r
656             } else if (formats[i] instanceof PluralFormat) {\r
657               String pat = ((PluralFormat)formats[i]).toPattern();\r
658               // TODO: PluralFormat doesn't do the single quote thing, just reapply\r
659               if (pat.indexOf('\'') != 0) {\r
660                 StringBuffer buf = new StringBuffer();\r
661                 for (int j = 0; j < pat.length(); ++j) {\r
662                   char ch = pat.charAt(j);\r
663                   if (ch == '\'') {\r
664                     buf.append(ch); // double it\r
665                   }\r
666                   buf.append(ch);\r
667                 }\r
668                 pat = buf.toString();\r
669               }\r
670               result.append(",plural," + pat);\r
671             } else {\r
672                 //result.append(", unknown");\r
673             }\r
674             result.append('}');\r
675         }\r
676         copyAndFixQuotes(pattern, lastOffset, pattern.length(), result);\r
677         return result.toString();\r
678     }\r
679 \r
680     /**\r
681      * Sets the formats to use for the values passed into\r
682      * <code>format</code> methods or returned from <code>parse</code>\r
683      * methods. The indices of elements in <code>newFormats</code>\r
684      * correspond to the argument indices used in the previously set\r
685      * pattern string.\r
686      * The order of formats in <code>newFormats</code> thus corresponds to\r
687      * the order of elements in the <code>arguments</code> array passed\r
688      * to the <code>format</code> methods or the result array returned\r
689      * by the <code>parse</code> methods.\r
690      * <p>\r
691      * If an argument index is used for more than one format element\r
692      * in the pattern string, then the corresponding new format is used\r
693      * for all such format elements. If an argument index is not used\r
694      * for any format element in the pattern string, then the\r
695      * corresponding new format is ignored. If fewer formats are provided\r
696      * than needed, then only the formats for argument indices less\r
697      * than <code>newFormats.length</code> are replaced.\r
698      *\r
699      * This method is only supported if the format does not use\r
700      * named arguments, otherwise an IllegalArgumentException is thrown.\r
701      *\r
702      * @param newFormats\r
703      *            the new formats to use\r
704      * @throws NullPointerException\r
705      *                if <code>newFormats</code> is null\r
706      * @throws IllegalArgumentException\r
707      *                if this formatter uses named arguments\r
708      * @stable ICU 3.0\r
709      */\r
710     public void setFormatsByArgumentIndex(Format[] newFormats) {\r
711         if (!argumentNamesAreNumeric) {\r
712             throw new IllegalArgumentException(\r
713                     "This method is not available in MessageFormat objects " +\r
714                     "that use alphanumeric argument names.");\r
715         }\r
716         for (int i = 0; i <= maxOffset; i++) {\r
717             int j = Integer.parseInt(argumentNames[i]);\r
718             if (j < newFormats.length) {\r
719                 formats[i] = newFormats[j];\r
720             }\r
721         }\r
722     }\r
723 \r
724     /**\r
725      * Sets the formats to use for the values passed into\r
726      * <code>format</code> methods or returned from <code>parse</code>\r
727      * methods. The keys in <code>newFormats</code> are the argument\r
728      * names in the previously set pattern string, and the values\r
729      * are the formats.\r
730      * <p>\r
731      * Only argument names from the pattern string are considered.\r
732      * Extra keys in <code>newFormats</code> that do not correspond\r
733      * to an argument name are ignored.  Similarly, if there is no\r
734      * format in newFormats for an argument name, the formatter\r
735      * for that argument remains unchanged.\r
736      * <p>\r
737      * This may be called on formats that do not use named arguments.\r
738      * In this case the map will be queried for key Strings that\r
739      * represent argument indices, e.g. "0", "1", "2" etc.\r
740      *\r
741      * @param newFormats a map from String to Format providing new\r
742      *        formats for named arguments.\r
743      * @stable ICU 3.8\r
744      */\r
745     public void setFormatsByArgumentName(Map newFormats) {\r
746         for (int i = 0; i <= maxOffset; i++) {\r
747             if (newFormats.containsKey(argumentNames[i])) {\r
748                 Format f = (Format)newFormats.get(argumentNames[i]);\r
749                 formats[i] = f;\r
750             }\r
751         }\r
752     }\r
753 \r
754     /**\r
755      * Sets the formats to use for the format elements in the\r
756      * previously set pattern string.\r
757      * The order of formats in <code>newFormats</code> corresponds to\r
758      * the order of format elements in the pattern string.\r
759      * <p>\r
760      * If more formats are provided than needed by the pattern string,\r
761      * the remaining ones are ignored. If fewer formats are provided\r
762      * than needed, then only the first <code>newFormats.length</code>\r
763      * formats are replaced.\r
764      * <p>\r
765      * Since the order of format elements in a pattern string often\r
766      * changes during localization, it is generally better to use the\r
767      * {@link #setFormatsByArgumentIndex setFormatsByArgumentIndex}\r
768      * method, which assumes an order of formats corresponding to the\r
769      * order of elements in the <code>arguments</code> array passed to\r
770      * the <code>format</code> methods or the result array returned by\r
771      * the <code>parse</code> methods.\r
772      *\r
773      * @param newFormats the new formats to use\r
774      * @exception NullPointerException if <code>newFormats</code> is null\r
775      * @stable ICU 3.0\r
776      */\r
777     public void setFormats(Format[] newFormats) {\r
778         int runsToCopy = newFormats.length;\r
779         if (runsToCopy > maxOffset + 1) {\r
780             runsToCopy = maxOffset + 1;\r
781         }\r
782         for (int i = 0; i < runsToCopy; i++) {\r
783             formats[i] = newFormats[i];\r
784         }\r
785     }\r
786 \r
787     /**\r
788      * Sets the format to use for the format elements within the\r
789      * previously set pattern string that use the given argument\r
790      * index.\r
791      * The argument index is part of the format element definition and\r
792      * represents an index into the <code>arguments</code> array passed\r
793      * to the <code>format</code> methods or the result array returned\r
794      * by the <code>parse</code> methods.\r
795      * <p>\r
796      * If the argument index is used for more than one format element\r
797      * in the pattern string, then the new format is used for all such\r
798      * format elements. If the argument index is not used for any format\r
799      * element in the pattern string, then the new format is ignored.\r
800      *\r
801      * This method is only supported when exclusively numbers are used for\r
802      * argument names. Otherwise an IllegalArgumentException is thrown.\r
803      *\r
804      * @param argumentIndex\r
805      *            the argument index for which to use the new format\r
806      * @param newFormat\r
807      *            the new format to use\r
808      * @exception IllegalArgumentException\r
809      *            if alphanumeric arguments where used in MessageFormat.\r
810      * @stable ICU 3.0\r
811      */\r
812     public void setFormatByArgumentIndex(int argumentIndex, Format newFormat) {\r
813         if (!argumentNamesAreNumeric) {\r
814             throw new IllegalArgumentException(\r
815                     "This method is not available in MessageFormat objects " +\r
816                     "that use alphanumeric argument names.");\r
817         }\r
818         for (int j = 0; j <= maxOffset; j++) {\r
819             if (Integer.parseInt(argumentNames[j]) == argumentIndex) {\r
820                 formats[j] = newFormat;\r
821             }\r
822         }\r
823     }\r
824 \r
825     /**\r
826      * Sets the format to use for the format elements within the\r
827      * previously set pattern string that use the given argument\r
828      * name.\r
829      * <p>\r
830      * If the argument name is used for more than one format element\r
831      * in the pattern string, then the new format is used for all such\r
832      * format elements. If the argument name is not used for any format\r
833      * element in the pattern string, then the new format is ignored.\r
834      * <p>\r
835      * This API may be used on formats that do not use named arguments.\r
836      * In this case <code>argumentName</code> should be a String that names\r
837      * an argument index, e.g. "0", "1", "2"... etc.  If it does not name\r
838      * a valid index, the format will be ignored.  No error is thrown.\r
839      *\r
840      * @param argumentName the name of the argument to change\r
841      * @param newFormat the new format to use\r
842      * @stable ICU 3.8\r
843      */\r
844     public void setFormatByArgumentName(String argumentName, Format newFormat) {\r
845         for (int i = 0; i <= maxOffset; ++i) {\r
846             if (argumentName.equals(argumentNames[i])) {\r
847                 formats[i] = newFormat;\r
848             }\r
849         }\r
850     }\r
851 \r
852     /**\r
853      * Sets the format to use for the format element with the given\r
854      * format element index within the previously set pattern string.\r
855      * The format element index is the zero-based number of the format\r
856      * element counting from the start of the pattern string.\r
857      * <p>\r
858      * Since the order of format elements in a pattern string often\r
859      * changes during localization, it is generally better to use the\r
860      * {@link #setFormatByArgumentIndex setFormatByArgumentIndex}\r
861      * method, which accesses format elements based on the argument\r
862      * index they specify.\r
863      *\r
864      * @param formatElementIndex the index of a format element within the pattern\r
865      * @param newFormat the format to use for the specified format element\r
866      * @exception ArrayIndexOutOfBoundsException if formatElementIndex is equal to or\r
867      *            larger than the number of format elements in the pattern string\r
868      * @stable ICU 3.0\r
869      */\r
870     public void setFormat(int formatElementIndex, Format newFormat) {\r
871         formats[formatElementIndex] = newFormat;\r
872     }\r
873 \r
874     /**\r
875      * Gets the formats used for the values passed into\r
876      * <code>format</code> methods or returned from <code>parse</code>\r
877      * methods. The indices of elements in the returned array\r
878      * correspond to the argument indices used in the previously set\r
879      * pattern string.\r
880      * The order of formats in the returned array thus corresponds to\r
881      * the order of elements in the <code>arguments</code> array passed\r
882      * to the <code>format</code> methods or the result array returned\r
883      * by the <code>parse</code> methods.\r
884      * <p>\r
885      * If an argument index is used for more than one format element\r
886      * in the pattern string, then the format used for the last such\r
887      * format element is returned in the array. If an argument index\r
888      * is not used for any format element in the pattern string, then\r
889      * null is returned in the array.\r
890      *\r
891      * This method is only supported when exclusively numbers are used for\r
892      * argument names. Otherwise an IllegalArgumentException is thrown.\r
893      *\r
894      * @return the formats used for the arguments within the pattern\r
895      * @throws IllegalArgumentException\r
896      *         if this format uses named arguments\r
897      * @stable ICU 3.0\r
898      */\r
899     public Format[] getFormatsByArgumentIndex() {\r
900         if (!argumentNamesAreNumeric) {\r
901             throw new IllegalArgumentException(\r
902                     "This method is not available in MessageFormat objects " +\r
903                     "that use alphanumeric argument names.");\r
904         }\r
905         int maximumArgumentNumber = -1;\r
906         for (int i = 0; i <= maxOffset; i++) {\r
907             int argumentNumber = Integer.parseInt(argumentNames[i]);\r
908             if (argumentNumber > maximumArgumentNumber) {\r
909                 maximumArgumentNumber = argumentNumber;\r
910             }\r
911         }\r
912         Format[] resultArray = new Format[maximumArgumentNumber + 1];\r
913         for (int i = 0; i <= maxOffset; i++) {\r
914             resultArray[Integer.parseInt(argumentNames[i])] = formats[i];\r
915         }\r
916         return resultArray;\r
917     }\r
918     // TODO: provide method public Map getFormatsByArgumentName().\r
919     // Where Map is: String argumentName --> Format format.\r
920 \r
921     /**\r
922      * Gets the formats used for the format elements in the\r
923      * previously set pattern string.\r
924      * The order of formats in the returned array corresponds to\r
925      * the order of format elements in the pattern string.\r
926      * <p>\r
927      * Since the order of format elements in a pattern string often\r
928      * changes during localization, it's generally better to use the\r
929      * {@link #getFormatsByArgumentIndex()}\r
930      * method, which assumes an order of formats corresponding to the\r
931      * order of elements in the <code>arguments</code> array passed to\r
932      * the <code>format</code> methods or the result array returned by\r
933      * the <code>parse</code> methods.\r
934      *\r
935      * This method is only supported when exclusively numbers are used for\r
936      * argument names. Otherwise an IllegalArgumentException is thrown.\r
937      *\r
938      * @return the formats used for the format elements in the pattern\r
939      * @stable ICU 3.0\r
940      */\r
941     public Format[] getFormats() {\r
942         Format[] resultArray = new Format[maxOffset + 1];\r
943         System.arraycopy(formats, 0, resultArray, 0, maxOffset + 1);\r
944         return resultArray;\r
945     }\r
946     \r
947     /**\r
948      * Get the format argument names. For more details, see {@link #setFormatByArgumentName(String, Format)}.\r
949      * @return List of names\r
950      * @deprecated\r
951      * @internal\r
952      */\r
953     public Set getFormatArgumentNames() {\r
954         Set result = new HashSet();\r
955         for (int i = 0; i <= maxOffset; ++i) {\r
956             result.add(argumentNames[i]);\r
957         }\r
958         return result;\r
959     }\r
960     \r
961     /**\r
962      * Get the formats according to their argument names. For more details, see {@link #setFormatByArgumentName(String, Format)}.\r
963      * @return format associated with the name, or null if there isn't one.\r
964      * @deprecated\r
965      * @internal\r
966      */\r
967     public Format getFormatByArgumentName(String argumentName) {\r
968         for (int i = 0; i <= maxOffset; ++i) {\r
969             if (argumentName.equals(argumentNames[i])) {\r
970                 return formats[i];\r
971             }\r
972         }\r
973         return null;\r
974     }\r
975 \r
976     /**\r
977      * Formats an array of objects and appends the <code>MessageFormat</code>'s\r
978      * pattern, with format elements replaced by the formatted objects, to the\r
979      * provided <code>StringBuffer</code>.\r
980      * <p>\r
981      * The text substituted for the individual format elements is derived from\r
982      * the current subformat of the format element and the\r
983      * <code>arguments</code> element at the format element's argument index\r
984      * as indicated by the first matching line of the following table. An\r
985      * argument is <i>unavailable</i> if <code>arguments</code> is\r
986      * <code>null</code> or has fewer than argumentIndex+1 elements.  When\r
987      * an argument is unavailable no substitution is performed.\r
988      * <p>\r
989      * <table border=1>\r
990      *    <tr>\r
991      *       <th>Subformat\r
992      *       <th>Argument\r
993      *       <th>Formatted Text\r
994      *    <tr>\r
995      *       <td><i>any</i>\r
996      *       <td><i>unavailable</i>\r
997      *       <td><code>"{" + argumentIndex + "}"</code>\r
998      *    <tr>\r
999      *       <td><i>any</i>\r
1000      *       <td><code>null</code>\r
1001      *       <td><code>"null"</code>\r
1002      *    <tr>\r
1003      *       <td><code>instanceof ChoiceFormat</code>\r
1004      *       <td><i>any</i>\r
1005      *       <td><code>subformat.format(argument).indexOf('{') >= 0 ?<br>\r
1006      *           (new MessageFormat(subformat.format(argument), getLocale())).format(argument) :\r
1007      *           subformat.format(argument)</code>\r
1008      *    <tr>\r
1009      *       <td><code>!= null</code>\r
1010      *       <td><i>any</i>\r
1011      *       <td><code>subformat.format(argument)</code>\r
1012      *    <tr>\r
1013      *       <td><code>null</code>\r
1014      *       <td><code>instanceof Number</code>\r
1015      *       <td><code>NumberFormat.getInstance(getLocale()).format(argument)</code>\r
1016      *    <tr>\r
1017      *       <td><code>null</code>\r
1018      *       <td><code>instanceof Date</code>\r
1019      *       <td><code>DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, getLocale()).format(argument)</code>\r
1020      *    <tr>\r
1021      *       <td><code>null</code>\r
1022      *       <td><code>instanceof String</code>\r
1023      *       <td><code>argument</code>\r
1024      *    <tr>\r
1025      *       <td><code>null</code>\r
1026      *       <td><i>any</i>\r
1027      *       <td><code>argument.toString()</code>\r
1028      * </table>\r
1029      * <p>\r
1030      * If <code>pos</code> is non-null, and refers to\r
1031      * <code>Field.ARGUMENT</code>, the location of the first formatted\r
1032      * string will be returned.\r
1033      *\r
1034      * This method is only supported when the format does not use named\r
1035      * arguments, otherwise an IllegalArgumentException is thrown.\r
1036      *\r
1037      * @param arguments an array of objects to be formatted and substituted.\r
1038      * @param result where text is appended.\r
1039      * @param pos On input: an alignment field, if desired.\r
1040      *            On output: the offsets of the alignment field.\r
1041      * @throws IllegalArgumentException if an argument in the\r
1042      *            <code>arguments</code> array is not of the type\r
1043      *            expected by the format element(s) that use it.\r
1044      * @throws IllegalArgumentException\r
1045      *            if this format uses named arguments\r
1046      * @stable ICU 3.0\r
1047      */\r
1048     public final StringBuffer format(Object[] arguments, StringBuffer result,\r
1049                                      FieldPosition pos)\r
1050     {\r
1051         if (!argumentNamesAreNumeric) {\r
1052             throw new IllegalArgumentException(\r
1053                   "This method is not available in MessageFormat objects " +\r
1054                   "that use alphanumeric argument names.");\r
1055         }\r
1056         return subformat(arguments, result, pos, null);\r
1057     }\r
1058 \r
1059     /**\r
1060      * Formats a map of objects and appends the <code>MessageFormat</code>'s\r
1061      * pattern, with format elements replaced by the formatted objects, to the\r
1062      * provided <code>StringBuffer</code>.\r
1063      * <p>\r
1064      * The text substituted for the individual format elements is derived from\r
1065      * the current subformat of the format element and the\r
1066      * <code>arguments</code> value corresopnding to the format element's\r
1067      * argument name.\r
1068      * <p>\r
1069      * This API may be called on formats that do not use named arguments.\r
1070      * In this case the the keys in <code>arguments</code> must be numeric\r
1071      * strings (e.g. "0", "1", "2"...).\r
1072      * <p>\r
1073      * An argument is <i>unavailable</i> if <code>arguments</code> is\r
1074      * <code>null</code> or does not have a value corresponding to an argument\r
1075      * name in the pattern.  When an argument is unavailable no substitution\r
1076      * is performed.\r
1077      *\r
1078      * @param arguments a map of objects to be formatted and substituted.\r
1079      * @param result where text is appended.\r
1080      * @param pos On input: an alignment field, if desired.\r
1081      *            On output: the offsets of the alignment field.\r
1082      * @throws IllegalArgumentException if an argument in the\r
1083      *         <code>arguments</code> array is not of the type\r
1084      *         expected by the format element(s) that use it.\r
1085      * @return the passed-in StringBuffer\r
1086      * @stable ICU 3.8\r
1087      */\r
1088     public final StringBuffer format(Map arguments, StringBuffer result,\r
1089                                      FieldPosition pos) {\r
1090         return subformat(arguments, result, pos, null);\r
1091     }\r
1092 \r
1093     /**\r
1094      * Creates a MessageFormat with the given pattern and uses it\r
1095      * to format the given arguments. This is equivalent to\r
1096      * <blockquote>\r
1097      *     <code>(new {@link #MessageFormat(String) MessageFormat}(pattern)).{@link #format(java.lang.Object[], java.lang.StringBuffer, java.text.FieldPosition) format}(arguments, new StringBuffer(), null).toString()</code>\r
1098      * </blockquote>\r
1099      *\r
1100      * @throws IllegalArgumentException if the pattern is invalid,\r
1101      *            or if an argument in the <code>arguments</code> array\r
1102      *            is not of the type expected by the format element(s)\r
1103      *            that use it.\r
1104      * @throws IllegalArgumentException\r
1105      *            if this format uses named arguments\r
1106      * @stable ICU 3.0\r
1107      */\r
1108     public static String format(String pattern, Object[] arguments) {\r
1109         MessageFormat temp = new MessageFormat(pattern);\r
1110         return temp.format(arguments);\r
1111     }\r
1112 \r
1113     /**\r
1114      * Creates a MessageFormat with the given pattern and uses it to\r
1115      * format the given arguments.  The pattern must identifyarguments\r
1116      * by name instead of by number.\r
1117      * <p>\r
1118      * @throws IllegalArgumentException if the pattern is invalid,\r
1119      *         or if an argument in the <code>arguments</code> map\r
1120      *         is not of the type expected by the format element(s)\r
1121      *         that use it.\r
1122      * @see #format(Map, StringBuffer, FieldPosition)\r
1123      * @see #format(String, Object[])\r
1124      * @stable ICU 3.8\r
1125      */\r
1126     public static String format(String pattern, Map arguments) {\r
1127         MessageFormat temp = new MessageFormat(pattern);\r
1128         return temp.format(arguments);\r
1129     }\r
1130 \r
1131     /**\r
1132      * Returns true if this MessageFormat uses named arguments,\r
1133      * and false otherwise.  See class description.\r
1134      *\r
1135      * @return true if named arguments are used.\r
1136      * @stable ICU 3.8\r
1137      */\r
1138     public boolean usesNamedArguments() {\r
1139         return !argumentNamesAreNumeric;\r
1140     }\r
1141 \r
1142     // Overrides\r
1143     /**\r
1144      * Formats a map or array of objects and appends the <code>MessageFormat</code>'s\r
1145      * pattern, with format elements replaced by the formatted objects, to the\r
1146      * provided <code>StringBuffer</code>.\r
1147      * This is equivalent to either of\r
1148      * <blockquote>\r
1149      *     <code>{@link #format(java.lang.Object[], java.lang.StringBuffer, java.text.FieldPosition) format}((Object[]) arguments, result, pos)</code>\r
1150      *     <code>{@link #format(java.util.Map, java.lang.StringBuffer, java.text.FieldPosition) format}((Map) arguments, result, pos)</code>\r
1151      * </blockquote>\r
1152      * A map must be provided if this format uses named arguments, otherwise\r
1153      * an IllegalArgumentException will be thrown.\r
1154      * @param arguments a map or array of objects to be formatted\r
1155      * @param result where text is appended\r
1156      * @param pos On input: an alignment field, if desired\r
1157      *            On output: the offsets of the alignment field\r
1158      * @throws IllegalArgumentException if an argument in\r
1159      *         <code>arguments</code> is not of the type\r
1160      *         expected by the format element(s) that use it\r
1161      * @throws IllegalArgumentException if <code>arguments<code> is\r
1162      *         an array of Object and this format uses named arguments\r
1163      * @stable ICU 3.0\r
1164      */\r
1165     public final StringBuffer format(Object arguments, StringBuffer result,\r
1166                                      FieldPosition pos)\r
1167     {\r
1168         if ((arguments == null || arguments instanceof Map)) {\r
1169             return subformat((Map) arguments, result, pos, null);\r
1170         } else {\r
1171             if (!argumentNamesAreNumeric) {\r
1172                 throw new IllegalArgumentException(\r
1173                         "This method is not available in MessageFormat objects " +\r
1174                         "that use alphanumeric argument names.");\r
1175             }\r
1176             return subformat((Object[]) arguments, result, pos, null);\r
1177         }\r
1178     }\r
1179 \r
1180 //#if defined(FOUNDATION10) || defined(J2SE13)\r
1181 //#else\r
1182     /**\r
1183      * Formats an array of objects and inserts them into the\r
1184      * <code>MessageFormat</code>'s pattern, producing an\r
1185      * <code>AttributedCharacterIterator</code>.\r
1186      * You can use the returned <code>AttributedCharacterIterator</code>\r
1187      * to build the resulting String, as well as to determine information\r
1188      * about the resulting String.\r
1189      * <p>\r
1190      * The text of the returned <code>AttributedCharacterIterator</code> is\r
1191      * the same that would be returned by\r
1192      * <blockquote>\r
1193      *     <code>{@link #format(java.lang.Object[], java.lang.StringBuffer, java.text.FieldPosition) format}(arguments, new StringBuffer(), null).toString()</code>\r
1194      * </blockquote>\r
1195      * <p>\r
1196      * In addition, the <code>AttributedCharacterIterator</code> contains at\r
1197      * least attributes indicating where text was generated from an\r
1198      * argument in the <code>arguments</code> array. The keys of these attributes are of\r
1199      * type <code>MessageFormat.Field</code>, their values are\r
1200      * <code>Integer</code> objects indicating the index in the <code>arguments</code>\r
1201      * array of the argument from which the text was generated.\r
1202      * <p>\r
1203      * The attributes/value from the underlying <code>Format</code>\r
1204      * instances that <code>MessageFormat</code> uses will also be\r
1205      * placed in the resulting <code>AttributedCharacterIterator</code>.\r
1206      * This allows you to not only find where an argument is placed in the\r
1207      * resulting String, but also which fields it contains in turn.\r
1208      *\r
1209      * @param arguments an array of objects to be formatted and substituted.\r
1210      * @return AttributedCharacterIterator describing the formatted value.\r
1211      * @exception NullPointerException if <code>arguments</code> is null.\r
1212      * @exception IllegalArgumentException if an argument in the\r
1213      *            <code>arguments</code> array is not of the type\r
1214      *            expected by the format element(s) that use it.\r
1215      * @stable ICU 3.8\r
1216      */\r
1217     public AttributedCharacterIterator formatToCharacterIterator(Object arguments) {\r
1218         StringBuffer result = new StringBuffer();\r
1219         ArrayList iterators = new ArrayList();\r
1220 \r
1221         if (arguments == null) {\r
1222             throw new NullPointerException(\r
1223                    "formatToCharacterIterator must be passed non-null object");\r
1224         }\r
1225         if (arguments instanceof Map) {\r
1226             subformat((Map)arguments, result, null, iterators);\r
1227         } else {\r
1228             subformat((Object[]) arguments, result, null, iterators);\r
1229         }\r
1230         if (iterators.size() == 0) {\r
1231             return _createAttributedCharacterIterator("");\r
1232         }\r
1233         return _createAttributedCharacterIterator(\r
1234                      (AttributedCharacterIterator[])iterators.toArray(\r
1235                      new AttributedCharacterIterator[iterators.size()]));\r
1236     }\r
1237 //#endif\r
1238 \r
1239     /**\r
1240      * Parses the string.\r
1241      *\r
1242      * <p>Caveats: The parse may fail in a number of circumstances.\r
1243      * For example:\r
1244      * <ul>\r
1245      * <li>If one of the arguments does not occur in the pattern.\r
1246      * <li>If the format of an argument loses information, such as\r
1247      *     with a choice format where a large number formats to "many".\r
1248      * <li>Does not yet handle recursion (where\r
1249      *     the substituted strings contain {n} references.)\r
1250      * <li>Will not always find a match (or the correct match)\r
1251      *     if some part of the parse is ambiguous.\r
1252      *     For example, if the pattern "{1},{2}" is used with the\r
1253      *     string arguments {"a,b", "c"}, it will format as "a,b,c".\r
1254      *     When the result is parsed, it will return {"a", "b,c"}.\r
1255      * <li>If a single argument is parsed more than once in the string,\r
1256      *     then the later parse wins.\r
1257      * </ul>\r
1258      * When the parse fails, use ParsePosition.getErrorIndex() to find out\r
1259      * where in the string did the parsing failed. The returned error\r
1260      * index is the starting offset of the sub-patterns that the string\r
1261      * is comparing with. For example, if the parsing string "AAA {0} BBB"\r
1262      * is comparing against the pattern "AAD {0} BBB", the error index is\r
1263      * 0. When an error occurs, the call to this method will return null.\r
1264      * If the source is null, return an empty array.\r
1265      * <p>\r
1266      * This method is only supported with numbered arguments.  If\r
1267      * the format pattern used named argument an\r
1268      * IllegalArgumentException is thrown.\r
1269      *\r
1270      * @throws IllegalArgumentException if this format uses named arguments\r
1271      * @stable ICU 3.0\r
1272      */\r
1273     public Object[] parse(String source, ParsePosition pos) {\r
1274         if (!argumentNamesAreNumeric) {\r
1275             throw new IllegalArgumentException(\r
1276                     "This method is not available in MessageFormat objects " +\r
1277                     "that use named argument.");\r
1278         }\r
1279         Map objectMap = parseToMap(source, pos);\r
1280         int maximumArgumentNumber = -1;\r
1281         for (int i = 0; i <= maxOffset; i++) {\r
1282             int argumentNumber = Integer.parseInt(argumentNames[i]);\r
1283             if (argumentNumber > maximumArgumentNumber) {\r
1284                 maximumArgumentNumber = argumentNumber;\r
1285             }\r
1286         }\r
1287 \r
1288         if (objectMap == null) {\r
1289             return null;\r
1290         }\r
1291 \r
1292         Object[] resultArray = new Object[maximumArgumentNumber + 1];\r
1293         Iterator keyIter = objectMap.keySet().iterator();\r
1294         while (keyIter.hasNext()) {\r
1295             String key = (String) keyIter.next();\r
1296             resultArray[Integer.parseInt(key)] = objectMap.get(key);\r
1297         }\r
1298 \r
1299         return resultArray;\r
1300     }\r
1301 \r
1302     /**\r
1303      * Parses the string, returning the results in a Map.\r
1304      * This is similar to the version that returns an array\r
1305      * of Object.  This supports both named and numbered\r
1306      * arguments-- if numbered, the keys in the map are the\r
1307      * corresponding Strings (e.g. "0", "1", "2"...).\r
1308      *\r
1309      * @param source the text to parse\r
1310      * @param pos the position at which to start parsing.  on return,\r
1311      *        contains the result of the parse.\r
1312      * @return a Map containing key/value pairs for each parsed argument.\r
1313      * @stable ICU 3.8\r
1314      */\r
1315     public Map parseToMap(String source, ParsePosition pos) {\r
1316         if (source == null) {\r
1317             Map empty = new HashMap();\r
1318             return empty;\r
1319         }\r
1320 \r
1321 //        int maximumArgumentNumber = -1;\r
1322 //        for (int i = 0; i <= maxOffset; i++) {\r
1323 //           int argumentNumber = Integer.parseInt(argumentNames[i]);\r
1324 //           if (argumentNumber > maximumArgumentNumber) {\r
1325 //               maximumArgumentNumber = argumentNumber;\r
1326 //           }\r
1327 //         }\r
1328 //        Object[] resultArray = new Object[maximumArgumentNumber + 1];\r
1329 \r
1330         Map resultMap = new HashMap();\r
1331 \r
1332         int patternOffset = 0;\r
1333         int sourceOffset = pos.getIndex();\r
1334         ParsePosition tempStatus = new ParsePosition(0);\r
1335         for (int i = 0; i <= maxOffset; ++i) {\r
1336             // match up to format\r
1337             int len = offsets[i] - patternOffset;\r
1338             if (len == 0 || pattern.regionMatches(patternOffset,\r
1339                                                   source, sourceOffset, len)) {\r
1340                 sourceOffset += len;\r
1341                 patternOffset += len;\r
1342             } else {\r
1343                 pos.setErrorIndex(sourceOffset);\r
1344                 return null; // leave index as is to signal error\r
1345             }\r
1346 \r
1347             // now use format\r
1348             if (formats[i] == null) {   // string format\r
1349                 // if at end, use longest possible match\r
1350                 // otherwise uses first match to intervening string\r
1351                 // does NOT recursively try all possibilities\r
1352                 int tempLength = (i != maxOffset) ? offsets[i+1] : pattern.length();\r
1353 \r
1354                 int next;\r
1355                 if (patternOffset >= tempLength) {\r
1356                     next = source.length();\r
1357                 }else{\r
1358                     next = source.indexOf( pattern.substring(patternOffset,tempLength), sourceOffset);\r
1359                 }\r
1360 \r
1361                 if (next < 0) {\r
1362                     pos.setErrorIndex(sourceOffset);\r
1363                     return null; // leave index as is to signal error\r
1364                 } else {\r
1365                     String strValue = source.substring(sourceOffset, next);\r
1366                     if (!strValue.equals("{" + argumentNames[i] + "}"))\r
1367                         resultMap.put(argumentNames[i], source.substring(sourceOffset, next));\r
1368 //                        resultArray[Integer.parseInt(argumentNames[i])] =\r
1369 //                            source.substring(sourceOffset, next);\r
1370                     sourceOffset = next;\r
1371                 }\r
1372             } else {\r
1373                 tempStatus.setIndex(sourceOffset);\r
1374                 resultMap.put(argumentNames[i], formats[i].parseObject(source, tempStatus));\r
1375 //                resultArray[Integer.parseInt(argumentNames[i])] =\r
1376 //                    formats[i].parseObject(source, tempStatus);\r
1377                 if (tempStatus.getIndex() == sourceOffset) {\r
1378                     pos.setErrorIndex(sourceOffset);\r
1379                     return null; // leave index as is to signal error\r
1380                 }\r
1381                 sourceOffset = tempStatus.getIndex(); // update\r
1382             }\r
1383         }\r
1384         int len = pattern.length() - patternOffset;\r
1385         if (len == 0 || pattern.regionMatches(patternOffset,\r
1386                                               source, sourceOffset, len)) {\r
1387             pos.setIndex(sourceOffset + len);\r
1388         } else {\r
1389             pos.setErrorIndex(sourceOffset);\r
1390             return null; // leave index as is to signal error\r
1391         }\r
1392         return resultMap;\r
1393     }\r
1394 \r
1395     /**\r
1396      * Parses text from the beginning of the given string to produce an object\r
1397      * array.\r
1398      * The method may not use the entire text of the given string.\r
1399      * <p>\r
1400      * See the {@link #parse(String, ParsePosition)} method for more information\r
1401      * on message parsing.\r
1402      *\r
1403      * @param source A <code>String</code> whose beginning should be parsed.\r
1404      * @return An <code>Object</code> array parsed from the string.\r
1405      * @exception ParseException\r
1406      *                if the beginning of the specified string cannot be parsed.\r
1407      * @exception IllegalArgumentException\r
1408      *                if this format uses named arguments\r
1409      * @stable ICU 3.0\r
1410      */\r
1411     public Object[] parse(String source) throws ParseException {\r
1412         ParsePosition pos = new ParsePosition(0);\r
1413         Object[] result = parse(source, pos);\r
1414         if (pos.getIndex() == 0) // unchanged, returned object is null\r
1415             throw new ParseException("MessageFormat parse error!",\r
1416                                      pos.getErrorIndex());\r
1417 \r
1418         return result;\r
1419     }\r
1420 \r
1421     /**\r
1422      * Parses text from the beginning of the given string to produce a map from\r
1423      * argument to values. The method may not use the entire text of the given string.\r
1424      * <p>\r
1425      * See the {@link #parse(String, ParsePosition)} method for more information\r
1426      * on message parsing.\r
1427      *\r
1428      * @param source A <code>String</code> whose beginning should be parsed.\r
1429      * @return A <code>Map</code> parsed from the string.\r
1430      * @throws ParseException if the beginning of the specified string cannot\r
1431      *         be parsed.\r
1432      * @see #parseToMap(String, ParsePosition)\r
1433      * @stable ICU 3.8\r
1434      */\r
1435     public Map parseToMap(String source) throws ParseException {\r
1436 \r
1437         ParsePosition pos = new ParsePosition(0);\r
1438         Map result = parseToMap(source, pos);\r
1439         if (pos.getIndex() == 0) // unchanged, returned object is null\r
1440             throw new ParseException("MessageFormat parse error!",\r
1441                                      pos.getErrorIndex());\r
1442 \r
1443         return result;\r
1444     }\r
1445 \r
1446     /**\r
1447      * Parses text from a string to produce an object array or Map.\r
1448      * <p>\r
1449      * The method attempts to parse text starting at the index given by\r
1450      * <code>pos</code>.\r
1451      * If parsing succeeds, then the index of <code>pos</code> is updated\r
1452      * to the index after the last character used (parsing does not necessarily\r
1453      * use all characters up to the end of the string), and the parsed\r
1454      * object array is returned. The updated <code>pos</code> can be used to\r
1455      * indicate the starting point for the next call to this method.\r
1456      * If an error occurs, then the index of <code>pos</code> is not\r
1457      * changed, the error index of <code>pos</code> is set to the index of\r
1458      * the character where the error occurred, and null is returned.\r
1459      * <p>\r
1460      * See the {@link #parse(String, ParsePosition)} method for more information\r
1461      * on message parsing.\r
1462      *\r
1463      * @param source A <code>String</code>, part of which should be parsed.\r
1464      * @param pos A <code>ParsePosition</code> object with index and error\r
1465      *            index information as described above.\r
1466      * @return An <code>Object</code> parsed from the string, either an\r
1467      *         array of Object, or a Map, depending on whether named\r
1468      *         arguments are used.  This can be queried using <code>usesNamedArguments</code>.\r
1469      *         In case of error, returns null.\r
1470      * @throws NullPointerException if <code>pos</code> is null.\r
1471      * @stable ICU 3.0\r
1472      */\r
1473     public Object parseObject(String source, ParsePosition pos) {\r
1474         if (argumentNamesAreNumeric) {\r
1475             return parse(source, pos);\r
1476         } else {\r
1477             return parseToMap(source, pos);\r
1478         }\r
1479     }\r
1480 \r
1481     /**\r
1482      * Creates and returns a copy of this object.\r
1483      *\r
1484      * @return a clone of this instance.\r
1485      * @stable ICU 3.0\r
1486      */\r
1487     public Object clone() {\r
1488         MessageFormat other = (MessageFormat) super.clone();\r
1489 \r
1490         // clone arrays. Can't do with utility because of bug in Cloneable\r
1491         other.formats = (Format[]) formats.clone(); // shallow clone\r
1492         for (int i = 0; i < formats.length; ++i) {\r
1493             if (formats[i] != null)\r
1494                 other.formats[i] = (Format) formats[i].clone();\r
1495         }\r
1496         // for primitives or immutables, shallow clone is enough\r
1497         other.offsets = (int[]) offsets.clone();\r
1498         other.argumentNames = (String[]) argumentNames.clone();\r
1499         other.argumentNamesAreNumeric = argumentNamesAreNumeric;\r
1500 \r
1501         return other;\r
1502     }\r
1503 \r
1504     /**\r
1505      * Equality comparison between two message format objects\r
1506      * @stable ICU 3.0\r
1507      */\r
1508     public boolean equals(Object obj) {\r
1509         if (this == obj)                      // quick check\r
1510             return true;\r
1511         if (obj == null || getClass() != obj.getClass())\r
1512             return false;\r
1513         MessageFormat other = (MessageFormat) obj;\r
1514         return (maxOffset == other.maxOffset\r
1515                 && pattern.equals(other.pattern)\r
1516                 && Utility.objectEquals(ulocale, other.ulocale) // does null check\r
1517                 && Utility.arrayEquals(offsets, other.offsets)\r
1518                 && Utility.arrayEquals(argumentNames, other.argumentNames)\r
1519                 && Utility.arrayEquals(formats, other.formats)\r
1520                 && (argumentNamesAreNumeric == other.argumentNamesAreNumeric));\r
1521     }\r
1522 \r
1523     /**\r
1524      * Generates a hash code for the message format object.\r
1525      * @stable ICU 3.0\r
1526      */\r
1527     public int hashCode() {\r
1528         return pattern.hashCode(); // enough for reasonable distribution\r
1529     }\r
1530 \r
1531 //#if defined(FOUNDATION10) || defined(J2SE13)\r
1532 //#else\r
1533     /**\r
1534      * Defines constants that are used as attribute keys in the\r
1535      * <code>AttributedCharacterIterator</code> returned\r
1536      * from <code>MessageFormat.formatToCharacterIterator</code>.\r
1537      *\r
1538      * @stable ICU 3.8\r
1539      */\r
1540     public static class Field extends Format.Field {\r
1541 \r
1542         private static final long serialVersionUID = 7510380454602616157L;\r
1543 \r
1544         /**\r
1545          * Create a <code>Field</code> with the specified name.\r
1546          *\r
1547          * @param name The name of the attribute\r
1548          *\r
1549          * @stable ICU 3.8\r
1550          */\r
1551         protected Field(String name) {\r
1552             super(name);\r
1553         }\r
1554 \r
1555         /**\r
1556          * Resolves instances being deserialized to the predefined constants.\r
1557          *\r
1558          * @return resolved MessageFormat.Field constant\r
1559          * @throws InvalidObjectException if the constant could not be resolved.\r
1560          *\r
1561          * @stable ICU 3.8\r
1562          */\r
1563         protected Object readResolve() throws InvalidObjectException {\r
1564             if (this.getClass() != MessageFormat.Field.class) {\r
1565                 throw new InvalidObjectException("A subclass of MessageFormat.Field must implement readResolve.");\r
1566             }\r
1567             if (this.getName().equals(ARGUMENT.getName())) {\r
1568                 return ARGUMENT;\r
1569             } else {\r
1570                 throw new InvalidObjectException("Unknown attribute name.");\r
1571             }\r
1572         }\r
1573 \r
1574         /**\r
1575          * Constant identifying a portion of a message that was generated\r
1576          * from an argument passed into <code>formatToCharacterIterator</code>.\r
1577          * The value associated with the key will be an <code>Integer</code>\r
1578          * indicating the index in the <code>arguments</code> array of the\r
1579          * argument from which the text was generated.\r
1580          *\r
1581          * @stable ICU 3.8\r
1582          */\r
1583         public static final Field ARGUMENT = new Field("message argument field");\r
1584 \r
1585     }\r
1586 //#endif\r
1587 \r
1588     // ===========================privates============================\r
1589 \r
1590     /**\r
1591      * The locale to use for formatting numbers and dates.\r
1592      * This is no longer used, and here only for serialization compatibility.\r
1593      * @serial\r
1594      */\r
1595     private Locale locale;\r
1596 \r
1597     /**\r
1598      * The locale to use for formatting numbers and dates.\r
1599      * @serial\r
1600      */\r
1601     private ULocale ulocale;\r
1602 \r
1603     /**\r
1604      * The string that the formatted values are to be plugged into.  In other words, this\r
1605      * is the pattern supplied on construction with all of the {} expressions taken out.\r
1606      * @serial\r
1607      */\r
1608     private String pattern = "";\r
1609 \r
1610     /** The initially expected number of subformats in the format */\r
1611     private static final int INITIAL_FORMATS = 10;\r
1612 \r
1613     /**\r
1614      * An array of formatters, which are used to format the arguments.\r
1615      * @serial\r
1616      */\r
1617     private Format[] formats = new Format[INITIAL_FORMATS];\r
1618 \r
1619     /**\r
1620      * The positions where the results of formatting each argument are to be\r
1621      * inserted into the pattern.\r
1622      *\r
1623      * @serial\r
1624      */\r
1625     private int[] offsets = new int[INITIAL_FORMATS];\r
1626 \r
1627     /**\r
1628      * The argument numbers corresponding to each formatter.  (The formatters are stored\r
1629      * in the order they occur in the pattern, not in the order in which the arguments\r
1630      * are specified.)\r
1631      * @serial\r
1632      */\r
1633     // retained for backwards compatibility\r
1634     private int[] argumentNumbers = new int[INITIAL_FORMATS];\r
1635 \r
1636     /**\r
1637      * The argument names corresponding to each formatter. (The formatters are\r
1638      * stored in the order they occur in the pattern, not in the order in which\r
1639      * the arguments are specified.)\r
1640      *\r
1641      * @serial\r
1642      */\r
1643     private String[] argumentNames = new String[INITIAL_FORMATS];\r
1644 \r
1645     /**\r
1646      * Is true iff all argument names are non-negative numbers.\r
1647      *\r
1648      * @serial\r
1649      */\r
1650     private boolean argumentNamesAreNumeric = true;\r
1651 \r
1652     /**\r
1653      * One less than the number of entries in <code>offsets</code>.  Can also be thought of\r
1654      * as the index of the highest-numbered element in <code>offsets</code> that is being used.\r
1655      * All of these arrays should have the same number of elements being used as <code>offsets</code>\r
1656      * does, and so this variable suffices to tell us how many entries are in all of them.\r
1657      * @serial\r
1658      */\r
1659     private int maxOffset = -1;\r
1660 \r
1661     /**\r
1662      * Internal routine used by format. If <code>characterIterators</code> is\r
1663      * non-null, AttributedCharacterIterator will be created from the\r
1664      * subformats as necessary. If <code>characterIterators</code> is null\r
1665      * and <code>fp</code> is non-null and identifies\r
1666      * <code>Field.MESSAGE_ARGUMENT</code>, the location of\r
1667      * the first replaced argument will be set in it.\r
1668      *\r
1669      * @exception IllegalArgumentException if an argument in the\r
1670      *            <code>arguments</code> array is not of the type\r
1671      *            expected by the format element(s) that use it.\r
1672      */\r
1673     private StringBuffer subformat(Object[] arguments, StringBuffer result,\r
1674                                    FieldPosition fp, List characterIterators) {\r
1675         return subformat(arrayToMap(arguments), result, fp, characterIterators);\r
1676     }\r
1677 \r
1678     /**\r
1679      * Internal routine used by format.\r
1680      *\r
1681      * @throws IllegalArgumentException if an argument in the\r
1682      *         <code>arguments</code> map is not of the type\r
1683      *         expected by the format element(s) that use it.\r
1684      */\r
1685     private StringBuffer subformat(Map arguments, StringBuffer result,\r
1686                                    FieldPosition fp, List characterIterators) {\r
1687         // note: this implementation assumes a fast substring & index.\r
1688         // if this is not true, would be better to append chars one by one.\r
1689         int lastOffset = 0;\r
1690 //#if defined(FOUNDATION10) || defined(J2SE13)\r
1691 //#else\r
1692         int last = result.length();\r
1693 //#endif\r
1694         for (int i = 0; i <= maxOffset; ++i) {\r
1695             result.append(pattern.substring(lastOffset, offsets[i]));\r
1696             lastOffset = offsets[i];\r
1697             String argumentName = argumentNames[i];\r
1698             if (arguments == null || !arguments.containsKey(argumentName)) {\r
1699                 result.append("{" + argumentName + "}");\r
1700                 continue;\r
1701             }\r
1702             // int argRecursion = ((recursionProtection >> (argumentNumber*2)) & 0x3);\r
1703             if (false) { // if (argRecursion == 3){\r
1704                 // prevent loop!!!\r
1705                 result.append('\uFFFD');\r
1706             } else {\r
1707                 Object obj = arguments.get(argumentName);\r
1708                 String arg = null;\r
1709                 Format subFormatter = null;\r
1710                 if (obj == null) {\r
1711                     arg = "null";\r
1712                 } else if (formats[i] != null) {\r
1713                     subFormatter = formats[i];\r
1714                     if (subFormatter instanceof ChoiceFormat\r
1715                         || subFormatter instanceof PluralFormat) {\r
1716                         arg = formats[i].format(obj);\r
1717                         // TODO: This should be made more robust.\r
1718                         //       Does this work with '{' in quotes?\r
1719                         if (arg.indexOf('{') >= 0) {\r
1720                             subFormatter = new MessageFormat(arg, ulocale);\r
1721                             obj = arguments;\r
1722                             arg = null;\r
1723                         }\r
1724                     }\r
1725                 } else if (obj instanceof Number) {\r
1726                     // format number if can\r
1727                     subFormatter = NumberFormat.getInstance(ulocale);\r
1728                 } else if (obj instanceof Date) {\r
1729                     // format a Date if can\r
1730                     subFormatter = DateFormat.getDateTimeInstance(\r
1731                              DateFormat.SHORT, DateFormat.SHORT, ulocale);//fix\r
1732                 } else if (obj instanceof String) {\r
1733                     arg = (String) obj;\r
1734 \r
1735                 } else {\r
1736                     arg = obj.toString();\r
1737                     if (arg == null) arg = "null";\r
1738                 }\r
1739 \r
1740                 // At this point we are in two states, either subFormatter\r
1741                 // is non-null indicating we should format obj using it,\r
1742                 // or arg is non-null and we should use it as the value.\r
1743 \r
1744 //#if defined(FOUNDATION10) || defined(J2SE13)\r
1745 //##                if (subFormatter != null) {\r
1746 //##                    arg = subFormatter.format(obj);\r
1747 //##                }\r
1748 //##                result.append(arg);\r
1749 //#else\r
1750                 if (characterIterators != null) {\r
1751                     // If characterIterators is non-null, it indicates we need\r
1752                     // to get the CharacterIterator from the child formatter.\r
1753                     if (last != result.length()) {\r
1754                         characterIterators.add(\r
1755                             _createAttributedCharacterIterator(result.substring\r
1756                                                               (last)));\r
1757                         last = result.length();\r
1758                     }\r
1759                     if (subFormatter != null) {\r
1760                         AttributedCharacterIterator subIterator =\r
1761                                    subFormatter.formatToCharacterIterator(obj);\r
1762 \r
1763                         append(result, subIterator);\r
1764                         if (last != result.length()) {\r
1765                             characterIterators.add(\r
1766                                          _createAttributedCharacterIterator(\r
1767                                          subIterator, Field.ARGUMENT,\r
1768                                          argumentNamesAreNumeric ? (Object)new Integer(argumentName) : (Object)argumentName));\r
1769                             last = result.length();\r
1770                         }\r
1771                         arg = null;\r
1772                     }\r
1773                     if (arg != null && arg.length() > 0) {\r
1774                         result.append(arg);\r
1775                         characterIterators.add(\r
1776                                  _createAttributedCharacterIterator(\r
1777                                  arg, Field.ARGUMENT,\r
1778                                  argumentNamesAreNumeric ? (Object)new Integer(argumentName) : (Object)argumentName));\r
1779                         last = result.length();\r
1780                     }\r
1781                 } else {\r
1782                     if (subFormatter != null) {\r
1783                         arg = subFormatter.format(obj);\r
1784                     }\r
1785                     last = result.length();\r
1786                     result.append(arg);\r
1787                     if (i == 0 && fp != null && Field.ARGUMENT.equals(\r
1788                                   fp.getFieldAttribute())) {\r
1789                         fp.setBeginIndex(last);\r
1790                         fp.setEndIndex(result.length());\r
1791                     }\r
1792                     last = result.length();\r
1793                 }\r
1794 //#endif\r
1795             }\r
1796         }\r
1797         result.append(pattern.substring(lastOffset, pattern.length()));\r
1798 //#if defined(FOUNDATION10) || defined(J2SE13)\r
1799 //#else\r
1800         if (characterIterators != null && last != result.length()) {\r
1801             characterIterators.add(_createAttributedCharacterIterator(\r
1802                                    result.substring(last)));\r
1803         }\r
1804 //#endif\r
1805         return result;\r
1806     }\r
1807 \r
1808 //#if defined(FOUNDATION10) || defined(J2SE13)\r
1809 //#else\r
1810     /**\r
1811      * Convenience method to append all the characters in\r
1812      * <code>iterator</code> to the StringBuffer <code>result</code>.\r
1813      */\r
1814     private void append(StringBuffer result, CharacterIterator iterator) {\r
1815         if (iterator.first() != CharacterIterator.DONE) {\r
1816             char aChar;\r
1817 \r
1818             result.append(iterator.first());\r
1819             while ((aChar = iterator.next()) != CharacterIterator.DONE) {\r
1820                 result.append(aChar);\r
1821             }\r
1822         }\r
1823     }\r
1824 //#endif\r
1825 \r
1826     private static final String[] typeList =\r
1827         {"", "number", "date", "time", "choice", "spellout", "ordinal",\r
1828          "duration", "plural"};\r
1829     private static final int\r
1830         TYPE_EMPTY = 0,\r
1831         TYPE_NUMBER = 1,\r
1832         TYPE_DATE = 2,\r
1833         TYPE_TIME = 3,\r
1834         TYPE_CHOICE = 4,\r
1835         TYPE_SPELLOUT = 5,\r
1836         TYPE_ORDINAL = 6,\r
1837         TYPE_DURATION = 7,\r
1838         TYPE_PLURAL = 8;\r
1839 \r
1840     private static final String[] modifierList =\r
1841         {"", "currency", "percent", "integer"};\r
1842 \r
1843     private static final int\r
1844         MODIFIER_EMPTY = 0,\r
1845         MODIFIER_CURRENCY = 1,\r
1846         MODIFIER_PERCENT = 2,\r
1847         MODIFIER_INTEGER = 3;\r
1848 \r
1849     private static final String[] dateModifierList =\r
1850         {"", "short", "medium", "long", "full"};\r
1851 \r
1852     private static final int\r
1853         DATE_MODIFIER_EMPTY = 0,\r
1854         DATE_MODIFIER_SHORT = 1,\r
1855         DATE_MODIFIER_MEDIUM = 2,\r
1856         DATE_MODIFIER_LONG = 3,\r
1857         DATE_MODIFIER_FULL = 4;\r
1858 \r
1859     private void makeFormat(int position, int offsetNumber,\r
1860                             StringBuffer[] segments)\r
1861     {\r
1862         // get the argument number\r
1863         // int argumentNumber;\r
1864         // try {\r
1865         //     argumentNumber = Integer.parseInt(segments[1].toString()); // always unlocalized!\r
1866         // } catch (NumberFormatException e) {\r
1867         //    throw new IllegalArgumentException("can't parse argument number "\r
1868         //            + segments[1]);\r
1869         //}\r
1870         // if (argumentNumber < 0) {\r
1871         //    throw new IllegalArgumentException("negative argument number "\r
1872         //            + argumentNumber);\r
1873         //}\r
1874 \r
1875         // resize format information arrays if necessary\r
1876         if (offsetNumber >= formats.length) {\r
1877             int newLength = formats.length * 2;\r
1878             Format[] newFormats = new Format[newLength];\r
1879             int[] newOffsets = new int[newLength];\r
1880             String[] newArgumentNames = new String[newLength];\r
1881             System.arraycopy(formats, 0, newFormats, 0, maxOffset + 1);\r
1882             System.arraycopy(offsets, 0, newOffsets, 0, maxOffset + 1);\r
1883             System.arraycopy(argumentNames, 0, newArgumentNames, 0,\r
1884                     maxOffset + 1);\r
1885             formats = newFormats;\r
1886             offsets = newOffsets;\r
1887             argumentNames = newArgumentNames;\r
1888         }\r
1889         int oldMaxOffset = maxOffset;\r
1890         maxOffset = offsetNumber;\r
1891         offsets[offsetNumber] = segments[0].length();\r
1892         argumentNames[offsetNumber] = segments[1].toString();\r
1893         // All argument names numeric ?\r
1894         int argumentNumber;\r
1895         try {\r
1896             // always unlocalized!\r
1897              argumentNumber = Integer.parseInt(segments[1].toString());\r
1898          } catch (NumberFormatException e) {\r
1899              argumentNumber = -1;\r
1900          }\r
1901          if (offsetNumber == 0) {\r
1902              // First argument determines whether all argument identifiers have\r
1903              // to be numbers or (IDStartChars IDContChars*) strings.\r
1904              argumentNamesAreNumeric = argumentNumber >= 0;\r
1905          }\r
1906 \r
1907          if (argumentNamesAreNumeric && argumentNumber < 0 ||\r
1908              !argumentNamesAreNumeric &&\r
1909              !isAlphaIdentifier(argumentNames[offsetNumber])) {\r
1910              throw new IllegalArgumentException(\r
1911                      "All argument identifiers have to be either non-negative " +\r
1912                      "numbers or strings following the pattern " +\r
1913                      "([:ID_Start:] [:ID_Continue:]*).\n" +\r
1914                      "For more details on these unicode sets, visit " +\r
1915                      "http://demo.icu-project.org/icu-bin/ubrowse");\r
1916          }\r
1917 \r
1918         // now get the format\r
1919         Format newFormat = null;\r
1920         switch (findKeyword(segments[2].toString(), typeList)) {\r
1921         case TYPE_EMPTY:\r
1922             break;\r
1923         case TYPE_NUMBER:\r
1924             switch (findKeyword(segments[3].toString(), modifierList)) {\r
1925             case MODIFIER_EMPTY:\r
1926                 newFormat = NumberFormat.getInstance(ulocale);\r
1927                 break;\r
1928             case MODIFIER_CURRENCY:\r
1929                 newFormat = NumberFormat.getCurrencyInstance(ulocale);\r
1930                 break;\r
1931             case MODIFIER_PERCENT:\r
1932                 newFormat = NumberFormat.getPercentInstance(ulocale);\r
1933                 break;\r
1934             case MODIFIER_INTEGER:\r
1935                 newFormat = NumberFormat.getIntegerInstance(ulocale);\r
1936                 break;\r
1937             default: // pattern\r
1938                 newFormat = new DecimalFormat(segments[3].toString(), new DecimalFormatSymbols(ulocale));\r
1939                 break;\r
1940             }\r
1941             break;\r
1942         case TYPE_DATE:\r
1943             switch (findKeyword(segments[3].toString(), dateModifierList)) {\r
1944             case DATE_MODIFIER_EMPTY:\r
1945                 newFormat = DateFormat.getDateInstance(DateFormat.DEFAULT, ulocale);\r
1946                 break;\r
1947             case DATE_MODIFIER_SHORT:\r
1948                 newFormat = DateFormat.getDateInstance(DateFormat.SHORT, ulocale);\r
1949                 break;\r
1950             case DATE_MODIFIER_MEDIUM:\r
1951                 newFormat = DateFormat.getDateInstance(DateFormat.DEFAULT, ulocale);\r
1952                 break;\r
1953             case DATE_MODIFIER_LONG:\r
1954                 newFormat = DateFormat.getDateInstance(DateFormat.LONG, ulocale);\r
1955                 break;\r
1956             case DATE_MODIFIER_FULL:\r
1957                 newFormat = DateFormat.getDateInstance(DateFormat.FULL, ulocale);\r
1958                 break;\r
1959             default:\r
1960                 newFormat = new SimpleDateFormat(segments[3].toString(), ulocale);\r
1961                 break;\r
1962             }\r
1963             break;\r
1964         case TYPE_TIME:\r
1965             switch (findKeyword(segments[3].toString(), dateModifierList)) {\r
1966             case DATE_MODIFIER_EMPTY:\r
1967                 newFormat = DateFormat.getTimeInstance(DateFormat.DEFAULT, ulocale);\r
1968                 break;\r
1969             case DATE_MODIFIER_SHORT:\r
1970                 newFormat = DateFormat.getTimeInstance(DateFormat.SHORT, ulocale);\r
1971                 break;\r
1972             case DATE_MODIFIER_MEDIUM:\r
1973                 newFormat = DateFormat.getTimeInstance(DateFormat.DEFAULT, ulocale);\r
1974                 break;\r
1975             case DATE_MODIFIER_LONG:\r
1976                 newFormat = DateFormat.getTimeInstance(DateFormat.LONG, ulocale);\r
1977                 break;\r
1978             case DATE_MODIFIER_FULL:\r
1979                 newFormat = DateFormat.getTimeInstance(DateFormat.FULL, ulocale);\r
1980                 break;\r
1981             default:\r
1982                 newFormat = new SimpleDateFormat(segments[3].toString(), ulocale);\r
1983                 break;\r
1984             }\r
1985             break;\r
1986         case TYPE_CHOICE:\r
1987             try {\r
1988                 newFormat = new ChoiceFormat(segments[3].toString());\r
1989             } catch (Exception e) {\r
1990                 maxOffset = oldMaxOffset;\r
1991                 throw new IllegalArgumentException("Choice Pattern incorrect");\r
1992             }\r
1993             break;\r
1994         case TYPE_SPELLOUT:\r
1995             {\r
1996                 RuleBasedNumberFormat rbnf = new RuleBasedNumberFormat(ulocale, RuleBasedNumberFormat.SPELLOUT);\r
1997                 String ruleset = segments[3].toString().trim();\r
1998                 if (ruleset.length() != 0) {\r
1999                     try {\r
2000                         rbnf.setDefaultRuleSet(ruleset);\r
2001                     }\r
2002                     catch (Exception e) {\r
2003                         // warn invalid ruleset\r
2004                     }\r
2005                 }\r
2006                 newFormat = rbnf;\r
2007             }\r
2008             break;\r
2009         case TYPE_ORDINAL:\r
2010             {\r
2011                 RuleBasedNumberFormat rbnf = new RuleBasedNumberFormat(ulocale, RuleBasedNumberFormat.ORDINAL);\r
2012                 String ruleset = segments[3].toString().trim();\r
2013                 if (ruleset.length() != 0) {\r
2014                     try {\r
2015                         rbnf.setDefaultRuleSet(ruleset);\r
2016                     }\r
2017                     catch (Exception e) {\r
2018                         // warn invalid ruleset\r
2019                     }\r
2020                 }\r
2021                 newFormat = rbnf;\r
2022             }\r
2023             break;\r
2024         case TYPE_DURATION:\r
2025             {\r
2026                 RuleBasedNumberFormat rbnf = new RuleBasedNumberFormat(ulocale, RuleBasedNumberFormat.DURATION);\r
2027                 String ruleset = segments[3].toString().trim();\r
2028                 if (ruleset.length() != 0) {\r
2029                     try {\r
2030                         rbnf.setDefaultRuleSet(ruleset);\r
2031                     }\r
2032                     catch (Exception e) {\r
2033                         // warn invalid ruleset\r
2034                     }\r
2035                 }\r
2036                 newFormat = rbnf;\r
2037             }\r
2038             break;\r
2039         case TYPE_PLURAL:\r
2040             {\r
2041                 // PluralFormat does not handle quotes.\r
2042                 // Remove quotes.\r
2043                 // TODO: Should PluralFormat handle quotes?\r
2044                 StringBuffer unquotedPattern = new StringBuffer();\r
2045                 String quotedPattern = segments[3].toString();\r
2046                 boolean inQuote = false;\r
2047                 for (int i = 0; i < quotedPattern.length(); ++i) {\r
2048                     char ch = quotedPattern.charAt(i);\r
2049                     if (ch == '\'') {\r
2050                         if (i+1 < quotedPattern.length() &&\r
2051                             quotedPattern.charAt(i+1) == '\'') {\r
2052                                 unquotedPattern.append(ch);\r
2053                                 ++i;\r
2054                             } else {\r
2055                                 inQuote = !inQuote;\r
2056                             }\r
2057                     } else {\r
2058                         unquotedPattern.append(ch);\r
2059                     }\r
2060                 }\r
2061 \r
2062                 PluralFormat pls = new PluralFormat(ulocale,\r
2063                                                     unquotedPattern.toString());\r
2064                 newFormat = pls;\r
2065             }\r
2066             break;\r
2067         default:\r
2068             maxOffset = oldMaxOffset;\r
2069             throw new IllegalArgumentException("unknown format type at ");\r
2070         }\r
2071         formats[offsetNumber] = newFormat;\r
2072         segments[1].setLength(0);   // throw away other segments\r
2073         segments[2].setLength(0);\r
2074         segments[3].setLength(0);\r
2075     }\r
2076 \r
2077     private static final int findKeyword(String s, String[] list) {\r
2078         s = s.trim().toLowerCase();\r
2079         for (int i = 0; i < list.length; ++i) {\r
2080             if (s.equals(list[i]))\r
2081                 return i;\r
2082         }\r
2083         return -1;\r
2084     }\r
2085 \r
2086     private static final void copyAndFixQuotes(String source, int start, int end, StringBuffer target) {\r
2087         // added 'gotLB' logic from ICU4C - questionable [alan]\r
2088         boolean gotLB = false;\r
2089         for (int i = start; i < end; ++i) {\r
2090             char ch = source.charAt(i);\r
2091             if (ch == '{') {\r
2092                 target.append("'{'");\r
2093                 gotLB = true;\r
2094             } else if (ch == '}') {\r
2095                 if (gotLB) {\r
2096                     target.append(ch);\r
2097                     gotLB = false;\r
2098                 } else {\r
2099                     target.append("'}'");\r
2100                 }\r
2101             } else if (ch == '\'') {\r
2102                 target.append("''");\r
2103             } else {\r
2104                 target.append(ch);\r
2105             }\r
2106         }\r
2107     }\r
2108 \r
2109     /**\r
2110      * After reading an object from the input stream, do a simple verification\r
2111      * to maintain class invariants.\r
2112      * @throws InvalidObjectException if the objects read from the stream is invalid.\r
2113      */\r
2114     private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {\r
2115         in.defaultReadObject();\r
2116         if (argumentNames == null) { // name mod, rev\r
2117           argumentNamesAreNumeric = true;\r
2118           argumentNames = new String[argumentNumbers.length];\r
2119           for (int i = 0; i < argumentNumbers.length; ++i) {\r
2120             argumentNames[i] = String.valueOf(argumentNumbers[i]);\r
2121           }\r
2122         }\r
2123         boolean isValid = maxOffset >= -1\r
2124                 && formats.length > maxOffset\r
2125                 && offsets.length > maxOffset\r
2126                 && argumentNames.length > maxOffset;\r
2127         if (isValid) {\r
2128             int lastOffset = pattern.length() + 1;\r
2129             for (int i = maxOffset; i >= 0; --i) {\r
2130                 if ((offsets[i] < 0) || (offsets[i] > lastOffset)) {\r
2131                     isValid = false;\r
2132                     break;\r
2133                 } else {\r
2134                     lastOffset = offsets[i];\r
2135                 }\r
2136             }\r
2137         }\r
2138         if (!isValid) {\r
2139             throw new InvalidObjectException("Could not reconstruct MessageFormat from corrupt stream.");\r
2140         }\r
2141         if (ulocale == null) {\r
2142             ulocale = ULocale.forLocale(locale);\r
2143         }\r
2144     }\r
2145 \r
2146     /**\r
2147      * This is a helper method for converting an object array into a map. The\r
2148      * key set of the map is [0, ..., array.length]. The value associated with\r
2149      * each key is the ith entry of the passed object array.\r
2150      *\r
2151      * @throws InvalidObjectException\r
2152      *             if the objects read from the stream is invalid.\r
2153      */\r
2154     private Map arrayToMap(Object[] array) {\r
2155         Map map = new HashMap();\r
2156         if (array != null) {\r
2157             for (int i = 0; i < array.length; ++i) {\r
2158                 map.put(Integer.toString(i), array[i]);\r
2159             }\r
2160         }\r
2161         return map;\r
2162     }\r
2163 \r
2164     private boolean isAlphaIdentifier(String argument) {\r
2165         if (argument.length() == 0) {\r
2166             return false;\r
2167         }\r
2168         for (int i = 0; i < argument.length(); ++i ) {\r
2169             if (i == 0 && !IDStartChars.contains(argument.charAt(i)) ||\r
2170                 i > 0 &&  !IDContChars.contains(argument.charAt(i))){\r
2171                 return false;\r
2172             }\r
2173         }\r
2174         return true;\r
2175     }\r
2176 \r
2177     private static final char SINGLE_QUOTE = '\'';\r
2178     private static final char CURLY_BRACE_LEFT = '{';\r
2179     private static final char CURLY_BRACE_RIGHT = '}';\r
2180 \r
2181     private static final int STATE_INITIAL = 0;\r
2182     private static final int STATE_SINGLE_QUOTE = 1;\r
2183     private static final int STATE_IN_QUOTE = 2;\r
2184     private static final int STATE_MSG_ELEMENT = 3;\r
2185 \r
2186     private static UnicodeSet IDStartChars = new UnicodeSet("[:ID_Start:]");\r
2187     private static UnicodeSet IDContChars = new UnicodeSet("[:ID_Continue:]");\r
2188 \r
2189     /**\r
2190      * Convert an 'apostrophe-friendly' pattern into a standard\r
2191      * pattern.  Standard patterns treat all apostrophes as\r
2192      * quotes, which is problematic in some languages, e.g.\r
2193      * French, where apostrophe is commonly used.  This utility\r
2194      * assumes that only an unpaired apostrophe immediately before\r
2195      * a brace is a true quote.  Other unpaired apostrophes are paired,\r
2196      * and the resulting standard pattern string is returned.\r
2197      *\r
2198      * <p><b>Note</b> it is not guaranteed that the returned pattern\r
2199      * is indeed a valid pattern.  The only effect is to convert\r
2200      * between patterns having different quoting semantics.\r
2201      *\r
2202      * @param pattern the 'apostrophe-friendly' patttern to convert\r
2203      * @return the standard equivalent of the original pattern\r
2204      * @stable ICU 3.4\r
2205      */\r
2206     public static String autoQuoteApostrophe(String pattern) {\r
2207         StringBuffer buf = new StringBuffer(pattern.length() * 2);\r
2208         int state = STATE_INITIAL;\r
2209         int braceCount = 0;\r
2210         for (int i = 0, j = pattern.length(); i < j; ++i) {\r
2211             char c = pattern.charAt(i);\r
2212             switch (state) {\r
2213             case STATE_INITIAL:\r
2214                 switch (c) {\r
2215                 case SINGLE_QUOTE:\r
2216                     state = STATE_SINGLE_QUOTE;\r
2217                     break;\r
2218                 case CURLY_BRACE_LEFT:\r
2219                     state = STATE_MSG_ELEMENT;\r
2220                     ++braceCount;\r
2221                     break;\r
2222                 }\r
2223                 break;\r
2224             case STATE_SINGLE_QUOTE:\r
2225                 switch (c) {\r
2226                 case SINGLE_QUOTE:\r
2227                     state = STATE_INITIAL;\r
2228                     break;\r
2229                 case CURLY_BRACE_LEFT:\r
2230                 case CURLY_BRACE_RIGHT:\r
2231                     state = STATE_IN_QUOTE;\r
2232                     break;\r
2233                 default:\r
2234                     buf.append(SINGLE_QUOTE);\r
2235                     state = STATE_INITIAL;\r
2236                     break;\r
2237                 }\r
2238                 break;\r
2239             case STATE_IN_QUOTE:\r
2240                 switch (c) {\r
2241                 case SINGLE_QUOTE:\r
2242                     state = STATE_INITIAL;\r
2243                     break;\r
2244                 }\r
2245                 break;\r
2246             case STATE_MSG_ELEMENT:\r
2247                 switch (c) {\r
2248                 case CURLY_BRACE_LEFT:\r
2249                     ++braceCount;\r
2250                     break;\r
2251                 case CURLY_BRACE_RIGHT:\r
2252                     if (--braceCount == 0) {\r
2253                         state = STATE_INITIAL;\r
2254                     }\r
2255                     break;\r
2256                 }\r
2257                 break;\r
2258             default: // Never happens.\r
2259                 break;\r
2260             }\r
2261             buf.append(c);\r
2262         }\r
2263         // End of scan\r
2264         if (state == STATE_SINGLE_QUOTE || state == STATE_IN_QUOTE) {\r
2265             buf.append(SINGLE_QUOTE);\r
2266         }\r
2267         return new String(buf);\r
2268     }\r
2269 \r
2270 //#if defined(FOUNDATION10) || defined(J2SE13)\r
2271 //#else\r
2272     //\r
2273     // private methods for AttributedCharacterIterator support\r
2274     //\r
2275     // Note: The equivalent methods are defined as package local methods in\r
2276     //       java.text.Format.  ICU cannot access these methods, so we have\r
2277     //       these methods locally, with "_" prefix for avoiding name collision.\r
2278     //       (The collision itself is not a problem, but Eclipse displays warnings\r
2279     //       by the default warning level.)  We may move these utility methods\r
2280     //       up to com.ibm.icu.text.UFormat later.  Yoshito\r
2281 \r
2282     private static AttributedCharacterIterator _createAttributedCharacterIterator(String text) {\r
2283         AttributedString as = new AttributedString(text);\r
2284         return as.getIterator();\r
2285     }\r
2286 \r
2287     private static AttributedCharacterIterator _createAttributedCharacterIterator(AttributedCharacterIterator[] iterators) {\r
2288         if (iterators == null || iterators.length == 0) {\r
2289             return _createAttributedCharacterIterator("");\r
2290         }\r
2291         // Create a single AttributedString\r
2292         StringBuffer sb = new StringBuffer();\r
2293         for (int i = 0; i < iterators.length; i++) {\r
2294             int index = iterators[i].getBeginIndex();\r
2295             int end = iterators[i].getEndIndex();\r
2296             while (index < end) {\r
2297                 sb.append(iterators[i].setIndex(index++));\r
2298             }\r
2299         }\r
2300         AttributedString as = new AttributedString(sb.toString());\r
2301 \r
2302         // Set attributes\r
2303         int offset = 0;\r
2304         for (int i = 0; i < iterators.length; i++) {\r
2305             iterators[i].first();\r
2306             int start = iterators[i].getBeginIndex();\r
2307             while (true) {\r
2308                 Map map = iterators[i].getAttributes();\r
2309                 int len = iterators[i].getRunLimit() - start; // run length\r
2310                 if (map.size() > 0) {\r
2311                     Iterator eit = map.entrySet().iterator();\r
2312                     while (eit.hasNext()) {\r
2313                         Map.Entry entry = (Map.Entry)eit.next();\r
2314                         as.addAttribute((AttributedCharacterIterator.Attribute)entry.getKey(), entry.getValue(),\r
2315                                 offset, offset + len);\r
2316                     }\r
2317                 }\r
2318                 offset += len;\r
2319                 start += len;\r
2320                 iterators[i].setIndex(start);\r
2321                 if (iterators[i].current() == CharacterIterator.DONE) {\r
2322                     break;\r
2323                 }\r
2324             }\r
2325         }\r
2326 \r
2327         return as.getIterator();\r
2328     }\r
2329 \r
2330     private static AttributedCharacterIterator _createAttributedCharacterIterator(AttributedCharacterIterator iterator,\r
2331             AttributedCharacterIterator.Attribute key, Object value) {\r
2332         AttributedString as = new AttributedString(iterator);\r
2333         as.addAttribute(key, value);\r
2334         return as.getIterator();\r
2335     }\r
2336 \r
2337     private static AttributedCharacterIterator _createAttributedCharacterIterator(String text,\r
2338             AttributedCharacterIterator.Attribute key, Object value) {\r
2339         AttributedString as = new AttributedString(text);\r
2340         as.addAttribute(key, value);\r
2341         return as.getIterator();\r
2342     }\r
2343 //#endif\r
2344 }\r