1594 lines · 66.6 KB · Java
RawDownload
/* * Copyright (c) 1996, 2010, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation.  Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ /* * (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. * * <h4><a name="patterns">Patterns and Their Interpretation</a></h4> * * <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. * * <p> * <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. * <p> * <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> * <p> * * <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> * form.applyPattern( *    "There {0,choice,0#are no files|1#is one file|1&lt;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 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     */    public 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 parts                        segments[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 segments                                segments[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 parts                        default:                            segments[part].append(ch);

Lines 1–500 of 1594.