2 **********************************************************************
3 * Copyright (c) 2004-2013, International Business Machines
4 * Corporation and others. All Rights Reserved.
5 **********************************************************************
7 * Created: April 6, 2004
9 **********************************************************************
11 package com.ibm.icu.text;
13 import java.io.IOException;
14 import java.io.InvalidObjectException;
15 import java.io.ObjectInputStream;
16 import java.text.AttributedCharacterIterator;
17 import java.text.AttributedCharacterIterator.Attribute;
18 import java.text.AttributedString;
19 import java.text.CharacterIterator;
20 import java.text.ChoiceFormat;
21 import java.text.FieldPosition;
22 import java.text.Format;
23 import java.text.ParseException;
24 import java.text.ParsePosition;
25 import java.util.ArrayList;
26 import java.util.Date;
27 import java.util.HashMap;
28 import java.util.HashSet;
29 import java.util.Iterator;
30 import java.util.List;
31 import java.util.Locale;
35 import com.ibm.icu.impl.PatternProps;
36 import com.ibm.icu.impl.Utility;
37 import com.ibm.icu.text.MessagePattern.ArgType;
38 import com.ibm.icu.text.MessagePattern.Part;
39 import com.ibm.icu.text.PluralRules.FixedDecimal;
40 import com.ibm.icu.text.PluralRules.PluralType;
41 import com.ibm.icu.util.ULocale;
42 import com.ibm.icu.util.ULocale.Category;
45 * {@icuenhanced java.text.MessageFormat}.{@icu _usage_}
47 * <p>MessageFormat prepares strings for display to users,
48 * with optional arguments (variables/placeholders).
49 * The arguments can occur in any order, which is necessary for translation
50 * into languages with different grammars.
52 * <p>A MessageFormat is constructed from a <em>pattern</em> string
53 * with arguments in {curly braces} which will be replaced by formatted values.
55 * <p><code>MessageFormat</code> differs from the other <code>Format</code>
56 * classes in that you create a <code>MessageFormat</code> object with one
57 * of its constructors (not with a <code>getInstance</code> style factory
58 * method). Factory methods aren't necessary because <code>MessageFormat</code>
59 * itself doesn't implement locale-specific behavior. Any locale-specific
60 * behavior is defined by the pattern that you provide and the
61 * subformats used for inserted arguments.
63 * <p>Arguments can be named (using identifiers) or numbered (using small ASCII-digit integers).
64 * Some of the API methods work only with argument numbers and throw an exception
65 * if the pattern has named arguments (see {@link #usesNamedArguments()}).
67 * <p>An argument might not specify any format type. In this case,
68 * a Number value is formatted with a default (for the locale) NumberFormat,
69 * a Date value is formatted with a default (for the locale) DateFormat,
70 * and for any other value its toString() value is used.
72 * <p>An argument might specify a "simple" type for which the specified
73 * Format object is created, cached and used.
75 * <p>An argument might have a "complex" type with nested MessageFormat sub-patterns.
76 * During formatting, one of these sub-messages is selected according to the argument value
77 * and recursively formatted.
79 * <p>After construction, a custom Format object can be set for
80 * a top-level argument, overriding the default formatting and parsing behavior
82 * However, custom formatting can be achieved more simply by writing
83 * a typeless argument in the pattern string
84 * and supplying it with a preformatted string value.
86 * <p>When formatting, MessageFormat takes a collection of argument values
87 * and writes an output string.
88 * The argument values may be passed as an array
89 * (when the pattern contains only numbered arguments)
90 * or as a Map (which works for both named and numbered arguments).
92 * <p>Each argument is matched with one of the input values by array index or map key
93 * and formatted according to its pattern specification
94 * (or using a custom Format object if one was set).
95 * A numbered pattern argument is matched with a map key that contains that number
96 * as an ASCII-decimal-digit string (without leading zero).
98 * <h4><a name="patterns">Patterns and Their Interpretation</a></h4>
100 * <code>MessageFormat</code> uses patterns of the following form:
102 * message = messageText (argument messageText)*
103 * argument = noneArg | simpleArg | complexArg
104 * complexArg = choiceArg | pluralArg | selectArg | selectordinalArg
106 * noneArg = '{' argNameOrNumber '}'
107 * simpleArg = '{' argNameOrNumber ',' argType [',' argStyle] '}'
108 * choiceArg = '{' argNameOrNumber ',' "choice" ',' choiceStyle '}'
109 * pluralArg = '{' argNameOrNumber ',' "plural" ',' pluralStyle '}'
110 * selectArg = '{' argNameOrNumber ',' "select" ',' selectStyle '}'
111 * selectordinalArg = '{' argNameOrNumber ',' "selectordinal" ',' pluralStyle '}'
113 * choiceStyle: see {@link ChoiceFormat}
114 * pluralStyle: see {@link PluralFormat}
115 * selectStyle: see {@link SelectFormat}
117 * argNameOrNumber = argName | argNumber
118 * argName = [^[[:Pattern_Syntax:][:Pattern_White_Space:]]]+
119 * argNumber = '0' | ('1'..'9' ('0'..'9')*)
121 * argType = "number" | "date" | "time" | "spellout" | "ordinal" | "duration"
122 * argStyle = "short" | "medium" | "long" | "full" | "integer" | "currency" | "percent" | argStyleText
123 * </pre></blockquote>
126 * <li>messageText can contain quoted literal strings including syntax characters.
127 * A quoted literal string begins with an ASCII apostrophe and a syntax character
128 * (usually a {curly brace}) and continues until the next single apostrophe.
129 * A double ASCII apostrohpe inside or outside of a quoted string represents
130 * one literal apostrophe.
131 * <li>Quotable syntax characters are the {curly braces} in all messageText parts,
132 * plus the '#' sign in a messageText immediately inside a pluralStyle,
133 * and the '|' symbol in a messageText immediately inside a choiceStyle.
134 * <li>See also {@link MessagePattern.ApostropheMode}
135 * <li>In argStyleText, every single ASCII apostrophe begins and ends quoted literal text,
136 * and unquoted {curly braces} must occur in matched pairs.
139 * <p>Recommendation: Use the real apostrophe (single quote) character \u2019 for
140 * human-readable text, and use the ASCII apostrophe (\u0027 ' )
141 * only in program syntax, like quoting in MessageFormat.
142 * See the annotations for U+0027 Apostrophe in The Unicode Standard.
144 * <p>The <code>choice</code> argument type is deprecated.
145 * Use <code>plural</code> arguments for proper plural selection,
146 * and <code>select</code> arguments for simple selection among a fixed set of choices.
148 * <p>The <code>argType</code> and <code>argStyle</code> values are used to create
149 * a <code>Format</code> instance for the format element. The following
150 * table shows how the values map to Format instances. Combinations not
151 * shown in the table are illegal. Any <code>argStyleText</code> must
152 * be a valid pattern string for the Format subclass used.
154 * <p><table border=1>
158 * <th>resulting Format object
160 * <td colspan=2><i>(none)</i>
161 * <td><code>null</code>
163 * <td rowspan=5><code>number</code>
165 * <td><code>NumberFormat.getInstance(getLocale())</code>
167 * <td><code>integer</code>
168 * <td><code>NumberFormat.getIntegerInstance(getLocale())</code>
170 * <td><code>currency</code>
171 * <td><code>NumberFormat.getCurrencyInstance(getLocale())</code>
173 * <td><code>percent</code>
174 * <td><code>NumberFormat.getPercentInstance(getLocale())</code>
176 * <td><i>argStyleText</i>
177 * <td><code>new DecimalFormat(argStyleText, new DecimalFormatSymbols(getLocale()))</code>
179 * <td rowspan=6><code>date</code>
181 * <td><code>DateFormat.getDateInstance(DateFormat.DEFAULT, getLocale())</code>
183 * <td><code>short</code>
184 * <td><code>DateFormat.getDateInstance(DateFormat.SHORT, getLocale())</code>
186 * <td><code>medium</code>
187 * <td><code>DateFormat.getDateInstance(DateFormat.DEFAULT, getLocale())</code>
189 * <td><code>long</code>
190 * <td><code>DateFormat.getDateInstance(DateFormat.LONG, getLocale())</code>
192 * <td><code>full</code>
193 * <td><code>DateFormat.getDateInstance(DateFormat.FULL, getLocale())</code>
195 * <td><i>argStyleText</i>
196 * <td><code>new SimpleDateFormat(argStyleText, getLocale())
198 * <td rowspan=6><code>time</code>
200 * <td><code>DateFormat.getTimeInstance(DateFormat.DEFAULT, getLocale())</code>
202 * <td><code>short</code>
203 * <td><code>DateFormat.getTimeInstance(DateFormat.SHORT, getLocale())</code>
205 * <td><code>medium</code>
206 * <td><code>DateFormat.getTimeInstance(DateFormat.DEFAULT, getLocale())</code>
208 * <td><code>long</code>
209 * <td><code>DateFormat.getTimeInstance(DateFormat.LONG, getLocale())</code>
211 * <td><code>full</code>
212 * <td><code>DateFormat.getTimeInstance(DateFormat.FULL, getLocale())</code>
214 * <td><i>argStyleText</i>
215 * <td><code>new SimpleDateFormat(argStyleText, getLocale())
217 * <td><code>spellout</code>
218 * <td><i>argStyleText (optional)</i>
219 * <td><code>new RuleBasedNumberFormat(getLocale(), RuleBasedNumberFormat.SPELLOUT)
220 * <br/> .setDefaultRuleset(argStyleText);</code>
222 * <td><code>ordinal</code>
223 * <td><i>argStyleText (optional)</i>
224 * <td><code>new RuleBasedNumberFormat(getLocale(), RuleBasedNumberFormat.ORDINAL)
225 * <br/> .setDefaultRuleset(argStyleText);</code>
227 * <td><code>duration</code>
228 * <td><i>argStyleText (optional)</i>
229 * <td><code>new RuleBasedNumberFormat(getLocale(), RuleBasedNumberFormat.DURATION)
230 * <br/> .setDefaultRuleset(argStyleText);</code>
234 * <h4><a name="diffsjdk">Differences from java.text.MessageFormat</a></h4>
236 * <p>The ICU MessageFormat supports both named and numbered arguments,
237 * while the JDK MessageFormat only supports numbered arguments.
238 * Named arguments make patterns more readable.
240 * <p>ICU implements a more user-friendly apostrophe quoting syntax.
241 * In message text, an apostrophe only begins quoting literal text
242 * if it immediately precedes a syntax character (mostly {curly braces}).<br>
243 * In the JDK MessageFormat, an apostrophe always begins quoting,
244 * which requires common text like "don't" and "aujourd'hui"
245 * to be written with doubled apostrophes like "don''t" and "aujourd''hui".
246 * For more details see {@link MessagePattern.ApostropheMode}.
248 * <p>ICU does not create a ChoiceFormat object for a choiceArg, pluralArg or selectArg
249 * but rather handles such arguments itself.
250 * The JDK MessageFormat does create and use a ChoiceFormat object
251 * (<code>new ChoiceFormat(argStyleText)</code>).
252 * The JDK does not support plural and select arguments at all.
254 * <h4>Usage Information</h4>
256 * <p>Here are some examples of usage:
259 * Object[] arguments = {
261 * new Date(System.currentTimeMillis()),
262 * "a disturbance in the Force"
265 * String result = MessageFormat.format(
266 * "At {1,time} on {1,date}, there was {2} on planet {0,number,integer}.",
269 * <em>output</em>: At 12:30 PM on Jul 3, 2053, there was a disturbance
270 * in the Force on planet 7.
274 * Typically, the message format will come from resources, and the
275 * arguments will be dynamically set at runtime.
280 * Object[] testArgs = { 3, "MyDisk" };
282 * MessageFormat form = new MessageFormat(
283 * "The disk \"{1}\" contains {0} file(s).");
285 * System.out.println(form.format(testArgs));
287 * // output, with different testArgs
288 * <em>output</em>: The disk "MyDisk" contains 0 file(s).
289 * <em>output</em>: The disk "MyDisk" contains 1 file(s).
290 * <em>output</em>: The disk "MyDisk" contains 1,273 file(s).
294 * <p>For messages that include plural forms, you can use a plural argument:
296 * MessageFormat msgFmt = new MessageFormat(
297 * "{num_files, plural, " +
298 * "=0{There are no files on disk \"{disk_name}\".}" +
299 * "=1{There is one file on disk \"{disk_name}\".}" +
300 * "other{There are # files on disk \"{disk_name}\".}}",
302 * Map args = new HashMap();
303 * args.put("num_files", 0);
304 * args.put("disk_name", "MyDisk");
305 * System.out.println(msgFmt.format(args));
306 * args.put("num_files", 3);
307 * System.out.println(msgFmt.format(args));
310 * There are no files on disk "MyDisk".
311 * There are 3 files on "MyDisk".
313 * See {@link PluralFormat} and {@link PluralRules} for details.
315 * <h4><a name="synchronization">Synchronization</a></h4>
317 * <p>MessageFormats are not synchronized.
318 * It is recommended to create separate format instances for each thread.
319 * If multiple threads access a format concurrently, it must be synchronized
322 * @see java.util.Locale
330 * @author Markus Scherer
333 public class MessageFormat extends UFormat {
335 // Incremented by 1 for ICU 4.8's new format.
336 static final long serialVersionUID = 7136212545847378652L;
339 * Constructs a MessageFormat for the default <code>FORMAT</code> locale and the
341 * Sets the locale and calls applyPattern(pattern).
343 * @param pattern the pattern for this message format
344 * @exception IllegalArgumentException if the pattern is invalid
345 * @see Category#FORMAT
348 public MessageFormat(String pattern) {
349 this.ulocale = ULocale.getDefault(Category.FORMAT);
350 applyPattern(pattern);
354 * Constructs a MessageFormat for the specified locale and
356 * Sets the locale and calls applyPattern(pattern).
358 * @param pattern the pattern for this message format
359 * @param locale the locale for this message format
360 * @exception IllegalArgumentException if the pattern is invalid
363 public MessageFormat(String pattern, Locale locale) {
364 this(pattern, ULocale.forLocale(locale));
368 * Constructs a MessageFormat for the specified locale and
370 * Sets the locale and calls applyPattern(pattern).
372 * @param pattern the pattern for this message format
373 * @param locale the locale for this message format
374 * @exception IllegalArgumentException if the pattern is invalid
377 public MessageFormat(String pattern, ULocale locale) {
378 this.ulocale = locale;
379 applyPattern(pattern);
383 * Sets the locale to be used for creating argument Format objects.
384 * This affects subsequent calls to the {@link #applyPattern applyPattern}
385 * method as well as to the <code>format</code> and
386 * {@link #formatToCharacterIterator formatToCharacterIterator} methods.
388 * @param locale the locale to be used when creating or comparing subformats
391 public void setLocale(Locale locale) {
392 setLocale(ULocale.forLocale(locale));
396 * Sets the locale to be used for creating argument Format objects.
397 * This affects subsequent calls to the {@link #applyPattern applyPattern}
398 * method as well as to the <code>format</code> and
399 * {@link #formatToCharacterIterator formatToCharacterIterator} methods.
401 * @param locale the locale to be used when creating or comparing subformats
404 public void setLocale(ULocale locale) {
405 /* Save the pattern, and then reapply so that */
406 /* we pick up any changes in locale specific */
408 String existingPattern = toPattern(); /*ibm.3550*/
409 this.ulocale = locale;
410 // Invalidate all stock formatters. They are no longer valid since
411 // the locale has changed.
412 stockDateFormatter = null;
413 stockNumberFormatter = null;
414 pluralProvider = null;
415 ordinalProvider = null;
416 applyPattern(existingPattern); /*ibm.3550*/
420 * Returns the locale that's used when creating or comparing subformats.
422 * @return the locale used when creating or comparing subformats
425 public Locale getLocale() {
426 return ulocale.toLocale();
430 * {@icu} Returns the locale that's used when creating argument Format objects.
432 * @return the locale used when creating or comparing subformats
435 public ULocale getULocale() {
440 * Sets the pattern used by this message format.
441 * Parses the pattern and caches Format objects for simple argument types.
442 * Patterns and their interpretation are specified in the
443 * <a href="#patterns">class description</a>.
445 * @param pttrn the pattern for this message format
446 * @throws IllegalArgumentException if the pattern is invalid
449 public void applyPattern(String pttrn) {
451 if (msgPattern == null) {
452 msgPattern = new MessagePattern(pttrn);
454 msgPattern.parse(pttrn);
456 // Cache the formats that are explicitly mentioned in the message pattern.
457 cacheExplicitFormats();
458 } catch(RuntimeException e) {
465 * {@icu} Sets the ApostropheMode and the pattern used by this message format.
466 * Parses the pattern and caches Format objects for simple argument types.
467 * Patterns and their interpretation are specified in the
468 * <a href="#patterns">class description</a>.
470 * This method is best used only once on a given object to avoid confusion about the mode,
471 * and after constructing the object with an empty pattern string to minimize overhead.
473 * @param pattern the pattern for this message format
474 * @param aposMode the new ApostropheMode
475 * @throws IllegalArgumentException if the pattern is invalid
476 * @see MessagePattern.ApostropheMode
479 public void applyPattern(String pattern, MessagePattern.ApostropheMode aposMode) {
480 if (msgPattern == null) {
481 msgPattern = new MessagePattern(aposMode);
482 } else if (aposMode != msgPattern.getApostropheMode()) {
483 msgPattern.clearPatternAndSetApostropheMode(aposMode);
485 applyPattern(pattern);
490 * @return this instance's ApostropheMode.
493 public MessagePattern.ApostropheMode getApostropheMode() {
494 if (msgPattern == null) {
495 msgPattern = new MessagePattern(); // Sets the default mode.
497 return msgPattern.getApostropheMode();
501 * Returns the applied pattern string.
502 * @return the pattern string
503 * @throws IllegalStateException after custom Format objects have been set
504 * via setFormat() or similar APIs
507 public String toPattern() {
508 // Return the original, applied pattern string, or else "".
509 // Note: This does not take into account
510 // - changes from setFormat() and similar methods, or
511 // - normalization of apostrophes and arguments, for example,
512 // whether some date/time/number formatter was created via a pattern
513 // but is equivalent to the "medium" default format.
514 if (customFormatArgStarts != null) {
515 throw new IllegalStateException(
516 "toPattern() is not supported after custom Format objects "+
517 "have been set via setFormat() or similar APIs");
519 if (msgPattern == null) {
522 String originalPattern = msgPattern.getPatternString();
523 return originalPattern == null ? "" : originalPattern;
527 * Returns the part index of the next ARG_START after partIndex, or -1 if there is none more.
528 * @param partIndex Part index of the previous ARG_START (initially 0).
530 private int nextTopLevelArgStart(int partIndex) {
531 if (partIndex != 0) {
532 partIndex = msgPattern.getLimitPartIndex(partIndex);
535 MessagePattern.Part.Type type = msgPattern.getPartType(++partIndex);
536 if (type == MessagePattern.Part.Type.ARG_START) {
539 if (type == MessagePattern.Part.Type.MSG_LIMIT) {
545 private boolean argNameMatches(int partIndex, String argName, int argNumber) {
546 Part part = msgPattern.getPart(partIndex);
547 return part.getType() == MessagePattern.Part.Type.ARG_NAME ?
548 msgPattern.partSubstringMatches(part, argName) :
549 part.getValue() == argNumber; // ARG_NUMBER
552 private String getArgName(int partIndex) {
553 Part part = msgPattern.getPart(partIndex);
554 if (part.getType() == MessagePattern.Part.Type.ARG_NAME) {
555 return msgPattern.getSubstring(part);
557 return Integer.toString(part.getValue());
562 * Sets the Format objects to use for the values passed into
563 * <code>format</code> methods or returned from <code>parse</code>
564 * methods. The indices of elements in <code>newFormats</code>
565 * correspond to the argument indices used in the previously set
567 * The order of formats in <code>newFormats</code> thus corresponds to
568 * the order of elements in the <code>arguments</code> array passed
569 * to the <code>format</code> methods or the result array returned
570 * by the <code>parse</code> methods.
572 * If an argument index is used for more than one format element
573 * in the pattern string, then the corresponding new format is used
574 * for all such format elements. If an argument index is not used
575 * for any format element in the pattern string, then the
576 * corresponding new format is ignored. If fewer formats are provided
577 * than needed, then only the formats for argument indices less
578 * than <code>newFormats.length</code> are replaced.
580 * This method is only supported if the format does not use
581 * named arguments, otherwise an IllegalArgumentException is thrown.
583 * @param newFormats the new formats to use
584 * @throws NullPointerException if <code>newFormats</code> is null
585 * @throws IllegalArgumentException if this formatter uses named arguments
588 public void setFormatsByArgumentIndex(Format[] newFormats) {
589 if (msgPattern.hasNamedArguments()) {
590 throw new IllegalArgumentException(
591 "This method is not available in MessageFormat objects " +
592 "that use alphanumeric argument names.");
594 for (int partIndex = 0; (partIndex = nextTopLevelArgStart(partIndex)) >= 0;) {
595 int argNumber = msgPattern.getPart(partIndex + 1).getValue();
596 if (argNumber < newFormats.length) {
597 setCustomArgStartFormat(partIndex, newFormats[argNumber]);
603 * {@icu} Sets the Format objects to use for the values passed into
604 * <code>format</code> methods or returned from <code>parse</code>
605 * methods. The keys in <code>newFormats</code> are the argument
606 * names in the previously set pattern string, and the values
609 * Only argument names from the pattern string are considered.
610 * Extra keys in <code>newFormats</code> that do not correspond
611 * to an argument name are ignored. Similarly, if there is no
612 * format in newFormats for an argument name, the formatter
613 * for that argument remains unchanged.
615 * This may be called on formats that do not use named arguments.
616 * In this case the map will be queried for key Strings that
617 * represent argument indices, e.g. "0", "1", "2" etc.
619 * @param newFormats a map from String to Format providing new
620 * formats for named arguments.
623 public void setFormatsByArgumentName(Map<String, Format> newFormats) {
624 for (int partIndex = 0; (partIndex = nextTopLevelArgStart(partIndex)) >= 0;) {
625 String key = getArgName(partIndex + 1);
626 if (newFormats.containsKey(key)) {
627 setCustomArgStartFormat(partIndex, newFormats.get(key));
633 * Sets the Format objects to use for the format elements in the
634 * previously set pattern string.
635 * The order of formats in <code>newFormats</code> corresponds to
636 * the order of format elements in the pattern string.
638 * If more formats are provided than needed by the pattern string,
639 * the remaining ones are ignored. If fewer formats are provided
640 * than needed, then only the first <code>newFormats.length</code>
641 * formats are replaced.
643 * Since the order of format elements in a pattern string often
644 * changes during localization, it is generally better to use the
645 * {@link #setFormatsByArgumentIndex setFormatsByArgumentIndex}
646 * method, which assumes an order of formats corresponding to the
647 * order of elements in the <code>arguments</code> array passed to
648 * the <code>format</code> methods or the result array returned by
649 * the <code>parse</code> methods.
651 * @param newFormats the new formats to use
652 * @exception NullPointerException if <code>newFormats</code> is null
655 public void setFormats(Format[] newFormats) {
656 int formatNumber = 0;
657 for (int partIndex = 0;
658 formatNumber < newFormats.length &&
659 (partIndex = nextTopLevelArgStart(partIndex)) >= 0;) {
660 setCustomArgStartFormat(partIndex, newFormats[formatNumber]);
666 * Sets the Format object to use for the format elements within the
667 * previously set pattern string that use the given argument
669 * The argument index is part of the format element definition and
670 * represents an index into the <code>arguments</code> array passed
671 * to the <code>format</code> methods or the result array returned
672 * by the <code>parse</code> methods.
674 * If the argument index is used for more than one format element
675 * in the pattern string, then the new format is used for all such
676 * format elements. If the argument index is not used for any format
677 * element in the pattern string, then the new format is ignored.
679 * This method is only supported when exclusively numbers are used for
680 * argument names. Otherwise an IllegalArgumentException is thrown.
682 * @param argumentIndex the argument index for which to use the new format
683 * @param newFormat the new format to use
684 * @throws IllegalArgumentException if this format uses named arguments
687 public void setFormatByArgumentIndex(int argumentIndex, Format newFormat) {
688 if (msgPattern.hasNamedArguments()) {
689 throw new IllegalArgumentException(
690 "This method is not available in MessageFormat objects " +
691 "that use alphanumeric argument names.");
693 for (int partIndex = 0; (partIndex = nextTopLevelArgStart(partIndex)) >= 0;) {
694 if (msgPattern.getPart(partIndex + 1).getValue() == argumentIndex) {
695 setCustomArgStartFormat(partIndex, newFormat);
701 * {@icu} Sets the Format object to use for the format elements within the
702 * previously set pattern string that use the given argument
705 * If the argument name is used for more than one format element
706 * in the pattern string, then the new format is used for all such
707 * format elements. If the argument name is not used for any format
708 * element in the pattern string, then the new format is ignored.
710 * This API may be used on formats that do not use named arguments.
711 * In this case <code>argumentName</code> should be a String that names
712 * an argument index, e.g. "0", "1", "2"... etc. If it does not name
713 * a valid index, the format will be ignored. No error is thrown.
715 * @param argumentName the name of the argument to change
716 * @param newFormat the new format to use
719 public void setFormatByArgumentName(String argumentName, Format newFormat) {
720 int argNumber = MessagePattern.validateArgumentName(argumentName);
721 if (argNumber < MessagePattern.ARG_NAME_NOT_NUMBER) {
724 for (int partIndex = 0; (partIndex = nextTopLevelArgStart(partIndex)) >= 0;) {
725 if (argNameMatches(partIndex + 1, argumentName, argNumber)) {
726 setCustomArgStartFormat(partIndex, newFormat);
732 * Sets the Format object to use for the format element with the given
733 * format element index within the previously set pattern string.
734 * The format element index is the zero-based number of the format
735 * element counting from the start of the pattern string.
737 * Since the order of format elements in a pattern string often
738 * changes during localization, it is generally better to use the
739 * {@link #setFormatByArgumentIndex setFormatByArgumentIndex}
740 * method, which accesses format elements based on the argument
741 * index they specify.
743 * @param formatElementIndex the index of a format element within the pattern
744 * @param newFormat the format to use for the specified format element
745 * @exception ArrayIndexOutOfBoundsException if formatElementIndex is equal to or
746 * larger than the number of format elements in the pattern string
749 public void setFormat(int formatElementIndex, Format newFormat) {
750 int formatNumber = 0;
751 for (int partIndex = 0; (partIndex = nextTopLevelArgStart(partIndex)) >= 0;) {
752 if (formatNumber == formatElementIndex) {
753 setCustomArgStartFormat(partIndex, newFormat);
758 throw new ArrayIndexOutOfBoundsException(formatElementIndex);
762 * Returns the Format objects used for the values passed into
763 * <code>format</code> methods or returned from <code>parse</code>
764 * methods. The indices of elements in the returned array
765 * correspond to the argument indices used in the previously set
767 * The order of formats in the returned array thus corresponds to
768 * the order of elements in the <code>arguments</code> array passed
769 * to the <code>format</code> methods or the result array returned
770 * by the <code>parse</code> methods.
772 * If an argument index is used for more than one format element
773 * in the pattern string, then the format used for the last such
774 * format element is returned in the array. If an argument index
775 * is not used for any format element in the pattern string, then
776 * null is returned in the array.
778 * This method is only supported when exclusively numbers are used for
779 * argument names. Otherwise an IllegalArgumentException is thrown.
781 * @return the formats used for the arguments within the pattern
782 * @throws IllegalArgumentException if this format uses named arguments
785 public Format[] getFormatsByArgumentIndex() {
786 if (msgPattern.hasNamedArguments()) {
787 throw new IllegalArgumentException(
788 "This method is not available in MessageFormat objects " +
789 "that use alphanumeric argument names.");
791 ArrayList<Format> list = new ArrayList<Format>();
792 for (int partIndex = 0; (partIndex = nextTopLevelArgStart(partIndex)) >= 0;) {
793 int argNumber = msgPattern.getPart(partIndex + 1).getValue();
794 while (argNumber >= list.size()) {
797 list.set(argNumber, cachedFormatters == null ? null : cachedFormatters.get(partIndex));
799 return list.toArray(new Format[list.size()]);
803 * Returns the Format objects used for the format elements in the
804 * previously set pattern string.
805 * The order of formats in the returned array corresponds to
806 * the order of format elements in the pattern string.
808 * Since the order of format elements in a pattern string often
809 * changes during localization, it's generally better to use the
810 * {@link #getFormatsByArgumentIndex()}
811 * method, which assumes an order of formats corresponding to the
812 * order of elements in the <code>arguments</code> array passed to
813 * the <code>format</code> methods or the result array returned by
814 * the <code>parse</code> methods.
816 * This method is only supported when exclusively numbers are used for
817 * argument names. Otherwise an IllegalArgumentException is thrown.
819 * @return the formats used for the format elements in the pattern
820 * @throws IllegalArgumentException if this format uses named arguments
823 public Format[] getFormats() {
824 ArrayList<Format> list = new ArrayList<Format>();
825 for (int partIndex = 0; (partIndex = nextTopLevelArgStart(partIndex)) >= 0;) {
826 list.add(cachedFormatters == null ? null : cachedFormatters.get(partIndex));
828 return list.toArray(new Format[list.size()]);
832 * {@icu} Returns the top-level argument names. For more details, see
833 * {@link #setFormatByArgumentName(String, Format)}.
834 * @return a Set of argument names
837 public Set<String> getArgumentNames() {
838 Set<String> result = new HashSet<String>();
839 for (int partIndex = 0; (partIndex = nextTopLevelArgStart(partIndex)) >= 0;) {
840 result.add(getArgName(partIndex + 1));
846 * {@icu} Returns the first top-level format associated with the given argument name.
847 * For more details, see {@link #setFormatByArgumentName(String, Format)}.
848 * @param argumentName The name of the desired argument.
849 * @return the Format associated with the name, or null if there isn't one.
852 public Format getFormatByArgumentName(String argumentName) {
853 if (cachedFormatters == null) {
856 int argNumber = MessagePattern.validateArgumentName(argumentName);
857 if (argNumber < MessagePattern.ARG_NAME_NOT_NUMBER) {
860 for (int partIndex = 0; (partIndex = nextTopLevelArgStart(partIndex)) >= 0;) {
861 if (argNameMatches(partIndex + 1, argumentName, argNumber)) {
862 return cachedFormatters.get(partIndex);
869 * Formats an array of objects and appends the <code>MessageFormat</code>'s
870 * pattern, with arguments replaced by the formatted objects, to the
871 * provided <code>StringBuffer</code>.
873 * The text substituted for the individual format elements is derived from
874 * the current subformat of the format element and the
875 * <code>arguments</code> element at the format element's argument index
876 * as indicated by the first matching line of the following table. An
877 * argument is <i>unavailable</i> if <code>arguments</code> is
878 * <code>null</code> or has fewer than argumentIndex+1 elements. When
879 * an argument is unavailable no substitution is performed.
883 * <th>argType or Format
888 * <td><i>unavailable</i>
889 * <td><code>"{" + argNameOrNumber + "}"</code>
892 * <td><code>null</code>
893 * <td><code>"null"</code>
895 * <td>custom Format <code>!= null</code>
897 * <td><code>customFormat.format(argument)</code>
899 * <td>noneArg, or custom Format <code>== null</code>
900 * <td><code>instanceof Number</code>
901 * <td><code>NumberFormat.getInstance(getLocale()).format(argument)</code>
903 * <td>noneArg, or custom Format <code>== null</code>
904 * <td><code>instanceof Date</code>
905 * <td><code>DateFormat.getDateTimeInstance(DateFormat.SHORT,
906 * DateFormat.SHORT, getLocale()).format(argument)</code>
908 * <td>noneArg, or custom Format <code>== null</code>
909 * <td><code>instanceof String</code>
910 * <td><code>argument</code>
912 * <td>noneArg, or custom Format <code>== null</code>
914 * <td><code>argument.toString()</code>
918 * <td>result of recursive formatting of a selected sub-message
921 * If <code>pos</code> is non-null, and refers to
922 * <code>Field.ARGUMENT</code>, the location of the first formatted
923 * string will be returned.
925 * This method is only supported when the format does not use named
926 * arguments, otherwise an IllegalArgumentException is thrown.
928 * @param arguments an array of objects to be formatted and substituted.
929 * @param result where text is appended.
930 * @param pos On input: an alignment field, if desired.
931 * On output: the offsets of the alignment field.
932 * @throws IllegalArgumentException if a value in the
933 * <code>arguments</code> array is not of the type
934 * expected by the corresponding argument or custom Format object.
935 * @throws IllegalArgumentException if this format uses named arguments
938 public final StringBuffer format(Object[] arguments, StringBuffer result,
941 format(arguments, null, new AppendableWrapper(result), pos);
946 * Formats a map of objects and appends the <code>MessageFormat</code>'s
947 * pattern, with arguments replaced by the formatted objects, to the
948 * provided <code>StringBuffer</code>.
950 * The text substituted for the individual format elements is derived from
951 * the current subformat of the format element and the
952 * <code>arguments</code> value corresopnding to the format element's
955 * A numbered pattern argument is matched with a map key that contains that number
956 * as an ASCII-decimal-digit string (without leading zero).
958 * An argument is <i>unavailable</i> if <code>arguments</code> is
959 * <code>null</code> or does not have a value corresponding to an argument
960 * name in the pattern. When an argument is unavailable no substitution
963 * @param arguments a map of objects to be formatted and substituted.
964 * @param result where text is appended.
965 * @param pos On input: an alignment field, if desired.
966 * On output: the offsets of the alignment field.
967 * @throws IllegalArgumentException if a value in the
968 * <code>arguments</code> array is not of the type
969 * expected by the corresponding argument or custom Format object.
970 * @return the passed-in StringBuffer
973 public final StringBuffer format(Map<String, Object> arguments, StringBuffer result,
975 format(null, arguments, new AppendableWrapper(result), pos);
980 * Creates a MessageFormat with the given pattern and uses it
981 * to format the given arguments. This is equivalent to
983 * <code>(new {@link #MessageFormat(String) MessageFormat}(pattern)).{@link
984 * #format(java.lang.Object[], java.lang.StringBuffer, java.text.FieldPosition)
985 * format}(arguments, new StringBuffer(), null).toString()</code>
988 * @throws IllegalArgumentException if the pattern is invalid
989 * @throws IllegalArgumentException if a value in the
990 * <code>arguments</code> array is not of the type
991 * expected by the corresponding argument or custom Format object.
992 * @throws IllegalArgumentException if this format uses named arguments
995 public static String format(String pattern, Object... arguments) {
996 MessageFormat temp = new MessageFormat(pattern);
997 return temp.format(arguments);
1001 * Creates a MessageFormat with the given pattern and uses it to
1002 * format the given arguments. The pattern must identifyarguments
1003 * by name instead of by number.
1005 * @throws IllegalArgumentException if the pattern is invalid
1006 * @throws IllegalArgumentException if a value in the
1007 * <code>arguments</code> array is not of the type
1008 * expected by the corresponding argument or custom Format object.
1009 * @see #format(Map, StringBuffer, FieldPosition)
1010 * @see #format(String, Object[])
1013 public static String format(String pattern, Map<String, Object> arguments) {
1014 MessageFormat temp = new MessageFormat(pattern);
1015 return temp.format(arguments);
1019 * {@icu} Returns true if this MessageFormat uses named arguments,
1020 * and false otherwise. See class description.
1022 * @return true if named arguments are used.
1025 public boolean usesNamedArguments() {
1026 return msgPattern.hasNamedArguments();
1031 * Formats a map or array of objects and appends the <code>MessageFormat</code>'s
1032 * pattern, with format elements replaced by the formatted objects, to the
1033 * provided <code>StringBuffer</code>.
1034 * This is equivalent to either of
1036 * <code>{@link #format(java.lang.Object[], java.lang.StringBuffer,
1037 * java.text.FieldPosition) format}((Object[]) arguments, result, pos)</code>
1038 * <code>{@link #format(java.util.Map, java.lang.StringBuffer,
1039 * java.text.FieldPosition) format}((Map) arguments, result, pos)</code>
1041 * A map must be provided if this format uses named arguments, otherwise
1042 * an IllegalArgumentException will be thrown.
1043 * @param arguments a map or array of objects to be formatted
1044 * @param result where text is appended
1045 * @param pos On input: an alignment field, if desired
1046 * On output: the offsets of the alignment field
1047 * @throws IllegalArgumentException if an argument in
1048 * <code>arguments</code> is not of the type
1049 * expected by the format element(s) that use it
1050 * @throws IllegalArgumentException if <code>arguments<code> is
1051 * an array of Object and this format uses named arguments
1054 public final StringBuffer format(Object arguments, StringBuffer result,
1057 format(arguments, new AppendableWrapper(result), pos);
1062 * Formats an array of objects and inserts them into the
1063 * <code>MessageFormat</code>'s pattern, producing an
1064 * <code>AttributedCharacterIterator</code>.
1065 * You can use the returned <code>AttributedCharacterIterator</code>
1066 * to build the resulting String, as well as to determine information
1067 * about the resulting String.
1069 * The text of the returned <code>AttributedCharacterIterator</code> is
1070 * the same that would be returned by
1072 * <code>{@link #format(java.lang.Object[], java.lang.StringBuffer,
1073 * java.text.FieldPosition) format}(arguments, new StringBuffer(), null).toString()</code>
1076 * In addition, the <code>AttributedCharacterIterator</code> contains at
1077 * least attributes indicating where text was generated from an
1078 * argument in the <code>arguments</code> array. The keys of these attributes are of
1079 * type <code>MessageFormat.Field</code>, their values are
1080 * <code>Integer</code> objects indicating the index in the <code>arguments</code>
1081 * array of the argument from which the text was generated.
1083 * The attributes/value from the underlying <code>Format</code>
1084 * instances that <code>MessageFormat</code> uses will also be
1085 * placed in the resulting <code>AttributedCharacterIterator</code>.
1086 * This allows you to not only find where an argument is placed in the
1087 * resulting String, but also which fields it contains in turn.
1089 * @param arguments an array of objects to be formatted and substituted.
1090 * @return AttributedCharacterIterator describing the formatted value.
1091 * @exception NullPointerException if <code>arguments</code> is null.
1092 * @throws IllegalArgumentException if a value in the
1093 * <code>arguments</code> array is not of the type
1094 * expected by the corresponding argument or custom Format object.
1097 public AttributedCharacterIterator formatToCharacterIterator(Object arguments) {
1098 if (arguments == null) {
1099 throw new NullPointerException(
1100 "formatToCharacterIterator must be passed non-null object");
1102 StringBuilder result = new StringBuilder();
1103 AppendableWrapper wrapper = new AppendableWrapper(result);
1104 wrapper.useAttributes();
1105 format(arguments, wrapper, null);
1106 AttributedString as = new AttributedString(result.toString());
1107 for (AttributeAndPosition a : wrapper.attributes) {
1108 as.addAttribute(a.key, a.value, a.start, a.limit);
1110 return as.getIterator();
1114 * Parses the string.
1116 * <p>Caveats: The parse may fail in a number of circumstances.
1119 * <li>If one of the arguments does not occur in the pattern.
1120 * <li>If the format of an argument loses information, such as
1121 * with a choice format where a large number formats to "many".
1122 * <li>Does not yet handle recursion (where
1123 * the substituted strings contain {n} references.)
1124 * <li>Will not always find a match (or the correct match)
1125 * if some part of the parse is ambiguous.
1126 * For example, if the pattern "{1},{2}" is used with the
1127 * string arguments {"a,b", "c"}, it will format as "a,b,c".
1128 * When the result is parsed, it will return {"a", "b,c"}.
1129 * <li>If a single argument is parsed more than once in the string,
1130 * then the later parse wins.
1132 * When the parse fails, use ParsePosition.getErrorIndex() to find out
1133 * where in the string did the parsing failed. The returned error
1134 * index is the starting offset of the sub-patterns that the string
1135 * is comparing with. For example, if the parsing string "AAA {0} BBB"
1136 * is comparing against the pattern "AAD {0} BBB", the error index is
1137 * 0. When an error occurs, the call to this method will return null.
1138 * If the source is null, return an empty array.
1140 * @throws IllegalArgumentException if this format uses named arguments
1143 public Object[] parse(String source, ParsePosition pos) {
1144 if (msgPattern.hasNamedArguments()) {
1145 throw new IllegalArgumentException(
1146 "This method is not available in MessageFormat objects " +
1147 "that use named argument.");
1150 // Count how many slots we need in the array.
1152 for (int partIndex = 0; (partIndex = nextTopLevelArgStart(partIndex)) >= 0;) {
1153 int argNumber=msgPattern.getPart(partIndex + 1).getValue();
1154 if (argNumber > maxArgId) {
1155 maxArgId = argNumber;
1158 Object[] resultArray = new Object[maxArgId + 1];
1160 int backupStartPos = pos.getIndex();
1161 parse(0, source, pos, resultArray, null);
1162 if (pos.getIndex() == backupStartPos) { // unchanged, returned object is null
1170 * {@icu} Parses the string, returning the results in a Map.
1171 * This is similar to the version that returns an array
1172 * of Object. This supports both named and numbered
1173 * arguments-- if numbered, the keys in the map are the
1174 * corresponding ASCII-decimal-digit strings (e.g. "0", "1", "2"...).
1176 * @param source the text to parse
1177 * @param pos the position at which to start parsing. on return,
1178 * contains the result of the parse.
1179 * @return a Map containing key/value pairs for each parsed argument.
1182 public Map<String, Object> parseToMap(String source, ParsePosition pos) {
1183 Map<String, Object> result = new HashMap<String, Object>();
1184 int backupStartPos = pos.getIndex();
1185 parse(0, source, pos, null, result);
1186 if (pos.getIndex() == backupStartPos) {
1193 * Parses text from the beginning of the given string to produce an object
1195 * The method may not use the entire text of the given string.
1197 * See the {@link #parse(String, ParsePosition)} method for more information
1198 * on message parsing.
1200 * @param source A <code>String</code> whose beginning should be parsed.
1201 * @return An <code>Object</code> array parsed from the string.
1202 * @exception ParseException if the beginning of the specified string cannot be parsed.
1203 * @exception IllegalArgumentException if this format uses named arguments
1206 public Object[] parse(String source) throws ParseException {
1207 ParsePosition pos = new ParsePosition(0);
1208 Object[] result = parse(source, pos);
1209 if (pos.getIndex() == 0) // unchanged, returned object is null
1210 throw new ParseException("MessageFormat parse error!",
1211 pos.getErrorIndex());
1217 * Parses the string, filling either the Map or the Array.
1218 * This is a private method that all the public parsing methods call.
1219 * This supports both named and numbered
1220 * arguments-- if numbered, the keys in the map are the
1221 * corresponding ASCII-decimal-digit strings (e.g. "0", "1", "2"...).
1223 * @param msgStart index in the message pattern to start from.
1224 * @param source the text to parse
1225 * @param pos the position at which to start parsing. on return,
1226 * contains the result of the parse.
1227 * @param args if not null, the parse results will be filled here (The pattern
1228 * has to have numbered arguments in order for this to not be null).
1229 * @param argsMap if not null, the parse results will be filled here.
1231 private void parse(int msgStart, String source, ParsePosition pos,
1232 Object[] args, Map<String, Object> argsMap) {
1233 if (source == null) {
1236 String msgString=msgPattern.getPatternString();
1237 int prevIndex=msgPattern.getPart(msgStart).getLimit();
1238 int sourceOffset = pos.getIndex();
1239 ParsePosition tempStatus = new ParsePosition(0);
1241 for(int i=msgStart+1; ; ++i) {
1242 Part part=msgPattern.getPart(i);
1243 Part.Type type=part.getType();
1244 int index=part.getIndex();
1245 // Make sure the literal string matches.
1246 int len = index - prevIndex;
1247 if (len == 0 || msgString.regionMatches(prevIndex, source, sourceOffset, len)) {
1248 sourceOffset += len;
1251 pos.setErrorIndex(sourceOffset);
1252 return; // leave index as is to signal error
1254 if(type==Part.Type.MSG_LIMIT) {
1255 // Things went well! Done.
1256 pos.setIndex(sourceOffset);
1259 if(type==Part.Type.SKIP_SYNTAX || type==Part.Type.INSERT_CHAR) {
1260 prevIndex=part.getLimit();
1263 // We do not support parsing Plural formats. (No REPLACE_NUMBER here.)
1264 assert type==Part.Type.ARG_START : "Unexpected Part "+part+" in parsed message.";
1265 int argLimit=msgPattern.getLimitPartIndex(i);
1267 ArgType argType=part.getArgType();
1268 part=msgPattern.getPart(++i);
1269 // Compute the argId, so we can use it as a key.
1274 argNumber=part.getValue(); // ARG_NUMBER
1275 argId = Integer.valueOf(argNumber);
1277 if(part.getType()==MessagePattern.Part.Type.ARG_NAME) {
1278 key=msgPattern.getSubstring(part);
1279 } else /* ARG_NUMBER */ {
1280 key=Integer.toString(part.getValue());
1286 Format formatter = null;
1287 boolean haveArgResult = false;
1288 Object argResult = null;
1289 if(cachedFormatters!=null && (formatter=cachedFormatters.get(i - 2))!=null) {
1290 // Just parse using the formatter.
1291 tempStatus.setIndex(sourceOffset);
1292 argResult = formatter.parseObject(source, tempStatus);
1293 if (tempStatus.getIndex() == sourceOffset) {
1294 pos.setErrorIndex(sourceOffset);
1295 return; // leave index as is to signal error
1297 haveArgResult = true;
1298 sourceOffset = tempStatus.getIndex();
1300 argType==ArgType.NONE ||
1301 (cachedFormatters!=null && cachedFormatters.containsKey(i - 2))) {
1302 // Match as a string.
1303 // if at end, use longest possible match
1304 // otherwise uses first match to intervening string
1305 // does NOT recursively try all possibilities
1306 String stringAfterArgument = getLiteralStringUntilNextArgument(argLimit);
1308 if (stringAfterArgument.length() != 0) {
1309 next = source.indexOf(stringAfterArgument, sourceOffset);
1311 next = source.length();
1314 pos.setErrorIndex(sourceOffset);
1315 return; // leave index as is to signal error
1317 String strValue = source.substring(sourceOffset, next);
1318 if (!strValue.equals("{" + argId.toString() + "}")) {
1319 haveArgResult = true;
1320 argResult = strValue;
1322 sourceOffset = next;
1324 } else if(argType==ArgType.CHOICE) {
1325 tempStatus.setIndex(sourceOffset);
1326 double choiceResult = parseChoiceArgument(msgPattern, i, source, tempStatus);
1327 if (tempStatus.getIndex() == sourceOffset) {
1328 pos.setErrorIndex(sourceOffset);
1329 return; // leave index as is to signal error
1331 argResult = choiceResult;
1332 haveArgResult = true;
1333 sourceOffset = tempStatus.getIndex();
1334 } else if(argType.hasPluralStyle() || argType==ArgType.SELECT) {
1336 throw new UnsupportedOperationException(
1337 "Parsing of plural/select/selectordinal argument is not supported.");
1339 // This should never happen.
1340 throw new IllegalStateException("unexpected argType "+argType);
1342 if (haveArgResult) {
1344 args[argNumber] = argResult;
1345 } else if (argsMap != null) {
1346 argsMap.put(key, argResult);
1349 prevIndex=msgPattern.getPart(argLimit).getLimit();
1355 * {@icu} Parses text from the beginning of the given string to produce a map from
1356 * argument to values. The method may not use the entire text of the given string.
1358 * <p>See the {@link #parse(String, ParsePosition)} method for more information on
1361 * @param source A <code>String</code> whose beginning should be parsed.
1362 * @return A <code>Map</code> parsed from the string.
1363 * @throws ParseException if the beginning of the specified string cannot
1365 * @see #parseToMap(String, ParsePosition)
1368 public Map<String, Object> parseToMap(String source) throws ParseException {
1369 ParsePosition pos = new ParsePosition(0);
1370 Map<String, Object> result = new HashMap<String, Object>();
1371 parse(0, source, pos, null, result);
1372 if (pos.getIndex() == 0) // unchanged, returned object is null
1373 throw new ParseException("MessageFormat parse error!",
1374 pos.getErrorIndex());
1380 * Parses text from a string to produce an object array or Map.
1382 * The method attempts to parse text starting at the index given by
1384 * If parsing succeeds, then the index of <code>pos</code> is updated
1385 * to the index after the last character used (parsing does not necessarily
1386 * use all characters up to the end of the string), and the parsed
1387 * object array is returned. The updated <code>pos</code> can be used to
1388 * indicate the starting point for the next call to this method.
1389 * If an error occurs, then the index of <code>pos</code> is not
1390 * changed, the error index of <code>pos</code> is set to the index of
1391 * the character where the error occurred, and null is returned.
1393 * See the {@link #parse(String, ParsePosition)} method for more information
1394 * on message parsing.
1396 * @param source A <code>String</code>, part of which should be parsed.
1397 * @param pos A <code>ParsePosition</code> object with index and error
1398 * index information as described above.
1399 * @return An <code>Object</code> parsed from the string, either an
1400 * array of Object, or a Map, depending on whether named
1401 * arguments are used. This can be queried using <code>usesNamedArguments</code>.
1402 * In case of error, returns null.
1403 * @throws NullPointerException if <code>pos</code> is null.
1406 public Object parseObject(String source, ParsePosition pos) {
1407 if (!msgPattern.hasNamedArguments()) {
1408 return parse(source, pos);
1410 return parseToMap(source, pos);
1419 public Object clone() {
1420 MessageFormat other = (MessageFormat) super.clone();
1422 if (customFormatArgStarts != null) {
1423 other.customFormatArgStarts = new HashSet<Integer>();
1424 for (Integer key : customFormatArgStarts) {
1425 other.customFormatArgStarts.add(key);
1428 other.customFormatArgStarts = null;
1431 if (cachedFormatters != null) {
1432 other.cachedFormatters = new HashMap<Integer, Format>();
1433 Iterator<Map.Entry<Integer, Format>> it = cachedFormatters.entrySet().iterator();
1434 while (it.hasNext()){
1435 Map.Entry<Integer, Format> entry = it.next();
1436 other.cachedFormatters.put(entry.getKey(), entry.getValue());
1439 other.cachedFormatters = null;
1442 other.msgPattern = msgPattern == null ? null : (MessagePattern)msgPattern.clone();
1443 other.stockDateFormatter =
1444 stockDateFormatter == null ? null : (DateFormat) stockDateFormatter.clone();
1445 other.stockNumberFormatter =
1446 stockNumberFormatter == null ? null : (NumberFormat) stockNumberFormatter.clone();
1448 other.pluralProvider = null;
1449 other.ordinalProvider = null;
1458 public boolean equals(Object obj) {
1459 if (this == obj) // quick check
1461 if (obj == null || getClass() != obj.getClass())
1463 MessageFormat other = (MessageFormat) obj;
1464 return Utility.objectEquals(ulocale, other.ulocale)
1465 && Utility.objectEquals(msgPattern, other.msgPattern)
1466 && Utility.objectEquals(cachedFormatters, other.cachedFormatters)
1467 && Utility.objectEquals(customFormatArgStarts, other.customFormatArgStarts);
1468 // Note: It might suffice to only compare custom formatters
1469 // rather than all formatters.
1477 public int hashCode() {
1478 return msgPattern.getPatternString().hashCode(); // enough for reasonable distribution
1482 * Defines constants that are used as attribute keys in the
1483 * <code>AttributedCharacterIterator</code> returned
1484 * from <code>MessageFormat.formatToCharacterIterator</code>.
1488 public static class Field extends Format.Field {
1490 private static final long serialVersionUID = 7510380454602616157L;
1493 * Create a <code>Field</code> with the specified name.
1495 * @param name The name of the attribute
1499 protected Field(String name) {
1504 * Resolves instances being deserialized to the predefined constants.
1506 * @return resolved MessageFormat.Field constant
1507 * @throws InvalidObjectException if the constant could not be resolved.
1511 protected Object readResolve() throws InvalidObjectException {
1512 if (this.getClass() != MessageFormat.Field.class) {
1513 throw new InvalidObjectException(
1514 "A subclass of MessageFormat.Field must implement readResolve.");
1516 if (this.getName().equals(ARGUMENT.getName())) {
1519 throw new InvalidObjectException("Unknown attribute name.");
1524 * Constant identifying a portion of a message that was generated
1525 * from an argument passed into <code>formatToCharacterIterator</code>.
1526 * The value associated with the key will be an <code>Integer</code>
1527 * indicating the index in the <code>arguments</code> array of the
1528 * argument from which the text was generated.
1532 public static final Field ARGUMENT = new Field("message argument field");
1535 // ===========================privates============================
1537 // *Important*: All fields must be declared *transient* so that we can fully
1538 // control serialization!
1539 // See for example Joshua Bloch's "Effective Java", chapter 10 Serialization.
1542 * The locale to use for formatting numbers and dates.
1544 private transient ULocale ulocale;
1547 * The MessagePattern which contains the parsed structure of the pattern string.
1549 private transient MessagePattern msgPattern;
1551 * Cached formatters so we can just use them whenever needed instead of creating
1552 * them from scratch every time.
1554 private transient Map<Integer, Format> cachedFormatters;
1556 * Set of ARG_START part indexes where custom, user-provided Format objects
1557 * have been set via setFormat() or similar API.
1559 private transient Set<Integer> customFormatArgStarts;
1562 * Stock formatters. Those are used when a format is not explicitly mentioned in
1563 * the message. The format is inferred from the argument.
1565 private transient DateFormat stockDateFormatter;
1566 private transient NumberFormat stockNumberFormatter;
1568 private transient PluralSelectorProvider pluralProvider;
1569 private transient PluralSelectorProvider ordinalProvider;
1571 private DateFormat getStockDateFormatter() {
1572 if (stockDateFormatter == null) {
1573 stockDateFormatter = DateFormat.getDateTimeInstance(
1574 DateFormat.SHORT, DateFormat.SHORT, ulocale);//fix
1576 return stockDateFormatter;
1578 private NumberFormat getStockNumberFormatter() {
1579 if (stockNumberFormatter == null) {
1580 stockNumberFormatter = NumberFormat.getInstance(ulocale);
1582 return stockNumberFormatter;
1585 // *Important*: All fields must be declared *transient*.
1586 // See the longer comment above ulocale.
1589 * Formats the arguments and writes the result into the
1590 * AppendableWrapper, updates the field position.
1592 * <p>Exactly one of args and argsMap must be null, the other non-null.
1594 * @param msgStart Index to msgPattern part to start formatting from.
1595 * @param pluralNumber null except when formatting a plural argument sub-message
1596 * where a '#' is replaced by the format string for this number.
1597 * @param args The formattable objects array. Non-null iff numbered values are used.
1598 * @param argsMap The key-value map of formattable objects. Non-null iff named values are used.
1599 * @param dest Output parameter to receive the result.
1600 * The result (string & attributes) is appended to existing contents.
1601 * @param fp Field position status.
1603 private void format(int msgStart, PluralSelectorContext pluralNumber,
1604 Object[] args, Map<String, Object> argsMap,
1605 AppendableWrapper dest, FieldPosition fp) {
1606 String msgString=msgPattern.getPatternString();
1607 int prevIndex=msgPattern.getPart(msgStart).getLimit();
1608 for(int i=msgStart+1;; ++i) {
1609 Part part=msgPattern.getPart(i);
1610 Part.Type type=part.getType();
1611 int index=part.getIndex();
1612 dest.append(msgString, prevIndex, index);
1613 if(type==Part.Type.MSG_LIMIT) {
1616 prevIndex=part.getLimit();
1617 if(type==Part.Type.REPLACE_NUMBER) {
1618 if(pluralNumber.forReplaceNumber) {
1619 // number-offset was already formatted.
1620 dest.formatAndAppend(pluralNumber.formatter,
1621 pluralNumber.number, pluralNumber.numberString);
1623 dest.formatAndAppend(getStockNumberFormatter(), pluralNumber.number);
1627 if(type!=Part.Type.ARG_START) {
1630 int argLimit=msgPattern.getLimitPartIndex(i);
1631 ArgType argType=part.getArgType();
1632 part=msgPattern.getPart(++i);
1634 boolean noArg=false;
1636 String argName=msgPattern.getSubstring(part);
1638 int argNumber=part.getValue(); // ARG_NUMBER
1639 if (dest.attributes != null) {
1640 // We only need argId if we add it into the attributes.
1641 argId = Integer.valueOf(argNumber);
1643 if(0<=argNumber && argNumber<args.length) {
1644 arg=args[argNumber];
1651 if(argsMap!=null && argsMap.containsKey(argName)) {
1652 arg=argsMap.get(argName);
1659 int prevDestLength=dest.length;
1660 Format formatter = null;
1662 dest.append("{"+argName+"}");
1663 } else if (arg == null) {
1664 dest.append("null");
1665 } else if(pluralNumber!=null && pluralNumber.numberArgIndex==(i-2)) {
1666 if(pluralNumber.offset == 0) {
1667 // The number was already formatted with this formatter.
1668 dest.formatAndAppend(pluralNumber.formatter, pluralNumber.number, pluralNumber.numberString);
1670 // Do not use the formatted (number-offset) string for a named argument
1671 // that formats the number without subtracting the offset.
1672 dest.formatAndAppend(pluralNumber.formatter, arg);
1674 } else if(cachedFormatters!=null && (formatter=cachedFormatters.get(i - 2))!=null) {
1675 // Handles all ArgType.SIMPLE, and formatters from setFormat() and its siblings.
1676 if ( formatter instanceof ChoiceFormat ||
1677 formatter instanceof PluralFormat ||
1678 formatter instanceof SelectFormat) {
1679 // We only handle nested formats here if they were provided via setFormat() or its siblings.
1680 // Otherwise they are not cached and instead handled below according to argType.
1681 String subMsgString = formatter.format(arg);
1682 if (subMsgString.indexOf('{') >= 0 ||
1683 (subMsgString.indexOf('\'') >= 0 && !msgPattern.jdkAposMode())) {
1684 MessageFormat subMsgFormat = new MessageFormat(subMsgString, ulocale);
1685 subMsgFormat.format(0, null, args, argsMap, dest, null);
1686 } else if (dest.attributes == null) {
1687 dest.append(subMsgString);
1689 // This formats the argument twice, once above to get the subMsgString
1690 // and then once more here.
1691 // It only happens in formatToCharacterIterator()
1692 // on a complex Format set via setFormat(),
1693 // and only when the selected subMsgString does not need further formatting.
1694 // This imitates ICU 4.6 behavior.
1695 dest.formatAndAppend(formatter, arg);
1698 dest.formatAndAppend(formatter, arg);
1701 argType==ArgType.NONE ||
1702 (cachedFormatters!=null && cachedFormatters.containsKey(i - 2))) {
1704 // any argument which got reset to null via setFormat() or its siblings.
1705 if (arg instanceof Number) {
1706 // format number if can
1707 dest.formatAndAppend(getStockNumberFormatter(), arg);
1708 } else if (arg instanceof Date) {
1709 // format a Date if can
1710 dest.formatAndAppend(getStockDateFormatter(), arg);
1712 dest.append(arg.toString());
1714 } else if(argType==ArgType.CHOICE) {
1715 if (!(arg instanceof Number)) {
1716 throw new IllegalArgumentException("'" + arg + "' is not a Number");
1718 double number = ((Number)arg).doubleValue();
1719 int subMsgStart=findChoiceSubMessage(msgPattern, i, number);
1720 formatComplexSubMessage(subMsgStart, null, args, argsMap, dest);
1721 } else if(argType.hasPluralStyle()) {
1722 if (!(arg instanceof Number)) {
1723 throw new IllegalArgumentException("'" + arg + "' is not a Number");
1725 PluralSelectorProvider selector;
1726 if(argType == ArgType.PLURAL) {
1727 if (pluralProvider == null) {
1728 pluralProvider = new PluralSelectorProvider(this, PluralType.CARDINAL);
1730 selector = pluralProvider;
1732 if (ordinalProvider == null) {
1733 ordinalProvider = new PluralSelectorProvider(this, PluralType.ORDINAL);
1735 selector = ordinalProvider;
1737 Number number = (Number)arg;
1738 double offset=msgPattern.getPluralOffset(i);
1739 PluralSelectorContext context =
1740 new PluralSelectorContext(i, argName, number, offset);
1741 int subMsgStart=PluralFormat.findSubMessage(
1742 msgPattern, i, selector, context, number.doubleValue());
1743 formatComplexSubMessage(subMsgStart, context, args, argsMap, dest);
1744 } else if(argType==ArgType.SELECT) {
1745 int subMsgStart=SelectFormat.findSubMessage(msgPattern, i, arg.toString());
1746 formatComplexSubMessage(subMsgStart, null, args, argsMap, dest);
1748 // This should never happen.
1749 throw new IllegalStateException("unexpected argType "+argType);
1751 fp = updateMetaData(dest, prevDestLength, fp, argId);
1752 prevIndex=msgPattern.getPart(argLimit).getLimit();
1757 private void formatComplexSubMessage(
1758 int msgStart, PluralSelectorContext pluralNumber,
1759 Object[] args, Map<String, Object> argsMap,
1760 AppendableWrapper dest) {
1761 if (!msgPattern.jdkAposMode()) {
1762 format(msgStart, pluralNumber, args, argsMap, dest, null);
1765 // JDK compatibility mode: (see JDK MessageFormat.format() API docs)
1766 // - remove SKIP_SYNTAX; that is, remove half of the apostrophes
1767 // - if the result string contains an open curly brace '{' then
1768 // instantiate a temporary MessageFormat object and format again;
1769 // otherwise just append the result string
1770 String msgString = msgPattern.getPatternString();
1771 String subMsgString;
1772 StringBuilder sb = null;
1773 int prevIndex = msgPattern.getPart(msgStart).getLimit();
1774 for (int i = msgStart;;) {
1775 Part part = msgPattern.getPart(++i);
1776 Part.Type type = part.getType();
1777 int index = part.getIndex();
1778 if (type == Part.Type.MSG_LIMIT) {
1780 subMsgString = msgString.substring(prevIndex, index);
1782 subMsgString = sb.append(msgString, prevIndex, index).toString();
1785 } else if (type == Part.Type.REPLACE_NUMBER || type == Part.Type.SKIP_SYNTAX) {
1787 sb = new StringBuilder();
1789 sb.append(msgString, prevIndex, index);
1790 if (type == Part.Type.REPLACE_NUMBER) {
1791 if(pluralNumber.forReplaceNumber) {
1792 // number-offset was already formatted.
1793 sb.append(pluralNumber.numberString);
1795 sb.append(getStockNumberFormatter().format(pluralNumber.number));
1798 prevIndex = part.getLimit();
1799 } else if (type == Part.Type.ARG_START) {
1801 sb = new StringBuilder();
1803 sb.append(msgString, prevIndex, index);
1805 i = msgPattern.getLimitPartIndex(i);
1806 index = msgPattern.getPart(i).getLimit();
1807 MessagePattern.appendReducedApostrophes(msgString, prevIndex, index, sb);
1811 if (subMsgString.indexOf('{') >= 0) {
1812 MessageFormat subMsgFormat = new MessageFormat("", ulocale);
1813 subMsgFormat.applyPattern(subMsgString, MessagePattern.ApostropheMode.DOUBLE_REQUIRED);
1814 subMsgFormat.format(0, null, args, argsMap, dest, null);
1816 dest.append(subMsgString);
1821 * Read as much literal string from the pattern string as possible. This stops
1822 * as soon as it finds an argument, or it reaches the end of the string.
1823 * @param from Index in the pattern string to start from.
1824 * @return A substring from the pattern string representing the longest possible
1825 * substring with no arguments.
1827 private String getLiteralStringUntilNextArgument(int from) {
1828 StringBuilder b = new StringBuilder();
1829 String msgString=msgPattern.getPatternString();
1830 int prevIndex=msgPattern.getPart(from).getLimit();
1831 for(int i=from+1;; ++i) {
1832 Part part=msgPattern.getPart(i);
1833 Part.Type type=part.getType();
1834 int index=part.getIndex();
1835 b.append(msgString, prevIndex, index);
1836 if(type==Part.Type.ARG_START || type==Part.Type.MSG_LIMIT) {
1837 return b.toString();
1839 assert type==Part.Type.SKIP_SYNTAX || type==Part.Type.INSERT_CHAR :
1840 "Unexpected Part "+part+" in parsed message.";
1841 prevIndex=part.getLimit();
1845 private FieldPosition updateMetaData(AppendableWrapper dest, int prevLength,
1846 FieldPosition fp, Object argId) {
1847 if (dest.attributes != null && prevLength < dest.length) {
1848 dest.attributes.add(new AttributeAndPosition(argId, prevLength, dest.length));
1850 if (fp != null && Field.ARGUMENT.equals(fp.getFieldAttribute())) {
1851 fp.setBeginIndex(prevLength);
1852 fp.setEndIndex(dest.length);
1858 // This lives here because ICU4J does not have its own ChoiceFormat class.
1860 * Finds the ChoiceFormat sub-message for the given number.
1861 * @param pattern A MessagePattern.
1862 * @param partIndex the index of the first ChoiceFormat argument style part.
1863 * @param number a number to be mapped to one of the ChoiceFormat argument's intervals
1864 * @return the sub-message start part index.
1866 private static int findChoiceSubMessage(MessagePattern pattern, int partIndex, double number) {
1867 int count=pattern.countParts();
1869 // Iterate over (ARG_INT|DOUBLE, ARG_SELECTOR, message) tuples
1870 // until ARG_LIMIT or end of choice-only pattern.
1871 // Ignore the first number and selector and start the loop on the first message.
1874 // Skip but remember the current sub-message.
1876 partIndex=pattern.getLimitPartIndex(partIndex);
1877 if(++partIndex>=count) {
1878 // Reached the end of the choice-only pattern.
1879 // Return with the last sub-message.
1882 Part part=pattern.getPart(partIndex++);
1883 Part.Type type=part.getType();
1884 if(type==Part.Type.ARG_LIMIT) {
1885 // Reached the end of the ChoiceFormat style.
1886 // Return with the last sub-message.
1889 // part is an ARG_INT or ARG_DOUBLE
1890 assert type.hasNumericValue();
1891 double boundary=pattern.getNumericValue(part);
1892 // Fetch the ARG_SELECTOR character.
1893 int selectorIndex=pattern.getPatternIndex(partIndex++);
1894 char boundaryChar=pattern.getPatternString().charAt(selectorIndex);
1895 if(boundaryChar=='<' ? !(number>boundary) : !(number>=boundary)) {
1896 // The number is in the interval between the previous boundary and the current one.
1897 // Return with the sub-message between them.
1898 // The !(a>b) and !(a>=b) comparisons are equivalent to
1899 // (a<=b) and (a<b) except they "catch" NaN.
1906 // Ported from C++ ChoiceFormat::parse().
1907 private static double parseChoiceArgument(
1908 MessagePattern pattern, int partIndex,
1909 String source, ParsePosition pos) {
1910 // find the best number (defined as the one with the longest parse)
1911 int start = pos.getIndex();
1912 int furthest = start;
1913 double bestNumber = Double.NaN;
1914 double tempNumber = 0.0;
1915 while (pattern.getPartType(partIndex) != Part.Type.ARG_LIMIT) {
1916 tempNumber = pattern.getNumericValue(pattern.getPart(partIndex));
1917 partIndex += 2; // skip the numeric part and ignore the ARG_SELECTOR
1918 int msgLimit = pattern.getLimitPartIndex(partIndex);
1919 int len = matchStringUntilLimitPart(pattern, partIndex, msgLimit, source, start);
1921 int newIndex = start + len;
1922 if (newIndex > furthest) {
1923 furthest = newIndex;
1924 bestNumber = tempNumber;
1925 if (furthest == source.length()) {
1930 partIndex = msgLimit + 1;
1932 if (furthest == start) {
1933 pos.setErrorIndex(start);
1935 pos.setIndex(furthest);
1941 * Matches the pattern string from the end of the partIndex to
1942 * the beginning of the limitPartIndex,
1943 * including all syntax except SKIP_SYNTAX,
1944 * against the source string starting at sourceOffset.
1945 * If they match, returns the length of the source string match.
1946 * Otherwise returns -1.
1948 private static int matchStringUntilLimitPart(
1949 MessagePattern pattern, int partIndex, int limitPartIndex,
1950 String source, int sourceOffset) {
1951 int matchingSourceLength = 0;
1952 String msgString = pattern.getPatternString();
1953 int prevIndex = pattern.getPart(partIndex).getLimit();
1955 Part part = pattern.getPart(++partIndex);
1956 if (partIndex == limitPartIndex || part.getType() == Part.Type.SKIP_SYNTAX) {
1957 int index = part.getIndex();
1958 int length = index - prevIndex;
1959 if (length != 0 && !source.regionMatches(sourceOffset, msgString, prevIndex, length)) {
1960 return -1; // mismatch
1962 matchingSourceLength += length;
1963 if (partIndex == limitPartIndex) {
1964 return matchingSourceLength;
1966 prevIndex = part.getLimit(); // SKIP_SYNTAX
1972 * Finds the "other" sub-message.
1973 * @param partIndex the index of the first PluralFormat argument style part.
1974 * @return the "other" sub-message start part index.
1976 private int findOtherSubMessage(int partIndex) {
1977 int count=msgPattern.countParts();
1978 MessagePattern.Part part=msgPattern.getPart(partIndex);
1979 if(part.getType().hasNumericValue()) {
1982 // Iterate over (ARG_SELECTOR [ARG_INT|ARG_DOUBLE] message) tuples
1983 // until ARG_LIMIT or end of plural-only pattern.
1985 part=msgPattern.getPart(partIndex++);
1986 MessagePattern.Part.Type type=part.getType();
1987 if(type==MessagePattern.Part.Type.ARG_LIMIT) {
1990 assert type==MessagePattern.Part.Type.ARG_SELECTOR;
1991 // part is an ARG_SELECTOR followed by an optional explicit value, and then a message
1992 if(msgPattern.partSubstringMatches(part, "other")) {
1995 if(msgPattern.getPartType(partIndex).hasNumericValue()) {
1996 ++partIndex; // skip the numeric-value part of "=1" etc.
1998 partIndex=msgPattern.getLimitPartIndex(partIndex);
1999 } while(++partIndex<count);
2004 * Returns the ARG_START index of the first occurrence of the plural number in a sub-message.
2005 * Returns -1 if it is a REPLACE_NUMBER.
2006 * Returns 0 if there is neither.
2008 private int findFirstPluralNumberArg(int msgStart, String argName) {
2009 for(int i=msgStart+1;; ++i) {
2010 Part part=msgPattern.getPart(i);
2011 Part.Type type=part.getType();
2012 if(type==Part.Type.MSG_LIMIT) {
2015 if(type==Part.Type.REPLACE_NUMBER) {
2018 if(type==Part.Type.ARG_START) {
2019 ArgType argType=part.getArgType();
2020 if(argName.length()!=0 && (argType==ArgType.NONE || argType==ArgType.SIMPLE)) {
2021 part=msgPattern.getPart(i+1); // ARG_NUMBER or ARG_NAME
2022 if(msgPattern.partSubstringMatches(part, argName)) {
2026 i=msgPattern.getLimitPartIndex(i);
2032 * Mutable input/output values for the PluralSelectorProvider.
2033 * Separate so that it is possible to make MessageFormat Freezable.
2035 private static final class PluralSelectorContext {
2036 private PluralSelectorContext(int start, String name, Number num, double off) {
2039 // number needs to be set even when select() is not called.
2040 // Keep it as a Number/Formattable:
2041 // For format() methods, and to preserve information (e.g., BigDecimal).
2045 number = num.doubleValue() - off;
2050 public String toString() {
2051 throw new AssertionError("PluralSelectorContext being formatted, rather than its number");
2054 // Input values for plural selection with decimals.
2057 /** argument number - plural offset */
2060 // Output values for plural selection with decimals.
2061 /** -1 if REPLACE_NUMBER, 0 arg not found, >0 ARG_START index */
2064 /** formatted argument number - plural offset */
2065 String numberString;
2066 /** true if number-offset was formatted with the stock number formatter */
2067 boolean forReplaceNumber;
2071 * This provider helps defer instantiation of a PluralRules object
2072 * until we actually need to select a keyword.
2073 * For example, if the number matches an explicit-value selector like "=1"
2074 * we do not need any PluralRules.
2076 private static final class PluralSelectorProvider implements PluralFormat.PluralSelector {
2077 public PluralSelectorProvider(MessageFormat mf, PluralType type) {
2081 public String select(Object ctx, double number) {
2083 rules = PluralRules.forLocale(msgFormat.ulocale, type);
2085 // Select a sub-message according to how the number is formatted,
2086 // which is specified in the selected sub-message.
2087 // We avoid this circle by looking at how
2088 // the number is formatted in the "other" sub-message
2089 // which must always be present and usually contains the number.
2090 // Message authors should be consistent across sub-messages.
2091 PluralSelectorContext context = (PluralSelectorContext)ctx;
2092 int otherIndex = msgFormat.findOtherSubMessage(context.startIndex);
2093 context.numberArgIndex = msgFormat.findFirstPluralNumberArg(otherIndex, context.argName);
2094 if(context.numberArgIndex > 0 && msgFormat.cachedFormatters != null) {
2095 context.formatter = msgFormat.cachedFormatters.get(context.numberArgIndex);
2097 if(context.formatter == null) {
2098 context.formatter = msgFormat.getStockNumberFormatter();
2099 context.forReplaceNumber = true;
2101 assert context.number.doubleValue() == number; // argument number minus the offset
2102 context.numberString = context.formatter.format(context.number);
2103 if(context.formatter instanceof DecimalFormat) {
2104 FixedDecimal dec = ((DecimalFormat)context.formatter).getFixedDecimal(number);
2105 return rules.select(dec);
2107 return rules.select(number);
2110 private MessageFormat msgFormat;
2111 private PluralRules rules;
2112 private PluralType type;
2115 @SuppressWarnings("unchecked")
2116 private void format(Object arguments, AppendableWrapper result, FieldPosition fp) {
2117 if ((arguments == null || arguments instanceof Map)) {
2118 format(null, (Map<String, Object>)arguments, result, fp);
2120 format((Object[])arguments, null, result, fp);
2125 * Internal routine used by format.
2127 * @throws IllegalArgumentException if an argument in the
2128 * <code>arguments</code> map is not of the type
2129 * expected by the format element(s) that use it.
2131 private void format(Object[] arguments, Map<String, Object> argsMap,
2132 AppendableWrapper dest, FieldPosition fp) {
2133 if (arguments != null && msgPattern.hasNamedArguments()) {
2134 throw new IllegalArgumentException(
2135 "This method is not available in MessageFormat objects " +
2136 "that use alphanumeric argument names.");
2138 format(0, null, arguments, argsMap, dest, fp);
2141 private void resetPattern() {
2142 if (msgPattern != null) {
2145 if (cachedFormatters != null) {
2146 cachedFormatters.clear();
2148 customFormatArgStarts = null;
2151 private static final String[] typeList =
2152 { "number", "date", "time", "spellout", "ordinal", "duration" };
2153 private static final int
2161 private static final String[] modifierList =
2162 {"", "currency", "percent", "integer"};
2164 private static final int
2166 MODIFIER_CURRENCY = 1,
2167 MODIFIER_PERCENT = 2,
2168 MODIFIER_INTEGER = 3;
2170 private static final String[] dateModifierList =
2171 {"", "short", "medium", "long", "full"};
2173 private static final int
2174 DATE_MODIFIER_EMPTY = 0,
2175 DATE_MODIFIER_SHORT = 1,
2176 DATE_MODIFIER_MEDIUM = 2,
2177 DATE_MODIFIER_LONG = 3,
2178 DATE_MODIFIER_FULL = 4;
2180 // Creates an appropriate Format object for the type and style passed.
2181 // Both arguments cannot be null.
2182 private Format createAppropriateFormat(String type, String style) {
2183 Format newFormat = null;
2184 int subformatType = findKeyword(type, typeList);
2185 switch (subformatType){
2187 switch (findKeyword(style, modifierList)) {
2188 case MODIFIER_EMPTY:
2189 newFormat = NumberFormat.getInstance(ulocale);
2191 case MODIFIER_CURRENCY:
2192 newFormat = NumberFormat.getCurrencyInstance(ulocale);
2194 case MODIFIER_PERCENT:
2195 newFormat = NumberFormat.getPercentInstance(ulocale);
2197 case MODIFIER_INTEGER:
2198 newFormat = NumberFormat.getIntegerInstance(ulocale);
2201 newFormat = new DecimalFormat(style,
2202 new DecimalFormatSymbols(ulocale));
2207 switch (findKeyword(style, dateModifierList)) {
2208 case DATE_MODIFIER_EMPTY:
2209 newFormat = DateFormat.getDateInstance(DateFormat.DEFAULT, ulocale);
2211 case DATE_MODIFIER_SHORT:
2212 newFormat = DateFormat.getDateInstance(DateFormat.SHORT, ulocale);
2214 case DATE_MODIFIER_MEDIUM:
2215 newFormat = DateFormat.getDateInstance(DateFormat.DEFAULT, ulocale);
2217 case DATE_MODIFIER_LONG:
2218 newFormat = DateFormat.getDateInstance(DateFormat.LONG, ulocale);
2220 case DATE_MODIFIER_FULL:
2221 newFormat = DateFormat.getDateInstance(DateFormat.FULL, ulocale);
2224 newFormat = new SimpleDateFormat(style, ulocale);
2229 switch (findKeyword(style, dateModifierList)) {
2230 case DATE_MODIFIER_EMPTY:
2231 newFormat = DateFormat.getTimeInstance(DateFormat.DEFAULT, ulocale);
2233 case DATE_MODIFIER_SHORT:
2234 newFormat = DateFormat.getTimeInstance(DateFormat.SHORT, ulocale);
2236 case DATE_MODIFIER_MEDIUM:
2237 newFormat = DateFormat.getTimeInstance(DateFormat.DEFAULT, ulocale);
2239 case DATE_MODIFIER_LONG:
2240 newFormat = DateFormat.getTimeInstance(DateFormat.LONG, ulocale);
2242 case DATE_MODIFIER_FULL:
2243 newFormat = DateFormat.getTimeInstance(DateFormat.FULL, ulocale);
2246 newFormat = new SimpleDateFormat(style, ulocale);
2252 RuleBasedNumberFormat rbnf = new RuleBasedNumberFormat(ulocale,
2253 RuleBasedNumberFormat.SPELLOUT);
2254 String ruleset = style.trim();
2255 if (ruleset.length() != 0) {
2257 rbnf.setDefaultRuleSet(ruleset);
2259 catch (Exception e) {
2260 // warn invalid ruleset
2268 RuleBasedNumberFormat rbnf = new RuleBasedNumberFormat(ulocale,
2269 RuleBasedNumberFormat.ORDINAL);
2270 String ruleset = style.trim();
2271 if (ruleset.length() != 0) {
2273 rbnf.setDefaultRuleSet(ruleset);
2275 catch (Exception e) {
2276 // warn invalid ruleset
2284 RuleBasedNumberFormat rbnf = new RuleBasedNumberFormat(ulocale,
2285 RuleBasedNumberFormat.DURATION);
2286 String ruleset = style.trim();
2287 if (ruleset.length() != 0) {
2289 rbnf.setDefaultRuleSet(ruleset);
2291 catch (Exception e) {
2292 // warn invalid ruleset
2299 throw new IllegalArgumentException("Unknown format type \"" + type + "\"");
2304 private static final Locale rootLocale = new Locale(""); // Locale.ROOT only @since 1.6
2306 private static final int findKeyword(String s, String[] list) {
2307 s = PatternProps.trimWhiteSpace(s).toLowerCase(rootLocale);
2308 for (int i = 0; i < list.length; ++i) {
2309 if (s.equals(list[i]))
2316 * Custom serialization, new in ICU 4.8.
2317 * We do not want to use default serialization because we only have a small
2318 * amount of persistent state which is better expressed explicitly
2319 * rather than via writing field objects.
2320 * @param out The output stream.
2321 * @serialData Writes the locale as a BCP 47 language tag string,
2322 * the MessagePattern.ApostropheMode as an object,
2323 * and the pattern string (null if none was applied).
2324 * Followed by an int with the number of (int formatIndex, Object formatter) pairs,
2325 * and that many such pairs, corresponding to previous setFormat() calls for custom formats.
2326 * Followed by an int with the number of (int, Object) pairs,
2327 * and that many such pairs, for future (post-ICU 4.8) extension of the serialization format.
2329 private void writeObject(java.io.ObjectOutputStream out) throws IOException {
2330 out.defaultWriteObject();
2331 // ICU 4.8 custom serialization.
2332 // locale as a BCP 47 language tag
2333 out.writeObject(ulocale.toLanguageTag());
2335 if (msgPattern == null) {
2336 msgPattern = new MessagePattern();
2338 out.writeObject(msgPattern.getApostropheMode());
2339 // message pattern string
2340 out.writeObject(msgPattern.getPatternString());
2341 // custom formatters
2342 if (customFormatArgStarts == null || customFormatArgStarts.isEmpty()) {
2345 out.writeInt(customFormatArgStarts.size());
2346 int formatIndex = 0;
2347 for (int partIndex = 0; (partIndex = nextTopLevelArgStart(partIndex)) >= 0;) {
2348 if (customFormatArgStarts.contains(partIndex)) {
2349 out.writeInt(formatIndex);
2350 out.writeObject(cachedFormatters.get(partIndex));
2355 // number of future (int, Object) pairs
2360 * Custom deserialization, new in ICU 4.8. See comments on writeObject().
2361 * @throws InvalidObjectException if the objects read from the stream is invalid.
2363 private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
2364 in.defaultReadObject();
2365 // ICU 4.8 custom deserialization.
2366 String languageTag = (String)in.readObject();
2367 ulocale = ULocale.forLanguageTag(languageTag);
2368 MessagePattern.ApostropheMode aposMode = (MessagePattern.ApostropheMode)in.readObject();
2369 if (msgPattern == null || aposMode != msgPattern.getApostropheMode()) {
2370 msgPattern = new MessagePattern(aposMode);
2372 String msg = (String)in.readObject();
2376 // custom formatters
2377 for (int numFormatters = in.readInt(); numFormatters > 0; --numFormatters) {
2378 int formatIndex = in.readInt();
2379 Format formatter = (Format)in.readObject();
2380 setFormat(formatIndex, formatter);
2382 // skip future (int, Object) pairs
2383 for (int numPairs = in.readInt(); numPairs > 0; --numPairs) {
2389 private void cacheExplicitFormats() {
2390 if (cachedFormatters != null) {
2391 cachedFormatters.clear();
2393 customFormatArgStarts = null;
2394 // The last two "parts" can at most be ARG_LIMIT and MSG_LIMIT
2395 // which we need not examine.
2396 int limit = msgPattern.countParts() - 2;
2397 // This loop starts at part index 1 because we do need to examine
2398 // ARG_START parts. (But we can ignore the MSG_START.)
2399 for(int i=1; i < limit; ++i) {
2400 Part part = msgPattern.getPart(i);
2401 if(part.getType()!=Part.Type.ARG_START) {
2404 ArgType argType=part.getArgType();
2405 if(argType != ArgType.SIMPLE) {
2410 String explicitType = msgPattern.getSubstring(msgPattern.getPart(i++));
2412 if ((part = msgPattern.getPart(i)).getType() == MessagePattern.Part.Type.ARG_STYLE) {
2413 style = msgPattern.getSubstring(part);
2416 Format formatter = createAppropriateFormat(explicitType, style);
2417 setArgStartFormat(index, formatter);
2422 * Sets a formatter for a MessagePattern ARG_START part index.
2424 private void setArgStartFormat(int argStart, Format formatter) {
2425 if (cachedFormatters == null) {
2426 cachedFormatters = new HashMap<Integer, Format>();
2428 cachedFormatters.put(argStart, formatter);
2432 * Sets a custom formatter for a MessagePattern ARG_START part index.
2433 * "Custom" formatters are provided by the user via setFormat() or similar APIs.
2435 private void setCustomArgStartFormat(int argStart, Format formatter) {
2436 setArgStartFormat(argStart, formatter);
2437 if (customFormatArgStarts == null) {
2438 customFormatArgStarts = new HashSet<Integer>();
2440 customFormatArgStarts.add(argStart);
2443 private static final char SINGLE_QUOTE = '\'';
2444 private static final char CURLY_BRACE_LEFT = '{';
2445 private static final char CURLY_BRACE_RIGHT = '}';
2447 private static final int STATE_INITIAL = 0;
2448 private static final int STATE_SINGLE_QUOTE = 1;
2449 private static final int STATE_IN_QUOTE = 2;
2450 private static final int STATE_MSG_ELEMENT = 3;
2453 * {@icu} Converts an 'apostrophe-friendly' pattern into a standard
2455 * <em>This is obsolete for ICU 4.8 and higher MessageFormat pattern strings.</em>
2456 * It can still be useful together with the JDK MessageFormat.
2458 * <p>See the class description for more about apostrophes and quoting,
2459 * and differences between ICU and the JDK.
2461 * <p>The JDK MessageFormat and ICU 4.6 and earlier MessageFormat
2462 * treat all ASCII apostrophes as
2463 * quotes, which is problematic in some languages, e.g.
2464 * French, where apostrophe is commonly used. This utility
2465 * assumes that only an unpaired apostrophe immediately before
2466 * a brace is a true quote. Other unpaired apostrophes are paired,
2467 * and the resulting standard pattern string is returned.
2469 * <p><b>Note</b>: It is not guaranteed that the returned pattern
2470 * is indeed a valid pattern. The only effect is to convert
2471 * between patterns having different quoting semantics.
2473 * <p><b>Note</b>: This method only works on top-level messageText,
2474 * not messageText nested inside a complexArg.
2476 * @param pattern the 'apostrophe-friendly' pattern to convert
2477 * @return the standard equivalent of the original pattern
2480 public static String autoQuoteApostrophe(String pattern) {
2481 StringBuilder buf = new StringBuilder(pattern.length() * 2);
2482 int state = STATE_INITIAL;
2484 for (int i = 0, j = pattern.length(); i < j; ++i) {
2485 char c = pattern.charAt(i);
2490 state = STATE_SINGLE_QUOTE;
2492 case CURLY_BRACE_LEFT:
2493 state = STATE_MSG_ELEMENT;
2498 case STATE_SINGLE_QUOTE:
2501 state = STATE_INITIAL;
2503 case CURLY_BRACE_LEFT:
2504 case CURLY_BRACE_RIGHT:
2505 state = STATE_IN_QUOTE;
2508 buf.append(SINGLE_QUOTE);
2509 state = STATE_INITIAL;
2513 case STATE_IN_QUOTE:
2516 state = STATE_INITIAL;
2520 case STATE_MSG_ELEMENT:
2522 case CURLY_BRACE_LEFT:
2525 case CURLY_BRACE_RIGHT:
2526 if (--braceCount == 0) {
2527 state = STATE_INITIAL;
2533 default: // Never happens.
2540 if (state == STATE_SINGLE_QUOTE || state == STATE_IN_QUOTE) {
2541 buf.append(SINGLE_QUOTE);
2543 return new String(buf);
2547 * Convenience wrapper for Appendable, tracks the result string length.
2548 * Also, Appendable throws IOException, and we turn that into a RuntimeException
2549 * so that we need no throws clauses.
2551 private static final class AppendableWrapper {
2552 public AppendableWrapper(StringBuilder sb) {
2554 length = sb.length();
2558 public AppendableWrapper(StringBuffer sb) {
2560 length = sb.length();
2564 public void useAttributes() {
2565 attributes = new ArrayList<AttributeAndPosition>();
2568 public void append(CharSequence s) {
2571 length += s.length();
2572 } catch(IOException e) {
2573 throw new RuntimeException(e);
2577 public void append(CharSequence s, int start, int limit) {
2579 app.append(s, start, limit);
2580 length += limit - start;
2581 } catch(IOException e) {
2582 throw new RuntimeException(e);
2586 public void append(CharacterIterator iterator) {
2587 length += append(app, iterator);
2590 public static int append(Appendable result, CharacterIterator iterator) {
2592 int start = iterator.getBeginIndex();
2593 int limit = iterator.getEndIndex();
2594 int length = limit - start;
2595 if (start < limit) {
2596 result.append(iterator.first());
2597 while (++start < limit) {
2598 result.append(iterator.next());
2602 } catch(IOException e) {
2603 throw new RuntimeException(e);
2607 public void formatAndAppend(Format formatter, Object arg) {
2608 if (attributes == null) {
2609 append(formatter.format(arg));
2611 AttributedCharacterIterator formattedArg = formatter.formatToCharacterIterator(arg);
2612 int prevLength = length;
2613 append(formattedArg);
2614 // Copy all of the attributes from formattedArg to our attributes list.
2615 formattedArg.first();
2616 int start = formattedArg.getIndex(); // Should be 0 but might not be.
2617 int limit = formattedArg.getEndIndex(); // == start + length - prevLength
2618 int offset = prevLength - start; // Adjust attribute indexes for the result string.
2619 while (start < limit) {
2620 Map<Attribute, Object> map = formattedArg.getAttributes();
2621 int runLimit = formattedArg.getRunLimit();
2622 if (map.size() != 0) {
2623 for (Map.Entry<Attribute, Object> entry : map.entrySet()) {
2625 new AttributeAndPosition(
2626 entry.getKey(), entry.getValue(),
2627 offset + start, offset + runLimit));
2631 formattedArg.setIndex(start);
2636 public void formatAndAppend(Format formatter, Object arg, String argString) {
2637 if (attributes == null && argString != null) {
2640 formatAndAppend(formatter, arg);
2644 private Appendable app;
2646 private List<AttributeAndPosition> attributes;
2649 private static final class AttributeAndPosition {
2651 * Defaults the field to Field.ARGUMENT.
2653 public AttributeAndPosition(Object fieldValue, int startIndex, int limitIndex) {
2654 init(Field.ARGUMENT, fieldValue, startIndex, limitIndex);
2657 public AttributeAndPosition(Attribute field, Object fieldValue, int startIndex, int limitIndex) {
2658 init(field, fieldValue, startIndex, limitIndex);
2661 public void init(Attribute field, Object fieldValue, int startIndex, int limitIndex) {
2668 private Attribute key;
2669 private Object value;