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