001/* Formatter.java -- printf-style formatting 002 Copyright (C) 2005 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.util; 040 041import gnu.java.lang.CPStringBuilder; 042 043import java.io.Closeable; 044import java.io.File; 045import java.io.FileNotFoundException; 046import java.io.FileOutputStream; 047import java.io.Flushable; 048import java.io.IOException; 049import java.io.OutputStream; 050import java.io.OutputStreamWriter; 051import java.io.PrintStream; 052import java.io.UnsupportedEncodingException; 053import java.math.BigInteger; 054import java.text.DateFormatSymbols; 055import java.text.DecimalFormatSymbols; 056 057import gnu.classpath.SystemProperties; 058 059/** 060 * <p> 061 * A Java formatter for <code>printf</code>-style format strings, 062 * as seen in the C programming language. This differs from the 063 * C interpretation of such strings by performing much stricter 064 * checking of format specifications and their corresponding 065 * arguments. While unknown conversions will be ignored in C, 066 * and invalid conversions will only produce compiler warnings, 067 * the Java version utilises a full range of run-time exceptions to 068 * handle these cases. The Java version is also more customisable 069 * by virtue of the provision of the {@link Formattable} interface, 070 * which allows an arbitrary class to be formatted by the formatter. 071 * </p> 072 * <p> 073 * The formatter is accessible by more convienient static methods. 074 * For example, streams now have appropriate format methods 075 * (the equivalent of <code>fprintf</code>) as do <code>String</code> 076 * objects (the equivalent of <code>sprintf</code>). 077 * </p> 078 * <p> 079 * <strong>Note</strong>: the formatter is not thread-safe. For 080 * multi-threaded access, external synchronization should be provided. 081 * </p> 082 * 083 * @author Tom Tromey (tromey@redhat.com) 084 * @author Andrew John Hughes (gnu_andrew@member.fsf.org) 085 * @since 1.5 086 */ 087public final class Formatter 088 implements Closeable, Flushable 089{ 090 091 /** 092 * The output of the formatter. 093 */ 094 private Appendable out; 095 096 /** 097 * The locale used by the formatter. 098 */ 099 private Locale locale; 100 101 /** 102 * Whether or not the formatter is closed. 103 */ 104 private boolean closed; 105 106 /** 107 * The last I/O exception thrown by the output stream. 108 */ 109 private IOException ioException; 110 111 // Some state used when actually formatting. 112 /** 113 * The format string. 114 */ 115 private String format; 116 117 /** 118 * The current index into the string. 119 */ 120 private int index; 121 122 /** 123 * The length of the format string. 124 */ 125 private int length; 126 127 /** 128 * The formatting locale. 129 */ 130 private Locale fmtLocale; 131 132 // Note that we include '-' twice. The flags are ordered to 133 // correspond to the values in FormattableFlags, and there is no 134 // flag (in the sense of this field used when parsing) for 135 // UPPERCASE; the second '-' serves as a placeholder. 136 /** 137 * A string used to index into the formattable flags. 138 */ 139 private static final String FLAGS = "--#+ 0,("; 140 141 /** 142 * The system line separator. 143 */ 144 private static final String lineSeparator 145 = SystemProperties.getProperty("line.separator"); 146 147 /** 148 * The type of numeric output format for a {@link BigDecimal}. 149 */ 150 public enum BigDecimalLayoutForm 151 { 152 DECIMAL_FLOAT, 153 SCIENTIFIC 154 } 155 156 /** 157 * Constructs a new <code>Formatter</code> using the default 158 * locale and a {@link StringBuilder} as the output stream. 159 */ 160 public Formatter() 161 { 162 this(null, Locale.getDefault()); 163 } 164 165 /** 166 * Constructs a new <code>Formatter</code> using the specified 167 * locale and a {@link StringBuilder} as the output stream. 168 * If the locale is <code>null</code>, then no localization 169 * is applied. 170 * 171 * @param loc the locale to use. 172 */ 173 public Formatter(Locale loc) 174 { 175 this(null, loc); 176 } 177 178 /** 179 * Constructs a new <code>Formatter</code> using the default 180 * locale and the specified output stream. 181 * 182 * @param app the output stream to use. 183 */ 184 public Formatter(Appendable app) 185 { 186 this(app, Locale.getDefault()); 187 } 188 189 /** 190 * Constructs a new <code>Formatter</code> using the specified 191 * locale and the specified output stream. If the locale is 192 * <code>null</code>, then no localization is applied. 193 * 194 * @param app the output stream to use. 195 * @param loc the locale to use. 196 */ 197 public Formatter(Appendable app, Locale loc) 198 { 199 this.out = app == null ? new StringBuilder() : app; 200 this.locale = loc; 201 } 202 203 /** 204 * Constructs a new <code>Formatter</code> using the default 205 * locale and character set, with the specified file as the 206 * output stream. 207 * 208 * @param file the file to use for output. 209 * @throws FileNotFoundException if the file does not exist 210 * and can not be created. 211 * @throws SecurityException if a security manager is present 212 * and doesn't allow writing to the file. 213 */ 214 public Formatter(File file) 215 throws FileNotFoundException 216 { 217 this(new OutputStreamWriter(new FileOutputStream(file))); 218 } 219 220 /** 221 * Constructs a new <code>Formatter</code> using the default 222 * locale, with the specified file as the output stream 223 * and the supplied character set. 224 * 225 * @param file the file to use for output. 226 * @param charset the character set to use for output. 227 * @throws FileNotFoundException if the file does not exist 228 * and can not be created. 229 * @throws SecurityException if a security manager is present 230 * and doesn't allow writing to the file. 231 * @throws UnsupportedEncodingException if the supplied character 232 * set is not supported. 233 */ 234 public Formatter(File file, String charset) 235 throws FileNotFoundException, UnsupportedEncodingException 236 { 237 this(file, charset, Locale.getDefault()); 238 } 239 240 /** 241 * Constructs a new <code>Formatter</code> using the specified 242 * file as the output stream with the supplied character set 243 * and locale. If the locale is <code>null</code>, then no 244 * localization is applied. 245 * 246 * @param file the file to use for output. 247 * @param charset the character set to use for output. 248 * @param loc the locale to use. 249 * @throws FileNotFoundException if the file does not exist 250 * and can not be created. 251 * @throws SecurityException if a security manager is present 252 * and doesn't allow writing to the file. 253 * @throws UnsupportedEncodingException if the supplied character 254 * set is not supported. 255 */ 256 public Formatter(File file, String charset, Locale loc) 257 throws FileNotFoundException, UnsupportedEncodingException 258 { 259 this(new OutputStreamWriter(new FileOutputStream(file), charset), 260 loc); 261 } 262 263 /** 264 * Constructs a new <code>Formatter</code> using the default 265 * locale and character set, with the specified output stream. 266 * 267 * @param out the output stream to use. 268 */ 269 public Formatter(OutputStream out) 270 { 271 this(new OutputStreamWriter(out)); 272 } 273 274 /** 275 * Constructs a new <code>Formatter</code> using the default 276 * locale, with the specified file output stream and the 277 * supplied character set. 278 * 279 * @param out the output stream. 280 * @param charset the character set to use for output. 281 * @throws UnsupportedEncodingException if the supplied character 282 * set is not supported. 283 */ 284 public Formatter(OutputStream out, String charset) 285 throws UnsupportedEncodingException 286 { 287 this(out, charset, Locale.getDefault()); 288 } 289 290 /** 291 * Constructs a new <code>Formatter</code> using the specified 292 * output stream with the supplied character set and locale. 293 * If the locale is <code>null</code>, then no localization is 294 * applied. 295 * 296 * @param out the output stream. 297 * @param charset the character set to use for output. 298 * @param loc the locale to use. 299 * @throws UnsupportedEncodingException if the supplied character 300 * set is not supported. 301 */ 302 public Formatter(OutputStream out, String charset, Locale loc) 303 throws UnsupportedEncodingException 304 { 305 this(new OutputStreamWriter(out, charset), loc); 306 } 307 308 /** 309 * Constructs a new <code>Formatter</code> using the default 310 * locale with the specified output stream. The character 311 * set used is that of the output stream. 312 * 313 * @param out the output stream to use. 314 */ 315 public Formatter(PrintStream out) 316 { 317 this((Appendable) out); 318 } 319 320 /** 321 * Constructs a new <code>Formatter</code> using the default 322 * locale and character set, with the specified file as the 323 * output stream. 324 * 325 * @param file the file to use for output. 326 * @throws FileNotFoundException if the file does not exist 327 * and can not be created. 328 * @throws SecurityException if a security manager is present 329 * and doesn't allow writing to the file. 330 */ 331 public Formatter(String file) throws FileNotFoundException 332 { 333 this(new OutputStreamWriter(new FileOutputStream(file))); 334 } 335 336 /** 337 * Constructs a new <code>Formatter</code> using the default 338 * locale, with the specified file as the output stream 339 * and the supplied character set. 340 * 341 * @param file the file to use for output. 342 * @param charset the character set to use for output. 343 * @throws FileNotFoundException if the file does not exist 344 * and can not be created. 345 * @throws SecurityException if a security manager is present 346 * and doesn't allow writing to the file. 347 * @throws UnsupportedEncodingException if the supplied character 348 * set is not supported. 349 */ 350 public Formatter(String file, String charset) 351 throws FileNotFoundException, UnsupportedEncodingException 352 { 353 this(file, charset, Locale.getDefault()); 354 } 355 356 /** 357 * Constructs a new <code>Formatter</code> using the specified 358 * file as the output stream with the supplied character set 359 * and locale. If the locale is <code>null</code>, then no 360 * localization is applied. 361 * 362 * @param file the file to use for output. 363 * @param charset the character set to use for output. 364 * @param loc the locale to use. 365 * @throws FileNotFoundException if the file does not exist 366 * and can not be created. 367 * @throws SecurityException if a security manager is present 368 * and doesn't allow writing to the file. 369 * @throws UnsupportedEncodingException if the supplied character 370 * set is not supported. 371 */ 372 public Formatter(String file, String charset, Locale loc) 373 throws FileNotFoundException, UnsupportedEncodingException 374 { 375 this(new OutputStreamWriter(new FileOutputStream(file), charset), 376 loc); 377 } 378 379 /** 380 * Closes the formatter, so as to release used resources. 381 * If the underlying output stream supports the {@link Closeable} 382 * interface, then this is also closed. Attempts to use 383 * a formatter instance, via any method other than 384 * {@link #ioException()}, after closure results in a 385 * {@link FormatterClosedException}. 386 */ 387 public void close() 388 { 389 if (closed) 390 return; 391 try 392 { 393 if (out instanceof Closeable) 394 ((Closeable) out).close(); 395 } 396 catch (IOException _) 397 { 398 // FIXME: do we ignore these or do we set ioException? 399 // The docs seem to indicate that we should ignore. 400 } 401 closed = true; 402 } 403 404 /** 405 * Flushes the formatter, writing any cached data to the output 406 * stream. If the underlying output stream supports the 407 * {@link Flushable} interface, it is also flushed. 408 * 409 * @throws FormatterClosedException if the formatter is closed. 410 */ 411 public void flush() 412 { 413 if (closed) 414 throw new FormatterClosedException(); 415 try 416 { 417 if (out instanceof Flushable) 418 ((Flushable) out).flush(); 419 } 420 catch (IOException _) 421 { 422 // FIXME: do we ignore these or do we set ioException? 423 // The docs seem to indicate that we should ignore. 424 } 425 } 426 427 /** 428 * Return the name corresponding to a flag. 429 * 430 * @param flags the flag to return the name of. 431 * @return the name of the flag. 432 */ 433 private String getName(int flags) 434 { 435 // FIXME: do we want all the flags in here? 436 // Or should we redo how this is reported? 437 int bit = Integer.numberOfTrailingZeros(flags); 438 return FLAGS.substring(bit, bit + 1); 439 } 440 441 /** 442 * Verify the flags passed to a conversion. 443 * 444 * @param flags the flags to verify. 445 * @param allowed the allowed flags mask. 446 * @param conversion the conversion character. 447 */ 448 private void checkFlags(int flags, int allowed, char conversion) 449 { 450 flags &= ~allowed; 451 if (flags != 0) 452 throw new FormatFlagsConversionMismatchException(getName(flags), 453 conversion); 454 } 455 456 /** 457 * Throw an exception if a precision was specified. 458 * 459 * @param precision the precision value (-1 indicates not specified). 460 */ 461 private void noPrecision(int precision) 462 { 463 if (precision != -1) 464 throw new IllegalFormatPrecisionException(precision); 465 } 466 467 /** 468 * Apply the numeric localization algorithm to a StringBuilder. 469 * 470 * @param builder the builder to apply to. 471 * @param flags the formatting flags to use. 472 * @param width the width of the numeric value. 473 * @param isNegative true if the value is negative. 474 */ 475 private void applyLocalization(CPStringBuilder builder, int flags, int width, 476 boolean isNegative) 477 { 478 DecimalFormatSymbols dfsyms; 479 if (fmtLocale == null) 480 dfsyms = new DecimalFormatSymbols(); 481 else 482 dfsyms = new DecimalFormatSymbols(fmtLocale); 483 484 // First replace each digit. 485 char zeroDigit = dfsyms.getZeroDigit(); 486 int decimalOffset = -1; 487 for (int i = builder.length() - 1; i >= 0; --i) 488 { 489 char c = builder.charAt(i); 490 if (c >= '0' && c <= '9') 491 builder.setCharAt(i, (char) (c - '0' + zeroDigit)); 492 else if (c == '.') 493 { 494 assert decimalOffset == -1; 495 decimalOffset = i; 496 } 497 } 498 499 // Localize the decimal separator. 500 if (decimalOffset != -1) 501 { 502 builder.deleteCharAt(decimalOffset); 503 builder.insert(decimalOffset, dfsyms.getDecimalSeparator()); 504 } 505 506 // Insert the grouping separators. 507 if ((flags & FormattableFlags.COMMA) != 0) 508 { 509 char groupSeparator = dfsyms.getGroupingSeparator(); 510 int groupSize = 3; // FIXME 511 int offset = (decimalOffset == -1) ? builder.length() : decimalOffset; 512 // We use '>' because we don't want to insert a separator 513 // before the first digit. 514 for (int i = offset - groupSize; i > 0; i -= groupSize) 515 builder.insert(i, groupSeparator); 516 } 517 518 if ((flags & FormattableFlags.ZERO) != 0) 519 { 520 // Zero fill. Note that according to the algorithm we do not 521 // insert grouping separators here. 522 for (int i = width - builder.length(); i > 0; --i) 523 builder.insert(0, zeroDigit); 524 } 525 526 if (isNegative) 527 { 528 if ((flags & FormattableFlags.PAREN) != 0) 529 { 530 builder.insert(0, '('); 531 builder.append(')'); 532 } 533 else 534 builder.insert(0, '-'); 535 } 536 else if ((flags & FormattableFlags.PLUS) != 0) 537 builder.insert(0, '+'); 538 else if ((flags & FormattableFlags.SPACE) != 0) 539 builder.insert(0, ' '); 540 } 541 542 /** 543 * A helper method that handles emitting a String after applying 544 * precision, width, justification, and upper case flags. 545 * 546 * @param arg the string to emit. 547 * @param flags the formatting flags to use. 548 * @param width the width to use. 549 * @param precision the precision to use. 550 * @throws IOException if the output stream throws an I/O error. 551 */ 552 private void genericFormat(String arg, int flags, int width, int precision) 553 throws IOException 554 { 555 if ((flags & FormattableFlags.UPPERCASE) != 0) 556 { 557 if (fmtLocale == null) 558 arg = arg.toUpperCase(); 559 else 560 arg = arg.toUpperCase(fmtLocale); 561 } 562 563 if (precision >= 0 && arg.length() > precision) 564 arg = arg.substring(0, precision); 565 566 boolean leftJustify = (flags & FormattableFlags.LEFT_JUSTIFY) != 0; 567 if (leftJustify && width == -1) 568 throw new MissingFormatWidthException("fixme"); 569 if (! leftJustify && arg.length() < width) 570 { 571 for (int i = width - arg.length(); i > 0; --i) 572 out.append(' '); 573 } 574 out.append(arg); 575 if (leftJustify && arg.length() < width) 576 { 577 for (int i = width - arg.length(); i > 0; --i) 578 out.append(' '); 579 } 580 } 581 582 /** 583 * Emit a boolean. 584 * 585 * @param arg the boolean to emit. 586 * @param flags the formatting flags to use. 587 * @param width the width to use. 588 * @param precision the precision to use. 589 * @param conversion the conversion character. 590 * @throws IOException if the output stream throws an I/O error. 591 */ 592 private void booleanFormat(Object arg, int flags, int width, int precision, 593 char conversion) 594 throws IOException 595 { 596 checkFlags(flags, 597 FormattableFlags.LEFT_JUSTIFY | FormattableFlags.UPPERCASE, 598 conversion); 599 String result; 600 if (arg instanceof Boolean) 601 result = String.valueOf((Boolean) arg); 602 else 603 result = arg == null ? "false" : "true"; 604 genericFormat(result, flags, width, precision); 605 } 606 607 /** 608 * Emit a hash code. 609 * 610 * @param arg the hash code to emit. 611 * @param flags the formatting flags to use. 612 * @param width the width to use. 613 * @param precision the precision to use. 614 * @param conversion the conversion character. 615 * @throws IOException if the output stream throws an I/O error. 616 */ 617 private void hashCodeFormat(Object arg, int flags, int width, int precision, 618 char conversion) 619 throws IOException 620 { 621 checkFlags(flags, 622 FormattableFlags.LEFT_JUSTIFY | FormattableFlags.UPPERCASE, 623 conversion); 624 genericFormat(arg == null ? "null" : Integer.toHexString(arg.hashCode()), 625 flags, width, precision); 626 } 627 628 /** 629 * Emit a String or Formattable conversion. 630 * 631 * @param arg the String or Formattable to emit. 632 * @param flags the formatting flags to use. 633 * @param width the width to use. 634 * @param precision the precision to use. 635 * @param conversion the conversion character. 636 * @throws IOException if the output stream throws an I/O error. 637 */ 638 private void stringFormat(Object arg, int flags, int width, int precision, 639 char conversion) 640 throws IOException 641 { 642 if (arg instanceof Formattable) 643 { 644 checkFlags(flags, 645 (FormattableFlags.LEFT_JUSTIFY 646 | FormattableFlags.UPPERCASE 647 | FormattableFlags.ALTERNATE), 648 conversion); 649 Formattable fmt = (Formattable) arg; 650 fmt.formatTo(this, flags, width, precision); 651 } 652 else 653 { 654 checkFlags(flags, 655 FormattableFlags.LEFT_JUSTIFY | FormattableFlags.UPPERCASE, 656 conversion); 657 genericFormat(arg == null ? "null" : arg.toString(), flags, width, 658 precision); 659 } 660 } 661 662 /** 663 * Emit a character. 664 * 665 * @param arg the character to emit. 666 * @param flags the formatting flags to use. 667 * @param width the width to use. 668 * @param precision the precision to use. 669 * @param conversion the conversion character. 670 * @throws IOException if the output stream throws an I/O error. 671 */ 672 private void characterFormat(Object arg, int flags, int width, int precision, 673 char conversion) 674 throws IOException 675 { 676 checkFlags(flags, 677 FormattableFlags.LEFT_JUSTIFY | FormattableFlags.UPPERCASE, 678 conversion); 679 noPrecision(precision); 680 681 if (arg == null) 682 { 683 genericFormat("null", flags, width, precision); 684 return; 685 } 686 687 int theChar; 688 if (arg instanceof Character) 689 theChar = ((Character) arg).charValue(); 690 else if (arg instanceof Byte) 691 theChar = (char) (((Byte) arg).byteValue ()); 692 else if (arg instanceof Short) 693 theChar = (char) (((Short) arg).shortValue ()); 694 else if (arg instanceof Integer) 695 { 696 theChar = ((Integer) arg).intValue(); 697 if (! Character.isValidCodePoint(theChar)) 698 throw new IllegalFormatCodePointException(theChar); 699 } 700 else 701 throw new IllegalFormatConversionException(conversion, arg.getClass()); 702 String result = new String(Character.toChars(theChar)); 703 genericFormat(result, flags, width, precision); 704 } 705 706 /** 707 * Emit a '%'. 708 * 709 * @param flags the formatting flags to use. 710 * @param width the width to use. 711 * @param precision the precision to use. 712 * @throws IOException if the output stream throws an I/O error. 713 */ 714 private void percentFormat(int flags, int width, int precision) 715 throws IOException 716 { 717 checkFlags(flags, FormattableFlags.LEFT_JUSTIFY, '%'); 718 noPrecision(precision); 719 genericFormat("%", flags, width, precision); 720 } 721 722 /** 723 * Emit a newline. 724 * 725 * @param flags the formatting flags to use. 726 * @param width the width to use. 727 * @param precision the precision to use. 728 * @throws IOException if the output stream throws an I/O error. 729 */ 730 private void newLineFormat(int flags, int width, int precision) 731 throws IOException 732 { 733 checkFlags(flags, 0, 'n'); 734 noPrecision(precision); 735 if (width != -1) 736 throw new IllegalFormatWidthException(width); 737 genericFormat(lineSeparator, flags, width, precision); 738 } 739 740 /** 741 * Helper method to do initial formatting and checking for integral 742 * conversions. 743 * 744 * @param arg the formatted argument. 745 * @param flags the formatting flags to use. 746 * @param width the width to use. 747 * @param precision the precision to use. 748 * @param radix the radix of the number. 749 * @param conversion the conversion character. 750 * @return the result. 751 */ 752 private CPStringBuilder basicIntegralConversion(Object arg, int flags, 753 int width, int precision, 754 int radix, char conversion) 755 { 756 assert radix == 8 || radix == 10 || radix == 16; 757 758 if (arg == null) 759 { 760 return new CPStringBuilder("null"); 761 } 762 763 noPrecision(precision); 764 765 // Some error checking. 766 if ((flags & FormattableFlags.PLUS) != 0 767 && (flags & FormattableFlags.SPACE) != 0) 768 throw new IllegalFormatFlagsException(getName(flags)); 769 770 if ((flags & FormattableFlags.LEFT_JUSTIFY) != 0 && width == -1) 771 throw new MissingFormatWidthException("fixme"); 772 773 // Do the base translation of the value to a string. 774 String result; 775 int basicFlags = (FormattableFlags.LEFT_JUSTIFY 776 // We already handled any possible error when 777 // parsing. 778 | FormattableFlags.UPPERCASE 779 | FormattableFlags.ZERO); 780 if (radix == 10) 781 basicFlags |= (FormattableFlags.PLUS 782 | FormattableFlags.SPACE 783 | FormattableFlags.COMMA 784 | FormattableFlags.PAREN); 785 else 786 basicFlags |= FormattableFlags.ALTERNATE; 787 788 if (arg instanceof BigInteger) 789 { 790 checkFlags(flags, 791 (basicFlags 792 | FormattableFlags.PLUS 793 | FormattableFlags.SPACE 794 | FormattableFlags.PAREN), 795 conversion); 796 BigInteger bi = (BigInteger) arg; 797 result = bi.toString(radix); 798 } 799 else if (arg instanceof Number 800 && ! (arg instanceof Float) 801 && ! (arg instanceof Double)) 802 { 803 checkFlags(flags, basicFlags, conversion); 804 long value = ((Number) arg).longValue (); 805 if (radix == 8) 806 result = Long.toOctalString(value); 807 else if (radix == 16) 808 result = Long.toHexString(value); 809 else 810 result = Long.toString(value); 811 } 812 else 813 throw new IllegalFormatConversionException(conversion, arg.getClass()); 814 815 return new CPStringBuilder(result); 816 } 817 818 /** 819 * Emit a hex or octal value. 820 * 821 * @param arg the hexadecimal or octal value. 822 * @param flags the formatting flags to use. 823 * @param width the width to use. 824 * @param precision the precision to use. 825 * @param radix the radix of the number. 826 * @param conversion the conversion character. 827 * @throws IOException if the output stream throws an I/O error. 828 */ 829 private void hexOrOctalConversion(Object arg, int flags, int width, 830 int precision, int radix, 831 char conversion) 832 throws IOException 833 { 834 assert radix == 8 || radix == 16; 835 836 CPStringBuilder builder = basicIntegralConversion(arg, flags, width, 837 precision, radix, 838 conversion); 839 int insertPoint = 0; 840 841 // Insert the sign. 842 if (builder.charAt(0) == '-') 843 { 844 // Already inserted. Note that we don't insert a sign, since 845 // the only case where it is needed it BigInteger, and it has 846 // already been inserted by toString. 847 ++insertPoint; 848 } 849 else if ((flags & FormattableFlags.PLUS) != 0) 850 { 851 builder.insert(insertPoint, '+'); 852 ++insertPoint; 853 } 854 else if ((flags & FormattableFlags.SPACE) != 0) 855 { 856 builder.insert(insertPoint, ' '); 857 ++insertPoint; 858 } 859 860 // Insert the radix prefix. 861 if ((flags & FormattableFlags.ALTERNATE) != 0) 862 { 863 builder.insert(insertPoint, radix == 8 ? "0" : "0x"); 864 insertPoint += radix == 8 ? 1 : 2; 865 } 866 867 // Now justify the result. 868 int resultWidth = builder.length(); 869 if (resultWidth < width) 870 { 871 char fill = ((flags & FormattableFlags.ZERO) != 0) ? '0' : ' '; 872 if ((flags & FormattableFlags.LEFT_JUSTIFY) != 0) 873 { 874 // Left justify. 875 if (fill == ' ') 876 insertPoint = builder.length(); 877 } 878 else 879 { 880 // Right justify. Insert spaces before the radix prefix 881 // and sign. 882 insertPoint = 0; 883 } 884 while (resultWidth++ < width) 885 builder.insert(insertPoint, fill); 886 } 887 888 String result = builder.toString(); 889 if ((flags & FormattableFlags.UPPERCASE) != 0) 890 { 891 if (fmtLocale == null) 892 result = result.toUpperCase(); 893 else 894 result = result.toUpperCase(fmtLocale); 895 } 896 897 out.append(result); 898 } 899 900 /** 901 * Emit a decimal value. 902 * 903 * @param arg the hexadecimal or octal value. 904 * @param flags the formatting flags to use. 905 * @param width the width to use. 906 * @param precision the precision to use. 907 * @param conversion the conversion character. 908 * @throws IOException if the output stream throws an I/O error. 909 */ 910 private void decimalConversion(Object arg, int flags, int width, 911 int precision, char conversion) 912 throws IOException 913 { 914 CPStringBuilder builder = basicIntegralConversion(arg, flags, width, 915 precision, 10, 916 conversion); 917 boolean isNegative = false; 918 if (builder.charAt(0) == '-') 919 { 920 // Sign handling is done during localization. 921 builder.deleteCharAt(0); 922 isNegative = true; 923 } 924 925 applyLocalization(builder, flags, width, isNegative); 926 genericFormat(builder.toString(), flags, width, precision); 927 } 928 929 /** 930 * Emit a single date or time conversion to a StringBuilder. 931 * 932 * @param builder the builder to write to. 933 * @param cal the calendar to use in the conversion. 934 * @param conversion the formatting character to specify the type of data. 935 * @param syms the date formatting symbols. 936 */ 937 private void singleDateTimeConversion(CPStringBuilder builder, Calendar cal, 938 char conversion, 939 DateFormatSymbols syms) 940 { 941 int oldLen = builder.length(); 942 int digits = -1; 943 switch (conversion) 944 { 945 case 'H': 946 builder.append(cal.get(Calendar.HOUR_OF_DAY)); 947 digits = 2; 948 break; 949 case 'I': 950 builder.append(cal.get(Calendar.HOUR)); 951 digits = 2; 952 break; 953 case 'k': 954 builder.append(cal.get(Calendar.HOUR_OF_DAY)); 955 break; 956 case 'l': 957 builder.append(cal.get(Calendar.HOUR)); 958 break; 959 case 'M': 960 builder.append(cal.get(Calendar.MINUTE)); 961 digits = 2; 962 break; 963 case 'S': 964 builder.append(cal.get(Calendar.SECOND)); 965 digits = 2; 966 break; 967 case 'N': 968 // FIXME: nanosecond ... 969 digits = 9; 970 break; 971 case 'p': 972 { 973 int ampm = cal.get(Calendar.AM_PM); 974 builder.append(syms.getAmPmStrings()[ampm]); 975 } 976 break; 977 case 'z': 978 { 979 int zone = cal.get(Calendar.ZONE_OFFSET) / (1000 * 60); 980 builder.append(zone); 981 digits = 4; 982 // Skip the '-' sign. 983 if (zone < 0) 984 ++oldLen; 985 } 986 break; 987 case 'Z': 988 { 989 // FIXME: DST? 990 int zone = cal.get(Calendar.ZONE_OFFSET) / (1000 * 60 * 60); 991 String[][] zs = syms.getZoneStrings(); 992 builder.append(zs[zone + 12][1]); 993 } 994 break; 995 case 's': 996 { 997 long val = cal.getTime().getTime(); 998 builder.append(val / 1000); 999 } 1000 break; 1001 case 'Q': 1002 { 1003 long val = cal.getTime().getTime(); 1004 builder.append(val); 1005 } 1006 break; 1007 case 'B': 1008 { 1009 int month = cal.get(Calendar.MONTH); 1010 builder.append(syms.getMonths()[month]); 1011 } 1012 break; 1013 case 'b': 1014 case 'h': 1015 { 1016 int month = cal.get(Calendar.MONTH); 1017 builder.append(syms.getShortMonths()[month]); 1018 } 1019 break; 1020 case 'A': 1021 { 1022 int day = cal.get(Calendar.DAY_OF_WEEK); 1023 builder.append(syms.getWeekdays()[day]); 1024 } 1025 break; 1026 case 'a': 1027 { 1028 int day = cal.get(Calendar.DAY_OF_WEEK); 1029 builder.append(syms.getShortWeekdays()[day]); 1030 } 1031 break; 1032 case 'C': 1033 builder.append(cal.get(Calendar.YEAR) / 100); 1034 digits = 2; 1035 break; 1036 case 'Y': 1037 builder.append(cal.get(Calendar.YEAR)); 1038 digits = 4; 1039 break; 1040 case 'y': 1041 builder.append(cal.get(Calendar.YEAR) % 100); 1042 digits = 2; 1043 break; 1044 case 'j': 1045 builder.append(cal.get(Calendar.DAY_OF_YEAR)); 1046 digits = 3; 1047 break; 1048 case 'm': 1049 builder.append(cal.get(Calendar.MONTH) + 1); 1050 digits = 2; 1051 break; 1052 case 'd': 1053 builder.append(cal.get(Calendar.DAY_OF_MONTH)); 1054 digits = 2; 1055 break; 1056 case 'e': 1057 builder.append(cal.get(Calendar.DAY_OF_MONTH)); 1058 break; 1059 case 'R': 1060 singleDateTimeConversion(builder, cal, 'H', syms); 1061 builder.append(':'); 1062 singleDateTimeConversion(builder, cal, 'M', syms); 1063 break; 1064 case 'T': 1065 singleDateTimeConversion(builder, cal, 'H', syms); 1066 builder.append(':'); 1067 singleDateTimeConversion(builder, cal, 'M', syms); 1068 builder.append(':'); 1069 singleDateTimeConversion(builder, cal, 'S', syms); 1070 break; 1071 case 'r': 1072 singleDateTimeConversion(builder, cal, 'I', syms); 1073 builder.append(':'); 1074 singleDateTimeConversion(builder, cal, 'M', syms); 1075 builder.append(':'); 1076 singleDateTimeConversion(builder, cal, 'S', syms); 1077 builder.append(' '); 1078 singleDateTimeConversion(builder, cal, 'p', syms); 1079 break; 1080 case 'D': 1081 singleDateTimeConversion(builder, cal, 'm', syms); 1082 builder.append('/'); 1083 singleDateTimeConversion(builder, cal, 'd', syms); 1084 builder.append('/'); 1085 singleDateTimeConversion(builder, cal, 'y', syms); 1086 break; 1087 case 'F': 1088 singleDateTimeConversion(builder, cal, 'Y', syms); 1089 builder.append('-'); 1090 singleDateTimeConversion(builder, cal, 'm', syms); 1091 builder.append('-'); 1092 singleDateTimeConversion(builder, cal, 'd', syms); 1093 break; 1094 case 'c': 1095 singleDateTimeConversion(builder, cal, 'a', syms); 1096 builder.append(' '); 1097 singleDateTimeConversion(builder, cal, 'b', syms); 1098 builder.append(' '); 1099 singleDateTimeConversion(builder, cal, 'd', syms); 1100 builder.append(' '); 1101 singleDateTimeConversion(builder, cal, 'T', syms); 1102 builder.append(' '); 1103 singleDateTimeConversion(builder, cal, 'Z', syms); 1104 builder.append(' '); 1105 singleDateTimeConversion(builder, cal, 'Y', syms); 1106 break; 1107 default: 1108 throw new UnknownFormatConversionException(String.valueOf(conversion)); 1109 } 1110 1111 if (digits > 0) 1112 { 1113 int newLen = builder.length(); 1114 int delta = newLen - oldLen; 1115 while (delta++ < digits) 1116 builder.insert(oldLen, '0'); 1117 } 1118 } 1119 1120 /** 1121 * Emit a date or time value. 1122 * 1123 * @param arg the date or time value. 1124 * @param flags the formatting flags to use. 1125 * @param width the width to use. 1126 * @param precision the precision to use. 1127 * @param conversion the conversion character. 1128 * @param subConversion the sub conversion character. 1129 * @throws IOException if the output stream throws an I/O error. 1130 */ 1131 private void dateTimeConversion(Object arg, int flags, int width, 1132 int precision, char conversion, 1133 char subConversion) 1134 throws IOException 1135 { 1136 noPrecision(precision); 1137 checkFlags(flags, 1138 FormattableFlags.LEFT_JUSTIFY | FormattableFlags.UPPERCASE, 1139 conversion); 1140 1141 Calendar cal; 1142 if (arg instanceof Calendar) 1143 cal = (Calendar) arg; 1144 else 1145 { 1146 Date date; 1147 if (arg instanceof Date) 1148 date = (Date) arg; 1149 else if (arg instanceof Long) 1150 date = new Date(((Long) arg).longValue()); 1151 else 1152 throw new IllegalFormatConversionException(conversion, 1153 arg.getClass()); 1154 if (fmtLocale == null) 1155 cal = Calendar.getInstance(); 1156 else 1157 cal = Calendar.getInstance(fmtLocale); 1158 cal.setTime(date); 1159 } 1160 1161 // We could try to be more efficient by computing this lazily. 1162 DateFormatSymbols syms; 1163 if (fmtLocale == null) 1164 syms = new DateFormatSymbols(); 1165 else 1166 syms = new DateFormatSymbols(fmtLocale); 1167 1168 CPStringBuilder result = new CPStringBuilder(); 1169 singleDateTimeConversion(result, cal, subConversion, syms); 1170 1171 genericFormat(result.toString(), flags, width, precision); 1172 } 1173 1174 /** 1175 * Advance the internal parsing index, and throw an exception 1176 * on overrun. 1177 * 1178 * @throws IllegalArgumentException on overrun. 1179 */ 1180 private void advance() 1181 { 1182 ++index; 1183 if (index >= length) 1184 { 1185 // FIXME: what exception here? 1186 throw new IllegalArgumentException(); 1187 } 1188 } 1189 1190 /** 1191 * Parse an integer appearing in the format string. Will return -1 1192 * if no integer was found. 1193 * 1194 * @return the parsed integer. 1195 */ 1196 private int parseInt() 1197 { 1198 int start = index; 1199 while (Character.isDigit(format.charAt(index))) 1200 advance(); 1201 if (start == index) 1202 return -1; 1203 return Integer.parseInt(format.substring(start, index)); 1204 } 1205 1206 /** 1207 * Parse the argument index. Returns -1 if there was no index, 0 if 1208 * we should re-use the previous index, and a positive integer to 1209 * indicate an absolute index. 1210 * 1211 * @return the parsed argument index. 1212 */ 1213 private int parseArgumentIndex() 1214 { 1215 int result = -1; 1216 int start = index; 1217 if (format.charAt(index) == '<') 1218 { 1219 result = 0; 1220 advance(); 1221 } 1222 else if (Character.isDigit(format.charAt(index))) 1223 { 1224 result = parseInt(); 1225 if (format.charAt(index) == '$') 1226 advance(); 1227 else 1228 { 1229 // Reset. 1230 index = start; 1231 result = -1; 1232 } 1233 } 1234 return result; 1235 } 1236 1237 /** 1238 * Parse a set of flags and return a bit mask of values from 1239 * FormattableFlags. Will throw an exception if a flag is 1240 * duplicated. 1241 * 1242 * @return the parsed flags. 1243 */ 1244 private int parseFlags() 1245 { 1246 int value = 0; 1247 int start = index; 1248 while (true) 1249 { 1250 int x = FLAGS.indexOf(format.charAt(index)); 1251 if (x == -1) 1252 break; 1253 int newValue = 1 << x; 1254 if ((value & newValue) != 0) 1255 throw new DuplicateFormatFlagsException(format.substring(start, 1256 index + 1)); 1257 value |= newValue; 1258 advance(); 1259 } 1260 return value; 1261 } 1262 1263 /** 1264 * Parse the width part of a format string. Returns -1 if no width 1265 * was specified. 1266 * 1267 * @return the parsed width. 1268 */ 1269 private int parseWidth() 1270 { 1271 return parseInt(); 1272 } 1273 1274 /** 1275 * If the current character is '.', parses the precision part of a 1276 * format string. Returns -1 if no precision was specified. 1277 * 1278 * @return the parsed precision. 1279 */ 1280 private int parsePrecision() 1281 { 1282 if (format.charAt(index) != '.') 1283 return -1; 1284 advance(); 1285 int precision = parseInt(); 1286 if (precision == -1) 1287 // FIXME 1288 throw new IllegalArgumentException(); 1289 return precision; 1290 } 1291 1292 /** 1293 * Outputs a formatted string based on the supplied specification, 1294 * <code>fmt</code>, and its arguments using the specified locale. 1295 * The locale of the formatter does not change as a result; the 1296 * specified locale is just used for this particular formatting 1297 * operation. If the locale is <code>null</code>, then no 1298 * localization is applied. 1299 * 1300 * @param loc the locale to use for this format. 1301 * @param fmt the format specification. 1302 * @param args the arguments to apply to the specification. 1303 * @throws IllegalFormatException if there is a problem with 1304 * the syntax of the format 1305 * specification or a mismatch 1306 * between it and the arguments. 1307 * @throws FormatterClosedException if the formatter is closed. 1308 */ 1309 public Formatter format(Locale loc, String fmt, Object... args) 1310 { 1311 if (closed) 1312 throw new FormatterClosedException(); 1313 1314 // Note the arguments are indexed starting at 1. 1315 int implicitArgumentIndex = 1; 1316 int previousArgumentIndex = 0; 1317 1318 try 1319 { 1320 fmtLocale = loc; 1321 format = fmt; 1322 length = format.length(); 1323 for (index = 0; index < length; ++index) 1324 { 1325 char c = format.charAt(index); 1326 if (c != '%') 1327 { 1328 out.append(c); 1329 continue; 1330 } 1331 1332 int start = index; 1333 advance(); 1334 1335 // We do the needed post-processing of this later, when we 1336 // determine whether an argument is actually needed by 1337 // this conversion. 1338 int argumentIndex = parseArgumentIndex(); 1339 1340 int flags = parseFlags(); 1341 int width = parseWidth(); 1342 int precision = parsePrecision(); 1343 char origConversion = format.charAt(index); 1344 char conversion = origConversion; 1345 if (Character.isUpperCase(conversion)) 1346 { 1347 flags |= FormattableFlags.UPPERCASE; 1348 conversion = Character.toLowerCase(conversion); 1349 } 1350 1351 Object argument = null; 1352 if (conversion == '%' || conversion == 'n') 1353 { 1354 if (argumentIndex != -1) 1355 { 1356 // FIXME: not sure about this. 1357 throw new UnknownFormatConversionException("FIXME"); 1358 } 1359 } 1360 else 1361 { 1362 if (argumentIndex == -1) 1363 argumentIndex = implicitArgumentIndex++; 1364 else if (argumentIndex == 0) 1365 argumentIndex = previousArgumentIndex; 1366 // Argument indices start at 1 but array indices at 0. 1367 --argumentIndex; 1368 if (args != null) 1369 { 1370 if (argumentIndex < 0 || argumentIndex >= args.length) 1371 throw new MissingFormatArgumentException(format.substring(start, index)); 1372 argument = args[argumentIndex]; 1373 } 1374 } 1375 1376 switch (conversion) 1377 { 1378 case 'b': 1379 booleanFormat(argument, flags, width, precision, 1380 origConversion); 1381 break; 1382 case 'h': 1383 hashCodeFormat(argument, flags, width, precision, 1384 origConversion); 1385 break; 1386 case 's': 1387 stringFormat(argument, flags, width, precision, 1388 origConversion); 1389 break; 1390 case 'c': 1391 characterFormat(argument, flags, width, precision, 1392 origConversion); 1393 break; 1394 case 'd': 1395 checkFlags(flags & FormattableFlags.UPPERCASE, 0, 'd'); 1396 decimalConversion(argument, flags, width, precision, 1397 origConversion); 1398 break; 1399 case 'o': 1400 checkFlags(flags & FormattableFlags.UPPERCASE, 0, 'o'); 1401 hexOrOctalConversion(argument, flags, width, precision, 8, 1402 origConversion); 1403 break; 1404 case 'x': 1405 hexOrOctalConversion(argument, flags, width, precision, 16, 1406 origConversion); 1407 case 'e': 1408 // scientificNotationConversion(); 1409 break; 1410 case 'f': 1411 // floatingDecimalConversion(); 1412 break; 1413 case 'g': 1414 // smartFloatingConversion(); 1415 break; 1416 case 'a': 1417 // hexFloatingConversion(); 1418 break; 1419 case 't': 1420 advance(); 1421 char subConversion = format.charAt(index); 1422 dateTimeConversion(argument, flags, width, precision, 1423 origConversion, subConversion); 1424 break; 1425 case '%': 1426 percentFormat(flags, width, precision); 1427 break; 1428 case 'n': 1429 newLineFormat(flags, width, precision); 1430 break; 1431 default: 1432 throw new UnknownFormatConversionException(String.valueOf(origConversion)); 1433 } 1434 } 1435 } 1436 catch (IOException exc) 1437 { 1438 ioException = exc; 1439 } 1440 return this; 1441 } 1442 1443 /** 1444 * Outputs a formatted string based on the supplied specification, 1445 * <code>fmt</code>, and its arguments using the formatter's locale. 1446 * 1447 * @param format the format specification. 1448 * @param args the arguments to apply to the specification. 1449 * @throws IllegalFormatException if there is a problem with 1450 * the syntax of the format 1451 * specification or a mismatch 1452 * between it and the arguments. 1453 * @throws FormatterClosedException if the formatter is closed. 1454 */ 1455 public Formatter format(String format, Object... args) 1456 { 1457 return format(locale, format, args); 1458 } 1459 1460 /** 1461 * Returns the last I/O exception thrown by the 1462 * <code>append()</code> operation of the underlying 1463 * output stream. 1464 * 1465 * @return the last I/O exception. 1466 */ 1467 public IOException ioException() 1468 { 1469 return ioException; 1470 } 1471 1472 /** 1473 * Returns the locale used by this formatter. 1474 * 1475 * @return the formatter's locale. 1476 * @throws FormatterClosedException if the formatter is closed. 1477 */ 1478 public Locale locale() 1479 { 1480 if (closed) 1481 throw new FormatterClosedException(); 1482 return locale; 1483 } 1484 1485 /** 1486 * Returns the output stream used by this formatter. 1487 * 1488 * @return the formatter's output stream. 1489 * @throws FormatterClosedException if the formatter is closed. 1490 */ 1491 public Appendable out() 1492 { 1493 if (closed) 1494 throw new FormatterClosedException(); 1495 return out; 1496 } 1497 1498 /** 1499 * Returns the result of applying {@link Object#toString()} 1500 * to the underlying output stream. The results returned 1501 * depend on the particular {@link Appendable} being used. 1502 * For example, a {@link StringBuilder} will return the 1503 * formatted output but an I/O stream will not. 1504 * 1505 * @throws FormatterClosedException if the formatter is closed. 1506 */ 1507 public String toString() 1508 { 1509 if (closed) 1510 throw new FormatterClosedException(); 1511 return out.toString(); 1512 } 1513}