1/*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements.  See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License.  You may obtain a copy of the License at
8 *
9 *     http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18package java.text;
19
20import java.io.IOException;
21import java.io.ObjectInputStream;
22import java.io.ObjectOutputStream;
23import java.io.ObjectStreamField;
24import java.util.ArrayList;
25import java.util.Arrays;
26import java.util.Date;
27import java.util.Iterator;
28import java.util.List;
29import java.util.Locale;
30import java.util.Vector;
31import libcore.util.EmptyArray;
32
33/**
34 * Produces concatenated messages in language-neutral way. New code
35 * should probably use {@link java.util.Formatter} instead.
36 * <p>
37 * {@code MessageFormat} takes a set of objects, formats them and then
38 * inserts the formatted strings into the pattern at the appropriate places.
39 * <p>
40 * <strong>Note:</strong> {@code MessageFormat} differs from the other
41 * {@code Format} classes in that you create a {@code MessageFormat}
42 * object with one of its constructors (not with a {@code getInstance}
43 * style factory method). The factory methods aren't necessary because
44 * {@code MessageFormat} itself doesn't implement locale-specific
45 * behavior. Any locale-specific behavior is defined by the pattern that you
46 * provide as well as the subformats used for inserted arguments.
47 *
48 * <h4><a name="patterns">Patterns and their interpretation</a></h4>
49 *
50 * {@code MessageFormat} uses patterns of the following form:
51 * <blockquote>
52 *
53 * <pre>
54 * <i>MessageFormatPattern:</i>
55 *         <i>String</i>
56 *         <i>MessageFormatPattern</i> <i>FormatElement</i> <i>String</i>
57 * <i>FormatElement:</i>
58 *         { <i>ArgumentIndex</i> }
59 *         { <i>ArgumentIndex</i> , <i>FormatType</i> }
60 *         { <i>ArgumentIndex</i> , <i>FormatType</i> , <i>FormatStyle</i> }
61 * <i>FormatType: one of </i>
62 *         number date time choice
63 * <i>FormatStyle:</i>
64 *         short
65 *         medium
66 *         long
67 *         full
68 *         integer
69 *         currency
70 *         percent
71 *         <i>SubformatPattern</i>
72 * <i>String:</i>
73 *         <i>StringPart&lt;sub&gt;opt&lt;/sub&gt;</i>
74 *         <i>String</i> <i>StringPart</i>
75 * <i>StringPart:</i>
76 *         ''
77 *         ' <i>QuotedString</i> '
78 *         <i>UnquotedString</i>
79 * <i>SubformatPattern:</i>
80 *         <i>SubformatPatternPart&lt;sub&gt;opt&lt;/sub&gt;</i>
81 *         <i>SubformatPattern</i> <i>SubformatPatternPart</i>
82 * <i>SubFormatPatternPart:</i>
83 *         ' <i>QuotedPattern</i> '
84 *         <i>UnquotedPattern</i>
85 * </pre>
86 *
87 * </blockquote>
88 *
89 * <p>
90 * Within a <i>String</i>, {@code "''"} represents a single quote. A
91 * <i>QuotedString</i> can contain arbitrary characters except single quotes;
92 * the surrounding single quotes are removed. An <i>UnquotedString</i> can
93 * contain arbitrary characters except single quotes and left curly brackets.
94 * Thus, a string that should result in the formatted message "'{0}'" can be
95 * written as {@code "'''{'0}''"} or {@code "'''{0}'''"}.
96 * <p>
97 * Within a <i>SubformatPattern</i>, different rules apply. A <i>QuotedPattern</i>
98 * can contain arbitrary characters except single quotes, but the surrounding
99 * single quotes are <strong>not</strong> removed, so they may be interpreted
100 * by the subformat. For example, {@code "{1,number,$'#',##}"} will
101 * produce a number format with the hash-sign quoted, with a result such as:
102 * "$#31,45". An <i>UnquotedPattern</i> can contain arbitrary characters except
103 * single quotes, but curly braces within it must be balanced. For example,
104 * {@code "ab {0} de"} and {@code "ab '}' de"} are valid subformat
105 * patterns, but {@code "ab {0'}' de"} and {@code "ab } de"} are
106 * not.
107 * <dl>
108 * <dt><b>Warning:</b></dt>
109 * <dd>The rules for using quotes within message format patterns unfortunately
110 * have shown to be somewhat confusing. In particular, it isn't always obvious
111 * to localizers whether single quotes need to be doubled or not. Make sure to
112 * inform localizers about the rules, and tell them (for example, by using
113 * comments in resource bundle source files) which strings will be processed by
114 * {@code MessageFormat}. Note that localizers may need to use single quotes in
115 * translated strings where the original version doesn't have them. <br>
116 * Note also that the simplest way to avoid the problem is to use the real
117 * apostrophe (single quote) character \u2019 (') for human-readable text, and
118 * to use the ASCII apostrophe (\u0027 ' ) only in program syntax, like quoting
119 * in {@code MessageFormat}. See the annotations for U+0027 Apostrophe in The Unicode
120 * Standard.
121 * </dl>
122 * <p>
123 * The <i>ArgumentIndex</i> value is a non-negative integer written using the
124 * digits '0' through '9', and represents an index into the
125 * {@code arguments} array passed to the {@code format} methods or
126 * the result array returned by the {@code parse} methods.
127 * <p>
128 * The <i>FormatType</i> and <i>FormatStyle</i> values are used to create a
129 * {@code Format} instance for the format element. The following table
130 * shows how the values map to {@code Format} instances. Combinations not shown in the
131 * table are illegal. A <i>SubformatPattern</i> must be a valid pattern string
132 * for the {@code Format} subclass used.
133 * <p>
134 * <table border=1>
135 * <tr>
136 * <th>Format Type</th>
137 * <th>Format Style</th>
138 * <th>Subformat Created</th>
139 * </tr>
140 * <tr>
141 * <td colspan="2"><i>(none)</i></td>
142 * <td>{@code null}</td>
143 * </tr>
144 * <tr>
145 * <td rowspan="5">{@code number}</td>
146 * <td><i>(none)</i></td>
147 * <td>{@code NumberFormat.getInstance(getLocale())}</td>
148 * </tr>
149 * <tr>
150 * <td>{@code integer}</td>
151 * <td>{@code NumberFormat.getIntegerInstance(getLocale())}</td>
152 * </tr>
153 * <tr>
154 * <td>{@code currency}</td>
155 * <td>{@code NumberFormat.getCurrencyInstance(getLocale())}</td>
156 * </tr>
157 * <tr>
158 * <td>{@code percent}</td>
159 * <td>{@code NumberFormat.getPercentInstance(getLocale())}</td>
160 * </tr>
161 * <tr>
162 * <td><i>SubformatPattern</i></td>
163 * <td>{@code new DecimalFormat(subformatPattern, new DecimalFormatSymbols(getLocale()))}</td>
164 * </tr>
165 * <tr>
166 * <td rowspan="6">{@code date}</td>
167 * <td><i>(none)</i></td>
168 * <td>{@code DateFormat.getDateInstance(DateFormat.DEFAULT, getLocale())}</td>
169 * </tr>
170 * <tr>
171 * <td>{@code short}</td>
172 * <td>{@code DateFormat.getDateInstance(DateFormat.SHORT, getLocale())}</td>
173 * </tr>
174 * <tr>
175 * <td>{@code medium}</td>
176 * <td>{@code DateFormat.getDateInstance(DateFormat.DEFAULT, getLocale())}</td>
177 * </tr>
178 * <tr>
179 * <td>{@code long}</td>
180 * <td>{@code DateFormat.getDateInstance(DateFormat.LONG, getLocale())}</td>
181 * </tr>
182 * <tr>
183 * <td>{@code full}</td>
184 * <td>{@code DateFormat.getDateInstance(DateFormat.FULL, getLocale())}</td>
185 * </tr>
186 * <tr>
187 * <td><i>SubformatPattern</i></td>
188 * <td>{@code new SimpleDateFormat(subformatPattern, getLocale())}</td>
189 * </tr>
190 * <tr>
191 * <td rowspan="6">{@code time}</td>
192 * <td><i>(none)</i></td>
193 * <td>{@code DateFormat.getTimeInstance(DateFormat.DEFAULT, getLocale())}</td>
194 * </tr>
195 * <tr>
196 * <td>{@code short}</td>
197 * <td>{@code DateFormat.getTimeInstance(DateFormat.SHORT, getLocale())}</td>
198 * </tr>
199 * <tr>
200 * <td>{@code medium}</td>
201 * <td>{@code DateFormat.getTimeInstance(DateFormat.DEFAULT, getLocale())}</td>
202 * </tr>
203 * <tr>
204 * <td>{@code long}</td>
205 * <td>{@code DateFormat.getTimeInstance(DateFormat.LONG, getLocale())}</td>
206 * </tr>
207 * <tr>
208 * <td>{@code full}</td>
209 * <td>{@code DateFormat.getTimeInstance(DateFormat.FULL, getLocale())}</td>
210 * </tr>
211 * <tr>
212 * <td><i>SubformatPattern</i></td>
213 * <td>{@code new SimpleDateFormat(subformatPattern, getLocale())}</td>
214 * </tr>
215 * <tr>
216 * <td>{@code choice}</td>
217 * <td><i>SubformatPattern</i></td>
218 * <td>{@code new ChoiceFormat(subformatPattern)}</td>
219 * </tr>
220 * </table>
221 *
222 * <h4>Usage Information</h4>
223 * <p>
224 * Here are some examples of usage: <blockquote>
225 *
226 * <pre>
227 * Object[] arguments = {
228 *         Integer.valueOf(7), new Date(System.currentTimeMillis()),
229 *         "a disturbance in the Force"};
230 * String result = MessageFormat.format(
231 *         "At {1,time} on {1,date}, there was {2} on planet {0,number,integer}.",
232 *         arguments);
233 * <em>
234 * Output:
235 * </em>
236 * At 12:30 PM on Jul 3, 2053, there was a disturbance in the Force on planet 7.
237 * </pre>
238 *
239 * </blockquote>
240 * <p>
241 * Typically, the message format will come from resources, and the
242 * arguments will be dynamically set at runtime.
243 * <p>
244 * Example 2: <blockquote>
245 *
246 * <pre>
247 * Object[] testArgs = {Long.valueOf(3), "MyDisk"};
248 * MessageFormat form = new MessageFormat("The disk \"{1}\" contains {0} file(s).");
249 * System.out.println(form.format(testArgs));
250 * <em>
251 * Output with different testArgs:
252 * </em>
253 * The disk "MyDisk" contains 0 file(s).
254 * The disk "MyDisk" contains 1 file(s).
255 * The disk "MyDisk" contains 1,273 file(s).
256 * </pre>
257 *
258 * </blockquote>
259 *
260 * <p>
261 * For more sophisticated patterns, you can use a {@code ChoiceFormat} to
262 * get output such as:
263 * <blockquote>
264 *
265 * <pre>
266 * MessageFormat form = new MessageFormat("The disk \"{1}\" contains {0}.");
267 * double[] filelimits = {0,1,2};
268 * String[] filepart = {"no files","one file","{0,number} files"};
269 * ChoiceFormat fileform = new ChoiceFormat(filelimits, filepart);
270 * form.setFormatByArgumentIndex(0, fileform);
271 * Object[] testArgs = {Long.valueOf(12373), "MyDisk"};
272 * System.out.println(form.format(testArgs));
273 * <em>
274 * Output (with different testArgs):
275 * </em>
276 * The disk "MyDisk" contains no files.
277 * The disk "MyDisk" contains one file.
278 * The disk "MyDisk" contains 1,273 files.
279 * </pre>
280 *
281 * </blockquote> You can either do this programmatically, as in the above
282 * example, or by using a pattern (see {@link ChoiceFormat} for more
283 * information) as in: <blockquote>
284 *
285 * <pre>
286 * form.applyPattern("There {0,choice,0#are no files|1#is one file|1&lt;are {0,number,integer} files}.");
287 * </pre>
288 *
289 * </blockquote>
290 * <p>
291 * <strong>Note:</strong> As we see above, the string produced by a
292 * {@code ChoiceFormat} in {@code MessageFormat} is treated
293 * specially; occurances of '{' are used to indicated subformats, and cause
294 * recursion. If you create both a {@code MessageFormat} and
295 * {@code ChoiceFormat} programmatically (instead of using the string
296 * patterns), then be careful not to produce a format that recurses on itself,
297 * which will cause an infinite loop.
298 * <p>
299 * When a single argument is parsed more than once in the string, the last match
300 * will be the final result of the parsing. For example:
301 * <blockquote>
302 * <pre>
303 * MessageFormat mf = new MessageFormat("{0,number,#.##}, {0,number,#.#}");
304 * Object[] objs = {new Double(3.1415)};
305 * String result = mf.format(objs);
306 * // result now equals "3.14, 3.1"
307 * objs = null;
308 * objs = mf.parse(result, new ParsePosition(0));
309 * // objs now equals {new Double(3.1)}
310 * </pre>
311 * </blockquote>
312 * <p>
313 * Likewise, parsing with a {@code MessageFormat} object using patterns
314 * containing multiple occurrences of the same argument would return the last
315 * match. For example:
316 * <blockquote>
317 * <pre>
318 * MessageFormat mf = new MessageFormat("{0}, {0}, {0}");
319 * String forParsing = "x, y, z";
320 * Object[] objs = mf.parse(forParsing, new ParsePosition(0));
321 * // result now equals {new String("z")}
322 * </pre>
323 * </blockquote>
324 * <h4><a name="synchronization">Synchronization</a></h4>
325 * <p>
326 * Message formats are not synchronized. It is recommended to create separate
327 * format instances for each thread. If multiple threads access a format
328 * concurrently, it must be synchronized externally.
329 *
330 * @see java.util.Formatter
331 */
332public class MessageFormat extends Format {
333
334    private static final long serialVersionUID = 6479157306784022952L;
335
336    private Locale locale;
337
338    transient private String[] strings;
339
340    private int[] argumentNumbers;
341
342    private Format[] formats;
343
344    private int maxOffset;
345
346    transient private int maxArgumentIndex;
347
348    /**
349     * Constructs a new {@code MessageFormat} using the specified pattern and {@code locale}.
350     *
351     * @param template
352     *            the pattern.
353     * @param locale
354     *            the locale.
355     * @throws IllegalArgumentException
356     *            if the pattern cannot be parsed.
357     */
358    public MessageFormat(String template, Locale locale) {
359        this.locale = locale;
360        applyPattern(template);
361    }
362
363    /**
364     * Constructs a new {@code MessageFormat} using the specified pattern and
365     * the user's default locale.
366     * See "<a href="../util/Locale.html#default_locale">Be wary of the default locale</a>".
367     *
368     * @param template
369     *            the pattern.
370     * @throws IllegalArgumentException
371     *            if the pattern cannot be parsed.
372     */
373    public MessageFormat(String template) {
374        this(template, Locale.getDefault());
375    }
376
377    /**
378     * Changes this {@code MessageFormat} to use the specified pattern.
379     *
380     * @param template
381     *            the new pattern.
382     * @throws IllegalArgumentException
383     *            if the pattern cannot be parsed.
384     */
385    public void applyPattern(String template) {
386        int length = template.length();
387        StringBuffer buffer = new StringBuffer();
388        ParsePosition position = new ParsePosition(0);
389        ArrayList<String> localStrings = new ArrayList<String>();
390        int argCount = 0;
391        int[] args = new int[10];
392        int maxArg = -1;
393        ArrayList<Format> localFormats = new ArrayList<Format>();
394        while (position.getIndex() < length) {
395            if (Format.upTo(template, position, buffer, '{')) {
396                int arg = 0;
397                int offset = position.getIndex();
398                if (offset >= length) {
399                    throw new IllegalArgumentException("Invalid argument number");
400                }
401                // Get argument number
402                char ch;
403                while ((ch = template.charAt(offset++)) != '}' && ch != ',') {
404                    if (ch < '0' && ch > '9') {
405                        throw new IllegalArgumentException("Invalid argument number");
406                    }
407
408                    arg = arg * 10 + (ch - '0');
409
410                    if (arg < 0 || offset >= length) {
411                        throw new IllegalArgumentException("Invalid argument number");
412                    }
413                }
414                offset--;
415                position.setIndex(offset);
416                localFormats.add(parseVariable(template, position));
417                if (argCount >= args.length) {
418                    int[] newArgs = new int[args.length * 2];
419                    System.arraycopy(args, 0, newArgs, 0, args.length);
420                    args = newArgs;
421                }
422                args[argCount++] = arg;
423                if (arg > maxArg) {
424                    maxArg = arg;
425                }
426            }
427            localStrings.add(buffer.toString());
428            buffer.setLength(0);
429        }
430        this.strings = localStrings.toArray(new String[localStrings.size()]);
431        argumentNumbers = args;
432        this.formats = localFormats.toArray(new Format[argCount]);
433        maxOffset = argCount - 1;
434        maxArgumentIndex = maxArg;
435    }
436
437    /**
438     * Returns a new instance of {@code MessageFormat} with the same pattern and
439     * formats as this {@code MessageFormat}.
440     *
441     * @return a shallow copy of this {@code MessageFormat}.
442     * @see java.lang.Cloneable
443     */
444    @Override
445    public Object clone() {
446        MessageFormat clone = (MessageFormat) super.clone();
447        Format[] array = new Format[formats.length];
448        for (int i = formats.length; --i >= 0;) {
449            if (formats[i] != null) {
450                array[i] = (Format) formats[i].clone();
451            }
452        }
453        clone.formats = array;
454        return clone;
455    }
456
457    /**
458     * Compares the specified object to this {@code MessageFormat} and indicates
459     * if they are equal. In order to be equal, {@code object} must be an
460     * instance of {@code MessageFormat} and have the same pattern.
461     *
462     * @param object
463     *            the object to compare with this object.
464     * @return {@code true} if the specified object is equal to this
465     *         {@code MessageFormat}; {@code false} otherwise.
466     * @see #hashCode
467     */
468    @Override
469    public boolean equals(Object object) {
470        if (this == object) {
471            return true;
472        }
473        if (!(object instanceof MessageFormat)) {
474            return false;
475        }
476        MessageFormat format = (MessageFormat) object;
477        if (maxOffset != format.maxOffset) {
478            return false;
479        }
480        // Must use a loop since the lengths may be different due
481        // to serialization cross-loading
482        for (int i = 0; i <= maxOffset; i++) {
483            if (argumentNumbers[i] != format.argumentNumbers[i]) {
484                return false;
485            }
486        }
487        return locale.equals(format.locale)
488                && Arrays.equals(strings, format.strings)
489                && Arrays.equals(formats, format.formats);
490    }
491
492    /**
493     * Formats the specified object using the rules of this message format and
494     * returns an {@code AttributedCharacterIterator} with the formatted message and
495     * attributes. The {@code AttributedCharacterIterator} returned also includes the
496     * attributes from the formats of this message format.
497     *
498     * @param object
499     *            the object to format.
500     * @return an {@code AttributedCharacterIterator} with the formatted message and
501     *         attributes.
502     * @throws IllegalArgumentException
503     *            if the arguments in the object array cannot be formatted
504     *            by this message format.
505     */
506    @Override
507    public AttributedCharacterIterator formatToCharacterIterator(Object object) {
508        if (object == null) {
509            throw new NullPointerException("object == null");
510        }
511
512        StringBuffer buffer = new StringBuffer();
513        ArrayList<FieldContainer> fields = new ArrayList<FieldContainer>();
514
515        // format the message, and find fields
516        formatImpl((Object[]) object, buffer, new FieldPosition(0), fields);
517
518        // create an AttributedString with the formatted buffer
519        AttributedString as = new AttributedString(buffer.toString());
520
521        // add MessageFormat field attributes and values to the AttributedString
522        for (FieldContainer fc : fields) {
523            as.addAttribute(fc.attribute, fc.value, fc.start, fc.end);
524        }
525
526        // return the CharacterIterator from AttributedString
527        return as.getIterator();
528    }
529
530    /**
531     * Converts the specified objects into a string which it appends to the
532     * specified string buffer using the pattern of this message format.
533     * <p>
534     * If the {@code field} member of the specified {@code FieldPosition} is
535     * {@code MessageFormat.Field.ARGUMENT}, then the begin and end index of
536     * this field position is set to the location of the first occurrence of a
537     * message format argument. Otherwise, the {@code FieldPosition} is ignored.
538     *
539     * @param objects
540     *            the array of objects to format.
541     * @param buffer
542     *            the target string buffer to append the formatted message to.
543     * @param field
544     *            on input: an optional alignment field; on output: the offsets
545     *            of the alignment field in the formatted text.
546     * @return the string buffer.
547     */
548    public final StringBuffer format(Object[] objects, StringBuffer buffer,
549            FieldPosition field) {
550        return formatImpl(objects, buffer, field, null);
551    }
552
553    private StringBuffer formatImpl(Object[] objects, StringBuffer buffer,
554            FieldPosition position, List<FieldContainer> fields) {
555        FieldPosition passedField = new FieldPosition(0);
556        for (int i = 0; i <= maxOffset; i++) {
557            buffer.append(strings[i]);
558            int begin = buffer.length();
559            Object arg;
560            if (objects != null && argumentNumbers[i] < objects.length) {
561                arg = objects[argumentNumbers[i]];
562            } else {
563                buffer.append('{');
564                buffer.append(argumentNumbers[i]);
565                buffer.append('}');
566                handleArgumentField(begin, buffer.length(), argumentNumbers[i], position, fields);
567                continue;
568            }
569            Format format = formats[i];
570            if (format == null || arg == null) {
571                if (arg instanceof Number) {
572                    format = NumberFormat.getInstance();
573                } else if (arg instanceof Date) {
574                    format = DateFormat.getInstance();
575                } else {
576                    buffer.append(arg);
577                    handleArgumentField(begin, buffer.length(), argumentNumbers[i], position, fields);
578                    continue;
579                }
580            }
581            if (format instanceof ChoiceFormat) {
582                String result = format.format(arg);
583                MessageFormat mf = new MessageFormat(result);
584                mf.setLocale(locale);
585                mf.format(objects, buffer, passedField);
586                handleArgumentField(begin, buffer.length(), argumentNumbers[i], position, fields);
587                handleFormat(format, arg, begin, fields);
588            } else {
589                format.format(arg, buffer, passedField);
590                handleArgumentField(begin, buffer.length(), argumentNumbers[i], position, fields);
591                handleFormat(format, arg, begin, fields);
592            }
593        }
594        if (maxOffset + 1 < strings.length) {
595            buffer.append(strings[maxOffset + 1]);
596        }
597        return buffer;
598    }
599
600    /**
601     * Adds a new FieldContainer with MessageFormat.Field.ARGUMENT field,
602     * argIndex, begin and end index to the fields list, or sets the
603     * position's begin and end index if it has MessageFormat.Field.ARGUMENT as
604     * its field attribute.
605     */
606    private void handleArgumentField(int begin, int end, int argIndex,
607            FieldPosition position, List<FieldContainer> fields) {
608        if (fields != null) {
609            fields.add(new FieldContainer(begin, end, Field.ARGUMENT, Integer.valueOf(argIndex)));
610        } else {
611            if (position != null
612                    && position.getFieldAttribute() == Field.ARGUMENT
613                    && position.getEndIndex() == 0) {
614                position.setBeginIndex(begin);
615                position.setEndIndex(end);
616            }
617        }
618    }
619
620    /**
621     * An inner class to store attributes, values, start and end indices.
622     * Instances of this inner class are used as elements for the fields list.
623     */
624    private static class FieldContainer {
625        int start, end;
626
627        AttributedCharacterIterator.Attribute attribute;
628
629        Object value;
630
631        public FieldContainer(int start, int end,
632                AttributedCharacterIterator.Attribute attribute, Object value) {
633            this.start = start;
634            this.end = end;
635            this.attribute = attribute;
636            this.value = value;
637        }
638    }
639
640    /**
641     * If fields list is not null, find and add the fields of this format to
642     * the fields list by iterating through its AttributedCharacterIterator
643     *
644     * @param format
645     *            the format to find fields for
646     * @param arg
647     *            object to format
648     * @param begin
649     *            the index where the string this format has formatted begins
650     */
651    private void handleFormat(Format format, Object arg, int begin, List<FieldContainer> fields) {
652        if (fields == null) {
653            return;
654        }
655        AttributedCharacterIterator iterator = format.formatToCharacterIterator(arg);
656        while (iterator.getIndex() != iterator.getEndIndex()) {
657            int start = iterator.getRunStart();
658            int end = iterator.getRunLimit();
659            Iterator<?> it = iterator.getAttributes().keySet().iterator();
660            while (it.hasNext()) {
661                AttributedCharacterIterator.Attribute attribute =
662                        (AttributedCharacterIterator.Attribute) it.next();
663                Object value = iterator.getAttribute(attribute);
664                fields.add(new FieldContainer(begin + start, begin + end, attribute, value));
665            }
666            iterator.setIndex(end);
667        }
668    }
669
670    /**
671     * Converts the specified objects into a string which it appends to the
672     * specified string buffer using the pattern of this message format.
673     * <p>
674     * If the {@code field} member of the specified {@code FieldPosition} is
675     * {@code MessageFormat.Field.ARGUMENT}, then the begin and end index of
676     * this field position is set to the location of the first occurrence of a
677     * message format argument. Otherwise, the {@code FieldPosition} is ignored.
678     * <p>
679     * Calling this method is equivalent to calling
680     * <blockquote>
681     *
682     * <pre>
683     * format((Object[])object, buffer, field)
684     * </pre>
685     *
686     * </blockquote>
687     *
688     * @param object
689     *            the object to format, must be an array of {@code Object}.
690     * @param buffer
691     *            the target string buffer to append the formatted message to.
692     * @param field
693     *            on input: an optional alignment field; on output: the offsets
694     *            of the alignment field in the formatted text.
695     * @return the string buffer.
696     * @throws ClassCastException
697     *             if {@code object} is not an array of {@code Object}.
698     */
699    @Override
700    public final StringBuffer format(Object object, StringBuffer buffer,
701            FieldPosition field) {
702        return format((Object[]) object, buffer, field);
703    }
704
705    /**
706     * Formats the supplied objects using the specified message format pattern.
707     *
708     * @param format the format string (see {@link java.util.Formatter#format})
709     * @param args
710     *            the list of arguments passed to the formatter. If there are
711     *            more arguments than required by {@code format},
712     *            additional arguments are ignored.
713     * @return the formatted result.
714     * @throws IllegalArgumentException
715     *            if the pattern cannot be parsed.
716     */
717    public static String format(String format, Object... args) {
718        if (args != null) {
719            for (int i = 0; i < args.length; i++) {
720                if (args[i] == null) {
721                    args[i] = "null";
722                }
723            }
724        }
725        return new MessageFormat(format).format(args);
726    }
727
728    /**
729     * Returns the {@code Format} instances used by this message format.
730     *
731     * @return an array of {@code Format} instances.
732     */
733    public Format[] getFormats() {
734        return formats.clone();
735    }
736
737    /**
738     * Returns the formats used for each argument index. If an argument is
739     * placed more than once in the pattern string, then this returns the format
740     * of the last one.
741     *
742     * @return an array of formats, ordered by argument index.
743     */
744    public Format[] getFormatsByArgumentIndex() {
745        Format[] answer = new Format[maxArgumentIndex + 1];
746        for (int i = 0; i < maxOffset + 1; i++) {
747            answer[argumentNumbers[i]] = formats[i];
748        }
749        return answer;
750    }
751
752    /**
753     * Sets the format used for the argument at index {@code argIndex} to
754     * {@code format}.
755     *
756     * @param argIndex
757     *            the index of the format to set.
758     * @param format
759     *            the format that will be set at index {@code argIndex}.
760     */
761    public void setFormatByArgumentIndex(int argIndex, Format format) {
762        for (int i = 0; i < maxOffset + 1; i++) {
763            if (argumentNumbers[i] == argIndex) {
764                formats[i] = format;
765            }
766        }
767    }
768
769    /**
770     * Sets the formats used for each argument. The {@code formats} array
771     * elements should be in the order of the argument indices.
772     *
773     * @param formats
774     *            the formats in an array.
775     */
776    public void setFormatsByArgumentIndex(Format[] formats) {
777        for (int j = 0; j < formats.length; j++) {
778            for (int i = 0; i < maxOffset + 1; i++) {
779                if (argumentNumbers[i] == j) {
780                    this.formats[i] = formats[j];
781                }
782            }
783        }
784    }
785
786    /**
787     * Returns the locale used when creating formats.
788     *
789     * @return the locale used to create formats.
790     */
791    public Locale getLocale() {
792        return locale;
793    }
794
795    @Override
796    public int hashCode() {
797        int hashCode = 0;
798        for (int i = 0; i <= maxOffset; i++) {
799            hashCode += argumentNumbers[i] + strings[i].hashCode();
800            if (formats[i] != null) {
801                hashCode += formats[i].hashCode();
802            }
803        }
804        if (maxOffset + 1 < strings.length) {
805            hashCode += strings[maxOffset + 1].hashCode();
806        }
807        if (locale != null) {
808            return hashCode + locale.hashCode();
809        }
810        return hashCode;
811    }
812
813    /**
814     * Parses the message arguments from the specified string using the rules of
815     * this message format.
816     *
817     * @param string
818     *            the string to parse.
819     * @return the array of {@code Object} arguments resulting from the parse.
820     * @throws ParseException
821     *            if an error occurs during parsing.
822     */
823    public Object[] parse(String string) throws ParseException {
824        ParsePosition position = new ParsePosition(0);
825        Object[] result = parse(string, position);
826        if (position.getIndex() == 0) {
827            throw new ParseException("Parse failure", position.getErrorIndex());
828        }
829        return result;
830    }
831
832    /**
833     * Parses the message argument from the specified string starting at the
834     * index specified by {@code position}. If the string is successfully
835     * parsed then the index of the {@code ParsePosition} is updated to the
836     * index following the parsed text. On error, the index is unchanged and the
837     * error index of {@code ParsePosition} is set to the index where the error
838     * occurred.
839     *
840     * @param string
841     *            the string to parse.
842     * @param position
843     *            input/output parameter, specifies the start index in
844     *            {@code string} from where to start parsing. If parsing is
845     *            successful, it is updated with the index following the parsed
846     *            text; on error, the index is unchanged and the error index is
847     *            set to the index where the error occurred.
848     * @return the array of objects resulting from the parse, or {@code null} if
849     *         there is an error.
850     */
851    public Object[] parse(String string, ParsePosition position) {
852        if (string == null) {
853            return EmptyArray.OBJECT;
854        }
855        ParsePosition internalPos = new ParsePosition(0);
856        int offset = position.getIndex();
857        Object[] result = new Object[maxArgumentIndex + 1];
858        for (int i = 0; i <= maxOffset; i++) {
859            String sub = strings[i];
860            if (!string.startsWith(sub, offset)) {
861                position.setErrorIndex(offset);
862                return null;
863            }
864            offset += sub.length();
865            Object parse;
866            Format format = formats[i];
867            if (format == null) {
868                if (i + 1 < strings.length) {
869                    int next = string.indexOf(strings[i + 1], offset);
870                    if (next == -1) {
871                        position.setErrorIndex(offset);
872                        return null;
873                    }
874                    parse = string.substring(offset, next);
875                    offset = next;
876                } else {
877                    parse = string.substring(offset);
878                    offset = string.length();
879                }
880            } else {
881                internalPos.setIndex(offset);
882                parse = format.parseObject(string, internalPos);
883                if (internalPos.getErrorIndex() != -1) {
884                    position.setErrorIndex(offset);
885                    return null;
886                }
887                offset = internalPos.getIndex();
888            }
889            result[argumentNumbers[i]] = parse;
890        }
891        if (maxOffset + 1 < strings.length) {
892            String sub = strings[maxOffset + 1];
893            if (!string.startsWith(sub, offset)) {
894                position.setErrorIndex(offset);
895                return null;
896            }
897            offset += sub.length();
898        }
899        position.setIndex(offset);
900        return result;
901    }
902
903    /**
904     * Parses the message argument from the specified string starting at the
905     * index specified by {@code position}. If the string is successfully
906     * parsed then the index of the {@code ParsePosition} is updated to the
907     * index following the parsed text. On error, the index is unchanged and the
908     * error index of {@code ParsePosition} is set to the index where the error
909     * occurred.
910     *
911     * @param string
912     *            the string to parse.
913     * @param position
914     *            input/output parameter, specifies the start index in
915     *            {@code string} from where to start parsing. If parsing is
916     *            successful, it is updated with the index following the parsed
917     *            text; on error, the index is unchanged and the error index is
918     *            set to the index where the error occurred.
919     * @return the array of objects resulting from the parse, or {@code null} if
920     *         there is an error.
921     */
922    @Override
923    public Object parseObject(String string, ParsePosition position) {
924        return parse(string, position);
925    }
926
927    private int match(String string, ParsePosition position, boolean last,
928            String[] tokens) {
929        int length = string.length(), offset = position.getIndex(), token = -1;
930        while (offset < length && Character.isWhitespace(string.charAt(offset))) {
931            offset++;
932        }
933        for (int i = tokens.length; --i >= 0;) {
934            if (string.regionMatches(true, offset, tokens[i], 0, tokens[i]
935                    .length())) {
936                token = i;
937                break;
938            }
939        }
940        if (token == -1) {
941            return -1;
942        }
943        offset += tokens[token].length();
944        while (offset < length && Character.isWhitespace(string.charAt(offset))) {
945            offset++;
946        }
947        char ch;
948        if (offset < length
949                && ((ch = string.charAt(offset)) == '}' || (!last && ch == ','))) {
950            position.setIndex(offset + 1);
951            return token;
952        }
953        return -1;
954    }
955
956    private Format parseVariable(String string, ParsePosition position) {
957        int length = string.length(), offset = position.getIndex();
958        char ch;
959        if (offset >= length || ((ch = string.charAt(offset++)) != '}' && ch != ',')) {
960            throw new IllegalArgumentException("Missing element format");
961        }
962        position.setIndex(offset);
963        if (ch == '}') {
964            return null;
965        }
966        int type = match(string, position, false,
967                new String[] { "time", "date", "number", "choice" });
968        if (type == -1) {
969            throw new IllegalArgumentException("Unknown element format");
970        }
971        StringBuffer buffer = new StringBuffer();
972        ch = string.charAt(position.getIndex() - 1);
973        switch (type) {
974            case 0: // time
975            case 1: // date
976                if (ch == '}') {
977                    return type == 1 ? DateFormat.getDateInstance(
978                            DateFormat.DEFAULT, locale) : DateFormat
979                            .getTimeInstance(DateFormat.DEFAULT, locale);
980                }
981                int dateStyle = match(string, position, true,
982                        new String[] { "full", "long", "medium", "short" });
983                if (dateStyle == -1) {
984                    Format.upToWithQuotes(string, position, buffer, '}', '{');
985                    return new SimpleDateFormat(buffer.toString(), locale);
986                }
987                switch (dateStyle) {
988                    case 0:
989                        dateStyle = DateFormat.FULL;
990                        break;
991                    case 1:
992                        dateStyle = DateFormat.LONG;
993                        break;
994                    case 2:
995                        dateStyle = DateFormat.MEDIUM;
996                        break;
997                    case 3:
998                        dateStyle = DateFormat.SHORT;
999                        break;
1000                }
1001                return type == 1 ? DateFormat
1002                        .getDateInstance(dateStyle, locale) : DateFormat
1003                        .getTimeInstance(dateStyle, locale);
1004            case 2: // number
1005                if (ch == '}') {
1006                    return NumberFormat.getInstance(locale);
1007                }
1008                int numberStyle = match(string, position, true,
1009                        new String[] { "currency", "percent", "integer" });
1010                if (numberStyle == -1) {
1011                    Format.upToWithQuotes(string, position, buffer, '}', '{');
1012                    return new DecimalFormat(buffer.toString(),
1013                            new DecimalFormatSymbols(locale));
1014                }
1015                switch (numberStyle) {
1016                    case 0: // currency
1017                        return NumberFormat.getCurrencyInstance(locale);
1018                    case 1: // percent
1019                        return NumberFormat.getPercentInstance(locale);
1020                }
1021                return NumberFormat.getIntegerInstance(locale);
1022        }
1023        // choice
1024        try {
1025            Format.upToWithQuotes(string, position, buffer, '}', '{');
1026        } catch (IllegalArgumentException e) {
1027            // ignored
1028        }
1029        return new ChoiceFormat(buffer.toString());
1030    }
1031
1032    /**
1033     * Sets the specified format used by this message format.
1034     *
1035     * @param offset
1036     *            the index of the format to change.
1037     * @param format
1038     *            the {@code Format} that replaces the old format.
1039     */
1040    public void setFormat(int offset, Format format) {
1041        formats[offset] = format;
1042    }
1043
1044    /**
1045     * Sets the formats used by this message format.
1046     *
1047     * @param formats
1048     *            an array of {@code Format}.
1049     */
1050    public void setFormats(Format[] formats) {
1051        int min = this.formats.length;
1052        if (formats.length < min) {
1053            min = formats.length;
1054        }
1055        for (int i = 0; i < min; i++) {
1056            this.formats[i] = formats[i];
1057        }
1058    }
1059
1060    /**
1061     * Sets the locale to use when creating {@code Format} instances. Changing
1062     * the locale may change the behavior of {@code applyPattern},
1063     * {@code toPattern}, {@code format} and {@code formatToCharacterIterator}.
1064     *
1065     * @param locale
1066     *            the new locale.
1067     */
1068    public void setLocale(Locale locale) {
1069        this.locale = locale;
1070        for (int i = 0; i <= maxOffset; i++) {
1071            Format format = formats[i];
1072            // java specification undefined for null argument, change into
1073            // a more tolerant implementation
1074            if (format instanceof DecimalFormat) {
1075                try {
1076                    formats[i] = new DecimalFormat(((DecimalFormat) format)
1077                            .toPattern(), new DecimalFormatSymbols(locale));
1078                } catch (NullPointerException npe){
1079                    formats[i] = null;
1080                }
1081            } else if (format instanceof SimpleDateFormat) {
1082                try {
1083                    formats[i] = new SimpleDateFormat(((SimpleDateFormat) format)
1084                            .toPattern(), locale);
1085                } catch (NullPointerException npe) {
1086                    formats[i] = null;
1087                }
1088            }
1089        }
1090    }
1091
1092    private String decodeDecimalFormat(StringBuffer buffer, Format format) {
1093        buffer.append(",number");
1094        if (format.equals(NumberFormat.getNumberInstance(locale))) {
1095            // Empty block
1096        } else if (format.equals(NumberFormat.getIntegerInstance(locale))) {
1097            buffer.append(",integer");
1098        } else if (format.equals(NumberFormat.getCurrencyInstance(locale))) {
1099            buffer.append(",currency");
1100        } else if (format.equals(NumberFormat.getPercentInstance(locale))) {
1101            buffer.append(",percent");
1102        } else {
1103            buffer.append(',');
1104            return ((DecimalFormat) format).toPattern();
1105        }
1106        return null;
1107    }
1108
1109    private String decodeSimpleDateFormat(StringBuffer buffer, Format format) {
1110        if (format.equals(DateFormat.getTimeInstance(DateFormat.DEFAULT, locale))) {
1111            buffer.append(",time");
1112        } else if (format.equals(DateFormat.getDateInstance(DateFormat.DEFAULT,
1113                locale))) {
1114            buffer.append(",date");
1115        } else if (format.equals(DateFormat.getTimeInstance(DateFormat.SHORT,
1116                locale))) {
1117            buffer.append(",time,short");
1118        } else if (format.equals(DateFormat.getDateInstance(DateFormat.SHORT,
1119                locale))) {
1120            buffer.append(",date,short");
1121        } else if (format.equals(DateFormat.getTimeInstance(DateFormat.LONG,
1122                locale))) {
1123            buffer.append(",time,long");
1124        } else if (format.equals(DateFormat.getDateInstance(DateFormat.LONG,
1125                locale))) {
1126            buffer.append(",date,long");
1127        } else if (format.equals(DateFormat.getTimeInstance(DateFormat.FULL,
1128                locale))) {
1129            buffer.append(",time,full");
1130        } else if (format.equals(DateFormat.getDateInstance(DateFormat.FULL,
1131                locale))) {
1132            buffer.append(",date,full");
1133        } else {
1134            buffer.append(",date,");
1135            return ((SimpleDateFormat) format).toPattern();
1136        }
1137        return null;
1138    }
1139
1140    /**
1141     * Returns the pattern of this message format.
1142     *
1143     * @return the pattern.
1144     */
1145    public String toPattern() {
1146        StringBuffer buffer = new StringBuffer();
1147        for (int i = 0; i <= maxOffset; i++) {
1148            appendQuoted(buffer, strings[i]);
1149            buffer.append('{');
1150            buffer.append(argumentNumbers[i]);
1151            Format format = formats[i];
1152            String pattern = null;
1153            if (format instanceof ChoiceFormat) {
1154                buffer.append(",choice,");
1155                pattern = ((ChoiceFormat) format).toPattern();
1156            } else if (format instanceof DecimalFormat) {
1157                pattern = decodeDecimalFormat(buffer, format);
1158            } else if (format instanceof SimpleDateFormat) {
1159                pattern = decodeSimpleDateFormat(buffer, format);
1160            } else if (format != null) {
1161                throw new IllegalArgumentException("Unknown format");
1162            }
1163            if (pattern != null) {
1164                boolean quote = false;
1165                int index = 0, length = pattern.length(), count = 0;
1166                while (index < length) {
1167                    char ch = pattern.charAt(index++);
1168                    if (ch == '\'') {
1169                        quote = !quote;
1170                    }
1171                    if (!quote) {
1172                        if (ch == '{') {
1173                            count++;
1174                        }
1175                        if (ch == '}') {
1176                            if (count > 0) {
1177                                count--;
1178                            } else {
1179                                buffer.append("'}");
1180                                ch = '\'';
1181                            }
1182                        }
1183                    }
1184                    buffer.append(ch);
1185                }
1186            }
1187            buffer.append('}');
1188        }
1189        if (maxOffset + 1 < strings.length) {
1190            appendQuoted(buffer, strings[maxOffset + 1]);
1191        }
1192        return buffer.toString();
1193    }
1194
1195    private void appendQuoted(StringBuffer buffer, String string) {
1196        int length = string.length();
1197        for (int i = 0; i < length; i++) {
1198            char ch = string.charAt(i);
1199            if (ch == '{' || ch == '}') {
1200                buffer.append('\'');
1201                buffer.append(ch);
1202                buffer.append('\'');
1203            } else {
1204                buffer.append(ch);
1205            }
1206        }
1207    }
1208
1209    private static final ObjectStreamField[] serialPersistentFields = {
1210        new ObjectStreamField("argumentNumbers", int[].class),
1211        new ObjectStreamField("formats", Format[].class),
1212        new ObjectStreamField("locale", Locale.class),
1213        new ObjectStreamField("maxOffset", int.class),
1214        new ObjectStreamField("offsets", int[].class),
1215        new ObjectStreamField("pattern", String.class),
1216    };
1217
1218    private void writeObject(ObjectOutputStream stream) throws IOException {
1219        ObjectOutputStream.PutField fields = stream.putFields();
1220        fields.put("argumentNumbers", argumentNumbers);
1221        Format[] compatibleFormats = formats;
1222        fields.put("formats", compatibleFormats);
1223        fields.put("locale", locale);
1224        fields.put("maxOffset", maxOffset);
1225        int offset = 0;
1226        int offsetsLength = maxOffset + 1;
1227        int[] offsets = new int[offsetsLength];
1228        StringBuilder pattern = new StringBuilder();
1229        for (int i = 0; i <= maxOffset; i++) {
1230            offset += strings[i].length();
1231            offsets[i] = offset;
1232            pattern.append(strings[i]);
1233        }
1234        if (maxOffset + 1 < strings.length) {
1235            pattern.append(strings[maxOffset + 1]);
1236        }
1237        fields.put("offsets", offsets);
1238        fields.put("pattern", pattern.toString());
1239        stream.writeFields();
1240    }
1241
1242    private void readObject(ObjectInputStream stream) throws IOException,
1243            ClassNotFoundException {
1244        ObjectInputStream.GetField fields = stream.readFields();
1245        argumentNumbers = (int[]) fields.get("argumentNumbers", null);
1246        formats = (Format[]) fields.get("formats", null);
1247        locale = (Locale) fields.get("locale", null);
1248        maxOffset = fields.get("maxOffset", 0);
1249        int[] offsets = (int[]) fields.get("offsets", null);
1250        String pattern = (String) fields.get("pattern", null);
1251        int length;
1252        if (maxOffset < 0) {
1253            length = pattern.length() > 0 ? 1 : 0;
1254        } else {
1255            length = maxOffset
1256                    + (offsets[maxOffset] == pattern.length() ? 1 : 2);
1257        }
1258        strings = new String[length];
1259        int last = 0;
1260        for (int i = 0; i <= maxOffset; i++) {
1261            strings[i] = pattern.substring(last, offsets[i]);
1262            last = offsets[i];
1263        }
1264        if (maxOffset + 1 < strings.length) {
1265            strings[strings.length - 1] = pattern.substring(last, pattern
1266                    .length());
1267        }
1268    }
1269
1270    /**
1271     * The instances of this inner class are used as attribute keys in
1272     * {@code AttributedCharacterIterator} that the
1273     * {@link MessageFormat#formatToCharacterIterator(Object)} method returns.
1274     * <p>
1275     * There is no public constructor in this class, the only instances are the
1276     * constants defined here.
1277     */
1278    public static class Field extends Format.Field {
1279
1280        private static final long serialVersionUID = 7899943957617360810L;
1281
1282        /**
1283         * This constant stands for the message argument.
1284         */
1285        public static final Field ARGUMENT = new Field("message argument field");
1286
1287        /**
1288         * Constructs a new instance of {@code MessageFormat.Field} with the
1289         * given field name.
1290         *
1291         * @param fieldName
1292         *            the field name.
1293         */
1294        protected Field(String fieldName) {
1295            super(fieldName);
1296        }
1297    }
1298}
1299