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