1 // 2016 and later: Unicode, Inc. and others. 2 // License & terms of use: http://www.unicode.org/copyright.html#License 3 /* 4 ********************************************************************** 5 * Copyright (c) 2004-2016, International Business Machines 6 * Corporation and others. All Rights Reserved. 7 ********************************************************************** 8 * Author: Alan Liu 9 * Created: April 6, 2004 10 * Since: ICU 3.0 11 ********************************************************************** 12 */ 13 package com.ibm.icu.text; 14 15 import java.io.IOException; 16 import java.io.InvalidObjectException; 17 import java.io.ObjectInputStream; 18 import java.text.AttributedCharacterIterator; 19 import java.text.AttributedCharacterIterator.Attribute; 20 import java.text.AttributedString; 21 import java.text.CharacterIterator; 22 import java.text.ChoiceFormat; 23 import java.text.FieldPosition; 24 import java.text.Format; 25 import java.text.ParseException; 26 import java.text.ParsePosition; 27 import java.util.ArrayList; 28 import java.util.Date; 29 import java.util.HashMap; 30 import java.util.HashSet; 31 import java.util.Iterator; 32 import java.util.List; 33 import java.util.Locale; 34 import java.util.Map; 35 import java.util.Set; 36 37 import com.ibm.icu.impl.PatternProps; 38 import com.ibm.icu.impl.Utility; 39 import com.ibm.icu.text.MessagePattern.ArgType; 40 import com.ibm.icu.text.MessagePattern.Part; 41 import com.ibm.icu.text.PluralRules.FixedDecimal; 42 import com.ibm.icu.text.PluralRules.PluralType; 43 import com.ibm.icu.util.ICUUncheckedIOException; 44 import com.ibm.icu.util.ULocale; 45 import com.ibm.icu.util.ULocale.Category; 46 47 /** 48 * {@icuenhanced java.text.MessageFormat}.{@icu _usage_} 49 * 50 * <p>MessageFormat prepares strings for display to users, 51 * with optional arguments (variables/placeholders). 52 * The arguments can occur in any order, which is necessary for translation 53 * into languages with different grammars. 54 * 55 * <p>A MessageFormat is constructed from a <em>pattern</em> string 56 * with arguments in {curly braces} which will be replaced by formatted values. 57 * 58 * <p><code>MessageFormat</code> differs from the other <code>Format</code> 59 * classes in that you create a <code>MessageFormat</code> object with one 60 * of its constructors (not with a <code>getInstance</code> style factory 61 * method). Factory methods aren't necessary because <code>MessageFormat</code> 62 * itself doesn't implement locale-specific behavior. Any locale-specific 63 * behavior is defined by the pattern that you provide and the 64 * subformats used for inserted arguments. 65 * 66 * <p>Arguments can be named (using identifiers) or numbered (using small ASCII-digit integers). 67 * Some of the API methods work only with argument numbers and throw an exception 68 * if the pattern has named arguments (see {@link #usesNamedArguments()}). 69 * 70 * <p>An argument might not specify any format type. In this case, 71 * a Number value is formatted with a default (for the locale) NumberFormat, 72 * a Date value is formatted with a default (for the locale) DateFormat, 73 * and for any other value its toString() value is used. 74 * 75 * <p>An argument might specify a "simple" type for which the specified 76 * Format object is created, cached and used. 77 * 78 * <p>An argument might have a "complex" type with nested MessageFormat sub-patterns. 79 * During formatting, one of these sub-messages is selected according to the argument value 80 * and recursively formatted. 81 * 82 * <p>After construction, a custom Format object can be set for 83 * a top-level argument, overriding the default formatting and parsing behavior 84 * for that argument. 85 * However, custom formatting can be achieved more simply by writing 86 * a typeless argument in the pattern string 87 * and supplying it with a preformatted string value. 88 * 89 * <p>When formatting, MessageFormat takes a collection of argument values 90 * and writes an output string. 91 * The argument values may be passed as an array 92 * (when the pattern contains only numbered arguments) 93 * or as a Map (which works for both named and numbered arguments). 94 * 95 * <p>Each argument is matched with one of the input values by array index or map key 96 * and formatted according to its pattern specification 97 * (or using a custom Format object if one was set). 98 * A numbered pattern argument is matched with a map key that contains that number 99 * as an ASCII-decimal-digit string (without leading zero). 100 * 101 * <h3><a name="patterns">Patterns and Their Interpretation</a></h3> 102 * 103 * <code>MessageFormat</code> uses patterns of the following form: 104 * <blockquote><pre> 105 * message = messageText (argument messageText)* 106 * argument = noneArg | simpleArg | complexArg 107 * complexArg = choiceArg | pluralArg | selectArg | selectordinalArg 108 * 109 * noneArg = '{' argNameOrNumber '}' 110 * simpleArg = '{' argNameOrNumber ',' argType [',' argStyle] '}' 111 * choiceArg = '{' argNameOrNumber ',' "choice" ',' choiceStyle '}' 112 * pluralArg = '{' argNameOrNumber ',' "plural" ',' pluralStyle '}' 113 * selectArg = '{' argNameOrNumber ',' "select" ',' selectStyle '}' 114 * selectordinalArg = '{' argNameOrNumber ',' "selectordinal" ',' pluralStyle '}' 115 * 116 * choiceStyle: see {@link ChoiceFormat} 117 * pluralStyle: see {@link PluralFormat} 118 * selectStyle: see {@link SelectFormat} 119 * 120 * argNameOrNumber = argName | argNumber 121 * argName = [^[[:Pattern_Syntax:][:Pattern_White_Space:]]]+ 122 * argNumber = '0' | ('1'..'9' ('0'..'9')*) 123 * 124 * argType = "number" | "date" | "time" | "spellout" | "ordinal" | "duration" 125 * argStyle = "short" | "medium" | "long" | "full" | "integer" | "currency" | "percent" | argStyleText 126 * </pre></blockquote> 127 * 128 * <ul> 129 * <li>messageText can contain quoted literal strings including syntax characters. 130 * A quoted literal string begins with an ASCII apostrophe and a syntax character 131 * (usually a {curly brace}) and continues until the next single apostrophe. 132 * A double ASCII apostrohpe inside or outside of a quoted string represents 133 * one literal apostrophe. 134 * <li>Quotable syntax characters are the {curly braces} in all messageText parts, 135 * plus the '#' sign in a messageText immediately inside a pluralStyle, 136 * and the '|' symbol in a messageText immediately inside a choiceStyle. 137 * <li>See also {@link MessagePattern.ApostropheMode} 138 * <li>In argStyleText, every single ASCII apostrophe begins and ends quoted literal text, 139 * and unquoted {curly braces} must occur in matched pairs. 140 * </ul> 141 * 142 * <p>Recommendation: Use the real apostrophe (single quote) character \u2019 for 143 * human-readable text, and use the ASCII apostrophe (\u0027 ' ) 144 * only in program syntax, like quoting in MessageFormat. 145 * See the annotations for U+0027 Apostrophe in The Unicode Standard. 146 * 147 * <p>The <code>choice</code> argument type is deprecated. 148 * Use <code>plural</code> arguments for proper plural selection, 149 * and <code>select</code> arguments for simple selection among a fixed set of choices. 150 * 151 * <p>The <code>argType</code> and <code>argStyle</code> values are used to create 152 * a <code>Format</code> instance for the format element. The following 153 * table shows how the values map to Format instances. Combinations not 154 * shown in the table are illegal. Any <code>argStyleText</code> must 155 * be a valid pattern string for the Format subclass used. 156 * 157 * <table border=1> 158 * <tr> 159 * <th>argType 160 * <th>argStyle 161 * <th>resulting Format object 162 * <tr> 163 * <td colspan=2><i>(none)</i> 164 * <td><code>null</code> 165 * <tr> 166 * <td rowspan=5><code>number</code> 167 * <td><i>(none)</i> 168 * <td><code>NumberFormat.getInstance(getLocale())</code> 169 * <tr> 170 * <td><code>integer</code> 171 * <td><code>NumberFormat.getIntegerInstance(getLocale())</code> 172 * <tr> 173 * <td><code>currency</code> 174 * <td><code>NumberFormat.getCurrencyInstance(getLocale())</code> 175 * <tr> 176 * <td><code>percent</code> 177 * <td><code>NumberFormat.getPercentInstance(getLocale())</code> 178 * <tr> 179 * <td><i>argStyleText</i> 180 * <td><code>new DecimalFormat(argStyleText, new DecimalFormatSymbols(getLocale()))</code> 181 * <tr> 182 * <td rowspan=6><code>date</code> 183 * <td><i>(none)</i> 184 * <td><code>DateFormat.getDateInstance(DateFormat.DEFAULT, getLocale())</code> 185 * <tr> 186 * <td><code>short</code> 187 * <td><code>DateFormat.getDateInstance(DateFormat.SHORT, getLocale())</code> 188 * <tr> 189 * <td><code>medium</code> 190 * <td><code>DateFormat.getDateInstance(DateFormat.DEFAULT, getLocale())</code> 191 * <tr> 192 * <td><code>long</code> 193 * <td><code>DateFormat.getDateInstance(DateFormat.LONG, getLocale())</code> 194 * <tr> 195 * <td><code>full</code> 196 * <td><code>DateFormat.getDateInstance(DateFormat.FULL, getLocale())</code> 197 * <tr> 198 * <td><i>argStyleText</i> 199 * <td><code>new SimpleDateFormat(argStyleText, getLocale())</code> 200 * <tr> 201 * <td rowspan=6><code>time</code> 202 * <td><i>(none)</i> 203 * <td><code>DateFormat.getTimeInstance(DateFormat.DEFAULT, getLocale())</code> 204 * <tr> 205 * <td><code>short</code> 206 * <td><code>DateFormat.getTimeInstance(DateFormat.SHORT, getLocale())</code> 207 * <tr> 208 * <td><code>medium</code> 209 * <td><code>DateFormat.getTimeInstance(DateFormat.DEFAULT, getLocale())</code> 210 * <tr> 211 * <td><code>long</code> 212 * <td><code>DateFormat.getTimeInstance(DateFormat.LONG, getLocale())</code> 213 * <tr> 214 * <td><code>full</code> 215 * <td><code>DateFormat.getTimeInstance(DateFormat.FULL, getLocale())</code> 216 * <tr> 217 * <td><i>argStyleText</i> 218 * <td><code>new SimpleDateFormat(argStyleText, getLocale())</code> 219 * <tr> 220 * <td><code>spellout</code> 221 * <td><i>argStyleText (optional)</i> 222 * <td><code>new RuleBasedNumberFormat(getLocale(), RuleBasedNumberFormat.SPELLOUT) 223 * <br> .setDefaultRuleset(argStyleText);</code> 224 * <tr> 225 * <td><code>ordinal</code> 226 * <td><i>argStyleText (optional)</i> 227 * <td><code>new RuleBasedNumberFormat(getLocale(), RuleBasedNumberFormat.ORDINAL) 228 * <br> .setDefaultRuleset(argStyleText);</code> 229 * <tr> 230 * <td><code>duration</code> 231 * <td><i>argStyleText (optional)</i> 232 * <td><code>new RuleBasedNumberFormat(getLocale(), RuleBasedNumberFormat.DURATION) 233 * <br> .setDefaultRuleset(argStyleText);</code> 234 * </table> 235 * 236 * <h4><a name="diffsjdk">Differences from java.text.MessageFormat</a></h4> 237 * 238 * <p>The ICU MessageFormat supports both named and numbered arguments, 239 * while the JDK MessageFormat only supports numbered arguments. 240 * Named arguments make patterns more readable. 241 * 242 * <p>ICU implements a more user-friendly apostrophe quoting syntax. 243 * In message text, an apostrophe only begins quoting literal text 244 * if it immediately precedes a syntax character (mostly {curly braces}).<br> 245 * In the JDK MessageFormat, an apostrophe always begins quoting, 246 * which requires common text like "don't" and "aujourd'hui" 247 * to be written with doubled apostrophes like "don''t" and "aujourd''hui". 248 * For more details see {@link MessagePattern.ApostropheMode}. 249 * 250 * <p>ICU does not create a ChoiceFormat object for a choiceArg, pluralArg or selectArg 251 * but rather handles such arguments itself. 252 * The JDK MessageFormat does create and use a ChoiceFormat object 253 * (<code>new ChoiceFormat(argStyleText)</code>). 254 * The JDK does not support plural and select arguments at all. 255 * 256 * <h4>Usage Information</h4> 257 * 258 * <p>Here are some examples of usage: 259 * <blockquote> 260 * <pre> 261 * Object[] arguments = { 262 * 7, 263 * new Date(System.currentTimeMillis()), 264 * "a disturbance in the Force" 265 * }; 266 * 267 * String result = MessageFormat.format( 268 * "At {1,time} on {1,date}, there was {2} on planet {0,number,integer}.", 269 * arguments); 270 * 271 * <em>output</em>: At 12:30 PM on Jul 3, 2053, there was a disturbance 272 * in the Force on planet 7. 273 * 274 * </pre> 275 * </blockquote> 276 * Typically, the message format will come from resources, and the 277 * arguments will be dynamically set at runtime. 278 * 279 * <p>Example 2: 280 * <blockquote> 281 * <pre> 282 * Object[] testArgs = { 3, "MyDisk" }; 283 * 284 * MessageFormat form = new MessageFormat( 285 * "The disk \"{1}\" contains {0} file(s)."); 286 * 287 * System.out.println(form.format(testArgs)); 288 * 289 * // output, with different testArgs 290 * <em>output</em>: The disk "MyDisk" contains 0 file(s). 291 * <em>output</em>: The disk "MyDisk" contains 1 file(s). 292 * <em>output</em>: The disk "MyDisk" contains 1,273 file(s). 293 * </pre> 294 * </blockquote> 295 * 296 * <p>For messages that include plural forms, you can use a plural argument: 297 * <pre> 298 * MessageFormat msgFmt = new MessageFormat( 299 * "{num_files, plural, " + 300 * "=0{There are no files on disk \"{disk_name}\".}" + 301 * "=1{There is one file on disk \"{disk_name}\".}" + 302 * "other{There are # files on disk \"{disk_name}\".}}", 303 * ULocale.ENGLISH); 304 * Map args = new HashMap(); 305 * args.put("num_files", 0); 306 * args.put("disk_name", "MyDisk"); 307 * System.out.println(msgFmt.format(args)); 308 * args.put("num_files", 3); 309 * System.out.println(msgFmt.format(args)); 310 * 311 * <em>output</em>: 312 * There are no files on disk "MyDisk". 313 * There are 3 files on "MyDisk". 314 * </pre> 315 * See {@link PluralFormat} and {@link PluralRules} for details. 316 * 317 * <h4><a name="synchronization">Synchronization</a></h4> 318 * 319 * <p>MessageFormats are not synchronized. 320 * It is recommended to create separate format instances for each thread. 321 * If multiple threads access a format concurrently, it must be synchronized 322 * externally. 323 * 324 * @see java.util.Locale 325 * @see Format 326 * @see NumberFormat 327 * @see DecimalFormat 328 * @see ChoiceFormat 329 * @see PluralFormat 330 * @see SelectFormat 331 * @author Mark Davis 332 * @author Markus Scherer 333 * @stable ICU 3.0 334 */ 335 public class MessageFormat extends UFormat { 336 337 // Incremented by 1 for ICU 4.8's new format. 338 static final long serialVersionUID = 7136212545847378652L; 339 340 /** 341 * Constructs a MessageFormat for the default <code>FORMAT</code> locale and the 342 * specified pattern. 343 * Sets the locale and calls applyPattern(pattern). 344 * 345 * @param pattern the pattern for this message format 346 * @exception IllegalArgumentException if the pattern is invalid 347 * @see Category#FORMAT 348 * @stable ICU 3.0 349 */ 350 public MessageFormat(String pattern) { 351 this.ulocale = ULocale.getDefault(Category.FORMAT); 352 applyPattern(pattern); 353 } 354 355 /** 356 * Constructs a MessageFormat for the specified locale and 357 * pattern. 358 * Sets the locale and calls applyPattern(pattern). 359 * 360 * @param pattern the pattern for this message format 361 * @param locale the locale for this message format 362 * @exception IllegalArgumentException if the pattern is invalid 363 * @stable ICU 3.0 364 */ 365 public MessageFormat(String pattern, Locale locale) { 366 this(pattern, ULocale.forLocale(locale)); 367 } 368 369 /** 370 * Constructs a MessageFormat for the specified locale and 371 * pattern. 372 * Sets the locale and calls applyPattern(pattern). 373 * 374 * @param pattern the pattern for this message format 375 * @param locale the locale for this message format 376 * @exception IllegalArgumentException if the pattern is invalid 377 * @stable ICU 3.2 378 */ 379 public MessageFormat(String pattern, ULocale locale) { 380 this.ulocale = locale; 381 applyPattern(pattern); 382 } 383 384 /** 385 * Sets the locale to be used for creating argument Format objects. 386 * This affects subsequent calls to the {@link #applyPattern applyPattern} 387 * method as well as to the <code>format</code> and 388 * {@link #formatToCharacterIterator formatToCharacterIterator} methods. 389 * 390 * @param locale the locale to be used when creating or comparing subformats 391 * @stable ICU 3.0 392 */ 393 public void setLocale(Locale locale) { 394 setLocale(ULocale.forLocale(locale)); 395 } 396 397 /** 398 * Sets the locale to be used for creating argument Format objects. 399 * This affects subsequent calls to the {@link #applyPattern applyPattern} 400 * method as well as to the <code>format</code> and 401 * {@link #formatToCharacterIterator formatToCharacterIterator} methods. 402 * 403 * @param locale the locale to be used when creating or comparing subformats 404 * @stable ICU 3.2 405 */ 406 public void setLocale(ULocale locale) { 407 /* Save the pattern, and then reapply so that */ 408 /* we pick up any changes in locale specific */ 409 /* elements */ 410 String existingPattern = toPattern(); /*ibm.3550*/ 411 this.ulocale = locale; 412 // Invalidate all stock formatters. They are no longer valid since 413 // the locale has changed. 414 stockDateFormatter = null; 415 stockNumberFormatter = null; 416 pluralProvider = null; 417 ordinalProvider = null; 418 applyPattern(existingPattern); /*ibm.3550*/ 419 } 420 421 /** 422 * Returns the locale that's used when creating or comparing subformats. 423 * 424 * @return the locale used when creating or comparing subformats 425 * @stable ICU 3.0 426 */ 427 public Locale getLocale() { 428 return ulocale.toLocale(); 429 } 430 431 /** 432 * {@icu} Returns the locale that's used when creating argument Format objects. 433 * 434 * @return the locale used when creating or comparing subformats 435 * @stable ICU 3.2 436 */ 437 public ULocale getULocale() { 438 return ulocale; 439 } 440 441 /** 442 * Sets the pattern used by this message format. 443 * Parses the pattern and caches Format objects for simple argument types. 444 * Patterns and their interpretation are specified in the 445 * <a href="#patterns">class description</a>. 446 * 447 * @param pttrn the pattern for this message format 448 * @throws IllegalArgumentException if the pattern is invalid 449 * @stable ICU 3.0 450 */ 451 public void applyPattern(String pttrn) { 452 try { 453 if (msgPattern == null) { 454 msgPattern = new MessagePattern(pttrn); 455 } else { 456 msgPattern.parse(pttrn); 457 } 458 // Cache the formats that are explicitly mentioned in the message pattern. 459 cacheExplicitFormats(); 460 } catch(RuntimeException e) { 461 resetPattern(); 462 throw e; 463 } 464 } 465 466 /** 467 * {@icu} Sets the ApostropheMode and the pattern used by this message format. 468 * Parses the pattern and caches Format objects for simple argument types. 469 * Patterns and their interpretation are specified in the 470 * <a href="#patterns">class description</a>. 471 * <p> 472 * This method is best used only once on a given object to avoid confusion about the mode, 473 * and after constructing the object with an empty pattern string to minimize overhead. 474 * 475 * @param pattern the pattern for this message format 476 * @param aposMode the new ApostropheMode 477 * @throws IllegalArgumentException if the pattern is invalid 478 * @see MessagePattern.ApostropheMode 479 * @stable ICU 4.8 480 */ 481 public void applyPattern(String pattern, MessagePattern.ApostropheMode aposMode) { 482 if (msgPattern == null) { 483 msgPattern = new MessagePattern(aposMode); 484 } else if (aposMode != msgPattern.getApostropheMode()) { 485 msgPattern.clearPatternAndSetApostropheMode(aposMode); 486 } 487 applyPattern(pattern); 488 } 489 490 /** 491 * {@icu} 492 * @return this instance's ApostropheMode. 493 * @stable ICU 4.8 494 */ 495 public MessagePattern.ApostropheMode getApostropheMode() { 496 if (msgPattern == null) { 497 msgPattern = new MessagePattern(); // Sets the default mode. 498 } 499 return msgPattern.getApostropheMode(); 500 } 501 502 /** 503 * Returns the applied pattern string. 504 * @return the pattern string 505 * @throws IllegalStateException after custom Format objects have been set 506 * via setFormat() or similar APIs 507 * @stable ICU 3.0 508 */ 509 public String toPattern() { 510 // Return the original, applied pattern string, or else "". 511 // Note: This does not take into account 512 // - changes from setFormat() and similar methods, or 513 // - normalization of apostrophes and arguments, for example, 514 // whether some date/time/number formatter was created via a pattern 515 // but is equivalent to the "medium" default format. 516 if (customFormatArgStarts != null) { 517 throw new IllegalStateException( 518 "toPattern() is not supported after custom Format objects "+ 519 "have been set via setFormat() or similar APIs"); 520 } 521 if (msgPattern == null) { 522 return ""; 523 } 524 String originalPattern = msgPattern.getPatternString(); 525 return originalPattern == null ? "" : originalPattern; 526 } 527 528 /** 529 * Returns the part index of the next ARG_START after partIndex, or -1 if there is none more. 530 * @param partIndex Part index of the previous ARG_START (initially 0). 531 */ 532 private int nextTopLevelArgStart(int partIndex) { 533 if (partIndex != 0) { 534 partIndex = msgPattern.getLimitPartIndex(partIndex); 535 } 536 for (;;) { 537 MessagePattern.Part.Type type = msgPattern.getPartType(++partIndex); 538 if (type == MessagePattern.Part.Type.ARG_START) { 539 return partIndex; 540 } 541 if (type == MessagePattern.Part.Type.MSG_LIMIT) { 542 return -1; 543 } 544 } 545 } 546 547 private boolean argNameMatches(int partIndex, String argName, int argNumber) { 548 Part part = msgPattern.getPart(partIndex); 549 return part.getType() == MessagePattern.Part.Type.ARG_NAME ? 550 msgPattern.partSubstringMatches(part, argName) : 551 part.getValue() == argNumber; // ARG_NUMBER 552 } 553 554 private String getArgName(int partIndex) { 555 Part part = msgPattern.getPart(partIndex); 556 if (part.getType() == MessagePattern.Part.Type.ARG_NAME) { 557 return msgPattern.getSubstring(part); 558 } else { 559 return Integer.toString(part.getValue()); 560 } 561 } 562 563 /** 564 * Sets the Format objects to use for the values passed into 565 * <code>format</code> methods or returned from <code>parse</code> 566 * methods. The indices of elements in <code>newFormats</code> 567 * correspond to the argument indices used in the previously set 568 * pattern string. 569 * The order of formats in <code>newFormats</code> thus corresponds to 570 * the order of elements in the <code>arguments</code> array passed 571 * to the <code>format</code> methods or the result array returned 572 * by the <code>parse</code> methods. 573 * <p> 574 * If an argument index is used for more than one format element 575 * in the pattern string, then the corresponding new format is used 576 * for all such format elements. If an argument index is not used 577 * for any format element in the pattern string, then the 578 * corresponding new format is ignored. If fewer formats are provided 579 * than needed, then only the formats for argument indices less 580 * than <code>newFormats.length</code> are replaced. 581 * 582 * This method is only supported if the format does not use 583 * named arguments, otherwise an IllegalArgumentException is thrown. 584 * 585 * @param newFormats the new formats to use 586 * @throws NullPointerException if <code>newFormats</code> is null 587 * @throws IllegalArgumentException if this formatter uses named arguments 588 * @stable ICU 3.0 589 */ 590 public void setFormatsByArgumentIndex(Format[] newFormats) { 591 if (msgPattern.hasNamedArguments()) { 592 throw new IllegalArgumentException( 593 "This method is not available in MessageFormat objects " + 594 "that use alphanumeric argument names."); 595 } 596 for (int partIndex = 0; (partIndex = nextTopLevelArgStart(partIndex)) >= 0;) { 597 int argNumber = msgPattern.getPart(partIndex + 1).getValue(); 598 if (argNumber < newFormats.length) { 599 setCustomArgStartFormat(partIndex, newFormats[argNumber]); 600 } 601 } 602 } 603 604 /** 605 * {@icu} Sets the Format objects to use for the values passed into 606 * <code>format</code> methods or returned from <code>parse</code> 607 * methods. The keys in <code>newFormats</code> are the argument 608 * names in the previously set pattern string, and the values 609 * are the formats. 610 * <p> 611 * Only argument names from the pattern string are considered. 612 * Extra keys in <code>newFormats</code> that do not correspond 613 * to an argument name are ignored. Similarly, if there is no 614 * format in newFormats for an argument name, the formatter 615 * for that argument remains unchanged. 616 * <p> 617 * This may be called on formats that do not use named arguments. 618 * In this case the map will be queried for key Strings that 619 * represent argument indices, e.g. "0", "1", "2" etc. 620 * 621 * @param newFormats a map from String to Format providing new 622 * formats for named arguments. 623 * @stable ICU 3.8 624 */ 625 public void setFormatsByArgumentName(Map<String, Format> newFormats) { 626 for (int partIndex = 0; (partIndex = nextTopLevelArgStart(partIndex)) >= 0;) { 627 String key = getArgName(partIndex + 1); 628 if (newFormats.containsKey(key)) { 629 setCustomArgStartFormat(partIndex, newFormats.get(key)); 630 } 631 } 632 } 633 634 /** 635 * Sets the Format objects to use for the format elements in the 636 * previously set pattern string. 637 * The order of formats in <code>newFormats</code> corresponds to 638 * the order of format elements in the pattern string. 639 * <p> 640 * If more formats are provided than needed by the pattern string, 641 * the remaining ones are ignored. If fewer formats are provided 642 * than needed, then only the first <code>newFormats.length</code> 643 * formats are replaced. 644 * <p> 645 * Since the order of format elements in a pattern string often 646 * changes during localization, it is generally better to use the 647 * {@link #setFormatsByArgumentIndex setFormatsByArgumentIndex} 648 * method, which assumes an order of formats corresponding to the 649 * order of elements in the <code>arguments</code> array passed to 650 * the <code>format</code> methods or the result array returned by 651 * the <code>parse</code> methods. 652 * 653 * @param newFormats the new formats to use 654 * @exception NullPointerException if <code>newFormats</code> is null 655 * @stable ICU 3.0 656 */ 657 public void setFormats(Format[] newFormats) { 658 int formatNumber = 0; 659 for (int partIndex = 0; 660 formatNumber < newFormats.length && 661 (partIndex = nextTopLevelArgStart(partIndex)) >= 0;) { 662 setCustomArgStartFormat(partIndex, newFormats[formatNumber]); 663 ++formatNumber; 664 } 665 } 666 667 /** 668 * Sets the Format object to use for the format elements within the 669 * previously set pattern string that use the given argument 670 * index. 671 * The argument index is part of the format element definition and 672 * represents an index into the <code>arguments</code> array passed 673 * to the <code>format</code> methods or the result array returned 674 * by the <code>parse</code> methods. 675 * <p> 676 * If the argument index is used for more than one format element 677 * in the pattern string, then the new format is used for all such 678 * format elements. If the argument index is not used for any format 679 * element in the pattern string, then the new format is ignored. 680 * 681 * This method is only supported when exclusively numbers are used for 682 * argument names. Otherwise an IllegalArgumentException is thrown. 683 * 684 * @param argumentIndex the argument index for which to use the new format 685 * @param newFormat the new format to use 686 * @throws IllegalArgumentException if this format uses named arguments 687 * @stable ICU 3.0 688 */ 689 public void setFormatByArgumentIndex(int argumentIndex, Format newFormat) { 690 if (msgPattern.hasNamedArguments()) { 691 throw new IllegalArgumentException( 692 "This method is not available in MessageFormat objects " + 693 "that use alphanumeric argument names."); 694 } 695 for (int partIndex = 0; (partIndex = nextTopLevelArgStart(partIndex)) >= 0;) { 696 if (msgPattern.getPart(partIndex + 1).getValue() == argumentIndex) { 697 setCustomArgStartFormat(partIndex, newFormat); 698 } 699 } 700 } 701 702 /** 703 * {@icu} Sets the Format object to use for the format elements within the 704 * previously set pattern string that use the given argument 705 * name. 706 * <p> 707 * If the argument name is used for more than one format element 708 * in the pattern string, then the new format is used for all such 709 * format elements. If the argument name is not used for any format 710 * element in the pattern string, then the new format is ignored. 711 * <p> 712 * This API may be used on formats that do not use named arguments. 713 * In this case <code>argumentName</code> should be a String that names 714 * an argument index, e.g. "0", "1", "2"... etc. If it does not name 715 * a valid index, the format will be ignored. No error is thrown. 716 * 717 * @param argumentName the name of the argument to change 718 * @param newFormat the new format to use 719 * @stable ICU 3.8 720 */ 721 public void setFormatByArgumentName(String argumentName, Format newFormat) { 722 int argNumber = MessagePattern.validateArgumentName(argumentName); 723 if (argNumber < MessagePattern.ARG_NAME_NOT_NUMBER) { 724 return; 725 } 726 for (int partIndex = 0; (partIndex = nextTopLevelArgStart(partIndex)) >= 0;) { 727 if (argNameMatches(partIndex + 1, argumentName, argNumber)) { 728 setCustomArgStartFormat(partIndex, newFormat); 729 } 730 } 731 } 732 733 /** 734 * Sets the Format object to use for the format element with the given 735 * format element index within the previously set pattern string. 736 * The format element index is the zero-based number of the format 737 * element counting from the start of the pattern string. 738 * <p> 739 * Since the order of format elements in a pattern string often 740 * changes during localization, it is generally better to use the 741 * {@link #setFormatByArgumentIndex setFormatByArgumentIndex} 742 * method, which accesses format elements based on the argument 743 * index they specify. 744 * 745 * @param formatElementIndex the index of a format element within the pattern 746 * @param newFormat the format to use for the specified format element 747 * @exception ArrayIndexOutOfBoundsException if formatElementIndex is equal to or 748 * larger than the number of format elements in the pattern string 749 * @stable ICU 3.0 750 */ 751 public void setFormat(int formatElementIndex, Format newFormat) { 752 int formatNumber = 0; 753 for (int partIndex = 0; (partIndex = nextTopLevelArgStart(partIndex)) >= 0;) { 754 if (formatNumber == formatElementIndex) { 755 setCustomArgStartFormat(partIndex, newFormat); 756 return; 757 } 758 ++formatNumber; 759 } 760 throw new ArrayIndexOutOfBoundsException(formatElementIndex); 761 } 762 763 /** 764 * Returns the Format objects used for the values passed into 765 * <code>format</code> methods or returned from <code>parse</code> 766 * methods. The indices of elements in the returned array 767 * correspond to the argument indices used in the previously set 768 * pattern string. 769 * The order of formats in the returned array thus corresponds to 770 * the order of elements in the <code>arguments</code> array passed 771 * to the <code>format</code> methods or the result array returned 772 * by the <code>parse</code> methods. 773 * <p> 774 * If an argument index is used for more than one format element 775 * in the pattern string, then the format used for the last such 776 * format element is returned in the array. If an argument index 777 * is not used for any format element in the pattern string, then 778 * null is returned in the array. 779 * 780 * This method is only supported when exclusively numbers are used for 781 * argument names. Otherwise an IllegalArgumentException is thrown. 782 * 783 * @return the formats used for the arguments within the pattern 784 * @throws IllegalArgumentException if this format uses named arguments 785 * @stable ICU 3.0 786 */ 787 public Format[] getFormatsByArgumentIndex() { 788 if (msgPattern.hasNamedArguments()) { 789 throw new IllegalArgumentException( 790 "This method is not available in MessageFormat objects " + 791 "that use alphanumeric argument names."); 792 } 793 ArrayList<Format> list = new ArrayList<Format>(); 794 for (int partIndex = 0; (partIndex = nextTopLevelArgStart(partIndex)) >= 0;) { 795 int argNumber = msgPattern.getPart(partIndex + 1).getValue(); 796 while (argNumber >= list.size()) { 797 list.add(null); 798 } 799 list.set(argNumber, cachedFormatters == null ? null : cachedFormatters.get(partIndex)); 800 } 801 return list.toArray(new Format[list.size()]); 802 } 803 804 /** 805 * Returns the Format objects used for the format elements in the 806 * previously set pattern string. 807 * The order of formats in the returned array corresponds to 808 * the order of format elements in the pattern string. 809 * <p> 810 * Since the order of format elements in a pattern string often 811 * changes during localization, it's generally better to use the 812 * {@link #getFormatsByArgumentIndex()} 813 * method, which assumes an order of formats corresponding to the 814 * order of elements in the <code>arguments</code> array passed to 815 * the <code>format</code> methods or the result array returned by 816 * the <code>parse</code> methods. 817 * 818 * This method is only supported when exclusively numbers are used for 819 * argument names. Otherwise an IllegalArgumentException is thrown. 820 * 821 * @return the formats used for the format elements in the pattern 822 * @throws IllegalArgumentException if this format uses named arguments 823 * @stable ICU 3.0 824 */ 825 public Format[] getFormats() { 826 ArrayList<Format> list = new ArrayList<Format>(); 827 for (int partIndex = 0; (partIndex = nextTopLevelArgStart(partIndex)) >= 0;) { 828 list.add(cachedFormatters == null ? null : cachedFormatters.get(partIndex)); 829 } 830 return list.toArray(new Format[list.size()]); 831 } 832 833 /** 834 * {@icu} Returns the top-level argument names. For more details, see 835 * {@link #setFormatByArgumentName(String, Format)}. 836 * @return a Set of argument names 837 * @stable ICU 4.8 838 */ 839 public Set<String> getArgumentNames() { 840 Set<String> result = new HashSet<String>(); 841 for (int partIndex = 0; (partIndex = nextTopLevelArgStart(partIndex)) >= 0;) { 842 result.add(getArgName(partIndex + 1)); 843 } 844 return result; 845 } 846 847 /** 848 * {@icu} Returns the first top-level format associated with the given argument name. 849 * For more details, see {@link #setFormatByArgumentName(String, Format)}. 850 * @param argumentName The name of the desired argument. 851 * @return the Format associated with the name, or null if there isn't one. 852 * @stable ICU 4.8 853 */ 854 public Format getFormatByArgumentName(String argumentName) { 855 if (cachedFormatters == null) { 856 return null; 857 } 858 int argNumber = MessagePattern.validateArgumentName(argumentName); 859 if (argNumber < MessagePattern.ARG_NAME_NOT_NUMBER) { 860 return null; 861 } 862 for (int partIndex = 0; (partIndex = nextTopLevelArgStart(partIndex)) >= 0;) { 863 if (argNameMatches(partIndex + 1, argumentName, argNumber)) { 864 return cachedFormatters.get(partIndex); 865 } 866 } 867 return null; 868 } 869 870 /** 871 * Formats an array of objects and appends the <code>MessageFormat</code>'s 872 * pattern, with arguments replaced by the formatted objects, to the 873 * provided <code>StringBuffer</code>. 874 * <p> 875 * The text substituted for the individual format elements is derived from 876 * the current subformat of the format element and the 877 * <code>arguments</code> element at the format element's argument index 878 * as indicated by the first matching line of the following table. An 879 * argument is <i>unavailable</i> if <code>arguments</code> is 880 * <code>null</code> or has fewer than argumentIndex+1 elements. When 881 * an argument is unavailable no substitution is performed. 882 * 883 * <table border=1> 884 * <tr> 885 * <th>argType or Format 886 * <th>value object 887 * <th>Formatted Text 888 * <tr> 889 * <td><i>any</i> 890 * <td><i>unavailable</i> 891 * <td><code>"{" + argNameOrNumber + "}"</code> 892 * <tr> 893 * <td><i>any</i> 894 * <td><code>null</code> 895 * <td><code>"null"</code> 896 * <tr> 897 * <td>custom Format <code>!= null</code> 898 * <td><i>any</i> 899 * <td><code>customFormat.format(argument)</code> 900 * <tr> 901 * <td>noneArg, or custom Format <code>== null</code> 902 * <td><code>instanceof Number</code> 903 * <td><code>NumberFormat.getInstance(getLocale()).format(argument)</code> 904 * <tr> 905 * <td>noneArg, or custom Format <code>== null</code> 906 * <td><code>instanceof Date</code> 907 * <td><code>DateFormat.getDateTimeInstance(DateFormat.SHORT, 908 * DateFormat.SHORT, getLocale()).format(argument)</code> 909 * <tr> 910 * <td>noneArg, or custom Format <code>== null</code> 911 * <td><code>instanceof String</code> 912 * <td><code>argument</code> 913 * <tr> 914 * <td>noneArg, or custom Format <code>== null</code> 915 * <td><i>any</i> 916 * <td><code>argument.toString()</code> 917 * <tr> 918 * <td>complexArg 919 * <td><i>any</i> 920 * <td>result of recursive formatting of a selected sub-message 921 * </table> 922 * <p> 923 * If <code>pos</code> is non-null, and refers to 924 * <code>Field.ARGUMENT</code>, the location of the first formatted 925 * string will be returned. 926 * 927 * This method is only supported when the format does not use named 928 * arguments, otherwise an IllegalArgumentException is thrown. 929 * 930 * @param arguments an array of objects to be formatted and substituted. 931 * @param result where text is appended. 932 * @param pos On input: an alignment field, if desired. 933 * On output: the offsets of the alignment field. 934 * @throws IllegalArgumentException if a value in the 935 * <code>arguments</code> array is not of the type 936 * expected by the corresponding argument or custom Format object. 937 * @throws IllegalArgumentException if this format uses named arguments 938 * @stable ICU 3.0 939 */ 940 public final StringBuffer format(Object[] arguments, StringBuffer result, 941 FieldPosition pos) 942 { 943 format(arguments, null, new AppendableWrapper(result), pos); 944 return result; 945 } 946 947 /** 948 * Formats a map of objects and appends the <code>MessageFormat</code>'s 949 * pattern, with arguments replaced by the formatted objects, to the 950 * provided <code>StringBuffer</code>. 951 * <p> 952 * The text substituted for the individual format elements is derived from 953 * the current subformat of the format element and the 954 * <code>arguments</code> value corresopnding to the format element's 955 * argument name. 956 * <p> 957 * A numbered pattern argument is matched with a map key that contains that number 958 * as an ASCII-decimal-digit string (without leading zero). 959 * <p> 960 * An argument is <i>unavailable</i> if <code>arguments</code> is 961 * <code>null</code> or does not have a value corresponding to an argument 962 * name in the pattern. When an argument is unavailable no substitution 963 * is performed. 964 * 965 * @param arguments a map of objects to be formatted and substituted. 966 * @param result where text is appended. 967 * @param pos On input: an alignment field, if desired. 968 * On output: the offsets of the alignment field. 969 * @throws IllegalArgumentException if a value in the 970 * <code>arguments</code> array is not of the type 971 * expected by the corresponding argument or custom Format object. 972 * @return the passed-in StringBuffer 973 * @stable ICU 3.8 974 */ 975 public final StringBuffer format(Map<String, Object> arguments, StringBuffer result, 976 FieldPosition pos) { 977 format(null, arguments, new AppendableWrapper(result), pos); 978 return result; 979 } 980 981 /** 982 * Creates a MessageFormat with the given pattern and uses it 983 * to format the given arguments. This is equivalent to 984 * <blockquote> 985 * <code>(new {@link #MessageFormat(String) MessageFormat}(pattern)).{@link 986 * #format(java.lang.Object[], java.lang.StringBuffer, java.text.FieldPosition) 987 * format}(arguments, new StringBuffer(), null).toString()</code> 988 * </blockquote> 989 * 990 * @throws IllegalArgumentException if the pattern is invalid 991 * @throws IllegalArgumentException if a value in the 992 * <code>arguments</code> array is not of the type 993 * expected by the corresponding argument or custom Format object. 994 * @throws IllegalArgumentException if this format uses named arguments 995 * @stable ICU 3.0 996 */ 997 public static String format(String pattern, Object... arguments) { 998 MessageFormat temp = new MessageFormat(pattern); 999 return temp.format(arguments); 1000 } 1001 1002 /** 1003 * Creates a MessageFormat with the given pattern and uses it to 1004 * format the given arguments. The pattern must identifyarguments 1005 * by name instead of by number. 1006 * <p> 1007 * @throws IllegalArgumentException if the pattern is invalid 1008 * @throws IllegalArgumentException if a value in the 1009 * <code>arguments</code> array is not of the type 1010 * expected by the corresponding argument or custom Format object. 1011 * @see #format(Map, StringBuffer, FieldPosition) 1012 * @see #format(String, Object[]) 1013 * @stable ICU 3.8 1014 */ 1015 public static String format(String pattern, Map<String, Object> arguments) { 1016 MessageFormat temp = new MessageFormat(pattern); 1017 return temp.format(arguments); 1018 } 1019 1020 /** 1021 * {@icu} Returns true if this MessageFormat uses named arguments, 1022 * and false otherwise. See class description. 1023 * 1024 * @return true if named arguments are used. 1025 * @stable ICU 3.8 1026 */ 1027 public boolean usesNamedArguments() { 1028 return msgPattern.hasNamedArguments(); 1029 } 1030 1031 // Overrides 1032 /** 1033 * Formats a map or array of objects and appends the <code>MessageFormat</code>'s 1034 * pattern, with format elements replaced by the formatted objects, to the 1035 * provided <code>StringBuffer</code>. 1036 * This is equivalent to either of 1037 * <blockquote> 1038 * <code>{@link #format(java.lang.Object[], java.lang.StringBuffer, 1039 * java.text.FieldPosition) format}((Object[]) arguments, result, pos)</code> 1040 * <code>{@link #format(java.util.Map, java.lang.StringBuffer, 1041 * java.text.FieldPosition) format}((Map) arguments, result, pos)</code> 1042 * </blockquote> 1043 * A map must be provided if this format uses named arguments, otherwise 1044 * an IllegalArgumentException will be thrown. 1045 * @param arguments a map or array of objects to be formatted 1046 * @param result where text is appended 1047 * @param pos On input: an alignment field, if desired 1048 * On output: the offsets of the alignment field 1049 * @throws IllegalArgumentException if an argument in 1050 * <code>arguments</code> is not of the type 1051 * expected by the format element(s) that use it 1052 * @throws IllegalArgumentException if <code>arguments</code> is 1053 * an array of Object and this format uses named arguments 1054 * @stable ICU 3.0 1055 */ 1056 @Override 1057 public final StringBuffer format(Object arguments, StringBuffer result, 1058 FieldPosition pos) 1059 { 1060 format(arguments, new AppendableWrapper(result), pos); 1061 return result; 1062 } 1063 1064 /** 1065 * Formats an array of objects and inserts them into the 1066 * <code>MessageFormat</code>'s pattern, producing an 1067 * <code>AttributedCharacterIterator</code>. 1068 * You can use the returned <code>AttributedCharacterIterator</code> 1069 * to build the resulting String, as well as to determine information 1070 * about the resulting String. 1071 * <p> 1072 * The text of the returned <code>AttributedCharacterIterator</code> is 1073 * the same that would be returned by 1074 * <blockquote> 1075 * <code>{@link #format(java.lang.Object[], java.lang.StringBuffer, 1076 * java.text.FieldPosition) format}(arguments, new StringBuffer(), null).toString()</code> 1077 * </blockquote> 1078 * <p> 1079 * In addition, the <code>AttributedCharacterIterator</code> contains at 1080 * least attributes indicating where text was generated from an 1081 * argument in the <code>arguments</code> array. The keys of these attributes are of 1082 * type <code>MessageFormat.Field</code>, their values are 1083 * <code>Integer</code> objects indicating the index in the <code>arguments</code> 1084 * array of the argument from which the text was generated. 1085 * <p> 1086 * The attributes/value from the underlying <code>Format</code> 1087 * instances that <code>MessageFormat</code> uses will also be 1088 * placed in the resulting <code>AttributedCharacterIterator</code>. 1089 * This allows you to not only find where an argument is placed in the 1090 * resulting String, but also which fields it contains in turn. 1091 * 1092 * @param arguments an array of objects to be formatted and substituted. 1093 * @return AttributedCharacterIterator describing the formatted value. 1094 * @exception NullPointerException if <code>arguments</code> is null. 1095 * @throws IllegalArgumentException if a value in the 1096 * <code>arguments</code> array is not of the type 1097 * expected by the corresponding argument or custom Format object. 1098 * @stable ICU 3.8 1099 */ 1100 @Override 1101 public AttributedCharacterIterator formatToCharacterIterator(Object arguments) { 1102 if (arguments == null) { 1103 throw new NullPointerException( 1104 "formatToCharacterIterator must be passed non-null object"); 1105 } 1106 StringBuilder result = new StringBuilder(); 1107 AppendableWrapper wrapper = new AppendableWrapper(result); 1108 wrapper.useAttributes(); 1109 format(arguments, wrapper, null); 1110 AttributedString as = new AttributedString(result.toString()); 1111 for (AttributeAndPosition a : wrapper.attributes) { 1112 as.addAttribute(a.key, a.value, a.start, a.limit); 1113 } 1114 return as.getIterator(); 1115 } 1116 1117 /** 1118 * Parses the string. 1119 * 1120 * <p>Caveats: The parse may fail in a number of circumstances. 1121 * For example: 1122 * <ul> 1123 * <li>If one of the arguments does not occur in the pattern. 1124 * <li>If the format of an argument loses information, such as 1125 * with a choice format where a large number formats to "many". 1126 * <li>Does not yet handle recursion (where 1127 * the substituted strings contain {n} references.) 1128 * <li>Will not always find a match (or the correct match) 1129 * if some part of the parse is ambiguous. 1130 * For example, if the pattern "{1},{2}" is used with the 1131 * string arguments {"a,b", "c"}, it will format as "a,b,c". 1132 * When the result is parsed, it will return {"a", "b,c"}. 1133 * <li>If a single argument is parsed more than once in the string, 1134 * then the later parse wins. 1135 * </ul> 1136 * When the parse fails, use ParsePosition.getErrorIndex() to find out 1137 * where in the string did the parsing failed. The returned error 1138 * index is the starting offset of the sub-patterns that the string 1139 * is comparing with. For example, if the parsing string "AAA {0} BBB" 1140 * is comparing against the pattern "AAD {0} BBB", the error index is 1141 * 0. When an error occurs, the call to this method will return null. 1142 * If the source is null, return an empty array. 1143 * 1144 * @throws IllegalArgumentException if this format uses named arguments 1145 * @stable ICU 3.0 1146 */ 1147 public Object[] parse(String source, ParsePosition pos) { 1148 if (msgPattern.hasNamedArguments()) { 1149 throw new IllegalArgumentException( 1150 "This method is not available in MessageFormat objects " + 1151 "that use named argument."); 1152 } 1153 1154 // Count how many slots we need in the array. 1155 int maxArgId = -1; 1156 for (int partIndex = 0; (partIndex = nextTopLevelArgStart(partIndex)) >= 0;) { 1157 int argNumber=msgPattern.getPart(partIndex + 1).getValue(); 1158 if (argNumber > maxArgId) { 1159 maxArgId = argNumber; 1160 } 1161 } 1162 Object[] resultArray = new Object[maxArgId + 1]; 1163 1164 int backupStartPos = pos.getIndex(); 1165 parse(0, source, pos, resultArray, null); 1166 if (pos.getIndex() == backupStartPos) { // unchanged, returned object is null 1167 return null; 1168 } 1169 1170 return resultArray; 1171 } 1172 1173 /** 1174 * {@icu} Parses the string, returning the results in a Map. 1175 * This is similar to the version that returns an array 1176 * of Object. This supports both named and numbered 1177 * arguments-- if numbered, the keys in the map are the 1178 * corresponding ASCII-decimal-digit strings (e.g. "0", "1", "2"...). 1179 * 1180 * @param source the text to parse 1181 * @param pos the position at which to start parsing. on return, 1182 * contains the result of the parse. 1183 * @return a Map containing key/value pairs for each parsed argument. 1184 * @stable ICU 3.8 1185 */ 1186 public Map<String, Object> parseToMap(String source, ParsePosition pos) { 1187 Map<String, Object> result = new HashMap<String, Object>(); 1188 int backupStartPos = pos.getIndex(); 1189 parse(0, source, pos, null, result); 1190 if (pos.getIndex() == backupStartPos) { 1191 return null; 1192 } 1193 return result; 1194 } 1195 1196 /** 1197 * Parses text from the beginning of the given string to produce an object 1198 * array. 1199 * The method may not use the entire text of the given string. 1200 * <p> 1201 * See the {@link #parse(String, ParsePosition)} method for more information 1202 * on message parsing. 1203 * 1204 * @param source A <code>String</code> whose beginning should be parsed. 1205 * @return An <code>Object</code> array parsed from the string. 1206 * @exception ParseException if the beginning of the specified string cannot be parsed. 1207 * @exception IllegalArgumentException if this format uses named arguments 1208 * @stable ICU 3.0 1209 */ 1210 public Object[] parse(String source) throws ParseException { 1211 ParsePosition pos = new ParsePosition(0); 1212 Object[] result = parse(source, pos); 1213 if (pos.getIndex() == 0) // unchanged, returned object is null 1214 throw new ParseException("MessageFormat parse error!", 1215 pos.getErrorIndex()); 1216 1217 return result; 1218 } 1219 1220 /** 1221 * Parses the string, filling either the Map or the Array. 1222 * This is a private method that all the public parsing methods call. 1223 * This supports both named and numbered 1224 * arguments-- if numbered, the keys in the map are the 1225 * corresponding ASCII-decimal-digit strings (e.g. "0", "1", "2"...). 1226 * 1227 * @param msgStart index in the message pattern to start from. 1228 * @param source the text to parse 1229 * @param pos the position at which to start parsing. on return, 1230 * contains the result of the parse. 1231 * @param args if not null, the parse results will be filled here (The pattern 1232 * has to have numbered arguments in order for this to not be null). 1233 * @param argsMap if not null, the parse results will be filled here. 1234 */ 1235 private void parse(int msgStart, String source, ParsePosition pos, 1236 Object[] args, Map<String, Object> argsMap) { 1237 if (source == null) { 1238 return; 1239 } 1240 String msgString=msgPattern.getPatternString(); 1241 int prevIndex=msgPattern.getPart(msgStart).getLimit(); 1242 int sourceOffset = pos.getIndex(); 1243 ParsePosition tempStatus = new ParsePosition(0); 1244 1245 for(int i=msgStart+1; ; ++i) { 1246 Part part=msgPattern.getPart(i); 1247 Part.Type type=part.getType(); 1248 int index=part.getIndex(); 1249 // Make sure the literal string matches. 1250 int len = index - prevIndex; 1251 if (len == 0 || msgString.regionMatches(prevIndex, source, sourceOffset, len)) { 1252 sourceOffset += len; 1253 prevIndex += len; 1254 } else { 1255 pos.setErrorIndex(sourceOffset); 1256 return; // leave index as is to signal error 1257 } 1258 if(type==Part.Type.MSG_LIMIT) { 1259 // Things went well! Done. 1260 pos.setIndex(sourceOffset); 1261 return; 1262 } 1263 if(type==Part.Type.SKIP_SYNTAX || type==Part.Type.INSERT_CHAR) { 1264 prevIndex=part.getLimit(); 1265 continue; 1266 } 1267 // We do not support parsing Plural formats. (No REPLACE_NUMBER here.) 1268 assert type==Part.Type.ARG_START : "Unexpected Part "+part+" in parsed message."; 1269 int argLimit=msgPattern.getLimitPartIndex(i); 1270 1271 ArgType argType=part.getArgType(); 1272 part=msgPattern.getPart(++i); 1273 // Compute the argId, so we can use it as a key. 1274 Object argId=null; 1275 int argNumber = 0; 1276 String key = null; 1277 if(args!=null) { 1278 argNumber=part.getValue(); // ARG_NUMBER 1279 argId = Integer.valueOf(argNumber); 1280 } else { 1281 if(part.getType()==MessagePattern.Part.Type.ARG_NAME) { 1282 key=msgPattern.getSubstring(part); 1283 } else /* ARG_NUMBER */ { 1284 key=Integer.toString(part.getValue()); 1285 } 1286 argId = key; 1287 } 1288 1289 ++i; 1290 Format formatter = null; 1291 boolean haveArgResult = false; 1292 Object argResult = null; 1293 if(cachedFormatters!=null && (formatter=cachedFormatters.get(i - 2))!=null) { 1294 // Just parse using the formatter. 1295 tempStatus.setIndex(sourceOffset); 1296 argResult = formatter.parseObject(source, tempStatus); 1297 if (tempStatus.getIndex() == sourceOffset) { 1298 pos.setErrorIndex(sourceOffset); 1299 return; // leave index as is to signal error 1300 } 1301 haveArgResult = true; 1302 sourceOffset = tempStatus.getIndex(); 1303 } else if( 1304 argType==ArgType.NONE || 1305 (cachedFormatters!=null && cachedFormatters.containsKey(i - 2))) { 1306 // Match as a string. 1307 // if at end, use longest possible match 1308 // otherwise uses first match to intervening string 1309 // does NOT recursively try all possibilities 1310 String stringAfterArgument = getLiteralStringUntilNextArgument(argLimit); 1311 int next; 1312 if (stringAfterArgument.length() != 0) { 1313 next = source.indexOf(stringAfterArgument, sourceOffset); 1314 } else { 1315 next = source.length(); 1316 } 1317 if (next < 0) { 1318 pos.setErrorIndex(sourceOffset); 1319 return; // leave index as is to signal error 1320 } else { 1321 String strValue = source.substring(sourceOffset, next); 1322 if (!strValue.equals("{" + argId.toString() + "}")) { 1323 haveArgResult = true; 1324 argResult = strValue; 1325 } 1326 sourceOffset = next; 1327 } 1328 } else if(argType==ArgType.CHOICE) { 1329 tempStatus.setIndex(sourceOffset); 1330 double choiceResult = parseChoiceArgument(msgPattern, i, source, tempStatus); 1331 if (tempStatus.getIndex() == sourceOffset) { 1332 pos.setErrorIndex(sourceOffset); 1333 return; // leave index as is to signal error 1334 } 1335 argResult = choiceResult; 1336 haveArgResult = true; 1337 sourceOffset = tempStatus.getIndex(); 1338 } else if(argType.hasPluralStyle() || argType==ArgType.SELECT) { 1339 // No can do! 1340 throw new UnsupportedOperationException( 1341 "Parsing of plural/select/selectordinal argument is not supported."); 1342 } else { 1343 // This should never happen. 1344 throw new IllegalStateException("unexpected argType "+argType); 1345 } 1346 if (haveArgResult) { 1347 if (args != null) { 1348 args[argNumber] = argResult; 1349 } else if (argsMap != null) { 1350 argsMap.put(key, argResult); 1351 } 1352 } 1353 prevIndex=msgPattern.getPart(argLimit).getLimit(); 1354 i=argLimit; 1355 } 1356 } 1357 1358 /** 1359 * {@icu} Parses text from the beginning of the given string to produce a map from 1360 * argument to values. The method may not use the entire text of the given string. 1361 * 1362 * <p>See the {@link #parse(String, ParsePosition)} method for more information on 1363 * message parsing. 1364 * 1365 * @param source A <code>String</code> whose beginning should be parsed. 1366 * @return A <code>Map</code> parsed from the string. 1367 * @throws ParseException if the beginning of the specified string cannot 1368 * be parsed. 1369 * @see #parseToMap(String, ParsePosition) 1370 * @stable ICU 3.8 1371 */ 1372 public Map<String, Object> parseToMap(String source) throws ParseException { 1373 ParsePosition pos = new ParsePosition(0); 1374 Map<String, Object> result = new HashMap<String, Object>(); 1375 parse(0, source, pos, null, result); 1376 if (pos.getIndex() == 0) // unchanged, returned object is null 1377 throw new ParseException("MessageFormat parse error!", 1378 pos.getErrorIndex()); 1379 1380 return result; 1381 } 1382 1383 /** 1384 * Parses text from a string to produce an object array or Map. 1385 * <p> 1386 * The method attempts to parse text starting at the index given by 1387 * <code>pos</code>. 1388 * If parsing succeeds, then the index of <code>pos</code> is updated 1389 * to the index after the last character used (parsing does not necessarily 1390 * use all characters up to the end of the string), and the parsed 1391 * object array is returned. The updated <code>pos</code> can be used to 1392 * indicate the starting point for the next call to this method. 1393 * If an error occurs, then the index of <code>pos</code> is not 1394 * changed, the error index of <code>pos</code> is set to the index of 1395 * the character where the error occurred, and null is returned. 1396 * <p> 1397 * See the {@link #parse(String, ParsePosition)} method for more information 1398 * on message parsing. 1399 * 1400 * @param source A <code>String</code>, part of which should be parsed. 1401 * @param pos A <code>ParsePosition</code> object with index and error 1402 * index information as described above. 1403 * @return An <code>Object</code> parsed from the string, either an 1404 * array of Object, or a Map, depending on whether named 1405 * arguments are used. This can be queried using <code>usesNamedArguments</code>. 1406 * In case of error, returns null. 1407 * @throws NullPointerException if <code>pos</code> is null. 1408 * @stable ICU 3.0 1409 */ 1410 @Override 1411 public Object parseObject(String source, ParsePosition pos) { 1412 if (!msgPattern.hasNamedArguments()) { 1413 return parse(source, pos); 1414 } else { 1415 return parseToMap(source, pos); 1416 } 1417 } 1418 1419 /** 1420 * {@inheritDoc} 1421 * @stable ICU 3.0 1422 */ 1423 @Override 1424 public Object clone() { 1425 MessageFormat other = (MessageFormat) super.clone(); 1426 1427 if (customFormatArgStarts != null) { 1428 other.customFormatArgStarts = new HashSet<Integer>(); 1429 for (Integer key : customFormatArgStarts) { 1430 other.customFormatArgStarts.add(key); 1431 } 1432 } else { 1433 other.customFormatArgStarts = null; 1434 } 1435 1436 if (cachedFormatters != null) { 1437 other.cachedFormatters = new HashMap<Integer, Format>(); 1438 Iterator<Map.Entry<Integer, Format>> it = cachedFormatters.entrySet().iterator(); 1439 while (it.hasNext()){ 1440 Map.Entry<Integer, Format> entry = it.next(); 1441 other.cachedFormatters.put(entry.getKey(), entry.getValue()); 1442 } 1443 } else { 1444 other.cachedFormatters = null; 1445 } 1446 1447 other.msgPattern = msgPattern == null ? null : (MessagePattern)msgPattern.clone(); 1448 other.stockDateFormatter = 1449 stockDateFormatter == null ? null : (DateFormat) stockDateFormatter.clone(); 1450 other.stockNumberFormatter = 1451 stockNumberFormatter == null ? null : (NumberFormat) stockNumberFormatter.clone(); 1452 1453 other.pluralProvider = null; 1454 other.ordinalProvider = null; 1455 return other; 1456 } 1457 1458 /** 1459 * {@inheritDoc} 1460 * @stable ICU 3.0 1461 */ 1462 @Override 1463 public boolean equals(Object obj) { 1464 if (this == obj) // quick check 1465 return true; 1466 if (obj == null || getClass() != obj.getClass()) 1467 return false; 1468 MessageFormat other = (MessageFormat) obj; 1469 return Utility.objectEquals(ulocale, other.ulocale) 1470 && Utility.objectEquals(msgPattern, other.msgPattern) 1471 && Utility.objectEquals(cachedFormatters, other.cachedFormatters) 1472 && Utility.objectEquals(customFormatArgStarts, other.customFormatArgStarts); 1473 // Note: It might suffice to only compare custom formatters 1474 // rather than all formatters. 1475 } 1476 1477 /** 1478 * {@inheritDoc} 1479 * @stable ICU 3.0 1480 */ 1481 @Override 1482 public int hashCode() { 1483 return msgPattern.getPatternString().hashCode(); // enough for reasonable distribution 1484 } 1485 1486 /** 1487 * Defines constants that are used as attribute keys in the 1488 * <code>AttributedCharacterIterator</code> returned 1489 * from <code>MessageFormat.formatToCharacterIterator</code>. 1490 * 1491 * @stable ICU 3.8 1492 */ 1493 public static class Field extends Format.Field { 1494 1495 private static final long serialVersionUID = 7510380454602616157L; 1496 1497 /** 1498 * Create a <code>Field</code> with the specified name. 1499 * 1500 * @param name The name of the attribute 1501 * 1502 * @stable ICU 3.8 1503 */ 1504 protected Field(String name) { 1505 super(name); 1506 } 1507 1508 /** 1509 * Resolves instances being deserialized to the predefined constants. 1510 * 1511 * @return resolved MessageFormat.Field constant 1512 * @throws InvalidObjectException if the constant could not be resolved. 1513 * 1514 * @stable ICU 3.8 1515 */ 1516 @Override 1517 protected Object readResolve() throws InvalidObjectException { 1518 if (this.getClass() != MessageFormat.Field.class) { 1519 throw new InvalidObjectException( 1520 "A subclass of MessageFormat.Field must implement readResolve."); 1521 } 1522 if (this.getName().equals(ARGUMENT.getName())) { 1523 return ARGUMENT; 1524 } else { 1525 throw new InvalidObjectException("Unknown attribute name."); 1526 } 1527 } 1528 1529 /** 1530 * Constant identifying a portion of a message that was generated 1531 * from an argument passed into <code>formatToCharacterIterator</code>. 1532 * The value associated with the key will be an <code>Integer</code> 1533 * indicating the index in the <code>arguments</code> array of the 1534 * argument from which the text was generated. 1535 * 1536 * @stable ICU 3.8 1537 */ 1538 public static final Field ARGUMENT = new Field("message argument field"); 1539 } 1540 1541 // ===========================privates============================ 1542 1543 // *Important*: All fields must be declared *transient* so that we can fully 1544 // control serialization! 1545 // See for example Joshua Bloch's "Effective Java", chapter 10 Serialization. 1546 1547 /** 1548 * The locale to use for formatting numbers and dates. 1549 */ 1550 private transient ULocale ulocale; 1551 1552 /** 1553 * The MessagePattern which contains the parsed structure of the pattern string. 1554 */ 1555 private transient MessagePattern msgPattern; 1556 /** 1557 * Cached formatters so we can just use them whenever needed instead of creating 1558 * them from scratch every time. 1559 */ 1560 private transient Map<Integer, Format> cachedFormatters; 1561 /** 1562 * Set of ARG_START part indexes where custom, user-provided Format objects 1563 * have been set via setFormat() or similar API. 1564 */ 1565 private transient Set<Integer> customFormatArgStarts; 1566 1567 /** 1568 * Stock formatters. Those are used when a format is not explicitly mentioned in 1569 * the message. The format is inferred from the argument. 1570 */ 1571 private transient DateFormat stockDateFormatter; 1572 private transient NumberFormat stockNumberFormatter; 1573 1574 private transient PluralSelectorProvider pluralProvider; 1575 private transient PluralSelectorProvider ordinalProvider; 1576 1577 private DateFormat getStockDateFormatter() { 1578 if (stockDateFormatter == null) { 1579 stockDateFormatter = DateFormat.getDateTimeInstance( 1580 DateFormat.SHORT, DateFormat.SHORT, ulocale);//fix 1581 } 1582 return stockDateFormatter; 1583 } 1584 private NumberFormat getStockNumberFormatter() { 1585 if (stockNumberFormatter == null) { 1586 stockNumberFormatter = NumberFormat.getInstance(ulocale); 1587 } 1588 return stockNumberFormatter; 1589 } 1590 1591 // *Important*: All fields must be declared *transient*. 1592 // See the longer comment above ulocale. 1593 1594 /** 1595 * Formats the arguments and writes the result into the 1596 * AppendableWrapper, updates the field position. 1597 * 1598 * <p>Exactly one of args and argsMap must be null, the other non-null. 1599 * 1600 * @param msgStart Index to msgPattern part to start formatting from. 1601 * @param pluralNumber null except when formatting a plural argument sub-message 1602 * where a '#' is replaced by the format string for this number. 1603 * @param args The formattable objects array. Non-null iff numbered values are used. 1604 * @param argsMap The key-value map of formattable objects. Non-null iff named values are used. 1605 * @param dest Output parameter to receive the result. 1606 * The result (string & attributes) is appended to existing contents. 1607 * @param fp Field position status. 1608 */ 1609 private void format(int msgStart, PluralSelectorContext pluralNumber, 1610 Object[] args, Map<String, Object> argsMap, 1611 AppendableWrapper dest, FieldPosition fp) { 1612 String msgString=msgPattern.getPatternString(); 1613 int prevIndex=msgPattern.getPart(msgStart).getLimit(); 1614 for(int i=msgStart+1;; ++i) { 1615 Part part=msgPattern.getPart(i); 1616 Part.Type type=part.getType(); 1617 int index=part.getIndex(); 1618 dest.append(msgString, prevIndex, index); 1619 if(type==Part.Type.MSG_LIMIT) { 1620 return; 1621 } 1622 prevIndex=part.getLimit(); 1623 if(type==Part.Type.REPLACE_NUMBER) { 1624 if(pluralNumber.forReplaceNumber) { 1625 // number-offset was already formatted. 1626 dest.formatAndAppend(pluralNumber.formatter, 1627 pluralNumber.number, pluralNumber.numberString); 1628 } else { 1629 dest.formatAndAppend(getStockNumberFormatter(), pluralNumber.number); 1630 } 1631 continue; 1632 } 1633 if(type!=Part.Type.ARG_START) { 1634 continue; 1635 } 1636 int argLimit=msgPattern.getLimitPartIndex(i); 1637 ArgType argType=part.getArgType(); 1638 part=msgPattern.getPart(++i); 1639 Object arg; 1640 boolean noArg=false; 1641 Object argId=null; 1642 String argName=msgPattern.getSubstring(part); 1643 if(args!=null) { 1644 int argNumber=part.getValue(); // ARG_NUMBER 1645 if (dest.attributes != null) { 1646 // We only need argId if we add it into the attributes. 1647 argId = Integer.valueOf(argNumber); 1648 } 1649 if(0<=argNumber && argNumber<args.length) { 1650 arg=args[argNumber]; 1651 } else { 1652 arg=null; 1653 noArg=true; 1654 } 1655 } else { 1656 argId = argName; 1657 if(argsMap!=null && argsMap.containsKey(argName)) { 1658 arg=argsMap.get(argName); 1659 } else { 1660 arg=null; 1661 noArg=true; 1662 } 1663 } 1664 ++i; 1665 int prevDestLength=dest.length; 1666 Format formatter = null; 1667 if (noArg) { 1668 dest.append("{"+argName+"}"); 1669 } else if (arg == null) { 1670 dest.append("null"); 1671 } else if(pluralNumber!=null && pluralNumber.numberArgIndex==(i-2)) { 1672 if(pluralNumber.offset == 0) { 1673 // The number was already formatted with this formatter. 1674 dest.formatAndAppend(pluralNumber.formatter, pluralNumber.number, pluralNumber.numberString); 1675 } else { 1676 // Do not use the formatted (number-offset) string for a named argument 1677 // that formats the number without subtracting the offset. 1678 dest.formatAndAppend(pluralNumber.formatter, arg); 1679 } 1680 } else if(cachedFormatters!=null && (formatter=cachedFormatters.get(i - 2))!=null) { 1681 // Handles all ArgType.SIMPLE, and formatters from setFormat() and its siblings. 1682 if ( formatter instanceof ChoiceFormat || 1683 formatter instanceof PluralFormat || 1684 formatter instanceof SelectFormat) { 1685 // We only handle nested formats here if they were provided via setFormat() or its siblings. 1686 // Otherwise they are not cached and instead handled below according to argType. 1687 String subMsgString = formatter.format(arg); 1688 if (subMsgString.indexOf('{') >= 0 || 1689 (subMsgString.indexOf('\'') >= 0 && !msgPattern.jdkAposMode())) { 1690 MessageFormat subMsgFormat = new MessageFormat(subMsgString, ulocale); 1691 subMsgFormat.format(0, null, args, argsMap, dest, null); 1692 } else if (dest.attributes == null) { 1693 dest.append(subMsgString); 1694 } else { 1695 // This formats the argument twice, once above to get the subMsgString 1696 // and then once more here. 1697 // It only happens in formatToCharacterIterator() 1698 // on a complex Format set via setFormat(), 1699 // and only when the selected subMsgString does not need further formatting. 1700 // This imitates ICU 4.6 behavior. 1701 dest.formatAndAppend(formatter, arg); 1702 } 1703 } else { 1704 dest.formatAndAppend(formatter, arg); 1705 } 1706 } else if( 1707 argType==ArgType.NONE || 1708 (cachedFormatters!=null && cachedFormatters.containsKey(i - 2))) { 1709 // ArgType.NONE, or 1710 // any argument which got reset to null via setFormat() or its siblings. 1711 if (arg instanceof Number) { 1712 // format number if can 1713 dest.formatAndAppend(getStockNumberFormatter(), arg); 1714 } else if (arg instanceof Date) { 1715 // format a Date if can 1716 dest.formatAndAppend(getStockDateFormatter(), arg); 1717 } else { 1718 dest.append(arg.toString()); 1719 } 1720 } else if(argType==ArgType.CHOICE) { 1721 if (!(arg instanceof Number)) { 1722 throw new IllegalArgumentException("'" + arg + "' is not a Number"); 1723 } 1724 double number = ((Number)arg).doubleValue(); 1725 int subMsgStart=findChoiceSubMessage(msgPattern, i, number); 1726 formatComplexSubMessage(subMsgStart, null, args, argsMap, dest); 1727 } else if(argType.hasPluralStyle()) { 1728 if (!(arg instanceof Number)) { 1729 throw new IllegalArgumentException("'" + arg + "' is not a Number"); 1730 } 1731 PluralSelectorProvider selector; 1732 if(argType == ArgType.PLURAL) { 1733 if (pluralProvider == null) { 1734 pluralProvider = new PluralSelectorProvider(this, PluralType.CARDINAL); 1735 } 1736 selector = pluralProvider; 1737 } else { 1738 if (ordinalProvider == null) { 1739 ordinalProvider = new PluralSelectorProvider(this, PluralType.ORDINAL); 1740 } 1741 selector = ordinalProvider; 1742 } 1743 Number number = (Number)arg; 1744 double offset=msgPattern.getPluralOffset(i); 1745 PluralSelectorContext context = 1746 new PluralSelectorContext(i, argName, number, offset); 1747 int subMsgStart=PluralFormat.findSubMessage( 1748 msgPattern, i, selector, context, number.doubleValue()); 1749 formatComplexSubMessage(subMsgStart, context, args, argsMap, dest); 1750 } else if(argType==ArgType.SELECT) { 1751 int subMsgStart=SelectFormat.findSubMessage(msgPattern, i, arg.toString()); 1752 formatComplexSubMessage(subMsgStart, null, args, argsMap, dest); 1753 } else { 1754 // This should never happen. 1755 throw new IllegalStateException("unexpected argType "+argType); 1756 } 1757 fp = updateMetaData(dest, prevDestLength, fp, argId); 1758 prevIndex=msgPattern.getPart(argLimit).getLimit(); 1759 i=argLimit; 1760 } 1761 } 1762 1763 private void formatComplexSubMessage( 1764 int msgStart, PluralSelectorContext pluralNumber, 1765 Object[] args, Map<String, Object> argsMap, 1766 AppendableWrapper dest) { 1767 if (!msgPattern.jdkAposMode()) { 1768 format(msgStart, pluralNumber, args, argsMap, dest, null); 1769 return; 1770 } 1771 // JDK compatibility mode: (see JDK MessageFormat.format() API docs) 1772 // - remove SKIP_SYNTAX; that is, remove half of the apostrophes 1773 // - if the result string contains an open curly brace '{' then 1774 // instantiate a temporary MessageFormat object and format again; 1775 // otherwise just append the result string 1776 String msgString = msgPattern.getPatternString(); 1777 String subMsgString; 1778 StringBuilder sb = null; 1779 int prevIndex = msgPattern.getPart(msgStart).getLimit(); 1780 for (int i = msgStart;;) { 1781 Part part = msgPattern.getPart(++i); 1782 Part.Type type = part.getType(); 1783 int index = part.getIndex(); 1784 if (type == Part.Type.MSG_LIMIT) { 1785 if (sb == null) { 1786 subMsgString = msgString.substring(prevIndex, index); 1787 } else { 1788 subMsgString = sb.append(msgString, prevIndex, index).toString(); 1789 } 1790 break; 1791 } else if (type == Part.Type.REPLACE_NUMBER || type == Part.Type.SKIP_SYNTAX) { 1792 if (sb == null) { 1793 sb = new StringBuilder(); 1794 } 1795 sb.append(msgString, prevIndex, index); 1796 if (type == Part.Type.REPLACE_NUMBER) { 1797 if(pluralNumber.forReplaceNumber) { 1798 // number-offset was already formatted. 1799 sb.append(pluralNumber.numberString); 1800 } else { 1801 sb.append(getStockNumberFormatter().format(pluralNumber.number)); 1802 } 1803 } 1804 prevIndex = part.getLimit(); 1805 } else if (type == Part.Type.ARG_START) { 1806 if (sb == null) { 1807 sb = new StringBuilder(); 1808 } 1809 sb.append(msgString, prevIndex, index); 1810 prevIndex = index; 1811 i = msgPattern.getLimitPartIndex(i); 1812 index = msgPattern.getPart(i).getLimit(); 1813 MessagePattern.appendReducedApostrophes(msgString, prevIndex, index, sb); 1814 prevIndex = index; 1815 } 1816 } 1817 if (subMsgString.indexOf('{') >= 0) { 1818 MessageFormat subMsgFormat = new MessageFormat("", ulocale); 1819 subMsgFormat.applyPattern(subMsgString, MessagePattern.ApostropheMode.DOUBLE_REQUIRED); 1820 subMsgFormat.format(0, null, args, argsMap, dest, null); 1821 } else { 1822 dest.append(subMsgString); 1823 } 1824 } 1825 1826 /** 1827 * Read as much literal string from the pattern string as possible. This stops 1828 * as soon as it finds an argument, or it reaches the end of the string. 1829 * @param from Index in the pattern string to start from. 1830 * @return A substring from the pattern string representing the longest possible 1831 * substring with no arguments. 1832 */ 1833 private String getLiteralStringUntilNextArgument(int from) { 1834 StringBuilder b = new StringBuilder(); 1835 String msgString=msgPattern.getPatternString(); 1836 int prevIndex=msgPattern.getPart(from).getLimit(); 1837 for(int i=from+1;; ++i) { 1838 Part part=msgPattern.getPart(i); 1839 Part.Type type=part.getType(); 1840 int index=part.getIndex(); 1841 b.append(msgString, prevIndex, index); 1842 if(type==Part.Type.ARG_START || type==Part.Type.MSG_LIMIT) { 1843 return b.toString(); 1844 } 1845 assert type==Part.Type.SKIP_SYNTAX || type==Part.Type.INSERT_CHAR : 1846 "Unexpected Part "+part+" in parsed message."; 1847 prevIndex=part.getLimit(); 1848 } 1849 } 1850 1851 private FieldPosition updateMetaData(AppendableWrapper dest, int prevLength, 1852 FieldPosition fp, Object argId) { 1853 if (dest.attributes != null && prevLength < dest.length) { 1854 dest.attributes.add(new AttributeAndPosition(argId, prevLength, dest.length)); 1855 } 1856 if (fp != null && Field.ARGUMENT.equals(fp.getFieldAttribute())) { 1857 fp.setBeginIndex(prevLength); 1858 fp.setEndIndex(dest.length); 1859 return null; 1860 } 1861 return fp; 1862 } 1863 1864 // This lives here because ICU4J does not have its own ChoiceFormat class. 1865 /** 1866 * Finds the ChoiceFormat sub-message for the given number. 1867 * @param pattern A MessagePattern. 1868 * @param partIndex the index of the first ChoiceFormat argument style part. 1869 * @param number a number to be mapped to one of the ChoiceFormat argument's intervals 1870 * @return the sub-message start part index. 1871 */ 1872 private static int findChoiceSubMessage(MessagePattern pattern, int partIndex, double number) { 1873 int count=pattern.countParts(); 1874 int msgStart; 1875 // Iterate over (ARG_INT|DOUBLE, ARG_SELECTOR, message) tuples 1876 // until ARG_LIMIT or end of choice-only pattern. 1877 // Ignore the first number and selector and start the loop on the first message. 1878 partIndex+=2; 1879 for(;;) { 1880 // Skip but remember the current sub-message. 1881 msgStart=partIndex; 1882 partIndex=pattern.getLimitPartIndex(partIndex); 1883 if(++partIndex>=count) { 1884 // Reached the end of the choice-only pattern. 1885 // Return with the last sub-message. 1886 break; 1887 } 1888 Part part=pattern.getPart(partIndex++); 1889 Part.Type type=part.getType(); 1890 if(type==Part.Type.ARG_LIMIT) { 1891 // Reached the end of the ChoiceFormat style. 1892 // Return with the last sub-message. 1893 break; 1894 } 1895 // part is an ARG_INT or ARG_DOUBLE 1896 assert type.hasNumericValue(); 1897 double boundary=pattern.getNumericValue(part); 1898 // Fetch the ARG_SELECTOR character. 1899 int selectorIndex=pattern.getPatternIndex(partIndex++); 1900 char boundaryChar=pattern.getPatternString().charAt(selectorIndex); 1901 if(boundaryChar=='<' ? !(number>boundary) : !(number>=boundary)) { 1902 // The number is in the interval between the previous boundary and the current one. 1903 // Return with the sub-message between them. 1904 // The !(a>b) and !(a>=b) comparisons are equivalent to 1905 // (a<=b) and (a<b) except they "catch" NaN. 1906 break; 1907 } 1908 } 1909 return msgStart; 1910 } 1911 1912 // Ported from C++ ChoiceFormat::parse(). 1913 private static double parseChoiceArgument( 1914 MessagePattern pattern, int partIndex, 1915 String source, ParsePosition pos) { 1916 // find the best number (defined as the one with the longest parse) 1917 int start = pos.getIndex(); 1918 int furthest = start; 1919 double bestNumber = Double.NaN; 1920 double tempNumber = 0.0; 1921 while (pattern.getPartType(partIndex) != Part.Type.ARG_LIMIT) { 1922 tempNumber = pattern.getNumericValue(pattern.getPart(partIndex)); 1923 partIndex += 2; // skip the numeric part and ignore the ARG_SELECTOR 1924 int msgLimit = pattern.getLimitPartIndex(partIndex); 1925 int len = matchStringUntilLimitPart(pattern, partIndex, msgLimit, source, start); 1926 if (len >= 0) { 1927 int newIndex = start + len; 1928 if (newIndex > furthest) { 1929 furthest = newIndex; 1930 bestNumber = tempNumber; 1931 if (furthest == source.length()) { 1932 break; 1933 } 1934 } 1935 } 1936 partIndex = msgLimit + 1; 1937 } 1938 if (furthest == start) { 1939 pos.setErrorIndex(start); 1940 } else { 1941 pos.setIndex(furthest); 1942 } 1943 return bestNumber; 1944 } 1945 1946 /** 1947 * Matches the pattern string from the end of the partIndex to 1948 * the beginning of the limitPartIndex, 1949 * including all syntax except SKIP_SYNTAX, 1950 * against the source string starting at sourceOffset. 1951 * If they match, returns the length of the source string match. 1952 * Otherwise returns -1. 1953 */ 1954 private static int matchStringUntilLimitPart( 1955 MessagePattern pattern, int partIndex, int limitPartIndex, 1956 String source, int sourceOffset) { 1957 int matchingSourceLength = 0; 1958 String msgString = pattern.getPatternString(); 1959 int prevIndex = pattern.getPart(partIndex).getLimit(); 1960 for (;;) { 1961 Part part = pattern.getPart(++partIndex); 1962 if (partIndex == limitPartIndex || part.getType() == Part.Type.SKIP_SYNTAX) { 1963 int index = part.getIndex(); 1964 int length = index - prevIndex; 1965 if (length != 0 && !source.regionMatches(sourceOffset, msgString, prevIndex, length)) { 1966 return -1; // mismatch 1967 } 1968 matchingSourceLength += length; 1969 if (partIndex == limitPartIndex) { 1970 return matchingSourceLength; 1971 } 1972 prevIndex = part.getLimit(); // SKIP_SYNTAX 1973 } 1974 } 1975 } 1976 1977 /** 1978 * Finds the "other" sub-message. 1979 * @param partIndex the index of the first PluralFormat argument style part. 1980 * @return the "other" sub-message start part index. 1981 */ 1982 private int findOtherSubMessage(int partIndex) { 1983 int count=msgPattern.countParts(); 1984 MessagePattern.Part part=msgPattern.getPart(partIndex); 1985 if(part.getType().hasNumericValue()) { 1986 ++partIndex; 1987 } 1988 // Iterate over (ARG_SELECTOR [ARG_INT|ARG_DOUBLE] message) tuples 1989 // until ARG_LIMIT or end of plural-only pattern. 1990 do { 1991 part=msgPattern.getPart(partIndex++); 1992 MessagePattern.Part.Type type=part.getType(); 1993 if(type==MessagePattern.Part.Type.ARG_LIMIT) { 1994 break; 1995 } 1996 assert type==MessagePattern.Part.Type.ARG_SELECTOR; 1997 // part is an ARG_SELECTOR followed by an optional explicit value, and then a message 1998 if(msgPattern.partSubstringMatches(part, "other")) { 1999 return partIndex; 2000 } 2001 if(msgPattern.getPartType(partIndex).hasNumericValue()) { 2002 ++partIndex; // skip the numeric-value part of "=1" etc. 2003 } 2004 partIndex=msgPattern.getLimitPartIndex(partIndex); 2005 } while(++partIndex<count); 2006 return 0; 2007 } 2008 2009 /** 2010 * Returns the ARG_START index of the first occurrence of the plural number in a sub-message. 2011 * Returns -1 if it is a REPLACE_NUMBER. 2012 * Returns 0 if there is neither. 2013 */ 2014 private int findFirstPluralNumberArg(int msgStart, String argName) { 2015 for(int i=msgStart+1;; ++i) { 2016 Part part=msgPattern.getPart(i); 2017 Part.Type type=part.getType(); 2018 if(type==Part.Type.MSG_LIMIT) { 2019 return 0; 2020 } 2021 if(type==Part.Type.REPLACE_NUMBER) { 2022 return -1; 2023 } 2024 if(type==Part.Type.ARG_START) { 2025 ArgType argType=part.getArgType(); 2026 if(argName.length()!=0 && (argType==ArgType.NONE || argType==ArgType.SIMPLE)) { 2027 part=msgPattern.getPart(i+1); // ARG_NUMBER or ARG_NAME 2028 if(msgPattern.partSubstringMatches(part, argName)) { 2029 return i; 2030 } 2031 } 2032 i=msgPattern.getLimitPartIndex(i); 2033 } 2034 } 2035 } 2036 2037 /** 2038 * Mutable input/output values for the PluralSelectorProvider. 2039 * Separate so that it is possible to make MessageFormat Freezable. 2040 */ 2041 private static final class PluralSelectorContext { 2042 private PluralSelectorContext(int start, String name, Number num, double off) { 2043 startIndex = start; 2044 argName = name; 2045 // number needs to be set even when select() is not called. 2046 // Keep it as a Number/Formattable: 2047 // For format() methods, and to preserve information (e.g., BigDecimal). 2048 if(off == 0) { 2049 number = num; 2050 } else { 2051 number = num.doubleValue() - off; 2052 } 2053 offset = off; 2054 } 2055 @Override 2056 public String toString() { 2057 throw new AssertionError("PluralSelectorContext being formatted, rather than its number"); 2058 } 2059 2060 // Input values for plural selection with decimals. 2061 int startIndex; 2062 String argName; 2063 /** argument number - plural offset */ 2064 Number number; 2065 double offset; 2066 // Output values for plural selection with decimals. 2067 /** -1 if REPLACE_NUMBER, 0 arg not found, >0 ARG_START index */ 2068 int numberArgIndex; 2069 Format formatter; 2070 /** formatted argument number - plural offset */ 2071 String numberString; 2072 /** true if number-offset was formatted with the stock number formatter */ 2073 boolean forReplaceNumber; 2074 } 2075 2076 /** 2077 * This provider helps defer instantiation of a PluralRules object 2078 * until we actually need to select a keyword. 2079 * For example, if the number matches an explicit-value selector like "=1" 2080 * we do not need any PluralRules. 2081 */ 2082 private static final class PluralSelectorProvider implements PluralFormat.PluralSelector { 2083 public PluralSelectorProvider(MessageFormat mf, PluralType type) { 2084 msgFormat = mf; 2085 this.type = type; 2086 } 2087 @Override 2088 public String select(Object ctx, double number) { 2089 if(rules == null) { 2090 rules = PluralRules.forLocale(msgFormat.ulocale, type); 2091 } 2092 // Select a sub-message according to how the number is formatted, 2093 // which is specified in the selected sub-message. 2094 // We avoid this circle by looking at how 2095 // the number is formatted in the "other" sub-message 2096 // which must always be present and usually contains the number. 2097 // Message authors should be consistent across sub-messages. 2098 PluralSelectorContext context = (PluralSelectorContext)ctx; 2099 int otherIndex = msgFormat.findOtherSubMessage(context.startIndex); 2100 context.numberArgIndex = msgFormat.findFirstPluralNumberArg(otherIndex, context.argName); 2101 if(context.numberArgIndex > 0 && msgFormat.cachedFormatters != null) { 2102 context.formatter = msgFormat.cachedFormatters.get(context.numberArgIndex); 2103 } 2104 if(context.formatter == null) { 2105 context.formatter = msgFormat.getStockNumberFormatter(); 2106 context.forReplaceNumber = true; 2107 } 2108 assert context.number.doubleValue() == number; // argument number minus the offset 2109 context.numberString = context.formatter.format(context.number); 2110 if(context.formatter instanceof DecimalFormat) { 2111 FixedDecimal dec = ((DecimalFormat)context.formatter).getFixedDecimal(number); 2112 return rules.select(dec); 2113 } else { 2114 return rules.select(number); 2115 } 2116 } 2117 private MessageFormat msgFormat; 2118 private PluralRules rules; 2119 private PluralType type; 2120 } 2121 2122 @SuppressWarnings("unchecked") 2123 private void format(Object arguments, AppendableWrapper result, FieldPosition fp) { 2124 if ((arguments == null || arguments instanceof Map)) { 2125 format(null, (Map<String, Object>)arguments, result, fp); 2126 } else { 2127 format((Object[])arguments, null, result, fp); 2128 } 2129 } 2130 2131 /** 2132 * Internal routine used by format. 2133 * 2134 * @throws IllegalArgumentException if an argument in the 2135 * <code>arguments</code> map is not of the type 2136 * expected by the format element(s) that use it. 2137 */ 2138 private void format(Object[] arguments, Map<String, Object> argsMap, 2139 AppendableWrapper dest, FieldPosition fp) { 2140 if (arguments != null && msgPattern.hasNamedArguments()) { 2141 throw new IllegalArgumentException( 2142 "This method is not available in MessageFormat objects " + 2143 "that use alphanumeric argument names."); 2144 } 2145 format(0, null, arguments, argsMap, dest, fp); 2146 } 2147 2148 private void resetPattern() { 2149 if (msgPattern != null) { 2150 msgPattern.clear(); 2151 } 2152 if (cachedFormatters != null) { 2153 cachedFormatters.clear(); 2154 } 2155 customFormatArgStarts = null; 2156 } 2157 2158 private static final String[] typeList = 2159 { "number", "date", "time", "spellout", "ordinal", "duration" }; 2160 private static final int 2161 TYPE_NUMBER = 0, 2162 TYPE_DATE = 1, 2163 TYPE_TIME = 2, 2164 TYPE_SPELLOUT = 3, 2165 TYPE_ORDINAL = 4, 2166 TYPE_DURATION = 5; 2167 2168 private static final String[] modifierList = 2169 {"", "currency", "percent", "integer"}; 2170 2171 private static final int 2172 MODIFIER_EMPTY = 0, 2173 MODIFIER_CURRENCY = 1, 2174 MODIFIER_PERCENT = 2, 2175 MODIFIER_INTEGER = 3; 2176 2177 private static final String[] dateModifierList = 2178 {"", "short", "medium", "long", "full"}; 2179 2180 private static final int 2181 DATE_MODIFIER_EMPTY = 0, 2182 DATE_MODIFIER_SHORT = 1, 2183 DATE_MODIFIER_MEDIUM = 2, 2184 DATE_MODIFIER_LONG = 3, 2185 DATE_MODIFIER_FULL = 4; 2186 2187 // Creates an appropriate Format object for the type and style passed. 2188 // Both arguments cannot be null. 2189 private Format createAppropriateFormat(String type, String style) { 2190 Format newFormat = null; 2191 int subformatType = findKeyword(type, typeList); 2192 switch (subformatType){ 2193 case TYPE_NUMBER: 2194 switch (findKeyword(style, modifierList)) { 2195 case MODIFIER_EMPTY: 2196 newFormat = NumberFormat.getInstance(ulocale); 2197 break; 2198 case MODIFIER_CURRENCY: 2199 newFormat = NumberFormat.getCurrencyInstance(ulocale); 2200 break; 2201 case MODIFIER_PERCENT: 2202 newFormat = NumberFormat.getPercentInstance(ulocale); 2203 break; 2204 case MODIFIER_INTEGER: 2205 newFormat = NumberFormat.getIntegerInstance(ulocale); 2206 break; 2207 default: // pattern 2208 newFormat = new DecimalFormat(style, 2209 new DecimalFormatSymbols(ulocale)); 2210 break; 2211 } 2212 break; 2213 case TYPE_DATE: 2214 switch (findKeyword(style, dateModifierList)) { 2215 case DATE_MODIFIER_EMPTY: 2216 newFormat = DateFormat.getDateInstance(DateFormat.DEFAULT, ulocale); 2217 break; 2218 case DATE_MODIFIER_SHORT: 2219 newFormat = DateFormat.getDateInstance(DateFormat.SHORT, ulocale); 2220 break; 2221 case DATE_MODIFIER_MEDIUM: 2222 newFormat = DateFormat.getDateInstance(DateFormat.DEFAULT, ulocale); 2223 break; 2224 case DATE_MODIFIER_LONG: 2225 newFormat = DateFormat.getDateInstance(DateFormat.LONG, ulocale); 2226 break; 2227 case DATE_MODIFIER_FULL: 2228 newFormat = DateFormat.getDateInstance(DateFormat.FULL, ulocale); 2229 break; 2230 default: 2231 newFormat = new SimpleDateFormat(style, ulocale); 2232 break; 2233 } 2234 break; 2235 case TYPE_TIME: 2236 switch (findKeyword(style, dateModifierList)) { 2237 case DATE_MODIFIER_EMPTY: 2238 newFormat = DateFormat.getTimeInstance(DateFormat.DEFAULT, ulocale); 2239 break; 2240 case DATE_MODIFIER_SHORT: 2241 newFormat = DateFormat.getTimeInstance(DateFormat.SHORT, ulocale); 2242 break; 2243 case DATE_MODIFIER_MEDIUM: 2244 newFormat = DateFormat.getTimeInstance(DateFormat.DEFAULT, ulocale); 2245 break; 2246 case DATE_MODIFIER_LONG: 2247 newFormat = DateFormat.getTimeInstance(DateFormat.LONG, ulocale); 2248 break; 2249 case DATE_MODIFIER_FULL: 2250 newFormat = DateFormat.getTimeInstance(DateFormat.FULL, ulocale); 2251 break; 2252 default: 2253 newFormat = new SimpleDateFormat(style, ulocale); 2254 break; 2255 } 2256 break; 2257 case TYPE_SPELLOUT: 2258 { 2259 RuleBasedNumberFormat rbnf = new RuleBasedNumberFormat(ulocale, 2260 RuleBasedNumberFormat.SPELLOUT); 2261 String ruleset = style.trim(); 2262 if (ruleset.length() != 0) { 2263 try { 2264 rbnf.setDefaultRuleSet(ruleset); 2265 } 2266 catch (Exception e) { 2267 // warn invalid ruleset 2268 } 2269 } 2270 newFormat = rbnf; 2271 } 2272 break; 2273 case TYPE_ORDINAL: 2274 { 2275 RuleBasedNumberFormat rbnf = new RuleBasedNumberFormat(ulocale, 2276 RuleBasedNumberFormat.ORDINAL); 2277 String ruleset = style.trim(); 2278 if (ruleset.length() != 0) { 2279 try { 2280 rbnf.setDefaultRuleSet(ruleset); 2281 } 2282 catch (Exception e) { 2283 // warn invalid ruleset 2284 } 2285 } 2286 newFormat = rbnf; 2287 } 2288 break; 2289 case TYPE_DURATION: 2290 { 2291 RuleBasedNumberFormat rbnf = new RuleBasedNumberFormat(ulocale, 2292 RuleBasedNumberFormat.DURATION); 2293 String ruleset = style.trim(); 2294 if (ruleset.length() != 0) { 2295 try { 2296 rbnf.setDefaultRuleSet(ruleset); 2297 } 2298 catch (Exception e) { 2299 // warn invalid ruleset 2300 } 2301 } 2302 newFormat = rbnf; 2303 } 2304 break; 2305 default: 2306 throw new IllegalArgumentException("Unknown format type \"" + type + "\""); 2307 } 2308 return newFormat; 2309 } 2310 2311 private static final Locale rootLocale = new Locale(""); // Locale.ROOT only @since 1.6 2312 2313 private static final int findKeyword(String s, String[] list) { 2314 s = PatternProps.trimWhiteSpace(s).toLowerCase(rootLocale); 2315 for (int i = 0; i < list.length; ++i) { 2316 if (s.equals(list[i])) 2317 return i; 2318 } 2319 return -1; 2320 } 2321 2322 /** 2323 * Custom serialization, new in ICU 4.8. 2324 * We do not want to use default serialization because we only have a small 2325 * amount of persistent state which is better expressed explicitly 2326 * rather than via writing field objects. 2327 * @param out The output stream. 2328 * @serialData Writes the locale as a BCP 47 language tag string, 2329 * the MessagePattern.ApostropheMode as an object, 2330 * and the pattern string (null if none was applied). 2331 * Followed by an int with the number of (int formatIndex, Object formatter) pairs, 2332 * and that many such pairs, corresponding to previous setFormat() calls for custom formats. 2333 * Followed by an int with the number of (int, Object) pairs, 2334 * and that many such pairs, for future (post-ICU 4.8) extension of the serialization format. 2335 */ 2336 private void writeObject(java.io.ObjectOutputStream out) throws IOException { 2337 out.defaultWriteObject(); 2338 // ICU 4.8 custom serialization. 2339 // locale as a BCP 47 language tag 2340 out.writeObject(ulocale.toLanguageTag()); 2341 // ApostropheMode 2342 if (msgPattern == null) { 2343 msgPattern = new MessagePattern(); 2344 } 2345 out.writeObject(msgPattern.getApostropheMode()); 2346 // message pattern string 2347 out.writeObject(msgPattern.getPatternString()); 2348 // custom formatters 2349 if (customFormatArgStarts == null || customFormatArgStarts.isEmpty()) { 2350 out.writeInt(0); 2351 } else { 2352 out.writeInt(customFormatArgStarts.size()); 2353 int formatIndex = 0; 2354 for (int partIndex = 0; (partIndex = nextTopLevelArgStart(partIndex)) >= 0;) { 2355 if (customFormatArgStarts.contains(partIndex)) { 2356 out.writeInt(formatIndex); 2357 out.writeObject(cachedFormatters.get(partIndex)); 2358 } 2359 ++formatIndex; 2360 } 2361 } 2362 // number of future (int, Object) pairs 2363 out.writeInt(0); 2364 } 2365 2366 /** 2367 * Custom deserialization, new in ICU 4.8. See comments on writeObject(). 2368 * @throws InvalidObjectException if the objects read from the stream is invalid. 2369 */ 2370 private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { 2371 in.defaultReadObject(); 2372 // ICU 4.8 custom deserialization. 2373 String languageTag = (String)in.readObject(); 2374 ulocale = ULocale.forLanguageTag(languageTag); 2375 MessagePattern.ApostropheMode aposMode = (MessagePattern.ApostropheMode)in.readObject(); 2376 if (msgPattern == null || aposMode != msgPattern.getApostropheMode()) { 2377 msgPattern = new MessagePattern(aposMode); 2378 } 2379 String msg = (String)in.readObject(); 2380 if (msg != null) { 2381 applyPattern(msg); 2382 } 2383 // custom formatters 2384 for (int numFormatters = in.readInt(); numFormatters > 0; --numFormatters) { 2385 int formatIndex = in.readInt(); 2386 Format formatter = (Format)in.readObject(); 2387 setFormat(formatIndex, formatter); 2388 } 2389 // skip future (int, Object) pairs 2390 for (int numPairs = in.readInt(); numPairs > 0; --numPairs) { 2391 in.readInt(); 2392 in.readObject(); 2393 } 2394 } 2395 2396 private void cacheExplicitFormats() { 2397 if (cachedFormatters != null) { 2398 cachedFormatters.clear(); 2399 } 2400 customFormatArgStarts = null; 2401 // The last two "parts" can at most be ARG_LIMIT and MSG_LIMIT 2402 // which we need not examine. 2403 int limit = msgPattern.countParts() - 2; 2404 // This loop starts at part index 1 because we do need to examine 2405 // ARG_START parts. (But we can ignore the MSG_START.) 2406 for(int i=1; i < limit; ++i) { 2407 Part part = msgPattern.getPart(i); 2408 if(part.getType()!=Part.Type.ARG_START) { 2409 continue; 2410 } 2411 ArgType argType=part.getArgType(); 2412 if(argType != ArgType.SIMPLE) { 2413 continue; 2414 } 2415 int index = i; 2416 i += 2; 2417 String explicitType = msgPattern.getSubstring(msgPattern.getPart(i++)); 2418 String style = ""; 2419 if ((part = msgPattern.getPart(i)).getType() == MessagePattern.Part.Type.ARG_STYLE) { 2420 style = msgPattern.getSubstring(part); 2421 ++i; 2422 } 2423 Format formatter = createAppropriateFormat(explicitType, style); 2424 setArgStartFormat(index, formatter); 2425 } 2426 } 2427 2428 /** 2429 * Sets a formatter for a MessagePattern ARG_START part index. 2430 */ 2431 private void setArgStartFormat(int argStart, Format formatter) { 2432 if (cachedFormatters == null) { 2433 cachedFormatters = new HashMap<Integer, Format>(); 2434 } 2435 cachedFormatters.put(argStart, formatter); 2436 } 2437 2438 /** 2439 * Sets a custom formatter for a MessagePattern ARG_START part index. 2440 * "Custom" formatters are provided by the user via setFormat() or similar APIs. 2441 */ 2442 private void setCustomArgStartFormat(int argStart, Format formatter) { 2443 setArgStartFormat(argStart, formatter); 2444 if (customFormatArgStarts == null) { 2445 customFormatArgStarts = new HashSet<Integer>(); 2446 } 2447 customFormatArgStarts.add(argStart); 2448 } 2449 2450 private static final char SINGLE_QUOTE = '\''; 2451 private static final char CURLY_BRACE_LEFT = '{'; 2452 private static final char CURLY_BRACE_RIGHT = '}'; 2453 2454 private static final int STATE_INITIAL = 0; 2455 private static final int STATE_SINGLE_QUOTE = 1; 2456 private static final int STATE_IN_QUOTE = 2; 2457 private static final int STATE_MSG_ELEMENT = 3; 2458 2459 /** 2460 * {@icu} Converts an 'apostrophe-friendly' pattern into a standard 2461 * pattern. 2462 * <em>This is obsolete for ICU 4.8 and higher MessageFormat pattern strings.</em> 2463 * It can still be useful together with {@link java.text.MessageFormat}. 2464 * 2465 * <p>See the class description for more about apostrophes and quoting, 2466 * and differences between ICU and {@link java.text.MessageFormat}. 2467 * 2468 * <p>{@link java.text.MessageFormat} and ICU 4.6 and earlier MessageFormat 2469 * treat all ASCII apostrophes as 2470 * quotes, which is problematic in some languages, e.g. 2471 * French, where apostrophe is commonly used. This utility 2472 * assumes that only an unpaired apostrophe immediately before 2473 * a brace is a true quote. Other unpaired apostrophes are paired, 2474 * and the resulting standard pattern string is returned. 2475 * 2476 * <p><b>Note</b>: It is not guaranteed that the returned pattern 2477 * is indeed a valid pattern. The only effect is to convert 2478 * between patterns having different quoting semantics. 2479 * 2480 * <p><b>Note</b>: This method only works on top-level messageText, 2481 * not messageText nested inside a complexArg. 2482 * 2483 * @param pattern the 'apostrophe-friendly' pattern to convert 2484 * @return the standard equivalent of the original pattern 2485 * @stable ICU 3.4 2486 */ 2487 public static String autoQuoteApostrophe(String pattern) { 2488 StringBuilder buf = new StringBuilder(pattern.length() * 2); 2489 int state = STATE_INITIAL; 2490 int braceCount = 0; 2491 for (int i = 0, j = pattern.length(); i < j; ++i) { 2492 char c = pattern.charAt(i); 2493 switch (state) { 2494 case STATE_INITIAL: 2495 switch (c) { 2496 case SINGLE_QUOTE: 2497 state = STATE_SINGLE_QUOTE; 2498 break; 2499 case CURLY_BRACE_LEFT: 2500 state = STATE_MSG_ELEMENT; 2501 ++braceCount; 2502 break; 2503 } 2504 break; 2505 case STATE_SINGLE_QUOTE: 2506 switch (c) { 2507 case SINGLE_QUOTE: 2508 state = STATE_INITIAL; 2509 break; 2510 case CURLY_BRACE_LEFT: 2511 case CURLY_BRACE_RIGHT: 2512 state = STATE_IN_QUOTE; 2513 break; 2514 default: 2515 buf.append(SINGLE_QUOTE); 2516 state = STATE_INITIAL; 2517 break; 2518 } 2519 break; 2520 case STATE_IN_QUOTE: 2521 switch (c) { 2522 case SINGLE_QUOTE: 2523 state = STATE_INITIAL; 2524 break; 2525 } 2526 break; 2527 case STATE_MSG_ELEMENT: 2528 switch (c) { 2529 case CURLY_BRACE_LEFT: 2530 ++braceCount; 2531 break; 2532 case CURLY_BRACE_RIGHT: 2533 if (--braceCount == 0) { 2534 state = STATE_INITIAL; 2535 } 2536 break; 2537 } 2538 break; 2539 ///CLOVER:OFF 2540 default: // Never happens. 2541 break; 2542 ///CLOVER:ON 2543 } 2544 buf.append(c); 2545 } 2546 // End of scan 2547 if (state == STATE_SINGLE_QUOTE || state == STATE_IN_QUOTE) { 2548 buf.append(SINGLE_QUOTE); 2549 } 2550 return new String(buf); 2551 } 2552 2553 /** 2554 * Convenience wrapper for Appendable, tracks the result string length. 2555 * Also, Appendable throws IOException, and we turn that into a RuntimeException 2556 * so that we need no throws clauses. 2557 */ 2558 private static final class AppendableWrapper { 2559 public AppendableWrapper(StringBuilder sb) { 2560 app = sb; 2561 length = sb.length(); 2562 attributes = null; 2563 } 2564 2565 public AppendableWrapper(StringBuffer sb) { 2566 app = sb; 2567 length = sb.length(); 2568 attributes = null; 2569 } 2570 2571 public void useAttributes() { 2572 attributes = new ArrayList<AttributeAndPosition>(); 2573 } 2574 2575 public void append(CharSequence s) { 2576 try { 2577 app.append(s); 2578 length += s.length(); 2579 } catch(IOException e) { 2580 throw new ICUUncheckedIOException(e); 2581 } 2582 } 2583 2584 public void append(CharSequence s, int start, int limit) { 2585 try { 2586 app.append(s, start, limit); 2587 length += limit - start; 2588 } catch(IOException e) { 2589 throw new ICUUncheckedIOException(e); 2590 } 2591 } 2592 2593 public void append(CharacterIterator iterator) { 2594 length += append(app, iterator); 2595 } 2596 2597 public static int append(Appendable result, CharacterIterator iterator) { 2598 try { 2599 int start = iterator.getBeginIndex(); 2600 int limit = iterator.getEndIndex(); 2601 int length = limit - start; 2602 if (start < limit) { 2603 result.append(iterator.first()); 2604 while (++start < limit) { 2605 result.append(iterator.next()); 2606 } 2607 } 2608 return length; 2609 } catch(IOException e) { 2610 throw new ICUUncheckedIOException(e); 2611 } 2612 } 2613 2614 public void formatAndAppend(Format formatter, Object arg) { 2615 if (attributes == null) { 2616 append(formatter.format(arg)); 2617 } else { 2618 AttributedCharacterIterator formattedArg = formatter.formatToCharacterIterator(arg); 2619 int prevLength = length; 2620 append(formattedArg); 2621 // Copy all of the attributes from formattedArg to our attributes list. 2622 formattedArg.first(); 2623 int start = formattedArg.getIndex(); // Should be 0 but might not be. 2624 int limit = formattedArg.getEndIndex(); // == start + length - prevLength 2625 int offset = prevLength - start; // Adjust attribute indexes for the result string. 2626 while (start < limit) { 2627 Map<Attribute, Object> map = formattedArg.getAttributes(); 2628 int runLimit = formattedArg.getRunLimit(); 2629 if (map.size() != 0) { 2630 for (Map.Entry<Attribute, Object> entry : map.entrySet()) { 2631 attributes.add( 2632 new AttributeAndPosition( 2633 entry.getKey(), entry.getValue(), 2634 offset + start, offset + runLimit)); 2635 } 2636 } 2637 start = runLimit; 2638 formattedArg.setIndex(start); 2639 } 2640 } 2641 } 2642 2643 public void formatAndAppend(Format formatter, Object arg, String argString) { 2644 if (attributes == null && argString != null) { 2645 append(argString); 2646 } else { 2647 formatAndAppend(formatter, arg); 2648 } 2649 } 2650 2651 private Appendable app; 2652 private int length; 2653 private List<AttributeAndPosition> attributes; 2654 } 2655 2656 private static final class AttributeAndPosition { 2657 /** 2658 * Defaults the field to Field.ARGUMENT. 2659 */ 2660 public AttributeAndPosition(Object fieldValue, int startIndex, int limitIndex) { 2661 init(Field.ARGUMENT, fieldValue, startIndex, limitIndex); 2662 } 2663 2664 public AttributeAndPosition(Attribute field, Object fieldValue, int startIndex, int limitIndex) { 2665 init(field, fieldValue, startIndex, limitIndex); 2666 } 2667 2668 public void init(Attribute field, Object fieldValue, int startIndex, int limitIndex) { 2669 key = field; 2670 value = fieldValue; 2671 start = startIndex; 2672 limit = limitIndex; 2673 } 2674 2675 private Attribute key; 2676 private Object value; 2677 private int start; 2678 private int limit; 2679 } 2680 } 2681