• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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         // Android-added: prevent setting unused formatters.
694         if (formatElementIndex > maxOffset) {
695             // Note: maxOffset is maximum index, not the length.
696             throw new ArrayIndexOutOfBoundsException(
697                     "maxOffset=" + maxOffset + "; formatElementIndex=" + formatElementIndex);
698         }
699         formats[formatElementIndex] = newFormat;
700     }
701 
702     /**
703      * Gets the formats used for the values passed into
704      * <code>format</code> methods or returned from <code>parse</code>
705      * methods. The indices of elements in the returned array
706      * correspond to the argument indices used in the previously set
707      * pattern string.
708      * The order of formats in the returned array thus corresponds to
709      * the order of elements in the <code>arguments</code> array passed
710      * to the <code>format</code> methods or the result array returned
711      * by the <code>parse</code> methods.
712      * <p>
713      * If an argument index is used for more than one format element
714      * in the pattern string, then the format used for the last such
715      * format element is returned in the array. If an argument index
716      * is not used for any format element in the pattern string, then
717      * null is returned in the array.
718      *
719      * @return the formats used for the arguments within the pattern
720      * @since 1.4
721      */
getFormatsByArgumentIndex()722     public Format[] getFormatsByArgumentIndex() {
723         int maximumArgumentNumber = -1;
724         for (int i = 0; i <= maxOffset; i++) {
725             if (argumentNumbers[i] > maximumArgumentNumber) {
726                 maximumArgumentNumber = argumentNumbers[i];
727             }
728         }
729         Format[] resultArray = new Format[maximumArgumentNumber + 1];
730         for (int i = 0; i <= maxOffset; i++) {
731             resultArray[argumentNumbers[i]] = formats[i];
732         }
733         return resultArray;
734     }
735 
736     /**
737      * Gets the formats used for the format elements in the
738      * previously set pattern string.
739      * The order of formats in the returned array corresponds to
740      * the order of format elements in the pattern string.
741      * <p>
742      * Since the order of format elements in a pattern string often
743      * changes during localization, it's generally better to use the
744      * {@link #getFormatsByArgumentIndex getFormatsByArgumentIndex}
745      * method, which assumes an order of formats corresponding to the
746      * order of elements in the <code>arguments</code> array passed to
747      * the <code>format</code> methods or the result array returned by
748      * the <code>parse</code> methods.
749      *
750      * @return the formats used for the format elements in the pattern
751      */
getFormats()752     public Format[] getFormats() {
753         Format[] resultArray = new Format[maxOffset + 1];
754         System.arraycopy(formats, 0, resultArray, 0, maxOffset + 1);
755         return resultArray;
756     }
757 
758     /**
759      * Formats an array of objects and appends the <code>MessageFormat</code>'s
760      * pattern, with format elements replaced by the formatted objects, to the
761      * provided <code>StringBuffer</code>.
762      * <p>
763      * The text substituted for the individual format elements is derived from
764      * the current subformat of the format element and the
765      * <code>arguments</code> element at the format element's argument index
766      * as indicated by the first matching line of the following table. An
767      * argument is <i>unavailable</i> if <code>arguments</code> is
768      * <code>null</code> or has fewer than argumentIndex+1 elements.
769      *
770      * <table border=1 summary="Examples of subformat,argument,and formatted text">
771      *    <tr>
772      *       <th>Subformat
773      *       <th>Argument
774      *       <th>Formatted Text
775      *    <tr>
776      *       <td><i>any</i>
777      *       <td><i>unavailable</i>
778      *       <td><code>"{" + argumentIndex + "}"</code>
779      *    <tr>
780      *       <td><i>any</i>
781      *       <td><code>null</code>
782      *       <td><code>"null"</code>
783      *    <tr>
784      *       <td><code>instanceof ChoiceFormat</code>
785      *       <td><i>any</i>
786      *       <td><code>subformat.format(argument).indexOf('{') &gt;= 0 ?<br>
787      *           (new MessageFormat(subformat.format(argument), getLocale())).format(argument) :
788      *           subformat.format(argument)</code>
789      *    <tr>
790      *       <td><code>!= null</code>
791      *       <td><i>any</i>
792      *       <td><code>subformat.format(argument)</code>
793      *    <tr>
794      *       <td><code>null</code>
795      *       <td><code>instanceof Number</code>
796      *       <td><code>NumberFormat.getInstance(getLocale()).format(argument)</code>
797      *    <tr>
798      *       <td><code>null</code>
799      *       <td><code>instanceof Date</code>
800      *       <td><code>DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, getLocale()).format(argument)</code>
801      *    <tr>
802      *       <td><code>null</code>
803      *       <td><code>instanceof String</code>
804      *       <td><code>argument</code>
805      *    <tr>
806      *       <td><code>null</code>
807      *       <td><i>any</i>
808      *       <td><code>argument.toString()</code>
809      * </table>
810      * <p>
811      * If <code>pos</code> is non-null, and refers to
812      * <code>Field.ARGUMENT</code>, the location of the first formatted
813      * string will be returned.
814      *
815      * @param arguments an array of objects to be formatted and substituted.
816      * @param result where text is appended.
817      * @param pos On input: an alignment field, if desired.
818      *            On output: the offsets of the alignment field.
819      * @return the string buffer passed in as {@code result}, with formatted
820      * text appended
821      * @exception IllegalArgumentException if an argument in the
822      *            <code>arguments</code> array is not of the type
823      *            expected by the format element(s) that use it.
824      */
format(Object[] arguments, StringBuffer result, FieldPosition pos)825     public final StringBuffer format(Object[] arguments, StringBuffer result,
826                                      FieldPosition pos)
827     {
828         return subformat(arguments, result, pos, null);
829     }
830 
831     /**
832      * Creates a MessageFormat with the given pattern and uses it
833      * to format the given arguments. This is equivalent to
834      * <blockquote>
835      *     <code>(new {@link #MessageFormat(String) MessageFormat}(pattern)).{@link #format(java.lang.Object[], java.lang.StringBuffer, java.text.FieldPosition) format}(arguments, new StringBuffer(), null).toString()</code>
836      * </blockquote>
837      *
838      * @param pattern   the pattern string
839      * @param arguments object(s) to format
840      * @return the formatted string
841      * @exception IllegalArgumentException if the pattern is invalid,
842      *            or if an argument in the <code>arguments</code> array
843      *            is not of the type expected by the format element(s)
844      *            that use it.
845      */
format(String pattern, Object ... arguments)846     public static String format(String pattern, Object ... arguments) {
847         MessageFormat temp = new MessageFormat(pattern);
848         return temp.format(arguments);
849     }
850 
851     // Overrides
852     /**
853      * Formats an array of objects and appends the <code>MessageFormat</code>'s
854      * pattern, with format elements replaced by the formatted objects, to the
855      * provided <code>StringBuffer</code>.
856      * This is equivalent to
857      * <blockquote>
858      *     <code>{@link #format(java.lang.Object[], java.lang.StringBuffer, java.text.FieldPosition) format}((Object[]) arguments, result, pos)</code>
859      * </blockquote>
860      *
861      * @param arguments an array of objects to be formatted and substituted.
862      * @param result where text is appended.
863      * @param pos On input: an alignment field, if desired.
864      *            On output: the offsets of the alignment field.
865      * @exception IllegalArgumentException if an argument in the
866      *            <code>arguments</code> array is not of the type
867      *            expected by the format element(s) that use it.
868      */
format(Object arguments, StringBuffer result, FieldPosition pos)869     public final StringBuffer format(Object arguments, StringBuffer result,
870                                      FieldPosition pos)
871     {
872         return subformat((Object[]) arguments, result, pos, null);
873     }
874 
875     /**
876      * Formats an array of objects and inserts them into the
877      * <code>MessageFormat</code>'s pattern, producing an
878      * <code>AttributedCharacterIterator</code>.
879      * You can use the returned <code>AttributedCharacterIterator</code>
880      * to build the resulting String, as well as to determine information
881      * about the resulting String.
882      * <p>
883      * The text of the returned <code>AttributedCharacterIterator</code> is
884      * the same that would be returned by
885      * <blockquote>
886      *     <code>{@link #format(java.lang.Object[], java.lang.StringBuffer, java.text.FieldPosition) format}(arguments, new StringBuffer(), null).toString()</code>
887      * </blockquote>
888      * <p>
889      * In addition, the <code>AttributedCharacterIterator</code> contains at
890      * least attributes indicating where text was generated from an
891      * argument in the <code>arguments</code> array. The keys of these attributes are of
892      * type <code>MessageFormat.Field</code>, their values are
893      * <code>Integer</code> objects indicating the index in the <code>arguments</code>
894      * array of the argument from which the text was generated.
895      * <p>
896      * The attributes/value from the underlying <code>Format</code>
897      * instances that <code>MessageFormat</code> uses will also be
898      * placed in the resulting <code>AttributedCharacterIterator</code>.
899      * This allows you to not only find where an argument is placed in the
900      * resulting String, but also which fields it contains in turn.
901      *
902      * @param arguments an array of objects to be formatted and substituted.
903      * @return AttributedCharacterIterator describing the formatted value.
904      * @exception NullPointerException if <code>arguments</code> is null.
905      * @exception IllegalArgumentException if an argument in the
906      *            <code>arguments</code> array is not of the type
907      *            expected by the format element(s) that use it.
908      * @since 1.4
909      */
formatToCharacterIterator(Object arguments)910     public AttributedCharacterIterator formatToCharacterIterator(Object arguments) {
911         StringBuffer result = new StringBuffer();
912         ArrayList<AttributedCharacterIterator> iterators = new ArrayList<>();
913 
914         if (arguments == null) {
915             throw new NullPointerException(
916                    "formatToCharacterIterator must be passed non-null object");
917         }
918         subformat((Object[]) arguments, result, null, iterators);
919         if (iterators.size() == 0) {
920             return createAttributedCharacterIterator("");
921         }
922         return createAttributedCharacterIterator(
923                      iterators.toArray(
924                      new AttributedCharacterIterator[iterators.size()]));
925     }
926 
927     /**
928      * Parses the string.
929      *
930      * <p>Caveats: The parse may fail in a number of circumstances.
931      * For example:
932      * <ul>
933      * <li>If one of the arguments does not occur in the pattern.
934      * <li>If the format of an argument loses information, such as
935      *     with a choice format where a large number formats to "many".
936      * <li>Does not yet handle recursion (where
937      *     the substituted strings contain {n} references.)
938      * <li>Will not always find a match (or the correct match)
939      *     if some part of the parse is ambiguous.
940      *     For example, if the pattern "{1},{2}" is used with the
941      *     string arguments {"a,b", "c"}, it will format as "a,b,c".
942      *     When the result is parsed, it will return {"a", "b,c"}.
943      * <li>If a single argument is parsed more than once in the string,
944      *     then the later parse wins.
945      * </ul>
946      * When the parse fails, use ParsePosition.getErrorIndex() to find out
947      * where in the string the parsing failed.  The returned error
948      * index is the starting offset of the sub-patterns that the string
949      * is comparing with.  For example, if the parsing string "AAA {0} BBB"
950      * is comparing against the pattern "AAD {0} BBB", the error index is
951      * 0. When an error occurs, the call to this method will return null.
952      * If the source is null, return an empty array.
953      *
954      * @param source the string to parse
955      * @param pos    the parse position
956      * @return an array of parsed objects
957      */
parse(String source, ParsePosition pos)958     public Object[] parse(String source, ParsePosition pos) {
959         if (source == null) {
960             Object[] empty = {};
961             return empty;
962         }
963 
964         int maximumArgumentNumber = -1;
965         for (int i = 0; i <= maxOffset; i++) {
966             if (argumentNumbers[i] > maximumArgumentNumber) {
967                 maximumArgumentNumber = argumentNumbers[i];
968             }
969         }
970         Object[] resultArray = new Object[maximumArgumentNumber + 1];
971 
972         int patternOffset = 0;
973         int sourceOffset = pos.index;
974         ParsePosition tempStatus = new ParsePosition(0);
975         for (int i = 0; i <= maxOffset; ++i) {
976             // match up to format
977             int len = offsets[i] - patternOffset;
978             if (len == 0 || pattern.regionMatches(patternOffset,
979                                                   source, sourceOffset, len)) {
980                 sourceOffset += len;
981                 patternOffset += len;
982             } else {
983                 pos.errorIndex = sourceOffset;
984                 return null; // leave index as is to signal error
985             }
986 
987             // now use format
988             if (formats[i] == null) {   // string format
989                 // if at end, use longest possible match
990                 // otherwise uses first match to intervening string
991                 // does NOT recursively try all possibilities
992                 int tempLength = (i != maxOffset) ? offsets[i+1] : pattern.length();
993 
994                 int next;
995                 if (patternOffset >= tempLength) {
996                     next = source.length();
997                 }else{
998                     next = source.indexOf(pattern.substring(patternOffset, tempLength),
999                                           sourceOffset);
1000                 }
1001 
1002                 if (next < 0) {
1003                     pos.errorIndex = sourceOffset;
1004                     return null; // leave index as is to signal error
1005                 } else {
1006                     String strValue= source.substring(sourceOffset,next);
1007                     if (!strValue.equals("{"+argumentNumbers[i]+"}"))
1008                         resultArray[argumentNumbers[i]]
1009                             = source.substring(sourceOffset,next);
1010                     sourceOffset = next;
1011                 }
1012             } else {
1013                 tempStatus.index = sourceOffset;
1014                 resultArray[argumentNumbers[i]]
1015                     = formats[i].parseObject(source,tempStatus);
1016                 if (tempStatus.index == sourceOffset) {
1017                     pos.errorIndex = sourceOffset;
1018                     return null; // leave index as is to signal error
1019                 }
1020                 sourceOffset = tempStatus.index; // update
1021             }
1022         }
1023         int len = pattern.length() - patternOffset;
1024         if (len == 0 || pattern.regionMatches(patternOffset,
1025                                               source, sourceOffset, len)) {
1026             pos.index = sourceOffset + len;
1027         } else {
1028             pos.errorIndex = sourceOffset;
1029             return null; // leave index as is to signal error
1030         }
1031         return resultArray;
1032     }
1033 
1034     /**
1035      * Parses text from the beginning of the given string to produce an object
1036      * array.
1037      * The method may not use the entire text of the given string.
1038      * <p>
1039      * See the {@link #parse(String, ParsePosition)} method for more information
1040      * on message parsing.
1041      *
1042      * @param source A <code>String</code> whose beginning should be parsed.
1043      * @return An <code>Object</code> array parsed from the string.
1044      * @exception ParseException if the beginning of the specified string
1045      *            cannot be parsed.
1046      */
parse(String source)1047     public Object[] parse(String source) throws ParseException {
1048         ParsePosition pos  = new ParsePosition(0);
1049         Object[] result = parse(source, pos);
1050         if (pos.index == 0)  // unchanged, returned object is null
1051             throw new ParseException("MessageFormat parse error!", pos.errorIndex);
1052 
1053         return result;
1054     }
1055 
1056     /**
1057      * Parses text from a string to produce an object array.
1058      * <p>
1059      * The method attempts to parse text starting at the index given by
1060      * <code>pos</code>.
1061      * If parsing succeeds, then the index of <code>pos</code> is updated
1062      * to the index after the last character used (parsing does not necessarily
1063      * use all characters up to the end of the string), and the parsed
1064      * object array is returned. The updated <code>pos</code> can be used to
1065      * indicate the starting point for the next call to this method.
1066      * If an error occurs, then the index of <code>pos</code> is not
1067      * changed, the error index of <code>pos</code> is set to the index of
1068      * the character where the error occurred, and null is returned.
1069      * <p>
1070      * See the {@link #parse(String, ParsePosition)} method for more information
1071      * on message parsing.
1072      *
1073      * @param source A <code>String</code>, part of which should be parsed.
1074      * @param pos A <code>ParsePosition</code> object with index and error
1075      *            index information as described above.
1076      * @return An <code>Object</code> array parsed from the string. In case of
1077      *         error, returns null.
1078      * @exception NullPointerException if <code>pos</code> is null.
1079      */
parseObject(String source, ParsePosition pos)1080     public Object parseObject(String source, ParsePosition pos) {
1081         return parse(source, pos);
1082     }
1083 
1084     /**
1085      * Creates and returns a copy of this object.
1086      *
1087      * @return a clone of this instance.
1088      */
clone()1089     public Object clone() {
1090         MessageFormat other = (MessageFormat) super.clone();
1091 
1092         // clone arrays. Can't do with utility because of bug in Cloneable
1093         other.formats = formats.clone(); // shallow clone
1094         for (int i = 0; i < formats.length; ++i) {
1095             if (formats[i] != null)
1096                 other.formats[i] = (Format)formats[i].clone();
1097         }
1098         // for primitives or immutables, shallow clone is enough
1099         other.offsets = offsets.clone();
1100         other.argumentNumbers = argumentNumbers.clone();
1101 
1102         return other;
1103     }
1104 
1105     /**
1106      * Equality comparison between two message format objects
1107      */
equals(Object obj)1108     public boolean equals(Object obj) {
1109         if (this == obj)                      // quick check
1110             return true;
1111         if (obj == null || getClass() != obj.getClass())
1112             return false;
1113         MessageFormat other = (MessageFormat) obj;
1114         return (maxOffset == other.maxOffset
1115                 && pattern.equals(other.pattern)
1116                 && ((locale != null && locale.equals(other.locale))
1117                  || (locale == null && other.locale == null))
1118                 && Arrays.equals(offsets,other.offsets)
1119                 && Arrays.equals(argumentNumbers,other.argumentNumbers)
1120                 && Arrays.equals(formats,other.formats));
1121     }
1122 
1123     /**
1124      * Generates a hash code for the message format object.
1125      */
hashCode()1126     public int hashCode() {
1127         return pattern.hashCode(); // enough for reasonable distribution
1128     }
1129 
1130 
1131     /**
1132      * Defines constants that are used as attribute keys in the
1133      * <code>AttributedCharacterIterator</code> returned
1134      * from <code>MessageFormat.formatToCharacterIterator</code>.
1135      *
1136      * @since 1.4
1137      */
1138     public static class Field extends Format.Field {
1139 
1140         // Proclaim serial compatibility with 1.4 FCS
1141         private static final long serialVersionUID = 7899943957617360810L;
1142 
1143         /**
1144          * Creates a Field with the specified name.
1145          *
1146          * @param name Name of the attribute
1147          */
Field(String name)1148         protected Field(String name) {
1149             super(name);
1150         }
1151 
1152         /**
1153          * Resolves instances being deserialized to the predefined constants.
1154          *
1155          * @throws InvalidObjectException if the constant could not be
1156          *         resolved.
1157          * @return resolved MessageFormat.Field constant
1158          */
readResolve()1159         protected Object readResolve() throws InvalidObjectException {
1160             if (this.getClass() != MessageFormat.Field.class) {
1161                 throw new InvalidObjectException("subclass didn't correctly implement readResolve");
1162             }
1163 
1164             return ARGUMENT;
1165         }
1166 
1167         //
1168         // The constants
1169         //
1170 
1171         /**
1172          * Constant identifying a portion of a message that was generated
1173          * from an argument passed into <code>formatToCharacterIterator</code>.
1174          * The value associated with the key will be an <code>Integer</code>
1175          * indicating the index in the <code>arguments</code> array of the
1176          * argument from which the text was generated.
1177          */
1178         public final static Field ARGUMENT =
1179                            new Field("message argument field");
1180     }
1181 
1182     // ===========================privates============================
1183 
1184     /**
1185      * The locale to use for formatting numbers and dates.
1186      * @serial
1187      */
1188     private Locale locale;
1189 
1190     /**
1191      * The string that the formatted values are to be plugged into.  In other words, this
1192      * is the pattern supplied on construction with all of the {} expressions taken out.
1193      * @serial
1194      */
1195     private String pattern = "";
1196 
1197     /** The initially expected number of subformats in the format */
1198     private static final int INITIAL_FORMATS = 10;
1199 
1200     /**
1201      * An array of formatters, which are used to format the arguments.
1202      * @serial
1203      */
1204     private Format[] formats = new Format[INITIAL_FORMATS];
1205 
1206     /**
1207      * The positions where the results of formatting each argument are to be inserted
1208      * into the pattern.
1209      * @serial
1210      */
1211     private int[] offsets = new int[INITIAL_FORMATS];
1212 
1213     /**
1214      * The argument numbers corresponding to each formatter.  (The formatters are stored
1215      * in the order they occur in the pattern, not in the order in which the arguments
1216      * are specified.)
1217      * @serial
1218      */
1219     private int[] argumentNumbers = new int[INITIAL_FORMATS];
1220 
1221     /**
1222      * One less than the number of entries in <code>offsets</code>.  Can also be thought of
1223      * as the index of the highest-numbered element in <code>offsets</code> that is being used.
1224      * All of these arrays should have the same number of elements being used as <code>offsets</code>
1225      * does, and so this variable suffices to tell us how many entries are in all of them.
1226      * @serial
1227      */
1228     private int maxOffset = -1;
1229 
1230     /**
1231      * Internal routine used by format. If <code>characterIterators</code> is
1232      * non-null, AttributedCharacterIterator will be created from the
1233      * subformats as necessary. If <code>characterIterators</code> is null
1234      * and <code>fp</code> is non-null and identifies
1235      * <code>Field.MESSAGE_ARGUMENT</code>, the location of
1236      * the first replaced argument will be set in it.
1237      *
1238      * @exception IllegalArgumentException if an argument in the
1239      *            <code>arguments</code> array is not of the type
1240      *            expected by the format element(s) that use it.
1241      */
subformat(Object[] arguments, StringBuffer result, FieldPosition fp, List<AttributedCharacterIterator> characterIterators)1242     private StringBuffer subformat(Object[] arguments, StringBuffer result,
1243                                    FieldPosition fp, List<AttributedCharacterIterator> characterIterators) {
1244         // note: this implementation assumes a fast substring & index.
1245         // if this is not true, would be better to append chars one by one.
1246         int lastOffset = 0;
1247         int last = result.length();
1248         for (int i = 0; i <= maxOffset; ++i) {
1249             result.append(pattern.substring(lastOffset, offsets[i]));
1250             lastOffset = offsets[i];
1251             int argumentNumber = argumentNumbers[i];
1252             if (arguments == null || argumentNumber >= arguments.length) {
1253                 result.append('{').append(argumentNumber).append('}');
1254                 continue;
1255             }
1256             // int argRecursion = ((recursionProtection >> (argumentNumber*2)) & 0x3);
1257             if (false) { // if (argRecursion == 3){
1258                 // prevent loop!!!
1259                 result.append('\uFFFD');
1260             } else {
1261                 Object obj = arguments[argumentNumber];
1262                 String arg = null;
1263                 Format subFormatter = null;
1264                 if (obj == null) {
1265                     arg = "null";
1266                 } else if (formats[i] != null) {
1267                     subFormatter = formats[i];
1268                     if (subFormatter instanceof ChoiceFormat) {
1269                         arg = formats[i].format(obj);
1270                         if (arg.indexOf('{') >= 0) {
1271                             subFormatter = new MessageFormat(arg, locale);
1272                             obj = arguments;
1273                             arg = null;
1274                         }
1275                     }
1276                 } else if (obj instanceof Number) {
1277                     // format number if can
1278                     subFormatter = NumberFormat.getInstance(locale);
1279                 } else if (obj instanceof Date) {
1280                     // format a Date if can
1281                     subFormatter = DateFormat.getDateTimeInstance(
1282                              DateFormat.SHORT, DateFormat.SHORT, locale);//fix
1283                 } else if (obj instanceof String) {
1284                     arg = (String) obj;
1285 
1286                 } else {
1287                     arg = obj.toString();
1288                     if (arg == null) arg = "null";
1289                 }
1290 
1291                 // At this point we are in two states, either subFormatter
1292                 // is non-null indicating we should format obj using it,
1293                 // or arg is non-null and we should use it as the value.
1294 
1295                 if (characterIterators != null) {
1296                     // If characterIterators is non-null, it indicates we need
1297                     // to get the CharacterIterator from the child formatter.
1298                     if (last != result.length()) {
1299                         characterIterators.add(
1300                             createAttributedCharacterIterator(result.substring
1301                                                               (last)));
1302                         last = result.length();
1303                     }
1304                     if (subFormatter != null) {
1305                         AttributedCharacterIterator subIterator =
1306                                    subFormatter.formatToCharacterIterator(obj);
1307 
1308                         append(result, subIterator);
1309                         if (last != result.length()) {
1310                             characterIterators.add(
1311                                          createAttributedCharacterIterator(
1312                                          subIterator, Field.ARGUMENT,
1313                                          Integer.valueOf(argumentNumber)));
1314                             last = result.length();
1315                         }
1316                         arg = null;
1317                     }
1318                     if (arg != null && arg.length() > 0) {
1319                         result.append(arg);
1320                         characterIterators.add(
1321                                  createAttributedCharacterIterator(
1322                                  arg, Field.ARGUMENT,
1323                                  Integer.valueOf(argumentNumber)));
1324                         last = result.length();
1325                     }
1326                 }
1327                 else {
1328                     if (subFormatter != null) {
1329                         arg = subFormatter.format(obj);
1330                     }
1331                     last = result.length();
1332                     result.append(arg);
1333                     if (i == 0 && fp != null && Field.ARGUMENT.equals(
1334                                   fp.getFieldAttribute())) {
1335                         fp.setBeginIndex(last);
1336                         fp.setEndIndex(result.length());
1337                     }
1338                     last = result.length();
1339                 }
1340             }
1341         }
1342         result.append(pattern.substring(lastOffset, pattern.length()));
1343         if (characterIterators != null && last != result.length()) {
1344             characterIterators.add(createAttributedCharacterIterator(
1345                                    result.substring(last)));
1346         }
1347         return result;
1348     }
1349 
1350     /**
1351      * Convenience method to append all the characters in
1352      * <code>iterator</code> to the StringBuffer <code>result</code>.
1353      */
append(StringBuffer result, CharacterIterator iterator)1354     private void append(StringBuffer result, CharacterIterator iterator) {
1355         if (iterator.first() != CharacterIterator.DONE) {
1356             char aChar;
1357 
1358             result.append(iterator.first());
1359             while ((aChar = iterator.next()) != CharacterIterator.DONE) {
1360                 result.append(aChar);
1361             }
1362         }
1363     }
1364 
1365     // Indices for segments
1366     private static final int SEG_RAW      = 0;
1367     private static final int SEG_INDEX    = 1;
1368     private static final int SEG_TYPE     = 2;
1369     private static final int SEG_MODIFIER = 3; // modifier or subformat
1370 
1371     // Indices for type keywords
1372     private static final int TYPE_NULL    = 0;
1373     private static final int TYPE_NUMBER  = 1;
1374     private static final int TYPE_DATE    = 2;
1375     private static final int TYPE_TIME    = 3;
1376     private static final int TYPE_CHOICE  = 4;
1377 
1378     private static final String[] TYPE_KEYWORDS = {
1379         "",
1380         "number",
1381         "date",
1382         "time",
1383         "choice"
1384     };
1385 
1386     // Indices for number modifiers
1387     private static final int MODIFIER_DEFAULT  = 0; // common in number and date-time
1388     private static final int MODIFIER_CURRENCY = 1;
1389     private static final int MODIFIER_PERCENT  = 2;
1390     private static final int MODIFIER_INTEGER  = 3;
1391 
1392     private static final String[] NUMBER_MODIFIER_KEYWORDS = {
1393         "",
1394         "currency",
1395         "percent",
1396         "integer"
1397     };
1398 
1399     // Indices for date-time modifiers
1400     private static final int MODIFIER_SHORT   = 1;
1401     private static final int MODIFIER_MEDIUM  = 2;
1402     private static final int MODIFIER_LONG    = 3;
1403     private static final int MODIFIER_FULL    = 4;
1404 
1405     private static final String[] DATE_TIME_MODIFIER_KEYWORDS = {
1406         "",
1407         "short",
1408         "medium",
1409         "long",
1410         "full"
1411     };
1412 
1413     // Date-time style values corresponding to the date-time modifiers.
1414     private static final int[] DATE_TIME_MODIFIERS = {
1415         DateFormat.DEFAULT,
1416         DateFormat.SHORT,
1417         DateFormat.MEDIUM,
1418         DateFormat.LONG,
1419         DateFormat.FULL,
1420     };
1421 
makeFormat(int position, int offsetNumber, StringBuilder[] textSegments)1422     private void makeFormat(int position, int offsetNumber,
1423                             StringBuilder[] textSegments)
1424     {
1425         String[] segments = new String[textSegments.length];
1426         for (int i = 0; i < textSegments.length; i++) {
1427             StringBuilder oneseg = textSegments[i];
1428             segments[i] = (oneseg != null) ? oneseg.toString() : "";
1429         }
1430 
1431         // get the argument number
1432         int argumentNumber;
1433         try {
1434             argumentNumber = Integer.parseInt(segments[SEG_INDEX]); // always unlocalized!
1435         } catch (NumberFormatException e) {
1436             throw new IllegalArgumentException("can't parse argument number: "
1437                                                + segments[SEG_INDEX], e);
1438         }
1439         if (argumentNumber < 0) {
1440             throw new IllegalArgumentException("negative argument number: "
1441                                                + argumentNumber);
1442         }
1443 
1444         // resize format information arrays if necessary
1445         if (offsetNumber >= formats.length) {
1446             int newLength = formats.length * 2;
1447             Format[] newFormats = new Format[newLength];
1448             int[] newOffsets = new int[newLength];
1449             int[] newArgumentNumbers = new int[newLength];
1450             System.arraycopy(formats, 0, newFormats, 0, maxOffset + 1);
1451             System.arraycopy(offsets, 0, newOffsets, 0, maxOffset + 1);
1452             System.arraycopy(argumentNumbers, 0, newArgumentNumbers, 0, maxOffset + 1);
1453             formats = newFormats;
1454             offsets = newOffsets;
1455             argumentNumbers = newArgumentNumbers;
1456         }
1457         int oldMaxOffset = maxOffset;
1458         maxOffset = offsetNumber;
1459         offsets[offsetNumber] = segments[SEG_RAW].length();
1460         argumentNumbers[offsetNumber] = argumentNumber;
1461 
1462         // now get the format
1463         Format newFormat = null;
1464         if (segments[SEG_TYPE].length() != 0) {
1465             int type = findKeyword(segments[SEG_TYPE], TYPE_KEYWORDS);
1466             switch (type) {
1467             case TYPE_NULL:
1468                 // Type "" is allowed. e.g., "{0,}", "{0,,}", and "{0,,#}"
1469                 // are treated as "{0}".
1470                 break;
1471 
1472             case TYPE_NUMBER:
1473                 switch (findKeyword(segments[SEG_MODIFIER], NUMBER_MODIFIER_KEYWORDS)) {
1474                 case MODIFIER_DEFAULT:
1475                     newFormat = NumberFormat.getInstance(locale);
1476                     break;
1477                 case MODIFIER_CURRENCY:
1478                     newFormat = NumberFormat.getCurrencyInstance(locale);
1479                     break;
1480                 case MODIFIER_PERCENT:
1481                     newFormat = NumberFormat.getPercentInstance(locale);
1482                     break;
1483                 case MODIFIER_INTEGER:
1484                     newFormat = NumberFormat.getIntegerInstance(locale);
1485                     break;
1486                 default: // DecimalFormat pattern
1487                     try {
1488                         newFormat = new DecimalFormat(segments[SEG_MODIFIER],
1489                                                       DecimalFormatSymbols.getInstance(locale));
1490                     } catch (IllegalArgumentException e) {
1491                         maxOffset = oldMaxOffset;
1492                         throw e;
1493                     }
1494                     break;
1495                 }
1496                 break;
1497 
1498             case TYPE_DATE:
1499             case TYPE_TIME:
1500                 int mod = findKeyword(segments[SEG_MODIFIER], DATE_TIME_MODIFIER_KEYWORDS);
1501                 if (mod >= 0 && mod < DATE_TIME_MODIFIER_KEYWORDS.length) {
1502                     if (type == TYPE_DATE) {
1503                         newFormat = DateFormat.getDateInstance(DATE_TIME_MODIFIERS[mod],
1504                                                                locale);
1505                     } else {
1506                         newFormat = DateFormat.getTimeInstance(DATE_TIME_MODIFIERS[mod],
1507                                                                locale);
1508                     }
1509                 } else {
1510                     // SimpleDateFormat pattern
1511                     try {
1512                         newFormat = new SimpleDateFormat(segments[SEG_MODIFIER], locale);
1513                     } catch (IllegalArgumentException e) {
1514                         maxOffset = oldMaxOffset;
1515                         throw e;
1516                     }
1517                 }
1518                 break;
1519 
1520             case TYPE_CHOICE:
1521                 try {
1522                     // ChoiceFormat pattern
1523                     newFormat = new ChoiceFormat(segments[SEG_MODIFIER]);
1524                 } catch (Exception e) {
1525                     maxOffset = oldMaxOffset;
1526                     throw new IllegalArgumentException("Choice Pattern incorrect: "
1527                                                        + segments[SEG_MODIFIER], e);
1528                 }
1529                 break;
1530 
1531             default:
1532                 maxOffset = oldMaxOffset;
1533                 throw new IllegalArgumentException("unknown format type: " +
1534                                                    segments[SEG_TYPE]);
1535             }
1536         }
1537         formats[offsetNumber] = newFormat;
1538     }
1539 
findKeyword(String s, String[] list)1540     private static final int findKeyword(String s, String[] list) {
1541         for (int i = 0; i < list.length; ++i) {
1542             if (s.equals(list[i]))
1543                 return i;
1544         }
1545 
1546         // Try trimmed lowercase.
1547         String ls = s.trim().toLowerCase(Locale.ROOT);
1548         if (ls != s) {
1549             for (int i = 0; i < list.length; ++i) {
1550                 if (ls.equals(list[i]))
1551                     return i;
1552             }
1553         }
1554         return -1;
1555     }
1556 
copyAndFixQuotes(String source, int start, int end, StringBuilder target)1557     private static final void copyAndFixQuotes(String source, int start, int end,
1558                                                StringBuilder target) {
1559         boolean quoted = false;
1560 
1561         for (int i = start; i < end; ++i) {
1562             char ch = source.charAt(i);
1563             if (ch == '{') {
1564                 if (!quoted) {
1565                     target.append('\'');
1566                     quoted = true;
1567                 }
1568                 target.append(ch);
1569             } else if (ch == '\'') {
1570                 target.append("''");
1571             } else {
1572                 if (quoted) {
1573                     target.append('\'');
1574                     quoted = false;
1575                 }
1576                 target.append(ch);
1577             }
1578         }
1579         if (quoted) {
1580             target.append('\'');
1581         }
1582     }
1583 
1584     /**
1585      * After reading an object from the input stream, do a simple verification
1586      * to maintain class invariants.
1587      * @throws InvalidObjectException if the objects read from the stream is invalid.
1588      */
readObject(ObjectInputStream in)1589     private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
1590         in.defaultReadObject();
1591         boolean isValid = maxOffset >= -1
1592                 && formats.length > maxOffset
1593                 && offsets.length > maxOffset
1594                 && argumentNumbers.length > maxOffset;
1595         if (isValid) {
1596             int lastOffset = pattern.length() + 1;
1597             for (int i = maxOffset; i >= 0; --i) {
1598                 if ((offsets[i] < 0) || (offsets[i] > lastOffset)) {
1599                     isValid = false;
1600                     break;
1601                 } else {
1602                     lastOffset = offsets[i];
1603                 }
1604             }
1605         }
1606         if (!isValid) {
1607             throw new InvalidObjectException("Could not reconstruct MessageFormat from corrupt stream.");
1608         }
1609     }
1610 }
1611