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