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