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)
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
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
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. */
41 import java
.io
.Closeable
;
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
;
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.
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>).
77 * <strong>Note</strong>: the formatter is not thread-safe. For
78 * multi-threaded access, external synchronization should be provided.
81 * @author Tom Tromey (tromey@redhat.com)
82 * @author Andrew John Hughes (gnu_andrew@member.fsf.org)
85 public final class Formatter
86 implements Closeable
, Flushable
90 * The output of the formatter.
92 private Appendable out
;
95 * The locale used by the formatter.
97 private Locale locale
;
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.
113 private String format
;
116 * The current index into the string.
121 * The length of the format string.
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
155 * Constructs a new <code>Formatter</code> using the default
156 * locale and a {@link StringBuilder} as the output stream.
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
169 * @param loc the locale to use.
171 public Formatter(Locale 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
;
202 * Constructs a new <code>Formatter</code> using the default
203 * locale and character set, with the specified file as the
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
),
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
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
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
),
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}.
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.
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.
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
)
450 throw new FormatFlagsConversionMismatchException(getName(flags
),
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
)
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
,
476 DecimalFormatSymbols dfsyms
;
477 if (fmtLocale
== null)
478 dfsyms
= new DecimalFormatSymbols();
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
));
492 assert decimalOffset
== -1;
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
);
526 if ((flags
& FormattableFlags
.PAREN
) != 0)
528 builder
.insert(0, '(');
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
)
553 if ((flags
& FormattableFlags
.UPPERCASE
) != 0)
555 if (fmtLocale
== null)
556 arg
= arg
.toUpperCase();
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
)
573 if (leftJustify
&& arg
.length() < width
)
575 for (int i
= width
- arg
.length(); i
> 0; --i
)
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
,
595 FormattableFlags
.LEFT_JUSTIFY
| FormattableFlags
.UPPERCASE
,
598 if (arg
instanceof Boolean
)
599 result
= String
.valueOf((Boolean
) arg
);
601 result
= arg
== null ?
"false" : "true";
602 genericFormat(result
, flags
, width
, precision
);
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
,
620 FormattableFlags
.LEFT_JUSTIFY
| FormattableFlags
.UPPERCASE
,
622 genericFormat(arg
== null ?
"null" : Integer
.toHexString(arg
.hashCode()),
623 flags
, width
, precision
);
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
,
640 if (arg
instanceof Formattable
)
643 (FormattableFlags
.LEFT_JUSTIFY
644 | FormattableFlags
.UPPERCASE
645 | FormattableFlags
.ALTERNATE
),
647 Formattable fmt
= (Formattable
) arg
;
648 fmt
.formatTo(this, flags
, width
, precision
);
653 FormattableFlags
.LEFT_JUSTIFY
| FormattableFlags
.UPPERCASE
,
655 genericFormat(arg
== null ?
"null" : arg
.toString(), flags
, width
,
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
,
675 FormattableFlags
.LEFT_JUSTIFY
| FormattableFlags
.UPPERCASE
,
677 noPrecision(precision
);
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
);
693 throw new IllegalFormatConversionException(conversion
, arg
.getClass());
694 String result
= new String(Character
.toChars(theChar
));
695 genericFormat(result
, flags
, width
, precision
);
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
)
709 checkFlags(flags
, FormattableFlags
.LEFT_JUSTIFY
, '%');
710 noPrecision(precision
);
711 genericFormat("%", flags
, width
, precision
);
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
)
725 checkFlags(flags
, 0, 'n');
726 noPrecision(precision
);
728 throw new IllegalFormatWidthException(width
);
729 genericFormat(lineSeparator
, flags
, width
, precision
);
733 * Helper method to do initial formatting and checking for integral
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.
761 int basicFlags
= (FormattableFlags
.LEFT_JUSTIFY
762 // We already handled any possible error when
764 | FormattableFlags
.UPPERCASE
765 | FormattableFlags
.ZERO
);
767 basicFlags
|= (FormattableFlags
.PLUS
768 | FormattableFlags
.SPACE
769 | FormattableFlags
.COMMA
770 | FormattableFlags
.PAREN
);
772 basicFlags
|= FormattableFlags
.ALTERNATE
;
774 if (arg
instanceof BigInteger
)
778 | FormattableFlags
.PLUS
779 | FormattableFlags
.SPACE
780 | FormattableFlags
.PAREN
),
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 ();
792 result
= Long
.toOctalString(value
);
793 else if (radix
== 16)
794 result
= Long
.toHexString(value
);
796 result
= Long
.toString(value
);
799 throw new IllegalFormatConversionException(conversion
, arg
.getClass());
801 return new StringBuilder(result
);
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
,
820 assert radix
== 8 || radix
== 16;
822 StringBuilder builder
= basicIntegralConversion(arg
, flags
, width
,
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.
835 else if ((flags
& FormattableFlags
.PLUS
) != 0)
837 builder
.insert(insertPoint
, '+');
840 else if ((flags
& FormattableFlags
.SPACE
) != 0)
842 builder
.insert(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)
862 insertPoint
= builder
.length();
866 // Right justify. Insert spaces before the radix prefix
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();
880 result
= result
.toUpperCase(fmtLocale
);
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
)
900 StringBuilder builder
= basicIntegralConversion(arg
, flags
, width
,
903 boolean isNegative
= false;
904 if (builder
.charAt(0) == '-')
906 // Sign handling is done during localization.
907 builder
.deleteCharAt(0);
911 applyLocalization(builder
, flags
, width
, isNegative
);
912 genericFormat(builder
.toString(), flags
, width
, precision
);
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
,
925 DateFormatSymbols syms
)
927 int oldLen
= builder
.length();
932 builder
.append(cal
.get(Calendar
.HOUR_OF_DAY
));
936 builder
.append(cal
.get(Calendar
.HOUR
));
940 builder
.append(cal
.get(Calendar
.HOUR_OF_DAY
));
943 builder
.append(cal
.get(Calendar
.HOUR
));
946 builder
.append(cal
.get(Calendar
.MINUTE
));
950 builder
.append(cal
.get(Calendar
.SECOND
));
954 // FIXME: nanosecond ...
959 int ampm
= cal
.get(Calendar
.AM_PM
);
960 builder
.append(syms
.getAmPmStrings()[ampm
]);
965 int zone
= cal
.get(Calendar
.ZONE_OFFSET
) / (1000 * 60);
966 builder
.append(zone
);
968 // Skip the '-' sign.
976 int zone
= cal
.get(Calendar
.ZONE_OFFSET
) / (1000 * 60 * 60);
977 String
[][] zs
= syms
.getZoneStrings();
978 builder
.append(zs
[zone
+ 12][1]);
983 long val
= cal
.getTime().getTime();
984 builder
.append(val
/ 1000);
989 long val
= cal
.getTime().getTime();
995 int month
= cal
.get(Calendar
.MONTH
);
996 builder
.append(syms
.getMonths()[month
]);
1002 int month
= cal
.get(Calendar
.MONTH
);
1003 builder
.append(syms
.getShortMonths()[month
]);
1008 int day
= cal
.get(Calendar
.DAY_OF_WEEK
);
1009 builder
.append(syms
.getWeekdays()[day
]);
1014 int day
= cal
.get(Calendar
.DAY_OF_WEEK
);
1015 builder
.append(syms
.getShortWeekdays()[day
]);
1019 builder
.append(cal
.get(Calendar
.YEAR
) / 100);
1023 builder
.append(cal
.get(Calendar
.YEAR
));
1027 builder
.append(cal
.get(Calendar
.YEAR
) % 100);
1031 builder
.append(cal
.get(Calendar
.DAY_OF_YEAR
));
1035 builder
.append(cal
.get(Calendar
.MONTH
) + 1);
1039 builder
.append(cal
.get(Calendar
.DAY_OF_MONTH
));
1043 builder
.append(cal
.get(Calendar
.DAY_OF_MONTH
));
1046 singleDateTimeConversion(builder
, cal
, 'H', syms
);
1047 builder
.append(':');
1048 singleDateTimeConversion(builder
, cal
, 'M', syms
);
1051 singleDateTimeConversion(builder
, cal
, 'H', syms
);
1052 builder
.append(':');
1053 singleDateTimeConversion(builder
, cal
, 'M', syms
);
1054 builder
.append(':');
1055 singleDateTimeConversion(builder
, cal
, 'S', syms
);
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
);
1067 singleDateTimeConversion(builder
, cal
, 'm', syms
);
1068 builder
.append('/');
1069 singleDateTimeConversion(builder
, cal
, 'd', syms
);
1070 builder
.append('/');
1071 singleDateTimeConversion(builder
, cal
, 'y', syms
);
1074 singleDateTimeConversion(builder
, cal
, 'Y', syms
);
1075 builder
.append('-');
1076 singleDateTimeConversion(builder
, cal
, 'm', syms
);
1077 builder
.append('-');
1078 singleDateTimeConversion(builder
, cal
, 'd', syms
);
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
);
1094 throw new UnknownFormatConversionException(String
.valueOf(conversion
));
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
,
1122 noPrecision(precision
);
1124 FormattableFlags
.LEFT_JUSTIFY
| FormattableFlags
.UPPERCASE
,
1128 if (arg
instanceof Calendar
)
1129 cal
= (Calendar
) arg
;
1133 if (arg
instanceof Date
)
1135 else if (arg
instanceof Long
)
1136 date
= new Date(((Long
) arg
).longValue());
1138 throw new IllegalFormatConversionException(conversion
,
1140 if (fmtLocale
== null)
1141 cal
= Calendar
.getInstance();
1143 cal
= Calendar
.getInstance(fmtLocale
);
1147 // We could try to be more efficient by computing this lazily.
1148 DateFormatSymbols syms
;
1149 if (fmtLocale
== null)
1150 syms
= new DateFormatSymbols();
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
1164 * @throws IllegalArgumentException on overrun.
1166 private void advance()
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()
1185 while (Character
.isDigit(format
.charAt(index
)))
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()
1203 if (format
.charAt(index
) == '<')
1208 else if (Character
.isDigit(format
.charAt(index
)))
1210 result
= parseInt();
1211 if (format
.charAt(index
) == '$')
1224 * Parse a set of flags and return a bit mask of values from
1225 * FormattableFlags. Will throw an exception if a flag is
1228 * @return the parsed flags.
1230 private int parseFlags()
1236 int x
= FLAGS
.indexOf(format
.charAt(index
));
1239 int newValue
= 1 << x
;
1240 if ((value
& newValue
) != 0)
1241 throw new DuplicateFormatFlagsException(format
.substring(start
,
1250 * Parse the width part of a format string. Returns -1 if no width
1253 * @return the parsed width.
1255 private int parseWidth()
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
) != '.')
1271 int precision
= parseInt();
1272 if (precision
== -1)
1274 throw new IllegalArgumentException();
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
)
1298 throw new FormatterClosedException();
1300 // Note the arguments are indexed starting at 1.
1301 int implicitArgumentIndex
= 1;
1302 int previousArgumentIndex
= 0;
1308 length
= format
.length();
1309 for (index
= 0; index
< length
; ++index
)
1311 char c
= format
.charAt(index
);
1321 // We do the needed post-processing of this later, when we
1322 // determine whether an argument is actually needed by
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");
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.
1354 if (argumentIndex
< 0 || argumentIndex
>= args
.length
)
1355 throw new MissingFormatArgumentException(format
.substring(start
, index
));
1356 argument
= args
[argumentIndex
];
1362 booleanFormat(argument
, flags
, width
, precision
,
1366 hashCodeFormat(argument
, flags
, width
, precision
,
1370 stringFormat(argument
, flags
, width
, precision
,
1374 characterFormat(argument
, flags
, width
, precision
,
1378 checkFlags(flags
& FormattableFlags
.UPPERCASE
, 0, 'd');
1379 decimalConversion(argument
, flags
, width
, precision
,
1383 checkFlags(flags
& FormattableFlags
.UPPERCASE
, 0, 'o');
1384 hexOrOctalConversion(argument
, flags
, width
, precision
, 8,
1388 hexOrOctalConversion(argument
, flags
, width
, precision
, 16,
1391 // scientificNotationConversion();
1394 // floatingDecimalConversion();
1397 // smartFloatingConversion();
1400 // hexFloatingConversion();
1404 char subConversion
= format
.charAt(index
);
1405 dateTimeConversion(argument
, flags
, width
, precision
,
1406 origConversion
, subConversion
);
1409 percentFormat(flags
, width
, precision
);
1412 newLineFormat(flags
, width
, precision
);
1415 throw new UnknownFormatConversionException(String
.valueOf(origConversion
));
1419 catch (IOException exc
)
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
1448 * @return the last I/O exception.
1450 public IOException
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()
1464 throw new FormatterClosedException();
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()
1477 throw new FormatterClosedException();
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()
1493 throw new FormatterClosedException();
1494 return out
.toString();