1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 package java.text; 19 20 import java.io.IOException; 21 import java.io.ObjectInputStream; 22 import java.io.ObjectOutputStream; 23 import java.io.ObjectStreamField; 24 import java.util.ArrayList; 25 import java.util.Arrays; 26 import java.util.Date; 27 import java.util.Iterator; 28 import java.util.List; 29 import java.util.Locale; 30 import java.util.Vector; 31 import libcore.util.EmptyArray; 32 33 /** 34 * Produces concatenated messages in language-neutral way. New code 35 * should probably use {@link java.util.Formatter} instead. 36 * <p> 37 * {@code MessageFormat} takes a set of objects, formats them and then 38 * inserts the formatted strings into the pattern at the appropriate places. 39 * <p> 40 * <strong>Note:</strong> {@code MessageFormat} differs from the other 41 * {@code Format} classes in that you create a {@code MessageFormat} 42 * object with one of its constructors (not with a {@code getInstance} 43 * style factory method). The factory methods aren't necessary because 44 * {@code MessageFormat} itself doesn't implement locale-specific 45 * behavior. Any locale-specific behavior is defined by the pattern that you 46 * provide as well as the subformats used for inserted arguments. 47 * 48 * <h4><a name="patterns">Patterns and their interpretation</a></h4> 49 * 50 * {@code MessageFormat} uses patterns of the following form: 51 * <blockquote> 52 * 53 * <pre> 54 * <i>MessageFormatPattern:</i> 55 * <i>String</i> 56 * <i>MessageFormatPattern</i> <i>FormatElement</i> <i>String</i> 57 * <i>FormatElement:</i> 58 * { <i>ArgumentIndex</i> } 59 * { <i>ArgumentIndex</i> , <i>FormatType</i> } 60 * { <i>ArgumentIndex</i> , <i>FormatType</i> , <i>FormatStyle</i> } 61 * <i>FormatType: one of </i> 62 * number date time choice 63 * <i>FormatStyle:</i> 64 * short 65 * medium 66 * long 67 * full 68 * integer 69 * currency 70 * percent 71 * <i>SubformatPattern</i> 72 * <i>String:</i> 73 * <i>StringPart<sub>opt</sub></i> 74 * <i>String</i> <i>StringPart</i> 75 * <i>StringPart:</i> 76 * '' 77 * ' <i>QuotedString</i> ' 78 * <i>UnquotedString</i> 79 * <i>SubformatPattern:</i> 80 * <i>SubformatPatternPart<sub>opt</sub></i> 81 * <i>SubformatPattern</i> <i>SubformatPatternPart</i> 82 * <i>SubFormatPatternPart:</i> 83 * ' <i>QuotedPattern</i> ' 84 * <i>UnquotedPattern</i> 85 * </pre> 86 * 87 * </blockquote> 88 * 89 * <p> 90 * Within a <i>String</i>, {@code "''"} represents a single quote. A 91 * <i>QuotedString</i> can contain arbitrary characters except single quotes; 92 * the surrounding single quotes are removed. An <i>UnquotedString</i> can 93 * contain arbitrary characters except single quotes and left curly brackets. 94 * Thus, a string that should result in the formatted message "'{0}'" can be 95 * written as {@code "'''{'0}''"} or {@code "'''{0}'''"}. 96 * <p> 97 * Within a <i>SubformatPattern</i>, different rules apply. A <i>QuotedPattern</i> 98 * can contain arbitrary characters except single quotes, but the surrounding 99 * single quotes are <strong>not</strong> removed, so they may be interpreted 100 * by the subformat. For example, {@code "{1,number,$'#',##}"} will 101 * produce a number format with the hash-sign quoted, with a result such as: 102 * "$#31,45". An <i>UnquotedPattern</i> can contain arbitrary characters except 103 * single quotes, but curly braces within it must be balanced. For example, 104 * {@code "ab {0} de"} and {@code "ab '}' de"} are valid subformat 105 * patterns, but {@code "ab {0'}' de"} and {@code "ab } de"} are 106 * not. 107 * <dl> 108 * <dt><b>Warning:</b></dt> 109 * <dd>The rules for using quotes within message format patterns unfortunately 110 * have shown to be somewhat confusing. In particular, it isn't always obvious 111 * to localizers whether single quotes need to be doubled or not. Make sure to 112 * inform localizers about the rules, and tell them (for example, by using 113 * comments in resource bundle source files) which strings will be processed by 114 * {@code MessageFormat}. Note that localizers may need to use single quotes in 115 * translated strings where the original version doesn't have them. <br> 116 * Note also that the simplest way to avoid the problem is to use the real 117 * apostrophe (single quote) character \u2019 (') for human-readable text, and 118 * to use the ASCII apostrophe (\u0027 ' ) only in program syntax, like quoting 119 * in {@code MessageFormat}. See the annotations for U+0027 Apostrophe in The Unicode 120 * Standard. 121 * </dl> 122 * <p> 123 * The <i>ArgumentIndex</i> value is a non-negative integer written using the 124 * digits '0' through '9', and represents an index into the 125 * {@code arguments} array passed to the {@code format} methods or 126 * the result array returned by the {@code parse} methods. 127 * <p> 128 * The <i>FormatType</i> and <i>FormatStyle</i> values are used to create a 129 * {@code Format} instance for the format element. The following table 130 * shows how the values map to {@code Format} instances. Combinations not shown in the 131 * table are illegal. A <i>SubformatPattern</i> must be a valid pattern string 132 * for the {@code Format} subclass used. 133 * <p> 134 * <table border=1> 135 * <tr> 136 * <th>Format Type</th> 137 * <th>Format Style</th> 138 * <th>Subformat Created</th> 139 * </tr> 140 * <tr> 141 * <td colspan="2"><i>(none)</i></td> 142 * <td>{@code null}</td> 143 * </tr> 144 * <tr> 145 * <td rowspan="5">{@code number}</td> 146 * <td><i>(none)</i></td> 147 * <td>{@code NumberFormat.getInstance(getLocale())}</td> 148 * </tr> 149 * <tr> 150 * <td>{@code integer}</td> 151 * <td>{@code NumberFormat.getIntegerInstance(getLocale())}</td> 152 * </tr> 153 * <tr> 154 * <td>{@code currency}</td> 155 * <td>{@code NumberFormat.getCurrencyInstance(getLocale())}</td> 156 * </tr> 157 * <tr> 158 * <td>{@code percent}</td> 159 * <td>{@code NumberFormat.getPercentInstance(getLocale())}</td> 160 * </tr> 161 * <tr> 162 * <td><i>SubformatPattern</i></td> 163 * <td>{@code new DecimalFormat(subformatPattern, new DecimalFormatSymbols(getLocale()))}</td> 164 * </tr> 165 * <tr> 166 * <td rowspan="6">{@code date}</td> 167 * <td><i>(none)</i></td> 168 * <td>{@code DateFormat.getDateInstance(DateFormat.DEFAULT, getLocale())}</td> 169 * </tr> 170 * <tr> 171 * <td>{@code short}</td> 172 * <td>{@code DateFormat.getDateInstance(DateFormat.SHORT, getLocale())}</td> 173 * </tr> 174 * <tr> 175 * <td>{@code medium}</td> 176 * <td>{@code DateFormat.getDateInstance(DateFormat.DEFAULT, getLocale())}</td> 177 * </tr> 178 * <tr> 179 * <td>{@code long}</td> 180 * <td>{@code DateFormat.getDateInstance(DateFormat.LONG, getLocale())}</td> 181 * </tr> 182 * <tr> 183 * <td>{@code full}</td> 184 * <td>{@code DateFormat.getDateInstance(DateFormat.FULL, getLocale())}</td> 185 * </tr> 186 * <tr> 187 * <td><i>SubformatPattern</i></td> 188 * <td>{@code new SimpleDateFormat(subformatPattern, getLocale())}</td> 189 * </tr> 190 * <tr> 191 * <td rowspan="6">{@code time}</td> 192 * <td><i>(none)</i></td> 193 * <td>{@code DateFormat.getTimeInstance(DateFormat.DEFAULT, getLocale())}</td> 194 * </tr> 195 * <tr> 196 * <td>{@code short}</td> 197 * <td>{@code DateFormat.getTimeInstance(DateFormat.SHORT, getLocale())}</td> 198 * </tr> 199 * <tr> 200 * <td>{@code medium}</td> 201 * <td>{@code DateFormat.getTimeInstance(DateFormat.DEFAULT, getLocale())}</td> 202 * </tr> 203 * <tr> 204 * <td>{@code long}</td> 205 * <td>{@code DateFormat.getTimeInstance(DateFormat.LONG, getLocale())}</td> 206 * </tr> 207 * <tr> 208 * <td>{@code full}</td> 209 * <td>{@code DateFormat.getTimeInstance(DateFormat.FULL, getLocale())}</td> 210 * </tr> 211 * <tr> 212 * <td><i>SubformatPattern</i></td> 213 * <td>{@code new SimpleDateFormat(subformatPattern, getLocale())}</td> 214 * </tr> 215 * <tr> 216 * <td>{@code choice}</td> 217 * <td><i>SubformatPattern</i></td> 218 * <td>{@code new ChoiceFormat(subformatPattern)}</td> 219 * </tr> 220 * </table> 221 * 222 * <h4>Usage Information</h4> 223 * <p> 224 * Here are some examples of usage: <blockquote> 225 * 226 * <pre> 227 * Object[] arguments = { 228 * Integer.valueOf(7), new Date(System.currentTimeMillis()), 229 * "a disturbance in the Force"}; 230 * String result = MessageFormat.format( 231 * "At {1,time} on {1,date}, there was {2} on planet {0,number,integer}.", 232 * arguments); 233 * <em> 234 * Output: 235 * </em> 236 * At 12:30 PM on Jul 3, 2053, there was a disturbance in the Force on planet 7. 237 * </pre> 238 * 239 * </blockquote> 240 * <p> 241 * Typically, the message format will come from resources, and the 242 * arguments will be dynamically set at runtime. 243 * <p> 244 * Example 2: <blockquote> 245 * 246 * <pre> 247 * Object[] testArgs = {Long.valueOf(3), "MyDisk"}; 248 * MessageFormat form = new MessageFormat("The disk \"{1}\" contains {0} file(s)."); 249 * System.out.println(form.format(testArgs)); 250 * <em> 251 * Output with different testArgs: 252 * </em> 253 * The disk "MyDisk" contains 0 file(s). 254 * The disk "MyDisk" contains 1 file(s). 255 * The disk "MyDisk" contains 1,273 file(s). 256 * </pre> 257 * 258 * </blockquote> 259 * 260 * <p> 261 * For more sophisticated patterns, you can use a {@code ChoiceFormat} to 262 * get output such as: 263 * <blockquote> 264 * 265 * <pre> 266 * MessageFormat form = new MessageFormat("The disk \"{1}\" contains {0}."); 267 * double[] filelimits = {0,1,2}; 268 * String[] filepart = {"no files","one file","{0,number} files"}; 269 * ChoiceFormat fileform = new ChoiceFormat(filelimits, filepart); 270 * form.setFormatByArgumentIndex(0, fileform); 271 * Object[] testArgs = {Long.valueOf(12373), "MyDisk"}; 272 * System.out.println(form.format(testArgs)); 273 * <em> 274 * Output (with different testArgs): 275 * </em> 276 * The disk "MyDisk" contains no files. 277 * The disk "MyDisk" contains one file. 278 * The disk "MyDisk" contains 1,273 files. 279 * </pre> 280 * 281 * </blockquote> You can either do this programmatically, as in the above 282 * example, or by using a pattern (see {@link ChoiceFormat} for more 283 * information) as in: <blockquote> 284 * 285 * <pre> 286 * form.applyPattern("There {0,choice,0#are no files|1#is one file|1<are {0,number,integer} files}."); 287 * </pre> 288 * 289 * </blockquote> 290 * <p> 291 * <strong>Note:</strong> As we see above, the string produced by a 292 * {@code ChoiceFormat} in {@code MessageFormat} is treated 293 * specially; occurances of '{' are used to indicated subformats, and cause 294 * recursion. If you create both a {@code MessageFormat} and 295 * {@code ChoiceFormat} programmatically (instead of using the string 296 * patterns), then be careful not to produce a format that recurses on itself, 297 * which will cause an infinite loop. 298 * <p> 299 * When a single argument is parsed more than once in the string, the last match 300 * will be the final result of the parsing. For example: 301 * <blockquote> 302 * <pre> 303 * MessageFormat mf = new MessageFormat("{0,number,#.##}, {0,number,#.#}"); 304 * Object[] objs = {new Double(3.1415)}; 305 * String result = mf.format(objs); 306 * // result now equals "3.14, 3.1" 307 * objs = null; 308 * objs = mf.parse(result, new ParsePosition(0)); 309 * // objs now equals {new Double(3.1)} 310 * </pre> 311 * </blockquote> 312 * <p> 313 * Likewise, parsing with a {@code MessageFormat} object using patterns 314 * containing multiple occurrences of the same argument would return the last 315 * match. For example: 316 * <blockquote> 317 * <pre> 318 * MessageFormat mf = new MessageFormat("{0}, {0}, {0}"); 319 * String forParsing = "x, y, z"; 320 * Object[] objs = mf.parse(forParsing, new ParsePosition(0)); 321 * // result now equals {new String("z")} 322 * </pre> 323 * </blockquote> 324 * <h4><a name="synchronization">Synchronization</a></h4> 325 * <p> 326 * Message formats are not synchronized. It is recommended to create separate 327 * format instances for each thread. If multiple threads access a format 328 * concurrently, it must be synchronized externally. 329 * 330 * @see java.util.Formatter 331 */ 332 public class MessageFormat extends Format { 333 334 private static final long serialVersionUID = 6479157306784022952L; 335 336 private Locale locale; 337 338 transient private String[] strings; 339 340 private int[] argumentNumbers; 341 342 private Format[] formats; 343 344 private int maxOffset; 345 346 transient private int maxArgumentIndex; 347 348 /** 349 * Constructs a new {@code MessageFormat} using the specified pattern and {@code locale}. 350 * 351 * @param template 352 * the pattern. 353 * @param locale 354 * the locale. 355 * @throws IllegalArgumentException 356 * if the pattern cannot be parsed. 357 */ 358 public MessageFormat(String template, Locale locale) { 359 this.locale = locale; 360 applyPattern(template); 361 } 362 363 /** 364 * Constructs a new {@code MessageFormat} using the specified pattern and 365 * the user's default locale. 366 * See "<a href="../util/Locale.html#default_locale">Be wary of the default locale</a>". 367 * 368 * @param template 369 * the pattern. 370 * @throws IllegalArgumentException 371 * if the pattern cannot be parsed. 372 */ 373 public MessageFormat(String template) { 374 this(template, Locale.getDefault()); 375 } 376 377 /** 378 * Changes this {@code MessageFormat} to use the specified pattern. 379 * 380 * @param template 381 * the new pattern. 382 * @throws IllegalArgumentException 383 * if the pattern cannot be parsed. 384 */ 385 public void applyPattern(String template) { 386 int length = template.length(); 387 StringBuffer buffer = new StringBuffer(); 388 ParsePosition position = new ParsePosition(0); 389 ArrayList<String> localStrings = new ArrayList<String>(); 390 int argCount = 0; 391 int[] args = new int[10]; 392 int maxArg = -1; 393 ArrayList<Format> localFormats = new ArrayList<Format>(); 394 while (position.getIndex() < length) { 395 if (Format.upTo(template, position, buffer, '{')) { 396 int arg = 0; 397 int offset = position.getIndex(); 398 if (offset >= length) { 399 throw new IllegalArgumentException("Invalid argument number"); 400 } 401 // Get argument number 402 char ch; 403 while ((ch = template.charAt(offset++)) != '}' && ch != ',') { 404 if (ch < '0' && ch > '9') { 405 throw new IllegalArgumentException("Invalid argument number"); 406 } 407 408 arg = arg * 10 + (ch - '0'); 409 410 if (arg < 0 || offset >= length) { 411 throw new IllegalArgumentException("Invalid argument number"); 412 } 413 } 414 offset--; 415 position.setIndex(offset); 416 localFormats.add(parseVariable(template, position)); 417 if (argCount >= args.length) { 418 int[] newArgs = new int[args.length * 2]; 419 System.arraycopy(args, 0, newArgs, 0, args.length); 420 args = newArgs; 421 } 422 args[argCount++] = arg; 423 if (arg > maxArg) { 424 maxArg = arg; 425 } 426 } 427 localStrings.add(buffer.toString()); 428 buffer.setLength(0); 429 } 430 this.strings = localStrings.toArray(new String[localStrings.size()]); 431 argumentNumbers = args; 432 this.formats = localFormats.toArray(new Format[argCount]); 433 maxOffset = argCount - 1; 434 maxArgumentIndex = maxArg; 435 } 436 437 /** 438 * Returns a new instance of {@code MessageFormat} with the same pattern and 439 * formats as this {@code MessageFormat}. 440 * 441 * @return a shallow copy of this {@code MessageFormat}. 442 * @see java.lang.Cloneable 443 */ 444 @Override 445 public Object clone() { 446 MessageFormat clone = (MessageFormat) super.clone(); 447 Format[] array = new Format[formats.length]; 448 for (int i = formats.length; --i >= 0;) { 449 if (formats[i] != null) { 450 array[i] = (Format) formats[i].clone(); 451 } 452 } 453 clone.formats = array; 454 return clone; 455 } 456 457 /** 458 * Compares the specified object to this {@code MessageFormat} and indicates 459 * if they are equal. In order to be equal, {@code object} must be an 460 * instance of {@code MessageFormat} and have the same pattern. 461 * 462 * @param object 463 * the object to compare with this object. 464 * @return {@code true} if the specified object is equal to this 465 * {@code MessageFormat}; {@code false} otherwise. 466 * @see #hashCode 467 */ 468 @Override 469 public boolean equals(Object object) { 470 if (this == object) { 471 return true; 472 } 473 if (!(object instanceof MessageFormat)) { 474 return false; 475 } 476 MessageFormat format = (MessageFormat) object; 477 if (maxOffset != format.maxOffset) { 478 return false; 479 } 480 // Must use a loop since the lengths may be different due 481 // to serialization cross-loading 482 for (int i = 0; i <= maxOffset; i++) { 483 if (argumentNumbers[i] != format.argumentNumbers[i]) { 484 return false; 485 } 486 } 487 return locale.equals(format.locale) 488 && Arrays.equals(strings, format.strings) 489 && Arrays.equals(formats, format.formats); 490 } 491 492 /** 493 * Formats the specified object using the rules of this message format and 494 * returns an {@code AttributedCharacterIterator} with the formatted message and 495 * attributes. The {@code AttributedCharacterIterator} returned also includes the 496 * attributes from the formats of this message format. 497 * 498 * @param object 499 * the object to format. 500 * @return an {@code AttributedCharacterIterator} with the formatted message and 501 * attributes. 502 * @throws IllegalArgumentException 503 * if the arguments in the object array cannot be formatted 504 * by this message format. 505 */ 506 @Override 507 public AttributedCharacterIterator formatToCharacterIterator(Object object) { 508 if (object == null) { 509 throw new NullPointerException("object == null"); 510 } 511 512 StringBuffer buffer = new StringBuffer(); 513 ArrayList<FieldContainer> fields = new ArrayList<FieldContainer>(); 514 515 // format the message, and find fields 516 formatImpl((Object[]) object, buffer, new FieldPosition(0), fields); 517 518 // create an AttributedString with the formatted buffer 519 AttributedString as = new AttributedString(buffer.toString()); 520 521 // add MessageFormat field attributes and values to the AttributedString 522 for (FieldContainer fc : fields) { 523 as.addAttribute(fc.attribute, fc.value, fc.start, fc.end); 524 } 525 526 // return the CharacterIterator from AttributedString 527 return as.getIterator(); 528 } 529 530 /** 531 * Converts the specified objects into a string which it appends to the 532 * specified string buffer using the pattern of this message format. 533 * <p> 534 * If the {@code field} member of the specified {@code FieldPosition} is 535 * {@code MessageFormat.Field.ARGUMENT}, then the begin and end index of 536 * this field position is set to the location of the first occurrence of a 537 * message format argument. Otherwise, the {@code FieldPosition} is ignored. 538 * 539 * @param objects 540 * the array of objects to format. 541 * @param buffer 542 * the target string buffer to append the formatted message to. 543 * @param field 544 * on input: an optional alignment field; on output: the offsets 545 * of the alignment field in the formatted text. 546 * @return the string buffer. 547 */ 548 public final StringBuffer format(Object[] objects, StringBuffer buffer, 549 FieldPosition field) { 550 return formatImpl(objects, buffer, field, null); 551 } 552 553 private StringBuffer formatImpl(Object[] objects, StringBuffer buffer, 554 FieldPosition position, List<FieldContainer> fields) { 555 FieldPosition passedField = new FieldPosition(0); 556 for (int i = 0; i <= maxOffset; i++) { 557 buffer.append(strings[i]); 558 int begin = buffer.length(); 559 Object arg; 560 if (objects != null && argumentNumbers[i] < objects.length) { 561 arg = objects[argumentNumbers[i]]; 562 } else { 563 buffer.append('{'); 564 buffer.append(argumentNumbers[i]); 565 buffer.append('}'); 566 handleArgumentField(begin, buffer.length(), argumentNumbers[i], position, fields); 567 continue; 568 } 569 Format format = formats[i]; 570 if (format == null || arg == null) { 571 if (arg instanceof Number) { 572 format = NumberFormat.getInstance(); 573 } else if (arg instanceof Date) { 574 format = DateFormat.getInstance(); 575 } else { 576 buffer.append(arg); 577 handleArgumentField(begin, buffer.length(), argumentNumbers[i], position, fields); 578 continue; 579 } 580 } 581 if (format instanceof ChoiceFormat) { 582 String result = format.format(arg); 583 MessageFormat mf = new MessageFormat(result); 584 mf.setLocale(locale); 585 mf.format(objects, buffer, passedField); 586 handleArgumentField(begin, buffer.length(), argumentNumbers[i], position, fields); 587 handleFormat(format, arg, begin, fields); 588 } else { 589 format.format(arg, buffer, passedField); 590 handleArgumentField(begin, buffer.length(), argumentNumbers[i], position, fields); 591 handleFormat(format, arg, begin, fields); 592 } 593 } 594 if (maxOffset + 1 < strings.length) { 595 buffer.append(strings[maxOffset + 1]); 596 } 597 return buffer; 598 } 599 600 /** 601 * Adds a new FieldContainer with MessageFormat.Field.ARGUMENT field, 602 * argIndex, begin and end index to the fields list, or sets the 603 * position's begin and end index if it has MessageFormat.Field.ARGUMENT as 604 * its field attribute. 605 */ 606 private void handleArgumentField(int begin, int end, int argIndex, 607 FieldPosition position, List<FieldContainer> fields) { 608 if (fields != null) { 609 fields.add(new FieldContainer(begin, end, Field.ARGUMENT, Integer.valueOf(argIndex))); 610 } else { 611 if (position != null 612 && position.getFieldAttribute() == Field.ARGUMENT 613 && position.getEndIndex() == 0) { 614 position.setBeginIndex(begin); 615 position.setEndIndex(end); 616 } 617 } 618 } 619 620 /** 621 * An inner class to store attributes, values, start and end indices. 622 * Instances of this inner class are used as elements for the fields list. 623 */ 624 private static class FieldContainer { 625 int start, end; 626 627 AttributedCharacterIterator.Attribute attribute; 628 629 Object value; 630 631 public FieldContainer(int start, int end, 632 AttributedCharacterIterator.Attribute attribute, Object value) { 633 this.start = start; 634 this.end = end; 635 this.attribute = attribute; 636 this.value = value; 637 } 638 } 639 640 /** 641 * If fields list is not null, find and add the fields of this format to 642 * the fields list by iterating through its AttributedCharacterIterator 643 * 644 * @param format 645 * the format to find fields for 646 * @param arg 647 * object to format 648 * @param begin 649 * the index where the string this format has formatted begins 650 */ 651 private void handleFormat(Format format, Object arg, int begin, List<FieldContainer> fields) { 652 if (fields == null) { 653 return; 654 } 655 AttributedCharacterIterator iterator = format.formatToCharacterIterator(arg); 656 while (iterator.getIndex() != iterator.getEndIndex()) { 657 int start = iterator.getRunStart(); 658 int end = iterator.getRunLimit(); 659 Iterator<?> it = iterator.getAttributes().keySet().iterator(); 660 while (it.hasNext()) { 661 AttributedCharacterIterator.Attribute attribute = 662 (AttributedCharacterIterator.Attribute) it.next(); 663 Object value = iterator.getAttribute(attribute); 664 fields.add(new FieldContainer(begin + start, begin + end, attribute, value)); 665 } 666 iterator.setIndex(end); 667 } 668 } 669 670 /** 671 * Converts the specified objects into a string which it appends to the 672 * specified string buffer using the pattern of this message format. 673 * <p> 674 * If the {@code field} member of the specified {@code FieldPosition} is 675 * {@code MessageFormat.Field.ARGUMENT}, then the begin and end index of 676 * this field position is set to the location of the first occurrence of a 677 * message format argument. Otherwise, the {@code FieldPosition} is ignored. 678 * <p> 679 * Calling this method is equivalent to calling 680 * <blockquote> 681 * 682 * <pre> 683 * format((Object[])object, buffer, field) 684 * </pre> 685 * 686 * </blockquote> 687 * 688 * @param object 689 * the object to format, must be an array of {@code Object}. 690 * @param buffer 691 * the target string buffer to append the formatted message to. 692 * @param field 693 * on input: an optional alignment field; on output: the offsets 694 * of the alignment field in the formatted text. 695 * @return the string buffer. 696 * @throws ClassCastException 697 * if {@code object} is not an array of {@code Object}. 698 */ 699 @Override 700 public final StringBuffer format(Object object, StringBuffer buffer, 701 FieldPosition field) { 702 return format((Object[]) object, buffer, field); 703 } 704 705 /** 706 * Formats the supplied objects using the specified message format pattern. 707 * 708 * @param format the format string (see {@link java.util.Formatter#format}) 709 * @param args 710 * the list of arguments passed to the formatter. If there are 711 * more arguments than required by {@code format}, 712 * additional arguments are ignored. 713 * @return the formatted result. 714 * @throws IllegalArgumentException 715 * if the pattern cannot be parsed. 716 */ 717 public static String format(String format, Object... args) { 718 if (args != null) { 719 for (int i = 0; i < args.length; i++) { 720 if (args[i] == null) { 721 args[i] = "null"; 722 } 723 } 724 } 725 return new MessageFormat(format).format(args); 726 } 727 728 /** 729 * Returns the {@code Format} instances used by this message format. 730 * 731 * @return an array of {@code Format} instances. 732 */ 733 public Format[] getFormats() { 734 return formats.clone(); 735 } 736 737 /** 738 * Returns the formats used for each argument index. If an argument is 739 * placed more than once in the pattern string, then this returns the format 740 * of the last one. 741 * 742 * @return an array of formats, ordered by argument index. 743 */ 744 public Format[] getFormatsByArgumentIndex() { 745 Format[] answer = new Format[maxArgumentIndex + 1]; 746 for (int i = 0; i < maxOffset + 1; i++) { 747 answer[argumentNumbers[i]] = formats[i]; 748 } 749 return answer; 750 } 751 752 /** 753 * Sets the format used for the argument at index {@code argIndex} to 754 * {@code format}. 755 * 756 * @param argIndex 757 * the index of the format to set. 758 * @param format 759 * the format that will be set at index {@code argIndex}. 760 */ 761 public void setFormatByArgumentIndex(int argIndex, Format format) { 762 for (int i = 0; i < maxOffset + 1; i++) { 763 if (argumentNumbers[i] == argIndex) { 764 formats[i] = format; 765 } 766 } 767 } 768 769 /** 770 * Sets the formats used for each argument. The {@code formats} array 771 * elements should be in the order of the argument indices. 772 * 773 * @param formats 774 * the formats in an array. 775 */ 776 public void setFormatsByArgumentIndex(Format[] formats) { 777 for (int j = 0; j < formats.length; j++) { 778 for (int i = 0; i < maxOffset + 1; i++) { 779 if (argumentNumbers[i] == j) { 780 this.formats[i] = formats[j]; 781 } 782 } 783 } 784 } 785 786 /** 787 * Returns the locale used when creating formats. 788 * 789 * @return the locale used to create formats. 790 */ 791 public Locale getLocale() { 792 return locale; 793 } 794 795 @Override 796 public int hashCode() { 797 int hashCode = 0; 798 for (int i = 0; i <= maxOffset; i++) { 799 hashCode += argumentNumbers[i] + strings[i].hashCode(); 800 if (formats[i] != null) { 801 hashCode += formats[i].hashCode(); 802 } 803 } 804 if (maxOffset + 1 < strings.length) { 805 hashCode += strings[maxOffset + 1].hashCode(); 806 } 807 if (locale != null) { 808 return hashCode + locale.hashCode(); 809 } 810 return hashCode; 811 } 812 813 /** 814 * Parses the message arguments from the specified string using the rules of 815 * this message format. 816 * 817 * @param string 818 * the string to parse. 819 * @return the array of {@code Object} arguments resulting from the parse. 820 * @throws ParseException 821 * if an error occurs during parsing. 822 */ 823 public Object[] parse(String string) throws ParseException { 824 ParsePosition position = new ParsePosition(0); 825 Object[] result = parse(string, position); 826 if (position.getIndex() == 0) { 827 throw new ParseException("Parse failure", position.getErrorIndex()); 828 } 829 return result; 830 } 831 832 /** 833 * Parses the message argument from the specified string starting at the 834 * index specified by {@code position}. If the string is successfully 835 * parsed then the index of the {@code ParsePosition} is updated to the 836 * index following the parsed text. On error, the index is unchanged and the 837 * error index of {@code ParsePosition} is set to the index where the error 838 * occurred. 839 * 840 * @param string 841 * the string to parse. 842 * @param position 843 * input/output parameter, specifies the start index in 844 * {@code string} from where to start parsing. If parsing is 845 * successful, it is updated with the index following the parsed 846 * text; on error, the index is unchanged and the error index is 847 * set to the index where the error occurred. 848 * @return the array of objects resulting from the parse, or {@code null} if 849 * there is an error. 850 */ 851 public Object[] parse(String string, ParsePosition position) { 852 if (string == null) { 853 return EmptyArray.OBJECT; 854 } 855 ParsePosition internalPos = new ParsePosition(0); 856 int offset = position.getIndex(); 857 Object[] result = new Object[maxArgumentIndex + 1]; 858 for (int i = 0; i <= maxOffset; i++) { 859 String sub = strings[i]; 860 if (!string.startsWith(sub, offset)) { 861 position.setErrorIndex(offset); 862 return null; 863 } 864 offset += sub.length(); 865 Object parse; 866 Format format = formats[i]; 867 if (format == null) { 868 if (i + 1 < strings.length) { 869 int next = string.indexOf(strings[i + 1], offset); 870 if (next == -1) { 871 position.setErrorIndex(offset); 872 return null; 873 } 874 parse = string.substring(offset, next); 875 offset = next; 876 } else { 877 parse = string.substring(offset); 878 offset = string.length(); 879 } 880 } else { 881 internalPos.setIndex(offset); 882 parse = format.parseObject(string, internalPos); 883 if (internalPos.getErrorIndex() != -1) { 884 position.setErrorIndex(offset); 885 return null; 886 } 887 offset = internalPos.getIndex(); 888 } 889 result[argumentNumbers[i]] = parse; 890 } 891 if (maxOffset + 1 < strings.length) { 892 String sub = strings[maxOffset + 1]; 893 if (!string.startsWith(sub, offset)) { 894 position.setErrorIndex(offset); 895 return null; 896 } 897 offset += sub.length(); 898 } 899 position.setIndex(offset); 900 return result; 901 } 902 903 /** 904 * Parses the message argument from the specified string starting at the 905 * index specified by {@code position}. If the string is successfully 906 * parsed then the index of the {@code ParsePosition} is updated to the 907 * index following the parsed text. On error, the index is unchanged and the 908 * error index of {@code ParsePosition} is set to the index where the error 909 * occurred. 910 * 911 * @param string 912 * the string to parse. 913 * @param position 914 * input/output parameter, specifies the start index in 915 * {@code string} from where to start parsing. If parsing is 916 * successful, it is updated with the index following the parsed 917 * text; on error, the index is unchanged and the error index is 918 * set to the index where the error occurred. 919 * @return the array of objects resulting from the parse, or {@code null} if 920 * there is an error. 921 */ 922 @Override 923 public Object parseObject(String string, ParsePosition position) { 924 return parse(string, position); 925 } 926 927 private int match(String string, ParsePosition position, boolean last, 928 String[] tokens) { 929 int length = string.length(), offset = position.getIndex(), token = -1; 930 while (offset < length && Character.isWhitespace(string.charAt(offset))) { 931 offset++; 932 } 933 for (int i = tokens.length; --i >= 0;) { 934 if (string.regionMatches(true, offset, tokens[i], 0, tokens[i] 935 .length())) { 936 token = i; 937 break; 938 } 939 } 940 if (token == -1) { 941 return -1; 942 } 943 offset += tokens[token].length(); 944 while (offset < length && Character.isWhitespace(string.charAt(offset))) { 945 offset++; 946 } 947 char ch; 948 if (offset < length 949 && ((ch = string.charAt(offset)) == '}' || (!last && ch == ','))) { 950 position.setIndex(offset + 1); 951 return token; 952 } 953 return -1; 954 } 955 956 private Format parseVariable(String string, ParsePosition position) { 957 int length = string.length(), offset = position.getIndex(); 958 char ch; 959 if (offset >= length || ((ch = string.charAt(offset++)) != '}' && ch != ',')) { 960 throw new IllegalArgumentException("Missing element format"); 961 } 962 position.setIndex(offset); 963 if (ch == '}') { 964 return null; 965 } 966 int type = match(string, position, false, 967 new String[] { "time", "date", "number", "choice" }); 968 if (type == -1) { 969 throw new IllegalArgumentException("Unknown element format"); 970 } 971 StringBuffer buffer = new StringBuffer(); 972 ch = string.charAt(position.getIndex() - 1); 973 switch (type) { 974 case 0: // time 975 case 1: // date 976 if (ch == '}') { 977 return type == 1 ? DateFormat.getDateInstance( 978 DateFormat.DEFAULT, locale) : DateFormat 979 .getTimeInstance(DateFormat.DEFAULT, locale); 980 } 981 int dateStyle = match(string, position, true, 982 new String[] { "full", "long", "medium", "short" }); 983 if (dateStyle == -1) { 984 Format.upToWithQuotes(string, position, buffer, '}', '{'); 985 return new SimpleDateFormat(buffer.toString(), locale); 986 } 987 switch (dateStyle) { 988 case 0: 989 dateStyle = DateFormat.FULL; 990 break; 991 case 1: 992 dateStyle = DateFormat.LONG; 993 break; 994 case 2: 995 dateStyle = DateFormat.MEDIUM; 996 break; 997 case 3: 998 dateStyle = DateFormat.SHORT; 999 break; 1000 } 1001 return type == 1 ? DateFormat 1002 .getDateInstance(dateStyle, locale) : DateFormat 1003 .getTimeInstance(dateStyle, locale); 1004 case 2: // number 1005 if (ch == '}') { 1006 return NumberFormat.getInstance(locale); 1007 } 1008 int numberStyle = match(string, position, true, 1009 new String[] { "currency", "percent", "integer" }); 1010 if (numberStyle == -1) { 1011 Format.upToWithQuotes(string, position, buffer, '}', '{'); 1012 return new DecimalFormat(buffer.toString(), 1013 new DecimalFormatSymbols(locale)); 1014 } 1015 switch (numberStyle) { 1016 case 0: // currency 1017 return NumberFormat.getCurrencyInstance(locale); 1018 case 1: // percent 1019 return NumberFormat.getPercentInstance(locale); 1020 } 1021 return NumberFormat.getIntegerInstance(locale); 1022 } 1023 // choice 1024 try { 1025 Format.upToWithQuotes(string, position, buffer, '}', '{'); 1026 } catch (IllegalArgumentException e) { 1027 // ignored 1028 } 1029 return new ChoiceFormat(buffer.toString()); 1030 } 1031 1032 /** 1033 * Sets the specified format used by this message format. 1034 * 1035 * @param offset 1036 * the index of the format to change. 1037 * @param format 1038 * the {@code Format} that replaces the old format. 1039 */ 1040 public void setFormat(int offset, Format format) { 1041 formats[offset] = format; 1042 } 1043 1044 /** 1045 * Sets the formats used by this message format. 1046 * 1047 * @param formats 1048 * an array of {@code Format}. 1049 */ 1050 public void setFormats(Format[] formats) { 1051 int min = this.formats.length; 1052 if (formats.length < min) { 1053 min = formats.length; 1054 } 1055 for (int i = 0; i < min; i++) { 1056 this.formats[i] = formats[i]; 1057 } 1058 } 1059 1060 /** 1061 * Sets the locale to use when creating {@code Format} instances. Changing 1062 * the locale may change the behavior of {@code applyPattern}, 1063 * {@code toPattern}, {@code format} and {@code formatToCharacterIterator}. 1064 * 1065 * @param locale 1066 * the new locale. 1067 */ 1068 public void setLocale(Locale locale) { 1069 this.locale = locale; 1070 for (int i = 0; i <= maxOffset; i++) { 1071 Format format = formats[i]; 1072 // java specification undefined for null argument, change into 1073 // a more tolerant implementation 1074 if (format instanceof DecimalFormat) { 1075 try { 1076 formats[i] = new DecimalFormat(((DecimalFormat) format) 1077 .toPattern(), new DecimalFormatSymbols(locale)); 1078 } catch (NullPointerException npe){ 1079 formats[i] = null; 1080 } 1081 } else if (format instanceof SimpleDateFormat) { 1082 try { 1083 formats[i] = new SimpleDateFormat(((SimpleDateFormat) format) 1084 .toPattern(), locale); 1085 } catch (NullPointerException npe) { 1086 formats[i] = null; 1087 } 1088 } 1089 } 1090 } 1091 1092 private String decodeDecimalFormat(StringBuffer buffer, Format format) { 1093 buffer.append(",number"); 1094 if (format.equals(NumberFormat.getNumberInstance(locale))) { 1095 // Empty block 1096 } else if (format.equals(NumberFormat.getIntegerInstance(locale))) { 1097 buffer.append(",integer"); 1098 } else if (format.equals(NumberFormat.getCurrencyInstance(locale))) { 1099 buffer.append(",currency"); 1100 } else if (format.equals(NumberFormat.getPercentInstance(locale))) { 1101 buffer.append(",percent"); 1102 } else { 1103 buffer.append(','); 1104 return ((DecimalFormat) format).toPattern(); 1105 } 1106 return null; 1107 } 1108 1109 private String decodeSimpleDateFormat(StringBuffer buffer, Format format) { 1110 if (format.equals(DateFormat.getTimeInstance(DateFormat.DEFAULT, locale))) { 1111 buffer.append(",time"); 1112 } else if (format.equals(DateFormat.getDateInstance(DateFormat.DEFAULT, 1113 locale))) { 1114 buffer.append(",date"); 1115 } else if (format.equals(DateFormat.getTimeInstance(DateFormat.SHORT, 1116 locale))) { 1117 buffer.append(",time,short"); 1118 } else if (format.equals(DateFormat.getDateInstance(DateFormat.SHORT, 1119 locale))) { 1120 buffer.append(",date,short"); 1121 } else if (format.equals(DateFormat.getTimeInstance(DateFormat.LONG, 1122 locale))) { 1123 buffer.append(",time,long"); 1124 } else if (format.equals(DateFormat.getDateInstance(DateFormat.LONG, 1125 locale))) { 1126 buffer.append(",date,long"); 1127 } else if (format.equals(DateFormat.getTimeInstance(DateFormat.FULL, 1128 locale))) { 1129 buffer.append(",time,full"); 1130 } else if (format.equals(DateFormat.getDateInstance(DateFormat.FULL, 1131 locale))) { 1132 buffer.append(",date,full"); 1133 } else { 1134 buffer.append(",date,"); 1135 return ((SimpleDateFormat) format).toPattern(); 1136 } 1137 return null; 1138 } 1139 1140 /** 1141 * Returns the pattern of this message format. 1142 * 1143 * @return the pattern. 1144 */ 1145 public String toPattern() { 1146 StringBuffer buffer = new StringBuffer(); 1147 for (int i = 0; i <= maxOffset; i++) { 1148 appendQuoted(buffer, strings[i]); 1149 buffer.append('{'); 1150 buffer.append(argumentNumbers[i]); 1151 Format format = formats[i]; 1152 String pattern = null; 1153 if (format instanceof ChoiceFormat) { 1154 buffer.append(",choice,"); 1155 pattern = ((ChoiceFormat) format).toPattern(); 1156 } else if (format instanceof DecimalFormat) { 1157 pattern = decodeDecimalFormat(buffer, format); 1158 } else if (format instanceof SimpleDateFormat) { 1159 pattern = decodeSimpleDateFormat(buffer, format); 1160 } else if (format != null) { 1161 throw new IllegalArgumentException("Unknown format"); 1162 } 1163 if (pattern != null) { 1164 boolean quote = false; 1165 int index = 0, length = pattern.length(), count = 0; 1166 while (index < length) { 1167 char ch = pattern.charAt(index++); 1168 if (ch == '\'') { 1169 quote = !quote; 1170 } 1171 if (!quote) { 1172 if (ch == '{') { 1173 count++; 1174 } 1175 if (ch == '}') { 1176 if (count > 0) { 1177 count--; 1178 } else { 1179 buffer.append("'}"); 1180 ch = '\''; 1181 } 1182 } 1183 } 1184 buffer.append(ch); 1185 } 1186 } 1187 buffer.append('}'); 1188 } 1189 if (maxOffset + 1 < strings.length) { 1190 appendQuoted(buffer, strings[maxOffset + 1]); 1191 } 1192 return buffer.toString(); 1193 } 1194 1195 private void appendQuoted(StringBuffer buffer, String string) { 1196 int length = string.length(); 1197 for (int i = 0; i < length; i++) { 1198 char ch = string.charAt(i); 1199 if (ch == '{' || ch == '}') { 1200 buffer.append('\''); 1201 buffer.append(ch); 1202 buffer.append('\''); 1203 } else { 1204 buffer.append(ch); 1205 } 1206 } 1207 } 1208 1209 private static final ObjectStreamField[] serialPersistentFields = { 1210 new ObjectStreamField("argumentNumbers", int[].class), 1211 new ObjectStreamField("formats", Format[].class), 1212 new ObjectStreamField("locale", Locale.class), 1213 new ObjectStreamField("maxOffset", int.class), 1214 new ObjectStreamField("offsets", int[].class), 1215 new ObjectStreamField("pattern", String.class), 1216 }; 1217 1218 private void writeObject(ObjectOutputStream stream) throws IOException { 1219 ObjectOutputStream.PutField fields = stream.putFields(); 1220 fields.put("argumentNumbers", argumentNumbers); 1221 Format[] compatibleFormats = formats; 1222 fields.put("formats", compatibleFormats); 1223 fields.put("locale", locale); 1224 fields.put("maxOffset", maxOffset); 1225 int offset = 0; 1226 int offsetsLength = maxOffset + 1; 1227 int[] offsets = new int[offsetsLength]; 1228 StringBuilder pattern = new StringBuilder(); 1229 for (int i = 0; i <= maxOffset; i++) { 1230 offset += strings[i].length(); 1231 offsets[i] = offset; 1232 pattern.append(strings[i]); 1233 } 1234 if (maxOffset + 1 < strings.length) { 1235 pattern.append(strings[maxOffset + 1]); 1236 } 1237 fields.put("offsets", offsets); 1238 fields.put("pattern", pattern.toString()); 1239 stream.writeFields(); 1240 } 1241 1242 private void readObject(ObjectInputStream stream) throws IOException, 1243 ClassNotFoundException { 1244 ObjectInputStream.GetField fields = stream.readFields(); 1245 argumentNumbers = (int[]) fields.get("argumentNumbers", null); 1246 formats = (Format[]) fields.get("formats", null); 1247 locale = (Locale) fields.get("locale", null); 1248 maxOffset = fields.get("maxOffset", 0); 1249 int[] offsets = (int[]) fields.get("offsets", null); 1250 String pattern = (String) fields.get("pattern", null); 1251 int length; 1252 if (maxOffset < 0) { 1253 length = pattern.length() > 0 ? 1 : 0; 1254 } else { 1255 length = maxOffset 1256 + (offsets[maxOffset] == pattern.length() ? 1 : 2); 1257 } 1258 strings = new String[length]; 1259 int last = 0; 1260 for (int i = 0; i <= maxOffset; i++) { 1261 strings[i] = pattern.substring(last, offsets[i]); 1262 last = offsets[i]; 1263 } 1264 if (maxOffset + 1 < strings.length) { 1265 strings[strings.length - 1] = pattern.substring(last, pattern 1266 .length()); 1267 } 1268 } 1269 1270 /** 1271 * The instances of this inner class are used as attribute keys in 1272 * {@code AttributedCharacterIterator} that the 1273 * {@link MessageFormat#formatToCharacterIterator(Object)} method returns. 1274 * <p> 1275 * There is no public constructor in this class, the only instances are the 1276 * constants defined here. 1277 */ 1278 public static class Field extends Format.Field { 1279 1280 private static final long serialVersionUID = 7899943957617360810L; 1281 1282 /** 1283 * This constant stands for the message argument. 1284 */ 1285 public static final Field ARGUMENT = new Field("message argument field"); 1286 1287 /** 1288 * Constructs a new instance of {@code MessageFormat.Field} with the 1289 * given field name. 1290 * 1291 * @param fieldName 1292 * the field name. 1293 */ 1294 protected Field(String fieldName) { 1295 super(fieldName); 1296 } 1297 } 1298 } 1299