Merge with trank @ 137446
[official-gcc.git] / libjava / classpath / java / util / Formatter.java
blob82130782e0f2a3eeac827d12322ebf1aa8178d13
1 /* Formatter.java -- printf-style formatting
2 Copyright (C) 2005 Free Software Foundation, Inc.
4 This file is part of GNU Classpath.
6 GNU Classpath is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2, or (at your option)
9 any later version.
11 GNU Classpath is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with GNU Classpath; see the file COPYING. If not, write to the
18 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 02110-1301 USA.
21 Linking this library statically or dynamically with other modules is
22 making a combined work based on this library. Thus, the terms and
23 conditions of the GNU General Public License cover the whole
24 combination.
26 As a special exception, the copyright holders of this library give you
27 permission to link this library with independent modules to produce an
28 executable, regardless of the license terms of these independent
29 modules, and to copy and distribute the resulting executable under
30 terms of your choice, provided that you also meet, for each linked
31 independent module, the terms and conditions of the license of that
32 module. An independent module is a module which is not derived from
33 or based on this library. If you modify this library, you may extend
34 this exception to your version of the library, but you are not
35 obligated to do so. If you do not wish to do so, delete this
36 exception statement from your version. */
39 package java.util;
41 import java.io.Closeable;
42 import java.io.File;
43 import java.io.FileNotFoundException;
44 import java.io.FileOutputStream;
45 import java.io.Flushable;
46 import java.io.IOException;
47 import java.io.OutputStream;
48 import java.io.OutputStreamWriter;
49 import java.io.PrintStream;
50 import java.io.UnsupportedEncodingException;
51 import java.math.BigInteger;
52 import java.text.DateFormatSymbols;
53 import java.text.DecimalFormatSymbols;
55 import gnu.classpath.SystemProperties;
57 /**
58 * <p>
59 * A Java formatter for <code>printf</code>-style format strings,
60 * as seen in the C programming language. This differs from the
61 * C interpretation of such strings by performing much stricter
62 * checking of format specifications and their corresponding
63 * arguments. While unknown conversions will be ignored in C,
64 * and invalid conversions will only produce compiler warnings,
65 * the Java version utilises a full range of run-time exceptions to
66 * handle these cases. The Java version is also more customisable
67 * by virtue of the provision of the {@link Formattable} interface,
68 * which allows an arbitrary class to be formatted by the formatter.
69 * </p>
70 * <p>
71 * The formatter is accessible by more convienient static methods.
72 * For example, streams now have appropriate format methods
73 * (the equivalent of <code>fprintf</code>) as do <code>String</code>
74 * objects (the equivalent of <code>sprintf</code>).
75 * </p>
76 * <p>
77 * <strong>Note</strong>: the formatter is not thread-safe. For
78 * multi-threaded access, external synchronization should be provided.
79 * </p>
81 * @author Tom Tromey (tromey@redhat.com)
82 * @author Andrew John Hughes (gnu_andrew@member.fsf.org)
83 * @since 1.5
85 public final class Formatter
86 implements Closeable, Flushable
89 /**
90 * The output of the formatter.
92 private Appendable out;
94 /**
95 * The locale used by the formatter.
97 private Locale locale;
99 /**
100 * Whether or not the formatter is closed.
102 private boolean closed;
105 * The last I/O exception thrown by the output stream.
107 private IOException ioException;
109 // Some state used when actually formatting.
111 * The format string.
113 private String format;
116 * The current index into the string.
118 private int index;
121 * The length of the format string.
123 private int length;
126 * The formatting locale.
128 private Locale fmtLocale;
130 // Note that we include '-' twice. The flags are ordered to
131 // correspond to the values in FormattableFlags, and there is no
132 // flag (in the sense of this field used when parsing) for
133 // UPPERCASE; the second '-' serves as a placeholder.
135 * A string used to index into the formattable flags.
137 private static final String FLAGS = "--#+ 0,(";
140 * The system line separator.
142 private static final String lineSeparator
143 = SystemProperties.getProperty("line.separator");
146 * The type of numeric output format for a {@link BigDecimal}.
148 public enum BigDecimalLayoutForm
150 DECIMAL_FLOAT,
151 SCIENTIFIC
155 * Constructs a new <code>Formatter</code> using the default
156 * locale and a {@link StringBuilder} as the output stream.
158 public Formatter()
160 this(null, Locale.getDefault());
164 * Constructs a new <code>Formatter</code> using the specified
165 * locale and a {@link StringBuilder} as the output stream.
166 * If the locale is <code>null</code>, then no localization
167 * is applied.
169 * @param loc the locale to use.
171 public Formatter(Locale loc)
173 this(null, loc);
177 * Constructs a new <code>Formatter</code> using the default
178 * locale and the specified output stream.
180 * @param app the output stream to use.
182 public Formatter(Appendable app)
184 this(app, Locale.getDefault());
188 * Constructs a new <code>Formatter</code> using the specified
189 * locale and the specified output stream. If the locale is
190 * <code>null</code>, then no localization is applied.
192 * @param app the output stream to use.
193 * @param loc the locale to use.
195 public Formatter(Appendable app, Locale loc)
197 this.out = app == null ? new StringBuilder() : app;
198 this.locale = loc;
202 * Constructs a new <code>Formatter</code> using the default
203 * locale and character set, with the specified file as the
204 * output stream.
206 * @param file the file to use for output.
207 * @throws FileNotFoundException if the file does not exist
208 * and can not be created.
209 * @throws SecurityException if a security manager is present
210 * and doesn't allow writing to the file.
212 public Formatter(File file)
213 throws FileNotFoundException
215 this(new OutputStreamWriter(new FileOutputStream(file)));
219 * Constructs a new <code>Formatter</code> using the default
220 * locale, with the specified file as the output stream
221 * and the supplied character set.
223 * @param file the file to use for output.
224 * @param charset the character set to use for output.
225 * @throws FileNotFoundException if the file does not exist
226 * and can not be created.
227 * @throws SecurityException if a security manager is present
228 * and doesn't allow writing to the file.
229 * @throws UnsupportedEncodingException if the supplied character
230 * set is not supported.
232 public Formatter(File file, String charset)
233 throws FileNotFoundException, UnsupportedEncodingException
235 this(file, charset, Locale.getDefault());
239 * Constructs a new <code>Formatter</code> using the specified
240 * file as the output stream with the supplied character set
241 * and locale. If the locale is <code>null</code>, then no
242 * localization is applied.
244 * @param file the file to use for output.
245 * @param charset the character set to use for output.
246 * @param loc the locale to use.
247 * @throws FileNotFoundException if the file does not exist
248 * and can not be created.
249 * @throws SecurityException if a security manager is present
250 * and doesn't allow writing to the file.
251 * @throws UnsupportedEncodingException if the supplied character
252 * set is not supported.
254 public Formatter(File file, String charset, Locale loc)
255 throws FileNotFoundException, UnsupportedEncodingException
257 this(new OutputStreamWriter(new FileOutputStream(file), charset),
258 loc);
262 * Constructs a new <code>Formatter</code> using the default
263 * locale and character set, with the specified output stream.
265 * @param out the output stream to use.
267 public Formatter(OutputStream out)
269 this(new OutputStreamWriter(out));
273 * Constructs a new <code>Formatter</code> using the default
274 * locale, with the specified file output stream and the
275 * supplied character set.
277 * @param out the output stream.
278 * @param charset the character set to use for output.
279 * @throws UnsupportedEncodingException if the supplied character
280 * set is not supported.
282 public Formatter(OutputStream out, String charset)
283 throws UnsupportedEncodingException
285 this(out, charset, Locale.getDefault());
289 * Constructs a new <code>Formatter</code> using the specified
290 * output stream with the supplied character set and locale.
291 * If the locale is <code>null</code>, then no localization is
292 * applied.
294 * @param out the output stream.
295 * @param charset the character set to use for output.
296 * @param loc the locale to use.
297 * @throws UnsupportedEncodingException if the supplied character
298 * set is not supported.
300 public Formatter(OutputStream out, String charset, Locale loc)
301 throws UnsupportedEncodingException
303 this(new OutputStreamWriter(out, charset), loc);
307 * Constructs a new <code>Formatter</code> using the default
308 * locale with the specified output stream. The character
309 * set used is that of the output stream.
311 * @param out the output stream to use.
313 public Formatter(PrintStream out)
315 this((Appendable) out);
319 * Constructs a new <code>Formatter</code> using the default
320 * locale and character set, with the specified file as the
321 * output stream.
323 * @param file the file to use for output.
324 * @throws FileNotFoundException if the file does not exist
325 * and can not be created.
326 * @throws SecurityException if a security manager is present
327 * and doesn't allow writing to the file.
329 public Formatter(String file) throws FileNotFoundException
331 this(new OutputStreamWriter(new FileOutputStream(file)));
335 * Constructs a new <code>Formatter</code> using the default
336 * locale, with the specified file as the output stream
337 * and the supplied character set.
339 * @param file the file to use for output.
340 * @param charset the character set to use for output.
341 * @throws FileNotFoundException if the file does not exist
342 * and can not be created.
343 * @throws SecurityException if a security manager is present
344 * and doesn't allow writing to the file.
345 * @throws UnsupportedEncodingException if the supplied character
346 * set is not supported.
348 public Formatter(String file, String charset)
349 throws FileNotFoundException, UnsupportedEncodingException
351 this(file, charset, Locale.getDefault());
355 * Constructs a new <code>Formatter</code> using the specified
356 * file as the output stream with the supplied character set
357 * and locale. If the locale is <code>null</code>, then no
358 * localization is applied.
360 * @param file the file to use for output.
361 * @param charset the character set to use for output.
362 * @param loc the locale to use.
363 * @throws FileNotFoundException if the file does not exist
364 * and can not be created.
365 * @throws SecurityException if a security manager is present
366 * and doesn't allow writing to the file.
367 * @throws UnsupportedEncodingException if the supplied character
368 * set is not supported.
370 public Formatter(String file, String charset, Locale loc)
371 throws FileNotFoundException, UnsupportedEncodingException
373 this(new OutputStreamWriter(new FileOutputStream(file), charset),
374 loc);
378 * Closes the formatter, so as to release used resources.
379 * If the underlying output stream supports the {@link Closeable}
380 * interface, then this is also closed. Attempts to use
381 * a formatter instance, via any method other than
382 * {@link #ioException()}, after closure results in a
383 * {@link FormatterClosedException}.
385 public void close()
387 if (closed)
388 return;
391 if (out instanceof Closeable)
392 ((Closeable) out).close();
394 catch (IOException _)
396 // FIXME: do we ignore these or do we set ioException?
397 // The docs seem to indicate that we should ignore.
399 closed = true;
403 * Flushes the formatter, writing any cached data to the output
404 * stream. If the underlying output stream supports the
405 * {@link Flushable} interface, it is also flushed.
407 * @throws FormatterClosedException if the formatter is closed.
409 public void flush()
411 if (closed)
412 throw new FormatterClosedException();
415 if (out instanceof Flushable)
416 ((Flushable) out).flush();
418 catch (IOException _)
420 // FIXME: do we ignore these or do we set ioException?
421 // The docs seem to indicate that we should ignore.
426 * Return the name corresponding to a flag.
428 * @param flags the flag to return the name of.
429 * @return the name of the flag.
431 private String getName(int flags)
433 // FIXME: do we want all the flags in here?
434 // Or should we redo how this is reported?
435 int bit = Integer.numberOfTrailingZeros(flags);
436 return FLAGS.substring(bit, bit + 1);
440 * Verify the flags passed to a conversion.
442 * @param flags the flags to verify.
443 * @param allowed the allowed flags mask.
444 * @param conversion the conversion character.
446 private void checkFlags(int flags, int allowed, char conversion)
448 flags &= ~allowed;
449 if (flags != 0)
450 throw new FormatFlagsConversionMismatchException(getName(flags),
451 conversion);
455 * Throw an exception if a precision was specified.
457 * @param precision the precision value (-1 indicates not specified).
459 private void noPrecision(int precision)
461 if (precision != -1)
462 throw new IllegalFormatPrecisionException(precision);
466 * Apply the numeric localization algorithm to a StringBuilder.
468 * @param builder the builder to apply to.
469 * @param flags the formatting flags to use.
470 * @param width the width of the numeric value.
471 * @param isNegative true if the value is negative.
473 private void applyLocalization(StringBuilder builder, int flags, int width,
474 boolean isNegative)
476 DecimalFormatSymbols dfsyms;
477 if (fmtLocale == null)
478 dfsyms = new DecimalFormatSymbols();
479 else
480 dfsyms = new DecimalFormatSymbols(fmtLocale);
482 // First replace each digit.
483 char zeroDigit = dfsyms.getZeroDigit();
484 int decimalOffset = -1;
485 for (int i = builder.length() - 1; i >= 0; --i)
487 char c = builder.charAt(i);
488 if (c >= '0' && c <= '9')
489 builder.setCharAt(i, (char) (c - '0' + zeroDigit));
490 else if (c == '.')
492 assert decimalOffset == -1;
493 decimalOffset = i;
497 // Localize the decimal separator.
498 if (decimalOffset != -1)
500 builder.deleteCharAt(decimalOffset);
501 builder.insert(decimalOffset, dfsyms.getDecimalSeparator());
504 // Insert the grouping separators.
505 if ((flags & FormattableFlags.COMMA) != 0)
507 char groupSeparator = dfsyms.getGroupingSeparator();
508 int groupSize = 3; // FIXME
509 int offset = (decimalOffset == -1) ? builder.length() : decimalOffset;
510 // We use '>' because we don't want to insert a separator
511 // before the first digit.
512 for (int i = offset - groupSize; i > 0; i -= groupSize)
513 builder.insert(i, groupSeparator);
516 if ((flags & FormattableFlags.ZERO) != 0)
518 // Zero fill. Note that according to the algorithm we do not
519 // insert grouping separators here.
520 for (int i = width - builder.length(); i > 0; --i)
521 builder.insert(0, zeroDigit);
524 if (isNegative)
526 if ((flags & FormattableFlags.PAREN) != 0)
528 builder.insert(0, '(');
529 builder.append(')');
531 else
532 builder.insert(0, '-');
534 else if ((flags & FormattableFlags.PLUS) != 0)
535 builder.insert(0, '+');
536 else if ((flags & FormattableFlags.SPACE) != 0)
537 builder.insert(0, ' ');
541 * A helper method that handles emitting a String after applying
542 * precision, width, justification, and upper case flags.
544 * @param arg the string to emit.
545 * @param flags the formatting flags to use.
546 * @param width the width to use.
547 * @param precision the precision to use.
548 * @throws IOException if the output stream throws an I/O error.
550 private void genericFormat(String arg, int flags, int width, int precision)
551 throws IOException
553 if ((flags & FormattableFlags.UPPERCASE) != 0)
555 if (fmtLocale == null)
556 arg = arg.toUpperCase();
557 else
558 arg = arg.toUpperCase(fmtLocale);
561 if (precision >= 0 && arg.length() > precision)
562 arg = arg.substring(0, precision);
564 boolean leftJustify = (flags & FormattableFlags.LEFT_JUSTIFY) != 0;
565 if (leftJustify && width == -1)
566 throw new MissingFormatWidthException("fixme");
567 if (! leftJustify && arg.length() < width)
569 for (int i = width - arg.length(); i > 0; --i)
570 out.append(' ');
572 out.append(arg);
573 if (leftJustify && arg.length() < width)
575 for (int i = width - arg.length(); i > 0; --i)
576 out.append(' ');
580 /**
581 * Emit a boolean.
583 * @param arg the boolean to emit.
584 * @param flags the formatting flags to use.
585 * @param width the width to use.
586 * @param precision the precision to use.
587 * @param conversion the conversion character.
588 * @throws IOException if the output stream throws an I/O error.
590 private void booleanFormat(Object arg, int flags, int width, int precision,
591 char conversion)
592 throws IOException
594 checkFlags(flags,
595 FormattableFlags.LEFT_JUSTIFY | FormattableFlags.UPPERCASE,
596 conversion);
597 String result;
598 if (arg instanceof Boolean)
599 result = String.valueOf((Boolean) arg);
600 else
601 result = arg == null ? "false" : "true";
602 genericFormat(result, flags, width, precision);
605 /**
606 * Emit a hash code.
608 * @param arg the hash code to emit.
609 * @param flags the formatting flags to use.
610 * @param width the width to use.
611 * @param precision the precision to use.
612 * @param conversion the conversion character.
613 * @throws IOException if the output stream throws an I/O error.
615 private void hashCodeFormat(Object arg, int flags, int width, int precision,
616 char conversion)
617 throws IOException
619 checkFlags(flags,
620 FormattableFlags.LEFT_JUSTIFY | FormattableFlags.UPPERCASE,
621 conversion);
622 genericFormat(arg == null ? "null" : Integer.toHexString(arg.hashCode()),
623 flags, width, precision);
626 /**
627 * Emit a String or Formattable conversion.
629 * @param arg the String or Formattable to emit.
630 * @param flags the formatting flags to use.
631 * @param width the width to use.
632 * @param precision the precision to use.
633 * @param conversion the conversion character.
634 * @throws IOException if the output stream throws an I/O error.
636 private void stringFormat(Object arg, int flags, int width, int precision,
637 char conversion)
638 throws IOException
640 if (arg instanceof Formattable)
642 checkFlags(flags,
643 (FormattableFlags.LEFT_JUSTIFY
644 | FormattableFlags.UPPERCASE
645 | FormattableFlags.ALTERNATE),
646 conversion);
647 Formattable fmt = (Formattable) arg;
648 fmt.formatTo(this, flags, width, precision);
650 else
652 checkFlags(flags,
653 FormattableFlags.LEFT_JUSTIFY | FormattableFlags.UPPERCASE,
654 conversion);
655 genericFormat(arg == null ? "null" : arg.toString(), flags, width,
656 precision);
660 /**
661 * Emit a character.
663 * @param arg the character to emit.
664 * @param flags the formatting flags to use.
665 * @param width the width to use.
666 * @param precision the precision to use.
667 * @param conversion the conversion character.
668 * @throws IOException if the output stream throws an I/O error.
670 private void characterFormat(Object arg, int flags, int width, int precision,
671 char conversion)
672 throws IOException
674 checkFlags(flags,
675 FormattableFlags.LEFT_JUSTIFY | FormattableFlags.UPPERCASE,
676 conversion);
677 noPrecision(precision);
679 int theChar;
680 if (arg instanceof Character)
681 theChar = ((Character) arg).charValue();
682 else if (arg instanceof Byte)
683 theChar = (char) (((Byte) arg).byteValue ());
684 else if (arg instanceof Short)
685 theChar = (char) (((Short) arg).shortValue ());
686 else if (arg instanceof Integer)
688 theChar = ((Integer) arg).intValue();
689 if (! Character.isValidCodePoint(theChar))
690 throw new IllegalFormatCodePointException(theChar);
692 else
693 throw new IllegalFormatConversionException(conversion, arg.getClass());
694 String result = new String(Character.toChars(theChar));
695 genericFormat(result, flags, width, precision);
698 /**
699 * Emit a '%'.
701 * @param flags the formatting flags to use.
702 * @param width the width to use.
703 * @param precision the precision to use.
704 * @throws IOException if the output stream throws an I/O error.
706 private void percentFormat(int flags, int width, int precision)
707 throws IOException
709 checkFlags(flags, FormattableFlags.LEFT_JUSTIFY, '%');
710 noPrecision(precision);
711 genericFormat("%", flags, width, precision);
714 /**
715 * Emit a newline.
717 * @param flags the formatting flags to use.
718 * @param width the width to use.
719 * @param precision the precision to use.
720 * @throws IOException if the output stream throws an I/O error.
722 private void newLineFormat(int flags, int width, int precision)
723 throws IOException
725 checkFlags(flags, 0, 'n');
726 noPrecision(precision);
727 if (width != -1)
728 throw new IllegalFormatWidthException(width);
729 genericFormat(lineSeparator, flags, width, precision);
733 * Helper method to do initial formatting and checking for integral
734 * conversions.
736 * @param arg the formatted argument.
737 * @param flags the formatting flags to use.
738 * @param width the width to use.
739 * @param precision the precision to use.
740 * @param radix the radix of the number.
741 * @param conversion the conversion character.
742 * @return the result.
744 private StringBuilder basicIntegralConversion(Object arg, int flags,
745 int width, int precision,
746 int radix, char conversion)
748 assert radix == 8 || radix == 10 || radix == 16;
749 noPrecision(precision);
751 // Some error checking.
752 if ((flags & FormattableFlags.PLUS) != 0
753 && (flags & FormattableFlags.SPACE) != 0)
754 throw new IllegalFormatFlagsException(getName(flags));
756 if ((flags & FormattableFlags.LEFT_JUSTIFY) != 0 && width == -1)
757 throw new MissingFormatWidthException("fixme");
759 // Do the base translation of the value to a string.
760 String result;
761 int basicFlags = (FormattableFlags.LEFT_JUSTIFY
762 // We already handled any possible error when
763 // parsing.
764 | FormattableFlags.UPPERCASE
765 | FormattableFlags.ZERO);
766 if (radix == 10)
767 basicFlags |= (FormattableFlags.PLUS
768 | FormattableFlags.SPACE
769 | FormattableFlags.COMMA
770 | FormattableFlags.PAREN);
771 else
772 basicFlags |= FormattableFlags.ALTERNATE;
774 if (arg instanceof BigInteger)
776 checkFlags(flags,
777 (basicFlags
778 | FormattableFlags.PLUS
779 | FormattableFlags.SPACE
780 | FormattableFlags.PAREN),
781 conversion);
782 BigInteger bi = (BigInteger) arg;
783 result = bi.toString(radix);
785 else if (arg instanceof Number
786 && ! (arg instanceof Float)
787 && ! (arg instanceof Double))
789 checkFlags(flags, basicFlags, conversion);
790 long value = ((Number) arg).longValue ();
791 if (radix == 8)
792 result = Long.toOctalString(value);
793 else if (radix == 16)
794 result = Long.toHexString(value);
795 else
796 result = Long.toString(value);
798 else
799 throw new IllegalFormatConversionException(conversion, arg.getClass());
801 return new StringBuilder(result);
804 /**
805 * Emit a hex or octal value.
807 * @param arg the hexadecimal or octal value.
808 * @param flags the formatting flags to use.
809 * @param width the width to use.
810 * @param precision the precision to use.
811 * @param radix the radix of the number.
812 * @param conversion the conversion character.
813 * @throws IOException if the output stream throws an I/O error.
815 private void hexOrOctalConversion(Object arg, int flags, int width,
816 int precision, int radix,
817 char conversion)
818 throws IOException
820 assert radix == 8 || radix == 16;
822 StringBuilder builder = basicIntegralConversion(arg, flags, width,
823 precision, radix,
824 conversion);
825 int insertPoint = 0;
827 // Insert the sign.
828 if (builder.charAt(0) == '-')
830 // Already inserted. Note that we don't insert a sign, since
831 // the only case where it is needed it BigInteger, and it has
832 // already been inserted by toString.
833 ++insertPoint;
835 else if ((flags & FormattableFlags.PLUS) != 0)
837 builder.insert(insertPoint, '+');
838 ++insertPoint;
840 else if ((flags & FormattableFlags.SPACE) != 0)
842 builder.insert(insertPoint, ' ');
843 ++insertPoint;
846 // Insert the radix prefix.
847 if ((flags & FormattableFlags.ALTERNATE) != 0)
849 builder.insert(insertPoint, radix == 8 ? "0" : "0x");
850 insertPoint += radix == 8 ? 1 : 2;
853 // Now justify the result.
854 int resultWidth = builder.length();
855 if (resultWidth < width)
857 char fill = ((flags & FormattableFlags.ZERO) != 0) ? '0' : ' ';
858 if ((flags & FormattableFlags.LEFT_JUSTIFY) != 0)
860 // Left justify.
861 if (fill == ' ')
862 insertPoint = builder.length();
864 else
866 // Right justify. Insert spaces before the radix prefix
867 // and sign.
868 insertPoint = 0;
870 while (resultWidth++ < width)
871 builder.insert(insertPoint, fill);
874 String result = builder.toString();
875 if ((flags & FormattableFlags.UPPERCASE) != 0)
877 if (fmtLocale == null)
878 result = result.toUpperCase();
879 else
880 result = result.toUpperCase(fmtLocale);
883 out.append(result);
886 /**
887 * Emit a decimal value.
889 * @param arg the hexadecimal or octal value.
890 * @param flags the formatting flags to use.
891 * @param width the width to use.
892 * @param precision the precision to use.
893 * @param conversion the conversion character.
894 * @throws IOException if the output stream throws an I/O error.
896 private void decimalConversion(Object arg, int flags, int width,
897 int precision, char conversion)
898 throws IOException
900 StringBuilder builder = basicIntegralConversion(arg, flags, width,
901 precision, 10,
902 conversion);
903 boolean isNegative = false;
904 if (builder.charAt(0) == '-')
906 // Sign handling is done during localization.
907 builder.deleteCharAt(0);
908 isNegative = true;
911 applyLocalization(builder, flags, width, isNegative);
912 genericFormat(builder.toString(), flags, width, precision);
915 /**
916 * Emit a single date or time conversion to a StringBuilder.
918 * @param builder the builder to write to.
919 * @param cal the calendar to use in the conversion.
920 * @param conversion the formatting character to specify the type of data.
921 * @param syms the date formatting symbols.
923 private void singleDateTimeConversion(StringBuilder builder, Calendar cal,
924 char conversion,
925 DateFormatSymbols syms)
927 int oldLen = builder.length();
928 int digits = -1;
929 switch (conversion)
931 case 'H':
932 builder.append(cal.get(Calendar.HOUR_OF_DAY));
933 digits = 2;
934 break;
935 case 'I':
936 builder.append(cal.get(Calendar.HOUR));
937 digits = 2;
938 break;
939 case 'k':
940 builder.append(cal.get(Calendar.HOUR_OF_DAY));
941 break;
942 case 'l':
943 builder.append(cal.get(Calendar.HOUR));
944 break;
945 case 'M':
946 builder.append(cal.get(Calendar.MINUTE));
947 digits = 2;
948 break;
949 case 'S':
950 builder.append(cal.get(Calendar.SECOND));
951 digits = 2;
952 break;
953 case 'N':
954 // FIXME: nanosecond ...
955 digits = 9;
956 break;
957 case 'p':
959 int ampm = cal.get(Calendar.AM_PM);
960 builder.append(syms.getAmPmStrings()[ampm]);
962 break;
963 case 'z':
965 int zone = cal.get(Calendar.ZONE_OFFSET) / (1000 * 60);
966 builder.append(zone);
967 digits = 4;
968 // Skip the '-' sign.
969 if (zone < 0)
970 ++oldLen;
972 break;
973 case 'Z':
975 // FIXME: DST?
976 int zone = cal.get(Calendar.ZONE_OFFSET) / (1000 * 60 * 60);
977 String[][] zs = syms.getZoneStrings();
978 builder.append(zs[zone + 12][1]);
980 break;
981 case 's':
983 long val = cal.getTime().getTime();
984 builder.append(val / 1000);
986 break;
987 case 'Q':
989 long val = cal.getTime().getTime();
990 builder.append(val);
992 break;
993 case 'B':
995 int month = cal.get(Calendar.MONTH);
996 builder.append(syms.getMonths()[month]);
998 break;
999 case 'b':
1000 case 'h':
1002 int month = cal.get(Calendar.MONTH);
1003 builder.append(syms.getShortMonths()[month]);
1005 break;
1006 case 'A':
1008 int day = cal.get(Calendar.DAY_OF_WEEK);
1009 builder.append(syms.getWeekdays()[day]);
1011 break;
1012 case 'a':
1014 int day = cal.get(Calendar.DAY_OF_WEEK);
1015 builder.append(syms.getShortWeekdays()[day]);
1017 break;
1018 case 'C':
1019 builder.append(cal.get(Calendar.YEAR) / 100);
1020 digits = 2;
1021 break;
1022 case 'Y':
1023 builder.append(cal.get(Calendar.YEAR));
1024 digits = 4;
1025 break;
1026 case 'y':
1027 builder.append(cal.get(Calendar.YEAR) % 100);
1028 digits = 2;
1029 break;
1030 case 'j':
1031 builder.append(cal.get(Calendar.DAY_OF_YEAR));
1032 digits = 3;
1033 break;
1034 case 'm':
1035 builder.append(cal.get(Calendar.MONTH) + 1);
1036 digits = 2;
1037 break;
1038 case 'd':
1039 builder.append(cal.get(Calendar.DAY_OF_MONTH));
1040 digits = 2;
1041 break;
1042 case 'e':
1043 builder.append(cal.get(Calendar.DAY_OF_MONTH));
1044 break;
1045 case 'R':
1046 singleDateTimeConversion(builder, cal, 'H', syms);
1047 builder.append(':');
1048 singleDateTimeConversion(builder, cal, 'M', syms);
1049 break;
1050 case 'T':
1051 singleDateTimeConversion(builder, cal, 'H', syms);
1052 builder.append(':');
1053 singleDateTimeConversion(builder, cal, 'M', syms);
1054 builder.append(':');
1055 singleDateTimeConversion(builder, cal, 'S', syms);
1056 break;
1057 case 'r':
1058 singleDateTimeConversion(builder, cal, 'I', syms);
1059 builder.append(':');
1060 singleDateTimeConversion(builder, cal, 'M', syms);
1061 builder.append(':');
1062 singleDateTimeConversion(builder, cal, 'S', syms);
1063 builder.append(' ');
1064 singleDateTimeConversion(builder, cal, 'p', syms);
1065 break;
1066 case 'D':
1067 singleDateTimeConversion(builder, cal, 'm', syms);
1068 builder.append('/');
1069 singleDateTimeConversion(builder, cal, 'd', syms);
1070 builder.append('/');
1071 singleDateTimeConversion(builder, cal, 'y', syms);
1072 break;
1073 case 'F':
1074 singleDateTimeConversion(builder, cal, 'Y', syms);
1075 builder.append('-');
1076 singleDateTimeConversion(builder, cal, 'm', syms);
1077 builder.append('-');
1078 singleDateTimeConversion(builder, cal, 'd', syms);
1079 break;
1080 case 'c':
1081 singleDateTimeConversion(builder, cal, 'a', syms);
1082 builder.append(' ');
1083 singleDateTimeConversion(builder, cal, 'b', syms);
1084 builder.append(' ');
1085 singleDateTimeConversion(builder, cal, 'd', syms);
1086 builder.append(' ');
1087 singleDateTimeConversion(builder, cal, 'T', syms);
1088 builder.append(' ');
1089 singleDateTimeConversion(builder, cal, 'Z', syms);
1090 builder.append(' ');
1091 singleDateTimeConversion(builder, cal, 'Y', syms);
1092 break;
1093 default:
1094 throw new UnknownFormatConversionException(String.valueOf(conversion));
1097 if (digits > 0)
1099 int newLen = builder.length();
1100 int delta = newLen - oldLen;
1101 while (delta++ < digits)
1102 builder.insert(oldLen, '0');
1107 * Emit a date or time value.
1109 * @param arg the date or time value.
1110 * @param flags the formatting flags to use.
1111 * @param width the width to use.
1112 * @param precision the precision to use.
1113 * @param conversion the conversion character.
1114 * @param subConversion the sub conversion character.
1115 * @throws IOException if the output stream throws an I/O error.
1117 private void dateTimeConversion(Object arg, int flags, int width,
1118 int precision, char conversion,
1119 char subConversion)
1120 throws IOException
1122 noPrecision(precision);
1123 checkFlags(flags,
1124 FormattableFlags.LEFT_JUSTIFY | FormattableFlags.UPPERCASE,
1125 conversion);
1127 Calendar cal;
1128 if (arg instanceof Calendar)
1129 cal = (Calendar) arg;
1130 else
1132 Date date;
1133 if (arg instanceof Date)
1134 date = (Date) arg;
1135 else if (arg instanceof Long)
1136 date = new Date(((Long) arg).longValue());
1137 else
1138 throw new IllegalFormatConversionException(conversion,
1139 arg.getClass());
1140 if (fmtLocale == null)
1141 cal = Calendar.getInstance();
1142 else
1143 cal = Calendar.getInstance(fmtLocale);
1144 cal.setTime(date);
1147 // We could try to be more efficient by computing this lazily.
1148 DateFormatSymbols syms;
1149 if (fmtLocale == null)
1150 syms = new DateFormatSymbols();
1151 else
1152 syms = new DateFormatSymbols(fmtLocale);
1154 StringBuilder result = new StringBuilder();
1155 singleDateTimeConversion(result, cal, subConversion, syms);
1157 genericFormat(result.toString(), flags, width, precision);
1161 * Advance the internal parsing index, and throw an exception
1162 * on overrun.
1164 * @throws IllegalArgumentException on overrun.
1166 private void advance()
1168 ++index;
1169 if (index >= length)
1171 // FIXME: what exception here?
1172 throw new IllegalArgumentException();
1177 * Parse an integer appearing in the format string. Will return -1
1178 * if no integer was found.
1180 * @return the parsed integer.
1182 private int parseInt()
1184 int start = index;
1185 while (Character.isDigit(format.charAt(index)))
1186 advance();
1187 if (start == index)
1188 return -1;
1189 return Integer.decode(format.substring(start, index));
1193 * Parse the argument index. Returns -1 if there was no index, 0 if
1194 * we should re-use the previous index, and a positive integer to
1195 * indicate an absolute index.
1197 * @return the parsed argument index.
1199 private int parseArgumentIndex()
1201 int result = -1;
1202 int start = index;
1203 if (format.charAt(index) == '<')
1205 result = 0;
1206 advance();
1208 else if (Character.isDigit(format.charAt(index)))
1210 result = parseInt();
1211 if (format.charAt(index) == '$')
1212 advance();
1213 else
1215 // Reset.
1216 index = start;
1217 result = -1;
1220 return result;
1224 * Parse a set of flags and return a bit mask of values from
1225 * FormattableFlags. Will throw an exception if a flag is
1226 * duplicated.
1228 * @return the parsed flags.
1230 private int parseFlags()
1232 int value = 0;
1233 int start = index;
1234 while (true)
1236 int x = FLAGS.indexOf(format.charAt(index));
1237 if (x == -1)
1238 break;
1239 int newValue = 1 << x;
1240 if ((value & newValue) != 0)
1241 throw new DuplicateFormatFlagsException(format.substring(start,
1242 index + 1));
1243 value |= newValue;
1244 advance();
1246 return value;
1250 * Parse the width part of a format string. Returns -1 if no width
1251 * was specified.
1253 * @return the parsed width.
1255 private int parseWidth()
1257 return parseInt();
1261 * If the current character is '.', parses the precision part of a
1262 * format string. Returns -1 if no precision was specified.
1264 * @return the parsed precision.
1266 private int parsePrecision()
1268 if (format.charAt(index) != '.')
1269 return -1;
1270 advance();
1271 int precision = parseInt();
1272 if (precision == -1)
1273 // FIXME
1274 throw new IllegalArgumentException();
1275 return precision;
1279 * Outputs a formatted string based on the supplied specification,
1280 * <code>fmt</code>, and its arguments using the specified locale.
1281 * The locale of the formatter does not change as a result; the
1282 * specified locale is just used for this particular formatting
1283 * operation. If the locale is <code>null</code>, then no
1284 * localization is applied.
1286 * @param loc the locale to use for this format.
1287 * @param fmt the format specification.
1288 * @param args the arguments to apply to the specification.
1289 * @throws IllegalFormatException if there is a problem with
1290 * the syntax of the format
1291 * specification or a mismatch
1292 * between it and the arguments.
1293 * @throws FormatterClosedException if the formatter is closed.
1295 public Formatter format(Locale loc, String fmt, Object... args)
1297 if (closed)
1298 throw new FormatterClosedException();
1300 // Note the arguments are indexed starting at 1.
1301 int implicitArgumentIndex = 1;
1302 int previousArgumentIndex = 0;
1306 fmtLocale = loc;
1307 format = fmt;
1308 length = format.length();
1309 for (index = 0; index < length; ++index)
1311 char c = format.charAt(index);
1312 if (c != '%')
1314 out.append(c);
1315 continue;
1318 int start = index;
1319 advance();
1321 // We do the needed post-processing of this later, when we
1322 // determine whether an argument is actually needed by
1323 // this conversion.
1324 int argumentIndex = parseArgumentIndex();
1326 int flags = parseFlags();
1327 int width = parseWidth();
1328 int precision = parsePrecision();
1329 char origConversion = format.charAt(index);
1330 char conversion = origConversion;
1331 if (Character.isUpperCase(conversion))
1333 flags |= FormattableFlags.UPPERCASE;
1334 conversion = Character.toLowerCase(conversion);
1337 Object argument = null;
1338 if (conversion == '%' || conversion == 'n')
1340 if (argumentIndex != -1)
1342 // FIXME: not sure about this.
1343 throw new UnknownFormatConversionException("FIXME");
1346 else
1348 if (argumentIndex == -1)
1349 argumentIndex = implicitArgumentIndex++;
1350 else if (argumentIndex == 0)
1351 argumentIndex = previousArgumentIndex;
1352 // Argument indices start at 1 but array indices at 0.
1353 --argumentIndex;
1354 if (argumentIndex < 0 || argumentIndex >= args.length)
1355 throw new MissingFormatArgumentException(format.substring(start, index));
1356 argument = args[argumentIndex];
1359 switch (conversion)
1361 case 'b':
1362 booleanFormat(argument, flags, width, precision,
1363 origConversion);
1364 break;
1365 case 'h':
1366 hashCodeFormat(argument, flags, width, precision,
1367 origConversion);
1368 break;
1369 case 's':
1370 stringFormat(argument, flags, width, precision,
1371 origConversion);
1372 break;
1373 case 'c':
1374 characterFormat(argument, flags, width, precision,
1375 origConversion);
1376 break;
1377 case 'd':
1378 checkFlags(flags & FormattableFlags.UPPERCASE, 0, 'd');
1379 decimalConversion(argument, flags, width, precision,
1380 origConversion);
1381 break;
1382 case 'o':
1383 checkFlags(flags & FormattableFlags.UPPERCASE, 0, 'o');
1384 hexOrOctalConversion(argument, flags, width, precision, 8,
1385 origConversion);
1386 break;
1387 case 'x':
1388 hexOrOctalConversion(argument, flags, width, precision, 16,
1389 origConversion);
1390 case 'e':
1391 // scientificNotationConversion();
1392 break;
1393 case 'f':
1394 // floatingDecimalConversion();
1395 break;
1396 case 'g':
1397 // smartFloatingConversion();
1398 break;
1399 case 'a':
1400 // hexFloatingConversion();
1401 break;
1402 case 't':
1403 advance();
1404 char subConversion = format.charAt(index);
1405 dateTimeConversion(argument, flags, width, precision,
1406 origConversion, subConversion);
1407 break;
1408 case '%':
1409 percentFormat(flags, width, precision);
1410 break;
1411 case 'n':
1412 newLineFormat(flags, width, precision);
1413 break;
1414 default:
1415 throw new UnknownFormatConversionException(String.valueOf(origConversion));
1419 catch (IOException exc)
1421 ioException = exc;
1423 return this;
1427 * Outputs a formatted string based on the supplied specification,
1428 * <code>fmt</code>, and its arguments using the formatter's locale.
1430 * @param format the format specification.
1431 * @param args the arguments to apply to the specification.
1432 * @throws IllegalFormatException if there is a problem with
1433 * the syntax of the format
1434 * specification or a mismatch
1435 * between it and the arguments.
1436 * @throws FormatterClosedException if the formatter is closed.
1438 public Formatter format(String format, Object... args)
1440 return format(locale, format, args);
1444 * Returns the last I/O exception thrown by the
1445 * <code>append()</code> operation of the underlying
1446 * output stream.
1448 * @return the last I/O exception.
1450 public IOException ioException()
1452 return ioException;
1456 * Returns the locale used by this formatter.
1458 * @return the formatter's locale.
1459 * @throws FormatterClosedException if the formatter is closed.
1461 public Locale locale()
1463 if (closed)
1464 throw new FormatterClosedException();
1465 return locale;
1469 * Returns the output stream used by this formatter.
1471 * @return the formatter's output stream.
1472 * @throws FormatterClosedException if the formatter is closed.
1474 public Appendable out()
1476 if (closed)
1477 throw new FormatterClosedException();
1478 return out;
1482 * Returns the result of applying {@link Object#toString()}
1483 * to the underlying output stream. The results returned
1484 * depend on the particular {@link Appendable} being used.
1485 * For example, a {@link StringBuilder} will return the
1486 * formatted output but an I/O stream will not.
1488 * @throws FormatterClosedException if the formatter is closed.
1490 public String toString()
1492 if (closed)
1493 throw new FormatterClosedException();
1494 return out.toString();