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