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