classpath-patches
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[cp-patches] [generics] Patch: FYI: first draft of Formatter


From: Tom Tromey
Subject: [cp-patches] [generics] Patch: FYI: first draft of Formatter
Date: 24 Sep 2005 20:48:52 -0600
User-agent: Gnus/5.09 (Gnus v5.9.0) Emacs/21.3.50

I'm checking this in on the generics branch.

This adds the new Formatter class.  It is not nearly complete, but is
capable of doing some simple formatting.  Essentially the "full"
decimal, floating point, and date/time formatting remains to be
written.  I have a test case for this but haven't yet put it in mauve.

This patch also includes the new Formattable interface, some fixes to
format-related exceptions, and the format and printf methods for
String and java.io.

I haven't written the javadoc yet.  I didn't put this on the trunk
because it is incomplete, it includes a new enum type, and it uses
varargs.  (The enum seems kind of useless and we could strip the
varargs for the trunk; once the javadoc is written.)

Tom

Index: ChangeLog
from  Tom Tromey  <address@hidden>

        * java/util/FormattableFlags.java (PLUS, SPACE, ZERO, COMMA,
        PAREN): New constants.
        * java/util/UnknownFormatConversionException.java
        (UnknownFormatConversionException): Set exception text.
        * java/util/IllegalFormatException.java (IllegalFormatException):
        New constructor.
        * java/util/FormatFlagsConversionMismatchException.java
        (FormatFlagsConversionMismatchException): Set exception text.
        * java/lang/String.java (format): New methods.
        * java/io/PrintStream.java (printf): New methods.
        (format): Likewise.
        * java/io/PrintWriter.java (printf): New methods.
        (format): Likewise.
        * java/util/Formattable.java: New file.
        * java/util/FormatterClosedException.java (serialVersionUID):
        Fixed.
        * java/util/Formatter.java: New file.

Index: java/io/PrintStream.java
===================================================================
RCS file: /cvsroot/classpath/classpath/java/io/PrintStream.java,v
retrieving revision 1.19.2.7
diff -u -r1.19.2.7 PrintStream.java
--- java/io/PrintStream.java 2 Aug 2005 20:12:20 -0000 1.19.2.7
+++ java/io/PrintStream.java 25 Sep 2005 02:52:25 -0000
@@ -39,6 +39,9 @@
 
 package java.io;
 
+import java.util.Locale;
+import java.util.Formatter;
+
 /* Written using "Java Class Libraries", 2nd edition, ISBN 0-201-31002-3
  * "The Java Language Specification", ISBN 0-201-63451-1
  * Status:  Believed complete and correct to 1.3
@@ -597,6 +600,32 @@
   public PrintStream append(CharSequence cs, int start, int end)
   {
     print(cs == null ? "null" : cs.subSequence(start, end).toString());
+    return this;
+  }
+
+  /** @since 1.5 */
+  public PrintStream printf(String format, Object... args)
+  {
+    return format(format, args);
+  }
+
+  /** @since 1.5 */
+  public PrintStream printf(Locale locale, String format, Object... args)
+  {
+    return format(locale, format, args);
+  }
+
+  /** @since 1.5 */
+  public PrintStream format(String format, Object... args)
+  {
+    return format(Locale.getDefault(), format, args);
+  }
+
+  /** @since 1.5 */
+  public PrintStream format(Locale locale, String format, Object... args)
+  {
+    Formatter f = new Formatter(this, locale);
+    f.format(format, args);
     return this;
   }
 } // class PrintStream
Index: java/io/PrintWriter.java
===================================================================
RCS file: /cvsroot/classpath/classpath/java/io/PrintWriter.java,v
retrieving revision 1.13.2.4
diff -u -r1.13.2.4 PrintWriter.java
--- java/io/PrintWriter.java 20 Sep 2005 18:46:27 -0000 1.13.2.4
+++ java/io/PrintWriter.java 25 Sep 2005 02:52:25 -0000
@@ -37,6 +37,9 @@
 
 package java.io;
 
