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