001/* MessageFormat.java - Localized message formatting. 002 Copyright (C) 1999, 2001, 2002, 2004, 2005, 2012 Free Software Foundation, Inc. 003 004This file is part of GNU Classpath. 005 006GNU Classpath is free software; you can redistribute it and/or modify 007it under the terms of the GNU General Public License as published by 008the Free Software Foundation; either version 2, or (at your option) 009any later version. 010 011GNU Classpath is distributed in the hope that it will be useful, but 012WITHOUT ANY WARRANTY; without even the implied warranty of 013MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 014General Public License for more details. 015 016You should have received a copy of the GNU General Public License 017along with GNU Classpath; see the file COPYING. If not, write to the 018Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 01902110-1301 USA. 020 021Linking this library statically or dynamically with other modules is 022making a combined work based on this library. Thus, the terms and 023conditions of the GNU General Public License cover the whole 024combination. 025 026As a special exception, the copyright holders of this library give you 027permission to link this library with independent modules to produce an 028executable, regardless of the license terms of these independent 029modules, and to copy and distribute the resulting executable under 030terms of your choice, provided that you also meet, for each linked 031independent module, the terms and conditions of the license of that 032module. An independent module is a module which is not derived from 033or based on this library. If you modify this library, you may extend 034this exception to your version of the library, but you are not 035obligated to do so. If you do not wish to do so, delete this 036exception statement from your version. */ 037 038 039package java.text; 040 041import gnu.java.lang.CPStringBuilder; 042 043import gnu.java.text.FormatCharacterIterator; 044 045import java.io.InvalidObjectException; 046 047import java.util.ArrayList; 048import java.util.Date; 049import java.util.HashMap; 050import java.util.List; 051import java.util.Locale; 052 053public class MessageFormat extends Format 054{ 055 /** 056 * @author Tom Tromey (tromey@cygnus.com) 057 * @author Jorge Aliss (jaliss@hotmail.com) 058 * @date March 3, 1999 059 */ 060 /* Written using "Java Class Libraries", 2nd edition, plus online 061 * API docs for JDK 1.2 from http://www.javasoft.com. 062 * Status: Believed complete and correct to 1.2, except serialization. 063 * and parsing. 064 */ 065 private static final class MessageFormatElement 066 { 067 // Argument number. 068 int argNumber; 069 // Formatter to be used. This is the format set by setFormat. 070 Format setFormat; 071 // Formatter to be used based on the type. 072 Format format; 073 074 // Argument will be checked to make sure it is an instance of this 075 // class. 076 Class<?> formatClass; 077 078 // Formatter type. 079 String type; 080 // Formatter style. 081 String style; 082 083 // Text to follow this element. 084 String trailer; 085 086 // Recompute the locale-based formatter. 087 void setLocale (Locale loc) 088 { 089 if (type != null) 090 { 091 if (type.equals("number")) 092 { 093 formatClass = java.lang.Number.class; 094 095 if (style == null) 096 format = NumberFormat.getInstance(loc); 097 else if (style.equals("currency")) 098 format = NumberFormat.getCurrencyInstance(loc); 099 else if (style.equals("percent")) 100 format = NumberFormat.getPercentInstance(loc); 101 else if (style.equals("integer")) 102 format = NumberFormat.getIntegerInstance(loc); 103 else 104 { 105 format = NumberFormat.getNumberInstance(loc); 106 DecimalFormat df = (DecimalFormat) format; 107 df.applyPattern(style); 108 } 109 } 110 else if (type.equals("time") || type.equals("date")) 111 { 112 formatClass = java.util.Date.class; 113 114 int val = DateFormat.DEFAULT; 115 boolean styleIsPattern = false; 116 if (style != null) 117 { 118 if (style.equals("short")) 119 val = DateFormat.SHORT; 120 else if (style.equals("medium")) 121 val = DateFormat.MEDIUM; 122 else if (style.equals("long")) 123 val = DateFormat.LONG; 124 else if (style.equals("full")) 125 val = DateFormat.FULL; 126 else 127 styleIsPattern = true; 128 } 129 130 if (type.equals("time")) 131 format = DateFormat.getTimeInstance(val, loc); 132 else 133 format = DateFormat.getDateInstance(val, loc); 134 135 if (styleIsPattern) 136 { 137 SimpleDateFormat sdf = (SimpleDateFormat) format; 138 sdf.applyPattern(style); 139 } 140 } 141 else if (type.equals("choice")) 142 { 143 formatClass = java.lang.Number.class; 144 145 if (style == null) 146 throw new 147 IllegalArgumentException ("style required for choice format"); 148 format = new ChoiceFormat (style); 149 } 150 } 151 } 152 } 153 154 private static final long serialVersionUID = 6479157306784022952L; 155 156 public static class Field extends Format.Field 157 { 158 static final long serialVersionUID = 7899943957617360810L; 159 160 /** 161 * This is the attribute set for all characters produced 162 * by MessageFormat during a formatting. 163 */ 164 public static final MessageFormat.Field ARGUMENT = new MessageFormat.Field("argument"); 165 166 // For deserialization 167 private Field() 168 { 169 super(""); 170 } 171 172 protected Field(String s) 173 { 174 super(s); 175 } 176 177 /** 178 * invoked to resolve the true static constant by 179 * comparing the deserialized object to know name. 180 * 181 * @return object constant 182 */ 183 protected Object readResolve() throws InvalidObjectException 184 { 185 if (getName().equals(ARGUMENT.getName())) 186 return ARGUMENT; 187 188 throw new InvalidObjectException("no such MessageFormat field called " + getName()); 189 } 190 191 } 192 193 // Helper that returns the text up to the next format opener. The 194 // text is put into BUFFER. Returns index of character after end of 195 // string. Throws IllegalArgumentException on error. 196 private static int scanString(String pat, int index, CPStringBuilder buffer) 197 { 198 int max = pat.length(); 199 buffer.setLength(0); 200 boolean quoted = false; 201 for (; index < max; ++index) 202 { 203 char c = pat.charAt(index); 204 if (quoted) 205 { 206 // In a quoted context, a single quote ends the quoting. 207 if (c == '\'') 208 quoted = false; 209 else 210 buffer.append(c); 211 } 212 // Check for '', which is a single quote. 213 else if (c == '\'' && index + 1 < max && pat.charAt(index + 1) == '\'') 214 { 215 buffer.append(c); 216 ++index; 217 } 218 else if (c == '\'') 219 { 220 // Start quoting. 221 quoted = true; 222 } 223 else if (c == '{') 224 break; 225 else 226 buffer.append(c); 227 } 228 // Note that we explicitly allow an unterminated quote. This is 229 // done for compatibility. 230 return index; 231 } 232 233 // This helper retrieves a single part of a format element. Returns 234 // the index of the terminating character. 235 private static int scanFormatElement(String pat, int index, 236 CPStringBuilder buffer, char term) 237 { 238 int max = pat.length(); 239 buffer.setLength(0); 240 int brace_depth = 1; 241 boolean quoted = false; 242 243 for (; index < max; ++index) 244 { 245 char c = pat.charAt(index); 246 // First see if we should turn off quoting. 247 if (quoted) 248 { 249 if (c == '\'') 250 quoted = false; 251 // In both cases we fall through to inserting the 252 // character here. 253 } 254 // See if we have just a plain quote to insert. 255 else if (c == '\'' && index + 1 < max 256 && pat.charAt(index + 1) == '\'') 257 { 258 buffer.append(c); 259 ++index; 260 } 261 // See if quoting should turn on. 262 else if (c == '\'') 263 quoted = true; 264 else if (c == '{') 265 ++brace_depth; 266 else if (c == '}') 267 { 268 if (--brace_depth == 0) 269 break; 270 } 271 // Check for TERM after braces, because TERM might be `}'. 272 else if (c == term) 273 break; 274 // All characters, including opening and closing quotes, are 275 // inserted here. 276 buffer.append(c); 277 } 278 return index; 279 } 280 281 // This is used to parse a format element and whatever non-format 282 // text might trail it. 283 private static int scanFormat(String pat, int index, CPStringBuilder buffer, 284 List<MessageFormatElement> elts, Locale locale) 285 { 286 MessageFormatElement mfe = new MessageFormatElement (); 287 elts.add(mfe); 288 289 int max = pat.length(); 290 291 // Skip the opening `{'. 292 ++index; 293 294 // Fetch the argument number. 295 index = scanFormatElement (pat, index, buffer, ','); 296 try 297 { 298 mfe.argNumber = Integer.parseInt(buffer.toString()); 299 } 300 catch (NumberFormatException nfx) 301 { 302 IllegalArgumentException iae = new IllegalArgumentException(pat); 303 iae.initCause(nfx); 304 throw iae; 305 } 306 307 // Extract the element format. 308 if (index < max && pat.charAt(index) == ',') 309 { 310 index = scanFormatElement (pat, index + 1, buffer, ','); 311 mfe.type = buffer.toString(); 312 313 // Extract the style. 314 if (index < max && pat.charAt(index) == ',') 315 { 316 index = scanFormatElement (pat, index + 1, buffer, '}'); 317 mfe.style = buffer.toString (); 318 } 319 } 320 321 // Advance past the last terminator. 322 if (index >= max || pat.charAt(index) != '}') 323 throw new IllegalArgumentException("Missing '}' at end of message format"); 324 ++index; 325 326 // Now fetch trailing string. 327 index = scanString (pat, index, buffer); 328 mfe.trailer = buffer.toString (); 329 330 mfe.setLocale(locale); 331 332 return index; 333 } 334 335 /** 336 * Applies the specified pattern to this MessageFormat. 337 * 338 * @param newPattern The Pattern 339 */ 340 public void applyPattern (String newPattern) 341 { 342 pattern = newPattern; 343 344 CPStringBuilder tempBuffer = new CPStringBuilder (); 345 346 int index = scanString (newPattern, 0, tempBuffer); 347 leader = tempBuffer.toString(); 348 349 List<MessageFormatElement> elts = new ArrayList<MessageFormatElement>(); 350 while (index < newPattern.length()) 351 index = scanFormat (newPattern, index, tempBuffer, elts, locale); 352 353 elements = elts.toArray(new MessageFormatElement[elts.size()]); 354 } 355 356 /** 357 * Overrides Format.clone() 358 */ 359 public Object clone () 360 { 361 MessageFormat c = (MessageFormat) super.clone (); 362 c.elements = (MessageFormatElement[]) elements.clone (); 363 return c; 364 } 365 366 /** 367 * Overrides Format.equals(Object obj) 368 */ 369 public boolean equals (Object obj) 370 { 371 if (! (obj instanceof MessageFormat)) 372 return false; 373 MessageFormat mf = (MessageFormat) obj; 374 return (pattern.equals(mf.pattern) 375 && locale.equals(mf.locale)); 376 } 377 378 /** 379 * A convinience method to format patterns. 380 * 381 * @param arguments The array containing the objects to be formatted. 382 */ 383 public AttributedCharacterIterator formatToCharacterIterator (Object arguments) 384 { 385 Object[] arguments_array = (Object[])arguments; 386 FormatCharacterIterator iterator = new FormatCharacterIterator(); 387 388 formatInternal(arguments_array, new StringBuffer(), null, iterator); 389 390 return iterator; 391 } 392 393 /** 394 * A convinience method to format patterns. 395 * 396 * @param pattern The pattern used when formatting. 397 * @param arguments The array containing the objects to be formatted. 398 */ 399 public static String format (String pattern, Object... arguments) 400 { 401 MessageFormat mf = new MessageFormat (pattern); 402 StringBuffer sb = new StringBuffer (); 403 FieldPosition fp = new FieldPosition (NumberFormat.INTEGER_FIELD); 404 return mf.formatInternal(arguments, sb, fp, null).toString(); 405 } 406 407 /** 408 * Returns the pattern with the formatted objects. 409 * 410 * @param arguments The array containing the objects to be formatted. 411 * @param appendBuf The StringBuffer where the text is appened. 412 * @param fp A FieldPosition object (it is ignored). 413 */ 414 public final StringBuffer format (Object arguments[], StringBuffer appendBuf, 415 FieldPosition fp) 416 { 417 return formatInternal(arguments, appendBuf, fp, null); 418 } 419 420 private StringBuffer formatInternal (Object arguments[], 421 StringBuffer appendBuf, 422 FieldPosition fp, 423 FormatCharacterIterator output_iterator) 424 { 425 appendBuf.append(leader); 426 if (output_iterator != null) 427 output_iterator.append(leader); 428 429 for (int i = 0; i < elements.length; ++i) 430 { 431 Object thisArg = null; 432 boolean unavailable = false; 433 if (arguments == null || elements[i].argNumber >= arguments.length) 434 unavailable = true; 435 else 436 thisArg = arguments[elements[i].argNumber]; 437 438 AttributedCharacterIterator iterator = null; 439 440 Format formatter = null; 441 442 if (fp != null && i == fp.getField() && fp.getFieldAttribute() == Field.ARGUMENT) 443 fp.setBeginIndex(appendBuf.length()); 444 445 if (unavailable) 446 appendBuf.append("{" + elements[i].argNumber + "}"); 447 else 448 { 449 if (elements[i].setFormat != null) 450 formatter = elements[i].setFormat; 451 else if (elements[i].format != null) 452 { 453 if (elements[i].formatClass != null 454 && ! elements[i].formatClass.isInstance(thisArg)) 455 throw new IllegalArgumentException("Wrong format class"); 456 457 formatter = elements[i].format; 458 } 459 else if (thisArg instanceof Number) 460 formatter = NumberFormat.getInstance(locale); 461 else if (thisArg instanceof Date) 462 formatter = DateFormat.getTimeInstance(DateFormat.DEFAULT, locale); 463 else 464 appendBuf.append(thisArg); 465 } 466 467 if (fp != null && fp.getField() == i && fp.getFieldAttribute() == Field.ARGUMENT) 468 fp.setEndIndex(appendBuf.length()); 469 470 if (formatter != null) 471 { 472 // Special-case ChoiceFormat. 473 if (formatter instanceof ChoiceFormat) 474 { 475 StringBuffer buf = new StringBuffer (); 476 formatter.format(thisArg, buf, fp); 477 MessageFormat mf = new MessageFormat (); 478 mf.setLocale(locale); 479 mf.applyPattern(buf.toString()); 480 mf.format(arguments, appendBuf, fp); 481 } 482 else 483 { 484 if (output_iterator != null) 485 iterator = formatter.formatToCharacterIterator(thisArg); 486 else 487 formatter.format(thisArg, appendBuf, fp); 488 } 489 490 elements[i].format = formatter; 491 } 492 493 if (output_iterator != null) 494 { 495 HashMap<MessageFormat.Field, Integer> hash_argument = 496 new HashMap<MessageFormat.Field, Integer>(); 497 int position = output_iterator.getEndIndex(); 498 499 hash_argument.put (MessageFormat.Field.ARGUMENT, 500 Integer.valueOf(elements[i].argNumber)); 501 502 503 if (iterator != null) 504 { 505 output_iterator.append(iterator); 506 output_iterator.addAttributes(hash_argument, position, 507 output_iterator.getEndIndex()); 508 } 509 else 510 output_iterator.append(thisArg.toString(), hash_argument); 511 512 output_iterator.append(elements[i].trailer); 513 } 514 515 appendBuf.append(elements[i].trailer); 516 } 517 518 return appendBuf; 519 } 520 521 /** 522 * Returns the pattern with the formatted objects. The first argument 523 * must be a array of Objects. 524 * This is equivalent to format((Object[]) objectArray, appendBuf, fpos) 525 * 526 * @param objectArray The object array to be formatted. 527 * @param appendBuf The StringBuffer where the text is appened. 528 * @param fpos A FieldPosition object (it is ignored). 529 */ 530 public final StringBuffer format (Object objectArray, StringBuffer appendBuf, 531 FieldPosition fpos) 532 { 533 return format ((Object[])objectArray, appendBuf, fpos); 534 } 535 536 /** 537 * Returns an array with the Formats for 538 * the arguments. 539 */ 540 public Format[] getFormats () 541 { 542 Format[] f = new Format[elements.length]; 543 for (int i = elements.length - 1; i >= 0; --i) 544 f[i] = elements[i].setFormat; 545 return f; 546 } 547 548 /** 549 * Returns the locale. 550 */ 551 public Locale getLocale () 552 { 553 return locale; 554 } 555 556 /** 557 * Overrides Format.hashCode() 558 */ 559 public int hashCode () 560 { 561 // FIXME: not a very good hash. 562 return pattern.hashCode() + locale.hashCode(); 563 } 564 565 private MessageFormat () 566 { 567 } 568 569 /** 570 * Creates a new MessageFormat object with 571 * the specified pattern 572 * 573 * @param pattern The Pattern 574 */ 575 public MessageFormat(String pattern) 576 { 577 this(pattern, Locale.getDefault()); 578 } 579 580 /** 581 * Creates a new MessageFormat object with 582 * the specified pattern 583 * 584 * @param pattern The Pattern 585 * @param locale The Locale to use 586 * 587 * @since 1.4 588 */ 589 public MessageFormat(String pattern, Locale locale) 590 { 591 this.locale = locale; 592 applyPattern (pattern); 593 } 594 595 /** 596 * Parse a string <code>sourceStr</code> against the pattern specified 597 * to the MessageFormat constructor. 598 * 599 * @param sourceStr the string to be parsed. 600 * @param pos the current parse position (and eventually the error position). 601 * @return the array of parsed objects sorted according to their argument number 602 * in the pattern. 603 */ 604 public Object[] parse (String sourceStr, ParsePosition pos) 605 { 606 // Check initial text. 607 int index = pos.getIndex(); 608 if (! sourceStr.startsWith(leader, index)) 609 { 610 pos.setErrorIndex(index); 611 return null; 612 } 613 index += leader.length(); 614 615 ArrayList<Object> results = new ArrayList<Object>(elements.length); 616 // Now check each format. 617 for (int i = 0; i < elements.length; ++i) 618 { 619 Format formatter = null; 620 if (elements[i].setFormat != null) 621 formatter = elements[i].setFormat; 622 else if (elements[i].format != null) 623 formatter = elements[i].format; 624 625 Object value = null; 626 if (formatter instanceof ChoiceFormat) 627 { 628 // We must special-case a ChoiceFormat because it might 629 // have recursive formatting. 630 ChoiceFormat cf = (ChoiceFormat) formatter; 631 String[] formats = (String[]) cf.getFormats(); 632 double[] limits = cf.getLimits(); 633 MessageFormat subfmt = new MessageFormat (); 634 subfmt.setLocale(locale); 635 ParsePosition subpos = new ParsePosition (index); 636 637 int j; 638 for (j = 0; value == null && j < limits.length; ++j) 639 { 640 subfmt.applyPattern(formats[j]); 641 subpos.setIndex(index); 642 value = subfmt.parse(sourceStr, subpos); 643 } 644 if (value != null) 645 { 646 index = subpos.getIndex(); 647 value = new Double (limits[j]); 648 } 649 } 650 else if (formatter != null) 651 { 652 pos.setIndex(index); 653 value = formatter.parseObject(sourceStr, pos); 654 if (value != null) 655 index = pos.getIndex(); 656 } 657 else 658 { 659 // We have a String format. This can lose in a number 660 // of ways, but we give it a shot. 661 int next_index; 662 if (elements[i].trailer.length() > 0) 663 next_index = sourceStr.indexOf(elements[i].trailer, index); 664 else 665 next_index = sourceStr.length(); 666 if (next_index == -1) 667 { 668 pos.setErrorIndex(index); 669 return null; 670 } 671 value = sourceStr.substring(index, next_index); 672 index = next_index; 673 } 674 675 if (value == null 676 || ! sourceStr.startsWith(elements[i].trailer, index)) 677 { 678 pos.setErrorIndex(index); 679 return null; 680 } 681 682 if (elements[i].argNumber >= results.size()) 683 { 684 // Emulate padding behaviour of Vector.setSize() with ArrayList 685 results.ensureCapacity(elements[i].argNumber + 1); 686 for (int a = results.size(); a <= elements[i].argNumber; ++a) 687 results.add(a, null); 688 } 689 results.set(elements[i].argNumber, value); 690 691 index += elements[i].trailer.length(); 692 } 693 694 return results.toArray(new Object[results.size()]); 695 } 696 697 public Object[] parse (String sourceStr) throws ParseException 698 { 699 ParsePosition pp = new ParsePosition (0); 700 Object[] r = parse (sourceStr, pp); 701 if (r == null) 702 throw new ParseException ("couldn't parse string", pp.getErrorIndex()); 703 return r; 704 } 705 706 public Object parseObject (String sourceStr, ParsePosition pos) 707 { 708 return parse (sourceStr, pos); 709 } 710 711 /** 712 * Sets the format for the argument at an specified 713 * index. 714 * 715 * @param variableNum The index. 716 * @param newFormat The Format object. 717 */ 718 public void setFormat (int variableNum, Format newFormat) 719 { 720 elements[variableNum].setFormat = newFormat; 721 } 722 723 /** 724 * Sets the formats for the arguments. 725 * 726 * @param newFormats An array of Format objects. 727 */ 728 public void setFormats (Format[] newFormats) 729 { 730 if (newFormats.length < elements.length) 731 throw new IllegalArgumentException("Not enough format objects"); 732 733 int len = Math.min(newFormats.length, elements.length); 734 for (int i = 0; i < len; ++i) 735 elements[i].setFormat = newFormats[i]; 736 } 737 738 /** 739 * Sets the locale. 740 * 741 * @param loc A Locale 742 */ 743 public void setLocale (Locale loc) 744 { 745 locale = loc; 746 if (elements != null) 747 { 748 for (int i = 0; i < elements.length; ++i) 749 elements[i].setLocale(loc); 750 } 751 } 752 753 /** 754 * Returns the pattern. 755 */ 756 public String toPattern () 757 { 758 return pattern; 759 } 760 761 /** 762 * Return the formatters used sorted by argument index. It uses the 763 * internal table to fill in this array: if a format has been 764 * set using <code>setFormat</code> or <code>setFormatByArgumentIndex</code> 765 * then it returns it at the right index. If not it uses the detected 766 * formatters during a <code>format</code> call. If nothing is known 767 * about that argument index it just puts null at that position. 768 * To get useful informations you may have to call <code>format</code> 769 * at least once. 770 * 771 * @return an array of formatters sorted by argument index. 772 */ 773 public Format[] getFormatsByArgumentIndex() 774 { 775 int argNumMax = 0; 776 // First, find the greatest argument number. 777 for (int i=0;i<elements.length;i++) 778 if (elements[i].argNumber > argNumMax) 779 argNumMax = elements[i].argNumber; 780 781 Format[] formats = new Format[argNumMax]; 782 for (int i=0;i<elements.length;i++) 783 { 784 if (elements[i].setFormat != null) 785 formats[elements[i].argNumber] = elements[i].setFormat; 786 else if (elements[i].format != null) 787 formats[elements[i].argNumber] = elements[i].format; 788 } 789 return formats; 790 } 791 792 /** 793 * Set the format to used using the argument index number. 794 * 795 * @param argumentIndex the argument index. 796 * @param newFormat the format to use for this argument. 797 */ 798 public void setFormatByArgumentIndex(int argumentIndex, 799 Format newFormat) 800 { 801 for (int i=0;i<elements.length;i++) 802 { 803 if (elements[i].argNumber == argumentIndex) 804 elements[i].setFormat = newFormat; 805 } 806 } 807 808 /** 809 * Set the format for argument using a specified array of formatters 810 * which is sorted according to the argument index. If the number of 811 * elements in the array is fewer than the number of arguments only 812 * the arguments specified by the array are touched. 813 * 814 * @param newFormats array containing the new formats to set. 815 * 816 * @throws NullPointerException if newFormats is null 817 */ 818 public void setFormatsByArgumentIndex(Format[] newFormats) 819 { 820 for (int i=0;i<newFormats.length;i++) 821 { 822 // Nothing better than that can exist here. 823 setFormatByArgumentIndex(i, newFormats[i]); 824 } 825 } 826 827 // The pattern string. 828 private String pattern; 829 // The locale. 830 private Locale locale; 831 // Variables. 832 private MessageFormatElement[] elements; 833 // Leader text. 834 private String leader; 835}