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
31import org.apache.harmony.text.internal.nls.Messages;
32
33/**
34 * Produces concatenated
35 * messages in language-neutral way. Use this class to construct messages
36 * displayed for end users.
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 *         new Integer(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 = {new Long(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 = {new Long(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.Locale
332 * @see Format
333 * @see NumberFormat
334 * @see DecimalFormat
335 * @see ChoiceFormat
336 */
337public class MessageFormat extends Format {
338
339    private static final long serialVersionUID = 6479157306784022952L;
340
341    private Locale locale = Locale.getDefault();
342
343    transient private String[] strings;
344
345    private int[] argumentNumbers;
346
347    private Format[] formats;
348
349    private int maxOffset;
350
351    transient private int maxArgumentIndex;
352
353    /**
354     * Constructs a new {@code MessageFormat} using the specified pattern and
355     * the specified locale for formats.
356     *
357     * @param template
358     *            the pattern.
359     * @param locale
360     *            the locale.
361     * @throws IllegalArgumentException
362     *            if the pattern cannot be parsed.
363     */
364    public MessageFormat(String template, Locale locale) {
365        this.locale = locale;
366        applyPattern(template);
367    }
368
369    /**
370     * Constructs a new {@code MessageFormat} using the specified pattern and
371     * the default locale for formats.
372     *
373     * @param template
374     *            the pattern.
375     * @throws IllegalArgumentException
376     *            if the pattern cannot be parsed.
377     */
378    public MessageFormat(String template) {
379        applyPattern(template);
380    }
381
382    /**
383     * Changes this {@code MessageFormat} to use the specified pattern.
384     *
385     * @param template
386     *            the new pattern.
387     * @throws IllegalArgumentException
388     *            if the pattern cannot be parsed.
389     */
390    public void applyPattern(String template) {
391        int length = template.length();
392        StringBuffer buffer = new StringBuffer();
393        ParsePosition position = new ParsePosition(0);
394        Vector<String> localStrings = new Vector<String>();
395        int argCount = 0;
396        int[] args = new int[10];
397        int maxArg = -1;
398        Vector<Format> localFormats = new Vector<Format>();
399        while (position.getIndex() < length) {
400            if (Format.upTo(template, position, buffer, '{')) {
401                int arg = 0;
402                int offset = position.getIndex();
403                if (offset >= length) {
404                    // text.19=Invalid argument number
405                    throw new IllegalArgumentException(Messages
406                            .getString("text.19")); //$NON-NLS-1$
407                }
408                // Get argument number
409                char ch;
410                while ((ch = template.charAt(offset++)) != '}' && ch != ',') {
411                    if (ch < '0' && ch > '9') {
412                        // text.19=Invalid argument number
413                        throw new IllegalArgumentException(Messages
414                            .getString("text.19")); //$NON-NLS-1$
415                    }
416
417                    arg = arg * 10 + (ch - '0');
418
419                    if (arg < 0 || offset >= length) {
420                        // text.19=Invalid argument number
421                        throw new IllegalArgumentException(Messages
422                            .getString("text.19")); //$NON-NLS-1$
423                    }
424                }
425                offset--;
426                position.setIndex(offset);
427                localFormats.addElement(parseVariable(template, position));
428                if (argCount >= args.length) {
429                    int[] newArgs = new int[args.length * 2];
430                    System.arraycopy(args, 0, newArgs, 0, args.length);
431                    args = newArgs;
432                }
433                args[argCount++] = arg;
434                if (arg > maxArg) {
435                    maxArg = arg;
436                }
437            }
438            localStrings.addElement(buffer.toString());
439            buffer.setLength(0);
440        }
441        this.strings = new String[localStrings.size()];
442        for (int i = 0; i < localStrings.size(); i++) {
443            this.strings[i] = localStrings.elementAt(i);
444        }
445        argumentNumbers = args;
446        this.formats = new Format[argCount];
447        for (int i = 0; i < argCount; i++) {
448            this.formats[i] = localFormats.elementAt(i);
449        }
450        maxOffset = argCount - 1;
451        maxArgumentIndex = maxArg;
452    }
453
454    /**
455     * Returns a new instance of {@code MessageFormat} with the same pattern and
456     * formats as this {@code MessageFormat}.
457     *
458     * @return a shallow copy of this {@code MessageFormat}.
459     * @see java.lang.Cloneable
460     */
461    @Override
462    public Object clone() {
463        MessageFormat clone = (MessageFormat) super.clone();
464        Format[] array = new Format[formats.length];
465        for (int i = formats.length; --i >= 0;) {
466            if (formats[i] != null) {
467                array[i] = (Format) formats[i].clone();
468            }
469        }
470        clone.formats = array;
471        return clone;
472    }
473
474    /**
475     * Compares the specified object to this {@code MessageFormat} and indicates
476     * if they are equal. In order to be equal, {@code object} must be an
477     * instance of {@code MessageFormat} and have the same pattern.
478     *
479     * @param object
480     *            the object to compare with this object.
481     * @return {@code true} if the specified object is equal to this
482     *         {@code MessageFormat}; {@code false} otherwise.
483     * @see #hashCode
484     */
485    @Override
486    public boolean equals(Object object) {
487        if (this == object) {
488            return true;
489        }
490        if (!(object instanceof MessageFormat)) {
491            return false;
492        }
493        MessageFormat format = (MessageFormat) object;
494        if (maxOffset != format.maxOffset) {
495            return false;
496        }
497        // Must use a loop since the lengths may be different due
498        // to serialization cross-loading
499        for (int i = 0; i <= maxOffset; i++) {
500            if (argumentNumbers[i] != format.argumentNumbers[i]) {
501                return false;
502            }
503        }
504        return locale.equals(format.locale)
505                && Arrays.equals(strings, format.strings)
506                && Arrays.equals(formats, format.formats);
507    }
508
509    /**
510     * Formats the specified object using the rules of this message format and
511     * returns an {@code AttributedCharacterIterator} with the formatted message and
512     * attributes. The {@code AttributedCharacterIterator} returned also includes the
513     * attributes from the formats of this message format.
514     *
515     * @param object
516     *            the object to format.
517     * @return an {@code AttributedCharacterIterator} with the formatted message and
518     *         attributes.
519     * @throws IllegalArgumentException
520     *            if the arguments in the object array cannot be formatted
521     *            by this message format.
522     */
523    @Override
524    public AttributedCharacterIterator formatToCharacterIterator(Object object) {
525        if (object == null) {
526            throw new NullPointerException();
527        }
528
529        StringBuffer buffer = new StringBuffer();
530        Vector<FieldContainer> fields = new Vector<FieldContainer>();
531
532        // format the message, and find fields
533        formatImpl((Object[]) object, buffer, new FieldPosition(0), fields);
534
535        // create an AttributedString with the formatted buffer
536        AttributedString as = new AttributedString(buffer.toString());
537
538        // add MessageFormat field attributes and values to the AttributedString
539        for (int i = 0; i < fields.size(); i++) {
540            FieldContainer fc = fields.elementAt(i);
541            as.addAttribute(fc.attribute, fc.value, fc.start, fc.end);
542        }
543
544        // return the CharacterIterator from AttributedString
545        return as.getIterator();
546    }
547
548    /**
549     * Converts the specified objects into a string which it appends to the
550     * specified string buffer using the pattern of this message format.
551     * <p>
552     * If the {@code field} member of the specified {@code FieldPosition} is
553     * {@code MessageFormat.Field.ARGUMENT}, then the begin and end index of
554     * this field position is set to the location of the first occurrence of a
555     * message format argument. Otherwise, the {@code FieldPosition} is ignored.
556     *
557     * @param objects
558     *            the array of objects to format.
559     * @param buffer
560     *            the target string buffer to append the formatted message to.
561     * @param field
562     *            on input: an optional alignment field; on output: the offsets
563     *            of the alignment field in the formatted text.
564     * @return the string buffer.
565     */
566    public final StringBuffer format(Object[] objects, StringBuffer buffer,
567            FieldPosition field) {
568        return formatImpl(objects, buffer, field, null);
569    }
570
571    private StringBuffer formatImpl(Object[] objects, StringBuffer buffer,
572            FieldPosition position, Vector<FieldContainer> fields) {
573        FieldPosition passedField = new FieldPosition(0);
574        for (int i = 0; i <= maxOffset; i++) {
575            buffer.append(strings[i]);
576            int begin = buffer.length();
577            Object arg;
578            if (objects != null && argumentNumbers[i] < objects.length) {
579                arg = objects[argumentNumbers[i]];
580            } else {
581                buffer.append('{');
582                buffer.append(argumentNumbers[i]);
583                buffer.append('}');
584                handleArgumentField(begin, buffer.length(), argumentNumbers[i],
585                        position, fields);
586                continue;
587            }
588            Format format = formats[i];
589            if (format == null || arg == null) {
590                if (arg instanceof Number) {
591                    format = NumberFormat.getInstance();
592                } else if (arg instanceof Date) {
593                    format = DateFormat.getInstance();
594                } else {
595                    buffer.append(arg);
596                    handleArgumentField(begin, buffer.length(),
597                            argumentNumbers[i], position, fields);
598                    continue;
599                }
600            }
601            if (format instanceof ChoiceFormat) {
602                String result = format.format(arg);
603                MessageFormat mf = new MessageFormat(result);
604                mf.setLocale(locale);
605                mf.format(objects, buffer, passedField);
606                handleArgumentField(begin, buffer.length(), argumentNumbers[i],
607                        position, fields);
608                handleformat(format, arg, begin, fields);
609            } else {
610                format.format(arg, buffer, passedField);
611                handleArgumentField(begin, buffer.length(), argumentNumbers[i],
612                        position, fields);
613                handleformat(format, arg, begin, fields);
614            }
615        }
616        if (maxOffset + 1 < strings.length) {
617            buffer.append(strings[maxOffset + 1]);
618        }
619        return buffer;
620    }
621
622    /**
623     * Adds a new FieldContainer with MessageFormat.Field.ARGUMENT field,
624     * argnumber, begin and end index to the fields vector, or sets the
625     * position's begin and end index if it has MessageFormat.Field.ARGUMENT as
626     * its field attribute.
627     *
628     * @param begin
629     * @param end
630     * @param argnumber
631     * @param position
632     * @param fields
633     */
634    private void handleArgumentField(int begin, int end, int argnumber,
635            FieldPosition position, Vector<FieldContainer> fields) {
636        if (fields != null) {
637            fields.add(new FieldContainer(begin, end, Field.ARGUMENT,
638                    new Integer(argnumber)));
639        } else {
640            if (position != null
641                    && position.getFieldAttribute() == Field.ARGUMENT
642                    && position.getEndIndex() == 0) {
643                position.setBeginIndex(begin);
644                position.setEndIndex(end);
645            }
646        }
647    }
648
649    /**
650     * An inner class to store attributes, values, start and end indices.
651     * Instances of this inner class are used as elements for the fields vector
652     */
653    private static class FieldContainer {
654        int start, end;
655
656        AttributedCharacterIterator.Attribute attribute;
657
658        Object value;
659
660        public FieldContainer(int start, int end,
661                AttributedCharacterIterator.Attribute attribute, Object value) {
662            this.start = start;
663            this.end = end;
664            this.attribute = attribute;
665            this.value = value;
666        }
667    }
668
669    /**
670     * If fields vector is not null, find and add the fields of this format to
671     * the fields vector by iterating through its AttributedCharacterIterator
672     *
673     * @param format
674     *            the format to find fields for
675     * @param arg
676     *            object to format
677     * @param begin
678     *            the index where the string this format has formatted begins
679     * @param fields
680     *            fields vector, each entry in this vector are of type
681     *            FieldContainer.
682     */
683    private void handleformat(Format format, Object arg, int begin,
684            Vector<FieldContainer> fields) {
685        if (fields != null) {
686            AttributedCharacterIterator iterator = format
687                    .formatToCharacterIterator(arg);
688            while (iterator.getIndex() != iterator.getEndIndex()) {
689                int start = iterator.getRunStart();
690                int end = iterator.getRunLimit();
691
692                Iterator<?> it = iterator.getAttributes().keySet().iterator();
693                while (it.hasNext()) {
694                    AttributedCharacterIterator.Attribute attribute = (AttributedCharacterIterator.Attribute) it
695                            .next();
696                    Object value = iterator.getAttribute(attribute);
697                    fields.add(new FieldContainer(begin + start, begin + end,
698                            attribute, value));
699                }
700                iterator.setIndex(end);
701            }
702        }
703    }
704
705    /**
706     * Converts the specified objects into a string which it appends to the
707     * specified string buffer using the pattern of this message format.
708     * <p>
709     * If the {@code field} member of the specified {@code FieldPosition} is
710     * {@code MessageFormat.Field.ARGUMENT}, then the begin and end index of
711     * this field position is set to the location of the first occurrence of a
712     * message format argument. Otherwise, the {@code FieldPosition} is ignored.
713     * <p>
714     * Calling this method is equivalent to calling
715     * <blockquote>
716     *
717     * <pre>
718     * format((Object[])object, buffer, field)
719     * </pre>
720     *
721     * </blockquote>
722     *
723     * @param object
724     *            the object to format, must be an array of {@code Object}.
725     * @param buffer
726     *            the target string buffer to append the formatted message to.
727     * @param field
728     *            on input: an optional alignment field; on output: the offsets
729     *            of the alignment field in the formatted text.
730     * @return the string buffer.
731     * @throws ClassCastException
732     *             if {@code object} is not an array of {@code Object}.
733     */
734    @Override
735    public final StringBuffer format(Object object, StringBuffer buffer,
736            FieldPosition field) {
737        return format((Object[]) object, buffer, field);
738    }
739
740    /**
741     * Formats the supplied objects using the specified message format pattern.
742     *
743     * @param template
744     *            the pattern to use for formatting.
745     * @param objects
746     *            the array of objects to format.
747     * @return the formatted result.
748     * @throws IllegalArgumentException
749     *            if the pattern cannot be parsed.
750     */
751    public static String format(String template, Object... objects) {
752        if (objects != null) {
753            for (int i = 0; i < objects.length; i++) {
754                if (objects[i] == null) {
755                    objects[i] = "null";
756                }
757            }
758        }
759        // BEGIN android-changed
760        return new MessageFormat(template).format(objects);
761        // END android-changed
762    }
763
764    /**
765     * Returns the {@code Format} instances used by this message format.
766     *
767     * @return an array of {@code Format} instances.
768     */
769    public Format[] getFormats() {
770        return formats.clone();
771    }
772
773    /**
774     * Returns the formats used for each argument index. If an argument is
775     * placed more than once in the pattern string, then this returns the format
776     * of the last one.
777     *
778     * @return an array of formats, ordered by argument index.
779     */
780    public Format[] getFormatsByArgumentIndex() {
781        Format[] answer = new Format[maxArgumentIndex + 1];
782        for (int i = 0; i < maxOffset + 1; i++) {
783            answer[argumentNumbers[i]] = formats[i];
784        }
785        return answer;
786    }
787
788    /**
789     * Sets the format used for the argument at index {@code argIndex} to
790     * {@code format}.
791     *
792     * @param argIndex
793     *            the index of the format to set.
794     * @param format
795     *            the format that will be set at index {@code argIndex}.
796     */
797    public void setFormatByArgumentIndex(int argIndex, Format format) {
798        for (int i = 0; i < maxOffset + 1; i++) {
799            if (argumentNumbers[i] == argIndex) {
800                formats[i] = format;
801            }
802        }
803    }
804
805    /**
806     * Sets the formats used for each argument. The {@code formats} array
807     * elements should be in the order of the argument indices.
808     *
809     * @param formats
810     *            the formats in an array.
811     */
812    public void setFormatsByArgumentIndex(Format[] formats) {
813        for (int j = 0; j < formats.length; j++) {
814            for (int i = 0; i < maxOffset + 1; i++) {
815                if (argumentNumbers[i] == j) {
816                    this.formats[i] = formats[j];
817                }
818            }
819        }
820    }
821
822    /**
823     * Returns the locale used when creating formats.
824     *
825     * @return the locale used to create formats.
826     */
827    public Locale getLocale() {
828        return locale;
829    }
830
831    @Override
832    public int hashCode() {
833        int hashCode = 0;
834        for (int i = 0; i <= maxOffset; i++) {
835            hashCode += argumentNumbers[i] + strings[i].hashCode();
836            if (formats[i] != null) {
837                hashCode += formats[i].hashCode();
838            }
839        }
840        if (maxOffset + 1 < strings.length) {
841            hashCode += strings[maxOffset + 1].hashCode();
842        }
843        if (locale != null) {
844            return hashCode + locale.hashCode();
845        }
846        return hashCode;
847    }
848
849    /**
850     * Parses the message arguments from the specified string using the rules of
851     * this message format.
852     *
853     * @param string
854     *            the string to parse.
855     * @return the array of {@code Object} arguments resulting from the parse.
856     * @throws ParseException
857     *            if an error occurs during parsing.
858     */
859    public Object[] parse(String string) throws ParseException {
860        ParsePosition position = new ParsePosition(0);
861        Object[] result = parse(string, position);
862        if (position.getIndex() == 0) {
863            // text.1B=MessageFormat.parseObject(String) parse failure
864            throw new ParseException(
865                    Messages.getString("text.1B"), position.getErrorIndex()); //$NON-NLS-1$
866        }
867        return result;
868    }
869
870    /**
871     * Parses the message argument from the specified string starting at the
872     * index specified by {@code position}. If the string is successfully
873     * parsed then the index of the {@code ParsePosition} is updated to the
874     * index following the parsed text. On error, the index is unchanged and the
875     * error index of {@code ParsePosition} is set to the index where the error
876     * occurred.
877     *
878     * @param string
879     *            the string to parse.
880     * @param position
881     *            input/output parameter, specifies the start index in
882     *            {@code string} from where to start parsing. If parsing is
883     *            successful, it is updated with the index following the parsed
884     *            text; on error, the index is unchanged and the error index is
885     *            set to the index where the error occurred.
886     * @return the array of objects resulting from the parse, or {@code null} if
887     *         there is an error.
888     */
889    public Object[] parse(String string, ParsePosition position) {
890        if (string == null) {
891            return new Object[0];
892        }
893        ParsePosition internalPos = new ParsePosition(0);
894        int offset = position.getIndex();
895        Object[] result = new Object[maxArgumentIndex + 1];
896        for (int i = 0; i <= maxOffset; i++) {
897            String sub = strings[i];
898            if (!string.startsWith(sub, offset)) {
899                position.setErrorIndex(offset);
900                return null;
901            }
902            offset += sub.length();
903            Object parse;
904            Format format = formats[i];
905            if (format == null) {
906                if (i + 1 < strings.length) {
907                    int next = string.indexOf(strings[i + 1], offset);
908                    if (next == -1) {
909                        position.setErrorIndex(offset);
910                        return null;
911                    }
912                    parse = string.substring(offset, next);
913                    offset = next;
914                } else {
915                    parse = string.substring(offset);
916                    offset = string.length();
917                }
918            } else {
919                internalPos.setIndex(offset);
920                parse = format.parseObject(string, internalPos);
921                if (internalPos.getErrorIndex() != -1) {
922                    position.setErrorIndex(offset);
923                    return null;
924                }
925                offset = internalPos.getIndex();
926            }
927            result[argumentNumbers[i]] = parse;
928        }
929        if (maxOffset + 1 < strings.length) {
930            String sub = strings[maxOffset + 1];
931            if (!string.startsWith(sub, offset)) {
932                position.setErrorIndex(offset);
933                return null;
934            }
935            offset += sub.length();
936        }
937        position.setIndex(offset);
938        return result;
939    }
940
941    /**
942     * Parses the message argument from the specified string starting at the
943     * index specified by {@code position}. If the string is successfully
944     * parsed then the index of the {@code ParsePosition} is updated to the
945     * index following the parsed text. On error, the index is unchanged and the
946     * error index of {@code ParsePosition} is set to the index where the error
947     * occurred.
948     *
949     * @param string
950     *            the string to parse.
951     * @param position
952     *            input/output parameter, specifies the start index in
953     *            {@code string} from where to start parsing. If parsing is
954     *            successful, it is updated with the index following the parsed
955     *            text; on error, the index is unchanged and the error index is
956     *            set to the index where the error occurred.
957     * @return the array of objects resulting from the parse, or {@code null} if
958     *         there is an error.
959     */
960    @Override
961    public Object parseObject(String string, ParsePosition position) {
962        return parse(string, position);
963    }
964
965    private int match(String string, ParsePosition position, boolean last,
966            String[] tokens) {
967        int length = string.length(), offset = position.getIndex(), token = -1;
968        while (offset < length && Character.isWhitespace(string.charAt(offset))) {
969            offset++;
970        }
971        for (int i = tokens.length; --i >= 0;) {
972            if (string.regionMatches(true, offset, tokens[i], 0, tokens[i]
973                    .length())) {
974                token = i;
975                break;
976            }
977        }
978        if (token == -1) {
979            return -1;
980        }
981        offset += tokens[token].length();
982        while (offset < length && Character.isWhitespace(string.charAt(offset))) {
983            offset++;
984        }
985        char ch;
986        if (offset < length
987                && ((ch = string.charAt(offset)) == '}' || (!last && ch == ','))) {
988            position.setIndex(offset + 1);
989            return token;
990        }
991        return -1;
992    }
993
994    private Format parseVariable(String string, ParsePosition position) {
995        int length = string.length(), offset = position.getIndex();
996        char ch;
997        if (offset >= length
998                || ((ch = string.charAt(offset++)) != '}' && ch != ',')) {
999            // text.15=Missing element format
1000            throw new IllegalArgumentException(Messages.getString("text.15")); //$NON-NLS-1$
1001        }
1002        position.setIndex(offset);
1003        if (ch == '}') {
1004            return null;
1005        }
1006        int type = match(string, position, false, new String[] { "time", //$NON-NLS-1$
1007                "date", "number", "choice" }); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
1008        if (type == -1) {
1009            // text.16=Unknown element format
1010            throw new IllegalArgumentException(Messages.getString("text.16")); //$NON-NLS-1$
1011        }
1012        StringBuffer buffer = new StringBuffer();
1013        ch = string.charAt(position.getIndex() - 1);
1014        switch (type) {
1015            case 0: // time
1016            case 1: // date
1017                if (ch == '}') {
1018                    return type == 1 ? DateFormat.getDateInstance(
1019                            DateFormat.DEFAULT, locale) : DateFormat
1020                            .getTimeInstance(DateFormat.DEFAULT, locale);
1021                }
1022                int dateStyle = match(string, position, true, new String[] {
1023                        "full", "long", "medium", "short" }); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
1024                if (dateStyle == -1) {
1025                    Format.upToWithQuotes(string, position, buffer, '}', '{');
1026                    return new SimpleDateFormat(buffer.toString(), locale);
1027                }
1028                switch (dateStyle) {
1029                    case 0:
1030                        dateStyle = DateFormat.FULL;
1031                        break;
1032                    case 1:
1033                        dateStyle = DateFormat.LONG;
1034                        break;
1035                    case 2:
1036                        dateStyle = DateFormat.MEDIUM;
1037                        break;
1038                    case 3:
1039                        dateStyle = DateFormat.SHORT;
1040                        break;
1041                }
1042                return type == 1 ? DateFormat
1043                        .getDateInstance(dateStyle, locale) : DateFormat
1044                        .getTimeInstance(dateStyle, locale);
1045            case 2: // number
1046                if (ch == '}') {
1047                    // BEGIN android-changed
1048                    return NumberFormat.getInstance(locale);
1049                    // END android-changed
1050                }
1051                int numberStyle = match(string, position, true, new String[] {
1052                        "currency", "percent", "integer" }); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
1053                if (numberStyle == -1) {
1054                    Format.upToWithQuotes(string, position, buffer, '}', '{');
1055                    return new DecimalFormat(buffer.toString(),
1056                            new DecimalFormatSymbols(locale));
1057                }
1058                switch (numberStyle) {
1059                    case 0: // currency
1060                        return NumberFormat.getCurrencyInstance(locale);
1061                    case 1: // percent
1062                        return NumberFormat.getPercentInstance(locale);
1063                }
1064                return NumberFormat.getIntegerInstance(locale);
1065        }
1066        // choice
1067        try {
1068            Format.upToWithQuotes(string, position, buffer, '}', '{');
1069        } catch (IllegalArgumentException e) {
1070            // ignored
1071        }
1072        return new ChoiceFormat(buffer.toString());
1073    }
1074
1075    /**
1076     * Sets the specified format used by this message format.
1077     *
1078     * @param offset
1079     *            the index of the format to change.
1080     * @param format
1081     *            the {@code Format} that replaces the old format.
1082     */
1083    public void setFormat(int offset, Format format) {
1084        formats[offset] = format;
1085    }
1086
1087    /**
1088     * Sets the formats used by this message format.
1089     *
1090     * @param formats
1091     *            an array of {@code Format}.
1092     */
1093    public void setFormats(Format[] formats) {
1094        int min = this.formats.length;
1095        if (formats.length < min) {
1096            min = formats.length;
1097        }
1098        for (int i = 0; i < min; i++) {
1099            this.formats[i] = formats[i];
1100        }
1101    }
1102
1103    /**
1104     * Sets the locale to use when creating {@code Format} instances. Changing
1105     * the locale may change the behavior of {@code applyPattern},
1106     * {@code toPattern}, {@code format} and {@code formatToCharacterIterator}.
1107     *
1108     * @param locale
1109     *            the new locale.
1110     */
1111    public void setLocale(Locale locale) {
1112        this.locale = locale;
1113        for (int i = 0; i <= maxOffset; i++) {
1114            Format format = formats[i];
1115            // BEGIN android-removed
1116            //if (format instanceof DecimalFormat) {
1117            //     formats[i] = new DecimalFormat(((DecimalFormat) format)
1118            //             .toPattern(), new DecimalFormatSymbols(locale));
1119            //} else if (format instanceof SimpleDateFormat) {
1120            //     formats[i] = new SimpleDateFormat(((SimpleDateFormat) format)
1121            //             .toPattern(), locale);
1122            //}
1123            // END android-removed
1124            // BEGIN android-added
1125            // java specification undefined for null argument, change into
1126            // a more tolerant implementation
1127            if (format instanceof DecimalFormat) {
1128                try {
1129                    formats[i] = new DecimalFormat(((DecimalFormat) format)
1130                            .toPattern(), new DecimalFormatSymbols(locale));
1131                } catch (NullPointerException npe){
1132                    formats[i] = null;
1133                }
1134            } else if (format instanceof SimpleDateFormat) {
1135                try {
1136                    formats[i] = new SimpleDateFormat(((SimpleDateFormat) format)
1137                            .toPattern(), locale);
1138                } catch (NullPointerException npe) {
1139                    formats[i] = null;
1140                }
1141            }
1142            // END android-added
1143        }
1144    }
1145
1146    private String decodeDecimalFormat(StringBuffer buffer, Format format) {
1147        buffer.append(",number"); //$NON-NLS-1$
1148        if (format.equals(NumberFormat.getNumberInstance(locale))) {
1149            // Empty block
1150        } else if (format.equals(NumberFormat.getIntegerInstance(locale))) {
1151            buffer.append(",integer"); //$NON-NLS-1$
1152        } else if (format.equals(NumberFormat.getCurrencyInstance(locale))) {
1153            buffer.append(",currency"); //$NON-NLS-1$
1154        } else if (format.equals(NumberFormat.getPercentInstance(locale))) {
1155            buffer.append(",percent"); //$NON-NLS-1$
1156        } else {
1157            buffer.append(',');
1158            return ((DecimalFormat) format).toPattern();
1159        }
1160        return null;
1161    }
1162
1163    private String decodeSimpleDateFormat(StringBuffer buffer, Format format) {
1164        if (format.equals(DateFormat
1165                .getTimeInstance(DateFormat.DEFAULT, locale))) {
1166            buffer.append(",time"); //$NON-NLS-1$
1167        } else if (format.equals(DateFormat.getDateInstance(DateFormat.DEFAULT,
1168                locale))) {
1169            buffer.append(",date"); //$NON-NLS-1$
1170        } else if (format.equals(DateFormat.getTimeInstance(DateFormat.SHORT,
1171                locale))) {
1172            buffer.append(",time,short"); //$NON-NLS-1$
1173        } else if (format.equals(DateFormat.getDateInstance(DateFormat.SHORT,
1174                locale))) {
1175            buffer.append(",date,short"); //$NON-NLS-1$
1176        } else if (format.equals(DateFormat.getTimeInstance(DateFormat.LONG,
1177                locale))) {
1178            buffer.append(",time,long"); //$NON-NLS-1$
1179        } else if (format.equals(DateFormat.getDateInstance(DateFormat.LONG,
1180                locale))) {
1181            buffer.append(",date,long"); //$NON-NLS-1$
1182        } else if (format.equals(DateFormat.getTimeInstance(DateFormat.FULL,
1183                locale))) {
1184            buffer.append(",time,full"); //$NON-NLS-1$
1185        } else if (format.equals(DateFormat.getDateInstance(DateFormat.FULL,
1186                locale))) {
1187            buffer.append(",date,full"); //$NON-NLS-1$
1188        } else {
1189            buffer.append(",date,"); //$NON-NLS-1$
1190            return ((SimpleDateFormat) format).toPattern();
1191        }
1192        return null;
1193    }
1194
1195    /**
1196     * Returns the pattern of this message format.
1197     *
1198     * @return the pattern.
1199     */
1200    public String toPattern() {
1201        StringBuffer buffer = new StringBuffer();
1202        for (int i = 0; i <= maxOffset; i++) {
1203            appendQuoted(buffer, strings[i]);
1204            buffer.append('{');
1205            buffer.append(argumentNumbers[i]);
1206            Format format = formats[i];
1207            String pattern = null;
1208            if (format instanceof ChoiceFormat) {
1209                buffer.append(",choice,"); //$NON-NLS-1$
1210                pattern = ((ChoiceFormat) format).toPattern();
1211            } else if (format instanceof DecimalFormat) {
1212                pattern = decodeDecimalFormat(buffer, format);
1213            } else if (format instanceof SimpleDateFormat) {
1214                pattern = decodeSimpleDateFormat(buffer, format);
1215            } else if (format != null) {
1216                // text.17=Unknown format
1217                throw new IllegalArgumentException(Messages
1218                        .getString("text.17")); //$NON-NLS-1$
1219            }
1220            if (pattern != null) {
1221                boolean quote = false;
1222                int index = 0, length = pattern.length(), count = 0;
1223                while (index < length) {
1224                    char ch = pattern.charAt(index++);
1225                    if (ch == '\'') {
1226                        quote = !quote;
1227                    }
1228                    if (!quote) {
1229                        if (ch == '{') {
1230                            count++;
1231                        }
1232                        if (ch == '}') {
1233                            if (count > 0) {
1234                                count--;
1235                            } else {
1236                                buffer.append("'}"); //$NON-NLS-1$
1237                                ch = '\'';
1238                            }
1239                        }
1240                    }
1241                    buffer.append(ch);
1242                }
1243            }
1244            buffer.append('}');
1245        }
1246        if (maxOffset + 1 < strings.length) {
1247            appendQuoted(buffer, strings[maxOffset + 1]);
1248        }
1249        return buffer.toString();
1250    }
1251
1252    private void appendQuoted(StringBuffer buffer, String string) {
1253        int length = string.length();
1254        for (int i = 0; i < length; i++) {
1255            char ch = string.charAt(i);
1256            if (ch == '{' || ch == '}') {
1257                buffer.append('\'');
1258                buffer.append(ch);
1259                buffer.append('\'');
1260            } else {
1261                buffer.append(ch);
1262            }
1263        }
1264    }
1265
1266    private static final ObjectStreamField[] serialPersistentFields = {
1267            new ObjectStreamField("argumentNumbers", int[].class), //$NON-NLS-1$
1268            new ObjectStreamField("formats", Format[].class), //$NON-NLS-1$
1269            new ObjectStreamField("locale", Locale.class), //$NON-NLS-1$
1270            new ObjectStreamField("maxOffset", Integer.TYPE), //$NON-NLS-1$
1271            new ObjectStreamField("offsets", int[].class), //$NON-NLS-1$
1272            new ObjectStreamField("pattern", String.class), }; //$NON-NLS-1$
1273
1274    private void writeObject(ObjectOutputStream stream) throws IOException {
1275        ObjectOutputStream.PutField fields = stream.putFields();
1276        fields.put("argumentNumbers", argumentNumbers); //$NON-NLS-1$
1277        Format[] compatibleFormats = formats;
1278        fields.put("formats", compatibleFormats); //$NON-NLS-1$
1279        fields.put("locale", locale); //$NON-NLS-1$
1280        fields.put("maxOffset", maxOffset); //$NON-NLS-1$
1281        int offset = 0;
1282        int offsetsLength = maxOffset + 1;
1283        int[] offsets = new int[offsetsLength];
1284        StringBuilder pattern = new StringBuilder();
1285        for (int i = 0; i <= maxOffset; i++) {
1286            offset += strings[i].length();
1287            offsets[i] = offset;
1288            pattern.append(strings[i]);
1289        }
1290        if (maxOffset + 1 < strings.length) {
1291            pattern.append(strings[maxOffset + 1]);
1292        }
1293        fields.put("offsets", offsets); //$NON-NLS-1$
1294        fields.put("pattern", pattern.toString()); //$NON-NLS-1$
1295        stream.writeFields();
1296    }
1297
1298    private void readObject(ObjectInputStream stream) throws IOException,
1299            ClassNotFoundException {
1300        ObjectInputStream.GetField fields = stream.readFields();
1301        argumentNumbers = (int[]) fields.get("argumentNumbers", null); //$NON-NLS-1$
1302        formats = (Format[]) fields.get("formats", null); //$NON-NLS-1$
1303        locale = (Locale) fields.get("locale", null); //$NON-NLS-1$
1304        maxOffset = fields.get("maxOffset", 0); //$NON-NLS-1$
1305        int[] offsets = (int[]) fields.get("offsets", null); //$NON-NLS-1$
1306        String pattern = (String) fields.get("pattern", null); //$NON-NLS-1$
1307        int length;
1308        if (maxOffset < 0) {
1309            length = pattern.length() > 0 ? 1 : 0;
1310        } else {
1311            length = maxOffset
1312                    + (offsets[maxOffset] == pattern.length() ? 1 : 2);
1313        }
1314        strings = new String[length];
1315        int last = 0;
1316        for (int i = 0; i <= maxOffset; i++) {
1317            strings[i] = pattern.substring(last, offsets[i]);
1318            last = offsets[i];
1319        }
1320        if (maxOffset + 1 < strings.length) {
1321            strings[strings.length - 1] = pattern.substring(last, pattern
1322                    .length());
1323        }
1324    }
1325
1326    /**
1327     * The instances of this inner class are used as attribute keys in
1328     * {@code AttributedCharacterIterator} that the
1329     * {@link MessageFormat#formatToCharacterIterator(Object)} method returns.
1330     * <p>
1331     * There is no public constructor in this class, the only instances are the
1332     * constants defined here.
1333     */
1334    public static class Field extends Format.Field {
1335
1336        private static final long serialVersionUID = 7899943957617360810L;
1337
1338        /**
1339         * This constant stands for the message argument.
1340         */
1341        public static final Field ARGUMENT = new Field("message argument field"); //$NON-NLS-1$
1342
1343        /**
1344         * Constructs a new instance of {@code MessageFormat.Field} with the
1345         * given field name.
1346         *
1347         * @param fieldName
1348         *            the field name.
1349         */
1350        protected Field(String fieldName) {
1351            super(fieldName);
1352        }
1353
1354        /**
1355         * Resolves instances that are deserialized to the constant
1356         * {@code MessageFormat.Field} values.
1357         *
1358         * @return the resolved field object.
1359         * @throws InvalidObjectException
1360         *             if an error occurs while resolving the field object.
1361         */
1362        @Override
1363        protected Object readResolve() throws InvalidObjectException {
1364            String name = this.getName();
1365            if (name == null) {
1366                // text.18=Not a valid {0}, subclass should override
1367                // readResolve()
1368                throw new InvalidObjectException(Messages.getString(
1369                        "text.18", "MessageFormat.Field")); //$NON-NLS-1$ //$NON-NLS-2$
1370            }
1371
1372            if (name.equals(ARGUMENT.getName())) {
1373                return ARGUMENT;
1374            }
1375            // text.18=Not a valid {0}, subclass should override readResolve()
1376            throw new InvalidObjectException(Messages.getString(
1377                    "text.18", "MessageFormat.Field")); //$NON-NLS-1$ //$NON-NLS-2$
1378        }
1379    }
1380
1381}
1382