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