libjava/ChangeLog:
[official-gcc.git] / libjava / classpath / java / util / Formatter.java
blob9217d93b6e914f8501fac9fba19d77e5add95fff
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 gnu.java.lang.CPStringBuilder;
43 import java.io.Closeable;
44 import java.io.File;
45 import java.io.FileNotFoundException;
46 import java.io.FileOutputStream;
47 import java.io.Flushable;
48 import java.io.IOException;
49 import java.io.OutputStream;
50 import java.io.OutputStreamWriter;
51 import java.io.PrintStream;
52 import java.io.UnsupportedEncodingException;
53 import java.math.BigInteger;
54 import java.text.DateFormatSymbols;
55 import java.text.DecimalFormatSymbols;
57 import gnu.classpath.SystemProperties;
59 /**
60 * <p>
61 * A Java formatter for <code>printf</code>-style format strings,
62 * as seen in the C programming language. This differs from the
63 * C interpretation of such strings by performing much stricter
64 * checking of format specifications and their corresponding
65 * arguments. While unknown conversions will be ignored in C,
66 * and invalid conversions will only produce compiler warnings,
67 * the Java version utilises a full range of run-time exceptions to
68 * handle these cases. The Java version is also more customisable
69 * by virtue of the provision of the {@link Formattable} interface,
70 * which allows an arbitrary class to be formatted by the formatter.
71 * </p>
72 * <p>
73 * The formatter is accessible by more convienient static methods.
74 * For example, streams now have appropriate format methods
75 * (the equivalent of <code>fprintf</code>) as do <code>String</code>
76 * objects (the equivalent of <code>sprintf</code>).
77 * </p>
78 * <p>
79 * <strong>Note</strong>: the formatter is not thread-safe. For
80 * multi-threaded access, external synchronization should be provided.
81 * </p>
83 * @author Tom Tromey (tromey@redhat.com)
84 * @author Andrew John Hughes (gnu_andrew@member.fsf.org)
85 * @since 1.5
87 public final class Formatter
88 implements Closeable, Flushable
91 /**
92 * The output of the formatter.
94 private Appendable out;
96 /**
97 * The locale used by the formatter.
99 private Locale locale;
102 * Whether or not the formatter is closed.
104 private boolean closed;
107 * The last I/O exception thrown by the output stream.
109 private IOException ioException;
111 // Some state used when actually formatting.
113 * The format string.
115 private String format;
118 * The current index into the string.
120 private int index;
123 * The length of the format string.
125 private int length;
128 * The formatting locale.
130 private Locale fmtLocale;
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.
137 * A string used to index into the formattable flags.
139 private static final String FLAGS = "--#+ 0,(";
142 * The system line separator.
144 private static final String lineSeparator
145 = SystemProperties.getProperty("line.separator");
148 * The type of numeric output format for a {@link BigDecimal}.
150 public enum BigDecimalLayoutForm
152 DECIMAL_FLOAT,
153 SCIENTIFIC
157 * Constructs a new <code>Formatter</code> using the default
158 * locale and a {@link StringBuilder} as the output stream.
160 public Formatter()
162 this(null, Locale.getDefault());
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.
171 * @param loc the locale to use.
173 public Formatter(Locale loc)
175 this(null, loc);
179 * Constructs a new <code>Formatter</code> using the default
180 * locale and the specified output stream.
182 * @param app the output stream to use.
184 public Formatter(Appendable app)
186 this(app, Locale.getDefault());
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.
194 * @param app the output stream to use.
195 * @param loc the locale to use.
197 public Formatter(Appendable app, Locale loc)
199 this.out = app == null ? new StringBuilder() : app;
200 this.locale = loc;
204 * Constructs a new <code>Formatter</code> using the default
205 * locale and character set, with the specified file as the
206 * output stream.
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.
214 public Formatter(File file)
215 throws FileNotFoundException
217 this(new OutputStreamWriter(new FileOutputStream(file)));
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.
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.
234 public Formatter(File file, String charset)
235 throws FileNotFoundException, UnsupportedEncodingException
237 this(file, charset, Locale.getDefault());
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.
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.
256 public Formatter(File file, String charset, Locale loc)
257 throws FileNotFoundException, UnsupportedEncodingException
259 this(new OutputStreamWriter(new FileOutputStream(file), charset),
260 loc);
264 * Constructs a new <code>Formatter</code> using the default
265 * locale and character set, with the specified output stream.
267 * @param out the output stream to use.
269 public Formatter(OutputStream out)
271 this(new OutputStreamWriter(out));
275 * Constructs a new <code>Formatter</code> using the default
276 * locale, with the specified file output stream and the
277 * supplied character set.
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.
284 public Formatter(OutputStream out, String charset)
285 throws UnsupportedEncodingException
287 this(out, charset, Locale.getDefault());
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.
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.
302 public Formatter(OutputStream out, String charset, Locale loc)
303 throws UnsupportedEncodingException
305 this(new OutputStreamWriter(out, charset), loc);
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.
313 * @param out the output stream to use.
315 public Formatter(PrintStream out)
317 this((Appendable) out);
321 * Constructs a new <code>Formatter</code> using the default
322 * locale and character set, with the specified file as the
323 * output stream.
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.
331 public Formatter(String file) throws FileNotFoundException
333 this(new OutputStreamWriter(new FileOutputStream(file)));
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.
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.
350 public Formatter(String file, String charset)
351 throws FileNotFoundException, UnsupportedEncodingException
353 this(file, charset, Locale.getDefault());
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.
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.
372 public Formatter(String file, String charset, Locale loc)
373 throws FileNotFoundException, UnsupportedEncodingException
375 this(new OutputStreamWriter(new FileOutputStream(file), charset),
376 loc);
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}.
387 public void close()
389 if (closed)
390 return;
393 if (out instanceof Closeable)
394 ((Closeable) out).close();
396 catch (IOException _)
398 // FIXME: do we ignore these or do we set ioException?
399 // The docs seem to indicate that we should ignore.
401 closed = true;
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.
409 * @throws FormatterClosedException if the formatter is closed.
411 public void flush()
413 if (closed)
414 throw new FormatterClosedException();
417 if (out instanceof Flushable)
418 ((Flushable) out).flush();
420 catch (IOException _)
422 // FIXME: do we ignore these or do we set ioException?
423 // The docs seem to indicate that we should ignore.
428 * Return the name corresponding to a flag.
430 * @param flags the flag to return the name of.
431 * @return the name of the flag.
433 private String getName(int flags)
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);
442 * Verify the flags passed to a conversion.
444 * @param flags the flags to verify.
445 * @param allowed the allowed flags mask.
446 * @param conversion the conversion character.
448 private void checkFlags(int flags, int allowed, char conversion)
450 flags &= ~allowed;
451 if (flags != 0)
452 throw new FormatFlagsConversionMismatchException(getName(flags),
453 conversion);
457 * Throw an exception if a precision was specified.
459 * @param precision the precision value (-1 indicates not specified).
461 private void noPrecision(int precision)
463 if (precision != -1)
464 throw new IllegalFormatPrecisionException(precision);
468 * Apply the numeric localization algorithm to a StringBuilder.
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.
475 private void applyLocalization(CPStringBuilder builder, int flags, int width,
476 boolean isNegative)
478 DecimalFormatSymbols dfsyms;
479 if (fmtLocale == null)
480 dfsyms = new DecimalFormatSymbols();
481 else
482 dfsyms = new DecimalFormatSymbols(fmtLocale);
484 // First replace each digit.
485 char zeroDigit = dfsyms.getZeroDigit();
486 int decimalOffset = -1;
487 for (int i = builder.length() - 1; i >= 0; --i)
489 char c = builder.charAt(i);
490 if (c >= '0' && c <= '9')
491 builder.setCharAt(i, (char) (c - '0' + zeroDigit));
492 else if (c == '.')
494 assert decimalOffset == -1;
495 decimalOffset = i;
499 // Localize the decimal separator.
500 if (decimalOffset != -1)
502 builder.deleteCharAt(decimalOffset);
503 builder.insert(decimalOffset, dfsyms.getDecimalSeparator());
506 // Insert the grouping separators.
507 if ((flags & FormattableFlags.COMMA) != 0)
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);
518 if ((flags & FormattableFlags.ZERO) != 0)
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);
526 if (isNegative)
528 if ((flags & FormattableFlags.PAREN) != 0)
530 builder.insert(0, '(');
531 builder.append(')');
533 else
534 builder.insert(0, '-');
536 else if ((flags & FormattableFlags.PLUS) != 0)
537 builder.insert(0, '+');
538 else if ((flags & FormattableFlags.SPACE) != 0)
539 builder.insert(0, ' ');
543 * A helper method that handles emitting a String after applying
544 * precision, width, justification, and upper case flags.
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.
552 private void genericFormat(String arg, int flags, int width, int precision)
553 throws IOException
555 if ((flags & FormattableFlags.UPPERCASE) != 0)
557 if (fmtLocale == null)
558 arg = arg.toUpperCase();
559 else
560 arg = arg.toUpperCase(fmtLocale);
563 if (precision >= 0 && arg.length() > precision)
564 arg = arg.substring(0, precision);
566 boolean leftJustify = (flags & FormattableFlags.LEFT_JUSTIFY) != 0;
567 if (leftJustify && width == -1)
568 throw new MissingFormatWidthException("fixme");
569 if (! leftJustify && arg.length() < width)
571 for (int i = width - arg.length(); i > 0; --i)
572 out.append(' ');
574 out.append(arg);
575 if (leftJustify && arg.length() < width)
577 for (int i = width - arg.length(); i > 0; --i)
578 out.append(' ');
582 /**
583 * Emit a boolean.
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.
592 private void booleanFormat(Object arg, int flags, int width, int precision,
593 char conversion)
594 throws IOException
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);
607 /**
608 * Emit a hash code.
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.
617 private void hashCodeFormat(Object arg, int flags, int width, int precision,
618 char conversion)
619 throws IOException
621 checkFlags(flags,
622 FormattableFlags.LEFT_JUSTIFY | FormattableFlags.UPPERCASE,
623 conversion);
624 genericFormat(arg == null ? "null" : Integer.toHexString(arg.hashCode()),
625 flags, width, precision);
628 /**
629 * Emit a String or Formattable conversion.
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.
638 private void stringFormat(Object arg, int flags, int width, int precision,
639 char conversion)
640 throws IOException
642 if (arg instanceof Formattable)
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);
652 else
654 checkFlags(flags,
655 FormattableFlags.LEFT_JUSTIFY | FormattableFlags.UPPERCASE,
656 conversion);
657 genericFormat(arg == null ? "null" : arg.toString(), flags, width,
658 precision);
662 /**
663 * Emit a character.
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.
672 private void characterFormat(Object arg, int flags, int width, int precision,
673 char conversion)
674 throws IOException
676 checkFlags(flags,
677 FormattableFlags.LEFT_JUSTIFY | FormattableFlags.UPPERCASE,
678 conversion);
679 noPrecision(precision);
681 int theChar;
682 if (arg instanceof Character)
683 theChar = ((Character) arg).charValue();
684 else if (arg instanceof Byte)
685 theChar = (char) (((Byte) arg).byteValue ());
686 else if (arg instanceof Short)
687 theChar = (char) (((Short) arg).shortValue ());
688 else if (arg instanceof Integer)
690 theChar = ((Integer) arg).intValue();
691 if (! Character.isValidCodePoint(theChar))
692 throw new IllegalFormatCodePointException(theChar);
694 else
695 throw new IllegalFormatConversionException(conversion, arg.getClass());
696 String result = new String(Character.toChars(theChar));
697 genericFormat(result, flags, width, precision);
700 /**
701 * Emit a '%'.
703 * @param flags the formatting flags to use.
704 * @param width the width to use.
705 * @param precision the precision to use.
706 * @throws IOException if the output stream throws an I/O error.
708 private void percentFormat(int flags, int width, int precision)
709 throws IOException
711 checkFlags(flags, FormattableFlags.LEFT_JUSTIFY, '%');
712 noPrecision(precision);
713 genericFormat("%", flags, width, precision);
716 /**
717 * Emit a newline.
719 * @param flags the formatting flags to use.
720 * @param width the width to use.
721 * @param precision the precision to use.
722 * @throws IOException if the output stream throws an I/O error.
724 private void newLineFormat(int flags, int width, int precision)
725 throws IOException
727 checkFlags(flags, 0, 'n');
728 noPrecision(precision);
729 if (width != -1)
730 throw new IllegalFormatWidthException(width);
731 genericFormat(lineSeparator, flags, width, precision);
735 * Helper method to do initial formatting and checking for integral
736 * conversions.
738 * @param arg the formatted argument.
739 * @param flags the formatting flags to use.
740 * @param width the width to use.
741 * @param precision the precision to use.
742 * @param radix the radix of the number.
743 * @param conversion the conversion character.
744 * @return the result.
746 private CPStringBuilder basicIntegralConversion(Object arg, int flags,
747 int width, int precision,
748 int radix, char conversion)
750 assert radix == 8 || radix == 10 || radix == 16;
751 noPrecision(precision);
753 // Some error checking.
754 if ((flags & FormattableFlags.PLUS) != 0
755 && (flags & FormattableFlags.SPACE) != 0)
756 throw new IllegalFormatFlagsException(getName(flags));
758 if ((flags & FormattableFlags.LEFT_JUSTIFY) != 0 && width == -1)
759 throw new MissingFormatWidthException("fixme");
761 // Do the base translation of the value to a string.
762 String result;
763 int basicFlags = (FormattableFlags.LEFT_JUSTIFY
764 // We already handled any possible error when
765 // parsing.
766 | FormattableFlags.UPPERCASE
767 | FormattableFlags.ZERO);
768 if (radix == 10)
769 basicFlags |= (FormattableFlags.PLUS
770 | FormattableFlags.SPACE
771 | FormattableFlags.COMMA
772 | FormattableFlags.PAREN);
773 else
774 basicFlags |= FormattableFlags.ALTERNATE;
776 if (arg instanceof BigInteger)
778 checkFlags(flags,
779 (basicFlags
780 | FormattableFlags.PLUS
781 | FormattableFlags.SPACE
782 | FormattableFlags.PAREN),
783 conversion);
784 BigInteger bi = (BigInteger) arg;
785 result = bi.toString(radix);
787 else if (arg instanceof Number
788 && ! (arg instanceof Float)
789 && ! (arg instanceof Double))
791 checkFlags(flags, basicFlags, conversion);
792 long value = ((Number) arg).longValue ();
793 if (radix == 8)
794 result = Long.toOctalString(value);
795 else if (radix == 16)
796 result = Long.toHexString(value);
797 else
798 result = Long.toString(value);
800 else
801 throw new IllegalFormatConversionException(conversion, arg.getClass());
803 return new CPStringBuilder(result);
806 /**
807 * Emit a hex or octal value.
809 * @param arg the hexadecimal or octal value.
810 * @param flags the formatting flags to use.
811 * @param width the width to use.
812 * @param precision the precision to use.
813 * @param radix the radix of the number.
814 * @param conversion the conversion character.
815 * @throws IOException if the output stream throws an I/O error.
817 private void hexOrOctalConversion(Object arg, int flags, int width,
818 int precision, int radix,
819 char conversion)
820 throws IOException
822 assert radix == 8 || radix == 16;
824 CPStringBuilder builder = basicIntegralConversion(arg, flags, width,
825 precision, radix,
826 conversion);
827 int insertPoint = 0;
829 // Insert the sign.
830 if (builder.charAt(0) == '-')
832 // Already inserted. Note that we don't insert a sign, since
833 // the only case where it is needed it BigInteger, and it has
834 // already been inserted by toString.
835 ++insertPoint;
837 else if ((flags & FormattableFlags.PLUS) != 0)
839 builder.insert(insertPoint, '+');
840 ++insertPoint;
842 else if ((flags & FormattableFlags.SPACE) != 0)
844 builder.insert(insertPoint, ' ');
845 ++insertPoint;
848 // Insert the radix prefix.
849 if ((flags & FormattableFlags.ALTERNATE) != 0)
851 builder.insert(insertPoint, radix == 8 ? "0" : "0x");
852 insertPoint += radix == 8 ? 1 : 2;
855 // Now justify the result.
856 int resultWidth = builder.length();
857 if (resultWidth < width)
859 char fill = ((flags & FormattableFlags.ZERO) != 0) ? '0' : ' ';
860 if ((flags & FormattableFlags.LEFT_JUSTIFY) != 0)
862 // Left justify.
863 if (fill == ' ')
864 insertPoint = builder.length();
866 else
868 // Right justify. Insert spaces before the radix prefix
869 // and sign.
870 insertPoint = 0;
872 while (resultWidth++ < width)
873 builder.insert(insertPoint, fill);
876 String result = builder.toString();
877 if ((flags & FormattableFlags.UPPERCASE) != 0)
879 if (fmtLocale == null)
880 result = result.toUpperCase();
881 else
882 result = result.toUpperCase(fmtLocale);
885 out.append(result);
888 /**
889 * Emit a decimal value.
891 * @param arg the hexadecimal or octal value.
892 * @param flags the formatting flags to use.
893 * @param width the width to use.
894 * @param precision the precision to use.
895 * @param conversion the conversion character.
896 * @throws IOException if the output stream throws an I/O error.
898 private void decimalConversion(Object arg, int flags, int width,
899 int precision, char conversion)
900 throws IOException
902 CPStringBuilder builder = basicIntegralConversion(arg, flags, width,
903 precision, 10,
904 conversion);
905 boolean isNegative = false;
906 if (builder.charAt(0) == '-')
908 // Sign handling is done during localization.
909 builder.deleteCharAt(0);
910 isNegative = true;
913 applyLocalization(builder, flags, width, isNegative);
914 genericFormat(builder.toString(), flags, width, precision);
917 /**
918 * Emit a single date or time conversion to a StringBuilder.
920 * @param builder the builder to write to.
921 * @param cal the calendar to use in the conversion.
922 * @param conversion the formatting character to specify the type of data.
923 * @param syms the date formatting symbols.
925 private void singleDateTimeConversion(CPStringBuilder builder, Calendar cal,
926 char conversion,
927 DateFormatSymbols syms)
929 int oldLen = builder.length();
930 int digits = -1;
931 switch (conversion)
933 case 'H':
934 builder.append(cal.get(Calendar.HOUR_OF_DAY));
935 digits = 2;
936 break;
937 case 'I':
938 builder.append(cal.get(Calendar.HOUR));
939 digits = 2;
940 break;
941 case 'k':
942 builder.append(cal.get(Calendar.HOUR_OF_DAY));
943 break;
944 case 'l':
945 builder.append(cal.get(Calendar.HOUR));
946 break;
947 case 'M':
948 builder.append(cal.get(Calendar.MINUTE));
949 digits = 2;
950 break;
951 case 'S':
952 builder.append(cal.get(Calendar.SECOND));
953 digits = 2;
954 break;
955 case 'N':
956 // FIXME: nanosecond ...
957 digits = 9;
958 break;
959 case 'p':
961 int ampm = cal.get(Calendar.AM_PM);
962 builder.append(syms.getAmPmStrings()[ampm]);
964 break;
965 case 'z':
967 int zone = cal.get(Calendar.ZONE_OFFSET) / (1000 * 60);
968 builder.append(zone);
969 digits = 4;
970 // Skip the '-' sign.
971 if (zone < 0)
972 ++oldLen;
974 break;
975 case 'Z':
977 // FIXME: DST?
978 int zone = cal.get(Calendar.ZONE_OFFSET) / (1000 * 60 * 60);
979 String[][] zs = syms.getZoneStrings();
980 builder.append(zs[zone + 12][1]);
982 break;
983 case 's':
985 long val = cal.getTime().getTime();
986 builder.append(val / 1000);
988 break;
989 case 'Q':
991 long val = cal.getTime().getTime();
992 builder.append(val);
994 break;
995 case 'B':
997 int month = cal.get(Calendar.MONTH);
998 builder.append(syms.getMonths()[month]);
1000 break;
1001 case 'b':
1002 case 'h':
1004 int month = cal.get(Calendar.MONTH);
1005 builder.append(syms.getShortMonths()[month]);
1007 break;
1008 case 'A':
1010 int day = cal.get(Calendar.DAY_OF_WEEK);
1011 builder.append(syms.getWeekdays()[day]);
1013 break;
1014 case 'a':
1016 int day = cal.get(Calendar.DAY_OF_WEEK);
1017 builder.append(syms.getShortWeekdays()[day]);
1019 break;
1020 case 'C':
1021 builder.append(cal.get(Calendar.YEAR) / 100);
1022 digits = 2;
1023 break;
1024 case 'Y':
1025 builder.append(cal.get(Calendar.YEAR));
1026 digits = 4;
1027 break;
1028 case 'y':
1029 builder.append(cal.get(Calendar.YEAR) % 100);
1030 digits = 2;
1031 break;
1032 case 'j':
1033 builder.append(cal.get(Calendar.DAY_OF_YEAR));
1034 digits = 3;
1035 break;
1036 case 'm':
1037 builder.append(cal.get(Calendar.MONTH) + 1);
1038 digits = 2;
1039 break;
1040 case 'd':
1041 builder.append(cal.get(Calendar.DAY_OF_MONTH));
1042 digits = 2;
1043 break;
1044 case 'e':
1045 builder.append(cal.get(Calendar.DAY_OF_MONTH));
1046 break;
1047 case 'R':
1048 singleDateTimeConversion(builder, cal, 'H', syms);
1049 builder.append(':');
1050 singleDateTimeConversion(builder, cal, 'M', syms);
1051 break;
1052 case 'T':
1053 singleDateTimeConversion(builder, cal, 'H', syms);
1054 builder.append(':');
1055 singleDateTimeConversion(builder, cal, 'M', syms);
1056 builder.append(':');
1057 singleDateTimeConversion(builder, cal, 'S', syms);
1058 break;
1059 case 'r':
1060 singleDateTimeConversion(builder, cal, 'I', syms);
1061 builder.append(':');
1062 singleDateTimeConversion(builder, cal, 'M', syms);
1063 builder.append(':');
1064 singleDateTimeConversion(builder, cal, 'S', syms);
1065 builder.append(' ');
1066 singleDateTimeConversion(builder, cal, 'p', syms);
1067 break;
1068 case 'D':
1069 singleDateTimeConversion(builder, cal, 'm', syms);
1070 builder.append('/');
1071 singleDateTimeConversion(builder, cal, 'd', syms);
1072 builder.append('/');
1073 singleDateTimeConversion(builder, cal, 'y', syms);
1074 break;
1075 case 'F':
1076 singleDateTimeConversion(builder, cal, 'Y', syms);
1077 builder.append('-');
1078 singleDateTimeConversion(builder, cal, 'm', syms);
1079 builder.append('-');
1080 singleDateTimeConversion(builder, cal, 'd', syms);
1081 break;
1082 case 'c':
1083 singleDateTimeConversion(builder, cal, 'a', syms);
1084 builder.append(' ');
1085 singleDateTimeConversion(builder, cal, 'b', syms);
1086 builder.append(' ');
1087 singleDateTimeConversion(builder, cal, 'd', syms);
1088 builder.append(' ');
1089 singleDateTimeConversion(builder, cal, 'T', syms);
1090 builder.append(' ');
1091 singleDateTimeConversion(builder, cal, 'Z', syms);
1092 builder.append(' ');
1093 singleDateTimeConversion(builder, cal, 'Y', syms);
1094 break;
1095 default:
1096 throw new UnknownFormatConversionException(String.valueOf(conversion));
1099 if (digits > 0)
1101 int newLen = builder.length();
1102 int delta = newLen - oldLen;
1103 while (delta++ < digits)
1104 builder.insert(oldLen, '0');
1109 * Emit a date or time value.
1111 * @param arg the date or time value.
1112 * @param flags the formatting flags to use.
1113 * @param width the width to use.
1114 * @param precision the precision to use.
1115 * @param conversion the conversion character.
1116 * @param subConversion the sub conversion character.
1117 * @throws IOException if the output stream throws an I/O error.
1119 private void dateTimeConversion(Object arg, int flags, int width,
1120 int precision, char conversion,
1121 char subConversion)
1122 throws IOException
1124 noPrecision(precision);
1125 checkFlags(flags,
1126 FormattableFlags.LEFT_JUSTIFY | FormattableFlags.UPPERCASE,
1127 conversion);
1129 Calendar cal;
1130 if (arg instanceof Calendar)
1131 cal = (Calendar) arg;
1132 else
1134 Date date;
1135 if (arg instanceof Date)
1136 date = (Date) arg;
1137 else if (arg instanceof Long)
1138 date = new Date(((Long) arg).longValue());
1139 else
1140 throw new IllegalFormatConversionException(conversion,
1141 arg.getClass());
1142 if (fmtLocale == null)
1143 cal = Calendar.getInstance();
1144 else
1145 cal = Calendar.getInstance(fmtLocale);
1146 cal.setTime(date);
1149 // We could try to be more efficient by computing this lazily.
1150 DateFormatSymbols syms;
1151 if (fmtLocale == null)
1152 syms = new DateFormatSymbols();
1153 else
1154 syms = new DateFormatSymbols(fmtLocale);
1156 CPStringBuilder result = new CPStringBuilder();
1157 singleDateTimeConversion(result, cal, subConversion, syms);
1159 genericFormat(result.toString(), flags, width, precision);
1163 * Advance the internal parsing index, and throw an exception
1164 * on overrun.
1166 * @throws IllegalArgumentException on overrun.
1168 private void advance()
1170 ++index;
1171 if (index >= length)
1173 // FIXME: what exception here?
1174 throw new IllegalArgumentException();
1179 * Parse an integer appearing in the format string. Will return -1
1180 * if no integer was found.
1182 * @return the parsed integer.
1184 private int parseInt()
1186 int start = index;
1187 while (Character.isDigit(format.charAt(index)))
1188 advance();
1189 if (start == index)
1190 return -1;
1191 return Integer.decode(format.substring(start, index));
1195 * Parse the argument index. Returns -1 if there was no index, 0 if
1196 * we should re-use the previous index, and a positive integer to
1197 * indicate an absolute index.
1199 * @return the parsed argument index.
1201 private int parseArgumentIndex()
1203 int result = -1;
1204 int start = index;
1205 if (format.charAt(index) == '<')
1207 result = 0;
1208 advance();
1210 else if (Character.isDigit(format.charAt(index)))
1212 result = parseInt();
1213 if (format.charAt(index) == '$')
1214 advance();
1215 else
1217 // Reset.
1218 index = start;
1219 result = -1;
1222 return result;
1226 * Parse a set of flags and return a bit mask of values from
1227 * FormattableFlags. Will throw an exception if a flag is
1228 * duplicated.
1230 * @return the parsed flags.
1232 private int parseFlags()
1234 int value = 0;
1235 int start = index;
1236 while (true)
1238 int x = FLAGS.indexOf(format.charAt(index));
1239 if (x == -1)
1240 break;
1241 int newValue = 1 << x;
1242 if ((value & newValue) != 0)
1243 throw new DuplicateFormatFlagsException(format.substring(start,
1244 index + 1));
1245 value |= newValue;
1246 advance();
1248 return value;
1252 * Parse the width part of a format string. Returns -1 if no width
1253 * was specified.
1255 * @return the parsed width.
1257 private int parseWidth()
1259 return parseInt();
1263 * If the current character is '.', parses the precision part of a
1264 * format string. Returns -1 if no precision was specified.
1266 * @return the parsed precision.
1268 private int parsePrecision()
1270 if (format.charAt(index) != '.')
1271 return -1;
1272 advance();
1273 int precision = parseInt();
1274 if (precision == -1)
1275 // FIXME
1276 throw new IllegalArgumentException();
1277 return precision;
1281 * Outputs a formatted string based on the supplied specification,
1282 * <code>fmt</code>, and its arguments using the specified locale.
1283 * The locale of the formatter does not change as a result; the
1284 * specified locale is just used for this particular formatting
1285 * operation. If the locale is <code>null</code>, then no
1286 * localization is applied.
1288 * @param loc the locale to use for this format.
1289 * @param fmt the format specification.
1290 * @param args the arguments to apply to the specification.
1291 * @throws IllegalFormatException if there is a problem with
1292 * the syntax of the format
1293 * specification or a mismatch
1294 * between it and the arguments.
1295 * @throws FormatterClosedException if the formatter is closed.
1297 public Formatter format(Locale loc, String fmt, Object... args)
1299 if (closed)
1300 throw new FormatterClosedException();
1302 // Note the arguments are indexed starting at 1.
1303 int implicitArgumentIndex = 1;
1304 int previousArgumentIndex = 0;
1308 fmtLocale = loc;
1309 format = fmt;
1310 length = format.length();
1311 for (index = 0; index < length; ++index)
1313 char c = format.charAt(index);
1314 if (c != '%')
1316 out.append(c);
1317 continue;
1320 int start = index;
1321 advance();
1323 // We do the needed post-processing of this later, when we
1324 // determine whether an argument is actually needed by
1325 // this conversion.
1326 int argumentIndex = parseArgumentIndex();
1328 int flags = parseFlags();
1329 int width = parseWidth();
1330 int precision = parsePrecision();
1331 char origConversion = format.charAt(index);
1332 char conversion = origConversion;
1333 if (Character.isUpperCase(conversion))
1335 flags |= FormattableFlags.UPPERCASE;
1336 conversion = Character.toLowerCase(conversion);
1339 Object argument = null;
1340 if (conversion == '%' || conversion == 'n')
1342 if (argumentIndex != -1)
1344 // FIXME: not sure about this.
1345 throw new UnknownFormatConversionException("FIXME");
1348 else
1350 if (argumentIndex == -1)
1351 argumentIndex = implicitArgumentIndex++;
1352 else if (argumentIndex == 0)
1353 argumentIndex = previousArgumentIndex;
1354 // Argument indices start at 1 but array indices at 0.
1355 --argumentIndex;
1356 if (argumentIndex < 0 || argumentIndex >= args.length)
1357 throw new MissingFormatArgumentException(format.substring(start, index));
1358 argument = args[argumentIndex];
1361 switch (conversion)
1363 case 'b':
1364 booleanFormat(argument, flags, width, precision,
1365 origConversion);
1366 break;
1367 case 'h':
1368 hashCodeFormat(argument, flags, width, precision,
1369 origConversion);
1370 break;
1371 case 's':
1372 stringFormat(argument, flags, width, precision,
1373 origConversion);
1374 break;
1375 case 'c':
1376 characterFormat(argument, flags, width, precision,
1377 origConversion);
1378 break;
1379 case 'd':
1380 checkFlags(flags & FormattableFlags.UPPERCASE, 0, 'd');
1381 decimalConversion(argument, flags, width, precision,
1382 origConversion);
1383 break;
1384 case 'o':
1385 checkFlags(flags & FormattableFlags.UPPERCASE, 0, 'o');
1386 hexOrOctalConversion(argument, flags, width, precision, 8,
1387 origConversion);
1388 break;
1389 case 'x':
1390 hexOrOctalConversion(argument, flags, width, precision, 16,
1391 origConversion);
1392 case 'e':
1393 // scientificNotationConversion();
1394 break;
1395 case 'f':
1396 // floatingDecimalConversion();
1397 break;
1398 case 'g':
1399 // smartFloatingConversion();
1400 break;
1401 case 'a':
1402 // hexFloatingConversion();
1403 break;
1404 case 't':
1405 advance();
1406 char subConversion = format.charAt(index);
1407 dateTimeConversion(argument, flags, width, precision,
1408 origConversion, subConversion);
1409 break;
1410 case '%':
1411 percentFormat(flags, width, precision);
1412 break;
1413 case 'n':
1414 newLineFormat(flags, width, precision);
1415 break;
1416 default:
1417 throw new UnknownFormatConversionException(String.valueOf(origConversion));
1421 catch (IOException exc)
1423 ioException = exc;
1425 return this;
1429 * Outputs a formatted string based on the supplied specification,
1430 * <code>fmt</code>, and its arguments using the formatter's locale.
1432 * @param format the format specification.
1433 * @param args the arguments to apply to the specification.
1434 * @throws IllegalFormatException if there is a problem with
1435 * the syntax of the format
1436 * specification or a mismatch
1437 * between it and the arguments.
1438 * @throws FormatterClosedException if the formatter is closed.
1440 public Formatter format(String format, Object... args)
1442 return format(locale, format, args);
1446 * Returns the last I/O exception thrown by the
1447 * <code>append()</code> operation of the underlying
1448 * output stream.
1450 * @return the last I/O exception.
1452 public IOException ioException()
1454 return ioException;
1458 * Returns the locale used by this formatter.
1460 * @return the formatter's locale.
1461 * @throws FormatterClosedException if the formatter is closed.
1463 public Locale locale()
1465 if (closed)
1466 throw new FormatterClosedException();
1467 return locale;
1471 * Returns the output stream used by this formatter.
1473 * @return the formatter's output stream.
1474 * @throws FormatterClosedException if the formatter is closed.
1476 public Appendable out()
1478 if (closed)
1479 throw new FormatterClosedException();
1480 return out;
1484 * Returns the result of applying {@link Object#toString()}
1485 * to the underlying output stream. The results returned
1486 * depend on the particular {@link Appendable} being used.
1487 * For example, a {@link StringBuilder} will return the
1488 * formatted output but an I/O stream will not.
1490 * @throws FormatterClosedException if the formatter is closed.
1492 public String toString()
1494 if (closed)
1495 throw new FormatterClosedException();
1496 return out.toString();