Path: blob/aarch64-shenandoah-jdk8u272-b10/jdk/src/share/classes/java/text/MessageFormat.java
38829 views
/*1* Copyright (c) 1996, 2013, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425/*26* (C) Copyright Taligent, Inc. 1996, 1997 - All Rights Reserved27* (C) Copyright IBM Corp. 1996 - 1998 - All Rights Reserved28*29* The original version of this source code and documentation is copyrighted30* and owned by Taligent, Inc., a wholly-owned subsidiary of IBM. These31* materials are provided under terms of a License Agreement between Taligent32* and Sun. This technology is protected by multiple US and International33* patents. This notice and attribution to Taligent may not be removed.34* Taligent is a registered trademark of Taligent, Inc.35*36*/3738package java.text;3940import java.io.InvalidObjectException;41import java.io.IOException;42import java.io.ObjectInputStream;43import java.text.DecimalFormat;44import java.util.ArrayList;45import java.util.Arrays;46import java.util.Date;47import java.util.List;48import java.util.Locale;495051/**52* <code>MessageFormat</code> provides a means to produce concatenated53* messages in a language-neutral way. Use this to construct messages54* displayed for end users.55*56* <p>57* <code>MessageFormat</code> takes a set of objects, formats them, then58* inserts the formatted strings into the pattern at the appropriate places.59*60* <p>61* <strong>Note:</strong>62* <code>MessageFormat</code> differs from the other <code>Format</code>63* classes in that you create a <code>MessageFormat</code> object with one64* of its constructors (not with a <code>getInstance</code> style factory65* method). The factory methods aren't necessary because <code>MessageFormat</code>66* itself doesn't implement locale specific behavior. Any locale specific67* behavior is defined by the pattern that you provide as well as the68* subformats used for inserted arguments.69*70* <h3><a name="patterns">Patterns and Their Interpretation</a></h3>71*72* <code>MessageFormat</code> uses patterns of the following form:73* <blockquote><pre>74* <i>MessageFormatPattern:</i>75* <i>String</i>76* <i>MessageFormatPattern</i> <i>FormatElement</i> <i>String</i>77*78* <i>FormatElement:</i>79* { <i>ArgumentIndex</i> }80* { <i>ArgumentIndex</i> , <i>FormatType</i> }81* { <i>ArgumentIndex</i> , <i>FormatType</i> , <i>FormatStyle</i> }82*83* <i>FormatType: one of </i>84* number date time choice85*86* <i>FormatStyle:</i>87* short88* medium89* long90* full91* integer92* currency93* percent94* <i>SubformatPattern</i>95* </pre></blockquote>96*97* <p>Within a <i>String</i>, a pair of single quotes can be used to98* quote any arbitrary characters except single quotes. For example,99* pattern string <code>"'{0}'"</code> represents string100* <code>"{0}"</code>, not a <i>FormatElement</i>. A single quote itself101* must be represented by doubled single quotes {@code ''} throughout a102* <i>String</i>. For example, pattern string <code>"'{''}'"</code> is103* interpreted as a sequence of <code>'{</code> (start of quoting and a104* left curly brace), <code>''</code> (a single quote), and105* <code>}'</code> (a right curly brace and end of quoting),106* <em>not</em> <code>'{'</code> and <code>'}'</code> (quoted left and107* right curly braces): representing string <code>"{'}"</code>,108* <em>not</em> <code>"{}"</code>.109*110* <p>A <i>SubformatPattern</i> is interpreted by its corresponding111* subformat, and subformat-dependent pattern rules apply. For example,112* pattern string <code>"{1,number,<u>$'#',##</u>}"</code>113* (<i>SubformatPattern</i> with underline) will produce a number format114* with the pound-sign quoted, with a result such as: {@code115* "$#31,45"}. Refer to each {@code Format} subclass documentation for116* details.117*118* <p>Any unmatched quote is treated as closed at the end of the given119* pattern. For example, pattern string {@code "'{0}"} is treated as120* pattern {@code "'{0}'"}.121*122* <p>Any curly braces within an unquoted pattern must be balanced. For123* example, <code>"ab {0} de"</code> and <code>"ab '}' de"</code> are124* valid patterns, but <code>"ab {0'}' de"</code>, <code>"ab } de"</code>125* and <code>"''{''"</code> are not.126*127* <dl><dt><b>Warning:</b><dd>The rules for using quotes within message128* format patterns unfortunately have shown to be somewhat confusing.129* In particular, it isn't always obvious to localizers whether single130* quotes need to be doubled or not. Make sure to inform localizers about131* the rules, and tell them (for example, by using comments in resource132* bundle source files) which strings will be processed by {@code MessageFormat}.133* Note that localizers may need to use single quotes in translated134* strings where the original version doesn't have them.135* </dl>136* <p>137* The <i>ArgumentIndex</i> value is a non-negative integer written138* using the digits {@code '0'} through {@code '9'}, and represents an index into the139* {@code arguments} array passed to the {@code format} methods140* or the result array returned by the {@code parse} methods.141* <p>142* The <i>FormatType</i> and <i>FormatStyle</i> values are used to create143* a {@code Format} instance for the format element. The following144* table shows how the values map to {@code Format} instances. Combinations not145* shown in the table are illegal. A <i>SubformatPattern</i> must146* be a valid pattern string for the {@code Format} subclass used.147*148* <table border=1 summary="Shows how FormatType and FormatStyle values map to Format instances">149* <tr>150* <th id="ft" class="TableHeadingColor">FormatType151* <th id="fs" class="TableHeadingColor">FormatStyle152* <th id="sc" class="TableHeadingColor">Subformat Created153* <tr>154* <td headers="ft"><i>(none)</i>155* <td headers="fs"><i>(none)</i>156* <td headers="sc"><code>null</code>157* <tr>158* <td headers="ft" rowspan=5><code>number</code>159* <td headers="fs"><i>(none)</i>160* <td headers="sc">{@link NumberFormat#getInstance(Locale) NumberFormat.getInstance}{@code (getLocale())}161* <tr>162* <td headers="fs"><code>integer</code>163* <td headers="sc">{@link NumberFormat#getIntegerInstance(Locale) NumberFormat.getIntegerInstance}{@code (getLocale())}164* <tr>165* <td headers="fs"><code>currency</code>166* <td headers="sc">{@link NumberFormat#getCurrencyInstance(Locale) NumberFormat.getCurrencyInstance}{@code (getLocale())}167* <tr>168* <td headers="fs"><code>percent</code>169* <td headers="sc">{@link NumberFormat#getPercentInstance(Locale) NumberFormat.getPercentInstance}{@code (getLocale())}170* <tr>171* <td headers="fs"><i>SubformatPattern</i>172* <td headers="sc">{@code new} {@link DecimalFormat#DecimalFormat(String,DecimalFormatSymbols) DecimalFormat}{@code (subformatPattern,} {@link DecimalFormatSymbols#getInstance(Locale) DecimalFormatSymbols.getInstance}{@code (getLocale()))}173* <tr>174* <td headers="ft" rowspan=6><code>date</code>175* <td headers="fs"><i>(none)</i>176* <td headers="sc">{@link DateFormat#getDateInstance(int,Locale) DateFormat.getDateInstance}{@code (}{@link DateFormat#DEFAULT}{@code , getLocale())}177* <tr>178* <td headers="fs"><code>short</code>179* <td headers="sc">{@link DateFormat#getDateInstance(int,Locale) DateFormat.getDateInstance}{@code (}{@link DateFormat#SHORT}{@code , getLocale())}180* <tr>181* <td headers="fs"><code>medium</code>182* <td headers="sc">{@link DateFormat#getDateInstance(int,Locale) DateFormat.getDateInstance}{@code (}{@link DateFormat#DEFAULT}{@code , getLocale())}183* <tr>184* <td headers="fs"><code>long</code>185* <td headers="sc">{@link DateFormat#getDateInstance(int,Locale) DateFormat.getDateInstance}{@code (}{@link DateFormat#LONG}{@code , getLocale())}186* <tr>187* <td headers="fs"><code>full</code>188* <td headers="sc">{@link DateFormat#getDateInstance(int,Locale) DateFormat.getDateInstance}{@code (}{@link DateFormat#FULL}{@code , getLocale())}189* <tr>190* <td headers="fs"><i>SubformatPattern</i>191* <td headers="sc">{@code new} {@link SimpleDateFormat#SimpleDateFormat(String,Locale) SimpleDateFormat}{@code (subformatPattern, getLocale())}192* <tr>193* <td headers="ft" rowspan=6><code>time</code>194* <td headers="fs"><i>(none)</i>195* <td headers="sc">{@link DateFormat#getTimeInstance(int,Locale) DateFormat.getTimeInstance}{@code (}{@link DateFormat#DEFAULT}{@code , getLocale())}196* <tr>197* <td headers="fs"><code>short</code>198* <td headers="sc">{@link DateFormat#getTimeInstance(int,Locale) DateFormat.getTimeInstance}{@code (}{@link DateFormat#SHORT}{@code , getLocale())}199* <tr>200* <td headers="fs"><code>medium</code>201* <td headers="sc">{@link DateFormat#getTimeInstance(int,Locale) DateFormat.getTimeInstance}{@code (}{@link DateFormat#DEFAULT}{@code , getLocale())}202* <tr>203* <td headers="fs"><code>long</code>204* <td headers="sc">{@link DateFormat#getTimeInstance(int,Locale) DateFormat.getTimeInstance}{@code (}{@link DateFormat#LONG}{@code , getLocale())}205* <tr>206* <td headers="fs"><code>full</code>207* <td headers="sc">{@link DateFormat#getTimeInstance(int,Locale) DateFormat.getTimeInstance}{@code (}{@link DateFormat#FULL}{@code , getLocale())}208* <tr>209* <td headers="fs"><i>SubformatPattern</i>210* <td headers="sc">{@code new} {@link SimpleDateFormat#SimpleDateFormat(String,Locale) SimpleDateFormat}{@code (subformatPattern, getLocale())}211* <tr>212* <td headers="ft"><code>choice</code>213* <td headers="fs"><i>SubformatPattern</i>214* <td headers="sc">{@code new} {@link ChoiceFormat#ChoiceFormat(String) ChoiceFormat}{@code (subformatPattern)}215* </table>216*217* <h4>Usage Information</h4>218*219* <p>220* Here are some examples of usage.221* In real internationalized programs, the message format pattern and other222* static strings will, of course, be obtained from resource bundles.223* Other parameters will be dynamically determined at runtime.224* <p>225* The first example uses the static method <code>MessageFormat.format</code>,226* which internally creates a <code>MessageFormat</code> for one-time use:227* <blockquote><pre>228* int planet = 7;229* String event = "a disturbance in the Force";230*231* String result = MessageFormat.format(232* "At {1,time} on {1,date}, there was {2} on planet {0,number,integer}.",233* planet, new Date(), event);234* </pre></blockquote>235* The output is:236* <blockquote><pre>237* At 12:30 PM on Jul 3, 2053, there was a disturbance in the Force on planet 7.238* </pre></blockquote>239*240* <p>241* The following example creates a <code>MessageFormat</code> instance that242* can be used repeatedly:243* <blockquote><pre>244* int fileCount = 1273;245* String diskName = "MyDisk";246* Object[] testArgs = {new Long(fileCount), diskName};247*248* MessageFormat form = new MessageFormat(249* "The disk \"{1}\" contains {0} file(s).");250*251* System.out.println(form.format(testArgs));252* </pre></blockquote>253* The output with different values for <code>fileCount</code>:254* <blockquote><pre>255* The disk "MyDisk" contains 0 file(s).256* The disk "MyDisk" contains 1 file(s).257* The disk "MyDisk" contains 1,273 file(s).258* </pre></blockquote>259*260* <p>261* For more sophisticated patterns, you can use a <code>ChoiceFormat</code>262* to produce correct forms for singular and plural:263* <blockquote><pre>264* MessageFormat form = new MessageFormat("The disk \"{1}\" contains {0}.");265* double[] filelimits = {0,1,2};266* String[] filepart = {"no files","one file","{0,number} files"};267* ChoiceFormat fileform = new ChoiceFormat(filelimits, filepart);268* form.setFormatByArgumentIndex(0, fileform);269*270* int fileCount = 1273;271* String diskName = "MyDisk";272* Object[] testArgs = {new Long(fileCount), diskName};273*274* System.out.println(form.format(testArgs));275* </pre></blockquote>276* The output with different values for <code>fileCount</code>:277* <blockquote><pre>278* The disk "MyDisk" contains no files.279* The disk "MyDisk" contains one file.280* The disk "MyDisk" contains 1,273 files.281* </pre></blockquote>282*283* <p>284* You can create the <code>ChoiceFormat</code> programmatically, as in the285* above example, or by using a pattern. See {@link ChoiceFormat}286* for more information.287* <blockquote><pre>{@code288* form.applyPattern(289* "There {0,choice,0#are no files|1#is one file|1<are {0,number,integer} files}.");290* }</pre></blockquote>291*292* <p>293* <strong>Note:</strong> As we see above, the string produced294* by a <code>ChoiceFormat</code> in <code>MessageFormat</code> is treated as special;295* occurrences of '{' are used to indicate subformats, and cause recursion.296* If you create both a <code>MessageFormat</code> and <code>ChoiceFormat</code>297* programmatically (instead of using the string patterns), then be careful not to298* produce a format that recurses on itself, which will cause an infinite loop.299* <p>300* When a single argument is parsed more than once in the string, the last match301* will be the final result of the parsing. For example,302* <blockquote><pre>303* MessageFormat mf = new MessageFormat("{0,number,#.##}, {0,number,#.#}");304* Object[] objs = {new Double(3.1415)};305* String result = mf.format( objs );306* // result now equals "3.14, 3.1"307* objs = null;308* objs = mf.parse(result, new ParsePosition(0));309* // objs now equals {new Double(3.1)}310* </pre></blockquote>311*312* <p>313* Likewise, parsing with a {@code MessageFormat} object using patterns containing314* multiple occurrences of the same argument would return the last match. For315* example,316* <blockquote><pre>317* MessageFormat mf = new MessageFormat("{0}, {0}, {0}");318* String forParsing = "x, y, z";319* Object[] objs = mf.parse(forParsing, new ParsePosition(0));320* // result now equals {new String("z")}321* </pre></blockquote>322*323* <h4><a name="synchronization">Synchronization</a></h4>324*325* <p>326* Message formats are not synchronized.327* It is recommended to create separate format instances for each thread.328* If multiple threads access a format concurrently, it must be synchronized329* externally.330*331* @see java.util.Locale332* @see Format333* @see NumberFormat334* @see DecimalFormat335* @see DecimalFormatSymbols336* @see ChoiceFormat337* @see DateFormat338* @see SimpleDateFormat339*340* @author Mark Davis341*/342343public class MessageFormat extends Format {344345private static final long serialVersionUID = 6479157306784022952L;346347/**348* Constructs a MessageFormat for the default349* {@link java.util.Locale.Category#FORMAT FORMAT} locale and the350* specified pattern.351* The constructor first sets the locale, then parses the pattern and352* creates a list of subformats for the format elements contained in it.353* Patterns and their interpretation are specified in the354* <a href="#patterns">class description</a>.355*356* @param pattern the pattern for this message format357* @exception IllegalArgumentException if the pattern is invalid358*/359public MessageFormat(String pattern) {360this.locale = Locale.getDefault(Locale.Category.FORMAT);361applyPattern(pattern);362}363364/**365* Constructs a MessageFormat for the specified locale and366* pattern.367* The constructor first sets the locale, then parses the pattern and368* creates a list of subformats for the format elements contained in it.369* Patterns and their interpretation are specified in the370* <a href="#patterns">class description</a>.371*372* @param pattern the pattern for this message format373* @param locale the locale for this message format374* @exception IllegalArgumentException if the pattern is invalid375* @since 1.4376*/377public MessageFormat(String pattern, Locale locale) {378this.locale = locale;379applyPattern(pattern);380}381382/**383* Sets the locale to be used when creating or comparing subformats.384* This affects subsequent calls385* <ul>386* <li>to the {@link #applyPattern applyPattern}387* and {@link #toPattern toPattern} methods if format elements specify388* a format type and therefore have the subformats created in the389* <code>applyPattern</code> method, as well as390* <li>to the <code>format</code> and391* {@link #formatToCharacterIterator formatToCharacterIterator} methods392* if format elements do not specify a format type and therefore have393* the subformats created in the formatting methods.394* </ul>395* Subformats that have already been created are not affected.396*397* @param locale the locale to be used when creating or comparing subformats398*/399public void setLocale(Locale locale) {400this.locale = locale;401}402403/**404* Gets the locale that's used when creating or comparing subformats.405*406* @return the locale used when creating or comparing subformats407*/408public Locale getLocale() {409return locale;410}411412413/**414* Sets the pattern used by this message format.415* The method parses the pattern and creates a list of subformats416* for the format elements contained in it.417* Patterns and their interpretation are specified in the418* <a href="#patterns">class description</a>.419*420* @param pattern the pattern for this message format421* @exception IllegalArgumentException if the pattern is invalid422*/423@SuppressWarnings("fallthrough") // fallthrough in switch is expected, suppress it424public void applyPattern(String pattern) {425StringBuilder[] segments = new StringBuilder[4];426// Allocate only segments[SEG_RAW] here. The rest are427// allocated on demand.428segments[SEG_RAW] = new StringBuilder();429430int part = SEG_RAW;431int formatNumber = 0;432boolean inQuote = false;433int braceStack = 0;434maxOffset = -1;435for (int i = 0; i < pattern.length(); ++i) {436char ch = pattern.charAt(i);437if (part == SEG_RAW) {438if (ch == '\'') {439if (i + 1 < pattern.length()440&& pattern.charAt(i+1) == '\'') {441segments[part].append(ch); // handle doubles442++i;443} else {444inQuote = !inQuote;445}446} else if (ch == '{' && !inQuote) {447part = SEG_INDEX;448if (segments[SEG_INDEX] == null) {449segments[SEG_INDEX] = new StringBuilder();450}451} else {452segments[part].append(ch);453}454} else {455if (inQuote) { // just copy quotes in parts456segments[part].append(ch);457if (ch == '\'') {458inQuote = false;459}460} else {461switch (ch) {462case ',':463if (part < SEG_MODIFIER) {464if (segments[++part] == null) {465segments[part] = new StringBuilder();466}467} else {468segments[part].append(ch);469}470break;471case '{':472++braceStack;473segments[part].append(ch);474break;475case '}':476if (braceStack == 0) {477part = SEG_RAW;478makeFormat(i, formatNumber, segments);479formatNumber++;480// throw away other segments481segments[SEG_INDEX] = null;482segments[SEG_TYPE] = null;483segments[SEG_MODIFIER] = null;484} else {485--braceStack;486segments[part].append(ch);487}488break;489case ' ':490// Skip any leading space chars for SEG_TYPE.491if (part != SEG_TYPE || segments[SEG_TYPE].length() > 0) {492segments[part].append(ch);493}494break;495case '\'':496inQuote = true;497// fall through, so we keep quotes in other parts498default:499segments[part].append(ch);500break;501}502}503}504}505if (braceStack == 0 && part != 0) {506maxOffset = -1;507throw new IllegalArgumentException("Unmatched braces in the pattern.");508}509this.pattern = segments[0].toString();510}511512513/**514* Returns a pattern representing the current state of the message format.515* The string is constructed from internal information and therefore516* does not necessarily equal the previously applied pattern.517*518* @return a pattern representing the current state of the message format519*/520public String toPattern() {521// later, make this more extensible522int lastOffset = 0;523StringBuilder result = new StringBuilder();524for (int i = 0; i <= maxOffset; ++i) {525copyAndFixQuotes(pattern, lastOffset, offsets[i], result);526lastOffset = offsets[i];527result.append('{').append(argumentNumbers[i]);528Format fmt = formats[i];529if (fmt == null) {530// do nothing, string format531} else if (fmt instanceof NumberFormat) {532if (fmt.equals(NumberFormat.getInstance(locale))) {533result.append(",number");534} else if (fmt.equals(NumberFormat.getCurrencyInstance(locale))) {535result.append(",number,currency");536} else if (fmt.equals(NumberFormat.getPercentInstance(locale))) {537result.append(",number,percent");538} else if (fmt.equals(NumberFormat.getIntegerInstance(locale))) {539result.append(",number,integer");540} else {541if (fmt instanceof DecimalFormat) {542result.append(",number,").append(((DecimalFormat)fmt).toPattern());543} else if (fmt instanceof ChoiceFormat) {544result.append(",choice,").append(((ChoiceFormat)fmt).toPattern());545} else {546// UNKNOWN547}548}549} else if (fmt instanceof DateFormat) {550int index;551for (index = MODIFIER_DEFAULT; index < DATE_TIME_MODIFIERS.length; index++) {552DateFormat df = DateFormat.getDateInstance(DATE_TIME_MODIFIERS[index],553locale);554if (fmt.equals(df)) {555result.append(",date");556break;557}558df = DateFormat.getTimeInstance(DATE_TIME_MODIFIERS[index],559locale);560if (fmt.equals(df)) {561result.append(",time");562break;563}564}565if (index >= DATE_TIME_MODIFIERS.length) {566if (fmt instanceof SimpleDateFormat) {567result.append(",date,").append(((SimpleDateFormat)fmt).toPattern());568} else {569// UNKNOWN570}571} else if (index != MODIFIER_DEFAULT) {572result.append(',').append(DATE_TIME_MODIFIER_KEYWORDS[index]);573}574} else {575//result.append(", unknown");576}577result.append('}');578}579copyAndFixQuotes(pattern, lastOffset, pattern.length(), result);580return result.toString();581}582583/**584* Sets the formats to use for the values passed into585* <code>format</code> methods or returned from <code>parse</code>586* methods. The indices of elements in <code>newFormats</code>587* correspond to the argument indices used in the previously set588* pattern string.589* The order of formats in <code>newFormats</code> thus corresponds to590* the order of elements in the <code>arguments</code> array passed591* to the <code>format</code> methods or the result array returned592* by the <code>parse</code> methods.593* <p>594* If an argument index is used for more than one format element595* in the pattern string, then the corresponding new format is used596* for all such format elements. If an argument index is not used597* for any format element in the pattern string, then the598* corresponding new format is ignored. If fewer formats are provided599* than needed, then only the formats for argument indices less600* than <code>newFormats.length</code> are replaced.601*602* @param newFormats the new formats to use603* @exception NullPointerException if <code>newFormats</code> is null604* @since 1.4605*/606public void setFormatsByArgumentIndex(Format[] newFormats) {607for (int i = 0; i <= maxOffset; i++) {608int j = argumentNumbers[i];609if (j < newFormats.length) {610formats[i] = newFormats[j];611}612}613}614615/**616* Sets the formats to use for the format elements in the617* previously set pattern string.618* The order of formats in <code>newFormats</code> corresponds to619* the order of format elements in the pattern string.620* <p>621* If more formats are provided than needed by the pattern string,622* the remaining ones are ignored. If fewer formats are provided623* than needed, then only the first <code>newFormats.length</code>624* formats are replaced.625* <p>626* Since the order of format elements in a pattern string often627* changes during localization, it is generally better to use the628* {@link #setFormatsByArgumentIndex setFormatsByArgumentIndex}629* method, which assumes an order of formats corresponding to the630* order of elements in the <code>arguments</code> array passed to631* the <code>format</code> methods or the result array returned by632* the <code>parse</code> methods.633*634* @param newFormats the new formats to use635* @exception NullPointerException if <code>newFormats</code> is null636*/637public void setFormats(Format[] newFormats) {638int runsToCopy = newFormats.length;639if (runsToCopy > maxOffset + 1) {640runsToCopy = maxOffset + 1;641}642for (int i = 0; i < runsToCopy; i++) {643formats[i] = newFormats[i];644}645}646647/**648* Sets the format to use for the format elements within the649* previously set pattern string that use the given argument650* index.651* The argument index is part of the format element definition and652* represents an index into the <code>arguments</code> array passed653* to the <code>format</code> methods or the result array returned654* by the <code>parse</code> methods.655* <p>656* If the argument index is used for more than one format element657* in the pattern string, then the new format is used for all such658* format elements. If the argument index is not used for any format659* element in the pattern string, then the new format is ignored.660*661* @param argumentIndex the argument index for which to use the new format662* @param newFormat the new format to use663* @since 1.4664*/665public void setFormatByArgumentIndex(int argumentIndex, Format newFormat) {666for (int j = 0; j <= maxOffset; j++) {667if (argumentNumbers[j] == argumentIndex) {668formats[j] = newFormat;669}670}671}672673/**674* Sets the format to use for the format element with the given675* format element index within the previously set pattern string.676* The format element index is the zero-based number of the format677* element counting from the start of the pattern string.678* <p>679* Since the order of format elements in a pattern string often680* changes during localization, it is generally better to use the681* {@link #setFormatByArgumentIndex setFormatByArgumentIndex}682* method, which accesses format elements based on the argument683* index they specify.684*685* @param formatElementIndex the index of a format element within the pattern686* @param newFormat the format to use for the specified format element687* @exception ArrayIndexOutOfBoundsException if {@code formatElementIndex} is equal to or688* larger than the number of format elements in the pattern string689*/690public void setFormat(int formatElementIndex, Format newFormat) {691formats[formatElementIndex] = newFormat;692}693694/**695* Gets the formats used for the values passed into696* <code>format</code> methods or returned from <code>parse</code>697* methods. The indices of elements in the returned array698* correspond to the argument indices used in the previously set699* pattern string.700* The order of formats in the returned array thus corresponds to701* the order of elements in the <code>arguments</code> array passed702* to the <code>format</code> methods or the result array returned703* by the <code>parse</code> methods.704* <p>705* If an argument index is used for more than one format element706* in the pattern string, then the format used for the last such707* format element is returned in the array. If an argument index708* is not used for any format element in the pattern string, then709* null is returned in the array.710*711* @return the formats used for the arguments within the pattern712* @since 1.4713*/714public Format[] getFormatsByArgumentIndex() {715int maximumArgumentNumber = -1;716for (int i = 0; i <= maxOffset; i++) {717if (argumentNumbers[i] > maximumArgumentNumber) {718maximumArgumentNumber = argumentNumbers[i];719}720}721Format[] resultArray = new Format[maximumArgumentNumber + 1];722for (int i = 0; i <= maxOffset; i++) {723resultArray[argumentNumbers[i]] = formats[i];724}725return resultArray;726}727728/**729* Gets the formats used for the format elements in the730* previously set pattern string.731* The order of formats in the returned array corresponds to732* the order of format elements in the pattern string.733* <p>734* Since the order of format elements in a pattern string often735* changes during localization, it's generally better to use the736* {@link #getFormatsByArgumentIndex getFormatsByArgumentIndex}737* method, which assumes an order of formats corresponding to the738* order of elements in the <code>arguments</code> array passed to739* the <code>format</code> methods or the result array returned by740* the <code>parse</code> methods.741*742* @return the formats used for the format elements in the pattern743*/744public Format[] getFormats() {745Format[] resultArray = new Format[maxOffset + 1];746System.arraycopy(formats, 0, resultArray, 0, maxOffset + 1);747return resultArray;748}749750/**751* Formats an array of objects and appends the <code>MessageFormat</code>'s752* pattern, with format elements replaced by the formatted objects, to the753* provided <code>StringBuffer</code>.754* <p>755* The text substituted for the individual format elements is derived from756* the current subformat of the format element and the757* <code>arguments</code> element at the format element's argument index758* as indicated by the first matching line of the following table. An759* argument is <i>unavailable</i> if <code>arguments</code> is760* <code>null</code> or has fewer than argumentIndex+1 elements.761*762* <table border=1 summary="Examples of subformat,argument,and formatted text">763* <tr>764* <th>Subformat765* <th>Argument766* <th>Formatted Text767* <tr>768* <td><i>any</i>769* <td><i>unavailable</i>770* <td><code>"{" + argumentIndex + "}"</code>771* <tr>772* <td><i>any</i>773* <td><code>null</code>774* <td><code>"null"</code>775* <tr>776* <td><code>instanceof ChoiceFormat</code>777* <td><i>any</i>778* <td><code>subformat.format(argument).indexOf('{') >= 0 ?<br>779* (new MessageFormat(subformat.format(argument), getLocale())).format(argument) :780* subformat.format(argument)</code>781* <tr>782* <td><code>!= null</code>783* <td><i>any</i>784* <td><code>subformat.format(argument)</code>785* <tr>786* <td><code>null</code>787* <td><code>instanceof Number</code>788* <td><code>NumberFormat.getInstance(getLocale()).format(argument)</code>789* <tr>790* <td><code>null</code>791* <td><code>instanceof Date</code>792* <td><code>DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, getLocale()).format(argument)</code>793* <tr>794* <td><code>null</code>795* <td><code>instanceof String</code>796* <td><code>argument</code>797* <tr>798* <td><code>null</code>799* <td><i>any</i>800* <td><code>argument.toString()</code>801* </table>802* <p>803* If <code>pos</code> is non-null, and refers to804* <code>Field.ARGUMENT</code>, the location of the first formatted805* string will be returned.806*807* @param arguments an array of objects to be formatted and substituted.808* @param result where text is appended.809* @param pos On input: an alignment field, if desired.810* On output: the offsets of the alignment field.811* @return the string buffer passed in as {@code result}, with formatted812* text appended813* @exception IllegalArgumentException if an argument in the814* <code>arguments</code> array is not of the type815* expected by the format element(s) that use it.816*/817public final StringBuffer format(Object[] arguments, StringBuffer result,818FieldPosition pos)819{820return subformat(arguments, result, pos, null);821}822823/**824* Creates a MessageFormat with the given pattern and uses it825* to format the given arguments. This is equivalent to826* <blockquote>827* <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>828* </blockquote>829*830* @param pattern the pattern string831* @param arguments object(s) to format832* @return the formatted string833* @exception IllegalArgumentException if the pattern is invalid,834* or if an argument in the <code>arguments</code> array835* is not of the type expected by the format element(s)836* that use it.837*/838public static String format(String pattern, Object ... arguments) {839MessageFormat temp = new MessageFormat(pattern);840return temp.format(arguments);841}842843// Overrides844/**845* Formats an array of objects and appends the <code>MessageFormat</code>'s846* pattern, with format elements replaced by the formatted objects, to the847* provided <code>StringBuffer</code>.848* This is equivalent to849* <blockquote>850* <code>{@link #format(java.lang.Object[], java.lang.StringBuffer, java.text.FieldPosition) format}((Object[]) arguments, result, pos)</code>851* </blockquote>852*853* @param arguments an array of objects to be formatted and substituted.854* @param result where text is appended.855* @param pos On input: an alignment field, if desired.856* On output: the offsets of the alignment field.857* @exception IllegalArgumentException if an argument in the858* <code>arguments</code> array is not of the type859* expected by the format element(s) that use it.860*/861public final StringBuffer format(Object arguments, StringBuffer result,862FieldPosition pos)863{864return subformat((Object[]) arguments, result, pos, null);865}866867/**868* Formats an array of objects and inserts them into the869* <code>MessageFormat</code>'s pattern, producing an870* <code>AttributedCharacterIterator</code>.871* You can use the returned <code>AttributedCharacterIterator</code>872* to build the resulting String, as well as to determine information873* about the resulting String.874* <p>875* The text of the returned <code>AttributedCharacterIterator</code> is876* the same that would be returned by877* <blockquote>878* <code>{@link #format(java.lang.Object[], java.lang.StringBuffer, java.text.FieldPosition) format}(arguments, new StringBuffer(), null).toString()</code>879* </blockquote>880* <p>881* In addition, the <code>AttributedCharacterIterator</code> contains at882* least attributes indicating where text was generated from an883* argument in the <code>arguments</code> array. The keys of these attributes are of884* type <code>MessageFormat.Field</code>, their values are885* <code>Integer</code> objects indicating the index in the <code>arguments</code>886* array of the argument from which the text was generated.887* <p>888* The attributes/value from the underlying <code>Format</code>889* instances that <code>MessageFormat</code> uses will also be890* placed in the resulting <code>AttributedCharacterIterator</code>.891* This allows you to not only find where an argument is placed in the892* resulting String, but also which fields it contains in turn.893*894* @param arguments an array of objects to be formatted and substituted.895* @return AttributedCharacterIterator describing the formatted value.896* @exception NullPointerException if <code>arguments</code> is null.897* @exception IllegalArgumentException if an argument in the898* <code>arguments</code> array is not of the type899* expected by the format element(s) that use it.900* @since 1.4901*/902public AttributedCharacterIterator formatToCharacterIterator(Object arguments) {903StringBuffer result = new StringBuffer();904ArrayList<AttributedCharacterIterator> iterators = new ArrayList<>();905906if (arguments == null) {907throw new NullPointerException(908"formatToCharacterIterator must be passed non-null object");909}910subformat((Object[]) arguments, result, null, iterators);911if (iterators.size() == 0) {912return createAttributedCharacterIterator("");913}914return createAttributedCharacterIterator(915iterators.toArray(916new AttributedCharacterIterator[iterators.size()]));917}918919/**920* Parses the string.921*922* <p>Caveats: The parse may fail in a number of circumstances.923* For example:924* <ul>925* <li>If one of the arguments does not occur in the pattern.926* <li>If the format of an argument loses information, such as927* with a choice format where a large number formats to "many".928* <li>Does not yet handle recursion (where929* the substituted strings contain {n} references.)930* <li>Will not always find a match (or the correct match)931* if some part of the parse is ambiguous.932* For example, if the pattern "{1},{2}" is used with the933* string arguments {"a,b", "c"}, it will format as "a,b,c".934* When the result is parsed, it will return {"a", "b,c"}.935* <li>If a single argument is parsed more than once in the string,936* then the later parse wins.937* </ul>938* When the parse fails, use ParsePosition.getErrorIndex() to find out939* where in the string the parsing failed. The returned error940* index is the starting offset of the sub-patterns that the string941* is comparing with. For example, if the parsing string "AAA {0} BBB"942* is comparing against the pattern "AAD {0} BBB", the error index is943* 0. When an error occurs, the call to this method will return null.944* If the source is null, return an empty array.945*946* @param source the string to parse947* @param pos the parse position948* @return an array of parsed objects949*/950public Object[] parse(String source, ParsePosition pos) {951if (source == null) {952Object[] empty = {};953return empty;954}955956int maximumArgumentNumber = -1;957for (int i = 0; i <= maxOffset; i++) {958if (argumentNumbers[i] > maximumArgumentNumber) {959maximumArgumentNumber = argumentNumbers[i];960}961}962Object[] resultArray = new Object[maximumArgumentNumber + 1];963964int patternOffset = 0;965int sourceOffset = pos.index;966ParsePosition tempStatus = new ParsePosition(0);967for (int i = 0; i <= maxOffset; ++i) {968// match up to format969int len = offsets[i] - patternOffset;970if (len == 0 || pattern.regionMatches(patternOffset,971source, sourceOffset, len)) {972sourceOffset += len;973patternOffset += len;974} else {975pos.errorIndex = sourceOffset;976return null; // leave index as is to signal error977}978979// now use format980if (formats[i] == null) { // string format981// if at end, use longest possible match982// otherwise uses first match to intervening string983// does NOT recursively try all possibilities984int tempLength = (i != maxOffset) ? offsets[i+1] : pattern.length();985986int next;987if (patternOffset >= tempLength) {988next = source.length();989}else{990next = source.indexOf(pattern.substring(patternOffset, tempLength),991sourceOffset);992}993994if (next < 0) {995pos.errorIndex = sourceOffset;996return null; // leave index as is to signal error997} else {998String strValue= source.substring(sourceOffset,next);999if (!strValue.equals("{"+argumentNumbers[i]+"}"))1000resultArray[argumentNumbers[i]]1001= source.substring(sourceOffset,next);1002sourceOffset = next;1003}1004} else {1005tempStatus.index = sourceOffset;1006resultArray[argumentNumbers[i]]1007= formats[i].parseObject(source,tempStatus);1008if (tempStatus.index == sourceOffset) {1009pos.errorIndex = sourceOffset;1010return null; // leave index as is to signal error1011}1012sourceOffset = tempStatus.index; // update1013}1014}1015int len = pattern.length() - patternOffset;1016if (len == 0 || pattern.regionMatches(patternOffset,1017source, sourceOffset, len)) {1018pos.index = sourceOffset + len;1019} else {1020pos.errorIndex = sourceOffset;1021return null; // leave index as is to signal error1022}1023return resultArray;1024}10251026/**1027* Parses text from the beginning of the given string to produce an object1028* array.1029* The method may not use the entire text of the given string.1030* <p>1031* See the {@link #parse(String, ParsePosition)} method for more information1032* on message parsing.1033*1034* @param source A <code>String</code> whose beginning should be parsed.1035* @return An <code>Object</code> array parsed from the string.1036* @exception ParseException if the beginning of the specified string1037* cannot be parsed.1038*/1039public Object[] parse(String source) throws ParseException {1040ParsePosition pos = new ParsePosition(0);1041Object[] result = parse(source, pos);1042if (pos.index == 0) // unchanged, returned object is null1043throw new ParseException("MessageFormat parse error!", pos.errorIndex);10441045return result;1046}10471048/**1049* Parses text from a string to produce an object array.1050* <p>1051* The method attempts to parse text starting at the index given by1052* <code>pos</code>.1053* If parsing succeeds, then the index of <code>pos</code> is updated1054* to the index after the last character used (parsing does not necessarily1055* use all characters up to the end of the string), and the parsed1056* object array is returned. The updated <code>pos</code> can be used to1057* indicate the starting point for the next call to this method.1058* If an error occurs, then the index of <code>pos</code> is not1059* changed, the error index of <code>pos</code> is set to the index of1060* the character where the error occurred, and null is returned.1061* <p>1062* See the {@link #parse(String, ParsePosition)} method for more information1063* on message parsing.1064*1065* @param source A <code>String</code>, part of which should be parsed.1066* @param pos A <code>ParsePosition</code> object with index and error1067* index information as described above.1068* @return An <code>Object</code> array parsed from the string. In case of1069* error, returns null.1070* @exception NullPointerException if <code>pos</code> is null.1071*/1072public Object parseObject(String source, ParsePosition pos) {1073return parse(source, pos);1074}10751076/**1077* Creates and returns a copy of this object.1078*1079* @return a clone of this instance.1080*/1081public Object clone() {1082MessageFormat other = (MessageFormat) super.clone();10831084// clone arrays. Can't do with utility because of bug in Cloneable1085other.formats = formats.clone(); // shallow clone1086for (int i = 0; i < formats.length; ++i) {1087if (formats[i] != null)1088other.formats[i] = (Format)formats[i].clone();1089}1090// for primitives or immutables, shallow clone is enough1091other.offsets = offsets.clone();1092other.argumentNumbers = argumentNumbers.clone();10931094return other;1095}10961097/**1098* Equality comparison between two message format objects1099*/1100public boolean equals(Object obj) {1101if (this == obj) // quick check1102return true;1103if (obj == null || getClass() != obj.getClass())1104return false;1105MessageFormat other = (MessageFormat) obj;1106return (maxOffset == other.maxOffset1107&& pattern.equals(other.pattern)1108&& ((locale != null && locale.equals(other.locale))1109|| (locale == null && other.locale == null))1110&& Arrays.equals(offsets,other.offsets)1111&& Arrays.equals(argumentNumbers,other.argumentNumbers)1112&& Arrays.equals(formats,other.formats));1113}11141115/**1116* Generates a hash code for the message format object.1117*/1118public int hashCode() {1119return pattern.hashCode(); // enough for reasonable distribution1120}112111221123/**1124* Defines constants that are used as attribute keys in the1125* <code>AttributedCharacterIterator</code> returned1126* from <code>MessageFormat.formatToCharacterIterator</code>.1127*1128* @since 1.41129*/1130public static class Field extends Format.Field {11311132// Proclaim serial compatibility with 1.4 FCS1133private static final long serialVersionUID = 7899943957617360810L;11341135/**1136* Creates a Field with the specified name.1137*1138* @param name Name of the attribute1139*/1140protected Field(String name) {1141super(name);1142}11431144/**1145* Resolves instances being deserialized to the predefined constants.1146*1147* @throws InvalidObjectException if the constant could not be1148* resolved.1149* @return resolved MessageFormat.Field constant1150*/1151protected Object readResolve() throws InvalidObjectException {1152if (this.getClass() != MessageFormat.Field.class) {1153throw new InvalidObjectException("subclass didn't correctly implement readResolve");1154}11551156return ARGUMENT;1157}11581159//1160// The constants1161//11621163/**1164* Constant identifying a portion of a message that was generated1165* from an argument passed into <code>formatToCharacterIterator</code>.1166* The value associated with the key will be an <code>Integer</code>1167* indicating the index in the <code>arguments</code> array of the1168* argument from which the text was generated.1169*/1170public final static Field ARGUMENT =1171new Field("message argument field");1172}11731174// ===========================privates============================11751176/**1177* The locale to use for formatting numbers and dates.1178* @serial1179*/1180private Locale locale;11811182/**1183* The string that the formatted values are to be plugged into. In other words, this1184* is the pattern supplied on construction with all of the {} expressions taken out.1185* @serial1186*/1187private String pattern = "";11881189/** The initially expected number of subformats in the format */1190private static final int INITIAL_FORMATS = 10;11911192/**1193* An array of formatters, which are used to format the arguments.1194* @serial1195*/1196private Format[] formats = new Format[INITIAL_FORMATS];11971198/**1199* The positions where the results of formatting each argument are to be inserted1200* into the pattern.1201* @serial1202*/1203private int[] offsets = new int[INITIAL_FORMATS];12041205/**1206* The argument numbers corresponding to each formatter. (The formatters are stored1207* in the order they occur in the pattern, not in the order in which the arguments1208* are specified.)1209* @serial1210*/1211private int[] argumentNumbers = new int[INITIAL_FORMATS];12121213/**1214* One less than the number of entries in <code>offsets</code>. Can also be thought of1215* as the index of the highest-numbered element in <code>offsets</code> that is being used.1216* All of these arrays should have the same number of elements being used as <code>offsets</code>1217* does, and so this variable suffices to tell us how many entries are in all of them.1218* @serial1219*/1220private int maxOffset = -1;12211222/**1223* Internal routine used by format. If <code>characterIterators</code> is1224* non-null, AttributedCharacterIterator will be created from the1225* subformats as necessary. If <code>characterIterators</code> is null1226* and <code>fp</code> is non-null and identifies1227* <code>Field.MESSAGE_ARGUMENT</code>, the location of1228* the first replaced argument will be set in it.1229*1230* @exception IllegalArgumentException if an argument in the1231* <code>arguments</code> array is not of the type1232* expected by the format element(s) that use it.1233*/1234private StringBuffer subformat(Object[] arguments, StringBuffer result,1235FieldPosition fp, List<AttributedCharacterIterator> characterIterators) {1236// note: this implementation assumes a fast substring & index.1237// if this is not true, would be better to append chars one by one.1238int lastOffset = 0;1239int last = result.length();1240for (int i = 0; i <= maxOffset; ++i) {1241result.append(pattern.substring(lastOffset, offsets[i]));1242lastOffset = offsets[i];1243int argumentNumber = argumentNumbers[i];1244if (arguments == null || argumentNumber >= arguments.length) {1245result.append('{').append(argumentNumber).append('}');1246continue;1247}1248// int argRecursion = ((recursionProtection >> (argumentNumber*2)) & 0x3);1249if (false) { // if (argRecursion == 3){1250// prevent loop!!!1251result.append('\uFFFD');1252} else {1253Object obj = arguments[argumentNumber];1254String arg = null;1255Format subFormatter = null;1256if (obj == null) {1257arg = "null";1258} else if (formats[i] != null) {1259subFormatter = formats[i];1260if (subFormatter instanceof ChoiceFormat) {1261arg = formats[i].format(obj);1262if (arg.indexOf('{') >= 0) {1263subFormatter = new MessageFormat(arg, locale);1264obj = arguments;1265arg = null;1266}1267}1268} else if (obj instanceof Number) {1269// format number if can1270subFormatter = NumberFormat.getInstance(locale);1271} else if (obj instanceof Date) {1272// format a Date if can1273subFormatter = DateFormat.getDateTimeInstance(1274DateFormat.SHORT, DateFormat.SHORT, locale);//fix1275} else if (obj instanceof String) {1276arg = (String) obj;12771278} else {1279arg = obj.toString();1280if (arg == null) arg = "null";1281}12821283// At this point we are in two states, either subFormatter1284// is non-null indicating we should format obj using it,1285// or arg is non-null and we should use it as the value.12861287if (characterIterators != null) {1288// If characterIterators is non-null, it indicates we need1289// to get the CharacterIterator from the child formatter.1290if (last != result.length()) {1291characterIterators.add(1292createAttributedCharacterIterator(result.substring1293(last)));1294last = result.length();1295}1296if (subFormatter != null) {1297AttributedCharacterIterator subIterator =1298subFormatter.formatToCharacterIterator(obj);12991300append(result, subIterator);1301if (last != result.length()) {1302characterIterators.add(1303createAttributedCharacterIterator(1304subIterator, Field.ARGUMENT,1305Integer.valueOf(argumentNumber)));1306last = result.length();1307}1308arg = null;1309}1310if (arg != null && arg.length() > 0) {1311result.append(arg);1312characterIterators.add(1313createAttributedCharacterIterator(1314arg, Field.ARGUMENT,1315Integer.valueOf(argumentNumber)));1316last = result.length();1317}1318}1319else {1320if (subFormatter != null) {1321arg = subFormatter.format(obj);1322}1323last = result.length();1324result.append(arg);1325if (i == 0 && fp != null && Field.ARGUMENT.equals(1326fp.getFieldAttribute())) {1327fp.setBeginIndex(last);1328fp.setEndIndex(result.length());1329}1330last = result.length();1331}1332}1333}1334result.append(pattern.substring(lastOffset, pattern.length()));1335if (characterIterators != null && last != result.length()) {1336characterIterators.add(createAttributedCharacterIterator(1337result.substring(last)));1338}1339return result;1340}13411342/**1343* Convenience method to append all the characters in1344* <code>iterator</code> to the StringBuffer <code>result</code>.1345*/1346private void append(StringBuffer result, CharacterIterator iterator) {1347if (iterator.first() != CharacterIterator.DONE) {1348char aChar;13491350result.append(iterator.first());1351while ((aChar = iterator.next()) != CharacterIterator.DONE) {1352result.append(aChar);1353}1354}1355}13561357// Indices for segments1358private static final int SEG_RAW = 0;1359private static final int SEG_INDEX = 1;1360private static final int SEG_TYPE = 2;1361private static final int SEG_MODIFIER = 3; // modifier or subformat13621363// Indices for type keywords1364private static final int TYPE_NULL = 0;1365private static final int TYPE_NUMBER = 1;1366private static final int TYPE_DATE = 2;1367private static final int TYPE_TIME = 3;1368private static final int TYPE_CHOICE = 4;13691370private static final String[] TYPE_KEYWORDS = {1371"",1372"number",1373"date",1374"time",1375"choice"1376};13771378// Indices for number modifiers1379private static final int MODIFIER_DEFAULT = 0; // common in number and date-time1380private static final int MODIFIER_CURRENCY = 1;1381private static final int MODIFIER_PERCENT = 2;1382private static final int MODIFIER_INTEGER = 3;13831384private static final String[] NUMBER_MODIFIER_KEYWORDS = {1385"",1386"currency",1387"percent",1388"integer"1389};13901391// Indices for date-time modifiers1392private static final int MODIFIER_SHORT = 1;1393private static final int MODIFIER_MEDIUM = 2;1394private static final int MODIFIER_LONG = 3;1395private static final int MODIFIER_FULL = 4;13961397private static final String[] DATE_TIME_MODIFIER_KEYWORDS = {1398"",1399"short",1400"medium",1401"long",1402"full"1403};14041405// Date-time style values corresponding to the date-time modifiers.1406private static final int[] DATE_TIME_MODIFIERS = {1407DateFormat.DEFAULT,1408DateFormat.SHORT,1409DateFormat.MEDIUM,1410DateFormat.LONG,1411DateFormat.FULL,1412};14131414private void makeFormat(int position, int offsetNumber,1415StringBuilder[] textSegments)1416{1417String[] segments = new String[textSegments.length];1418for (int i = 0; i < textSegments.length; i++) {1419StringBuilder oneseg = textSegments[i];1420segments[i] = (oneseg != null) ? oneseg.toString() : "";1421}14221423// get the argument number1424int argumentNumber;1425try {1426argumentNumber = Integer.parseInt(segments[SEG_INDEX]); // always unlocalized!1427} catch (NumberFormatException e) {1428throw new IllegalArgumentException("can't parse argument number: "1429+ segments[SEG_INDEX], e);1430}1431if (argumentNumber < 0) {1432throw new IllegalArgumentException("negative argument number: "1433+ argumentNumber);1434}14351436// resize format information arrays if necessary1437if (offsetNumber >= formats.length) {1438int newLength = formats.length * 2;1439Format[] newFormats = new Format[newLength];1440int[] newOffsets = new int[newLength];1441int[] newArgumentNumbers = new int[newLength];1442System.arraycopy(formats, 0, newFormats, 0, maxOffset + 1);1443System.arraycopy(offsets, 0, newOffsets, 0, maxOffset + 1);1444System.arraycopy(argumentNumbers, 0, newArgumentNumbers, 0, maxOffset + 1);1445formats = newFormats;1446offsets = newOffsets;1447argumentNumbers = newArgumentNumbers;1448}1449int oldMaxOffset = maxOffset;1450maxOffset = offsetNumber;1451offsets[offsetNumber] = segments[SEG_RAW].length();1452argumentNumbers[offsetNumber] = argumentNumber;14531454// now get the format1455Format newFormat = null;1456if (segments[SEG_TYPE].length() != 0) {1457int type = findKeyword(segments[SEG_TYPE], TYPE_KEYWORDS);1458switch (type) {1459case TYPE_NULL:1460// Type "" is allowed. e.g., "{0,}", "{0,,}", and "{0,,#}"1461// are treated as "{0}".1462break;14631464case TYPE_NUMBER:1465switch (findKeyword(segments[SEG_MODIFIER], NUMBER_MODIFIER_KEYWORDS)) {1466case MODIFIER_DEFAULT:1467newFormat = NumberFormat.getInstance(locale);1468break;1469case MODIFIER_CURRENCY:1470newFormat = NumberFormat.getCurrencyInstance(locale);1471break;1472case MODIFIER_PERCENT:1473newFormat = NumberFormat.getPercentInstance(locale);1474break;1475case MODIFIER_INTEGER:1476newFormat = NumberFormat.getIntegerInstance(locale);1477break;1478default: // DecimalFormat pattern1479try {1480newFormat = new DecimalFormat(segments[SEG_MODIFIER],1481DecimalFormatSymbols.getInstance(locale));1482} catch (IllegalArgumentException e) {1483maxOffset = oldMaxOffset;1484throw e;1485}1486break;1487}1488break;14891490case TYPE_DATE:1491case TYPE_TIME:1492int mod = findKeyword(segments[SEG_MODIFIER], DATE_TIME_MODIFIER_KEYWORDS);1493if (mod >= 0 && mod < DATE_TIME_MODIFIER_KEYWORDS.length) {1494if (type == TYPE_DATE) {1495newFormat = DateFormat.getDateInstance(DATE_TIME_MODIFIERS[mod],1496locale);1497} else {1498newFormat = DateFormat.getTimeInstance(DATE_TIME_MODIFIERS[mod],1499locale);1500}1501} else {1502// SimpleDateFormat pattern1503try {1504newFormat = new SimpleDateFormat(segments[SEG_MODIFIER], locale);1505} catch (IllegalArgumentException e) {1506maxOffset = oldMaxOffset;1507throw e;1508}1509}1510break;15111512case TYPE_CHOICE:1513try {1514// ChoiceFormat pattern1515newFormat = new ChoiceFormat(segments[SEG_MODIFIER]);1516} catch (Exception e) {1517maxOffset = oldMaxOffset;1518throw new IllegalArgumentException("Choice Pattern incorrect: "1519+ segments[SEG_MODIFIER], e);1520}1521break;15221523default:1524maxOffset = oldMaxOffset;1525throw new IllegalArgumentException("unknown format type: " +1526segments[SEG_TYPE]);1527}1528}1529formats[offsetNumber] = newFormat;1530}15311532private static final int findKeyword(String s, String[] list) {1533for (int i = 0; i < list.length; ++i) {1534if (s.equals(list[i]))1535return i;1536}15371538// Try trimmed lowercase.1539String ls = s.trim().toLowerCase(Locale.ROOT);1540if (ls != s) {1541for (int i = 0; i < list.length; ++i) {1542if (ls.equals(list[i]))1543return i;1544}1545}1546return -1;1547}15481549private static final void copyAndFixQuotes(String source, int start, int end,1550StringBuilder target) {1551boolean quoted = false;15521553for (int i = start; i < end; ++i) {1554char ch = source.charAt(i);1555if (ch == '{') {1556if (!quoted) {1557target.append('\'');1558quoted = true;1559}1560target.append(ch);1561} else if (ch == '\'') {1562target.append("''");1563} else {1564if (quoted) {1565target.append('\'');1566quoted = false;1567}1568target.append(ch);1569}1570}1571if (quoted) {1572target.append('\'');1573}1574}15751576/**1577* After reading an object from the input stream, do a simple verification1578* to maintain class invariants.1579* @throws InvalidObjectException if the objects read from the stream is invalid.1580*/1581private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {1582in.defaultReadObject();1583boolean isValid = maxOffset >= -11584&& formats.length > maxOffset1585&& offsets.length > maxOffset1586&& argumentNumbers.length > maxOffset;1587if (isValid) {1588int lastOffset = pattern.length() + 1;1589for (int i = maxOffset; i >= 0; --i) {1590if ((offsets[i] < 0) || (offsets[i] > lastOffset)) {1591isValid = false;1592break;1593} else {1594lastOffset = offsets[i];1595}1596}1597}1598if (!isValid) {1599throw new InvalidObjectException("Could not reconstruct MessageFormat from corrupt stream.");1600}1601}1602}160316041605