/* ********************************************************************** * Copyright (c) 2004-2014, International Business Machines * Corporation and others. All Rights Reserved. ********************************************************************** * Author: Alan Liu * Created: April 6, 2004 * Since: ICU 3.0 ********************************************************************** */ package com.ibm.icu.simple; import java.io.IOException; import java.io.InvalidObjectException; import java.text.AttributedCharacterIterator; import java.text.AttributedCharacterIterator.Attribute; import java.text.AttributedString; import java.text.CharacterIterator; import java.text.ChoiceFormat; import java.text.DateFormat; import java.text.DecimalFormat; import java.text.DecimalFormatSymbols; import java.text.FieldPosition; import java.text.Format; import java.text.NumberFormat; import java.text.ParseException; import java.text.ParsePosition; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; import com.ibm.icu.impl.PatternProps; import com.ibm.icu.simple.PluralRules.PluralType; import com.ibm.icu.text.MessagePattern; import com.ibm.icu.text.MessagePattern.ArgType; import com.ibm.icu.text.MessagePattern.Part; import com.ibm.icu.text.SelectFormat; import com.ibm.icu.util.ICUUncheckedIOException; /** * {@icuenhanced java.text.MessageFormat}.{@icu _usage_} * *
MessageFormat prepares strings for display to users, * with optional arguments (variables/placeholders). * The arguments can occur in any order, which is necessary for translation * into languages with different grammars. * *
A MessageFormat is constructed from a pattern string * with arguments in {curly braces} which will be replaced by formatted values. * *
MessageFormat
differs from the other Format
* classes in that you create a MessageFormat
object with one
* of its constructors (not with a getInstance
style factory
* method). Factory methods aren't necessary because MessageFormat
* itself doesn't implement locale-specific behavior. Any locale-specific
* behavior is defined by the pattern that you provide and the
* subformats used for inserted arguments.
*
*
Arguments can be named (using identifiers) or numbered (using small ASCII-digit integers). * Some of the API methods work only with argument numbers and throw an exception * if the pattern has named arguments (see {@link #usesNamedArguments()}). * *
An argument might not specify any format type. In this case, * a Number value is formatted with a default (for the locale) NumberFormat, * a Date value is formatted with a default (for the locale) DateFormat, * and for any other value its toString() value is used. * *
An argument might specify a "simple" type for which the specified * Format object is created, cached and used. * *
An argument might have a "complex" type with nested MessageFormat sub-patterns. * During formatting, one of these sub-messages is selected according to the argument value * and recursively formatted. * *
After construction, a custom Format object can be set for * a top-level argument, overriding the default formatting and parsing behavior * for that argument. * However, custom formatting can be achieved more simply by writing * a typeless argument in the pattern string * and supplying it with a preformatted string value. * *
When formatting, MessageFormat takes a collection of argument values * and writes an output string. * The argument values may be passed as an array * (when the pattern contains only numbered arguments) * or as a Map (which works for both named and numbered arguments). * *
Each argument is matched with one of the input values by array index or map key * and formatted according to its pattern specification * (or using a custom Format object if one was set). * A numbered pattern argument is matched with a map key that contains that number * as an ASCII-decimal-digit string (without leading zero). * *
MessageFormat
uses patterns of the following form:
* * ** message = messageText (argument messageText)* * argument = noneArg | simpleArg | complexArg * complexArg = choiceArg | pluralArg | selectArg | selectordinalArg * * noneArg = '{' argNameOrNumber '}' * simpleArg = '{' argNameOrNumber ',' argType [',' argStyle] '}' * choiceArg = '{' argNameOrNumber ',' "choice" ',' choiceStyle '}' * pluralArg = '{' argNameOrNumber ',' "plural" ',' pluralStyle '}' * selectArg = '{' argNameOrNumber ',' "select" ',' selectStyle '}' * selectordinalArg = '{' argNameOrNumber ',' "selectordinal" ',' pluralStyle '}' * * choiceStyle: see {@link ChoiceFormat} * pluralStyle: see {@link PluralFormat} * selectStyle: see {@link SelectFormat} * * argNameOrNumber = argName | argNumber * argName = [^[[:Pattern_Syntax:][:Pattern_White_Space:]]]+ * argNumber = '0' | ('1'..'9' ('0'..'9')*) * * argType = "number" | "date" | "time" | "spellout" | "ordinal" | "duration" * argStyle = "short" | "medium" | "long" | "full" | "integer" | "currency" | "percent" | argStyleText *
Recommendation: Use the real apostrophe (single quote) character \u2019 for * human-readable text, and use the ASCII apostrophe (\u0027 ' ) * only in program syntax, like quoting in MessageFormat. * See the annotations for U+0027 Apostrophe in The Unicode Standard. * *
The choice
argument type is deprecated.
* Use plural
arguments for proper plural selection,
* and select
arguments for simple selection among a fixed set of choices.
*
*
The argType
and argStyle
values are used to create
* a Format
instance for the format element. The following
* table shows how the values map to Format instances. Combinations not
* shown in the table are illegal. Any argStyleText
must
* be a valid pattern string for the Format subclass used.
*
*
argType * | argStyle * | resulting Format object * | |||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
(none) * | null
* | ||||||||||||||||||||||
number
* | (none) * | NumberFormat.getInstance(getLocale())
* | |||||||||||||||||||||
integer
* | NumberFormat.getIntegerInstance(getLocale())
* | ||||||||||||||||||||||
currency
* | NumberFormat.getCurrencyInstance(getLocale())
* | ||||||||||||||||||||||
percent
* | NumberFormat.getPercentInstance(getLocale())
* | ||||||||||||||||||||||
argStyleText * | new DecimalFormat(argStyleText, new DecimalFormatSymbols(getLocale()))
* | ||||||||||||||||||||||
date
* | (none) * | DateFormat.getDateInstance(DateFormat.DEFAULT, getLocale())
* | |||||||||||||||||||||
short
* | DateFormat.getDateInstance(DateFormat.SHORT, getLocale())
* | ||||||||||||||||||||||
medium
* | DateFormat.getDateInstance(DateFormat.DEFAULT, getLocale())
* | ||||||||||||||||||||||
long
* | DateFormat.getDateInstance(DateFormat.LONG, getLocale())
* | ||||||||||||||||||||||
full
* | DateFormat.getDateInstance(DateFormat.FULL, getLocale())
* | ||||||||||||||||||||||
argStyleText * | new SimpleDateFormat(argStyleText, getLocale())
* |
* *
The ICU MessageFormat supports both named and numbered arguments, * while the JDK MessageFormat only supports numbered arguments. * Named arguments make patterns more readable. * *
ICU implements a more user-friendly apostrophe quoting syntax.
* In message text, an apostrophe only begins quoting literal text
* if it immediately precedes a syntax character (mostly {curly braces}).
* In the JDK MessageFormat, an apostrophe always begins quoting,
* which requires common text like "don't" and "aujourd'hui"
* to be written with doubled apostrophes like "don''t" and "aujourd''hui".
* For more details see {@link MessagePattern.ApostropheMode}.
*
*
ICU does not create a ChoiceFormat object for a choiceArg, pluralArg or selectArg
* but rather handles such arguments itself.
* The JDK MessageFormat does create and use a ChoiceFormat object
* (new ChoiceFormat(argStyleText)
).
* The JDK does not support plural and select arguments at all.
*
*
Here are some examples of usage: *
** Typically, the message format will come from resources, and the * arguments will be dynamically set at runtime. * ** Object[] arguments = { * 7, * new Date(System.currentTimeMillis()), * "a disturbance in the Force" * }; * * String result = MessageFormat.format( * "At {1,time} on {1,date}, there was {2} on planet {0,number,integer}.", * arguments); * * output: At 12:30 PM on Jul 3, 2053, there was a disturbance * in the Force on planet 7. * **
Example 2: *
** ** Object[] testArgs = { 3, "MyDisk" }; * * MessageFormat form = new MessageFormat( * "The disk \"{1}\" contains {0} file(s)."); * * System.out.println(form.format(testArgs)); * * // output, with different testArgs * output: The disk "MyDisk" contains 0 file(s). * output: The disk "MyDisk" contains 1 file(s). * output: The disk "MyDisk" contains 1,273 file(s). **
For messages that include plural forms, you can use a plural argument: *
* MessageFormat msgFmt = new MessageFormat( * "{num_files, plural, " + * "=0{There are no files on disk \"{disk_name}\".}" + * "=1{There is one file on disk \"{disk_name}\".}" + * "other{There are # files on disk \"{disk_name}\".}}", * ULocale.ENGLISH); * Map args = new HashMap(); * args.put("num_files", 0); * args.put("disk_name", "MyDisk"); * System.out.println(msgFmt.format(args)); * args.put("num_files", 3); * System.out.println(msgFmt.format(args)); * * output: * There are no files on disk "MyDisk". * There are 3 files on "MyDisk". ** See {@link PluralFormat} and {@link PluralRules} for details. * *
MessageFormats 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 ChoiceFormat
* @see PluralFormat
* @see SelectFormat
* @author Mark Davis
* @author Markus Scherer
* @stable ICU 3.0
*/
public class MessageFormat extends Format {
// Incremented by 1 for ICU 4.8's new format.
static final long serialVersionUID = 7136212545847378652L;
/**
* Formats a message pattern string with a variable number of name/value pair arguments.
* Creates an ICU MessageFormat for the locale and pattern,
* and formats with the arguments.
*
* @param locale Locale for number formatting and plural selection etc.
* @param msg an ICU-MessageFormat-syntax string
* @param nameValuePairs (argument name, argument value) pairs
*/
public static final String formatNamedArgs(Locale locale, String msg, Object... nameValuePairs) {
StringBuilder result = new StringBuilder(msg.length());
new MessageFormat(msg, locale).format(0, null, null, null, nameValuePairs,
new AppendableWrapper(result), null);
return result.toString();
}
/**
* Constructs a MessageFormat for the default FORMAT
locale and the
* specified pattern.
* Sets the locale and calls applyPattern(pattern).
*
* @param pattern the pattern for this message format
* @exception IllegalArgumentException if the pattern is invalid
* @see Category#FORMAT
* @stable ICU 3.0
*/
public MessageFormat(String pattern) {
locale_ = Locale.getDefault(); // Category.FORMAT
applyPattern(pattern);
}
/**
* Constructs a MessageFormat for the specified locale and
* pattern.
* Sets the locale and calls applyPattern(pattern).
*
* @param pattern the pattern for this message format
* @param locale the locale for this message format
* @exception IllegalArgumentException if the pattern is invalid
* @stable ICU 3.0
*/
public MessageFormat(String pattern, Locale locale) {
locale_ = locale;
applyPattern(pattern);
}
/**
* Returns the locale that's used when creating or comparing subformats.
*
* @return the locale used when creating or comparing subformats
* @stable ICU 3.0
*/
public Locale getLocale() {
return locale_;
}
/**
* Sets the pattern used by this message format.
* Parses the pattern and caches Format objects for simple argument types.
* Patterns and their interpretation are specified in the
* class description.
*
* @param pttrn the pattern for this message format
* @throws IllegalArgumentException if the pattern is invalid
* @stable ICU 3.0
*/
public void applyPattern(String pttrn) {
try {
if (msgPattern == null) {
msgPattern = new MessagePattern(pttrn);
} else {
msgPattern.parse(pttrn);
}
// Cache the formats that are explicitly mentioned in the message pattern.
cacheExplicitFormats();
} catch(RuntimeException e) {
resetPattern();
throw e;
}
}
/**
* {@icu} Sets the ApostropheMode and the pattern used by this message format.
* Parses the pattern and caches Format objects for simple argument types.
* Patterns and their interpretation are specified in the
* class description.
*
* This method is best used only once on a given object to avoid confusion about the mode,
* and after constructing the object with an empty pattern string to minimize overhead.
*
* @param pattern the pattern for this message format
* @param aposMode the new ApostropheMode
* @throws IllegalArgumentException if the pattern is invalid
* @see MessagePattern.ApostropheMode
* @stable ICU 4.8
*/
public void applyPattern(String pattern, MessagePattern.ApostropheMode aposMode) {
if (msgPattern == null) {
msgPattern = new MessagePattern(aposMode);
} else if (aposMode != msgPattern.getApostropheMode()) {
msgPattern.clearPatternAndSetApostropheMode(aposMode);
}
applyPattern(pattern);
}
/**
* {@icu}
* @return this instance's ApostropheMode.
* @stable ICU 4.8
*/
public MessagePattern.ApostropheMode getApostropheMode() {
if (msgPattern == null) {
msgPattern = new MessagePattern(); // Sets the default mode.
}
return msgPattern.getApostropheMode();
}
/**
* Returns the applied pattern string.
* @return the pattern string
* @throws IllegalStateException after custom Format objects have been set
* via setFormat() or similar APIs
* @stable ICU 3.0
*/
public String toPattern() {
// Return the original, applied pattern string, or else "".
// Note: This does not take into account
// - changes from setFormat() and similar methods, or
// - normalization of apostrophes and arguments, for example,
// whether some date/time/number formatter was created via a pattern
// but is equivalent to the "medium" default format.
if (customFormatArgStarts != null) {
throw new IllegalStateException(
"toPattern() is not supported after custom Format objects "+
"have been set via setFormat() or similar APIs");
}
if (msgPattern == null) {
return "";
}
String originalPattern = msgPattern.getPatternString();
return originalPattern == null ? "" : originalPattern;
}
/**
* Returns the part index of the next ARG_START after partIndex, or -1 if there is none more.
* @param partIndex Part index of the previous ARG_START (initially 0).
*/
private int nextTopLevelArgStart(int partIndex) {
if (partIndex != 0) {
partIndex = msgPattern.getLimitPartIndex(partIndex);
}
for (;;) {
MessagePattern.Part.Type type = msgPattern.getPartType(++partIndex);
if (type == MessagePattern.Part.Type.ARG_START) {
return partIndex;
}
if (type == MessagePattern.Part.Type.MSG_LIMIT) {
return -1;
}
}
}
private boolean argNameMatches(int partIndex, String argName, int argNumber) {
Part part = msgPattern.getPart(partIndex);
return part.getType() == MessagePattern.Part.Type.ARG_NAME ?
msgPattern.partSubstringMatches(part, argName) :
part.getValue() == argNumber; // ARG_NUMBER
}
private String getArgName(int partIndex) {
Part part = msgPattern.getPart(partIndex);
if (part.getType() == MessagePattern.Part.Type.ARG_NAME) {
return msgPattern.getSubstring(part);
} else {
return Integer.toString(part.getValue());
}
}
/**
* Sets the Format objects to use for the values passed into
* format
methods or returned from parse
* methods. The indices of elements in newFormats
* correspond to the argument indices used in the previously set
* pattern string.
* The order of formats in newFormats
thus corresponds to
* the order of elements in the arguments
array passed
* to the format
methods or the result array returned
* by the parse
methods.
*
* 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 newFormats.length
are replaced.
*
* This method is only supported if the format does not use
* named arguments, otherwise an IllegalArgumentException is thrown.
*
* @param newFormats the new formats to use
* @throws NullPointerException if newFormats
is null
* @throws IllegalArgumentException if this formatter uses named arguments
* @stable ICU 3.0
*/
public void setFormatsByArgumentIndex(Format[] newFormats) {
if (msgPattern.hasNamedArguments()) {
throw new IllegalArgumentException(
"This method is not available in MessageFormat objects " +
"that use alphanumeric argument names.");
}
for (int partIndex = 0; (partIndex = nextTopLevelArgStart(partIndex)) >= 0;) {
int argNumber = msgPattern.getPart(partIndex + 1).getValue();
if (argNumber < newFormats.length) {
setCustomArgStartFormat(partIndex, newFormats[argNumber]);
}
}
}
/**
* {@icu} Sets the Format objects to use for the values passed into
* format
methods or returned from parse
* methods. The keys in newFormats
are the argument
* names in the previously set pattern string, and the values
* are the formats.
*
* Only argument names from the pattern string are considered.
* Extra keys in newFormats
that do not correspond
* to an argument name are ignored. Similarly, if there is no
* format in newFormats for an argument name, the formatter
* for that argument remains unchanged.
*
* This may be called on formats that do not use named arguments.
* In this case the map will be queried for key Strings that
* represent argument indices, e.g. "0", "1", "2" etc.
*
* @param newFormats a map from String to Format providing new
* formats for named arguments.
* @stable ICU 3.8
*/
public void setFormatsByArgumentName(Map
* 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
* 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
* 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.
*
* This method is only supported when exclusively numbers are used for
* argument names. Otherwise an IllegalArgumentException is thrown.
*
* @param argumentIndex the argument index for which to use the new format
* @param newFormat the new format to use
* @throws IllegalArgumentException if this format uses named arguments
* @stable ICU 3.0
*/
public void setFormatByArgumentIndex(int argumentIndex, Format newFormat) {
if (msgPattern.hasNamedArguments()) {
throw new IllegalArgumentException(
"This method is not available in MessageFormat objects " +
"that use alphanumeric argument names.");
}
for (int partIndex = 0; (partIndex = nextTopLevelArgStart(partIndex)) >= 0;) {
if (msgPattern.getPart(partIndex + 1).getValue() == argumentIndex) {
setCustomArgStartFormat(partIndex, newFormat);
}
}
}
/**
* {@icu} Sets the Format object to use for the format elements within the
* previously set pattern string that use the given argument
* name.
*
* If the argument name 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 name is not used for any format
* element in the pattern string, then the new format is ignored.
*
* This API may be used on formats that do not use named arguments.
* In this case
* 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 formatElementIndex is equal to or
* larger than the number of format elements in the pattern string
* @stable ICU 3.0
*/
public void setFormat(int formatElementIndex, Format newFormat) {
int formatNumber = 0;
for (int partIndex = 0; (partIndex = nextTopLevelArgStart(partIndex)) >= 0;) {
if (formatNumber == formatElementIndex) {
setCustomArgStartFormat(partIndex, newFormat);
return;
}
++formatNumber;
}
throw new ArrayIndexOutOfBoundsException(formatElementIndex);
}
/**
* Returns the Format objects used for the values passed into
*
* 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.
*
* This method is only supported when exclusively numbers are used for
* argument names. Otherwise an IllegalArgumentException is thrown.
*
* @return the formats used for the arguments within the pattern
* @throws IllegalArgumentException if this format uses named arguments
* @stable ICU 3.0
*/
public Format[] getFormatsByArgumentIndex() {
if (msgPattern.hasNamedArguments()) {
throw new IllegalArgumentException(
"This method is not available in MessageFormat objects " +
"that use alphanumeric argument names.");
}
ArrayList
* Since the order of format elements in a pattern string often
* changes during localization, it's generally better to use the
* {@link #getFormatsByArgumentIndex()}
* method, which assumes an order of formats corresponding to the
* order of elements in the
* The text substituted for the individual format elements is derived from
* the current subformat of the format element and the
*
*
* If
* The text substituted for the individual format elements is derived from
* the current subformat of the format element and the
*
* A numbered pattern argument is matched with a map key that contains that number
* as an ASCII-decimal-digit string (without leading zero).
*
* An argument is unavailable if
* @throws IllegalArgumentException if the pattern is invalid
* @throws IllegalArgumentException if a value in the
*
* The text of the returned
* In addition, the
* The attributes/value from the underlying Caveats: The parse may fail in a number of circumstances.
* For example:
*
* See the {@link #parse(String, ParsePosition)} method for more information
* on message parsing.
*
* @param source A See the {@link #parse(String, ParsePosition)} method for more information on
* message parsing.
*
* @param source A
* The method attempts to parse text starting at the index given by
*
* See the {@link #parse(String, ParsePosition)} method for more information
* on message parsing.
*
* @param source A Exactly one of args and argsMap must be null, the other non-null.
*
* @param msgStart Index to msgPattern part to start formatting from.
* @param pluralNumber null except when formatting a plural argument sub-message
* where a '#' is replaced by the format string for this number.
* @param args The formattable objects array. Non-null iff numbered values are used.
* @param argsMap The key-value map of formattable objects. Non-null iff named values are used.
* @param dest Output parameter to receive the result.
* The result (string & attributes) is appended to existing contents.
* @param fp Field position status.
*/
private void format(int msgStart, PluralSelectorContext pluralNumber,
Object[] args, Map See the class description for more about apostrophes and quoting,
* and differences between ICU and the JDK.
*
* The JDK MessageFormat and ICU 4.6 and earlier MessageFormat
* treat all ASCII apostrophes as
* quotes, which is problematic in some languages, e.g.
* French, where apostrophe is commonly used. This utility
* assumes that only an unpaired apostrophe immediately before
* a brace is a true quote. Other unpaired apostrophes are paired,
* and the resulting standard pattern string is returned.
*
* Note: It is not guaranteed that the returned pattern
* is indeed a valid pattern. The only effect is to convert
* between patterns having different quoting semantics.
*
* Note: This method only works on top-level messageText,
* not messageText nested inside a complexArg.
*
* @param pattern the 'apostrophe-friendly' pattern to convert
* @return the standard equivalent of the original pattern
* @stable ICU 3.4
*/
public static String autoQuoteApostrophe(String pattern) {
StringBuilder buf = new StringBuilder(pattern.length() * 2);
int state = STATE_INITIAL;
int braceCount = 0;
for (int i = 0, j = pattern.length(); i < j; ++i) {
char c = pattern.charAt(i);
switch (state) {
case STATE_INITIAL:
switch (c) {
case SINGLE_QUOTE:
state = STATE_SINGLE_QUOTE;
break;
case CURLY_BRACE_LEFT:
state = STATE_MSG_ELEMENT;
++braceCount;
break;
}
break;
case STATE_SINGLE_QUOTE:
switch (c) {
case SINGLE_QUOTE:
state = STATE_INITIAL;
break;
case CURLY_BRACE_LEFT:
case CURLY_BRACE_RIGHT:
state = STATE_IN_QUOTE;
break;
default:
buf.append(SINGLE_QUOTE);
state = STATE_INITIAL;
break;
}
break;
case STATE_IN_QUOTE:
switch (c) {
case SINGLE_QUOTE:
state = STATE_INITIAL;
break;
}
break;
case STATE_MSG_ELEMENT:
switch (c) {
case CURLY_BRACE_LEFT:
++braceCount;
break;
case CURLY_BRACE_RIGHT:
if (--braceCount == 0) {
state = STATE_INITIAL;
}
break;
}
break;
///CLOVER:OFF
default: // Never happens.
break;
///CLOVER:ON
}
buf.append(c);
}
// End of scan
if (state == STATE_SINGLE_QUOTE || state == STATE_IN_QUOTE) {
buf.append(SINGLE_QUOTE);
}
return new String(buf);
}
/**
* Convenience wrapper for Appendable, tracks the result string length.
* Also, Appendable throws IOException, and we turn that into a RuntimeException
* so that we need no throws clauses.
*/
private static final class AppendableWrapper {
public AppendableWrapper(StringBuilder sb) {
app = sb;
length = sb.length();
attributes = null;
}
public AppendableWrapper(StringBuffer sb) {
app = sb;
length = sb.length();
attributes = null;
}
public void useAttributes() {
attributes = new ArrayListnewFormats
corresponds to
* the order of format elements in the pattern string.
* newFormats.length
* formats are replaced.
* arguments
array passed to
* the format
methods or the result array returned by
* the parse
methods.
*
* @param newFormats the new formats to use
* @exception NullPointerException if newFormats
is null
* @stable ICU 3.0
*/
public void setFormats(Format[] newFormats) {
int formatNumber = 0;
for (int partIndex = 0;
formatNumber < newFormats.length &&
(partIndex = nextTopLevelArgStart(partIndex)) >= 0;) {
setCustomArgStartFormat(partIndex, newFormats[formatNumber]);
++formatNumber;
}
}
/**
* Sets the Format object 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 arguments
array passed
* to the format
methods or the result array returned
* by the parse
methods.
* argumentName
should be a String that names
* an argument index, e.g. "0", "1", "2"... etc. If it does not name
* a valid index, the format will be ignored. No error is thrown.
*
* @param argumentName the name of the argument to change
* @param newFormat the new format to use
* @stable ICU 3.8
*/
public void setFormatByArgumentName(String argumentName, Format newFormat) {
int argNumber = MessagePattern.validateArgumentName(argumentName);
if (argNumber < MessagePattern.ARG_NAME_NOT_NUMBER) {
return;
}
for (int partIndex = 0; (partIndex = nextTopLevelArgStart(partIndex)) >= 0;) {
if (argNameMatches(partIndex + 1, argumentName, argNumber)) {
setCustomArgStartFormat(partIndex, newFormat);
}
}
}
/**
* Sets the Format object 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.
* format
methods or returned from parse
* 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 arguments
array passed
* to the format
methods or the result array returned
* by the parse
methods.
* arguments
array passed to
* the format
methods or the result array returned by
* the parse
methods.
*
* This method is only supported when exclusively numbers are used for
* argument names. Otherwise an IllegalArgumentException is thrown.
*
* @return the formats used for the format elements in the pattern
* @throws IllegalArgumentException if this format uses named arguments
* @stable ICU 3.0
*/
public Format[] getFormats() {
ArrayListMessageFormat
's
* pattern, with arguments replaced by the formatted objects, to the
* provided StringBuffer
.
* arguments
element at the format element's argument index
* as indicated by the first matching line of the following table. An
* argument is unavailable if arguments
is
* null
or has fewer than argumentIndex+1 elements. When
* an argument is unavailable no substitution is performed.
*
*
*
* argType or Format
* value object
* Formatted Text
*
* any
* unavailable
* "{" + argNameOrNumber + "}"
*
* any
* null
* "null"
*
* custom Format != null
* any
* customFormat.format(argument)
*
* noneArg, or custom Format == null
* instanceof Number
* NumberFormat.getInstance(getLocale()).format(argument)
*
* noneArg, or custom Format == null
* instanceof Date
* DateFormat.getDateTimeInstance(DateFormat.SHORT,
* DateFormat.SHORT, getLocale()).format(argument)
*
* noneArg, or custom Format == null
* instanceof String
* argument
*
* noneArg, or custom Format == null
* any
* argument.toString()
*
* complexArg
* any
* result of recursive formatting of a selected sub-message
* pos
is non-null, and refers to
* Field.ARGUMENT
, the location of the first formatted
* string will be returned.
*
* This method is only supported when the format does not use named
* arguments, otherwise an IllegalArgumentException is thrown.
*
* @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.
* @throws IllegalArgumentException if a value in the
* arguments
array is not of the type
* expected by the corresponding argument or custom Format object.
* @throws IllegalArgumentException if this format uses named arguments
* @stable ICU 3.0
*/
public final StringBuffer format(Object[] arguments, StringBuffer result,
FieldPosition pos)
{
format(arguments, null, new AppendableWrapper(result), pos);
return result;
}
/**
* Formats a map of objects and appends the MessageFormat
's
* pattern, with arguments replaced by the formatted objects, to the
* provided StringBuffer
.
* arguments
value corresopnding to the format element's
* argument name.
* arguments
is
* null
or does not have a value corresponding to an argument
* name in the pattern. When an argument is unavailable no substitution
* is performed.
*
* @param arguments a map 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.
* @throws IllegalArgumentException if a value in the
* arguments
array is not of the type
* expected by the corresponding argument or custom Format object.
* @return the passed-in StringBuffer
* @stable ICU 3.8
*/
public final StringBuffer format(Map
*
*
* @throws IllegalArgumentException if the pattern is invalid
* @throws IllegalArgumentException if a value in the
* (new {@link #MessageFormat(String) MessageFormat}(pattern)).{@link
* #format(java.lang.Object[], java.lang.StringBuffer, java.text.FieldPosition)
* format}(arguments, new StringBuffer(), null).toString()
* arguments
array is not of the type
* expected by the corresponding argument or custom Format object.
* @throws IllegalArgumentException if this format uses named arguments
* @stable ICU 3.0
*/
public static String format(String pattern, Object... arguments) {
MessageFormat temp = new MessageFormat(pattern);
return temp.format(arguments);
}
/**
* Creates a MessageFormat with the given pattern and uses it to
* format the given arguments. The pattern must identifyarguments
* by name instead of by number.
* arguments
array is not of the type
* expected by the corresponding argument or custom Format object.
* @see #format(Map, StringBuffer, FieldPosition)
* @see #format(String, Object[])
* @stable ICU 3.8
*/
public static String format(String pattern, MapMessageFormat
's
* pattern, with format elements replaced by the formatted objects, to the
* provided StringBuffer
.
* This is equivalent to either of
*
*
* A map must be provided if this format uses named arguments, otherwise
* an IllegalArgumentException will be thrown.
* @param arguments a map or array of objects to be formatted
* @param result where text is appended
* @param pos On input: an alignment field, if desired
* On output: the offsets of the alignment field
* @throws IllegalArgumentException if an argument in
* {@link #format(java.lang.Object[], java.lang.StringBuffer,
* java.text.FieldPosition) format}((Object[]) arguments, result, pos)
* {@link #format(java.util.Map, java.lang.StringBuffer,
* java.text.FieldPosition) format}((Map) arguments, result, pos)
* arguments
is not of the type
* expected by the format element(s) that use it
* @throws IllegalArgumentException if arguments
is
* an array of Object and this format uses named arguments
* @stable ICU 3.0
*/
public final StringBuffer format(Object arguments, StringBuffer result,
FieldPosition pos)
{
format(arguments, new AppendableWrapper(result), pos);
return result;
}
/**
* Formats an array of objects and inserts them into the
*
MessageFormat
's pattern, producing an
* AttributedCharacterIterator
.
* You can use the returned AttributedCharacterIterator
* to build the resulting String, as well as to determine information
* about the resulting String.
* AttributedCharacterIterator
is
* the same that would be returned by
*
*
* {@link #format(java.lang.Object[], java.lang.StringBuffer,
* java.text.FieldPosition) format}(arguments, new StringBuffer(), null).toString()
* AttributedCharacterIterator
contains at
* least attributes indicating where text was generated from an
* argument in the arguments
array. The keys of these attributes are of
* type MessageFormat.Field
, their values are
* Integer
objects indicating the index in the arguments
* array of the argument from which the text was generated.
* Format
* instances that MessageFormat
uses will also be
* placed in the resulting AttributedCharacterIterator
.
* 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 arguments
is null.
* @throws IllegalArgumentException if a value in the
* arguments
array is not of the type
* expected by the corresponding argument or custom Format object.
* @stable ICU 3.8
*/
public AttributedCharacterIterator formatToCharacterIterator(Object arguments) {
if (arguments == null) {
throw new NullPointerException(
"formatToCharacterIterator must be passed non-null object");
}
StringBuilder result = new StringBuilder();
AppendableWrapper wrapper = new AppendableWrapper(result);
wrapper.useAttributes();
format(arguments, wrapper, null);
AttributedString as = new AttributedString(result.toString());
for (AttributeAndPosition a : wrapper.attributes) {
as.addAttribute(a.key, a.value, a.start, a.limit);
}
return as.getIterator();
}
/**
* Parses the string.
*
*
*
* When the parse fails, use ParsePosition.getErrorIndex() to find out
* where in the string did 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.
*
* @throws IllegalArgumentException if this format uses named arguments
* @stable ICU 3.0
*/
public Object[] parse(String source, ParsePosition pos) {
if (msgPattern.hasNamedArguments()) {
throw new IllegalArgumentException(
"This method is not available in MessageFormat objects " +
"that use named argument.");
}
// Count how many slots we need in the array.
int maxArgId = -1;
for (int partIndex = 0; (partIndex = nextTopLevelArgStart(partIndex)) >= 0;) {
int argNumber=msgPattern.getPart(partIndex + 1).getValue();
if (argNumber > maxArgId) {
maxArgId = argNumber;
}
}
Object[] resultArray = new Object[maxArgId + 1];
int backupStartPos = pos.getIndex();
parse(0, source, pos, resultArray, null);
if (pos.getIndex() == backupStartPos) { // unchanged, returned object is null
return null;
}
return resultArray;
}
/**
* {@icu} Parses the string, returning the results in a Map.
* This is similar to the version that returns an array
* of Object. This supports both named and numbered
* arguments-- if numbered, the keys in the map are the
* corresponding ASCII-decimal-digit strings (e.g. "0", "1", "2"...).
*
* @param source the text to parse
* @param pos the position at which to start parsing. on return,
* contains the result of the parse.
* @return a Map containing key/value pairs for each parsed argument.
* @stable ICU 3.8
*/
public MapString
whose beginning should be parsed.
* @return An Object
array parsed from the string.
* @exception ParseException if the beginning of the specified string cannot be parsed.
* @exception IllegalArgumentException if this format uses named arguments
* @stable ICU 3.0
*/
public Object[] parse(String source) throws ParseException {
ParsePosition pos = new ParsePosition(0);
Object[] result = parse(source, pos);
if (pos.getIndex() == 0) // unchanged, returned object is null
throw new ParseException("MessageFormat parse error!",
pos.getErrorIndex());
return result;
}
/**
* Parses the string, filling either the Map or the Array.
* This is a private method that all the public parsing methods call.
* This supports both named and numbered
* arguments-- if numbered, the keys in the map are the
* corresponding ASCII-decimal-digit strings (e.g. "0", "1", "2"...).
*
* @param msgStart index in the message pattern to start from.
* @param source the text to parse
* @param pos the position at which to start parsing. on return,
* contains the result of the parse.
* @param args if not null, the parse results will be filled here (The pattern
* has to have numbered arguments in order for this to not be null).
* @param argsMap if not null, the parse results will be filled here.
*/
private void parse(int msgStart, String source, ParsePosition pos,
Object[] args, MapString
whose beginning should be parsed.
* @return A Map
parsed from the string.
* @throws ParseException if the beginning of the specified string cannot
* be parsed.
* @see #parseToMap(String, ParsePosition)
* @stable ICU 3.8
*/
public Mappos
.
* If parsing succeeds, then the index of pos
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 pos
can be used to
* indicate the starting point for the next call to this method.
* If an error occurs, then the index of pos
is not
* changed, the error index of pos
is set to the index of
* the character where the error occurred, and null is returned.
* String
, part of which should be parsed.
* @param pos A ParsePosition
object with index and error
* index information as described above.
* @return An Object
parsed from the string, either an
* array of Object, or a Map, depending on whether named
* arguments are used. This can be queried using usesNamedArguments
.
* In case of error, returns null.
* @throws NullPointerException if pos
is null.
* @stable ICU 3.0
*/
public Object parseObject(String source, ParsePosition pos) {
if (!msgPattern.hasNamedArguments()) {
return parse(source, pos);
} else {
return parseToMap(source, pos);
}
}
/**
* {@inheritDoc}
* @stable ICU 3.0
@Override
public boolean equals(Object obj) {
if (this == obj) // quick check
return true;
if (obj == null || getClass() != obj.getClass())
return false;
MessageFormat other = (MessageFormat) obj;
return Utility.objectEquals(ulocale, other.ulocale)
&& Utility.objectEquals(msgPattern, other.msgPattern)
&& Utility.objectEquals(cachedFormatters, other.cachedFormatters)
&& Utility.objectEquals(customFormatArgStarts, other.customFormatArgStarts);
// Note: It might suffice to only compare custom formatters
// rather than all formatters.
}
*/
/**
* {@inheritDoc}
* @stable ICU 3.0
*/
@Override
public int hashCode() {
return msgPattern.getPatternString().hashCode(); // enough for reasonable distribution
}
/**
* Defines constants that are used as attribute keys in the
* AttributedCharacterIterator
returned
* from MessageFormat.formatToCharacterIterator
.
*
* @stable ICU 3.8
*/
public static class Field extends Format.Field {
private static final long serialVersionUID = 7510380454602616157L;
/**
* Create a Field
with the specified name.
*
* @param name The name of the attribute
*
* @stable ICU 3.8
*/
protected Field(String name) {
super(name);
}
/**
* Resolves instances being deserialized to the predefined constants.
*
* @return resolved MessageFormat.Field constant
* @throws InvalidObjectException if the constant could not be resolved.
*
* @stable ICU 3.8
*/
protected Object readResolve() throws InvalidObjectException {
if (this.getClass() != MessageFormat.Field.class) {
throw new InvalidObjectException(
"A subclass of MessageFormat.Field must implement readResolve.");
}
if (this.getName().equals(ARGUMENT.getName())) {
return ARGUMENT;
} else {
throw new InvalidObjectException("Unknown attribute name.");
}
}
/**
* Constant identifying a portion of a message that was generated
* from an argument passed into formatToCharacterIterator
.
* The value associated with the key will be an Integer
* indicating the index in the arguments
array of the
* argument from which the text was generated.
*
* @stable ICU 3.8
*/
public static final Field ARGUMENT = new Field("message argument field");
}
// ===========================privates============================
// *Important*: All fields must be declared *transient* so that we can fully
// control serialization!
// See for example Joshua Bloch's "Effective Java", chapter 10 Serialization.
/**
* The locale to use for formatting numbers and dates.
*/
private transient Locale locale_;
/**
* The MessagePattern which contains the parsed structure of the pattern string.
*/
private transient MessagePattern msgPattern;
/**
* Cached formatters so we can just use them whenever needed instead of creating
* them from scratch every time.
*/
private transient Maparguments
map is not of the type
* expected by the format element(s) that use it.
*/
private void format(Object[] arguments, Map