Home | History | Annotate | Download | only in text
      1 /*
      2  * Copyright (C) 2014 The Android Open Source Project
      3  * Copyright (c) 1996, 2013, Oracle and/or its affiliates. All rights reserved.
      4  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
      5  *
      6  * This code is free software; you can redistribute it and/or modify it
      7  * under the terms of the GNU General Public License version 2 only, as
      8  * published by the Free Software Foundation.  Oracle designates this
      9  * particular file as subject to the "Classpath" exception as provided
     10  * by Oracle in the LICENSE file that accompanied this code.
     11  *
     12  * This code is distributed in the hope that it will be useful, but WITHOUT
     13  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
     14  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
     15  * version 2 for more details (a copy is included in the LICENSE file that
     16  * accompanied this code).
     17  *
     18  * You should have received a copy of the GNU General Public License version
     19  * 2 along with this work; if not, write to the Free Software Foundation,
     20  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
     21  *
     22  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
     23  * or visit www.oracle.com if you need additional information or have any
     24  * questions.
     25  */
     26 
     27 /*
     28  * (C) Copyright Taligent, Inc. 1996, 1997 - All Rights Reserved
     29  * (C) Copyright IBM Corp. 1996 - 1998 - All Rights Reserved
     30  *
     31  *   The original version of this source code and documentation is copyrighted
     32  * and owned by Taligent, Inc., a wholly-owned subsidiary of IBM. These
     33  * materials are provided under terms of a License Agreement between Taligent
     34  * and Sun. This technology is protected by multiple US and International
     35  * patents. This notice and attribution to Taligent may not be removed.
     36  *   Taligent is a registered trademark of Taligent, Inc.
     37  *
     38  */
     39 
     40 package java.text;
     41 
     42 import java.io.InvalidObjectException;
     43 import java.io.IOException;
     44 import java.io.ObjectInputStream;
     45 import java.text.DecimalFormat;
     46 import java.util.ArrayList;
     47 import java.util.Arrays;
     48 import java.util.Date;
     49 import java.util.List;
     50 import java.util.Locale;
     51 
     52 
     53 /**
     54  * <code>MessageFormat</code> provides a means to produce concatenated
     55  * messages in a language-neutral way. Use this to construct messages
     56  * displayed for end users.
     57  *
     58  * <p>
     59  * <code>MessageFormat</code> takes a set of objects, formats them, then
     60  * inserts the formatted strings into the pattern at the appropriate places.
     61  *
     62  * <p>
     63  * <strong>Note:</strong>
     64  * <code>MessageFormat</code> differs from the other <code>Format</code>
     65  * classes in that you create a <code>MessageFormat</code> object with one
     66  * of its constructors (not with a <code>getInstance</code> style factory
     67  * method). The factory methods aren't necessary because <code>MessageFormat</code>
     68  * itself doesn't implement locale specific behavior. Any locale specific
     69  * behavior is defined by the pattern that you provide as well as the
     70  * subformats used for inserted arguments.
     71  *
     72  * <h3><a name="patterns">Patterns and Their Interpretation</a></h3>
     73  *
     74  * <code>MessageFormat</code> uses patterns of the following form:
     75  * <blockquote><pre>
     76  * <i>MessageFormatPattern:</i>
     77  *         <i>String</i>
     78  *         <i>MessageFormatPattern</i> <i>FormatElement</i> <i>String</i>
     79  *
     80  * <i>FormatElement:</i>
     81  *         { <i>ArgumentIndex</i> }
     82  *         { <i>ArgumentIndex</i> , <i>FormatType</i> }
     83  *         { <i>ArgumentIndex</i> , <i>FormatType</i> , <i>FormatStyle</i> }
     84  *
     85  * <i>FormatType: one of </i>
     86  *         number date time choice
     87  *
     88  * <i>FormatStyle:</i>
     89  *         short
     90  *         medium
     91  *         long
     92  *         full
     93  *         integer
     94  *         currency
     95  *         percent
     96  *         <i>SubformatPattern</i>
     97  * </pre></blockquote>
     98  *
     99  * <p>Within a <i>String</i>, a pair of single quotes can be used to
    100  * quote any arbitrary characters except single quotes. For example,
    101  * pattern string <code>"'{0}'"</code> represents string
    102  * <code>"{0}"</code>, not a <i>FormatElement</i>. A single quote itself
    103  * must be represented by doubled single quotes {@code ''} throughout a
    104  * <i>String</i>.  For example, pattern string <code>"'{''}'"</code> is
    105  * interpreted as a sequence of <code>'{</code> (start of quoting and a
    106  * left curly brace), <code>''</code> (a single quote), and
    107  * <code>}'</code> (a right curly brace and end of quoting),
    108  * <em>not</em> <code>'{'</code> and <code>'}'</code> (quoted left and
    109  * right curly braces): representing string <code>"{'}"</code>,
    110  * <em>not</em> <code>"{}"</code>.
    111  *
    112  * <p>A <i>SubformatPattern</i> is interpreted by its corresponding
    113  * subformat, and subformat-dependent pattern rules apply. For example,
    114  * pattern string <code>"{1,number,<u>$'#',##</u>}"</code>
    115  * (<i>SubformatPattern</i> with underline) will produce a number format
    116  * with the pound-sign quoted, with a result such as: {@code
    117  * "$#31,45"}. Refer to each {@code Format} subclass documentation for
    118  * details.
    119  *
    120  * <p>Any unmatched quote is treated as closed at the end of the given
    121  * pattern. For example, pattern string {@code "'{0}"} is treated as
    122  * pattern {@code "'{0}'"}.
    123  *
    124  * <p>Any curly braces within an unquoted pattern must be balanced. For
    125  * example, <code>"ab {0} de"</code> and <code>"ab '}' de"</code> are
    126  * valid patterns, but <code>"ab {0'}' de"</code>, <code>"ab } de"</code>
    127  * and <code>"''{''"</code> are not.
    128  *
    129  * <dl><dt><b>Warning:</b><dd>The rules for using quotes within message
    130  * format patterns unfortunately have shown to be somewhat confusing.
    131  * In particular, it isn't always obvious to localizers whether single
    132  * quotes need to be doubled or not. Make sure to inform localizers about
    133  * the rules, and tell them (for example, by using comments in resource
    134  * bundle source files) which strings will be processed by {@code MessageFormat}.
    135  * Note that localizers may need to use single quotes in translated
    136  * strings where the original version doesn't have them.
    137  * </dl>
    138  * <p>
    139  * The <i>ArgumentIndex</i> value is a non-negative integer written
    140  * using the digits {@code '0'} through {@code '9'}, and represents an index into the
    141  * {@code arguments} array passed to the {@code format} methods
    142  * or the result array returned by the {@code parse} methods.
    143  * <p>
    144  * The <i>FormatType</i> and <i>FormatStyle</i> values are used to create
    145  * a {@code Format} instance for the format element. The following
    146  * table shows how the values map to {@code Format} instances. Combinations not
    147  * shown in the table are illegal. A <i>SubformatPattern</i> must
    148  * be a valid pattern string for the {@code Format} subclass used.
    149  *
    150  * <table border=1 summary="Shows how FormatType and FormatStyle values map to Format instances">
    151  *    <tr>
    152  *       <th id="ft" class="TableHeadingColor">FormatType
    153  *       <th id="fs" class="TableHeadingColor">FormatStyle
    154  *       <th id="sc" class="TableHeadingColor">Subformat Created
    155  *    <tr>
    156  *       <td headers="ft"><i>(none)</i>
    157  *       <td headers="fs"><i>(none)</i>
    158  *       <td headers="sc"><code>null</code>
    159  *    <tr>
    160  *       <td headers="ft" rowspan=5><code>number</code>
    161  *       <td headers="fs"><i>(none)</i>
    162  *       <td headers="sc">{@link NumberFormat#getInstance(Locale) NumberFormat.getInstance}{@code (getLocale())}
    163  *    <tr>
    164  *       <td headers="fs"><code>integer</code>
    165  *       <td headers="sc">{@link NumberFormat#getIntegerInstance(Locale) NumberFormat.getIntegerInstance}{@code (getLocale())}
    166  *    <tr>
    167  *       <td headers="fs"><code>currency</code>
    168  *       <td headers="sc">{@link NumberFormat#getCurrencyInstance(Locale) NumberFormat.getCurrencyInstance}{@code (getLocale())}
    169  *    <tr>
    170  *       <td headers="fs"><code>percent</code>
    171  *       <td headers="sc">{@link NumberFormat#getPercentInstance(Locale) NumberFormat.getPercentInstance}{@code (getLocale())}
    172  *    <tr>
    173  *       <td headers="fs"><i>SubformatPattern</i>
    174  *       <td headers="sc">{@code new} {@link DecimalFormat#DecimalFormat(String,DecimalFormatSymbols) DecimalFormat}{@code (subformatPattern,} {@link DecimalFormatSymbols#getInstance(Locale) DecimalFormatSymbols.getInstance}{@code (getLocale()))}
    175  *    <tr>
    176  *       <td headers="ft" rowspan=6><code>date</code>
    177  *       <td headers="fs"><i>(none)</i>
    178  *       <td headers="sc">{@link DateFormat#getDateInstance(int,Locale) DateFormat.getDateInstance}{@code (}{@link DateFormat#DEFAULT}{@code , getLocale())}
    179  *    <tr>
    180  *       <td headers="fs"><code>short</code>
    181  *       <td headers="sc">{@link DateFormat#getDateInstance(int,Locale) DateFormat.getDateInstance}{@code (}{@link DateFormat#SHORT}{@code , getLocale())}
    182  *    <tr>
    183  *       <td headers="fs"><code>medium</code>
    184  *       <td headers="sc">{@link DateFormat#getDateInstance(int,Locale) DateFormat.getDateInstance}{@code (}{@link DateFormat#DEFAULT}{@code , getLocale())}
    185  *    <tr>
    186  *       <td headers="fs"><code>long</code>
    187  *       <td headers="sc">{@link DateFormat#getDateInstance(int,Locale) DateFormat.getDateInstance}{@code (}{@link DateFormat#LONG}{@code , getLocale())}
    188  *    <tr>
    189  *       <td headers="fs"><code>full</code>
    190  *       <td headers="sc">{@link DateFormat#getDateInstance(int,Locale) DateFormat.getDateInstance}{@code (}{@link DateFormat#FULL}{@code , getLocale())}
    191  *    <tr>
    192  *       <td headers="fs"><i>SubformatPattern</i>
    193  *       <td headers="sc">{@code new} {@link SimpleDateFormat#SimpleDateFormat(String,Locale) SimpleDateFormat}{@code (subformatPattern, getLocale())}
    194  *    <tr>
    195  *       <td headers="ft" rowspan=6><code>time</code>
    196  *       <td headers="fs"><i>(none)</i>
    197  *       <td headers="sc">{@link DateFormat#getTimeInstance(int,Locale) DateFormat.getTimeInstance}{@code (}{@link DateFormat#DEFAULT}{@code , getLocale())}
    198  *    <tr>
    199  *       <td headers="fs"><code>short</code>
    200  *       <td headers="sc">{@link DateFormat#getTimeInstance(int,Locale) DateFormat.getTimeInstance}{@code (}{@link DateFormat#SHORT}{@code , getLocale())}
    201  *    <tr>
    202  *       <td headers="fs"><code>medium</code>
    203  *       <td headers="sc">{@link DateFormat#getTimeInstance(int,Locale) DateFormat.getTimeInstance}{@code (}{@link DateFormat#DEFAULT}{@code , getLocale())}
    204  *    <tr>
    205  *       <td headers="fs"><code>long</code>
    206  *       <td headers="sc">{@link DateFormat#getTimeInstance(int,Locale) DateFormat.getTimeInstance}{@code (}{@link DateFormat#LONG}{@code , getLocale())}
    207  *    <tr>
    208  *       <td headers="fs"><code>full</code>
    209  *       <td headers="sc">{@link DateFormat#getTimeInstance(int,Locale) DateFormat.getTimeInstance}{@code (}{@link DateFormat#FULL}{@code , getLocale())}
    210  *    <tr>
    211  *       <td headers="fs"><i>SubformatPattern</i>
    212  *       <td headers="sc">{@code new} {@link SimpleDateFormat#SimpleDateFormat(String,Locale) SimpleDateFormat}{@code (subformatPattern, getLocale())}
    213  *    <tr>
    214  *       <td headers="ft"><code>choice</code>
    215  *       <td headers="fs"><i>SubformatPattern</i>
    216  *       <td headers="sc">{@code new} {@link ChoiceFormat#ChoiceFormat(String) ChoiceFormat}{@code (subformatPattern)}
    217  * </table>
    218  *
    219  * <h4>Usage Information</h4>
    220  *
    221  * <p>
    222  * Here are some examples of usage.
    223  * In real internationalized programs, the message format pattern and other
    224  * static strings will, of course, be obtained from resource bundles.
    225  * Other parameters will be dynamically determined at runtime.
    226  * <p>
    227  * The first example uses the static method <code>MessageFormat.format</code>,
    228  * which internally creates a <code>MessageFormat</code> for one-time use:
    229  * <blockquote><pre>
    230  * int planet = 7;
    231  * String event = "a disturbance in the Force";
    232  *
    233  * String result = MessageFormat.format(
    234  *     "At {1,time} on {1,date}, there was {2} on planet {0,number,integer}.",
    235  *     planet, new Date(), event);
    236  * </pre></blockquote>
    237  * The output is:
    238  * <blockquote><pre>
    239  * At 12:30 PM on Jul 3, 2053, there was a disturbance in the Force on planet 7.
    240  * </pre></blockquote>
    241  *
    242  * <p>
    243  * The following example creates a <code>MessageFormat</code> instance that
    244  * can be used repeatedly:
    245  * <blockquote><pre>
    246  * int fileCount = 1273;
    247  * String diskName = "MyDisk";
    248  * Object[] testArgs = {new Long(fileCount), diskName};
    249  *
    250  * MessageFormat form = new MessageFormat(
    251  *     "The disk \"{1}\" contains {0} file(s).");
    252  *
    253  * System.out.println(form.format(testArgs));
    254  * </pre></blockquote>
    255  * The output with different values for <code>fileCount</code>:
    256  * <blockquote><pre>
    257  * The disk "MyDisk" contains 0 file(s).
    258  * The disk "MyDisk" contains 1 file(s).
    259  * The disk "MyDisk" contains 1,273 file(s).
    260  * </pre></blockquote>
    261  *
    262  * <p>
    263  * For more sophisticated patterns, you can use a <code>ChoiceFormat</code>
    264  * to produce correct forms for singular and plural:
    265  * <blockquote><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  *
    272  * int fileCount = 1273;
    273  * String diskName = "MyDisk";
    274  * Object[] testArgs = {new Long(fileCount), diskName};
    275  *
    276  * System.out.println(form.format(testArgs));
    277  * </pre></blockquote>
    278  * The output with different values for <code>fileCount</code>:
    279  * <blockquote><pre>
    280  * The disk "MyDisk" contains no files.
    281  * The disk "MyDisk" contains one file.
    282  * The disk "MyDisk" contains 1,273 files.
    283  * </pre></blockquote>
    284  *
    285  * <p>
    286  * You can create the <code>ChoiceFormat</code> programmatically, as in the
    287  * above example, or by using a pattern. See {@link ChoiceFormat}
    288  * for more information.
    289  * <blockquote><pre>{@code
    290  * form.applyPattern(
    291  *    "There {0,choice,0#are no files|1#is one file|1<are {0,number,integer} files}.");
    292  * }</pre></blockquote>
    293  *
    294  * <p>
    295  * <strong>Note:</strong> As we see above, the string produced
    296  * by a <code>ChoiceFormat</code> in <code>MessageFormat</code> is treated as special;
    297  * occurrences of '{' are used to indicate subformats, and cause recursion.
    298  * If you create both a <code>MessageFormat</code> and <code>ChoiceFormat</code>
    299  * programmatically (instead of using the string patterns), then be careful not to
    300  * produce a format that recurses on itself, which will cause an infinite loop.
    301  * <p>
    302  * When a single argument is parsed more than once in the string, the last match
    303  * will be the final result of the parsing.  For example,
    304  * <blockquote><pre>
    305  * MessageFormat mf = new MessageFormat("{0,number,#.##}, {0,number,#.#}");
    306  * Object[] objs = {new Double(3.1415)};
    307  * String result = mf.format( objs );
    308  * // result now equals "3.14, 3.1"
    309  * objs = null;
    310  * objs = mf.parse(result, new ParsePosition(0));
    311  * // objs now equals {new Double(3.1)}
    312  * </pre></blockquote>
    313  *
    314  * <p>
    315  * Likewise, parsing with a {@code MessageFormat} object using patterns containing
    316  * multiple occurrences of the same argument would return the last match.  For
    317  * example,
    318  * <blockquote><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></blockquote>
    324  *
    325  * <h4><a name="synchronization">Synchronization</a></h4>
    326  *
    327  * <p>
    328  * Message formats are not synchronized.
    329  * It is recommended to create separate format instances for each thread.
    330  * If multiple threads access a format concurrently, it must be synchronized
    331  * externally.
    332  *
    333  * @see          java.util.Locale
    334  * @see          Format
    335  * @see          NumberFormat
    336  * @see          DecimalFormat
    337  * @see          DecimalFormatSymbols
    338  * @see          ChoiceFormat
    339  * @see          DateFormat
    340  * @see          SimpleDateFormat
    341  *
    342  * @author       Mark Davis
    343  */
    344 
    345 public class MessageFormat extends Format {
    346 
    347     private static final long serialVersionUID = 6479157306784022952L;
    348 
    349     /**
    350      * Constructs a MessageFormat for the default
    351      * {@link java.util.Locale.Category#FORMAT FORMAT} locale and the
    352      * specified pattern.
    353      * The constructor first sets the locale, then parses the pattern and
    354      * creates a list of subformats for the format elements contained in it.
    355      * Patterns and their interpretation are specified in the
    356      * <a href="#patterns">class description</a>.
    357      *
    358      * @param pattern the pattern for this message format
    359      * @exception IllegalArgumentException if the pattern is invalid
    360      */
    361     public MessageFormat(String pattern) {
    362         this.locale = Locale.getDefault(Locale.Category.FORMAT);
    363         applyPattern(pattern);
    364     }
    365 
    366     /**
    367      * Constructs a MessageFormat for the specified locale and
    368      * pattern.
    369      * The constructor first sets the locale, then parses the pattern and
    370      * creates a list of subformats for the format elements contained in it.
    371      * Patterns and their interpretation are specified in the
    372      * <a href="#patterns">class description</a>.
    373      *
    374      * @param pattern the pattern for this message format
    375      * @param locale the locale for this message format
    376      * @exception IllegalArgumentException if the pattern is invalid
    377      * @since 1.4
    378      */
    379     public MessageFormat(String pattern, Locale locale) {
    380         this.locale = locale;
    381         applyPattern(pattern);
    382     }
    383 
    384     /**
    385      * Sets the locale to be used when creating or comparing subformats.
    386      * This affects subsequent calls
    387      * <ul>
    388      * <li>to the {@link #applyPattern applyPattern}
    389      *     and {@link #toPattern toPattern} methods if format elements specify
    390      *     a format type and therefore have the subformats created in the
    391      *     <code>applyPattern</code> method, as well as
    392      * <li>to the <code>format</code> and
    393      *     {@link #formatToCharacterIterator formatToCharacterIterator} methods
    394      *     if format elements do not specify a format type and therefore have
    395      *     the subformats created in the formatting methods.
    396      * </ul>
    397      * Subformats that have already been created are not affected.
    398      *
    399      * @param locale the locale to be used when creating or comparing subformats
    400      */
    401     public void setLocale(Locale locale) {
    402         this.locale = locale;
    403     }
    404 
    405     /**
    406      * Gets the locale that's used when creating or comparing subformats.
    407      *
    408      * @return the locale used when creating or comparing subformats
    409      */
    410     public Locale getLocale() {
    411         return locale;
    412     }
    413 
    414 
    415     /**
    416      * Sets the pattern used by this message format.
    417      * The method parses the pattern and creates a list of subformats
    418      * for the format elements contained in it.
    419      * Patterns and their interpretation are specified in the
    420      * <a href="#patterns">class description</a>.
    421      *
    422      * @param pattern the pattern for this message format
    423      * @exception IllegalArgumentException if the pattern is invalid
    424      */
    425     @SuppressWarnings("fallthrough") // fallthrough in switch is expected, suppress it
    426     public void applyPattern(String pattern) {
    427             StringBuilder[] segments = new StringBuilder[4];
    428             // Allocate only segments[SEG_RAW] here. The rest are
    429             // allocated on demand.
    430             segments[SEG_RAW] = new StringBuilder();
    431 
    432             int part = SEG_RAW;
    433             int formatNumber = 0;
    434             boolean inQuote = false;
    435             int braceStack = 0;
    436             maxOffset = -1;
    437             for (int i = 0; i < pattern.length(); ++i) {
    438                 char ch = pattern.charAt(i);
    439                 if (part == SEG_RAW) {
    440                     if (ch == '\'') {
    441                         if (i + 1 < pattern.length()
    442                             && pattern.charAt(i+1) == '\'') {
    443                             segments[part].append(ch);  // handle doubles
    444                             ++i;
    445                         } else {
    446                             inQuote = !inQuote;
    447                         }
    448                     } else if (ch == '{' && !inQuote) {
    449                         part = SEG_INDEX;
    450                         if (segments[SEG_INDEX] == null) {
    451                             segments[SEG_INDEX] = new StringBuilder();
    452                         }
    453                     } else {
    454                         segments[part].append(ch);
    455                     }
    456                 } else  {
    457                     if (inQuote) {              // just copy quotes in parts
    458                         segments[part].append(ch);
    459                         if (ch == '\'') {
    460                             inQuote = false;
    461                         }
    462                     } else {
    463                         switch (ch) {
    464                         case ',':
    465                             if (part < SEG_MODIFIER) {
    466                                 if (segments[++part] == null) {
    467                                     segments[part] = new StringBuilder();
    468                                 }
    469                             } else {
    470                                 segments[part].append(ch);
    471                             }
    472                             break;
    473                         case '{':
    474                             ++braceStack;
    475                             segments[part].append(ch);
    476                             break;
    477                         case '}':
    478                             if (braceStack == 0) {
    479                                 part = SEG_RAW;
    480                                 makeFormat(i, formatNumber, segments);
    481                                 formatNumber++;
    482                                 // throw away other segments
    483                                 segments[SEG_INDEX] = null;
    484                                 segments[SEG_TYPE] = null;
    485                                 segments[SEG_MODIFIER] = null;
    486                             } else {
    487                                 --braceStack;
    488                                 segments[part].append(ch);
    489                             }
    490                             break;
    491                         case ' ':
    492                             // Skip any leading space chars for SEG_TYPE.
    493                             if (part != SEG_TYPE || segments[SEG_TYPE].length() > 0) {
    494                                 segments[part].append(ch);
    495                             }
    496                             break;
    497                         case '\'':
    498                             inQuote = true;
    499                             // fall through, so we keep quotes in other parts
    500                         default:
    501                             segments[part].append(ch);
    502                             break;
    503                         }
    504                     }
    505                 }
    506             }
    507             if (braceStack == 0 && part != 0) {
    508                 maxOffset = -1;
    509                 throw new IllegalArgumentException("Unmatched braces in the pattern.");
    510             }
    511             this.pattern = segments[0].toString();
    512     }
    513 
    514 
    515     /**
    516      * Returns a pattern representing the current state of the message format.
    517      * The string is constructed from internal information and therefore
    518      * does not necessarily equal the previously applied pattern.
    519      *
    520      * @return a pattern representing the current state of the message format
    521      */
    522     public String toPattern() {
    523         // later, make this more extensible
    524         int lastOffset = 0;
    525         StringBuilder result = new StringBuilder();
    526         for (int i = 0; i <= maxOffset; ++i) {
    527             copyAndFixQuotes(pattern, lastOffset, offsets[i], result);
    528             lastOffset = offsets[i];
    529             result.append('{').append(argumentNumbers[i]);
    530             Format fmt = formats[i];
    531             if (fmt == null) {
    532                 // do nothing, string format
    533             } else if (fmt instanceof NumberFormat) {
    534                 if (fmt.equals(NumberFormat.getInstance(locale))) {
    535                     result.append(",number");
    536                 } else if (fmt.equals(NumberFormat.getCurrencyInstance(locale))) {
    537                     result.append(",number,currency");
    538                 } else if (fmt.equals(NumberFormat.getPercentInstance(locale))) {
    539                     result.append(",number,percent");
    540                 } else if (fmt.equals(NumberFormat.getIntegerInstance(locale))) {
    541                     result.append(",number,integer");
    542                 } else {
    543                     if (fmt instanceof DecimalFormat) {
    544                         result.append(",number,").append(((DecimalFormat)fmt).toPattern());
    545                     } else if (fmt instanceof ChoiceFormat) {
    546                         result.append(",choice,").append(((ChoiceFormat)fmt).toPattern());
    547                     } else {
    548                         // UNKNOWN
    549                     }
    550                 }
    551             } else if (fmt instanceof DateFormat) {
    552                 int index;
    553                 for (index = MODIFIER_DEFAULT; index < DATE_TIME_MODIFIERS.length; index++) {
    554                     DateFormat df = DateFormat.getDateInstance(DATE_TIME_MODIFIERS[index],
    555                                                                locale);
    556                     if (fmt.equals(df)) {
    557                         result.append(",date");
    558                         break;
    559                     }
    560                     df = DateFormat.getTimeInstance(DATE_TIME_MODIFIERS[index],
    561                                                     locale);
    562                     if (fmt.equals(df)) {
    563                         result.append(",time");
    564                         break;
    565                     }
    566                 }
    567                 if (index >= DATE_TIME_MODIFIERS.length) {
    568                     if (fmt instanceof SimpleDateFormat) {
    569                         result.append(",date,").append(((SimpleDateFormat)fmt).toPattern());
    570                     } else {
    571                         // UNKNOWN
    572                     }
    573                 } else if (index != MODIFIER_DEFAULT) {
    574                     result.append(',').append(DATE_TIME_MODIFIER_KEYWORDS[index]);
    575                 }
    576             } else {
    577                 //result.append(", unknown");
    578             }
    579             result.append('}');
    580         }
    581         copyAndFixQuotes(pattern, lastOffset, pattern.length(), result);
    582         return result.toString();
    583     }
    584 
    585     /**
    586      * Sets the formats to use for the values passed into
    587      * <code>format</code> methods or returned from <code>parse</code>
    588      * methods. The indices of elements in <code>newFormats</code>
    589      * correspond to the argument indices used in the previously set
    590      * pattern string.
    591      * The order of formats in <code>newFormats</code> thus corresponds to
    592      * the order of elements in the <code>arguments</code> array passed
    593      * to the <code>format</code> methods or the result array returned
    594      * by the <code>parse</code> methods.
    595      * <p>
    596      * If an argument index is used for more than one format element
    597      * in the pattern string, then the corresponding new format is used
    598      * for all such format elements. If an argument index is not used
    599      * for any format element in the pattern string, then the
    600      * corresponding new format is ignored. If fewer formats are provided
    601      * than needed, then only the formats for argument indices less
    602      * than <code>newFormats.length</code> are replaced.
    603      *
    604      * @param newFormats the new formats to use
    605      * @exception NullPointerException if <code>newFormats</code> is null
    606      * @since 1.4
    607      */
    608     public void setFormatsByArgumentIndex(Format[] newFormats) {
    609         for (int i = 0; i <= maxOffset; i++) {
    610             int j = argumentNumbers[i];
    611             if (j < newFormats.length) {
    612                 formats[i] = newFormats[j];
    613             }
    614         }
    615     }
    616 
    617     /**
    618      * Sets the formats to use for the format elements in the
    619      * previously set pattern string.
    620      * The order of formats in <code>newFormats</code> corresponds to
    621      * the order of format elements in the pattern string.
    622      * <p>
    623      * If more formats are provided than needed by the pattern string,
    624      * the remaining ones are ignored. If fewer formats are provided
    625      * than needed, then only the first <code>newFormats.length</code>
    626      * formats are replaced.
    627      * <p>
    628      * Since the order of format elements in a pattern string often
    629      * changes during localization, it is generally better to use the
    630      * {@link #setFormatsByArgumentIndex setFormatsByArgumentIndex}
    631      * method, which assumes an order of formats corresponding to the
    632      * order of elements in the <code>arguments</code> array passed to
    633      * the <code>format</code> methods or the result array returned by
    634      * the <code>parse</code> methods.
    635      *
    636      * @param newFormats the new formats to use
    637      * @exception NullPointerException if <code>newFormats</code> is null
    638      */
    639     public void setFormats(Format[] newFormats) {
    640         int runsToCopy = newFormats.length;
    641         if (runsToCopy > maxOffset + 1) {
    642             runsToCopy = maxOffset + 1;
    643         }
    644         for (int i = 0; i < runsToCopy; i++) {
    645             formats[i] = newFormats[i];
    646         }
    647     }
    648 
    649     /**
    650      * Sets the format to use for the format elements within the
    651      * previously set pattern string that use the given argument
    652      * index.
    653      * The argument index is part of the format element definition and
    654      * represents an index into the <code>arguments</code> array passed
    655      * to the <code>format</code> methods or the result array returned
    656      * by the <code>parse</code> methods.
    657      * <p>
    658      * If the argument index is used for more than one format element
    659      * in the pattern string, then the new format is used for all such
    660      * format elements. If the argument index is not used for any format
    661      * element in the pattern string, then the new format is ignored.
    662      *
    663      * @param argumentIndex the argument index for which to use the new format
    664      * @param newFormat the new format to use
    665      * @since 1.4
    666      */
    667     public void setFormatByArgumentIndex(int argumentIndex, Format newFormat) {
    668         for (int j = 0; j <= maxOffset; j++) {
    669             if (argumentNumbers[j] == argumentIndex) {
    670                 formats[j] = newFormat;
    671             }
    672         }
    673     }
    674 
    675     /**
    676      * Sets the format to use for the format element with the given
    677      * format element index within the previously set pattern string.
    678      * The format element index is the zero-based number of the format
    679      * element counting from the start of the pattern string.
    680      * <p>
    681      * Since the order of format elements in a pattern string often
    682      * changes during localization, it is generally better to use the
    683      * {@link #setFormatByArgumentIndex setFormatByArgumentIndex}
    684      * method, which accesses format elements based on the argument
    685      * index they specify.
    686      *
    687      * @param formatElementIndex the index of a format element within the pattern
    688      * @param newFormat the format to use for the specified format element
    689      * @exception ArrayIndexOutOfBoundsException if {@code formatElementIndex} is equal to or
    690      *            larger than the number of format elements in the pattern string
    691      */
    692     public void setFormat(int formatElementIndex, Format newFormat) {
    693         // Android-added: prevent setting unused formatters.
    694         if (formatElementIndex > maxOffset) {
    695             throw new ArrayIndexOutOfBoundsException(maxOffset, formatElementIndex);
    696         }
    697         formats[formatElementIndex] = newFormat;
    698     }
    699 
    700     /**
    701      * Gets the formats used for the values passed into
    702      * <code>format</code> methods or returned from <code>parse</code>
    703      * methods. The indices of elements in the returned array
    704      * correspond to the argument indices used in the previously set
    705      * pattern string.
    706      * The order of formats in the returned array thus corresponds to
    707      * the order of elements in the <code>arguments</code> array passed
    708      * to the <code>format</code> methods or the result array returned
    709      * by the <code>parse</code> methods.
    710      * <p>
    711      * If an argument index is used for more than one format element
    712      * in the pattern string, then the format used for the last such
    713      * format element is returned in the array. If an argument index
    714      * is not used for any format element in the pattern string, then
    715      * null is returned in the array.
    716      *
    717      * @return the formats used for the arguments within the pattern
    718      * @since 1.4
    719      */
    720     public Format[] getFormatsByArgumentIndex() {
    721         int maximumArgumentNumber = -1;
    722         for (int i = 0; i <= maxOffset; i++) {
    723             if (argumentNumbers[i] > maximumArgumentNumber) {
    724                 maximumArgumentNumber = argumentNumbers[i];
    725             }
    726         }
    727         Format[] resultArray = new Format[maximumArgumentNumber + 1];
    728         for (int i = 0; i <= maxOffset; i++) {
    729             resultArray[argumentNumbers[i]] = formats[i];
    730         }
    731         return resultArray;
    732     }
    733 
    734     /**
    735      * Gets the formats used for the format elements in the
    736      * previously set pattern string.
    737      * The order of formats in the returned array corresponds to
    738      * the order of format elements in the pattern string.
    739      * <p>
    740      * Since the order of format elements in a pattern string often
    741      * changes during localization, it's generally better to use the
    742      * {@link #getFormatsByArgumentIndex getFormatsByArgumentIndex}
    743      * method, which assumes an order of formats corresponding to the
    744      * order of elements in the <code>arguments</code> array passed to
    745      * the <code>format</code> methods or the result array returned by
    746      * the <code>parse</code> methods.
    747      *
    748      * @return the formats used for the format elements in the pattern
    749      */
    750     public Format[] getFormats() {
    751         Format[] resultArray = new Format[maxOffset + 1];
    752         System.arraycopy(formats, 0, resultArray, 0, maxOffset + 1);
    753         return resultArray;
    754     }
    755 
    756     /**
    757      * Formats an array of objects and appends the <code>MessageFormat</code>'s
    758      * pattern, with format elements replaced by the formatted objects, to the
    759      * provided <code>StringBuffer</code>.
    760      * <p>
    761      * The text substituted for the individual format elements is derived from
    762      * the current subformat of the format element and the
    763      * <code>arguments</code> element at the format element's argument index
    764      * as indicated by the first matching line of the following table. An
    765      * argument is <i>unavailable</i> if <code>arguments</code> is
    766      * <code>null</code> or has fewer than argumentIndex+1 elements.
    767      *
    768      * <table border=1 summary="Examples of subformat,argument,and formatted text">
    769      *    <tr>
    770      *       <th>Subformat
    771      *       <th>Argument
    772      *       <th>Formatted Text
    773      *    <tr>
    774      *       <td><i>any</i>
    775      *       <td><i>unavailable</i>
    776      *       <td><code>"{" + argumentIndex + "}"</code>
    777      *    <tr>
    778      *       <td><i>any</i>
    779      *       <td><code>null</code>
    780      *       <td><code>"null"</code>
    781      *    <tr>
    782      *       <td><code>instanceof ChoiceFormat</code>
    783      *       <td><i>any</i>
    784      *       <td><code>subformat.format(argument).indexOf('{') &gt;= 0 ?<br>
    785      *           (new MessageFormat(subformat.format(argument), getLocale())).format(argument) :
    786      *           subformat.format(argument)</code>
    787      *    <tr>
    788      *       <td><code>!= null</code>
    789      *       <td><i>any</i>
    790      *       <td><code>subformat.format(argument)</code>
    791      *    <tr>
    792      *       <td><code>null</code>
    793      *       <td><code>instanceof Number</code>
    794      *       <td><code>NumberFormat.getInstance(getLocale()).format(argument)</code>
    795      *    <tr>
    796      *       <td><code>null</code>
    797      *       <td><code>instanceof Date</code>
    798      *       <td><code>DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, getLocale()).format(argument)</code>
    799      *    <tr>
    800      *       <td><code>null</code>
    801      *       <td><code>instanceof String</code>
    802      *       <td><code>argument</code>
    803      *    <tr>
    804      *       <td><code>null</code>
    805      *       <td><i>any</i>
    806      *       <td><code>argument.toString()</code>
    807      * </table>
    808      * <p>
    809      * If <code>pos</code> is non-null, and refers to
    810      * <code>Field.ARGUMENT</code>, the location of the first formatted
    811      * string will be returned.
    812      *
    813      * @param arguments an array of objects to be formatted and substituted.
    814      * @param result where text is appended.
    815      * @param pos On input: an alignment field, if desired.
    816      *            On output: the offsets of the alignment field.
    817      * @return the string buffer passed in as {@code result}, with formatted
    818      * text appended
    819      * @exception IllegalArgumentException if an argument in the
    820      *            <code>arguments</code> array is not of the type
    821      *            expected by the format element(s) that use it.
    822      */
    823     public final StringBuffer format(Object[] arguments, StringBuffer result,
    824                                      FieldPosition pos)
    825     {
    826         return subformat(arguments, result, pos, null);
    827     }
    828 
    829     /**
    830      * Creates a MessageFormat with the given pattern and uses it
    831      * to format the given arguments. This is equivalent to
    832      * <blockquote>
    833      *     <code>(new {@link #MessageFormat(String) MessageFormat}(pattern)).{@link #format(java.lang.Object[], java.lang.StringBuffer, java.text.FieldPosition) format}(arguments, new StringBuffer(), null).toString()</code>
    834      * </blockquote>
    835      *
    836      * @param pattern   the pattern string
    837      * @param arguments object(s) to format
    838      * @return the formatted string
    839      * @exception IllegalArgumentException if the pattern is invalid,
    840      *            or if an argument in the <code>arguments</code> array
    841      *            is not of the type expected by the format element(s)
    842      *            that use it.
    843      */
    844     public static String format(String pattern, Object ... arguments) {
    845         MessageFormat temp = new MessageFormat(pattern);
    846         return temp.format(arguments);
    847     }
    848 
    849     // Overrides
    850     /**
    851      * Formats an array of objects and appends the <code>MessageFormat</code>'s
    852      * pattern, with format elements replaced by the formatted objects, to the
    853      * provided <code>StringBuffer</code>.
    854      * This is equivalent to
    855      * <blockquote>
    856      *     <code>{@link #format(java.lang.Object[], java.lang.StringBuffer, java.text.FieldPosition) format}((Object[]) arguments, result, pos)</code>
    857      * </blockquote>
    858      *
    859      * @param arguments an array of objects to be formatted and substituted.
    860      * @param result where text is appended.
    861      * @param pos On input: an alignment field, if desired.
    862      *            On output: the offsets of the alignment field.
    863      * @exception IllegalArgumentException if an argument in the
    864      *            <code>arguments</code> array is not of the type
    865      *            expected by the format element(s) that use it.
    866      */
    867     public final StringBuffer format(Object arguments, StringBuffer result,
    868                                      FieldPosition pos)
    869     {
    870         return subformat((Object[]) arguments, result, pos, null);
    871     }
    872 
    873     /**
    874      * Formats an array of objects and inserts them into the
    875      * <code>MessageFormat</code>'s pattern, producing an
    876      * <code>AttributedCharacterIterator</code>.
    877      * You can use the returned <code>AttributedCharacterIterator</code>
    878      * to build the resulting String, as well as to determine information
    879      * about the resulting String.
    880      * <p>
    881      * The text of the returned <code>AttributedCharacterIterator</code> is
    882      * the same that would be returned by
    883      * <blockquote>
    884      *     <code>{@link #format(java.lang.Object[], java.lang.StringBuffer, java.text.FieldPosition) format}(arguments, new StringBuffer(), null).toString()</code>
    885      * </blockquote>
    886      * <p>
    887      * In addition, the <code>AttributedCharacterIterator</code> contains at
    888      * least attributes indicating where text was generated from an
    889      * argument in the <code>arguments</code> array. The keys of these attributes are of
    890      * type <code>MessageFormat.Field</code>, their values are
    891      * <code>Integer</code> objects indicating the index in the <code>arguments</code>
    892      * array of the argument from which the text was generated.
    893      * <p>
    894      * The attributes/value from the underlying <code>Format</code>
    895      * instances that <code>MessageFormat</code> uses will also be
    896      * placed in the resulting <code>AttributedCharacterIterator</code>.
    897      * This allows you to not only find where an argument is placed in the
    898      * resulting String, but also which fields it contains in turn.
    899      *
    900      * @param arguments an array of objects to be formatted and substituted.
    901      * @return AttributedCharacterIterator describing the formatted value.
    902      * @exception NullPointerException if <code>arguments</code> is null.
    903      * @exception IllegalArgumentException if an argument in the
    904      *            <code>arguments</code> array is not of the type
    905      *            expected by the format element(s) that use it.
    906      * @since 1.4
    907      */
    908     public AttributedCharacterIterator formatToCharacterIterator(Object arguments) {
    909         StringBuffer result = new StringBuffer();
    910         ArrayList<AttributedCharacterIterator> iterators = new ArrayList<>();
    911 
    912         if (arguments == null) {
    913             throw new NullPointerException(
    914                    "formatToCharacterIterator must be passed non-null object");
    915         }
    916         subformat((Object[]) arguments, result, null, iterators);
    917         if (iterators.size() == 0) {
    918             return createAttributedCharacterIterator("");
    919         }
    920         return createAttributedCharacterIterator(
    921                      iterators.toArray(
    922                      new AttributedCharacterIterator[iterators.size()]));
    923     }
    924 
    925     /**
    926      * Parses the string.
    927      *
    928      * <p>Caveats: The parse may fail in a number of circumstances.
    929      * For example:
    930      * <ul>
    931      * <li>If one of the arguments does not occur in the pattern.
    932      * <li>If the format of an argument loses information, such as
    933      *     with a choice format where a large number formats to "many".
    934      * <li>Does not yet handle recursion (where
    935      *     the substituted strings contain {n} references.)
    936      * <li>Will not always find a match (or the correct match)
    937      *     if some part of the parse is ambiguous.
    938      *     For example, if the pattern "{1},{2}" is used with the
    939      *     string arguments {"a,b", "c"}, it will format as "a,b,c".
    940      *     When the result is parsed, it will return {"a", "b,c"}.
    941      * <li>If a single argument is parsed more than once in the string,
    942      *     then the later parse wins.
    943      * </ul>
    944      * When the parse fails, use ParsePosition.getErrorIndex() to find out
    945      * where in the string the parsing failed.  The returned error
    946      * index is the starting offset of the sub-patterns that the string
    947      * is comparing with.  For example, if the parsing string "AAA {0} BBB"
    948      * is comparing against the pattern "AAD {0} BBB", the error index is
    949      * 0. When an error occurs, the call to this method will return null.
    950      * If the source is null, return an empty array.
    951      *
    952      * @param source the string to parse
    953      * @param pos    the parse position
    954      * @return an array of parsed objects
    955      */
    956     public Object[] parse(String source, ParsePosition pos) {
    957         if (source == null) {
    958             Object[] empty = {};
    959             return empty;
    960         }
    961 
    962         int maximumArgumentNumber = -1;
    963         for (int i = 0; i <= maxOffset; i++) {
    964             if (argumentNumbers[i] > maximumArgumentNumber) {
    965                 maximumArgumentNumber = argumentNumbers[i];
    966             }
    967         }
    968         Object[] resultArray = new Object[maximumArgumentNumber + 1];
    969 
    970         int patternOffset = 0;
    971         int sourceOffset = pos.index;
    972         ParsePosition tempStatus = new ParsePosition(0);
    973         for (int i = 0; i <= maxOffset; ++i) {
    974             // match up to format
    975             int len = offsets[i] - patternOffset;
    976             if (len == 0 || pattern.regionMatches(patternOffset,
    977                                                   source, sourceOffset, len)) {
    978                 sourceOffset += len;
    979                 patternOffset += len;
    980             } else {
    981                 pos.errorIndex = sourceOffset;
    982                 return null; // leave index as is to signal error
    983             }
    984 
    985             // now use format
    986             if (formats[i] == null) {   // string format
    987                 // if at end, use longest possible match
    988                 // otherwise uses first match to intervening string
    989                 // does NOT recursively try all possibilities
    990                 int tempLength = (i != maxOffset) ? offsets[i+1] : pattern.length();
    991 
    992                 int next;
    993                 if (patternOffset >= tempLength) {
    994                     next = source.length();
    995                 }else{
    996                     next = source.indexOf(pattern.substring(patternOffset, tempLength),
    997                                           sourceOffset);
    998                 }
    999 
   1000                 if (next < 0) {
   1001                     pos.errorIndex = sourceOffset;
   1002                     return null; // leave index as is to signal error
   1003                 } else {
   1004                     String strValue= source.substring(sourceOffset,next);
   1005                     if (!strValue.equals("{"+argumentNumbers[i]+"}"))
   1006                         resultArray[argumentNumbers[i]]
   1007                             = source.substring(sourceOffset,next);
   1008                     sourceOffset = next;
   1009                 }
   1010             } else {
   1011                 tempStatus.index = sourceOffset;
   1012                 resultArray[argumentNumbers[i]]
   1013                     = formats[i].parseObject(source,tempStatus);
   1014                 if (tempStatus.index == sourceOffset) {
   1015                     pos.errorIndex = sourceOffset;
   1016                     return null; // leave index as is to signal error
   1017                 }
   1018                 sourceOffset = tempStatus.index; // update
   1019             }
   1020         }
   1021         int len = pattern.length() - patternOffset;
   1022         if (len == 0 || pattern.regionMatches(patternOffset,
   1023                                               source, sourceOffset, len)) {
   1024             pos.index = sourceOffset + len;
   1025         } else {
   1026             pos.errorIndex = sourceOffset;
   1027             return null; // leave index as is to signal error
   1028         }
   1029         return resultArray;
   1030     }
   1031 
   1032     /**
   1033      * Parses text from the beginning of the given string to produce an object
   1034      * array.
   1035      * The method may not use the entire text of the given string.
   1036      * <p>
   1037      * See the {@link #parse(String, ParsePosition)} method for more information
   1038      * on message parsing.
   1039      *
   1040      * @param source A <code>String</code> whose beginning should be parsed.
   1041      * @return An <code>Object</code> array parsed from the string.
   1042      * @exception ParseException if the beginning of the specified string
   1043      *            cannot be parsed.
   1044      */
   1045     public Object[] parse(String source) throws ParseException {
   1046         ParsePosition pos  = new ParsePosition(0);
   1047         Object[] result = parse(source, pos);
   1048         if (pos.index == 0)  // unchanged, returned object is null
   1049             throw new ParseException("MessageFormat parse error!", pos.errorIndex);
   1050 
   1051         return result;
   1052     }
   1053 
   1054     /**
   1055      * Parses text from a string to produce an object array.
   1056      * <p>
   1057      * The method attempts to parse text starting at the index given by
   1058      * <code>pos</code>.
   1059      * If parsing succeeds, then the index of <code>pos</code> is updated
   1060      * to the index after the last character used (parsing does not necessarily
   1061      * use all characters up to the end of the string), and the parsed
   1062      * object array is returned. The updated <code>pos</code> can be used to
   1063      * indicate the starting point for the next call to this method.
   1064      * If an error occurs, then the index of <code>pos</code> is not
   1065      * changed, the error index of <code>pos</code> is set to the index of
   1066      * the character where the error occurred, and null is returned.
   1067      * <p>
   1068      * See the {@link #parse(String, ParsePosition)} method for more information
   1069      * on message parsing.
   1070      *
   1071      * @param source A <code>String</code>, part of which should be parsed.
   1072      * @param pos A <code>ParsePosition</code> object with index and error
   1073      *            index information as described above.
   1074      * @return An <code>Object</code> array parsed from the string. In case of
   1075      *         error, returns null.
   1076      * @exception NullPointerException if <code>pos</code> is null.
   1077      */
   1078     public Object parseObject(String source, ParsePosition pos) {
   1079         return parse(source, pos);
   1080     }
   1081 
   1082     /**
   1083      * Creates and returns a copy of this object.
   1084      *
   1085      * @return a clone of this instance.
   1086      */
   1087     public Object clone() {
   1088         MessageFormat other = (MessageFormat) super.clone();
   1089 
   1090         // clone arrays. Can't do with utility because of bug in Cloneable
   1091         other.formats = formats.clone(); // shallow clone
   1092         for (int i = 0; i < formats.length; ++i) {
   1093             if (formats[i] != null)
   1094                 other.formats[i] = (Format)formats[i].clone();
   1095         }
   1096         // for primitives or immutables, shallow clone is enough
   1097         other.offsets = offsets.clone();
   1098         other.argumentNumbers = argumentNumbers.clone();
   1099 
   1100         return other;
   1101     }
   1102 
   1103     /**
   1104      * Equality comparison between two message format objects
   1105      */
   1106     public boolean equals(Object obj) {
   1107         if (this == obj)                      // quick check
   1108             return true;
   1109         if (obj == null || getClass() != obj.getClass())
   1110             return false;
   1111         MessageFormat other = (MessageFormat) obj;
   1112         return (maxOffset == other.maxOffset
   1113                 && pattern.equals(other.pattern)
   1114                 && ((locale != null && locale.equals(other.locale))
   1115                  || (locale == null && other.locale == null))
   1116                 && Arrays.equals(offsets,other.offsets)
   1117                 && Arrays.equals(argumentNumbers,other.argumentNumbers)
   1118                 && Arrays.equals(formats,other.formats));
   1119     }
   1120 
   1121     /**
   1122      * Generates a hash code for the message format object.
   1123      */
   1124     public int hashCode() {
   1125         return pattern.hashCode(); // enough for reasonable distribution
   1126     }
   1127 
   1128 
   1129     /**
   1130      * Defines constants that are used as attribute keys in the
   1131      * <code>AttributedCharacterIterator</code> returned
   1132      * from <code>MessageFormat.formatToCharacterIterator</code>.
   1133      *
   1134      * @since 1.4
   1135      */
   1136     public static class Field extends Format.Field {
   1137 
   1138         // Proclaim serial compatibility with 1.4 FCS
   1139         private static final long serialVersionUID = 7899943957617360810L;
   1140 
   1141         /**
   1142          * Creates a Field with the specified name.
   1143          *
   1144          * @param name Name of the attribute
   1145          */
   1146         protected Field(String name) {
   1147             super(name);
   1148         }
   1149 
   1150         /**
   1151          * Resolves instances being deserialized to the predefined constants.
   1152          *
   1153          * @throws InvalidObjectException if the constant could not be
   1154          *         resolved.
   1155          * @return resolved MessageFormat.Field constant
   1156          */
   1157         protected Object readResolve() throws InvalidObjectException {
   1158             if (this.getClass() != MessageFormat.Field.class) {
   1159                 throw new InvalidObjectException("subclass didn't correctly implement readResolve");
   1160             }
   1161 
   1162             return ARGUMENT;
   1163         }
   1164 
   1165         //
   1166         // The constants
   1167         //
   1168 
   1169         /**
   1170          * Constant identifying a portion of a message that was generated
   1171          * from an argument passed into <code>formatToCharacterIterator</code>.
   1172          * The value associated with the key will be an <code>Integer</code>
   1173          * indicating the index in the <code>arguments</code> array of the
   1174          * argument from which the text was generated.
   1175          */
   1176         public final static Field ARGUMENT =
   1177                            new Field("message argument field");
   1178     }
   1179 
   1180     // ===========================privates============================
   1181 
   1182     /**
   1183      * The locale to use for formatting numbers and dates.
   1184      * @serial
   1185      */
   1186     private Locale locale;
   1187 
   1188     /**
   1189      * The string that the formatted values are to be plugged into.  In other words, this
   1190      * is the pattern supplied on construction with all of the {} expressions taken out.
   1191      * @serial
   1192      */
   1193     private String pattern = "";
   1194 
   1195     /** The initially expected number of subformats in the format */
   1196     private static final int INITIAL_FORMATS = 10;
   1197 
   1198     /**
   1199      * An array of formatters, which are used to format the arguments.
   1200      * @serial
   1201      */
   1202     private Format[] formats = new Format[INITIAL_FORMATS];
   1203 
   1204     /**
   1205      * The positions where the results of formatting each argument are to be inserted
   1206      * into the pattern.
   1207      * @serial
   1208      */
   1209     private int[] offsets = new int[INITIAL_FORMATS];
   1210 
   1211     /**
   1212      * The argument numbers corresponding to each formatter.  (The formatters are stored
   1213      * in the order they occur in the pattern, not in the order in which the arguments
   1214      * are specified.)
   1215      * @serial
   1216      */
   1217     private int[] argumentNumbers = new int[INITIAL_FORMATS];
   1218 
   1219     /**
   1220      * One less than the number of entries in <code>offsets</code>.  Can also be thought of
   1221      * as the index of the highest-numbered element in <code>offsets</code> that is being used.
   1222      * All of these arrays should have the same number of elements being used as <code>offsets</code>
   1223      * does, and so this variable suffices to tell us how many entries are in all of them.
   1224      * @serial
   1225      */
   1226     private int maxOffset = -1;
   1227 
   1228     /**
   1229      * Internal routine used by format. If <code>characterIterators</code> is
   1230      * non-null, AttributedCharacterIterator will be created from the
   1231      * subformats as necessary. If <code>characterIterators</code> is null
   1232      * and <code>fp</code> is non-null and identifies
   1233      * <code>Field.MESSAGE_ARGUMENT</code>, the location of
   1234      * the first replaced argument will be set in it.
   1235      *
   1236      * @exception IllegalArgumentException if an argument in the
   1237      *            <code>arguments</code> array is not of the type
   1238      *            expected by the format element(s) that use it.
   1239      */
   1240     private StringBuffer subformat(Object[] arguments, StringBuffer result,
   1241                                    FieldPosition fp, List<AttributedCharacterIterator> characterIterators) {
   1242         // note: this implementation assumes a fast substring & index.
   1243         // if this is not true, would be better to append chars one by one.
   1244         int lastOffset = 0;
   1245         int last = result.length();
   1246         for (int i = 0; i <= maxOffset; ++i) {
   1247             result.append(pattern.substring(lastOffset, offsets[i]));
   1248             lastOffset = offsets[i];
   1249             int argumentNumber = argumentNumbers[i];
   1250             if (arguments == null || argumentNumber >= arguments.length) {
   1251                 result.append('{').append(argumentNumber).append('}');
   1252                 continue;
   1253             }
   1254             // int argRecursion = ((recursionProtection >> (argumentNumber*2)) & 0x3);
   1255             if (false) { // if (argRecursion == 3){
   1256                 // prevent loop!!!
   1257                 result.append('\uFFFD');
   1258             } else {
   1259                 Object obj = arguments[argumentNumber];
   1260                 String arg = null;
   1261                 Format subFormatter = null;
   1262                 if (obj == null) {
   1263                     arg = "null";
   1264                 } else if (formats[i] != null) {
   1265                     subFormatter = formats[i];
   1266                     if (subFormatter instanceof ChoiceFormat) {
   1267                         arg = formats[i].format(obj);
   1268                         if (arg.indexOf('{') >= 0) {
   1269                             subFormatter = new MessageFormat(arg, locale);
   1270                             obj = arguments;
   1271                             arg = null;
   1272                         }
   1273                     }
   1274                 } else if (obj instanceof Number) {
   1275                     // format number if can
   1276                     subFormatter = NumberFormat.getInstance(locale);
   1277                 } else if (obj instanceof Date) {
   1278                     // format a Date if can
   1279                     subFormatter = DateFormat.getDateTimeInstance(
   1280                              DateFormat.SHORT, DateFormat.SHORT, locale);//fix
   1281                 } else if (obj instanceof String) {
   1282                     arg = (String) obj;
   1283 
   1284                 } else {
   1285                     arg = obj.toString();
   1286                     if (arg == null) arg = "null";
   1287                 }
   1288 
   1289                 // At this point we are in two states, either subFormatter
   1290                 // is non-null indicating we should format obj using it,
   1291                 // or arg is non-null and we should use it as the value.
   1292 
   1293                 if (characterIterators != null) {
   1294                     // If characterIterators is non-null, it indicates we need
   1295                     // to get the CharacterIterator from the child formatter.
   1296                     if (last != result.length()) {
   1297                         characterIterators.add(
   1298                             createAttributedCharacterIterator(result.substring
   1299                                                               (last)));
   1300                         last = result.length();
   1301                     }
   1302                     if (subFormatter != null) {
   1303                         AttributedCharacterIterator subIterator =
   1304                                    subFormatter.formatToCharacterIterator(obj);
   1305 
   1306                         append(result, subIterator);
   1307                         if (last != result.length()) {
   1308                             characterIterators.add(
   1309                                          createAttributedCharacterIterator(
   1310                                          subIterator, Field.ARGUMENT,
   1311                                          Integer.valueOf(argumentNumber)));
   1312                             last = result.length();
   1313                         }
   1314                         arg = null;
   1315                     }
   1316                     if (arg != null && arg.length() > 0) {
   1317                         result.append(arg);
   1318                         characterIterators.add(
   1319                                  createAttributedCharacterIterator(
   1320                                  arg, Field.ARGUMENT,
   1321                                  Integer.valueOf(argumentNumber)));
   1322                         last = result.length();
   1323                     }
   1324                 }
   1325                 else {
   1326                     if (subFormatter != null) {
   1327                         arg = subFormatter.format(obj);
   1328                     }
   1329                     last = result.length();
   1330                     result.append(arg);
   1331                     if (i == 0 && fp != null && Field.ARGUMENT.equals(
   1332                                   fp.getFieldAttribute())) {
   1333                         fp.setBeginIndex(last);
   1334                         fp.setEndIndex(result.length());
   1335                     }
   1336                     last = result.length();
   1337                 }
   1338             }
   1339         }
   1340         result.append(pattern.substring(lastOffset, pattern.length()));
   1341         if (characterIterators != null && last != result.length()) {
   1342             characterIterators.add(createAttributedCharacterIterator(
   1343                                    result.substring(last)));
   1344         }
   1345         return result;
   1346     }
   1347 
   1348     /**
   1349      * Convenience method to append all the characters in
   1350      * <code>iterator</code> to the StringBuffer <code>result</code>.
   1351      */
   1352     private void append(StringBuffer result, CharacterIterator iterator) {
   1353         if (iterator.first() != CharacterIterator.DONE) {
   1354             char aChar;
   1355 
   1356             result.append(iterator.first());
   1357             while ((aChar = iterator.next()) != CharacterIterator.DONE) {
   1358                 result.append(aChar);
   1359             }
   1360         }
   1361     }
   1362 
   1363     // Indices for segments
   1364     private static final int SEG_RAW      = 0;
   1365     private static final int SEG_INDEX    = 1;
   1366     private static final int SEG_TYPE     = 2;
   1367     private static final int SEG_MODIFIER = 3; // modifier or subformat
   1368 
   1369     // Indices for type keywords
   1370     private static final int TYPE_NULL    = 0;
   1371     private static final int TYPE_NUMBER  = 1;
   1372     private static final int TYPE_DATE    = 2;
   1373     private static final int TYPE_TIME    = 3;
   1374     private static final int TYPE_CHOICE  = 4;
   1375 
   1376     private static final String[] TYPE_KEYWORDS = {
   1377         "",
   1378         "number",
   1379         "date",
   1380         "time",
   1381         "choice"
   1382     };
   1383 
   1384     // Indices for number modifiers
   1385     private static final int MODIFIER_DEFAULT  = 0; // common in number and date-time
   1386     private static final int MODIFIER_CURRENCY = 1;
   1387     private static final int MODIFIER_PERCENT  = 2;
   1388     private static final int MODIFIER_INTEGER  = 3;
   1389 
   1390     private static final String[] NUMBER_MODIFIER_KEYWORDS = {
   1391         "",
   1392         "currency",
   1393         "percent",
   1394         "integer"
   1395     };
   1396 
   1397     // Indices for date-time modifiers
   1398     private static final int MODIFIER_SHORT   = 1;
   1399     private static final int MODIFIER_MEDIUM  = 2;
   1400     private static final int MODIFIER_LONG    = 3;
   1401     private static final int MODIFIER_FULL    = 4;
   1402 
   1403     private static final String[] DATE_TIME_MODIFIER_KEYWORDS = {
   1404         "",
   1405         "short",
   1406         "medium",
   1407         "long",
   1408         "full"
   1409     };
   1410 
   1411     // Date-time style values corresponding to the date-time modifiers.
   1412     private static final int[] DATE_TIME_MODIFIERS = {
   1413         DateFormat.DEFAULT,
   1414         DateFormat.SHORT,
   1415         DateFormat.MEDIUM,
   1416         DateFormat.LONG,
   1417         DateFormat.FULL,
   1418     };
   1419 
   1420     private void makeFormat(int position, int offsetNumber,
   1421                             StringBuilder[] textSegments)
   1422     {
   1423         String[] segments = new String[textSegments.length];
   1424         for (int i = 0; i < textSegments.length; i++) {
   1425             StringBuilder oneseg = textSegments[i];
   1426             segments[i] = (oneseg != null) ? oneseg.toString() : "";
   1427         }
   1428 
   1429         // get the argument number
   1430         int argumentNumber;
   1431         try {
   1432             argumentNumber = Integer.parseInt(segments[SEG_INDEX]); // always unlocalized!
   1433         } catch (NumberFormatException e) {
   1434             throw new IllegalArgumentException("can't parse argument number: "
   1435                                                + segments[SEG_INDEX], e);
   1436         }
   1437         if (argumentNumber < 0) {
   1438             throw new IllegalArgumentException("negative argument number: "
   1439                                                + argumentNumber);
   1440         }
   1441 
   1442         // resize format information arrays if necessary
   1443         if (offsetNumber >= formats.length) {
   1444             int newLength = formats.length * 2;
   1445             Format[] newFormats = new Format[newLength];
   1446             int[] newOffsets = new int[newLength];
   1447             int[] newArgumentNumbers = new int[newLength];
   1448             System.arraycopy(formats, 0, newFormats, 0, maxOffset + 1);
   1449             System.arraycopy(offsets, 0, newOffsets, 0, maxOffset + 1);
   1450             System.arraycopy(argumentNumbers, 0, newArgumentNumbers, 0, maxOffset + 1);
   1451             formats = newFormats;
   1452             offsets = newOffsets;
   1453             argumentNumbers = newArgumentNumbers;
   1454         }
   1455         int oldMaxOffset = maxOffset;
   1456         maxOffset = offsetNumber;
   1457         offsets[offsetNumber] = segments[SEG_RAW].length();
   1458         argumentNumbers[offsetNumber] = argumentNumber;
   1459 
   1460         // now get the format
   1461         Format newFormat = null;
   1462         if (segments[SEG_TYPE].length() != 0) {
   1463             int type = findKeyword(segments[SEG_TYPE], TYPE_KEYWORDS);
   1464             switch (type) {
   1465             case TYPE_NULL:
   1466                 // Type "" is allowed. e.g., "{0,}", "{0,,}", and "{0,,#}"
   1467                 // are treated as "{0}".
   1468                 break;
   1469 
   1470             case TYPE_NUMBER:
   1471                 switch (findKeyword(segments[SEG_MODIFIER], NUMBER_MODIFIER_KEYWORDS)) {
   1472                 case MODIFIER_DEFAULT:
   1473                     newFormat = NumberFormat.getInstance(locale);
   1474                     break;
   1475                 case MODIFIER_CURRENCY:
   1476                     newFormat = NumberFormat.getCurrencyInstance(locale);
   1477                     break;
   1478                 case MODIFIER_PERCENT:
   1479                     newFormat = NumberFormat.getPercentInstance(locale);
   1480                     break;
   1481                 case MODIFIER_INTEGER:
   1482                     newFormat = NumberFormat.getIntegerInstance(locale);
   1483                     break;
   1484                 default: // DecimalFormat pattern
   1485                     try {
   1486                         newFormat = new DecimalFormat(segments[SEG_MODIFIER],
   1487                                                       DecimalFormatSymbols.getInstance(locale));
   1488                     } catch (IllegalArgumentException e) {
   1489                         maxOffset = oldMaxOffset;
   1490                         throw e;
   1491                     }
   1492                     break;
   1493                 }
   1494                 break;
   1495 
   1496             case TYPE_DATE:
   1497             case TYPE_TIME:
   1498                 int mod = findKeyword(segments[SEG_MODIFIER], DATE_TIME_MODIFIER_KEYWORDS);
   1499                 if (mod >= 0 && mod < DATE_TIME_MODIFIER_KEYWORDS.length) {
   1500                     if (type == TYPE_DATE) {
   1501                         newFormat = DateFormat.getDateInstance(DATE_TIME_MODIFIERS[mod],
   1502                                                                locale);
   1503                     } else {
   1504                         newFormat = DateFormat.getTimeInstance(DATE_TIME_MODIFIERS[mod],
   1505                                                                locale);
   1506                     }
   1507                 } else {
   1508                     // SimpleDateFormat pattern
   1509                     try {
   1510                         newFormat = new SimpleDateFormat(segments[SEG_MODIFIER], locale);
   1511                     } catch (IllegalArgumentException e) {
   1512                         maxOffset = oldMaxOffset;
   1513                         throw e;
   1514                     }
   1515                 }
   1516                 break;
   1517 
   1518             case TYPE_CHOICE:
   1519                 try {
   1520                     // ChoiceFormat pattern
   1521                     newFormat = new ChoiceFormat(segments[SEG_MODIFIER]);
   1522                 } catch (Exception e) {
   1523                     maxOffset = oldMaxOffset;
   1524                     throw new IllegalArgumentException("Choice Pattern incorrect: "
   1525                                                        + segments[SEG_MODIFIER], e);
   1526                 }
   1527                 break;
   1528 
   1529             default:
   1530                 maxOffset = oldMaxOffset;
   1531                 throw new IllegalArgumentException("unknown format type: " +
   1532                                                    segments[SEG_TYPE]);
   1533             }
   1534         }
   1535         formats[offsetNumber] = newFormat;
   1536     }
   1537 
   1538     private static final int findKeyword(String s, String[] list) {
   1539         for (int i = 0; i < list.length; ++i) {
   1540             if (s.equals(list[i]))
   1541                 return i;
   1542         }
   1543 
   1544         // Try trimmed lowercase.
   1545         String ls = s.trim().toLowerCase(Locale.ROOT);
   1546         if (ls != s) {
   1547             for (int i = 0; i < list.length; ++i) {
   1548                 if (ls.equals(list[i]))
   1549                     return i;
   1550             }
   1551         }
   1552         return -1;
   1553     }
   1554 
   1555     private static final void copyAndFixQuotes(String source, int start, int end,
   1556                                                StringBuilder target) {
   1557         boolean quoted = false;
   1558 
   1559         for (int i = start; i < end; ++i) {
   1560             char ch = source.charAt(i);
   1561             if (ch == '{') {
   1562                 if (!quoted) {
   1563                     target.append('\'');
   1564                     quoted = true;
   1565                 }
   1566                 target.append(ch);
   1567             } else if (ch == '\'') {
   1568                 target.append("''");
   1569             } else {
   1570                 if (quoted) {
   1571                     target.append('\'');
   1572                     quoted = false;
   1573                 }
   1574                 target.append(ch);
   1575             }
   1576         }
   1577         if (quoted) {
   1578             target.append('\'');
   1579         }
   1580     }
   1581 
   1582     /**
   1583      * After reading an object from the input stream, do a simple verification
   1584      * to maintain class invariants.
   1585      * @throws InvalidObjectException if the objects read from the stream is invalid.
   1586      */
   1587     private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
   1588         in.defaultReadObject();
   1589         boolean isValid = maxOffset >= -1
   1590                 && formats.length > maxOffset
   1591                 && offsets.length > maxOffset
   1592                 && argumentNumbers.length > maxOffset;
   1593         if (isValid) {
   1594             int lastOffset = pattern.length() + 1;
   1595             for (int i = maxOffset; i >= 0; --i) {
   1596                 if ((offsets[i] < 0) || (offsets[i] > lastOffset)) {
   1597                     isValid = false;
   1598                     break;
   1599                 } else {
   1600                     lastOffset = offsets[i];
   1601                 }
   1602             }
   1603         }
   1604         if (!isValid) {
   1605             throw new InvalidObjectException("Could not reconstruct MessageFormat from corrupt stream.");
   1606         }
   1607     }
   1608 }
   1609