1 /* MessageFormat.java - Localized message formatting. 2 Copyright (C) 1999, 2001, 2002 Free Software Foundation, Inc. 3 4 This file is part of GNU Classpath. 5 6 GNU Classpath is free software; you can redistribute it and/or modify 7 it under the terms of the GNU General Public License as published by 8 the Free Software Foundation; either version 2, or (at your option) 9 any later version. 10 11 GNU Classpath is distributed in the hope that it will be useful, but 12 WITHOUT ANY WARRANTY; without even the implied warranty of 13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 General Public License for more details. 15 16 You should have received a copy of the GNU General Public License 17 along with GNU Classpath; see the file COPYING. If not, write to the 18 Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 19 02111-1307 USA. 20 21 Linking this library statically or dynamically with other modules is 22 making a combined work based on this library. Thus, the terms and 23 conditions of the GNU General Public License cover the whole 24 combination. 25 26 As a special exception, the copyright holders of this library give you 27 permission to link this library with independent modules to produce an 28 executable, regardless of the license terms of these independent 29 modules, and to copy and distribute the resulting executable under 30 terms of your choice, provided that you also meet, for each linked 31 independent module, the terms and conditions of the license of that 32 module. An independent module is a module which is not derived from 33 or based on this library. If you modify this library, you may extend 34 this exception to your version of the library, but you are not 35 obligated to do so. If you do not wish to do so, delete this 36 exception statement from your version. */ 37 38 39 package java.text; 40 41 import java.util.Date; 42 import java.util.Locale; 43 import java.util.Vector; 44 45 /** 46 * @author Tom Tromey <tromey@cygnus.com> 47 * @author Jorge Aliss <jaliss@hotmail.com> 48 * @date March 3, 1999 49 */ 50 /* Written using "Java Class Libraries", 2nd edition, plus online 51 * API docs for JDK 1.2 from http://www.javasoft.com. 52 * Status: Believed complete and correct to 1.2, except serialization. 53 * and parsing. 54 */ 55 final class MessageFormatElement 56 { 57 // Argument number. 58 int argNumber; 59 // Formatter to be used. This is the format set by setFormat. 60 Format setFormat; 61 // Formatter to be used based on the type. 62 Format format; 63 64 // Argument will be checked to make sure it is an instance of this 65 // class. 66 Class formatClass; 67 68 // Formatter type. 69 String type; 70 // Formatter style. 71 String style; 72 73 // Text to follow this element. 74 String trailer; 75 76 // Recompute the locale-based formatter. setLocale(Locale loc)77 void setLocale (Locale loc) 78 { 79 if (type == null) 80 ; 81 else if (type.equals("number")) 82 { 83 formatClass = java.lang.Number.class; 84 85 if (style == null) 86 format = NumberFormat.getInstance(loc); 87 else if (style.equals("currency")) 88 format = NumberFormat.getCurrencyInstance(loc); 89 else if (style.equals("percent")) 90 format = NumberFormat.getPercentInstance(loc); 91 else if (style.equals("integer")) 92 { 93 NumberFormat nf = NumberFormat.getNumberInstance(loc); 94 nf.setMaximumFractionDigits(0); 95 nf.setGroupingUsed(false); 96 format = nf; 97 } 98 else 99 { 100 format = NumberFormat.getNumberInstance(loc); 101 DecimalFormat df = (DecimalFormat) format; 102 df.applyPattern(style); 103 } 104 } 105 else if (type.equals("time") || type.equals("date")) 106 { 107 formatClass = java.util.Date.class; 108 109 int val = DateFormat.DEFAULT; 110 if (style == null) 111 ; 112 else if (style.equals("short")) 113 val = DateFormat.SHORT; 114 else if (style.equals("medium")) 115 val = DateFormat.MEDIUM; 116 else if (style.equals("long")) 117 val = DateFormat.LONG; 118 else if (style.equals("full")) 119 val = DateFormat.FULL; 120 121 if (type.equals("time")) 122 format = DateFormat.getTimeInstance(val, loc); 123 else 124 format = DateFormat.getDateInstance(val, loc); 125 126 if (style != null && val == DateFormat.DEFAULT) 127 { 128 SimpleDateFormat sdf = (SimpleDateFormat) format; 129 sdf.applyPattern(style); 130 } 131 } 132 else if (type.equals("choice")) 133 { 134 formatClass = java.lang.Number.class; 135 136 if (style == null) 137 throw new 138 IllegalArgumentException ("style required for choice format"); 139 format = new ChoiceFormat (style); 140 } 141 } 142 } 143 144 public class MessageFormat extends Format 145 { 146 private static final long serialVersionUID = 6479157306784022952L; 147 148 // Helper that returns the text up to the next format opener. The 149 // text is put into BUFFER. Returns index of character after end of 150 // string. Throws IllegalArgumentException on error. scanString(String pat, int index, StringBuffer buffer)151 private static final int scanString (String pat, int index, 152 StringBuffer buffer) 153 { 154 int max = pat.length(); 155 buffer.setLength(0); 156 for (; index < max; ++index) 157 { 158 char c = pat.charAt(index); 159 if (c == '\'' && index + 2 < max && pat.charAt(index + 2) == '\'') 160 { 161 buffer.append(pat.charAt(index + 1)); 162 index += 2; 163 } 164 else if (c == '\'' && index + 1 < max 165 && pat.charAt(index + 1) == '\'') 166 { 167 buffer.append(c); 168 ++index; 169 } 170 else if (c == '{') 171 break; 172 else if (c == '}') 173 throw new IllegalArgumentException("Found '}' without '{'"); 174 else 175 buffer.append(c); 176 } 177 return index; 178 } 179 180 // This helper retrieves a single part of a format element. Returns 181 // the index of the terminating character. scanFormatElement(String pat, int index, StringBuffer buffer, char term)182 private static final int scanFormatElement (String pat, int index, 183 StringBuffer buffer, 184 char term) 185 { 186 int max = pat.length(); 187 buffer.setLength(0); 188 int brace_depth = 1; 189 190 for (; index < max; ++index) 191 { 192 char c = pat.charAt(index); 193 if (c == '\'' && index + 2 < max && pat.charAt(index + 2) == '\'') 194 { 195 buffer.append(c); 196 buffer.append(pat.charAt(index + 1)); 197 buffer.append(c); 198 index += 2; 199 } 200 else if (c == '\'' && index + 1 < max 201 && pat.charAt(index + 1) == '\'') 202 { 203 buffer.append(c); 204 ++index; 205 } 206 else if (c == '{') 207 { 208 buffer.append(c); 209 ++brace_depth; 210 } 211 else if (c == '}') 212 { 213 if (--brace_depth == 0) 214 break; 215 buffer.append(c); 216 } 217 // Check for TERM after braces, because TERM might be `}'. 218 else if (c == term) 219 break; 220 else 221 buffer.append(c); 222 } 223 return index; 224 } 225 226 // This is used to parse a format element and whatever non-format 227 // text might trail it. scanFormat(String pat, int index, StringBuffer buffer, Vector elts, Locale locale)228 private static final int scanFormat (String pat, int index, 229 StringBuffer buffer, Vector elts, 230 Locale locale) 231 { 232 MessageFormatElement mfe = new MessageFormatElement (); 233 elts.addElement(mfe); 234 235 int max = pat.length(); 236 237 // Skip the opening `{'. 238 ++index; 239 240 // Fetch the argument number. 241 index = scanFormatElement (pat, index, buffer, ','); 242 try 243 { 244 mfe.argNumber = Integer.parseInt(buffer.toString()); 245 } 246 catch (NumberFormatException nfx) 247 { 248 throw new IllegalArgumentException("Failed to parse integer string"); 249 } 250 251 // Extract the element format. 252 if (index < max && pat.charAt(index) == ',') 253 { 254 index = scanFormatElement (pat, index + 1, buffer, ','); 255 mfe.type = buffer.toString(); 256 257 // Extract the style. 258 if (index < max && pat.charAt(index) == ',') 259 { 260 index = scanFormatElement (pat, index + 1, buffer, '}'); 261 mfe.style = buffer.toString (); 262 } 263 } 264 265 // Advance past the last terminator. 266 if (index >= max || pat.charAt(index) != '}') 267 throw new IllegalArgumentException("Missing '}' at end of message format"); 268 ++index; 269 270 // Now fetch trailing string. 271 index = scanString (pat, index, buffer); 272 mfe.trailer = buffer.toString (); 273 274 mfe.setLocale(locale); 275 276 return index; 277 } 278 279 /** 280 * Applies the specified pattern to this MessageFormat. 281 * 282 * @param aPattern The Pattern 283 */ applyPattern(String newPattern)284 public void applyPattern (String newPattern) 285 { 286 pattern = newPattern; 287 288 StringBuffer tempBuffer = new StringBuffer (); 289 290 int index = scanString (newPattern, 0, tempBuffer); 291 leader = tempBuffer.toString(); 292 293 Vector elts = new Vector (); 294 while (index < newPattern.length()) 295 index = scanFormat (newPattern, index, tempBuffer, elts, locale); 296 297 elements = new MessageFormatElement[elts.size()]; 298 elts.copyInto(elements); 299 } 300 301 /** 302 * Overrides Format.clone() 303 */ clone()304 public Object clone () 305 { 306 MessageFormat c = (MessageFormat) super.clone (); 307 c.elements = (MessageFormatElement[]) elements.clone (); 308 return c; 309 } 310 311 /** 312 * Overrides Format.equals(Object obj) 313 */ equals(Object obj)314 public boolean equals (Object obj) 315 { 316 if (! (obj instanceof MessageFormat)) 317 return false; 318 MessageFormat mf = (MessageFormat) obj; 319 return (pattern.equals(mf.pattern) 320 && locale.equals(mf.locale)); 321 } 322 323 /** 324 * A convinience method to format patterns. 325 * 326 * @param aPattern The pattern used when formatting. 327 * @param arguments The array containing the objects to be formatted. 328 */ format(String pattern, Object arguments[])329 public static String format (String pattern, Object arguments[]) 330 { 331 MessageFormat mf = new MessageFormat (pattern); 332 StringBuffer sb = new StringBuffer (); 333 FieldPosition fp = new FieldPosition (NumberFormat.INTEGER_FIELD); 334 return mf.format(arguments, sb, fp).toString(); 335 } 336 337 /** 338 * Returns the pattern with the formatted objects. 339 * 340 * @param source The array containing the objects to be formatted. 341 * @param result The StringBuffer where the text is appened. 342 * @param fp A FieldPosition object (it is ignored). 343 */ format(Object arguments[], StringBuffer appendBuf, FieldPosition ignore)344 public final StringBuffer format (Object arguments[], StringBuffer appendBuf, 345 FieldPosition ignore) 346 { 347 appendBuf.append(leader); 348 349 for (int i = 0; i < elements.length; ++i) 350 { 351 if (elements[i].argNumber >= arguments.length) 352 throw new IllegalArgumentException("Not enough arguments given"); 353 354 Object thisArg = arguments[elements[i].argNumber]; 355 356 Format formatter = null; 357 if (elements[i].setFormat != null) 358 formatter = elements[i].setFormat; 359 else if (elements[i].format != null) 360 { 361 if (elements[i].formatClass != null 362 && ! elements[i].formatClass.isInstance(thisArg)) 363 throw new IllegalArgumentException("Wrong format class"); 364 365 formatter = elements[i].format; 366 } 367 else if (thisArg instanceof Number) 368 formatter = NumberFormat.getInstance(locale); 369 else if (thisArg instanceof Date) 370 formatter = DateFormat.getTimeInstance(DateFormat.DEFAULT, locale); 371 else 372 appendBuf.append(thisArg); 373 374 if (formatter != null) 375 { 376 // Special-case ChoiceFormat. 377 if (formatter instanceof ChoiceFormat) 378 { 379 StringBuffer buf = new StringBuffer (); 380 formatter.format(thisArg, buf, ignore); 381 MessageFormat mf = new MessageFormat (); 382 mf.setLocale(locale); 383 mf.applyPattern(buf.toString()); 384 mf.format(arguments, appendBuf, ignore); 385 } 386 else 387 formatter.format(thisArg, appendBuf, ignore); 388 } 389 390 appendBuf.append(elements[i].trailer); 391 } 392 393 return appendBuf; 394 } 395 396 /** 397 * Returns the pattern with the formatted objects. 398 * 399 * @param source The object to be formatted. 400 * @param result The StringBuffer where the text is appened. 401 * @param fp A FieldPosition object (it is ignored). 402 */ format(Object singleArg, StringBuffer appendBuf, FieldPosition ignore)403 public final StringBuffer format (Object singleArg, StringBuffer appendBuf, 404 FieldPosition ignore) 405 { 406 Object[] args; 407 408 if (singleArg instanceof Object[]) 409 { 410 // This isn't specified in any manual, but it follows the 411 // JDK implementation. 412 args = (Object[]) singleArg; 413 } 414 else 415 { 416 args = new Object[1]; 417 args[0] = singleArg; 418 } 419 return format (args, appendBuf, ignore); 420 } 421 422 /** 423 * Returns an array with the Formats for 424 * the arguments. 425 */ getFormats()426 public Format[] getFormats () 427 { 428 Format[] f = new Format[elements.length]; 429 for (int i = elements.length - 1; i >= 0; --i) 430 f[i] = elements[i].setFormat; 431 return f; 432 } 433 434 /** 435 * Returns the locale. 436 */ getLocale()437 public Locale getLocale () 438 { 439 return locale; 440 } 441 442 /** 443 * Overrides Format.hashCode() 444 */ hashCode()445 public int hashCode () 446 { 447 // FIXME: not a very good hash. 448 return pattern.hashCode() + locale.hashCode(); 449 } 450 MessageFormat()451 private MessageFormat () 452 { 453 } 454 455 /** 456 * Creates a new MessageFormat object with 457 * the specified pattern 458 * 459 * @param pattern The Pattern 460 */ MessageFormat(String pattern)461 public MessageFormat(String pattern) 462 { 463 this(pattern, Locale.getDefault()); 464 } 465 466 /** 467 * Creates a new MessageFormat object with 468 * the specified pattern 469 * 470 * @param pattern The Pattern 471 * @param locale The Locale to use 472 * 473 * @since 1.4 474 */ MessageFormat(String pattern, Locale locale)475 public MessageFormat(String pattern, Locale locale) 476 { 477 this.locale = locale; 478 applyPattern (pattern); 479 } 480 parse(String sourceStr, ParsePosition pos)481 public Object[] parse (String sourceStr, ParsePosition pos) 482 { 483 // Check initial text. 484 int index = pos.getIndex(); 485 if (! sourceStr.startsWith(leader, index)) 486 { 487 pos.setErrorIndex(index); 488 return null; 489 } 490 index += leader.length(); 491 492 Vector results = new Vector (elements.length, 1); 493 // Now check each format. 494 for (int i = 0; i < elements.length; ++i) 495 { 496 Format formatter = null; 497 if (elements[i].setFormat != null) 498 formatter = elements[i].setFormat; 499 else if (elements[i].format != null) 500 formatter = elements[i].format; 501 502 Object value = null; 503 if (formatter instanceof ChoiceFormat) 504 { 505 // We must special-case a ChoiceFormat because it might 506 // have recursive formatting. 507 ChoiceFormat cf = (ChoiceFormat) formatter; 508 String[] formats = (String[]) cf.getFormats(); 509 double[] limits = (double[]) cf.getLimits(); 510 MessageFormat subfmt = new MessageFormat (); 511 subfmt.setLocale(locale); 512 ParsePosition subpos = new ParsePosition (index); 513 514 int j; 515 for (j = 0; value == null && j < limits.length; ++j) 516 { 517 subfmt.applyPattern(formats[j]); 518 subpos.setIndex(index); 519 value = subfmt.parse(sourceStr, subpos); 520 } 521 if (value != null) 522 { 523 index = subpos.getIndex(); 524 value = new Double (limits[j]); 525 } 526 } 527 else if (formatter != null) 528 { 529 pos.setIndex(index); 530 value = formatter.parseObject(sourceStr, pos); 531 if (value != null) 532 index = pos.getIndex(); 533 } 534 else 535 { 536 // We have a String format. This can lose in a number 537 // of ways, but we give it a shot. 538 int next_index = sourceStr.indexOf(elements[i].trailer, index); 539 if (next_index == -1) 540 { 541 pos.setErrorIndex(index); 542 return null; 543 } 544 value = sourceStr.substring(index, next_index); 545 index = next_index; 546 } 547 548 if (value == null 549 || ! sourceStr.startsWith(elements[i].trailer, index)) 550 { 551 pos.setErrorIndex(index); 552 return null; 553 } 554 555 if (elements[i].argNumber >= results.size()) 556 results.setSize(elements[i].argNumber + 1); 557 results.setElementAt(value, elements[i].argNumber); 558 559 index += elements[i].trailer.length(); 560 } 561 562 Object[] r = new Object[results.size()]; 563 results.copyInto(r); 564 return r; 565 } 566 parse(String sourceStr)567 public Object[] parse (String sourceStr) throws ParseException 568 { 569 ParsePosition pp = new ParsePosition (0); 570 Object[] r = parse (sourceStr, pp); 571 if (r == null) 572 throw new ParseException ("couldn't parse string", pp.getErrorIndex()); 573 return r; 574 } 575 parseObject(String sourceStr, ParsePosition pos)576 public Object parseObject (String sourceStr, ParsePosition pos) 577 { 578 return parse (sourceStr, pos); 579 } 580 581 /** 582 * Sets the format for the argument at an specified 583 * index. 584 * 585 * @param index The index. 586 * @format The Format object. 587 */ setFormat(int variableNum, Format newFormat)588 public void setFormat (int variableNum, Format newFormat) 589 { 590 elements[variableNum].setFormat = newFormat; 591 } 592 593 /** 594 * Sets the formats for the arguments. 595 * 596 * @param formats An array of Format objects. 597 */ setFormats(Format[] newFormats)598 public void setFormats (Format[] newFormats) 599 { 600 if (newFormats.length < elements.length) 601 throw new IllegalArgumentException("Not enough format objects"); 602 603 int len = Math.min(newFormats.length, elements.length); 604 for (int i = 0; i < len; ++i) 605 elements[i].setFormat = newFormats[i]; 606 } 607 608 /** 609 * Sets the locale. 610 * 611 * @param locale A Locale 612 */ setLocale(Locale loc)613 public void setLocale (Locale loc) 614 { 615 locale = loc; 616 if (elements != null) 617 { 618 for (int i = 0; i < elements.length; ++i) 619 elements[i].setLocale(loc); 620 } 621 } 622 623 /** 624 * Returns the pattern. 625 */ toPattern()626 public String toPattern () 627 { 628 return pattern; 629 } 630 631 // The pattern string. 632 private String pattern; 633 // The locale. 634 private Locale locale; 635 // Variables. 636 private MessageFormatElement[] elements; 637 // Leader text. 638 private String leader; 639 } 640