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