+import java.util.Locale;
+import java.util.Formatter;
+
 /* Written using "Java Class Libraries", 2nd edition, plus online
  * API docs for JDK 1.2 beta from http://www.javasoft.com.
  * Status:  Believed complete and correct.
@@ -655,6 +658,32 @@
   public PrintWriter append(CharSequence cs, int start, int end)
   {
     write(cs == null ? "null" : cs.subSequence(start, end).toString());
+    return this;
+  }
+
+  /** @since 1.5 */
+  public PrintWriter printf(String format, Object... args)
+  {
+    return format(format, args);
+  }
+
+  /** @since 1.5 */
+  public PrintWriter printf(Locale locale, String format, Object... args)
+  {
+    return format(locale, format, args);
+  }
+
+  /** @since 1.5 */
+  public PrintWriter format(String format, Object... args)
+  {
+    return format(Locale.getDefault(), format, args);
+  }
+
+  /** @since 1.5 */
+  public PrintWriter format(Locale locale, String format, Object... args)
+  {
+    Formatter f = new Formatter(this, locale);
+    f.format(format, args);
     return this;
   }
 }
Index: java/lang/String.java
===================================================================
RCS file: /cvsroot/classpath/classpath/java/lang/String.java,v
retrieving revision 1.58.2.11
diff -u -r1.58.2.11 String.java
--- java/lang/String.java 20 Sep 2005 18:46:28 -0000 1.58.2.11
+++ java/lang/String.java 25 Sep 2005 02:52:26 -0000
@@ -54,6 +54,7 @@
 import java.nio.charset.UnsupportedCharsetException;
 import java.text.Collator;
 import java.util.Comparator;
+import java.util.Formatter;
 import java.util.Locale;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
@@ -1706,6 +1707,20 @@
   public static String valueOf(double d)
   {
     return Double.toString(d);
+  }
+
+
+  /** @since 1.5 */
+  public static String format(Locale locale, String format, Object... args)
+  {
+    Formatter f = new Formatter(locale);
+    return f.format(format, args).toString();
+  }
+
+  /** @since 1.5 */
+  public static String format(String format, Object... args)
+  {
+    return format(Locale.getDefault(), format, args);
   }
 
   /**
Index: java/util/FormatFlagsConversionMismatchException.java
===================================================================
RCS file: 
/cvsroot/classpath/classpath/java/util/Attic/FormatFlagsConversionMismatchException.java,v
retrieving revision 1.1.2.3
diff -u -r1.1.2.3 FormatFlagsConversionMismatchException.java
--- java/util/FormatFlagsConversionMismatchException.java 21 Sep 2005 21:32:40 
-0000 1.1.2.3
+++ java/util/FormatFlagsConversionMismatchException.java 25 Sep 2005 02:52:26 
-0000
@@ -51,6 +51,7 @@
 
   public FormatFlagsConversionMismatchException(String f, char c)
   {
+    super("invalid flag " + f + " for conversion " + c);
     this.f = f;
     this.c = c;
   }
Index: java/util/Formattable.java
===================================================================
RCS file: java/util/Formattable.java
diff -N java/util/Formattable.java
--- /dev/null   1 Jan 1970 00:00:00 -0000
+++ java/util/Formattable.java 25 Sep 2005 02:52:26 -0000
@@ -0,0 +1,46 @@
+/* Formattable.java -- Objects which can be passed to a Formatter
+   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;
+
+/** @since 1.5 */
+public interface Formattable
+{
+  public void formatTo(Formatter formatter, int flags, int width,
+                      int precision);
+}
Index: java/util/FormattableFlags.java
===================================================================
RCS file: /cvsroot/classpath/classpath/java/util/Attic/FormattableFlags.java,v
retrieving revision 1.1.2.1
diff -u -r1.1.2.1 FormattableFlags.java
--- java/util/FormattableFlags.java 14 Aug 2005 00:52:32 -0000 1.1.2.1
+++ java/util/FormattableFlags.java 25 Sep 2005 02:52:26 -0000
@@ -45,6 +45,14 @@
   public static final int UPPERCASE = 2;
   public static final int ALTERNATE = 4;
 
