/** Copyright (c) 1996, 2013, Oracle and/or its affiliates. All rights reserved.* ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.*********************//** (C) Copyright Taligent, Inc. 1996, 1997 - All Rights Reserved* (C) Copyright IBM Corp. 1996 - 1998 - All Rights Reserved** The original version of this source code and documentation is copyrighted* and owned by Taligent, Inc., a wholly-owned subsidiary of IBM. These* materials are provided under terms of a License Agreement between Taligent* and Sun. This technology is protected by multiple US and International* patents. This notice and attribution to Taligent may not be removed.* Taligent is a registered trademark of Taligent, Inc.**/package java.text;import java.io.InvalidObjectException;import java.io.IOException;import java.io.ObjectInputStream;import java.text.DecimalFormat;import java.util.ArrayList;import java.util.Arrays;import java.util.Date;import java.util.List;import java.util.Locale;/*** <code>MessageFormat</code> provides a means to produce concatenated* messages in a language-neutral way. Use this to construct messages* displayed for end users.** <p>* <code>MessageFormat</code> takes a set of objects, formats them, then* inserts the formatted strings into the pattern at the appropriate places.** <p>* <strong>Note:</strong>* <code>MessageFormat</code> differs from the other <code>Format</code>* classes in that you create a <code>MessageFormat</code> object with one* of its constructors (not with a <code>getInstance</code> style factory* method). The factory methods aren't necessary because <code>MessageFormat</code>* itself doesn't implement locale specific behavior. Any locale specific* behavior is defined by the pattern that you provide as well as the* subformats used for inserted arguments.** <h3><a name="patterns">Patterns and Their Interpretation</a></h3>** <code>MessageFormat</code> uses patterns of the following form:* <blockquote><pre>* <i>MessageFormatPattern:</i>* <i>String</i>* <i>MessageFormatPattern</i> <i>FormatElement</i> <i>String</i>** <i>FormatElement:</i>* { <i>ArgumentIndex</i> }* { <i>ArgumentIndex</i> , <i>FormatType</i> }* { <i>ArgumentIndex</i> , <i>FormatType</i> , <i>FormatStyle</i> }** <i>FormatType: one of </i>* number date time choice** <i>FormatStyle:</i>* short* medium* long* full* integer* currency* percent* <i>SubformatPattern</i>* </pre></blockquote>** <p>Within a <i>String</i>, a pair of single quotes can be used to* quote any arbitrary characters except single quotes. For example,* pattern string <code>"'{0}'"</code> represents string* <code>"{0}"</code>, not a <i>FormatElement</i>. A single quote itself* must be represented by doubled single quotes {@code ''} throughout a* <i>String</i>. For example, pattern string <code>"'{''}'"</code> is* interpreted as a sequence of <code>'{</code> (start of quoting and a* left curly brace), <code>''</code> (a single quote), and* <code>}'</code> (a right curly brace and end of quoting),* <em>not</em> <code>'{'</code> and <code>'}'</code> (quoted left and* right curly braces): representing string <code>"{'}"</code>,* <em>not</em> <code>"{}"</code>.** <p>A <i>SubformatPattern</i> is interpreted by its corresponding* subformat, and subformat-dependent pattern rules apply. For example,* pattern string <code>"{1,number,<u>$'#',##</u>}"</code>* (<i>SubformatPattern</i> with underline) will produce a number format* with the pound-sign quoted, with a result such as: {@code* "$#31,45"}. Refer to each {@code Format} subclass documentation for* details.** <p>Any unmatched quote is treated as closed at the end of the given* pattern. For example, pattern string {@code "'{0}"} is treated as* pattern {@code "'{0}'"}.** <p>Any curly braces within an unquoted pattern must be balanced. For* example, <code>"ab {0} de"</code> and <code>"ab '}' de"</code> are* valid patterns, but <code>"ab {0'}' de"</code>, <code>"ab } de"</code>* and <code>"''{''"</code> are not.** <dl><dt><b>Warning:</b><dd>The rules for using quotes within message* format patterns unfortunately have shown to be somewhat confusing.* In particular, it isn't always obvious to localizers whether single* quotes need to be doubled or not. Make sure to inform localizers about* the rules, and tell them (for example, by using comments in resource* bundle source files) which strings will be processed by {@code MessageFormat}.* Note that localizers may need to use single quotes in translated* strings where the original version doesn't have them.* </dl>* <p>* The <i>ArgumentIndex</i> value is a non-negative integer written* using the digits {@code '0'} through {@code '9'}, and represents an index into the* {@code arguments} array passed to the {@code format} methods* or the result array returned by the {@code parse} methods.* <p>* The <i>FormatType</i> and <i>FormatStyle</i> values are used to create* a {@code Format} instance for the format element. The following* table shows how the values map to {@code Format} instances. Combinations not* shown in the table are illegal. A <i>SubformatPattern</i> must* be a valid pattern string for the {@code Format} subclass used.** <table border=1 summary="Shows how FormatType and FormatStyle values map to Format instances">* <tr>* <th id="ft" class="TableHeadingColor">FormatType* <th id="fs" class="TableHeadingColor">FormatStyle* <th id="sc" class="TableHeadingColor">Subformat Created* <tr>* <td headers="ft"><i>(none)</i>* <td headers="fs"><i>(none)</i>* <td headers="sc"><code>null</code>* <tr>* <td headers="ft" rowspan=5><code>number</code>* <td headers="fs"><i>(none)</i>* <td headers="sc">{@link NumberFormat#getInstance(Locale) NumberFormat.getInstance}{@code (getLocale())}* <tr>* <td headers="fs"><code>integer</code>* <td headers="sc">{@link NumberFormat#getIntegerInstance(Locale) NumberFormat.getIntegerInstance}{@code (getLocale())}* <tr>* <td headers="fs"><code>currency</code>* <td headers="sc">{@link NumberFormat#getCurrencyInstance(Locale) NumberFormat.getCurrencyInstance}{@code (getLocale())}* <tr>* <td headers="fs"><code>percent</code>* <td headers="sc">{@link NumberFormat#getPercentInstance(Locale) NumberFormat.getPercentInstance}{@code (getLocale())}* <tr>* <td headers="fs"><i>SubformatPattern</i>* <td headers="sc">{@code new} {@link DecimalFormat#DecimalFormat(String,DecimalFormatSymbols) DecimalFormat}{@code (subformatPattern,} {@link DecimalFormatSymbols#getInstance(Locale) DecimalFormatSymbols.getInstance}{@code (getLocale()))}* <tr>* <td headers="ft" rowspan=6><code>date</code>* <td headers="fs"><i>(none)</i>* <td headers="sc">{@link DateFormat#getDateInstance(int,Locale) DateFormat.getDateInstance}{@code (}{@link DateFormat#DEFAULT}{@code , getLocale())}* <tr>* <td headers="fs"><code>short</code>* <td headers="sc">{@link DateFormat#getDateInstance(int,Locale) DateFormat.getDateInstance}{@code (}{@link DateFormat#SHORT}{@code , getLocale())}* <tr>* <td headers="fs"><code>medium</code>* <td headers="sc">{@link DateFormat#getDateInstance(int,Locale) DateFormat.getDateInstance}{@code (}{@link DateFormat#DEFAULT}{@code , getLocale())}* <tr>* <td headers="fs"><code>long</code>* <td headers="sc">{@link DateFormat#getDateInstance(int,Locale) DateFormat.getDateInstance}{@code (}{@link DateFormat#LONG}{@code , getLocale())}* <tr>* <td headers="fs"><code>full</code>* <td headers="sc">{@link DateFormat#getDateInstance(int,Locale) DateFormat.getDateInstance}{@code (}{@link DateFormat#FULL}{@code , getLocale())}* <tr>* <td headers="fs"><i>SubformatPattern</i>* <td headers="sc">{@code new} {@link SimpleDateFormat#SimpleDateFormat(String,Locale) SimpleDateFormat}{@code (subformatPattern, getLocale())}* <tr>* <td headers="ft" rowspan=6><code>time</code>* <td headers="fs"><i>(none)</i>* <td headers="sc">{@link DateFormat#getTimeInstance(int,Locale) DateFormat.getTimeInstance}{@code (}{@link DateFormat#DEFAULT}{@code , getLocale())}* <tr>* <td headers="fs"><code>short</code>* <td headers="sc">{@link DateFormat#getTimeInstance(int,Locale) DateFormat.getTimeInstance}{@code (}{@link DateFormat#SHORT}{@code , getLocale())}* <tr>* <td headers="fs"><code>medium</code>* <td headers="sc">{@link DateFormat#getTimeInstance(int,Locale) DateFormat.getTimeInstance}{@code (}{@link DateFormat#DEFAULT}{@code , getLocale())}* <tr>* <td headers="fs"><code>long</code>* <td headers="sc">{@link DateFormat#getTimeInstance(int,Locale) DateFormat.getTimeInstance}{@code (}{@link DateFormat#LONG}{@code , getLocale())}* <tr>* <td headers="fs"><code>full</code>* <td headers="sc">{@link DateFormat#getTimeInstance(int,Locale) DateFormat.getTimeInstance}{@code (}{@link DateFormat#FULL}{@code , getLocale())}* <tr>* <td headers="fs"><i>SubformatPattern</i>* <td headers="sc">{@code new} {@link SimpleDateFormat#SimpleDateFormat(String,Locale) SimpleDateFormat}{@code (subformatPattern, getLocale())}* <tr>* <td headers="ft"><code>choice</code>* <td headers="fs"><i>SubformatPattern</i>* <td headers="sc">{@code new} {@link ChoiceFormat#ChoiceFormat(String) ChoiceFormat}{@code (subformatPattern)}* </table>** <h4>Usage Information</h4>** <p>* Here are some examples of usage.* In real internationalized programs, the message format pattern and other* static strings will, of course, be obtained from resource bundles.* Other parameters will be dynamically determined at runtime.* <p>* The first example uses the static method <code>MessageFormat.format</code>,* which internally creates a <code>MessageFormat</code> for one-time use:* <blockquote><pre>* int planet = 7;* String event = "a disturbance in the Force";** String result = MessageFormat.format(* "At {1,time} on {1,date}, there was {2} on planet {0,number,integer}.",* planet, new Date(), event);* </pre></blockquote>* The output is:* <blockquote><pre>* At 12:30 PM on Jul 3, 2053, there was a disturbance in the Force on planet 7.* </pre></blockquote>** <p>* The following example creates a <code>MessageFormat</code> instance that* can be used repeatedly:* <blockquote><pre>* int fileCount = 1273;* String diskName = "MyDisk";* Object[] testArgs = {new Long(fileCount), diskName};** MessageFormat form = new MessageFormat(* "The disk \"{1}\" contains {0} file(s).");** System.out.println(form.format(testArgs));* </pre></blockquote>* The output with different values for <code>fileCount</code>:* <blockquote><pre>* The disk "MyDisk" contains 0 file(s).* The disk "MyDisk" contains 1 file(s).* The disk "MyDisk" contains 1,273 file(s).* </pre></blockquote>** <p>* For more sophisticated patterns, you can use a <code>ChoiceFormat</code>* to produce correct forms for singular and plural:* <blockquote><pre>* MessageFormat form = new MessageFormat("The disk \"{1}\" contains {0}.");* double[] filelimits = {0,1,2};* String[] filepart = {"no files","one file","{0,number} files"};* ChoiceFormat fileform = new ChoiceFormat(filelimits, filepart);* form.setFormatByArgumentIndex(0, fileform);** int fileCount = 1273;* String diskName = "MyDisk";* Object[] testArgs = {new Long(fileCount), diskName};** System.out.println(form.format(testArgs));* </pre></blockquote>* The output with different values for <code>fileCount</code>:* <blockquote><pre>* The disk "MyDisk" contains no files.* The disk "MyDisk" contains one file.* The disk "MyDisk" contains 1,273 files.* </pre></blockquote>** <p>* You can create the <code>ChoiceFormat</code> programmatically, as in the* above example, or by using a pattern. See {@link ChoiceFormat}* for more information.* <blockquote><pre>{@code* form.applyPattern(* "There {0,choice,0#are no files|1#is one file|1<are {0,number,integer} files}.");* }</pre></blockquote>** <p>* <strong>Note:</strong> As we see above, the string produced* by a <code>ChoiceFormat</code> in <code>MessageFormat</code> is treated as special;* occurrences of '{' are used to indicate subformats, and cause recursion.* If you create both a <code>MessageFormat</code> and <code>ChoiceFormat</code>* programmatically (instead of using the string patterns), then be careful not to* produce a format that recurses on itself, which will cause an infinite loop.* <p>* When a single argument is parsed more than once in the string, the last match* will be the final result of the parsing. For example,* <blockquote><pre>* MessageFormat mf = new MessageFormat("{0,number,#.##}, {0,number,#.#}");* Object[] objs = {new Double(3.1415)};* String result = mf.format( objs );* // result now equals "3.14, 3.1"* objs = null;* objs = mf.parse(result, new ParsePosition(0));* // objs now equals {new Double(3.1)}* </pre></blockquote>** <p>* Likewise, parsing with a {@code MessageFormat} object using patterns containing* multiple occurrences of the same argument would return the last match. For* example,* <blockquote><pre>* MessageFormat mf = new MessageFormat("{0}, {0}, {0}");* String forParsing = "x, y, z";* Object[] objs = mf.parse(forParsing, new ParsePosition(0));* // result now equals {new String("z")}* </pre></blockquote>** <h4><a name="synchronization">Synchronization</a></h4>** <p>* Message formats are not synchronized.* It is recommended to create separate format instances for each thread.* If multiple threads access a format concurrently, it must be synchronized* externally.** @see java.util.Locale* @see Format* @see NumberFormat* @see DecimalFormat* @see DecimalFormatSymbols* @see ChoiceFormat* @see DateFormat* @see SimpleDateFormat** @author Mark Davis*/public class MessageFormat extends Format {private static final long serialVersionUID = 6479157306784022952L;/*** Constructs a MessageFormat for the default* {@link java.util.Locale.Category#FORMAT FORMAT} locale and the* specified pattern.* The constructor first sets the locale, then parses the pattern and* creates a list of subformats for the format elements contained in it.* Patterns and their interpretation are specified in the* <a href="#patterns">class description</a>.** @param pattern the pattern for this message format* @exception IllegalArgumentException if the pattern is invalid*/public MessageFormat(String pattern) {this.locale = Locale.getDefault(Locale.Category.FORMAT);applyPattern(pattern);}/*** Constructs a MessageFormat for the specified locale and* pattern.* The constructor first sets the locale, then parses the pattern and* creates a list of subformats for the format elements contained in it.* Patterns and their interpretation are specified in the* <a href="#patterns">class description</a>.** @param pattern the pattern for this message format* @param locale the locale for this message format* @exception IllegalArgumentException if the pattern is invalid* @since 1.4*/public MessageFormat(String pattern, Locale locale) {this.locale = locale;applyPattern(pattern);}/*** Sets the locale to be used when creating or comparing subformats.* This affects subsequent calls* <ul>* <li>to the {@link #applyPattern applyPattern}* and {@link #toPattern toPattern} methods if format elements specify* a format type and therefore have the subformats created in the* <code>applyPattern</code> method, as well as* <li>to the <code>format</code> and* {@link #formatToCharacterIterator formatToCharacterIterator} methods* if format elements do not specify a format type and therefore have* the subformats created in the formatting methods.* </ul>* Subformats that have already been created are not affected.** @param locale the locale to be used when creating or comparing subformats*/public void setLocale(Locale locale) {this.locale = locale;}/*** Gets the locale that's used when creating or comparing subformats.** @return the locale used when creating or comparing subformats*/public Locale getLocale() {return locale;}/*** Sets the pattern used by this message format.* The method parses the pattern and creates a list of subformats* for the format elements contained in it.* Patterns and their interpretation are specified in the* <a href="#patterns">class description</a>.** @param pattern the pattern for this message format* @exception IllegalArgumentException if the pattern is invalid*/@SuppressWarnings("fallthrough") // fallthrough in switch is expected, suppress itpublic void applyPattern(String pattern) {StringBuilder[] segments = new StringBuilder[4];// Allocate only segments[SEG_RAW] here. The rest are// allocated on demand.segments[SEG_RAW] = new StringBuilder();int part = SEG_RAW;int formatNumber = 0;boolean inQuote = false;int braceStack = 0;maxOffset = -1;for (int i = 0; i < pattern.length(); ++i) {char ch = pattern.charAt(i);if (part == SEG_RAW) {if (ch == '\'') {if (i + 1 < pattern.length()&& pattern.charAt(i+1) == '\'') {segments[part].append(ch); // handle doubles++i;} else {inQuote = !inQuote;}} else if (ch == '{' && !inQuote) {part = SEG_INDEX;if (segments[SEG_INDEX] == null) {segments[SEG_INDEX] = new StringBuilder();}} else {segments[part].append(ch);}} else {if (inQuote) { // just copy quotes in partssegments[part].append(ch);if (ch == '\'') {inQuote = false;}} else {switch (ch) {case ',':if (part < SEG_MODIFIER) {if (segments[++part] == null) {segments[part] = new StringBuilder();}} else {segments[part].append(ch);}break;case '{':++braceStack;segments[part].append(ch);break;case '}':if (braceStack == 0) {part = SEG_RAW;makeFormat(i, formatNumber, segments);formatNumber++;// throw away other segmentssegments[SEG_INDEX] = null;segments[SEG_TYPE] = null;segments[SEG_MODIFIER] = null;} else {--braceStack;segments[part].append(ch);}break;case ' ':// Skip any leading space chars for SEG_TYPE.if (part != SEG_TYPE || segments[SEG_TYPE].length() > 0) {segments[part].append(ch);}break;case '\'':inQuote = true;// fall through, so we keep quotes in other partsdefault:segments[part].append(ch);break;}}}}if (braceStack == 0 && part != 0) {maxOffset = -1;throw new IllegalArgumentException("Unmatched braces in the pattern.");}this.pattern = segments[0].toString();}/*** Returns a pattern representing the current state of the message format.* The string is constructed from internal information and therefore* does not necessarily equal the previously applied pattern.** @return a pattern representing the current state of the message format*/public String toPattern() {// later, make this more extensibleint lastOffset = 0;StringBuilder result = new StringBuilder();for (int i = 0; i <= maxOffset; ++i) {copyAndFixQuotes(pattern, lastOffset, offsets[i], result);lastOffset = offsets[i];result.append('{').append(argumentNumbers[i]);Format fmt = formats[i];if (fmt == null) {// do nothing, string format} else if (fmt instanceof NumberFormat) {if (fmt.equals(NumberFormat.getInstance(locale))) {result.append(",number");} else if (fmt.equals(NumberFormat.getCurrencyInstance(locale))) {result.append(",number,currency");} else if (fmt.equals(NumberFormat.getPercentInstance(locale))) {result.append(",number,percent");} else if (fmt.equals(NumberFormat.getIntegerInstance(locale))) {result.append(",number,integer");} else {if (fmt instanceof DecimalFormat) {result.append(",number,").append(((DecimalFormat)fmt).toPattern());} else if (fmt instanceof ChoiceFormat) {result.append(",choice,").append(((ChoiceFormat)fmt).toPattern());} else {// UNKNOWN}}} else if (fmt instanceof DateFormat) {int index;for (index = MODIFIER_DEFAULT; index < DATE_TIME_MODIFIERS.length; index++) {DateFormat df = DateFormat.getDateInstance(DATE_TIME_MODIFIERS[index],locale);if (fmt.equals(df)) {result.append(",date");break;}df = DateFormat.getTimeInstance(DATE_TIME_MODIFIERS[index],locale);if (fmt.equals(df)) {result.append(",time");break;}}if (index >= DATE_TIME_MODIFIERS.length) {if (fmt instanceof SimpleDateFormat) {result.append(",date,").append(((SimpleDateFormat)fmt).toPattern());} else {// UNKNOWN}} else if (index != MODIFIER_DEFAULT) {result.append(',').append(DATE_TIME_MODIFIER_KEYWORDS[index]);}} else {//result.append(", unknown");}result.append('}');}copyAndFixQuotes(pattern, lastOffset, pattern.length(), result);return result.toString();}/*** Sets the formats to use for the values passed into* <code>format</code> methods or returned from <code>parse</code>* methods. The indices of elements in <code>newFormats</code>* correspond to the argument indices used in the previously set* pattern string.* The order of formats in <code>newFormats</code> thus corresponds to* the order of elements in the <code>arguments</code> array passed* to the <code>format</code> methods or the result array returned* by the <code>parse</code> methods.* <p>* If an argument index is used for more than one format element* in the pattern string, then the corresponding new format is used* for all such format elements. If an argument index is not used* for any format element in the pattern string, then the* corresponding new format is ignored. If fewer formats are provided* than needed, then only the formats for argument indices less* than <code>newFormats.length</code> are replaced.** @param newFormats the new formats to use* @exception NullPointerException if <code>newFormats</code> is null* @since 1.4*/public void setFormatsByArgumentIndex(Format[] newFormats) {for (int i = 0; i <= maxOffset; i++) {int j = argumentNumbers[i];if (j < newFormats.length) {formats[i] = newFormats[j];}}}/*** Sets the formats to use for the format elements in the* previously set pattern string.* The order of formats in <code>newFormats</code> corresponds to* the order of format elements in the pattern string.* <p>* If more formats are provided than needed by the pattern string,* the remaining ones are ignored. If fewer formats are provided* than needed, then only the first <code>newFormats.length</code>* formats are replaced.* <p>* Since the order of format elements in a pattern string often* changes during localization, it is generally better to use the* {@link #setFormatsByArgumentIndex setFormatsByArgumentIndex}* method, which assumes an order of formats corresponding to the* order of elements in the <code>arguments</code> array passed to* the <code>format</code> methods or the result array returned by* the <code>parse</code> methods.** @param newFormats the new formats to use* @exception NullPointerException if <code>newFormats</code> is null*/public void setFormats(Format[] newFormats) {int runsToCopy = newFormats.length;if (runsToCopy > maxOffset + 1) {runsToCopy = maxOffset + 1;}for (int i = 0; i < runsToCopy; i++) {formats[i] = newFormats[i];}}/*** Sets the format to use for the format elements within the* previously set pattern string that use the given argument* index.* The argument index is part of the format element definition and* represents an index into the <code>arguments</code> array passed* to the <code>format</code> methods or the result array returned* by the <code>parse</code> methods.* <p>* If the argument index is used for more than one format element* in the pattern string, then the new format is used for all such* format elements. If the argument index is not used for any format* element in the pattern string, then the new format is ignored.** @param argumentIndex the argument index for which to use the new format* @param newFormat the new format to use* @since 1.4*/public void setFormatByArgumentIndex(int argumentIndex, Format newFormat) {for (int j = 0; j <= maxOffset; j++) {if (argumentNumbers[j] == argumentIndex) {formats[j] = newFormat;}}}/*** Sets the format to use for the format element with the given* format element index within the previously set pattern string.* The format element index is the zero-based number of the format* element counting from the start of the pattern string.* <p>* Since the order of format elements in a pattern string often* changes during localization, it is generally better to use the* {@link #setFormatByArgumentIndex setFormatByArgumentIndex}* method, which accesses format elements based on the argument* index they specify.** @param formatElementIndex the index of a format element within the pattern* @param newFormat the format to use for the specified format element* @exception ArrayIndexOutOfBoundsException if {@code formatElementIndex} is equal to or* larger than the number of format elements in the pattern string*/public void setFormat(int formatElementIndex, Format newFormat) {formats[formatElementIndex] = newFormat;}/*** Gets the formats used for the values passed into* <code>format</code> methods or returned from <code>parse</code>* methods. The indices of elements in the returned array* correspond to the argument indices used in the previously set* pattern string.* The order of formats in the returned array thus corresponds to* the order of elements in the <code>arguments</code> array passed* to the <code>format</code> methods or the result array returned* by the <code>parse</code> methods.* <p>* If an argument index is used for more than one format element* in the pattern string, then the format used for the last such* format element is returned in the array. If an argument index* is not used for any format element in the pattern string, then* null is returned in the array.** @return the formats used for the arguments within the pattern* @since 1.4*/public Format[] getFormatsByArgumentIndex() {int maximumArgumentNumber = -1;for (int i = 0; i <= maxOffset; i++) {if (argumentNumbers[i] > maximumArgumentNumber) {maximumArgumentNumber = argumentNumbers[i];}}Format[] resultArray = new Format[maximumArgumentNumber + 1];for (int i = 0; i <= maxOffset; i++) {resultArray[argumentNumbers[i]] = formats[i];}return resultArray;}/*** Gets the formats used for the format elements in the* previously set pattern string.* The order of formats in the returned array corresponds to* the order of format elements in the pattern string.* <p>* Since the order of format elements in a pattern string often* changes during localization, it's generally better to use the* {@link #getFormatsByArgumentIndex getFormatsByArgumentIndex}* method, which assumes an order of formats corresponding to the* order of elements in the <code>arguments</code> array passed to* the <code>format</code> methods or the result array returned by* the <code>parse</code> methods.** @return the formats used for the format elements in the pattern*/public Format[] getFormats() {Format[] resultArray = new Format[maxOffset + 1];System.arraycopy(formats, 0, resultArray, 0, maxOffset + 1);return resultArray;}/*** Formats an array of objects and appends the <code>MessageFormat</code>'s* pattern, with format elements replaced by the formatted objects, to the* provided <code>StringBuffer</code>.* <p>* The text substituted for the individual format elements is derived from* the current subformat of the format element and the* <code>arguments</code> element at the format element's argument index* as indicated by the first matching line of the following table. An* argument is <i>unavailable</i> if <code>arguments</code> is* <code>null</code> or has fewer than argumentIndex+1 elements.** <table border=1 summary="Examples of subformat,argument,and formatted text">* <tr>* <th>Subformat* <th>Argument* <th>Formatted Text* <tr>* <td><i>any</i>* <td><i>unavailable</i>* <td><code>"{" + argumentIndex + "}"</code>* <tr>* <td><i>any</i>* <td><code>null</code>* <td><code>"null"</code>* <tr>* <td><code>instanceof ChoiceFormat</code>* <td><i>any</i>* <td><code>subformat.format(argument).indexOf('{') >= 0 ?<br>* (new MessageFormat(subformat.format(argument), getLocale())).format(argument) :* subformat.format(argument)</code>* <tr>* <td><code>!= null</code>* <td><i>any</i>* <td><code>subformat.format(argument)</code>* <tr>* <td><code>null</code>* <td><code>instanceof Number</code>* <td><code>NumberFormat.getInstance(getLocale()).format(argument)</code>* <tr>* <td><code>null</code>* <td><code>instanceof Date</code>* <td><code>DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, getLocale()).format(argument)</code>* <tr>* <td><code>null</code>* <td><code>instanceof String</code>* <td><code>argument</code>* <tr>* <td><code>null</code>* <td><i>any</i>* <td><code>argument.toString()</code>* </table>* <p>* If <code>pos</code> is non-null, and refers to* <code>Field.ARGUMENT</code>, the location of the first formatted* string will be returned.** @param arguments an array of objects to be formatted and substituted.* @param result where text is appended.* @param pos On input: an alignment field, if desired.* On output: the offsets of the alignment field.* @return the string buffer passed in as {@code result}, with formatted* text appended* @exception IllegalArgumentException if an argument in the* <code>arguments</code> array is not of the type* expected by the format element(s) that use it.*/public final StringBuffer format(Object[] arguments, StringBuffer result,FieldPosition pos){return subformat(arguments, result, pos, null);}/*** Creates a MessageFormat with the given pattern and uses it* to format the given arguments. This is equivalent to* <blockquote>* <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>* </blockquote>** @param pattern the pattern string* @param arguments object(s) to format* @return the formatted string* @exception IllegalArgumentException if the pattern is invalid,* or if an argument in the <code>arguments</code> array* is not of the type expected by the format element(s)* that use it.*/public static String format(String pattern, Object ... arguments) {MessageFormat temp = new MessageFormat(pattern);return temp.format(arguments);}// Overrides/*** Formats an array of objects and appends the <code>MessageFormat</code>'s* pattern, with format elements replaced by the formatted objects, to the* provided <code>StringBuffer</code>.* This is equivalent to* <blockquote>* <code>{@link #format(java.lang.Object[], java.lang.StringBuffer, java.text.FieldPosition) format}((Object[]) arguments, result, pos)</code>* </blockquote>** @param arguments an array of objects to be formatted and substituted.* @param result where text is appended.* @param pos On input: an alignment field, if desired.* On output: the offsets of the alignment field.* @exception IllegalArgumentException if an argument in the* <code>arguments</code> array is not of the type* expected by the format element(s) that use it.*/public final StringBuffer format(Object arguments, StringBuffer result,FieldPosition pos){return subformat((Object[]) arguments, result, pos, null);}/*** Formats an array of objects and inserts them into the* <code>MessageFormat</code>'s pattern, producing an* <code>AttributedCharacterIterator</code>.* You can use the returned <code>AttributedCharacterIterator</code>* to build the resulting String, as well as to determine information* about the resulting String.* <p>* The text of the returned <code>AttributedCharacterIterator</code> is* the same that would be returned by* <blockquote>* <code>{@link #format(java.lang.Object[], java.lang.StringBuffer, java.text.FieldPosition) format}(arguments, new StringBuffer(), null).toString()</code>* </blockquote>* <p>* In addition, the <code>AttributedCharacterIterator</code> contains at* least attributes indicating where text was generated from an* argument in the <code>arguments</code> array. The keys of these attributes are of* type <code>MessageFormat.Field</code>, their values are* <code>Integer</code> objects indicating the index in the <code>arguments</code>* array of the argument from which the text was generated.* <p>* The attributes/value from the underlying <code>Format</code>* instances that <code>MessageFormat</code> uses will also be* placed in the resulting <code>AttributedCharacterIterator</code>.* This allows you to not only find where an argument is placed in the* resulting String, but also which fields it contains in turn.** @param arguments an array of objects to be formatted and substituted.* @return AttributedCharacterIterator describing the formatted value.* @exception NullPointerException if <code>arguments</code> is null.* @exception IllegalArgumentException if an argument in the* <code>arguments</code> array is not of the type* expected by the format element(s) that use it.* @since 1.4*/public AttributedCharacterIterator formatToCharacterIterator(Object arguments) {StringBuffer result = new StringBuffer();ArrayList<AttributedCharacterIterator> iterators = new ArrayList<>();if (arguments == null) {throw new NullPointerException("formatToCharacterIterator must be passed non-null object");}subformat((Object[]) arguments, result, null, iterators);if (iterators.size() == 0) {return createAttributedCharacterIterator("");}return createAttributedCharacterIterator(iterators.toArray(new AttributedCharacterIterator[iterators.size()]));}/*** Parses the string.** <p>Caveats: The parse may fail in a number of circumstances.* For example:* <ul>* <li>If one of the arguments does not occur in the pattern.* <li>If the format of an argument loses information, such as* with a choice format where a large number formats to "many".* <li>Does not yet handle recursion (where* the substituted strings contain {n} references.)* <li>Will not always find a match (or the correct match)* if some part of the parse is ambiguous.* For example, if the pattern "{1},{2}" is used with the* string arguments {"a,b", "c"}, it will format as "a,b,c".* When the result is parsed, it will return {"a", "b,c"}.* <li>If a single argument is parsed more than once in the string,* then the later parse wins.* </ul>* When the parse fails, use ParsePosition.getErrorIndex() to find out* where in the string the parsing failed. The returned error* index is the starting offset of the sub-patterns that the string* is comparing with. For example, if the parsing string "AAA {0} BBB"* is comparing against the pattern "AAD {0} BBB", the error index is* 0. When an error occurs, the call to this method will return null.* If the source is null, return an empty array.** @param source the string to parse* @param pos the parse position* @return an array of parsed objects*/public Object[] parse(String source, ParsePosition pos) {if (source == null) {Object[] empty = {};return empty;}int maximumArgumentNumber = -1;for (int i = 0; i <= maxOffset; i++) {if (argumentNumbers[i] > maximumArgumentNumber) {maximumArgumentNumber = argumentNumbers[i];}}Object[] resultArray = new Object[maximumArgumentNumber + 1];int patternOffset = 0;int sourceOffset = pos.index;ParsePosition tempStatus = new ParsePosition(0);for (int i = 0; i <= maxOffset; ++i) {// match up to formatint len = offsets[i] - patternOffset;if (len == 0 || pattern.regionMatches(patternOffset,source, sourceOffset, len)) {sourceOffset += len;patternOffset += len;} else {pos.errorIndex = sourceOffset;return null; // leave index as is to signal error}// now use formatif (formats[i] == null) { // string format// if at end, use longest possible match// otherwise uses first match to intervening string// does NOT recursively try all possibilitiesint tempLength = (i != maxOffset) ? offsets[i+1] : pattern.length();int next;if (patternOffset >= tempLength) {next = source.length();}else{next = source.indexOf(pattern.substring(patternOffset, tempLength),sourceOffset);}if (next < 0) {pos.errorIndex = sourceOffset;return null; // leave index as is to signal error} else {String strValue= source.substring(sourceOffset,next);if (!strValue.equals("{"+argumentNumbers[i]+"}"))resultArray[argumentNumbers[i]]= source.substring(sourceOffset,next);sourceOffset = next;}} else {tempStatus.index = sourceOffset;resultArray[argumentNumbers[i]]= formats[i].parseObject(source,tempStatus);if (tempStatus.index == sourceOffset) {pos.errorIndex = sourceOffset;return null; // leave index as is to signal error}sourceOffset = tempStatus.index; // update}}int len = pattern.length() - patternOffset;if (len == 0 || pattern.regionMatches(patternOffset,source, sourceOffset, len)) {pos.index = sourceOffset + len;} else {pos.errorIndex = sourceOffset;return null; // leave index as is to signal error}return resultArray;}/*** Parses text from the beginning of the given string to produce an object* array.* The method may not use the entire text of the given string.* <p>* See the {@link #parse(String, ParsePosition)} method for more information* on message parsing.** @param source A <code>String</code> whose beginning should be parsed.* @return An <code>Object</code> array parsed from the string.* @exception ParseException if the beginning of the specified string* cannot be parsed.*/public Object[] parse(String source) throws ParseException {ParsePosition pos = new ParsePosition(0);Object[] result = parse(source, pos);if (pos.index == 0) // unchanged, returned object is nullthrow new ParseException("MessageFormat parse error!", pos.errorIndex);return result;}/*** Parses text from a string to produce an object array.* <p>* The method attempts to parse text starting at the index given by* <code>pos</code>.* If parsing succeeds, then the index of <code>pos</code> is updated* to the index after the last character used (parsing does not necessarily* use all characters up to the end of the string), and the parsed* object array is returned. The updated <code>pos</code> can be used to* indicate the starting point for the next call to this method.* If an error occurs, then the index of <code>pos</code> is not* changed, the error index of <code>pos</code> is set to the index of* the character where the error occurred, and null is returned.* <p>* See the {@link #parse(String, ParsePosition)} method for more information* on message parsing.** @param source A <code>String</code>, part of which should be parsed.* @param pos A <code>ParsePosition</code> object with index and error* index information as described above.* @return An <code>Object</code> array parsed from the string. In case of* error, returns null.* @exception NullPointerException if <code>pos</code> is null.*/public Object parseObject(String source, ParsePosition pos) {return parse(source, pos);}/*** Creates and returns a copy of this object.** @return a clone of this instance.*/public Object clone() {MessageFormat other = (MessageFormat) super.clone();// clone arrays. Can't do with utility because of bug in Cloneableother.formats = formats.clone(); // shallow clonefor (int i = 0; i < formats.length; ++i) {if (formats[i] != null)other.formats[i] = (Format)formats[i].clone();}// for primitives or immutables, shallow clone is enoughother.offsets = offsets.clone();other.argumentNumbers = argumentNumbers.clone();return other;}/*** Equality comparison between two message format objects*/public boolean equals(Object obj) {if (this == obj) // quick checkreturn true;if (obj == null || getClass() != obj.getClass())return false;MessageFormat other = (MessageFormat) obj;return (maxOffset == other.maxOffset&& pattern.equals(other.pattern)&& ((locale != null && locale.equals(other.locale))|| (locale == null && other.locale == null))&& Arrays.equals(offsets,other.offsets)&& Arrays.equals(argumentNumbers,other.argumentNumbers)&& Arrays.equals(formats,other.formats));}/*** Generates a hash code for the message format object.*/public int hashCode() {return pattern.hashCode(); // enough for reasonable distribution}/*** Defines constants that are used as attribute keys in the* <code>AttributedCharacterIterator</code> returned* from <code>MessageFormat.formatToCharacterIterator</code>.** @since 1.4*/public static class Field extends Format.Field {// Proclaim serial compatibility with 1.4 FCSprivate static final long serialVersionUID = 7899943957617360810L;/*** Creates a Field with the specified name.** @param name Name of the attribute*/protected Field(String name) {super(name);}/*** Resolves instances being deserialized to the predefined constants.** @throws InvalidObjectException if the constant could not be* resolved.* @return resolved MessageFormat.Field constant*/protected Object readResolve() throws InvalidObjectException {if (this.getClass() != MessageFormat.Field.class) {throw new InvalidObjectException("subclass didn't correctly implement readResolve");}return ARGUMENT;}//// The constants///*** Constant identifying a portion of a message that was generated* from an argument passed into <code>formatToCharacterIterator</code>.* The value associated with the key will be an <code>Integer</code>* indicating the index in the <code>arguments</code> array of the* argument from which the text was generated.*/public final static Field ARGUMENT =new Field("message argument field");}// ===========================privates============================/*** The locale to use for formatting numbers and dates.* @serial*/private Locale locale;/*** The string that the formatted values are to be plugged into. In other words, this* is the pattern supplied on construction with all of the {} expressions taken out.* @serial*/private String pattern = "";/** The initially expected number of subformats in the format */private static final int INITIAL_FORMATS = 10;/*** An array of formatters, which are used to format the arguments.* @serial*/private Format[] formats = new Format[INITIAL_FORMATS];/*** The positions where the results of formatting each argument are to be inserted* into the pattern.* @serial*/private int[] offsets = new int[INITIAL_FORMATS];/*** The argument numbers corresponding to each formatter. (The formatters are stored* in the order they occur in the pattern, not in the order in which the arguments* are specified.)* @serial*/private int[] argumentNumbers = new int[INITIAL_FORMATS];/*** One less than the number of entries in <code>offsets</code>. Can also be thought of* as the index of the highest-numbered element in <code>offsets</code> that is being used.* All of these arrays should have the same number of elements being used as <code>offsets</code>* does, and so this variable suffices to tell us how many entries are in all of them.* @serial*/private int maxOffset = -1;/*** Internal routine used by format. If <code>characterIterators</code> is* non-null, AttributedCharacterIterator will be created from the* subformats as necessary. If <code>characterIterators</code> is null* and <code>fp</code> is non-null and identifies* <code>Field.MESSAGE_ARGUMENT</code>, the location of* the first replaced argument will be set in it.** @exception IllegalArgumentException if an argument in the* <code>arguments</code> array is not of the type* expected by the format element(s) that use it.*/private StringBuffer subformat(Object[] arguments, StringBuffer result,FieldPosition fp, List<AttributedCharacterIterator> characterIterators) {// note: this implementation assumes a fast substring & index.// if this is not true, would be better to append chars one by one.int lastOffset = 0;int last = result.length();for (int i = 0; i <= maxOffset; ++i) {result.append(pattern.substring(lastOffset, offsets[i]));lastOffset = offsets[i];int argumentNumber = argumentNumbers[i];if (arguments == null || argumentNumber >= arguments.length) {result.append('{').append(argumentNumber).append('}');continue;}// int argRecursion = ((recursionProtection >> (argumentNumber*2)) & 0x3);if (false) { // if (argRecursion == 3){// prevent loop!!!result.append('\uFFFD');} else {Object obj = arguments[argumentNumber];String arg = null;Format subFormatter = null;if (obj == null) {arg = "null";} else if (formats[i] != null) {subFormatter = formats[i];if (subFormatter instanceof ChoiceFormat) {arg = formats[i].format(obj);if (arg.indexOf('{') >= 0) {subFormatter = new MessageFormat(arg, locale);obj = arguments;arg = null;}}} else if (obj instanceof Number) {// format number if cansubFormatter = NumberFormat.getInstance(locale);} else if (obj instanceof Date) {// format a Date if cansubFormatter = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, locale);//fix} else if (obj instanceof String) {arg = (String) obj;} else {arg = obj.toString();if (arg == null) arg = "null";}// At this point we are in two states, either subFormatter// is non-null indicating we should format obj using it,// or arg is non-null and we should use it as the value.if (characterIterators != null) {// If characterIterators is non-null, it indicates we need// to get the CharacterIterator from the child formatter.if (last != result.length()) {characterIterators.add(createAttributedCharacterIterator(result.substring(last)));last = result.length();}if (subFormatter != null) {AttributedCharacterIterator subIterator =subFormatter.formatToCharacterIterator(obj);append(result, subIterator);if (last != result.length()) {characterIterators.add(createAttributedCharacterIterator(subIterator, Field.ARGUMENT,Integer.valueOf(argumentNumber)));last = result.length();}arg = null;}if (arg != null && arg.length() > 0) {result.append(arg);characterIterators.add(createAttributedCharacterIterator(arg, Field.ARGUMENT,Integer.valueOf(argumentNumber)));last = result.length();}}else {if (subFormatter != null) {arg = subFormatter.format(obj);}last = result.length();result.append(arg);if (i == 0 && fp != null && Field.ARGUMENT.equals(fp.getFieldAttribute())) {fp.setBeginIndex(last);fp.setEndIndex(result.length());}last = result.length();}}}result.append(pattern.substring(lastOffset, pattern.length()));if (characterIterators != null && last != result.length()) {characterIterators.add(createAttributedCharacterIterator(result.substring(last)));}return result;}/*** Convenience method to append all the characters in* <code>iterator</code> to the StringBuffer <code>result</code>.*/private void append(StringBuffer result, CharacterIterator iterator) {if (iterator.first() != CharacterIterator.DONE) {char aChar;result.append(iterator.first());while ((aChar = iterator.next()) != CharacterIterator.DONE) {result.append(aChar);}}}// Indices for segmentsprivate static final int SEG_RAW = 0;private static final int SEG_INDEX = 1;private static final int SEG_TYPE = 2;private static final int SEG_MODIFIER = 3; // modifier or subformat// Indices for type keywordsprivate static final int TYPE_NULL = 0;private static final int TYPE_NUMBER = 1;private static final int TYPE_DATE = 2;private static final int TYPE_TIME = 3;private static final int TYPE_CHOICE = 4;private static final String[] TYPE_KEYWORDS = {"","number","date","time","choice"};// Indices for number modifiersprivate static final int MODIFIER_DEFAULT = 0; // common in number and date-timeprivate static final int MODIFIER_CURRENCY = 1;private static final int MODIFIER_PERCENT = 2;private static final int MODIFIER_INTEGER = 3;private static final String[] NUMBER_MODIFIER_KEYWORDS = {"","currency","percent","integer"};// Indices for date-time modifiersprivate static final int MODIFIER_SHORT = 1;private static final int MODIFIER_MEDIUM = 2;private static final int MODIFIER_LONG = 3;private static final int MODIFIER_FULL = 4;private static final String[] DATE_TIME_MODIFIER_KEYWORDS = {"","short","medium","long","full"};// Date-time style values corresponding to the date-time modifiers.private static final int[] DATE_TIME_MODIFIERS = {DateFormat.DEFAULT,DateFormat.SHORT,DateFormat.MEDIUM,DateFormat.LONG,DateFormat.FULL,};private void makeFormat(int position, int offsetNumber,StringBuilder[] textSegments){String[] segments = new String[textSegments.length];for (int i = 0; i < textSegments.length; i++) {StringBuilder oneseg = textSegments[i];segments[i] = (oneseg != null) ? oneseg.toString() : "";}// get the argument numberint argumentNumber;try {argumentNumber = Integer.parseInt(segments[SEG_INDEX]); // always unlocalized!} catch (NumberFormatException e) {throw new IllegalArgumentException("can't parse argument number: "+ segments[SEG_INDEX], e);}if (argumentNumber < 0) {throw new IllegalArgumentException("negative argument number: "+ argumentNumber);}// resize format information arrays if necessaryif (offsetNumber >= formats.length) {int newLength = formats.length * 2;Format[] newFormats = new Format[newLength];int[] newOffsets = new int[newLength];int[] newArgumentNumbers = new int[newLength];System.arraycopy(formats, 0, newFormats, 0, maxOffset + 1);System.arraycopy(offsets, 0, newOffsets, 0, maxOffset + 1);System.arraycopy(argumentNumbers, 0, newArgumentNumbers, 0, maxOffset + 1);formats = newFormats;offsets = newOffsets;argumentNumbers = newArgumentNumbers;}int oldMaxOffset = maxOffset;maxOffset = offsetNumber;offsets[offsetNumber] = segments[SEG_RAW].length();argumentNumbers[offsetNumber] = argumentNumber;// now get the formatFormat newFormat = null;if (segments[SEG_TYPE].length() != 0) {int type = findKeyword(segments[SEG_TYPE], TYPE_KEYWORDS);switch (type) {case TYPE_NULL:// Type "" is allowed. e.g., "{0,}", "{0,,}", and "{0,,#}"// are treated as "{0}".break;case TYPE_NUMBER:switch (findKeyword(segments[SEG_MODIFIER], NUMBER_MODIFIER_KEYWORDS)) {case MODIFIER_DEFAULT:newFormat = NumberFormat.getInstance(locale);break;case MODIFIER_CURRENCY:newFormat = NumberFormat.getCurrencyInstance(locale);break;case MODIFIER_PERCENT:newFormat = NumberFormat.getPercentInstance(locale);break;case MODIFIER_INTEGER:newFormat = NumberFormat.getIntegerInstance(locale);break;default: // DecimalFormat patterntry {newFormat = new DecimalFormat(segments[SEG_MODIFIER],DecimalFormatSymbols.getInstance(locale));} catch (IllegalArgumentException e) {maxOffset = oldMaxOffset;throw e;}break;}break;case TYPE_DATE:case TYPE_TIME:int mod = findKeyword(segments[SEG_MODIFIER], DATE_TIME_MODIFIER_KEYWORDS);if (mod >= 0 && mod < DATE_TIME_MODIFIER_KEYWORDS.length) {if (type == TYPE_DATE) {newFormat = DateFormat.getDateInstance(DATE_TIME_MODIFIERS[mod],locale);} else {newFormat = DateFormat.getTimeInstance(DATE_TIME_MODIFIERS[mod],locale);}} else {// SimpleDateFormat patterntry {newFormat = new SimpleDateFormat(segments[SEG_MODIFIER], locale);} catch (IllegalArgumentException e) {maxOffset = oldMaxOffset;throw e;}}break;case TYPE_CHOICE:try {// ChoiceFormat patternnewFormat = new ChoiceFormat(segments[SEG_MODIFIER]);} catch (Exception e) {maxOffset = oldMaxOffset;throw new IllegalArgumentException("Choice Pattern incorrect: "+ segments[SEG_MODIFIER], e);}break;default:maxOffset = oldMaxOffset;throw new IllegalArgumentException("unknown format type: " +segments[SEG_TYPE]);}}formats[offsetNumber] = newFormat;}private static final int findKeyword(String s, String[] list) {for (int i = 0; i < list.length; ++i) {if (s.equals(list[i]))return i;}// Try trimmed lowercase.String ls = s.trim().toLowerCase(Locale.ROOT);if (ls != s) {for (int i = 0; i < list.length; ++i) {if (ls.equals(list[i]))return i;}}return -1;}private static final void copyAndFixQuotes(String source, int start, int end,StringBuilder target) {boolean quoted = false;for (int i = start; i < end; ++i) {char ch = source.charAt(i);if (ch == '{') {if (!quoted) {target.append('\'');quoted = true;}target.append(ch);} else if (ch == '\'') {target.append("''");} else {if (quoted) {target.append('\'');quoted = false;}target.append(ch);}}if (quoted) {target.append('\'');}}/*** After reading an object from the input stream, do a simple verification* to maintain class invariants.* @throws InvalidObjectException if the objects read from the stream is invalid.*/private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {in.defaultReadObject();boolean isValid = maxOffset >= -1&& formats.length > maxOffset&& offsets.length > maxOffset&& argumentNumbers.length > maxOffset;if (isValid) {int lastOffset = pattern.length() + 1;for (int i = maxOffset; i >= 0; --i) {if ((offsets[i] < 0) || (offsets[i] > lastOffset)) {isValid = false;break;} else {lastOffset = offsets[i];}}}if (!isValid) {throw new InvalidObjectException("Could not reconstruct MessageFormat from corrupt stream.");}}}
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。