diff options
Diffstat (limited to 'libjava/classpath/java/util/Formatter.java')
-rw-r--r-- | libjava/classpath/java/util/Formatter.java | 1513 |
1 files changed, 0 insertions, 1513 deletions
diff --git a/libjava/classpath/java/util/Formatter.java b/libjava/classpath/java/util/Formatter.java deleted file mode 100644 index 466fab5..0000000 --- a/libjava/classpath/java/util/Formatter.java +++ /dev/null @@ -1,1513 +0,0 @@ -/* Formatter.java -- printf-style formatting - Copyright (C) 2005 Free Software Foundation, Inc. - -This file is part of GNU Classpath. - -GNU Classpath is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2, or (at your option) -any later version. - -GNU Classpath is distributed in the hope that it will be useful, but -WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -General Public License for more details. - -You should have received a copy of the GNU General Public License -along with GNU Classpath; see the file COPYING. If not, write to the -Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA -02110-1301 USA. - -Linking this library statically or dynamically with other modules is -making a combined work based on this library. Thus, the terms and -conditions of the GNU General Public License cover the whole -combination. - -As a special exception, the copyright holders of this library give you -permission to link this library with independent modules to produce an -executable, regardless of the license terms of these independent -modules, and to copy and distribute the resulting executable under -terms of your choice, provided that you also meet, for each linked -independent module, the terms and conditions of the license of that -module. An independent module is a module which is not derived from -or based on this library. If you modify this library, you may extend -this exception to your version of the library, but you are not -obligated to do so. If you do not wish to do so, delete this -exception statement from your version. */ - - -package java.util; - -import gnu.java.lang.CPStringBuilder; - -import java.io.Closeable; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.Flushable; -import java.io.IOException; -import java.io.OutputStream; -import java.io.OutputStreamWriter; -import java.io.PrintStream; -import java.io.UnsupportedEncodingException; -import java.math.BigInteger; -import java.text.DateFormatSymbols; -import java.text.DecimalFormatSymbols; - -import gnu.classpath.SystemProperties; - -/** - * <p> - * A Java formatter for <code>printf</code>-style format strings, - * as seen in the C programming language. This differs from the - * C interpretation of such strings by performing much stricter - * checking of format specifications and their corresponding - * arguments. While unknown conversions will be ignored in C, - * and invalid conversions will only produce compiler warnings, - * the Java version utilises a full range of run-time exceptions to - * handle these cases. The Java version is also more customisable - * by virtue of the provision of the {@link Formattable} interface, - * which allows an arbitrary class to be formatted by the formatter. - * </p> - * <p> - * The formatter is accessible by more convienient static methods. - * For example, streams now have appropriate format methods - * (the equivalent of <code>fprintf</code>) as do <code>String</code> - * objects (the equivalent of <code>sprintf</code>). - * </p> - * <p> - * <strong>Note</strong>: the formatter is not thread-safe. For - * multi-threaded access, external synchronization should be provided. - * </p> - * - * @author Tom Tromey (tromey@redhat.com) - * @author Andrew John Hughes (gnu_andrew@member.fsf.org) - * @since 1.5 - */ -public final class Formatter - implements Closeable, Flushable -{ - - /** - * The output of the formatter. - */ - private Appendable out; - - /** - * The locale used by the formatter. - */ - private Locale locale; - - /** - * Whether or not the formatter is closed. - */ - private boolean closed; - - /** - * The last I/O exception thrown by the output stream. - */ - private IOException ioException; - - // Some state used when actually formatting. - /** - * The format string. - */ - private String format; - - /** - * The current index into the string. - */ - private int index; - - /** - * The length of the format string. - */ - private int length; - - /** - * The formatting locale. - */ - private Locale fmtLocale; - - // Note that we include '-' twice. The flags are ordered to - // correspond to the values in FormattableFlags, and there is no - // flag (in the sense of this field used when parsing) for - // UPPERCASE; the second '-' serves as a placeholder. - /** - * A string used to index into the formattable flags. - */ - private static final String FLAGS = "--#+ 0,("; - - /** - * The system line separator. - */ - private static final String lineSeparator - = SystemProperties.getProperty("line.separator"); - - /** - * The type of numeric output format for a {@link BigDecimal}. - */ - public enum BigDecimalLayoutForm - { - DECIMAL_FLOAT, - SCIENTIFIC - } - - /** - * Constructs a new <code>Formatter</code> using the default - * locale and a {@link StringBuilder} as the output stream. - */ - public Formatter() - { - this(null, Locale.getDefault()); - } - - /** - * Constructs a new <code>Formatter</code> using the specified - * locale and a {@link StringBuilder} as the output stream. - * If the locale is <code>null</code>, then no localization - * is applied. - * - * @param loc the locale to use. - */ - public Formatter(Locale loc) - { - this(null, loc); - } - - /** - * Constructs a new <code>Formatter</code> using the default - * locale and the specified output stream. - * - * @param app the output stream to use. - */ - public Formatter(Appendable app) - { - this(app, Locale.getDefault()); - } - - /** - * Constructs a new <code>Formatter</code> using the specified - * locale and the specified output stream. If the locale is - * <code>null</code>, then no localization is applied. - * - * @param app the output stream to use. - * @param loc the locale to use. - */ - public Formatter(Appendable app, Locale loc) - { - this.out = app == null ? new StringBuilder() : app; - this.locale = loc; - } - - /** - * Constructs a new <code>Formatter</code> using the default - * locale and character set, with the specified file as the - * output stream. - * - * @param file the file to use for output. - * @throws FileNotFoundException if the file does not exist - * and can not be created. - * @throws SecurityException if a security manager is present - * and doesn't allow writing to the file. - */ - public Formatter(File file) - throws FileNotFoundException - { - this(new OutputStreamWriter(new FileOutputStream(file))); - } - - /** - * Constructs a new <code>Formatter</code> using the default - * locale, with the specified file as the output stream - * and the supplied character set. - * - * @param file the file to use for output. - * @param charset the character set to use for output. - * @throws FileNotFoundException if the file does not exist - * and can not be created. - * @throws SecurityException if a security manager is present - * and doesn't allow writing to the file. - * @throws UnsupportedEncodingException if the supplied character - * set is not supported. - */ - public Formatter(File file, String charset) - throws FileNotFoundException, UnsupportedEncodingException - { - this(file, charset, Locale.getDefault()); - } - - /** - * Constructs a new <code>Formatter</code> using the specified - * file as the output stream with the supplied character set - * and locale. If the locale is <code>null</code>, then no - * localization is applied. - * - * @param file the file to use for output. - * @param charset the character set to use for output. - * @param loc the locale to use. - * @throws FileNotFoundException if the file does not exist - * and can not be created. - * @throws SecurityException if a security manager is present - * and doesn't allow writing to the file. - * @throws UnsupportedEncodingException if the supplied character - * set is not supported. - */ - public Formatter(File file, String charset, Locale loc) - throws FileNotFoundException, UnsupportedEncodingException - { - this(new OutputStreamWriter(new FileOutputStream(file), charset), - loc); - } - - /** - * Constructs a new <code>Formatter</code> using the default - * locale and character set, with the specified output stream. - * - * @param out the output stream to use. - */ - public Formatter(OutputStream out) - { - this(new OutputStreamWriter(out)); - } - - /** - * Constructs a new <code>Formatter</code> using the default - * locale, with the specified file output stream and the - * supplied character set. - * - * @param out the output stream. - * @param charset the character set to use for output. - * @throws UnsupportedEncodingException if the supplied character - * set is not supported. - */ - public Formatter(OutputStream out, String charset) - throws UnsupportedEncodingException - { - this(out, charset, Locale.getDefault()); - } - - /** - * Constructs a new <code>Formatter</code> using the specified - * output stream with the supplied character set and locale. - * If the locale is <code>null</code>, then no localization is - * applied. - * - * @param out the output stream. - * @param charset the character set to use for output. - * @param loc the locale to use. - * @throws UnsupportedEncodingException if the supplied character - * set is not supported. - */ - public Formatter(OutputStream out, String charset, Locale loc) - throws UnsupportedEncodingException - { - this(new OutputStreamWriter(out, charset), loc); - } - - /** - * Constructs a new <code>Formatter</code> using the default - * locale with the specified output stream. The character - * set used is that of the output stream. - * - * @param out the output stream to use. - */ - public Formatter(PrintStream out) - { - this((Appendable) out); - } - - /** - * Constructs a new <code>Formatter</code> using the default - * locale and character set, with the specified file as the - * output stream. - * - * @param file the file to use for output. - * @throws FileNotFoundException if the file does not exist - * and can not be created. - * @throws SecurityException if a security manager is present - * and doesn't allow writing to the file. - */ - public Formatter(String file) throws FileNotFoundException - { - this(new OutputStreamWriter(new FileOutputStream(file))); - } - - /** - * Constructs a new <code>Formatter</code> using the default - * locale, with the specified file as the output stream - * and the supplied character set. - * - * @param file the file to use for output. - * @param charset the character set to use for output. - * @throws FileNotFoundException if the file does not exist - * and can not be created. - * @throws SecurityException if a security manager is present - * and doesn't allow writing to the file. - * @throws UnsupportedEncodingException if the supplied character - * set is not supported. - */ - public Formatter(String file, String charset) - throws FileNotFoundException, UnsupportedEncodingException - { - this(file, charset, Locale.getDefault()); - } - - /** - * Constructs a new <code>Formatter</code> using the specified - * file as the output stream with the supplied character set - * and locale. If the locale is <code>null</code>, then no - * localization is applied. - * - * @param file the file to use for output. - * @param charset the character set to use for output. - * @param loc the locale to use. - * @throws FileNotFoundException if the file does not exist - * and can not be created. - * @throws SecurityException if a security manager is present - * and doesn't allow writing to the file. - * @throws UnsupportedEncodingException if the supplied character - * set is not supported. - */ - public Formatter(String file, String charset, Locale loc) - throws FileNotFoundException, UnsupportedEncodingException - { - this(new OutputStreamWriter(new FileOutputStream(file), charset), - loc); - } - - /** - * Closes the formatter, so as to release used resources. - * If the underlying output stream supports the {@link Closeable} - * interface, then this is also closed. Attempts to use - * a formatter instance, via any method other than - * {@link #ioException()}, after closure results in a - * {@link FormatterClosedException}. - */ - public void close() - { - if (closed) - return; - try - { - if (out instanceof Closeable) - ((Closeable) out).close(); - } - catch (IOException _) - { - // FIXME: do we ignore these or do we set ioException? - // The docs seem to indicate that we should ignore. - } - closed = true; - } - - /** - * Flushes the formatter, writing any cached data to the output - * stream. If the underlying output stream supports the - * {@link Flushable} interface, it is also flushed. - * - * @throws FormatterClosedException if the formatter is closed. - */ - public void flush() - { - if (closed) - throw new FormatterClosedException(); - try - { - if (out instanceof Flushable) - ((Flushable) out).flush(); - } - catch (IOException _) - { - // FIXME: do we ignore these or do we set ioException? - // The docs seem to indicate that we should ignore. - } - } - - /** - * Return the name corresponding to a flag. - * - * @param flags the flag to return the name of. - * @return the name of the flag. - */ - private String getName(int flags) - { - // FIXME: do we want all the flags in here? - // Or should we redo how this is reported? - int bit = Integer.numberOfTrailingZeros(flags); - return FLAGS.substring(bit, bit + 1); - } - - /** - * Verify the flags passed to a conversion. - * - * @param flags the flags to verify. - * @param allowed the allowed flags mask. - * @param conversion the conversion character. - */ - private void checkFlags(int flags, int allowed, char conversion) - { - flags &= ~allowed; - if (flags != 0) - throw new FormatFlagsConversionMismatchException(getName(flags), - conversion); - } - - /** - * Throw an exception if a precision was specified. - * - * @param precision the precision value (-1 indicates not specified). - */ - private void noPrecision(int precision) - { - if (precision != -1) - throw new IllegalFormatPrecisionException(precision); - } - - /** - * Apply the numeric localization algorithm to a StringBuilder. - * - * @param builder the builder to apply to. - * @param flags the formatting flags to use. - * @param width the width of the numeric value. - * @param isNegative true if the value is negative. - */ - private void applyLocalization(CPStringBuilder builder, int flags, int width, - boolean isNegative) - { - DecimalFormatSymbols dfsyms; - if (fmtLocale == null) - dfsyms = new DecimalFormatSymbols(); - else - dfsyms = new DecimalFormatSymbols(fmtLocale); - - // First replace each digit. - char zeroDigit = dfsyms.getZeroDigit(); - int decimalOffset = -1; - for (int i = builder.length() - 1; i >= 0; --i) - { - char c = builder.charAt(i); - if (c >= '0' && c <= '9') - builder.setCharAt(i, (char) (c - '0' + zeroDigit)); - else if (c == '.') - { - assert decimalOffset == -1; - decimalOffset = i; - } - } - - // Localize the decimal separator. - if (decimalOffset != -1) - { - builder.deleteCharAt(decimalOffset); - builder.insert(decimalOffset, dfsyms.getDecimalSeparator()); - } - - // Insert the grouping separators. - if ((flags & FormattableFlags.COMMA) != 0) - { - char groupSeparator = dfsyms.getGroupingSeparator(); - int groupSize = 3; // FIXME - int offset = (decimalOffset == -1) ? builder.length() : decimalOffset; - // We use '>' because we don't want to insert a separator - // before the first digit. - for (int i = offset - groupSize; i > 0; i -= groupSize) - builder.insert(i, groupSeparator); - } - - if ((flags & FormattableFlags.ZERO) != 0) - { - // Zero fill. Note that according to the algorithm we do not - // insert grouping separators here. - for (int i = width - builder.length(); i > 0; --i) - builder.insert(0, zeroDigit); - } - - if (isNegative) - { - if ((flags & FormattableFlags.PAREN) != 0) - { - builder.insert(0, '('); - builder.append(')'); - } - else - builder.insert(0, '-'); - } - else if ((flags & FormattableFlags.PLUS) != 0) - builder.insert(0, '+'); - else if ((flags & FormattableFlags.SPACE) != 0) - builder.insert(0, ' '); - } - - /** - * A helper method that handles emitting a String after applying - * precision, width, justification, and upper case flags. - * - * @param arg the string to emit. - * @param flags the formatting flags to use. - * @param width the width to use. - * @param precision the precision to use. - * @throws IOException if the output stream throws an I/O error. - */ - private void genericFormat(String arg, int flags, int width, int precision) - throws IOException - { - if ((flags & FormattableFlags.UPPERCASE) != 0) - { - if (fmtLocale == null) - arg = arg.toUpperCase(); - else - arg = arg.toUpperCase(fmtLocale); - } - - if (precision >= 0 && arg.length() > precision) - arg = arg.substring(0, precision); - - boolean leftJustify = (flags & FormattableFlags.LEFT_JUSTIFY) != 0; - if (leftJustify && width == -1) - throw new MissingFormatWidthException("fixme"); - if (! leftJustify && arg.length() < width) - { - for (int i = width - arg.length(); i > 0; --i) - out.append(' '); - } - out.append(arg); - if (leftJustify && arg.length() < width) - { - for (int i = width - arg.length(); i > 0; --i) - out.append(' '); - } - } - - /** - * Emit a boolean. - * - * @param arg the boolean to emit. - * @param flags the formatting flags to use. - * @param width the width to use. - * @param precision the precision to use. - * @param conversion the conversion character. - * @throws IOException if the output stream throws an I/O error. - */ - private void booleanFormat(Object arg, int flags, int width, int precision, - char conversion) - throws IOException - { - checkFlags(flags, - FormattableFlags.LEFT_JUSTIFY | FormattableFlags.UPPERCASE, - conversion); - String result; - if (arg instanceof Boolean) - result = String.valueOf((Boolean) arg); - else - result = arg == null ? "false" : "true"; - genericFormat(result, flags, width, precision); - } - - /** - * Emit a hash code. - * - * @param arg the hash code to emit. - * @param flags the formatting flags to use. - * @param width the width to use. - * @param precision the precision to use. - * @param conversion the conversion character. - * @throws IOException if the output stream throws an I/O error. - */ - private void hashCodeFormat(Object arg, int flags, int width, int precision, - char conversion) - throws IOException - { - checkFlags(flags, - FormattableFlags.LEFT_JUSTIFY | FormattableFlags.UPPERCASE, - conversion); - genericFormat(arg == null ? "null" : Integer.toHexString(arg.hashCode()), - flags, width, precision); - } - - /** - * Emit a String or Formattable conversion. - * - * @param arg the String or Formattable to emit. - * @param flags the formatting flags to use. - * @param width the width to use. - * @param precision the precision to use. - * @param conversion the conversion character. - * @throws IOException if the output stream throws an I/O error. - */ - private void stringFormat(Object arg, int flags, int width, int precision, - char conversion) - throws IOException - { - if (arg instanceof Formattable) - { - checkFlags(flags, - (FormattableFlags.LEFT_JUSTIFY - | FormattableFlags.UPPERCASE - | FormattableFlags.ALTERNATE), - conversion); - Formattable fmt = (Formattable) arg; - fmt.formatTo(this, flags, width, precision); - } - else - { - checkFlags(flags, - FormattableFlags.LEFT_JUSTIFY | FormattableFlags.UPPERCASE, - conversion); - genericFormat(arg == null ? "null" : arg.toString(), flags, width, - precision); - } - } - - /** - * Emit a character. - * - * @param arg the character to emit. - * @param flags the formatting flags to use. - * @param width the width to use. - * @param precision the precision to use. - * @param conversion the conversion character. - * @throws IOException if the output stream throws an I/O error. - */ - private void characterFormat(Object arg, int flags, int width, int precision, - char conversion) - throws IOException - { - checkFlags(flags, - FormattableFlags.LEFT_JUSTIFY | FormattableFlags.UPPERCASE, - conversion); - noPrecision(precision); - - if (arg == null) - { - genericFormat("null", flags, width, precision); - return; - } - - int theChar; - if (arg instanceof Character) - theChar = ((Character) arg).charValue(); - else if (arg instanceof Byte) - theChar = (char) (((Byte) arg).byteValue ()); - else if (arg instanceof Short) - theChar = (char) (((Short) arg).shortValue ()); - else if (arg instanceof Integer) - { - theChar = ((Integer) arg).intValue(); - if (! Character.isValidCodePoint(theChar)) - throw new IllegalFormatCodePointException(theChar); - } - else - throw new IllegalFormatConversionException(conversion, arg.getClass()); - String result = new String(Character.toChars(theChar)); - genericFormat(result, flags, width, precision); - } - - /** - * Emit a '%'. - * - * @param flags the formatting flags to use. - * @param width the width to use. - * @param precision the precision to use. - * @throws IOException if the output stream throws an I/O error. - */ - private void percentFormat(int flags, int width, int precision) - throws IOException - { - checkFlags(flags, FormattableFlags.LEFT_JUSTIFY, '%'); - noPrecision(precision); - genericFormat("%", flags, width, precision); - } - - /** - * Emit a newline. - * - * @param flags the formatting flags to use. - * @param width the width to use. - * @param precision the precision to use. - * @throws IOException if the output stream throws an I/O error. - */ - private void newLineFormat(int flags, int width, int precision) - throws IOException - { - checkFlags(flags, 0, 'n'); - noPrecision(precision); - if (width != -1) - throw new IllegalFormatWidthException(width); - genericFormat(lineSeparator, flags, width, precision); - } - - /** - * Helper method to do initial formatting and checking for integral - * conversions. - * - * @param arg the formatted argument. - * @param flags the formatting flags to use. - * @param width the width to use. - * @param precision the precision to use. - * @param radix the radix of the number. - * @param conversion the conversion character. - * @return the result. - */ - private CPStringBuilder basicIntegralConversion(Object arg, int flags, - int width, int precision, - int radix, char conversion) - { - assert radix == 8 || radix == 10 || radix == 16; - - if (arg == null) - { - return new CPStringBuilder("null"); - } - - noPrecision(precision); - - // Some error checking. - if ((flags & FormattableFlags.PLUS) != 0 - && (flags & FormattableFlags.SPACE) != 0) - throw new IllegalFormatFlagsException(getName(flags)); - - if ((flags & FormattableFlags.LEFT_JUSTIFY) != 0 && width == -1) - throw new MissingFormatWidthException("fixme"); - - // Do the base translation of the value to a string. - String result; - int basicFlags = (FormattableFlags.LEFT_JUSTIFY - // We already handled any possible error when - // parsing. - | FormattableFlags.UPPERCASE - | FormattableFlags.ZERO); - if (radix == 10) - basicFlags |= (FormattableFlags.PLUS - | FormattableFlags.SPACE - | FormattableFlags.COMMA - | FormattableFlags.PAREN); - else - basicFlags |= FormattableFlags.ALTERNATE; - - if (arg instanceof BigInteger) - { - checkFlags(flags, - (basicFlags - | FormattableFlags.PLUS - | FormattableFlags.SPACE - | FormattableFlags.PAREN), - conversion); - BigInteger bi = (BigInteger) arg; - result = bi.toString(radix); - } - else if (arg instanceof Number - && ! (arg instanceof Float) - && ! (arg instanceof Double)) - { - checkFlags(flags, basicFlags, conversion); - long value = ((Number) arg).longValue (); - if (radix == 8) - result = Long.toOctalString(value); - else if (radix == 16) - result = Long.toHexString(value); - else - result = Long.toString(value); - } - else - throw new IllegalFormatConversionException(conversion, arg.getClass()); - - return new CPStringBuilder(result); - } - - /** - * Emit a hex or octal value. - * - * @param arg the hexadecimal or octal value. - * @param flags the formatting flags to use. - * @param width the width to use. - * @param precision the precision to use. - * @param radix the radix of the number. - * @param conversion the conversion character. - * @throws IOException if the output stream throws an I/O error. - */ - private void hexOrOctalConversion(Object arg, int flags, int width, - int precision, int radix, - char conversion) - throws IOException - { - assert radix == 8 || radix == 16; - - CPStringBuilder builder = basicIntegralConversion(arg, flags, width, - precision, radix, - conversion); - int insertPoint = 0; - - // Insert the sign. - if (builder.charAt(0) == '-') - { - // Already inserted. Note that we don't insert a sign, since - // the only case where it is needed it BigInteger, and it has - // already been inserted by toString. - ++insertPoint; - } - else if ((flags & FormattableFlags.PLUS) != 0) - { - builder.insert(insertPoint, '+'); - ++insertPoint; - } - else if ((flags & FormattableFlags.SPACE) != 0) - { - builder.insert(insertPoint, ' '); - ++insertPoint; - } - - // Insert the radix prefix. - if ((flags & FormattableFlags.ALTERNATE) != 0) - { - builder.insert(insertPoint, radix == 8 ? "0" : "0x"); - insertPoint += radix == 8 ? 1 : 2; - } - - // Now justify the result. - int resultWidth = builder.length(); - if (resultWidth < width) - { - char fill = ((flags & FormattableFlags.ZERO) != 0) ? '0' : ' '; - if ((flags & FormattableFlags.LEFT_JUSTIFY) != 0) - { - // Left justify. - if (fill == ' ') - insertPoint = builder.length(); - } - else - { - // Right justify. Insert spaces before the radix prefix - // and sign. - insertPoint = 0; - } - while (resultWidth++ < width) - builder.insert(insertPoint, fill); - } - - String result = builder.toString(); - if ((flags & FormattableFlags.UPPERCASE) != 0) - { - if (fmtLocale == null) - result = result.toUpperCase(); - else - result = result.toUpperCase(fmtLocale); - } - - out.append(result); - } - - /** - * Emit a decimal value. - * - * @param arg the hexadecimal or octal value. - * @param flags the formatting flags to use. - * @param width the width to use. - * @param precision the precision to use. - * @param conversion the conversion character. - * @throws IOException if the output stream throws an I/O error. - */ - private void decimalConversion(Object arg, int flags, int width, - int precision, char conversion) - throws IOException - { - CPStringBuilder builder = basicIntegralConversion(arg, flags, width, - precision, 10, - conversion); - boolean isNegative = false; - if (builder.charAt(0) == '-') - { - // Sign handling is done during localization. - builder.deleteCharAt(0); - isNegative = true; - } - - applyLocalization(builder, flags, width, isNegative); - genericFormat(builder.toString(), flags, width, precision); - } - - /** - * Emit a single date or time conversion to a StringBuilder. - * - * @param builder the builder to write to. - * @param cal the calendar to use in the conversion. - * @param conversion the formatting character to specify the type of data. - * @param syms the date formatting symbols. - */ - private void singleDateTimeConversion(CPStringBuilder builder, Calendar cal, - char conversion, - DateFormatSymbols syms) - { - int oldLen = builder.length(); - int digits = -1; - switch (conversion) - { - case 'H': - builder.append(cal.get(Calendar.HOUR_OF_DAY)); - digits = 2; - break; - case 'I': - builder.append(cal.get(Calendar.HOUR)); - digits = 2; - break; - case 'k': - builder.append(cal.get(Calendar.HOUR_OF_DAY)); - break; - case 'l': - builder.append(cal.get(Calendar.HOUR)); - break; - case 'M': - builder.append(cal.get(Calendar.MINUTE)); - digits = 2; - break; - case 'S': - builder.append(cal.get(Calendar.SECOND)); - digits = 2; - break; - case 'N': - // FIXME: nanosecond ... - digits = 9; - break; - case 'p': - { - int ampm = cal.get(Calendar.AM_PM); - builder.append(syms.getAmPmStrings()[ampm]); - } - break; - case 'z': - { - int zone = cal.get(Calendar.ZONE_OFFSET) / (1000 * 60); - builder.append(zone); - digits = 4; - // Skip the '-' sign. - if (zone < 0) - ++oldLen; - } - break; - case 'Z': - { - // FIXME: DST? - int zone = cal.get(Calendar.ZONE_OFFSET) / (1000 * 60 * 60); - String[][] zs = syms.getZoneStrings(); - builder.append(zs[zone + 12][1]); - } - break; - case 's': - { - long val = cal.getTime().getTime(); - builder.append(val / 1000); - } - break; - case 'Q': - { - long val = cal.getTime().getTime(); - builder.append(val); - } - break; - case 'B': - { - int month = cal.get(Calendar.MONTH); - builder.append(syms.getMonths()[month]); - } - break; - case 'b': - case 'h': - { - int month = cal.get(Calendar.MONTH); - builder.append(syms.getShortMonths()[month]); - } - break; - case 'A': - { - int day = cal.get(Calendar.DAY_OF_WEEK); - builder.append(syms.getWeekdays()[day]); - } - break; - case 'a': - { - int day = cal.get(Calendar.DAY_OF_WEEK); - builder.append(syms.getShortWeekdays()[day]); - } - break; - case 'C': - builder.append(cal.get(Calendar.YEAR) / 100); - digits = 2; - break; - case 'Y': - builder.append(cal.get(Calendar.YEAR)); - digits = 4; - break; - case 'y': - builder.append(cal.get(Calendar.YEAR) % 100); - digits = 2; - break; - case 'j': - builder.append(cal.get(Calendar.DAY_OF_YEAR)); - digits = 3; - break; - case 'm': - builder.append(cal.get(Calendar.MONTH) + 1); - digits = 2; - break; - case 'd': - builder.append(cal.get(Calendar.DAY_OF_MONTH)); - digits = 2; - break; - case 'e': - builder.append(cal.get(Calendar.DAY_OF_MONTH)); - break; - case 'R': - singleDateTimeConversion(builder, cal, 'H', syms); - builder.append(':'); - singleDateTimeConversion(builder, cal, 'M', syms); - break; - case 'T': - singleDateTimeConversion(builder, cal, 'H', syms); - builder.append(':'); - singleDateTimeConversion(builder, cal, 'M', syms); - builder.append(':'); - singleDateTimeConversion(builder, cal, 'S', syms); - break; - case 'r': - singleDateTimeConversion(builder, cal, 'I', syms); - builder.append(':'); - singleDateTimeConversion(builder, cal, 'M', syms); - builder.append(':'); - singleDateTimeConversion(builder, cal, 'S', syms); - builder.append(' '); - singleDateTimeConversion(builder, cal, 'p', syms); - break; - case 'D': - singleDateTimeConversion(builder, cal, 'm', syms); - builder.append('/'); - singleDateTimeConversion(builder, cal, 'd', syms); - builder.append('/'); - singleDateTimeConversion(builder, cal, 'y', syms); - break; - case 'F': - singleDateTimeConversion(builder, cal, 'Y', syms); - builder.append('-'); - singleDateTimeConversion(builder, cal, 'm', syms); - builder.append('-'); - singleDateTimeConversion(builder, cal, 'd', syms); - break; - case 'c': - singleDateTimeConversion(builder, cal, 'a', syms); - builder.append(' '); - singleDateTimeConversion(builder, cal, 'b', syms); - builder.append(' '); - singleDateTimeConversion(builder, cal, 'd', syms); - builder.append(' '); - singleDateTimeConversion(builder, cal, 'T', syms); - builder.append(' '); - singleDateTimeConversion(builder, cal, 'Z', syms); - builder.append(' '); - singleDateTimeConversion(builder, cal, 'Y', syms); - break; - default: - throw new UnknownFormatConversionException(String.valueOf(conversion)); - } - - if (digits > 0) - { - int newLen = builder.length(); - int delta = newLen - oldLen; - while (delta++ < digits) - builder.insert(oldLen, '0'); - } - } - - /** - * Emit a date or time value. - * - * @param arg the date or time value. - * @param flags the formatting flags to use. - * @param width the width to use. - * @param precision the precision to use. - * @param conversion the conversion character. - * @param subConversion the sub conversion character. - * @throws IOException if the output stream throws an I/O error. - */ - private void dateTimeConversion(Object arg, int flags, int width, - int precision, char conversion, - char subConversion) - throws IOException - { - noPrecision(precision); - checkFlags(flags, - FormattableFlags.LEFT_JUSTIFY | FormattableFlags.UPPERCASE, - conversion); - - Calendar cal; - if (arg instanceof Calendar) - cal = (Calendar) arg; - else - { - Date date; - if (arg instanceof Date) - date = (Date) arg; - else if (arg instanceof Long) - date = new Date(((Long) arg).longValue()); - else - throw new IllegalFormatConversionException(conversion, - arg.getClass()); - if (fmtLocale == null) - cal = Calendar.getInstance(); - else - cal = Calendar.getInstance(fmtLocale); - cal.setTime(date); - } - - // We could try to be more efficient by computing this lazily. - DateFormatSymbols syms; - if (fmtLocale == null) - syms = new DateFormatSymbols(); - else - syms = new DateFormatSymbols(fmtLocale); - - CPStringBuilder result = new CPStringBuilder(); - singleDateTimeConversion(result, cal, subConversion, syms); - - genericFormat(result.toString(), flags, width, precision); - } - - /** - * Advance the internal parsing index, and throw an exception - * on overrun. - * - * @throws IllegalArgumentException on overrun. - */ - private void advance() - { - ++index; - if (index >= length) - { - // FIXME: what exception here? - throw new IllegalArgumentException(); - } - } - - /** - * Parse an integer appearing in the format string. Will return -1 - * if no integer was found. - * - * @return the parsed integer. - */ - private int parseInt() - { - int start = index; - while (Character.isDigit(format.charAt(index))) - advance(); - if (start == index) - return -1; - return Integer.parseInt(format.substring(start, index)); - } - - /** - * Parse the argument index. Returns -1 if there was no index, 0 if - * we should re-use the previous index, and a positive integer to - * indicate an absolute index. - * - * @return the parsed argument index. - */ - private int parseArgumentIndex() - { - int result = -1; - int start = index; - if (format.charAt(index) == '<') - { - result = 0; - advance(); - } - else if (Character.isDigit(format.charAt(index))) - { - result = parseInt(); - if (format.charAt(index) == '$') - advance(); - else - { - // Reset. - index = start; - result = -1; - } - } - return result; - } - - /** - * Parse a set of flags and return a bit mask of values from - * FormattableFlags. Will throw an exception if a flag is - * duplicated. - * - * @return the parsed flags. - */ - private int parseFlags() - { - int value = 0; - int start = index; - while (true) - { - int x = FLAGS.indexOf(format.charAt(index)); - if (x == -1) - break; - int newValue = 1 << x; - if ((value & newValue) != 0) - throw new DuplicateFormatFlagsException(format.substring(start, - index + 1)); - value |= newValue; - advance(); - } - return value; - } - - /** - * Parse the width part of a format string. Returns -1 if no width - * was specified. - * - * @return the parsed width. - */ - private int parseWidth() - { - return parseInt(); - } - - /** - * If the current character is '.', parses the precision part of a - * format string. Returns -1 if no precision was specified. - * - * @return the parsed precision. - */ - private int parsePrecision() - { - if (format.charAt(index) != '.') - return -1; - advance(); - int precision = parseInt(); - if (precision == -1) - // FIXME - throw new IllegalArgumentException(); - return precision; - } - - /** - * Outputs a formatted string based on the supplied specification, - * <code>fmt</code>, and its arguments using the specified locale. - * The locale of the formatter does not change as a result; the - * specified locale is just used for this particular formatting - * operation. If the locale is <code>null</code>, then no - * localization is applied. - * - * @param loc the locale to use for this format. - * @param fmt the format specification. - * @param args the arguments to apply to the specification. - * @throws IllegalFormatException if there is a problem with - * the syntax of the format - * specification or a mismatch - * between it and the arguments. - * @throws FormatterClosedException if the formatter is closed. - */ - public Formatter format(Locale loc, String fmt, Object... args) - { - if (closed) - throw new FormatterClosedException(); - - // Note the arguments are indexed starting at 1. - int implicitArgumentIndex = 1; - int previousArgumentIndex = 0; - - try - { - fmtLocale = loc; - format = fmt; - length = format.length(); - for (index = 0; index < length; ++index) - { - char c = format.charAt(index); - if (c != '%') - { - out.append(c); - continue; - } - - int start = index; - advance(); - - // We do the needed post-processing of this later, when we - // determine whether an argument is actually needed by - // this conversion. - int argumentIndex = parseArgumentIndex(); - - int flags = parseFlags(); - int width = parseWidth(); - int precision = parsePrecision(); - char origConversion = format.charAt(index); - char conversion = origConversion; - if (Character.isUpperCase(conversion)) - { - flags |= FormattableFlags.UPPERCASE; - conversion = Character.toLowerCase(conversion); - } - - Object argument = null; - if (conversion == '%' || conversion == 'n') - { - if (argumentIndex != -1) - { - // FIXME: not sure about this. - throw new UnknownFormatConversionException("FIXME"); - } - } - else - { - if (argumentIndex == -1) - argumentIndex = implicitArgumentIndex++; - else if (argumentIndex == 0) - argumentIndex = previousArgumentIndex; - // Argument indices start at 1 but array indices at 0. - --argumentIndex; - if (args != null) - { - if (argumentIndex < 0 || argumentIndex >= args.length) - throw new MissingFormatArgumentException(format.substring(start, index)); - argument = args[argumentIndex]; - } - } - - switch (conversion) - { - case 'b': - booleanFormat(argument, flags, width, precision, - origConversion); - break; - case 'h': - hashCodeFormat(argument, flags, width, precision, - origConversion); - break; - case 's': - stringFormat(argument, flags, width, precision, - origConversion); - break; - case 'c': - characterFormat(argument, flags, width, precision, - origConversion); - break; - case 'd': - checkFlags(flags & FormattableFlags.UPPERCASE, 0, 'd'); - decimalConversion(argument, flags, width, precision, - origConversion); - break; - case 'o': - checkFlags(flags & FormattableFlags.UPPERCASE, 0, 'o'); - hexOrOctalConversion(argument, flags, width, precision, 8, - origConversion); - break; - case 'x': - hexOrOctalConversion(argument, flags, width, precision, 16, - origConversion); - case 'e': - // scientificNotationConversion(); - break; - case 'f': - // floatingDecimalConversion(); - break; - case 'g': - // smartFloatingConversion(); - break; - case 'a': - // hexFloatingConversion(); - break; - case 't': - advance(); - char subConversion = format.charAt(index); - dateTimeConversion(argument, flags, width, precision, - origConversion, subConversion); - break; - case '%': - percentFormat(flags, width, precision); - break; - case 'n': - newLineFormat(flags, width, precision); - break; - default: - throw new UnknownFormatConversionException(String.valueOf(origConversion)); - } - } - } - catch (IOException exc) - { - ioException = exc; - } - return this; - } - - /** - * Outputs a formatted string based on the supplied specification, - * <code>fmt</code>, and its arguments using the formatter's locale. - * - * @param format the format specification. - * @param args the arguments to apply to the specification. - * @throws IllegalFormatException if there is a problem with - * the syntax of the format - * specification or a mismatch - * between it and the arguments. - * @throws FormatterClosedException if the formatter is closed. - */ - public Formatter format(String format, Object... args) - { - return format(locale, format, args); - } - - /** - * Returns the last I/O exception thrown by the - * <code>append()</code> operation of the underlying - * output stream. - * - * @return the last I/O exception. - */ - public IOException ioException() - { - return ioException; - } - - /** - * Returns the locale used by this formatter. - * - * @return the formatter's locale. - * @throws FormatterClosedException if the formatter is closed. - */ - public Locale locale() - { - if (closed) - throw new FormatterClosedException(); - return locale; - } - - /** - * Returns the output stream used by this formatter. - * - * @return the formatter's output stream. - * @throws FormatterClosedException if the formatter is closed. - */ - public Appendable out() - { - if (closed) - throw new FormatterClosedException(); - return out; - } - - /** - * Returns the result of applying {@link Object#toString()} - * to the underlying output stream. The results returned - * depend on the particular {@link Appendable} being used. - * For example, a {@link StringBuilder} will return the - * formatted output but an I/O stream will not. - * - * @throws FormatterClosedException if the formatter is closed. - */ - public String toString() - { - if (closed) - throw new FormatterClosedException(); - return out.toString(); - } -} |