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