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