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