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