1 /* 2 * Copyright (c) 1996, 2010, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 /* 27 * (C) Copyright Taligent, Inc. 1996, 1997 - All Rights Reserved 28 * (C) Copyright IBM Corp. 1996 - 1998 - All Rights Reserved 29 * 30 * The original version of this source code and documentation is copyrighted 31 * and owned by Taligent, Inc., a wholly-owned subsidiary of IBM. These 32 * materials are provided under terms of a License Agreement between Taligent 33 * and Sun. This technology is protected by multiple US and International 34 * patents. This notice and attribution to Taligent may not be removed. 35 * Taligent is a registered trademark of Taligent, Inc. 36 * 37 */ 38 39 package java.text; 40 41 import java.io.InvalidObjectException; 42 import java.io.IOException; 43 import java.io.ObjectInputStream; 44 import java.text.DecimalFormat; 45 import java.util.ArrayList; 46 import java.util.Arrays; 47 import java.util.Date; 48 import java.util.List; 49 import java.util.Locale; 50 51 52 /** 53 * <code>MessageFormat</code> provides a means to produce concatenated 54 * messages in a language-neutral way. Use this to construct messages 55 * displayed for end users. 56 * 57 * <p> 58 * <code>MessageFormat</code> takes a set of objects, formats them, then 59 * inserts the formatted strings into the pattern at the appropriate places. 60 * 61 * <p> 62 * <strong>Note:</strong> 63 * <code>MessageFormat</code> differs from the other <code>Format</code> 64 * classes in that you create a <code>MessageFormat</code> object with one 65 * of its constructors (not with a <code>getInstance</code> style factory 66 * method). The factory methods aren't necessary because <code>MessageFormat</code> 67 * itself doesn't implement locale specific behavior. Any locale specific 68 * behavior is defined by the pattern that you provide as well as the 69 * subformats used for inserted arguments. 70 * 71 * <h4><a name="patterns">Patterns and Their Interpretation</a></h4> 72 * 73 * <code>MessageFormat</code> uses patterns of the following form: 74 * <blockquote><pre> 75 * <i>MessageFormatPattern:</i> 76 * <i>String</i> 77 * <i>MessageFormatPattern</i> <i>FormatElement</i> <i>String</i> 78 * 79 * <i>FormatElement:</i> 80 * { <i>ArgumentIndex</i> } 81 * { <i>ArgumentIndex</i> , <i>FormatType</i> } 82 * { <i>ArgumentIndex</i> , <i>FormatType</i> , <i>FormatStyle</i> } 83 * 84 * <i>FormatType: one of </i> 85 * number date time choice 86 * 87 * <i>FormatStyle:</i> 88 * short 89 * medium 90 * long 91 * full 92 * integer 93 * currency 94 * percent 95 * <i>SubformatPattern</i> 96 * 97 * <i>String:</i> 98 * <i>StringPart<sub>opt</sub></i> 99 * <i>String</i> <i>StringPart</i> 100 * 101 * <i>StringPart:</i> 102 * '' 103 * ' <i>QuotedString</i> ' 104 * <i>UnquotedString</i> 105 * 106 * <i>SubformatPattern:</i> 107 * <i>SubformatPatternPart<sub>opt</sub></i> 108 * <i>SubformatPattern</i> <i>SubformatPatternPart</i> 109 * 110 * <i>SubFormatPatternPart:</i> 111 * ' <i>QuotedPattern</i> ' 112 * <i>UnquotedPattern</i> 113 * </pre></blockquote> 114 * 115 * <p> 116 * Within a <i>String</i>, <code>"''"</code> represents a single 117 * quote. A <i>QuotedString</i> can contain arbitrary characters 118 * except single quotes; the surrounding single quotes are removed. 119 * An <i>UnquotedString</i> can contain arbitrary characters 120 * except single quotes and left curly brackets. Thus, a string that 121 * should result in the formatted message "'{0}'" can be written as 122 * <code>"'''{'0}''"</code> or <code>"'''{0}'''"</code>. 123 * <p> 124 * Within a <i>SubformatPattern</i>, different rules apply. 125 * A <i>QuotedPattern</i> can contain arbitrary characters 126 * except single quotes; but the surrounding single quotes are 127 * <strong>not</strong> removed, so they may be interpreted by the 128 * subformat. For example, <code>"{1,number,$'#',##}"</code> will 129 * produce a number format with the pound-sign quoted, with a result 130 * such as: "$#31,45". 131 * An <i>UnquotedPattern</i> can contain arbitrary characters 132 * except single quotes, but curly braces within it must be balanced. 133 * For example, <code>"ab {0} de"</code> and <code>"ab '}' de"</code> 134 * are valid subformat patterns, but <code>"ab {0'}' de"</code> and 135 * <code>"ab } de"</code> are not. 136 * <p> 137 * <dl><dt><b>Warning:</b><dd>The rules for using quotes within message 138 * format patterns unfortunately have shown to be somewhat confusing. 139 * In particular, it isn't always obvious to localizers whether single 140 * quotes need to be doubled or not. Make sure to inform localizers about 141 * the rules, and tell them (for example, by using comments in resource 142 * bundle source files) which strings will be processed by MessageFormat. 143 * Note that localizers may need to use single quotes in translated 144 * strings where the original version doesn't have them. 145 * </dl> 146 * <p> 147 * The <i>ArgumentIndex</i> value is a non-negative integer written 148 * using the digits '0' through '9', and represents an index into the 149 * <code>arguments</code> array passed to the <code>format</code> methods 150 * or the result array returned by the <code>parse</code> methods. 151 * <p> 152 * The <i>FormatType</i> and <i>FormatStyle</i> values are used to create 153 * a <code>Format</code> instance for the format element. The following 154 * table shows how the values map to Format instances. Combinations not 155 * shown in the table are illegal. A <i>SubformatPattern</i> must 156 * be a valid pattern string for the Format subclass used. 157 * <p> 158 * <table border=1 summary="Shows how FormatType and FormatStyle values map to Format instances"> 159 * <tr> 160 * <th id="ft">Format Type 161 * <th id="fs">Format Style 162 * <th id="sc">Subformat Created 163 * <tr> 164 * <td headers="ft"><i>(none)</i> 165 * <td headers="fs"><i>(none)</i> 166 * <td headers="sc"><code>null</code> 167 * <tr> 168 * <td headers="ft" rowspan=5><code>number</code> 169 * <td headers="fs"><i>(none)</i> 170 * <td headers="sc"><code>NumberFormat.getInstance(getLocale())</code> 171 * <tr> 172 * <td headers="fs"><code>integer</code> 173 * <td headers="sc"><code>NumberFormat.getIntegerInstance(getLocale())</code> 174 * <tr> 175 * <td headers="fs"><code>currency</code> 176 * <td headers="sc"><code>NumberFormat.getCurrencyInstance(getLocale())</code> 177 * <tr> 178 * <td headers="fs"><code>percent</code> 179 * <td headers="sc"><code>NumberFormat.getPercentInstance(getLocale())</code> 180 * <tr> 181 * <td headers="fs"><i>SubformatPattern</i> 182 * <td headers="sc"><code>new DecimalFormat(subformatPattern, DecimalFormatSymbols.getInstance(getLocale()))</code> 183 * <tr> 184 * <td headers="ft" rowspan=6><code>date</code> 185 * <td headers="fs"><i>(none)</i> 186 * <td headers="sc"><code>DateFormat.getDateInstance(DateFormat.DEFAULT, getLocale())</code> 187 * <tr> 188 * <td headers="fs"><code>short</code> 189 * <td headers="sc"><code>DateFormat.getDateInstance(DateFormat.SHORT, getLocale())</code> 190 * <tr> 191 * <td headers="fs"><code>medium</code> 192 * <td headers="sc"><code>DateFormat.getDateInstance(DateFormat.DEFAULT, getLocale())</code> 193 * <tr> 194 * <td headers="fs"><code>long</code> 195 * <td headers="sc"><code>DateFormat.getDateInstance(DateFormat.LONG, getLocale())</code> 196 * <tr> 197 * <td headers="fs"><code>full</code> 198 * <td headers="sc"><code>DateFormat.getDateInstance(DateFormat.FULL, getLocale())</code> 199 * <tr> 200 * <td headers="fs"><i>SubformatPattern</i> 201 * <td headers="sc"><code>new SimpleDateFormat(subformatPattern, getLocale())</code> 202 * <tr> 203 * <td headers="ft" rowspan=6><code>time</code> 204 * <td headers="fs"><i>(none)</i> 205 * <td headers="sc"><code>DateFormat.getTimeInstance(DateFormat.DEFAULT, getLocale())</code> 206 * <tr> 207 * <td headers="fs"><code>short</code> 208 * <td headers="sc"><code>DateFormat.getTimeInstance(DateFormat.SHORT, getLocale())</code> 209 * <tr> 210 * <td headers="fs"><code>medium</code> 211 * <td headers="sc"><code>DateFormat.getTimeInstance(DateFormat.DEFAULT, getLocale())</code> 212 * <tr> 213 * <td headers="fs"><code>long</code> 214 * <td headers="sc"><code>DateFormat.getTimeInstance(DateFormat.LONG, getLocale())</code> 215 * <tr> 216 * <td headers="fs"><code>full</code> 217 * <td headers="sc"><code>DateFormat.getTimeInstance(DateFormat.FULL, getLocale())</code> 218 * <tr> 219 * <td headers="fs"><i>SubformatPattern</i> 220 * <td headers="sc"><code>new SimpleDateFormat(subformatPattern, getLocale())</code> 221 * <tr> 222 * <td headers="ft"><code>choice</code> 223 * <td headers="fs"><i>SubformatPattern</i> 224 * <td headers="sc"><code>new ChoiceFormat(subformatPattern)</code> 225 * </table> 226 * <p> 227 * 228 * <h4>Usage Information</h4> 229 * 230 * <p> 231 * Here are some examples of usage. 232 * In real internationalized programs, the message format pattern and other 233 * static strings will, of course, be obtained from resource bundles. 234 * Other parameters will be dynamically determined at runtime. 235 * <p> 236 * The first example uses the static method <code>MessageFormat.format</code>, 237 * which internally creates a <code>MessageFormat</code> for one-time use: 238 * <blockquote><pre> 239 * int planet = 7; 240 * String event = "a disturbance in the Force"; 241 * 242 * String result = MessageFormat.format( 243 * "At {1,time} on {1,date}, there was {2} on planet {0,number,integer}.", 244 * planet, new Date(), event); 245 * </pre></blockquote> 246 * The output is: 247 * <blockquote><pre> 248 * At 12:30 PM on Jul 3, 2053, there was a disturbance in the Force on planet 7. 249 * </pre></blockquote> 250 * 251 * <p> 252 * The following example creates a <code>MessageFormat</code> instance that 253 * can be used repeatedly: 254 * <blockquote><pre> 255 * int fileCount = 1273; 256 * String diskName = "MyDisk"; 257 * Object[] testArgs = {new Long(fileCount), diskName}; 258 * 259 * MessageFormat form = new MessageFormat( 260 * "The disk \"{1}\" contains {0} file(s)."); 261 * 262 * System.out.println(form.format(testArgs)); 263 * </pre></blockquote> 264 * The output with different values for <code>fileCount</code>: 265 * <blockquote><pre> 266 * The disk "MyDisk" contains 0 file(s). 267 * The disk "MyDisk" contains 1 file(s). 268 * The disk "MyDisk" contains 1,273 file(s). 269 * </pre></blockquote> 270 * 271 * <p> 272 * For more sophisticated patterns, you can use a <code>ChoiceFormat</code> 273 * to produce correct forms for singular and plural: 274 * <blockquote><pre> 275 * MessageFormat form = new MessageFormat("The disk \"{1}\" contains {0}."); 276 * double[] filelimits = {0,1,2}; 277 * String[] filepart = {"no files","one file","{0,number} files"}; 278 * ChoiceFormat fileform = new ChoiceFormat(filelimits, filepart); 279 * form.setFormatByArgumentIndex(0, fileform); 280 * 281 * int fileCount = 1273; 282 * String diskName = "MyDisk"; 283 * Object[] testArgs = {new Long(fileCount), diskName}; 284 * 285 * System.out.println(form.format(testArgs)); 286 * </pre></blockquote> 287 * The output with different values for <code>fileCount</code>: 288 * <blockquote><pre> 289 * The disk "MyDisk" contains no files. 290 * The disk "MyDisk" contains one file. 291 * The disk "MyDisk" contains 1,273 files. 292 * </pre></blockquote> 293 * 294 * <p> 295 * You can create the <code>ChoiceFormat</code> programmatically, as in the 296 * above example, or by using a pattern. See {@link ChoiceFormat} 297 * for more information. 298 * <blockquote><pre> 299 * form.applyPattern( 300 * "There {0,choice,0#are no files|1#is one file|1<are {0,number,integer} files}."); 301 * </pre></blockquote> 302 * 303 * <p> 304 * <strong>Note:</strong> As we see above, the string produced 305 * by a <code>ChoiceFormat</code> in <code>MessageFormat</code> is treated as special; 306 * occurrences of '{' are used to indicate subformats, and cause recursion. 307 * If you create both a <code>MessageFormat</code> and <code>ChoiceFormat</code> 308 * programmatically (instead of using the string patterns), then be careful not to 309 * produce a format that recurses on itself, which will cause an infinite loop. 310 * <p> 311 * When a single argument is parsed more than once in the string, the last match 312 * will be the final result of the parsing. For example, 313 * <blockquote><pre> 314 * MessageFormat mf = new MessageFormat("{0,number,#.##}, {0,number,#.#}"); 315 * Object[] objs = {new Double(3.1415)}; 316 * String result = mf.format( objs ); 317 * // result now equals "3.14, 3.1" 318 * objs = null; 319 * objs = mf.parse(result, new ParsePosition(0)); 320 * // objs now equals {new Double(3.1)} 321 * </pre></blockquote> 322 * 323 * <p> 324 * Likewise, parsing with a MessageFormat object using patterns containing 325 * multiple occurrences of the same argument would return the last match. For 326 * example, 327 * <blockquote><pre> 328 * MessageFormat mf = new MessageFormat("{0}, {0}, {0}"); 329 * String forParsing = "x, y, z"; 330 * Object[] objs = mf.parse(forParsing, new ParsePosition(0)); 331 * // result now equals {new String("z")} 332 * </pre></blockquote> 333 * 334 * <h4><a name="synchronization">Synchronization</a></h4> 335 * 336 * <p> 337 * Message formats are not synchronized. 338 * It is recommended to create separate format instances for each thread. 339 * If multiple threads access a format concurrently, it must be synchronized 340 * externally. 341 * 342 * @see java.util.Locale 343 * @see Format 344 * @see NumberFormat 345 * @see DecimalFormat 346 * @see ChoiceFormat 347 * @author Mark Davis 348 */ 349 350 public class MessageFormat extends Format { 351 352 private static final long serialVersionUID = 6479157306784022952L; 353 354 /** 355 * Constructs a MessageFormat for the default locale and the 356 * specified pattern. 357 * The constructor first sets the locale, then parses the pattern and 358 * creates a list of subformats for the format elements contained in it. 359 * Patterns and their interpretation are specified in the 360 * <a href="#patterns">class description</a>. 361 * 362 * @param pattern the pattern for this message format 363 * @exception IllegalArgumentException if the pattern is invalid 364 */ 365 public MessageFormat(String pattern) { 366 this.locale = Locale.getDefault(Locale.Category.FORMAT); 367 applyPattern(pattern); 368 } 369 370 /** 371 * Constructs a MessageFormat for the specified locale and 372 * pattern. 373 * The constructor first sets the locale, then parses the pattern and 374 * creates a list of subformats for the format elements contained in it. 375 * Patterns and their interpretation are specified in the 376 * <a href="#patterns">class description</a>. 377 * 378 * @param pattern the pattern for this message format 379 * @param locale the locale for this message format 380 * @exception IllegalArgumentException if the pattern is invalid 381 * @since 1.4 382 */ 383 public MessageFormat(String pattern, Locale locale) { 384 this.locale = locale; 385 applyPattern(pattern); 386 } 387 388 /** 389 * Sets the locale to be used when creating or comparing subformats. 390 * This affects subsequent calls 391 * <ul> 392 * <li>to the {@link #applyPattern applyPattern} 393 * and {@link #toPattern toPattern} methods if format elements specify 394 * a format type and therefore have the subformats created in the 395 * <code>applyPattern</code> method, as well as 396 * <li>to the <code>format</code> and 397 * {@link #formatToCharacterIterator formatToCharacterIterator} methods 398 * if format elements do not specify a format type and therefore have 399 * the subformats created in the formatting methods. 400 * </ul> 401 * Subformats that have already been created are not affected. 402 * 403 * @param locale the locale to be used when creating or comparing subformats 404 */ 405 public void setLocale(Locale locale) { 406 this.locale = locale; 407 } 408 409 /** 410 * Gets the locale that's used when creating or comparing subformats. 411 * 412 * @return the locale used when creating or comparing subformats 413 */ 414 public Locale getLocale() { 415 return locale; 416 } 417 418 419 /** 420 * Sets the pattern used by this message format. 421 * The method parses the pattern and creates a list of subformats 422 * for the format elements contained in it. 423 * Patterns and their interpretation are specified in the 424 * <a href="#patterns">class description</a>. 425 * 426 * @param pattern the pattern for this message format 427 * @exception IllegalArgumentException if the pattern is invalid 428 */ 429 public void applyPattern(String pattern) { 430 StringBuffer[] segments = new StringBuffer[4]; 431 for (int i = 0; i < segments.length; ++i) { 432 segments[i] = new StringBuffer(); 433 } 434 int part = 0; 435 int formatNumber = 0; 436 boolean inQuote = false; 437 int braceStack = 0; 438 maxOffset = -1; 439 for (int i = 0; i < pattern.length(); ++i) { 440 char ch = pattern.charAt(i); 441 if (part == 0) { 442 if (ch == '\'') { 443 if (i + 1 < pattern.length() 444 && pattern.charAt(i+1) == '\'') { 445 segments[part].append(ch); // handle doubles 446 ++i; 447 } else { 448 inQuote = !inQuote; 449 } 450 } else if (ch == '{' && !inQuote) { 451 part = 1; 452 } else { 453 segments[part].append(ch); 454 } 455 } else if (inQuote) { // just copy quotes in parts 456 segments[part].append(ch); 457 if (ch == '\'') { 458 inQuote = false; 459 } 460 } else { 461 switch (ch) { 462 case ',': 463 if (part < 3) 464 part += 1; 465 else 466 segments[part].append(ch); 467 break; 468 case '{': 469 ++braceStack; 470 segments[part].append(ch); 471 break; 472 case '}': 473 if (braceStack == 0) { 474 part = 0; 475 makeFormat(i, formatNumber, segments); 476 formatNumber++; 477 } else { 478 --braceStack; 479 segments[part].append(ch); 480 } 481 break; 482 case '\'': 483 inQuote = true; 484 // fall through, so we keep quotes in other parts 485 default: 486 segments[part].append(ch); 487 break; 488 } 489 } 490 } 491 if (braceStack == 0 && part != 0) { 492 maxOffset = -1; 493 throw new IllegalArgumentException("Unmatched braces in the pattern."); 494 } 495 this.pattern = segments[0].toString(); 496 } 497 498 499 /** 500 * Returns a pattern representing the current state of the message format. 501 * The string is constructed from internal information and therefore 502 * does not necessarily equal the previously applied pattern. 503 * 504 * @return a pattern representing the current state of the message format 505 */ 506 public String toPattern() { 507 // later, make this more extensible 508 int lastOffset = 0; 509 StringBuffer result = new StringBuffer(); 510 for (int i = 0; i <= maxOffset; ++i) { 511 copyAndFixQuotes(pattern, lastOffset, offsets[i],result); 512 lastOffset = offsets[i]; 513 result.append('{'); 514 result.append(argumentNumbers[i]); 515 if (formats[i] == null) { 516 // do nothing, string format 517 } else if (formats[i] instanceof DecimalFormat) { 518 if (formats[i].equals(NumberFormat.getInstance(locale))) { 519 result.append(",number"); 520 } else if (formats[i].equals(NumberFormat.getCurrencyInstance(locale))) { 521 result.append(",number,currency"); 522 } else if (formats[i].equals(NumberFormat.getPercentInstance(locale))) { 523 result.append(",number,percent"); 524 } else if (formats[i].equals(NumberFormat.getIntegerInstance(locale))) { 525 result.append(",number,integer"); 526 } else { 527 result.append(",number," + 528 ((DecimalFormat)formats[i]).toPattern()); 529 } 530 } else if (formats[i] instanceof SimpleDateFormat) { 531 if (formats[i].equals(DateFormat.getDateInstance( 532 DateFormat.DEFAULT,locale))) { 533 result.append(",date"); 534 } else if (formats[i].equals(DateFormat.getDateInstance( 535 DateFormat.SHORT,locale))) { 536 result.append(",date,short"); 537 } else if (formats[i].equals(DateFormat.getDateInstance( 538 DateFormat.DEFAULT,locale))) { 539 result.append(",date,medium"); 540 } else if (formats[i].equals(DateFormat.getDateInstance( 541 DateFormat.LONG,locale))) { 542 result.append(",date,long"); 543 } else if (formats[i].equals(DateFormat.getDateInstance( 544 DateFormat.FULL,locale))) { 545 result.append(",date,full"); 546 } else if (formats[i].equals(DateFormat.getTimeInstance( 547 DateFormat.DEFAULT,locale))) { 548 result.append(",time"); 549 } else if (formats[i].equals(DateFormat.getTimeInstance( 550 DateFormat.SHORT,locale))) { 551 result.append(",time,short"); 552 } else if (formats[i].equals(DateFormat.getTimeInstance( 553 DateFormat.DEFAULT,locale))) { 554 result.append(",time,medium"); 555 } else if (formats[i].equals(DateFormat.getTimeInstance( 556 DateFormat.LONG,locale))) { 557 result.append(",time,long"); 558 } else if (formats[i].equals(DateFormat.getTimeInstance( 559 DateFormat.FULL,locale))) { 560 result.append(",time,full"); 561 } else { 562 result.append(",date," 563 + ((SimpleDateFormat)formats[i]).toPattern()); 564 } 565 } else if (formats[i] instanceof ChoiceFormat) { 566 result.append(",choice," 567 + ((ChoiceFormat)formats[i]).toPattern()); 568 } else { 569 //result.append(", unknown"); 570 } 571 result.append('}'); 572 } 573 copyAndFixQuotes(pattern, lastOffset, pattern.length(), result); 574 return result.toString(); 575 } 576 577 /** 578 * Sets the formats to use for the values passed into 579 * <code>format</code> methods or returned from <code>parse</code> 580 * methods. The indices of elements in <code>newFormats</code> 581 * correspond to the argument indices used in the previously set 582 * pattern string. 583 * The order of formats in <code>newFormats</code> thus corresponds to 584 * the order of elements in the <code>arguments</code> array passed 585 * to the <code>format</code> methods or the result array returned 586 * by the <code>parse</code> methods. 587 * <p> 588 * If an argument index is used for more than one format element 589 * in the pattern string, then the corresponding new format is used 590 * for all such format elements. If an argument index is not used 591 * for any format element in the pattern string, then the 592 * corresponding new format is ignored. If fewer formats are provided 593 * than needed, then only the formats for argument indices less 594 * than <code>newFormats.length</code> are replaced. 595 * 596 * @param newFormats the new formats to use 597 * @exception NullPointerException if <code>newFormats</code> is null 598 * @since 1.4 599 */ 600 public void setFormatsByArgumentIndex(Format[] newFormats) { 601 for (int i = 0; i <= maxOffset; i++) { 602 int j = argumentNumbers[i]; 603 if (j < newFormats.length) { 604 formats[i] = newFormats[j]; 605 } 606 } 607 } 608 609 /** 610 * Sets the formats to use for the format elements in the 611 * previously set pattern string. 612 * The order of formats in <code>newFormats</code> corresponds to 613 * the order of format elements in the pattern string. 614 * <p> 615 * If more formats are provided than needed by the pattern string, 616 * the remaining ones are ignored. If fewer formats are provided 617 * than needed, then only the first <code>newFormats.length</code> 618 * formats are replaced. 619 * <p> 620 * Since the order of format elements in a pattern string often 621 * changes during localization, it is generally better to use the 622 * {@link #setFormatsByArgumentIndex setFormatsByArgumentIndex} 623 * method, which assumes an order of formats corresponding to the 624 * order of elements in the <code>arguments</code> array passed to 625 * the <code>format</code> methods or the result array returned by 626 * the <code>parse</code> methods. 627 * 628 * @param newFormats the new formats to use 629 * @exception NullPointerException if <code>newFormats</code> is null 630 */ 631 public void setFormats(Format[] newFormats) { 632 int runsToCopy = newFormats.length; 633 if (runsToCopy > maxOffset + 1) { 634 runsToCopy = maxOffset + 1; 635 } 636 for (int i = 0; i < runsToCopy; i++) { 637 formats[i] = newFormats[i]; 638 } 639 } 640 641 /** 642 * Sets the format to use for the format elements within the 643 * previously set pattern string that use the given argument 644 * index. 645 * The argument index is part of the format element definition and 646 * represents an index into the <code>arguments</code> array passed 647 * to the <code>format</code> methods or the result array returned 648 * by the <code>parse</code> methods. 649 * <p> 650 * If the argument index is used for more than one format element 651 * in the pattern string, then the new format is used for all such 652 * format elements. If the argument index is not used for any format 653 * element in the pattern string, then the new format is ignored. 654 * 655 * @param argumentIndex the argument index for which to use the new format 656 * @param newFormat the new format to use 657 * @since 1.4 658 */ 659 public void setFormatByArgumentIndex(int argumentIndex, Format newFormat) { 660 for (int j = 0; j <= maxOffset; j++) { 661 if (argumentNumbers[j] == argumentIndex) { 662 formats[j] = newFormat; 663 } 664 } 665 } 666 667 /** 668 * Sets the format to use for the format element with the given 669 * format element index within the previously set pattern string. 670 * The format element index is the zero-based number of the format 671 * element counting from the start of the pattern string. 672 * <p> 673 * Since the order of format elements in a pattern string often 674 * changes during localization, it is generally better to use the 675 * {@link #setFormatByArgumentIndex setFormatByArgumentIndex} 676 * method, which accesses format elements based on the argument 677 * index they specify. 678 * 679 * @param formatElementIndex the index of a format element within the pattern 680 * @param newFormat the format to use for the specified format element 681 * @exception ArrayIndexOutOfBoundsException if formatElementIndex is equal to or 682 * larger than the number of format elements in the pattern string 683 */ 684 public void setFormat(int formatElementIndex, Format newFormat) { 685 formats[formatElementIndex] = newFormat; 686 } 687 688 /** 689 * Gets the formats used for the values passed into 690 * <code>format</code> methods or returned from <code>parse</code> 691 * methods. The indices of elements in the returned array 692 * correspond to the argument indices used in the previously set 693 * pattern string. 694 * The order of formats in the returned array thus corresponds to 695 * the order of elements in the <code>arguments</code> array passed 696 * to the <code>format</code> methods or the result array returned 697 * by the <code>parse</code> methods. 698 * <p> 699 * If an argument index is used for more than one format element 700 * in the pattern string, then the format used for the last such 701 * format element is returned in the array. If an argument index 702 * is not used for any format element in the pattern string, then 703 * null is returned in the array. 704 * 705 * @return the formats used for the arguments within the pattern 706 * @since 1.4 707 */ 708 public Format[] getFormatsByArgumentIndex() { 709 int maximumArgumentNumber = -1; 710 for (int i = 0; i <= maxOffset; i++) { 711 if (argumentNumbers[i] > maximumArgumentNumber) { 712 maximumArgumentNumber = argumentNumbers[i]; 713 } 714 } 715 Format[] resultArray = new Format[maximumArgumentNumber + 1]; 716 for (int i = 0; i <= maxOffset; i++) { 717 resultArray[argumentNumbers[i]] = formats[i]; 718 } 719 return resultArray; 720 } 721 722 /** 723 * Gets the formats used for the format elements in the 724 * previously set pattern string. 725 * The order of formats in the returned array corresponds to 726 * the order of format elements in the pattern string. 727 * <p> 728 * Since the order of format elements in a pattern string often 729 * changes during localization, it's generally better to use the 730 * {@link #getFormatsByArgumentIndex getFormatsByArgumentIndex} 731 * method, which assumes an order of formats corresponding to the 732 * order of elements in the <code>arguments</code> array passed to 733 * the <code>format</code> methods or the result array returned by 734 * the <code>parse</code> methods. 735 * 736 * @return the formats used for the format elements in the pattern 737 */ 738 public Format[] getFormats() { 739 Format[] resultArray = new Format[maxOffset + 1]; 740 System.arraycopy(formats, 0, resultArray, 0, maxOffset + 1); 741 return resultArray; 742 } 743 744 /** 745 * Formats an array of objects and appends the <code>MessageFormat</code>'s 746 * pattern, with format elements replaced by the formatted objects, to the 747 * provided <code>StringBuffer</code>. 748 * <p> 749 * The text substituted for the individual format elements is derived from 750 * the current subformat of the format element and the 751 * <code>arguments</code> element at the format element's argument index 752 * as indicated by the first matching line of the following table. An 753 * argument is <i>unavailable</i> if <code>arguments</code> is 754 * <code>null</code> or has fewer than argumentIndex+1 elements. 755 * <p> 756 * <table border=1 summary="Examples of subformat,argument,and formatted text"> 757 * <tr> 758 * <th>Subformat 759 * <th>Argument 760 * <th>Formatted Text 761 * <tr> 762 * <td><i>any</i> 763 * <td><i>unavailable</i> 764 * <td><code>"{" + argumentIndex + "}"</code> 765 * <tr> 766 * <td><i>any</i> 767 * <td><code>null</code> 768 * <td><code>"null"</code> 769 * <tr> 770 * <td><code>instanceof ChoiceFormat</code> 771 * <td><i>any</i> 772 * <td><code>subformat.format(argument).indexOf('{') >= 0 ?<br> 773 * (new MessageFormat(subformat.format(argument), getLocale())).format(argument) : 774 * subformat.format(argument)</code> 775 * <tr> 776 * <td><code>!= null</code> 777 * <td><i>any</i> 778 * <td><code>subformat.format(argument)</code> 779 * <tr> 780 * <td><code>null</code> 781 * <td><code>instanceof Number</code> 782 * <td><code>NumberFormat.getInstance(getLocale()).format(argument)</code> 783 * <tr> 784 * <td><code>null</code> 785 * <td><code>instanceof Date</code> 786 * <td><code>DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, getLocale()).format(argument)</code> 787 * <tr> 788 * <td><code>null</code> 789 * <td><code>instanceof String</code> 790 * <td><code>argument</code> 791 * <tr> 792 * <td><code>null</code> 793 * <td><i>any</i> 794 * <td><code>argument.toString()</code> 795 * </table> 796 * <p> 797 * If <code>pos</code> is non-null, and refers to 798 * <code>Field.ARGUMENT</code>, the location of the first formatted 799 * string will be returned. 800 * 801 * @param arguments an array of objects to be formatted and substituted. 802 * @param result where text is appended. 803 * @param pos On input: an alignment field, if desired. 804 * On output: the offsets of the alignment field. 805 * @exception IllegalArgumentException if an argument in the 806 * <code>arguments</code> array is not of the type 807 * expected by the format element(s) that use it. 808 */ 809 public final StringBuffer format(Object[] arguments, StringBuffer result, 810 FieldPosition pos) 811 { 812 return subformat(arguments, result, pos, null); 813 } 814 815 /** 816 * Creates a MessageFormat with the given pattern and uses it 817 * to format the given arguments. This is equivalent to 818 * <blockquote> 819 * <code>(new {@link #MessageFormat(String) MessageFormat}(pattern)).{@link #format(java.lang.Object[], java.lang.StringBuffer, java.text.FieldPosition) format}(arguments, new StringBuffer(), null).toString()</code> 820 * </blockquote> 821 * 822 * @exception IllegalArgumentException if the pattern is invalid, 823 * or if an argument in the <code>arguments</code> array 824 * is not of the type expected by the format element(s) 825 * that use it. 826 */ 827 public static String format(String pattern, Object ... arguments) { 828 MessageFormat temp = new MessageFormat(pattern); 829 return temp.format(arguments); 830 } 831 832 // Overrides 833 /** 834 * Formats an array of objects and appends the <code>MessageFormat</code>'s 835 * pattern, with format elements replaced by the formatted objects, to the 836 * provided <code>StringBuffer</code>. 837 * This is equivalent to 838 * <blockquote> 839 * <code>{@link #format(java.lang.Object[], java.lang.StringBuffer, java.text.FieldPosition) format}((Object[]) arguments, result, pos)</code> 840 * </blockquote> 841 * 842 * @param arguments an array of objects to be formatted and substituted. 843 * @param result where text is appended. 844 * @param pos On input: an alignment field, if desired. 845 * On output: the offsets of the alignment field. 846 * @exception IllegalArgumentException if an argument in the 847 * <code>arguments</code> array is not of the type 848 * expected by the format element(s) that use it. 849 */ 850 public final StringBuffer format(Object arguments, StringBuffer result, 851 FieldPosition pos) 852 { 853 return subformat((Object[]) arguments, result, pos, null); 854 } 855 856 /** 857 * Formats an array of objects and inserts them into the 858 * <code>MessageFormat</code>'s pattern, producing an 859 * <code>AttributedCharacterIterator</code>. 860 * You can use the returned <code>AttributedCharacterIterator</code> 861 * to build the resulting String, as well as to determine information 862 * about the resulting String. 863 * <p> 864 * The text of the returned <code>AttributedCharacterIterator</code> is 865 * the same that would be returned by 866 * <blockquote> 867 * <code>{@link #format(java.lang.Object[], java.lang.StringBuffer, java.text.FieldPosition) format}(arguments, new StringBuffer(), null).toString()</code> 868 * </blockquote> 869 * <p> 870 * In addition, the <code>AttributedCharacterIterator</code> contains at 871 * least attributes indicating where text was generated from an 872 * argument in the <code>arguments</code> array. The keys of these attributes are of 873 * type <code>MessageFormat.Field</code>, their values are 874 * <code>Integer</code> objects indicating the index in the <code>arguments</code> 875 * array of the argument from which the text was generated. 876 * <p> 877 * The attributes/value from the underlying <code>Format</code> 878 * instances that <code>MessageFormat</code> uses will also be 879 * placed in the resulting <code>AttributedCharacterIterator</code>. 880 * This allows you to not only find where an argument is placed in the 881 * resulting String, but also which fields it contains in turn. 882 * 883 * @param arguments an array of objects to be formatted and substituted. 884 * @return AttributedCharacterIterator describing the formatted value. 885 * @exception NullPointerException if <code>arguments</code> is null. 886 * @exception IllegalArgumentException if an argument in the 887 * <code>arguments</code> array is not of the type 888 * expected by the format element(s) that use it. 889 * @since 1.4 890 */ 891 public AttributedCharacterIterator formatToCharacterIterator(Object arguments) { 892 StringBuffer result = new StringBuffer(); 893 ArrayList iterators = new ArrayList(); 894 895 if (arguments == null) { 896 throw new NullPointerException( 897 "formatToCharacterIterator must be passed non-null object"); 898 } 899 subformat((Object[]) arguments, result, null, iterators); 900 if (iterators.size() == 0) { 901 return createAttributedCharacterIterator(""); 902 } 903 return createAttributedCharacterIterator( 904 (AttributedCharacterIterator[])iterators.toArray( 905 new AttributedCharacterIterator[iterators.size()])); 906 } 907 908 /** 909 * Parses the string. 910 * 911 * <p>Caveats: The parse may fail in a number of circumstances. 912 * For example: 913 * <ul> 914 * <li>If one of the arguments does not occur in the pattern. 915 * <li>If the format of an argument loses information, such as 916 * with a choice format where a large number formats to "many". 917 * <li>Does not yet handle recursion (where 918 * the substituted strings contain {n} references.) 919 * <li>Will not always find a match (or the correct match) 920 * if some part of the parse is ambiguous. 921 * For example, if the pattern "{1},{2}" is used with the 922 * string arguments {"a,b", "c"}, it will format as "a,b,c". 923 * When the result is parsed, it will return {"a", "b,c"}. 924 * <li>If a single argument is parsed more than once in the string, 925 * then the later parse wins. 926 * </ul> 927 * When the parse fails, use ParsePosition.getErrorIndex() to find out 928 * where in the string the parsing failed. The returned error 929 * index is the starting offset of the sub-patterns that the string 930 * is comparing with. For example, if the parsing string "AAA {0} BBB" 931 * is comparing against the pattern "AAD {0} BBB", the error index is 932 * 0. When an error occurs, the call to this method will return null. 933 * If the source is null, return an empty array. 934 */ 935 public Object[] parse(String source, ParsePosition pos) { 936 if (source == null) { 937 Object[] empty = {}; 938 return empty; 939 } 940 941 int maximumArgumentNumber = -1; 942 for (int i = 0; i <= maxOffset; i++) { 943 if (argumentNumbers[i] > maximumArgumentNumber) { 944 maximumArgumentNumber = argumentNumbers[i]; 945 } 946 } 947 Object[] resultArray = new Object[maximumArgumentNumber + 1]; 948 949 int patternOffset = 0; 950 int sourceOffset = pos.index; 951 ParsePosition tempStatus = new ParsePosition(0); 952 for (int i = 0; i <= maxOffset; ++i) { 953 // match up to format 954 int len = offsets[i] - patternOffset; 955 if (len == 0 || pattern.regionMatches(patternOffset, 956 source, sourceOffset, len)) { 957 sourceOffset += len; 958 patternOffset += len; 959 } else { 960 pos.errorIndex = sourceOffset; 961 return null; // leave index as is to signal error 962 } 963 964 // now use format 965 if (formats[i] == null) { // string format 966 // if at end, use longest possible match 967 // otherwise uses first match to intervening string 968 // does NOT recursively try all possibilities 969 int tempLength = (i != maxOffset) ? offsets[i+1] : pattern.length(); 970 971 int next; 972 if (patternOffset >= tempLength) { 973 next = source.length(); 974 }else{ 975 next = source.indexOf( pattern.substring(patternOffset,tempLength), sourceOffset); 976 } 977 978 if (next < 0) { 979 pos.errorIndex = sourceOffset; 980 return null; // leave index as is to signal error 981 } else { 982 String strValue= source.substring(sourceOffset,next); 983 if (!strValue.equals("{"+argumentNumbers[i]+"}")) 984 resultArray[argumentNumbers[i]] 985 = source.substring(sourceOffset,next); 986 sourceOffset = next; 987 } 988 } else { 989 tempStatus.index = sourceOffset; 990 resultArray[argumentNumbers[i]] 991 = formats[i].parseObject(source,tempStatus); 992 if (tempStatus.index == sourceOffset) { 993 pos.errorIndex = sourceOffset; 994 return null; // leave index as is to signal error 995 } 996 sourceOffset = tempStatus.index; // update 997 } 998 } 999 int len = pattern.length() - patternOffset; 1000 if (len == 0 || pattern.regionMatches(patternOffset, 1001 source, sourceOffset, len)) { 1002 pos.index = sourceOffset + len; 1003 } else { 1004 pos.errorIndex = sourceOffset; 1005 return null; // leave index as is to signal error 1006 } 1007 return resultArray; 1008 } 1009 1010 /** 1011 * Parses text from the beginning of the given string to produce an object 1012 * array. 1013 * The method may not use the entire text of the given string. 1014 * <p> 1015 * See the {@link #parse(String, ParsePosition)} method for more information 1016 * on message parsing. 1017 * 1018 * @param source A <code>String</code> whose beginning should be parsed. 1019 * @return An <code>Object</code> array parsed from the string. 1020 * @exception ParseException if the beginning of the specified string 1021 * cannot be parsed. 1022 */ 1023 public Object[] parse(String source) throws ParseException { 1024 ParsePosition pos = new ParsePosition(0); 1025 Object[] result = parse(source, pos); 1026 if (pos.index == 0) // unchanged, returned object is null 1027 throw new ParseException("MessageFormat parse error!", pos.errorIndex); 1028 1029 return result; 1030 } 1031 1032 /** 1033 * Parses text from a string to produce an object array. 1034 * <p> 1035 * The method attempts to parse text starting at the index given by 1036 * <code>pos</code>. 1037 * If parsing succeeds, then the index of <code>pos</code> is updated 1038 * to the index after the last character used (parsing does not necessarily 1039 * use all characters up to the end of the string), and the parsed 1040 * object array is returned. The updated <code>pos</code> can be used to 1041 * indicate the starting point for the next call to this method. 1042 * If an error occurs, then the index of <code>pos</code> is not 1043 * changed, the error index of <code>pos</code> is set to the index of 1044 * the character where the error occurred, and null is returned. 1045 * <p> 1046 * See the {@link #parse(String, ParsePosition)} method for more information 1047 * on message parsing. 1048 * 1049 * @param source A <code>String</code>, part of which should be parsed. 1050 * @param pos A <code>ParsePosition</code> object with index and error 1051 * index information as described above. 1052 * @return An <code>Object</code> array parsed from the string. In case of 1053 * error, returns null. 1054 * @exception NullPointerException if <code>pos</code> is null. 1055 */ 1056 public Object parseObject(String source, ParsePosition pos) { 1057 return parse(source, pos); 1058 } 1059 1060 /** 1061 * Creates and returns a copy of this object. 1062 * 1063 * @return a clone of this instance. 1064 */ 1065 public Object clone() { 1066 MessageFormat other = (MessageFormat) super.clone(); 1067 1068 // clone arrays. Can't do with utility because of bug in Cloneable 1069 other.formats = (Format[]) formats.clone(); // shallow clone 1070 for (int i = 0; i < formats.length; ++i) { 1071 if (formats[i] != null) 1072 other.formats[i] = (Format)formats[i].clone(); 1073 } 1074 // for primitives or immutables, shallow clone is enough 1075 other.offsets = (int[]) offsets.clone(); 1076 other.argumentNumbers = (int[]) argumentNumbers.clone(); 1077 1078 return other; 1079 } 1080 1081 /** 1082 * Equality comparison between two message format objects 1083 */ 1084 public boolean equals(Object obj) { 1085 if (this == obj) // quick check 1086 return true; 1087 if (obj == null || getClass() != obj.getClass()) 1088 return false; 1089 MessageFormat other = (MessageFormat) obj; 1090 return (maxOffset == other.maxOffset 1091 && pattern.equals(other.pattern) 1092 && ((locale != null && locale.equals(other.locale)) 1093 || (locale == null && other.locale == null)) 1094 && Arrays.equals(offsets,other.offsets) 1095 && Arrays.equals(argumentNumbers,other.argumentNumbers) 1096 && Arrays.equals(formats,other.formats)); 1097 } 1098 1099 /** 1100 * Generates a hash code for the message format object. 1101 */ 1102 public int hashCode() { 1103 return pattern.hashCode(); // enough for reasonable distribution 1104 } 1105 1106 1107 /** 1108 * Defines constants that are used as attribute keys in the 1109 * <code>AttributedCharacterIterator</code> returned 1110 * from <code>MessageFormat.formatToCharacterIterator</code>. 1111 * 1112 * @since 1.4 1113 */ 1114 public static class Field extends Format.Field { 1115 1116 // Proclaim serial compatibility with 1.4 FCS 1117 private static final long serialVersionUID = 7899943957617360810L; 1118 1119 /** 1120 * Creates a Field with the specified name. 1121 * 1122 * @param name Name of the attribute 1123 */ 1124 protected Field(String name) { 1125 super(name); 1126 } 1127 1128 /** 1129 * Resolves instances being deserialized to the predefined constants. 1130 * 1131 * @throws InvalidObjectException if the constant could not be 1132 * resolved. 1133 * @return resolved MessageFormat.Field constant 1134 */ 1135 protected Object readResolve() throws InvalidObjectException { 1136 if (this.getClass() != MessageFormat.Field.class) { 1137 throw new InvalidObjectException("subclass didn't correctly implement readResolve"); 1138 } 1139 1140 return ARGUMENT; 1141 } 1142 1143 // 1144 // The constants 1145 // 1146 1147 /** 1148 * Constant identifying a portion of a message that was generated 1149 * from an argument passed into <code>formatToCharacterIterator</code>. 1150 * The value associated with the key will be an <code>Integer</code> 1151 * indicating the index in the <code>arguments</code> array of the 1152 * argument from which the text was generated. 1153 */ 1154 public final static Field ARGUMENT = 1155 new Field("message argument field"); 1156 } 1157 1158 // ===========================privates============================ 1159 1160 /** 1161 * The locale to use for formatting numbers and dates. 1162 * @serial 1163 */ 1164 private Locale locale; 1165 1166 /** 1167 * The string that the formatted values are to be plugged into. In other words, this 1168 * is the pattern supplied on construction with all of the {} expressions taken out. 1169 * @serial 1170 */ 1171 private String pattern = ""; 1172 1173 /** The initially expected number of subformats in the format */ 1174 private static final int INITIAL_FORMATS = 10; 1175 1176 /** 1177 * An array of formatters, which are used to format the arguments. 1178 * @serial 1179 */ 1180 private Format[] formats = new Format[INITIAL_FORMATS]; 1181 1182 /** 1183 * The positions where the results of formatting each argument are to be inserted 1184 * into the pattern. 1185 * @serial 1186 */ 1187 private int[] offsets = new int[INITIAL_FORMATS]; 1188 1189 /** 1190 * The argument numbers corresponding to each formatter. (The formatters are stored 1191 * in the order they occur in the pattern, not in the order in which the arguments 1192 * are specified.) 1193 * @serial 1194 */ 1195 private int[] argumentNumbers = new int[INITIAL_FORMATS]; 1196 1197 /** 1198 * One less than the number of entries in <code>offsets</code>. Can also be thought of 1199 * as the index of the highest-numbered element in <code>offsets</code> that is being used. 1200 * All of these arrays should have the same number of elements being used as <code>offsets</code> 1201 * does, and so this variable suffices to tell us how many entries are in all of them. 1202 * @serial 1203 */ 1204 private int maxOffset = -1; 1205 1206 /** 1207 * Internal routine used by format. If <code>characterIterators</code> is 1208 * non-null, AttributedCharacterIterator will be created from the 1209 * subformats as necessary. If <code>characterIterators</code> is null 1210 * and <code>fp</code> is non-null and identifies 1211 * <code>Field.MESSAGE_ARGUMENT</code>, the location of 1212 * the first replaced argument will be set in it. 1213 * 1214 * @exception IllegalArgumentException if an argument in the 1215 * <code>arguments</code> array is not of the type 1216 * expected by the format element(s) that use it. 1217 */ 1218 private StringBuffer subformat(Object[] arguments, StringBuffer result, 1219 FieldPosition fp, List characterIterators) { 1220 // note: this implementation assumes a fast substring & index. 1221 // if this is not true, would be better to append chars one by one. 1222 int lastOffset = 0; 1223 int last = result.length(); 1224 for (int i = 0; i <= maxOffset; ++i) { 1225 result.append(pattern.substring(lastOffset, offsets[i])); 1226 lastOffset = offsets[i]; 1227 int argumentNumber = argumentNumbers[i]; 1228 if (arguments == null || argumentNumber >= arguments.length) { 1229 result.append("{" + argumentNumber + "}"); 1230 continue; 1231 } 1232 // int argRecursion = ((recursionProtection >> (argumentNumber*2)) & 0x3); 1233 if (false) { // if (argRecursion == 3){ 1234 // prevent loop!!! 1235 result.append('\uFFFD'); 1236 } else { 1237 Object obj = arguments[argumentNumber]; 1238 String arg = null; 1239 Format subFormatter = null; 1240 if (obj == null) { 1241 arg = "null"; 1242 } else if (formats[i] != null) { 1243 subFormatter = formats[i]; 1244 if (subFormatter instanceof ChoiceFormat) { 1245 arg = formats[i].format(obj); 1246 if (arg.indexOf('{') >= 0) { 1247 subFormatter = new MessageFormat(arg, locale); 1248 obj = arguments; 1249 arg = null; 1250 } 1251 } 1252 } else if (obj instanceof Number) { 1253 // format number if can 1254 subFormatter = NumberFormat.getInstance(locale); 1255 } else if (obj instanceof Date) { 1256 // format a Date if can 1257 subFormatter = DateFormat.getDateTimeInstance( 1258 DateFormat.SHORT, DateFormat.SHORT, locale);//fix 1259 } else if (obj instanceof String) { 1260 arg = (String) obj; 1261 1262 } else { 1263 arg = obj.toString(); 1264 if (arg == null) arg = "null"; 1265 } 1266 1267 // At this point we are in two states, either subFormatter 1268 // is non-null indicating we should format obj using it, 1269 // or arg is non-null and we should use it as the value. 1270 1271 if (characterIterators != null) { 1272 // If characterIterators is non-null, it indicates we need 1273 // to get the CharacterIterator from the child formatter. 1274 if (last != result.length()) { 1275 characterIterators.add( 1276 createAttributedCharacterIterator(result.substring 1277 (last))); 1278 last = result.length(); 1279 } 1280 if (subFormatter != null) { 1281 AttributedCharacterIterator subIterator = 1282 subFormatter.formatToCharacterIterator(obj); 1283 1284 append(result, subIterator); 1285 if (last != result.length()) { 1286 characterIterators.add( 1287 createAttributedCharacterIterator( 1288 subIterator, Field.ARGUMENT, 1289 Integer.valueOf(argumentNumber))); 1290 last = result.length(); 1291 } 1292 arg = null; 1293 } 1294 if (arg != null && arg.length() > 0) { 1295 result.append(arg); 1296 characterIterators.add( 1297 createAttributedCharacterIterator( 1298 arg, Field.ARGUMENT, 1299 Integer.valueOf(argumentNumber))); 1300 last = result.length(); 1301 } 1302 } 1303 else { 1304 if (subFormatter != null) { 1305 arg = subFormatter.format(obj); 1306 } 1307 last = result.length(); 1308 result.append(arg); 1309 if (i == 0 && fp != null && Field.ARGUMENT.equals( 1310 fp.getFieldAttribute())) { 1311 fp.setBeginIndex(last); 1312 fp.setEndIndex(result.length()); 1313 } 1314 last = result.length(); 1315 } 1316 } 1317 } 1318 result.append(pattern.substring(lastOffset, pattern.length())); 1319 if (characterIterators != null && last != result.length()) { 1320 characterIterators.add(createAttributedCharacterIterator( 1321 result.substring(last))); 1322 } 1323 return result; 1324 } 1325 1326 /** 1327 * Convenience method to append all the characters in 1328 * <code>iterator</code> to the StringBuffer <code>result</code>. 1329 */ 1330 private void append(StringBuffer result, CharacterIterator iterator) { 1331 if (iterator.first() != CharacterIterator.DONE) { 1332 char aChar; 1333 1334 result.append(iterator.first()); 1335 while ((aChar = iterator.next()) != CharacterIterator.DONE) { 1336 result.append(aChar); 1337 } 1338 } 1339 } 1340 1341 private static final String[] typeList = 1342 {"", "", "number", "", "date", "", "time", "", "choice"}; 1343 private static final String[] modifierList = 1344 {"", "", "currency", "", "percent", "", "integer"}; 1345 private static final String[] dateModifierList = 1346 {"", "", "short", "", "medium", "", "long", "", "full"}; 1347 1348 private void makeFormat(int position, int offsetNumber, 1349 StringBuffer[] segments) 1350 { 1351 // get the argument number 1352 int argumentNumber; 1353 try { 1354 argumentNumber = Integer.parseInt(segments[1].toString()); // always unlocalized! 1355 } catch (NumberFormatException e) { 1356 throw new IllegalArgumentException("can't parse argument number: " + segments[1]); 1357 } 1358 if (argumentNumber < 0) { 1359 throw new IllegalArgumentException("negative argument number: " + argumentNumber); 1360 } 1361 1362 // resize format information arrays if necessary 1363 if (offsetNumber >= formats.length) { 1364 int newLength = formats.length * 2; 1365 Format[] newFormats = new Format[newLength]; 1366 int[] newOffsets = new int[newLength]; 1367 int[] newArgumentNumbers = new int[newLength]; 1368 System.arraycopy(formats, 0, newFormats, 0, maxOffset + 1); 1369 System.arraycopy(offsets, 0, newOffsets, 0, maxOffset + 1); 1370 System.arraycopy(argumentNumbers, 0, newArgumentNumbers, 0, maxOffset + 1); 1371 formats = newFormats; 1372 offsets = newOffsets; 1373 argumentNumbers = newArgumentNumbers; 1374 } 1375 int oldMaxOffset = maxOffset; 1376 maxOffset = offsetNumber; 1377 offsets[offsetNumber] = segments[0].length(); 1378 argumentNumbers[offsetNumber] = argumentNumber; 1379 1380 // now get the format 1381 Format newFormat = null; 1382 switch (findKeyword(segments[2].toString(), typeList)) { 1383 case 0: 1384 break; 1385 case 1: case 2:// number 1386 switch (findKeyword(segments[3].toString(), modifierList)) { 1387 case 0: // default; 1388 newFormat = NumberFormat.getInstance(locale); 1389 break; 1390 case 1: case 2:// currency 1391 newFormat = NumberFormat.getCurrencyInstance(locale); 1392 break; 1393 case 3: case 4:// percent 1394 newFormat = NumberFormat.getPercentInstance(locale); 1395 break; 1396 case 5: case 6:// integer 1397 newFormat = NumberFormat.getIntegerInstance(locale); 1398 break; 1399 default: // pattern 1400 newFormat = new DecimalFormat(segments[3].toString(), DecimalFormatSymbols.getInstance(locale)); 1401 break; 1402 } 1403 break; 1404 case 3: case 4: // date 1405 switch (findKeyword(segments[3].toString(), dateModifierList)) { 1406 case 0: // default 1407 newFormat = DateFormat.getDateInstance(DateFormat.DEFAULT, locale); 1408 break; 1409 case 1: case 2: // short 1410 newFormat = DateFormat.getDateInstance(DateFormat.SHORT, locale); 1411 break; 1412 case 3: case 4: // medium 1413 newFormat = DateFormat.getDateInstance(DateFormat.DEFAULT, locale); 1414 break; 1415 case 5: case 6: // long 1416 newFormat = DateFormat.getDateInstance(DateFormat.LONG, locale); 1417 break; 1418 case 7: case 8: // full 1419 newFormat = DateFormat.getDateInstance(DateFormat.FULL, locale); 1420 break; 1421 default: 1422 newFormat = new SimpleDateFormat(segments[3].toString(), locale); 1423 break; 1424 } 1425 break; 1426 case 5: case 6:// time 1427 switch (findKeyword(segments[3].toString(), dateModifierList)) { 1428 case 0: // default 1429 newFormat = DateFormat.getTimeInstance(DateFormat.DEFAULT, locale); 1430 break; 1431 case 1: case 2: // short 1432 newFormat = DateFormat.getTimeInstance(DateFormat.SHORT, locale); 1433 break; 1434 case 3: case 4: // medium 1435 newFormat = DateFormat.getTimeInstance(DateFormat.DEFAULT, locale); 1436 break; 1437 case 5: case 6: // long 1438 newFormat = DateFormat.getTimeInstance(DateFormat.LONG, locale); 1439 break; 1440 case 7: case 8: // full 1441 newFormat = DateFormat.getTimeInstance(DateFormat.FULL, locale); 1442 break; 1443 default: 1444 newFormat = new SimpleDateFormat(segments[3].toString(), locale); 1445 break; 1446 } 1447 break; 1448 case 7: case 8:// choice 1449 try { 1450 newFormat = new ChoiceFormat(segments[3].toString()); 1451 } catch (Exception e) { 1452 maxOffset = oldMaxOffset; 1453 throw new IllegalArgumentException( 1454 "Choice Pattern incorrect"); 1455 } 1456 break; 1457 default: 1458 maxOffset = oldMaxOffset; 1459 throw new IllegalArgumentException("unknown format type: " + 1460 segments[2].toString()); 1461 } 1462 formats[offsetNumber] = newFormat; 1463 segments[1].setLength(0); // throw away other segments 1464 segments[2].setLength(0); 1465 segments[3].setLength(0); 1466 } 1467 1468 private static final int findKeyword(String s, String[] list) { 1469 s = s.trim().toLowerCase(); 1470 for (int i = 0; i < list.length; ++i) { 1471 if (s.equals(list[i])) 1472 return i; 1473 } 1474 return -1; 1475 } 1476 1477 private static final void copyAndFixQuotes( 1478 String source, int start, int end, StringBuffer target) { 1479 for (int i = start; i < end; ++i) { 1480 char ch = source.charAt(i); 1481 if (ch == '{') { 1482 target.append("'{'"); 1483 } else if (ch == '}') { 1484 target.append("'}'"); 1485 } else if (ch == '\'') { 1486 target.append("''"); 1487 } else { 1488 target.append(ch); 1489 } 1490 } 1491 } 1492 1493 /** 1494 * After reading an object from the input stream, do a simple verification 1495 * to maintain class invariants. 1496 * @throws InvalidObjectException if the objects read from the stream is invalid. 1497 */ 1498 private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { 1499 in.defaultReadObject(); 1500 boolean isValid = maxOffset >= -1 1501 && formats.length > maxOffset 1502 && offsets.length > maxOffset 1503 && argumentNumbers.length > maxOffset; 1504 if (isValid) { 1505 int lastOffset = pattern.length() + 1; 1506 for (int i = maxOffset; i >= 0; --i) { 1507 if ((offsets[i] < 0) || (offsets[i] > lastOffset)) { 1508 isValid = false; 1509 break; 1510 } else { 1511 lastOffset = offsets[i]; 1512 } 1513 } 1514 } 1515 if (!isValid) { 1516 throw new InvalidObjectException("Could not reconstruct MessageFormat from corrupt stream."); 1517 } 1518 } 1519 }