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 gnu
.java
.lang
.CPStringBuilder
;
43 import java
.io
.Closeable
;
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
;
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.
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>).
79 * <strong>Note</strong>: the formatter is not thread-safe. For
80 * multi-threaded access, external synchronization should be provided.
83 * @author Tom Tromey (tromey@redhat.com)
84 * @author Andrew John Hughes (gnu_andrew@member.fsf.org)
87 public final class Formatter
88 implements Closeable
, Flushable
92 * The output of the formatter.
94 private Appendable out
;
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.
115 private String format
;
118 * The current index into the string.
123 * The length of the format string.
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
157 * Constructs a new <code>Formatter</code> using the default
158 * locale and a {@link StringBuilder} as the output stream.
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
171 * @param loc the locale to use.
173 public Formatter(Locale 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
;
204 * Constructs a new <code>Formatter</code> using the default
205 * locale and character set, with the specified file as the
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
),
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
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
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
),
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}.
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.
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.
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
)
452 throw new FormatFlagsConversionMismatchException(getName(flags
),
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
)
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
,
478 DecimalFormatSymbols dfsyms
;
479 if (fmtLocale
== null)
480 dfsyms
= new DecimalFormatSymbols();
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
));
494 assert decimalOffset
== -1;
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
);
528 if ((flags
& FormattableFlags
.PAREN
) != 0)
530 builder
.insert(0, '(');
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
)
555 if ((flags
& FormattableFlags
.UPPERCASE
) != 0)
557 if (fmtLocale
== null)
558 arg
= arg
.toUpperCase();
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
)
575 if (leftJustify
&& arg
.length() < width
)
577 for (int i
= width
- arg
.length(); i
> 0; --i
)
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
,
597 FormattableFlags
.LEFT_JUSTIFY
| FormattableFlags
.UPPERCASE
,
600 if (arg
instanceof Boolean
)
601 result
= String
.valueOf((Boolean
) arg
);
603 result
= arg
== null ?
"false" : "true";
604 genericFormat(result
, flags
, width
, precision
);
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
,
622 FormattableFlags
.LEFT_JUSTIFY
| FormattableFlags
.UPPERCASE
,
624 genericFormat(arg
== null ?
"null" : Integer
.toHexString(arg
.hashCode()),
625 flags
, width
, precision
);
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
,
642 if (arg
instanceof Formattable
)
645 (FormattableFlags
.LEFT_JUSTIFY
646 | FormattableFlags
.UPPERCASE
647 | FormattableFlags
.ALTERNATE
),
649 Formattable fmt
= (Formattable
) arg
;
650 fmt
.formatTo(this, flags
, width
, precision
);
655 FormattableFlags
.LEFT_JUSTIFY
| FormattableFlags
.UPPERCASE
,
657 genericFormat(arg
== null ?
"null" : arg
.toString(), flags
, width
,
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
,
677 FormattableFlags
.LEFT_JUSTIFY
| FormattableFlags
.UPPERCASE
,
679 noPrecision(precision
);
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
);
695 throw new IllegalFormatConversionException(conversion
, arg
.getClass());
696 String result
= new String(Character
.toChars(theChar
));
697 genericFormat(result
, flags
, width
, precision
);
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
)
711 checkFlags(flags
, FormattableFlags
.LEFT_JUSTIFY
, '%');
712 noPrecision(precision
);
713 genericFormat("%", flags
, width
, precision
);
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
)
727 checkFlags(flags
, 0, 'n');
728 noPrecision(precision
);
730 throw new IllegalFormatWidthException(width
);
731 genericFormat(lineSeparator
, flags
, width
, precision
);
735 * Helper method to do initial formatting and checking for integral
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.
763 int basicFlags
= (FormattableFlags
.LEFT_JUSTIFY
764 // We already handled any possible error when
766 | FormattableFlags
.UPPERCASE
767 | FormattableFlags
.ZERO
);
769 basicFlags
|= (FormattableFlags
.PLUS
770 | FormattableFlags
.SPACE
771 | FormattableFlags
.COMMA
772 | FormattableFlags
.PAREN
);
774 basicFlags
|= FormattableFlags
.ALTERNATE
;
776 if (arg
instanceof BigInteger
)
780 | FormattableFlags
.PLUS
781 | FormattableFlags
.SPACE
782 | FormattableFlags
.PAREN
),
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 ();
794 result
= Long
.toOctalString(value
);
795 else if (radix
== 16)
796 result
= Long
.toHexString(value
);
798 result
= Long
.toString(value
);
801 throw new IllegalFormatConversionException(conversion
, arg
.getClass());
803 return new CPStringBuilder(result
);
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
,
822 assert radix
== 8 || radix
== 16;
824 CPStringBuilder builder
= basicIntegralConversion(arg
, flags
, width
,
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.
837 else if ((flags
& FormattableFlags
.PLUS
) != 0)
839 builder
.insert(insertPoint
, '+');
842 else if ((flags
& FormattableFlags
.SPACE
) != 0)
844 builder
.insert(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)
864 insertPoint
= builder
.length();
868 // Right justify. Insert spaces before the radix prefix
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();
882 result
= result
.toUpperCase(fmtLocale
);
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
)
902 CPStringBuilder builder
= basicIntegralConversion(arg
, flags
, width
,
905 boolean isNegative
= false;
906 if (builder
.charAt(0) == '-')
908 // Sign handling is done during localization.
909 builder
.deleteCharAt(0);
913 applyLocalization(builder
, flags
, width
, isNegative
);
914 genericFormat(builder
.toString(), flags
, width
, precision
);
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
,
927 DateFormatSymbols syms
)
929 int oldLen
= builder
.length();
934 builder
.append(cal
.get(Calendar
.HOUR_OF_DAY
));
938 builder
.append(cal
.get(Calendar
.HOUR
));
942 builder
.append(cal
.get(Calendar
.HOUR_OF_DAY
));
945 builder
.append(cal
.get(Calendar
.HOUR
));
948 builder
.append(cal
.get(Calendar
.MINUTE
));
952 builder
.append(cal
.get(Calendar
.SECOND
));
956 // FIXME: nanosecond ...
961 int ampm
= cal
.get(Calendar
.AM_PM
);
962 builder
.append(syms
.getAmPmStrings()[ampm
]);
967 int zone
= cal
.get(Calendar
.ZONE_OFFSET
) / (1000 * 60);
968 builder
.append(zone
);
970 // Skip the '-' sign.
978 int zone
= cal
.get(Calendar
.ZONE_OFFSET
) / (1000 * 60 * 60);
979 String
[][] zs
= syms
.getZoneStrings();
980 builder
.append(zs
[zone
+ 12][1]);
985 long val
= cal
.getTime().getTime();
986 builder
.append(val
/ 1000);
991 long val
= cal
.getTime().getTime();
997 int month
= cal
.get(Calendar
.MONTH
);
998 builder
.append(syms
.getMonths()[month
]);
1004 int month
= cal
.get(Calendar
.MONTH
);
1005 builder
.append(syms
.getShortMonths()[month
]);
1010 int day
= cal
.get(Calendar
.DAY_OF_WEEK
);
1011 builder
.append(syms
.getWeekdays()[day
]);
1016 int day
= cal
.get(Calendar
.DAY_OF_WEEK
);
1017 builder
.append(syms
.getShortWeekdays()[day
]);
1021 builder
.append(cal
.get(Calendar
.YEAR
) / 100);
1025 builder
.append(cal
.get(Calendar
.YEAR
));
1029 builder
.append(cal
.get(Calendar
.YEAR
) % 100);
1033 builder
.append(cal
.get(Calendar
.DAY_OF_YEAR
));
1037 builder
.append(cal
.get(Calendar
.MONTH
) + 1);
1041 builder
.append(cal
.get(Calendar
.DAY_OF_MONTH
));
1045 builder
.append(cal
.get(Calendar
.DAY_OF_MONTH
));
1048 singleDateTimeConversion(builder
, cal
, 'H', syms
);
1049 builder
.append(':');
1050 singleDateTimeConversion(builder
, cal
, 'M', syms
);
1053 singleDateTimeConversion(builder
, cal
, 'H', syms
);
1054 builder
.append(':');
1055 singleDateTimeConversion(builder
, cal
, 'M', syms
);
1056 builder
.append(':');
1057 singleDateTimeConversion(builder
, cal
, 'S', syms
);
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
);
1069 singleDateTimeConversion(builder
, cal
, 'm', syms
);
1070 builder
.append('/');
1071 singleDateTimeConversion(builder
, cal
, 'd', syms
);
1072 builder
.append('/');
1073 singleDateTimeConversion(builder
, cal
, 'y', syms
);
1076 singleDateTimeConversion(builder
, cal
, 'Y', syms
);
1077 builder
.append('-');
1078 singleDateTimeConversion(builder
, cal
, 'm', syms
);
1079 builder
.append('-');
1080 singleDateTimeConversion(builder
, cal
, 'd', syms
);
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
);
1096 throw new UnknownFormatConversionException(String
.valueOf(conversion
));
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
,
1124 noPrecision(precision
);
1126 FormattableFlags
.LEFT_JUSTIFY
| FormattableFlags
.UPPERCASE
,
1130 if (arg
instanceof Calendar
)
1131 cal
= (Calendar
) arg
;
1135 if (arg
instanceof Date
)
1137 else if (arg
instanceof Long
)
1138 date
= new Date(((Long
) arg
).longValue());
1140 throw new IllegalFormatConversionException(conversion
,
1142 if (fmtLocale
== null)
1143 cal
= Calendar
.getInstance();
1145 cal
= Calendar
.getInstance(fmtLocale
);
1149 // We could try to be more efficient by computing this lazily.
1150 DateFormatSymbols syms
;
1151 if (fmtLocale
== null)
1152 syms
= new DateFormatSymbols();
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
1166 * @throws IllegalArgumentException on overrun.
1168 private void advance()
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()
1187 while (Character
.isDigit(format
.charAt(index
)))
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()
1205 if (format
.charAt(index
) == '<')
1210 else if (Character
.isDigit(format
.charAt(index
)))
1212 result
= parseInt();
1213 if (format
.charAt(index
) == '$')
1226 * Parse a set of flags and return a bit mask of values from
1227 * FormattableFlags. Will throw an exception if a flag is
1230 * @return the parsed flags.
1232 private int parseFlags()
1238 int x
= FLAGS
.indexOf(format
.charAt(index
));
1241 int newValue
= 1 << x
;
1242 if ((value
& newValue
) != 0)
1243 throw new DuplicateFormatFlagsException(format
.substring(start
,
1252 * Parse the width part of a format string. Returns -1 if no width
1255 * @return the parsed width.
1257 private int parseWidth()
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
) != '.')
1273 int precision
= parseInt();
1274 if (precision
== -1)
1276 throw new IllegalArgumentException();
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
)
1300 throw new FormatterClosedException();
1302 // Note the arguments are indexed starting at 1.
1303 int implicitArgumentIndex
= 1;
1304 int previousArgumentIndex
= 0;
1310 length
= format
.length();
1311 for (index
= 0; index
< length
; ++index
)
1313 char c
= format
.charAt(index
);
1323 // We do the needed post-processing of this later, when we
1324 // determine whether an argument is actually needed by
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");
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.
1356 if (argumentIndex
< 0 || argumentIndex
>= args
.length
)
1357 throw new MissingFormatArgumentException(format
.substring(start
, index
));
1358 argument
= args
[argumentIndex
];
1364 booleanFormat(argument
, flags
, width
, precision
,
1368 hashCodeFormat(argument
, flags
, width
, precision
,
1372 stringFormat(argument
, flags
, width
, precision
,
1376 characterFormat(argument
, flags
, width
, precision
,
1380 checkFlags(flags
& FormattableFlags
.UPPERCASE
, 0, 'd');
1381 decimalConversion(argument
, flags
, width
, precision
,
1385 checkFlags(flags
& FormattableFlags
.UPPERCASE
, 0, 'o');
1386 hexOrOctalConversion(argument
, flags
, width
, precision
, 8,
1390 hexOrOctalConversion(argument
, flags
, width
, precision
, 16,
1393 // scientificNotationConversion();
1396 // floatingDecimalConversion();
1399 // smartFloatingConversion();
1402 // hexFloatingConversion();
1406 char subConversion
= format
.charAt(index
);
1407 dateTimeConversion(argument
, flags
, width
, precision
,
1408 origConversion
, subConversion
);
1411 percentFormat(flags
, width
, precision
);
1414 newLineFormat(flags
, width
, precision
);
1417 throw new UnknownFormatConversionException(String
.valueOf(origConversion
));
1421 catch (IOException exc
)
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
1450 * @return the last I/O exception.
1452 public IOException
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()
1466 throw new FormatterClosedException();
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()
1479 throw new FormatterClosedException();
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()
1495 throw new FormatterClosedException();
1496 return out
.toString();