+  // Used internally by Formatter.
+  // Changes here must be reflected in the FLAGS string there.
+  static final int PLUS = 8;
+  static final int SPACE = 16;
+  static final int ZERO = 32;
+  static final int COMMA = 64;
+  static final int PAREN = 128;
+
   // Not instantiable.
   private FormattableFlags()
   {
Index: java/util/Formatter.java
===================================================================
RCS file: java/util/Formatter.java
diff -N java/util/Formatter.java
--- /dev/null   1 Jan 1970 00:00:00 -0000
+++ java/util/Formatter.java 25 Sep 2005 02:52:26 -0000
@@ -0,0 +1,741 @@
+/* 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 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;
+
+/** @since 1.5 */
+public class Formatter implements Closeable, Flushable
+{
+  private Appendable out;
+  private Locale locale;
+  private boolean closed;
+  private IOException ioException;
+
+  // Some state used when actually formatting.
+  private String format;
+  private int index;
+  private int length;
+  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.
+  private static final String FLAGS = "--#+ 0,(";
+
+  private static final String lineSeparator
+    = System.getProperty("line.separator");
+
+  public enum BigDecimalLayoutForm
+  {
+    DECIMAL_FLOAT,
+    SCIENTIFIC
+  }
+
+  public Formatter()
+  {
+    this.out = new StringBuilder();
+  }
+
+  public Formatter(Locale loc)
+  {
+    this.out = new StringBuilder();
+    this.locale = loc;
+  }
+
+  public Formatter(Appendable app)
+  {
+    this(app, null);
+  }
+
+  public Formatter(Appendable app, Locale loc)
+  {
+    this.out = app == null ? new StringBuilder() : app;
+    this.locale = loc;
+  }
+
+  public Formatter(File file) throws FileNotFoundException
+  {
+    this.out = new OutputStreamWriter(new FileOutputStream(file));
+  }
+
+  public Formatter(File file, String charset)
+    throws FileNotFoundException, UnsupportedEncodingException
+  {
+    this(file, charset, null);
+  }
+
+  public Formatter(File file, String charset, Locale loc)
+    throws FileNotFoundException, UnsupportedEncodingException
+  {
+    this.out = new OutputStreamWriter(new FileOutputStream(file), charset);
+  }
+
+  public Formatter(OutputStream out)
+  {
+    this.out = new OutputStreamWriter(out);
+  }
+
+  public Formatter(OutputStream out, String charset)
+    throws UnsupportedEncodingException
+  {
+    this(out, charset, null);
+  }
+
+  public Formatter(OutputStream out, String charset, Locale loc)
+    throws UnsupportedEncodingException
+  {
+    this.out = new OutputStreamWriter(out, charset);
+    this.locale = loc;
+  }
+
+  public Formatter(PrintStream out)
+  {
+    this.out = out;
+  }
+
+  public Formatter(String file) throws FileNotFoundException
+  {
+    this.out = new OutputStreamWriter(new FileOutputStream(file));
+  }
+
+  public Formatter(String file, String charset)
+    throws FileNotFoundException, UnsupportedEncodingException
+  {
+    this(file, charset, null);
+  }
+
+  public Formatter(String file, String charset, Locale loc)
+    throws FileNotFoundException, UnsupportedEncodingException
+  {
+    this.out = new OutputStreamWriter(new FileOutputStream(file), charset);
+    this.locale = loc;
+  }
+
+  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;
+  }
+
+  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.
+   */
+  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.
+   */
+  private void checkFlags(int flags, int allowed, char conversion)
+  {
+    flags &= ~allowed;
+    if (flags != 0)
+      throw new FormatFlagsConversionMismatchException(getName(flags),
+                                                      conversion);
+  }
+
+  /**
+   * Throw an exception is a precision was specified.
+   */
+  private void noPrecision(int precision)
+  {
+    if (precision != -1)
+      throw new IllegalFormatPrecisionException(precision);
+  }
+
+  /**
+   * A helper method that handles emitting a String after applying
+   * precision, width, justification, and upper case flags.
+   */
+  private void genericFormat(String arg, int flags, int width, int precision)
+    throws IOException
+  {
+    if (precision >= 0 && arg.length() > precision)
+      arg = arg.substring(0, precision);
+    boolean left = (flags & FormattableFlags.LEFT_JUSTIFY) != 0;
+    if (left && width == -1)
+      throw new MissingFormatWidthException("fixme");
+    if (! left && arg.length() < width)
+      {
+       for (int i = arg.length() - width; i >= 0; --i)
+         out.append(' ');
+      }
+    if ((flags & FormattableFlags.UPPERCASE) != 0)
+      {
+       if (fmtLocale == null)
+         arg = arg.toUpperCase();
+       else
+         arg = arg.toUpperCase(fmtLocale);
+      }
+    out.append(arg);
+    if (left && arg.length() < width)
+      {
+       for (int i = arg.length() - width; i >= 0; --i)
+         out.append(' ');
+      }
+  }
+
+  /** Emit a boolean.  */
+  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.  */
+  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 via a String or Formattable conversion.  */
+  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 value.  */
+  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);
+
+    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 '%'.  */
+  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.  */
+  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);
+  }
+
+  /** Emit a hex or octal value.  */
+  private void hexOrOctalConversion(Object arg, int flags, int width,
+                                   int precision, int radix,
+                                   char conversion)
+    throws IOException
+  {
+    assert radix == 8 || radix == 16;
+    noPrecision(precision);
+
+    // Some error checking.
+    if ((flags & FormattableFlags.ZERO) != 0
+       && (flags & FormattableFlags.LEFT_JUSTIFY) == 0)
+      throw new IllegalFormatFlagsException(getName(flags));
+    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;
+    if (arg instanceof BigInteger)
+      {
+       checkFlags(flags,
+                  (FormattableFlags.LEFT_JUSTIFY
+                   // We already handled any possible error when
+                   // parsing.
+                   | FormattableFlags.UPPERCASE
+                   | FormattableFlags.ALTERNATE
+                   | FormattableFlags.PLUS
+                   | FormattableFlags.SPACE
+                   | FormattableFlags.ZERO
+                   | FormattableFlags.PAREN),
+                  conversion);
+       BigInteger bi = (BigInteger) arg;
+       result = bi.toString(radix);
+      }
+    else if (arg instanceof Number
+            && ! (arg instanceof Float)
+            && ! (arg instanceof Double))
+      {
+       checkFlags(flags,
+                  (FormattableFlags.LEFT_JUSTIFY
+                   // We already handled any possible error when
+                   // parsing.
+                   | FormattableFlags.UPPERCASE
+                   | FormattableFlags.ALTERNATE
+                   | FormattableFlags.ZERO),
+                  conversion);
+       long value = ((Number) arg).longValue ();
+       result = (radix == 8 ? Long.toOctalString(value)
+                 : Long.toHexString(value));
+      }
+    else
+      throw new IllegalFormatConversionException(conversion, arg.getClass());
+
+    // We use a string builder to do further manipulations.
+    StringBuilder builder = new StringBuilder(result);
+    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 = result.length();
+    if (resultWidth < width)
+      {
+       char fill = ((flags & FormattableFlags.ZERO) != 0) ? '0' : ' ';
+       if ((flags & FormattableFlags.LEFT_JUSTIFY) == 0)
+         {
+           // Right justify.
+           insertPoint = builder.length();
+         }
+       else if (fill == ' ')
+         {
+           // Insert spaces before the radix prefix and sign.
+           insertPoint = 0;
+         }
+       while (resultWidth++ < width)
+         builder.insert(insertPoint, fill);
+      }
+
+    result = builder.toString();
+    if ((flags & FormattableFlags.UPPERCASE) != 0)
+      {
+       if (fmtLocale == null)
+         result = result.toUpperCase();
+       else
+         result = result.toUpperCase(fmtLocale);
+      }
+
+    out.append(result);
+  }
+
+
+  /**
+   * Advance the internal parsing index, and throw an exception
+   * 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.
+   */
+  private int parseInt()
+  {
+    int start = index;
+    while (Character.isDigit(format.charAt(index)))
+      advance();
+    if (start == index)
+      return -1;
+    return Integer.decode(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.
+   */
+  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.
+   */
+  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.
+   */
+  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.
+   */
+  private int parsePrecision()
+  {
+    if (format.charAt(index) != '.')
+      return -1;
+    advance();
+    int precision = parseInt();
+    if (precision == -1)
+      // FIXME
+      throw new IllegalArgumentException();
+    return precision;
+  }
+
+  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
+      {
+       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 (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':
+               // decimalFormat();
+               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':
+//             char format = parseDateTimeFormat();
+//             timeDateFormat();
+               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;
+  }
+
+  public Formatter format(String format, Object... args)
+  {
+    return format(locale, format, args);
+  }
+
+  public IOException ioException()
+  {
+    return ioException;
+  }
+
+  public Locale locale()
+  {
+    if (closed)
+      throw new FormatterClosedException();
+    return locale;
+  }
+
+  public Appendable out()
+  {
+    if (closed)
+      throw new FormatterClosedException();
+    return out;
+  }
+
+  public String toString()
+  {
+    if (closed)
+      throw new FormatterClosedException();
+    return out.toString();
+  }
+}
Index: java/util/FormatterClosedException.java
===================================================================
RCS file: 
/cvsroot/classpath/classpath/java/util/Attic/FormatterClosedException.java,v
retrieving revision 1.1.2.3
diff -u -r1.1.2.3 FormatterClosedException.java
--- java/util/FormatterClosedException.java 21 Sep 2005 21:32:40 -0000 1.1.2.3
+++ java/util/FormatterClosedException.java 25 Sep 2005 02:52:26 -0000
@@ -41,7 +41,7 @@
 /** @since 1.5 */
 public class FormatterClosedException extends IllegalStateException
 {
-  private static final long serialVersionUID = 1811216L;
+  private static final long serialVersionUID = 18111216L;
 
   public FormatterClosedException()
   {
Index: java/util/IllegalFormatException.java
===================================================================
RCS file: 
/cvsroot/classpath/classpath/java/util/Attic/IllegalFormatException.java,v
retrieving revision 1.1.2.3
diff -u -r1.1.2.3 IllegalFormatException.java
--- java/util/IllegalFormatException.java 21 Sep 2005 21:32:40 -0000 1.1.2.3
+++ java/util/IllegalFormatException.java 25 Sep 2005 02:52:26 -0000
@@ -46,4 +46,9 @@
   IllegalFormatException()
   {
   }
+
+  IllegalFormatException(String msg)
+  {
+    super(msg);
+  }
 }
Index: java/util/UnknownFormatConversionException.java
===================================================================
RCS file: 
/cvsroot/classpath/classpath/java/util/Attic/UnknownFormatConversionException.java,v
retrieving revision 1.1.2.3
diff -u -r1.1.2.3 UnknownFormatConversionException.java
--- java/util/UnknownFormatConversionException.java 21 Sep 2005 21:32:40 -0000 
1.1.2.3
+++ java/util/UnknownFormatConversionException.java 25 Sep 2005 02:52:26 -0000
@@ -48,6 +48,7 @@
 
   public UnknownFormatConversionException(String s)
   {
+    super("unknown format conversion: " + s);
     this.s = s;
   }
 




reply via email to

[Prev in Thread] Current Thread [Next in Thread]