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