This is the mail archive of the java-patches@gcc.gnu.org mailing list for the Java project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

Patch: RFC: InputStreamReader and OutputStreamWriter


I'm not checking this in yet.

This patch is one way to add new java.nio-aware constructors to
InputStreamReader and OutputStreamWriter.  It does this by adding a
layer of indirection, in the classic mold.

Another approach would be to add a field to InputStreamReader and
OutputStreamWriter, and then have two paths in each method -- one that
does what the method does today, and another to forward to a Reader or
Writer from java.nio.

I don't much care which one we do.  One is easier to read, the other
may have better performance in common situations (I didn't look).

Sometime this year, hopefully, this code will go away or be rewritten
anyway, when we do the Big Classpath Merge.

Any preferences?

Tom

Index: ChangeLog
from  Tom Tromey  <tromey@redhat.com>
	* java/io/OutputStreamWriter.java (OutputStreamWriter): Rewrote.
	(ConvertWriter): New nested class.
	(OutputStreamWriter): New constructors.
	* java/io/InputStreamReader.java (InputStreamReader): Rewrote.
	(ConvertReader): New nested class.
	(InputStreamReader): New constructors.

Index: java/io/InputStreamReader.java
===================================================================
RCS file: /cvs/gcc/gcc/libjava/java/io/InputStreamReader.java,v
retrieving revision 1.19
diff -u -r1.19 InputStreamReader.java
--- java/io/InputStreamReader.java 9 Mar 2005 22:11:33 -0000 1.19
+++ java/io/InputStreamReader.java 11 Mar 2005 00:28:48 -0000
@@ -38,6 +38,10 @@
 
 package java.io;
 
+import java.nio.channels.Channels;
+import java.nio.charset.Charset;
+import java.nio.charset.CharsetDecoder;
+
 import gnu.gcj.convert.*;
 
 /**
@@ -87,20 +91,12 @@
  */
 public class InputStreamReader extends Reader
 {
-  BufferedInputStream in;
+  // Our implementation simply wraps another Reader -- either a
+  // ConvertReader or a java.nio Reader.
+  Reader in;
 
-  // Buffer of chars read from in and converted but not consumed.
-  char[] work;
-  // Next available character (in work buffer) to read.
-  int wpos;
-  // Last available character (in work buffer) to read.
-  int wcount;
-
-  /*
-   * This is the byte-character decoder class that does the reading and
-   * translation of bytes from the underlying stream.
-   */
-  BytesToUnicode converter;
+  // Name of the encoding we're using.
+  String encoding;
 
   /**
    * This method initializes a new instance of <code>InputStreamReader</code>
@@ -133,21 +129,45 @@
 
   private InputStreamReader(InputStream in, BytesToUnicode decoder)
   {
-    // FIXME: someone could pass in a BufferedInputStream whose buffer
-    // is smaller than the longest encoded character for this
-    // encoding.  We will probably go into an infinite loop in this
-    // case.  We probably ought to just have our own byte buffering
-    // here.
-    this.in = in instanceof BufferedInputStream
-              ? (BufferedInputStream) in
-              : new BufferedInputStream(in);
-    /* Don't need to call super(in) here as long as the lock gets set. */
-    this.lock = in;
-    converter = decoder;
-    converter.setInput(this.in.buf, 0, 0);
+    this.in = new ConvertReader(in, decoder);
+    // Don't need to call super(in) here as long as the lock gets set.
+    this.lock = this.in;
+    this.encoding = decoder.getName();
   }
 
   /**
+   * Creates an InputStreamReader that uses a decoder of the given
+   * charset to decode the bytes in the InputStream into
+   * @since 1.4
+   * characters.
+   */
+  public InputStreamReader(InputStream in, Charset charset)
+  {
+    /* FIXME: InputStream is wrapped in Channel which is read by a
+     * Reader-implementation for channels. However to fix this we
+     * need to completely move to NIO-style character
+     * encoding/decoding.
+     */
+    this.in = Channels.newReader(Channels.newChannel(in), charset.newDecoder(),
+				 -1);
+    this.encoding = charset.name();
+    this.lock = this.in;
+  }
+
+  /**
+   * Creates an InputStreamReader that uses the given charset decoder
+   * to decode the bytes in the InputStream into characters.
+   * @since 1.4
+   */
+  public InputStreamReader(InputStream in, CharsetDecoder decoder)
+  {
+    // FIXME: see {@link InputStreamReader(InputStream, Charset)
+    this.in = Channels.newReader(Channels.newChannel(in), decoder, -1);
+    this.encoding = decoder.charset().name();
+    this.lock = this.in;
+  }
+  
+  /**
    * This method closes this stream, as well as the underlying 
    * <code>InputStream</code>.
    *
@@ -160,8 +180,6 @@
 	if (in != null)
 	  in.close();
 	in = null;
-	work = null;
-	wpos = wcount = 0;
       }
   }
 
@@ -174,7 +192,7 @@
    */
   public String getEncoding()
   {
-    return in != null ? converter.getName() : null;
+    return encoding;
   }
 
   /**
@@ -194,14 +212,7 @@
       {
 	if (in == null)
 	  throw new IOException("Stream closed");
-
-	if (wpos < wcount)
-	  return true;
-
-	// According to the spec, an InputStreamReader is ready if its
-	// input buffer is not empty (above), or if bytes are
-	// available on the underlying byte stream.
-	return in.available () > 0;
+	return in.ready();
       }
   }
 
@@ -228,18 +239,7 @@
 	if (length == 0)
 	  return 0;
 
-	int wavail = wcount - wpos;
-	if (wavail <= 0)
-	  {
-	    // Nothing waiting, so refill their buffer.
-	    return refill(buf, offset, length);
-	  }
-
-	if (length > wavail)
-	  length = wavail;
-	System.arraycopy(work, wpos, buf, offset, length);
-	wpos += length;
-	return length;
+	return in.read(buf, offset, length);
       }
   }
 
@@ -256,42 +256,137 @@
       {
 	if (in == null)
 	  throw new IOException("Stream closed");
-
-	int wavail = wcount - wpos;
-	if (wavail <= 0)
-	  {
-	    // Nothing waiting, so refill our internal buffer.
-	    wpos = wcount = 0;
-	    if (work == null)
-	       work = new char[100];
-	    int count = refill(work, 0, work.length);
-	    if (count == -1)
-	      return -1;
-	    wcount += count;
-	  }
-
-	return work[wpos++];
+	return in.read();
       }
   }
 
-  // Read more bytes and convert them into the specified buffer.
-  // Returns the number of converted characters or -1 on EOF.
-  private int refill(char[] buf, int offset, int length) throws IOException
+  static class ConvertReader extends Reader
   {
-    for (;;)
-      {
-	// We have knowledge of the internals of BufferedInputStream
-	// here.  Eww.
-	// BufferedInputStream.refill() can only be called when
-	// `pos>=count'.
-	boolean r = in.pos < in.count || in.refill ();
-	if (! r)
-	  return -1;
-	converter.setInput(in.buf, in.pos, in.count);
-	int count = converter.read(buf, offset, length);
-	in.skip(converter.inpos - in.pos);
-	if (count > 0)
-	  return count;
-      }
+    BufferedInputStream in;
+
+    // Buffer of chars read from in and converted but not consumed.
+    char[] work;
+    // Next available character (in work buffer) to read.
+    int wpos;
+    // Last available character (in work buffer) to read.
+    int wcount;
+
+    /*
+     * This is the byte-character decoder class that does the reading and
+     * translation of bytes from the underlying stream.
+     */
+    BytesToUnicode converter;
+
+    ConvertReader(InputStream in)
+    {
+      this(in, BytesToUnicode.getDefaultDecoder());
+    }
+
+    ConvertReader(InputStream in, String encoding_name)
+      throws UnsupportedEncodingException
+    {
+      this(in, BytesToUnicode.getDecoder(encoding_name));
+    }
+
+    private ConvertReader(InputStream in, BytesToUnicode decoder)
+    {
+      // FIXME: someone could pass in a BufferedInputStream whose buffer
+      // is smaller than the longest encoded character for this
+      // encoding.  We will probably go into an infinite loop in this
+      // case.  We probably ought to just have our own byte buffering
+      // here.
+      this.in = in instanceof BufferedInputStream
+	? (BufferedInputStream) in
+	: new BufferedInputStream(in);
+      converter = decoder;
+      converter.setInput(this.in.buf, 0, 0);
+    }
+
+    public void close() throws IOException
+    {
+      if (in != null)
+	in.close();
+      in = null;
+      work = null;
+      wpos = wcount = 0;
+    }
+
+    public boolean ready() throws IOException
+    {
+      if (in == null)
+	throw new IOException("Stream closed");
+
+      if (wpos < wcount)
+	return true;
+
+      // According to the spec, a ConvertReader is ready if its
+      // input buffer is not empty (above), or if bytes are
+      // available on the underlying byte stream.
+      return in.available () > 0;
+    }
+
+    public int read (char[] buf, int offset, int length) throws IOException
+    {
+      if (in == null)
+	throw new IOException("Stream closed");
+
+      if (length == 0)
+	return 0;
+
+      int wavail = wcount - wpos;
+      if (wavail <= 0)
+	{
+	  // Nothing waiting, so refill their buffer.
+	  return refill(buf, offset, length);
+	}
+
+      if (length > wavail)
+	length = wavail;
+      System.arraycopy(work, wpos, buf, offset, length);
+      wpos += length;
+      return length;
+    }
+
+    public int read() throws IOException
+    {
+      if (in == null)
+	throw new IOException("Stream closed");
+
+      int wavail = wcount - wpos;
+      if (wavail <= 0)
+	{
+	  // Nothing waiting, so refill our internal buffer.
+	  wpos = wcount = 0;
+	  if (work == null)
+	    work = new char[100];
+	  int count = refill(work, 0, work.length);
+	  if (count == -1)
+	    return -1;
+	  wcount += count;
+	}
+
+      return work[wpos++];
+    }
+
+    // Read more bytes and convert them into the specified buffer.
+    // Returns the number of converted characters or -1 on EOF.
+    private int refill(char[] buf, int offset, int length) throws IOException
+    {
+      for (;;)
+	{
+	  // We have knowledge of the internals of BufferedInputStream
+	  // here.  Eww.
+	  // BufferedInputStream.refill() can only be called when
+	  // `pos>=count'.
+	  boolean r = in.pos < in.count || in.refill ();
+	  if (! r)
+	    return -1;
+	  converter.setInput(in.buf, in.pos, in.count);
+	  int count = converter.read(buf, offset, length);
+	  in.skip(converter.inpos - in.pos);
+	  if (count > 0)
+	    return count;
+	}
+    }
   }
 }
Index: java/io/OutputStreamWriter.java
===================================================================
RCS file: /cvs/gcc/gcc/libjava/java/io/OutputStreamWriter.java,v
retrieving revision 1.18
diff -u -r1.18 OutputStreamWriter.java
--- java/io/OutputStreamWriter.java 17 Feb 2005 07:48:32 -0000 1.18
+++ java/io/OutputStreamWriter.java 11 Mar 2005 00:28:48 -0000
@@ -38,6 +38,9 @@
 
 package java.io;
 
+import java.nio.channels.Channels;
+import java.nio.charset.Charset;
+import java.nio.charset.CharsetEncoder;
 import gnu.gcj.convert.UnicodeToBytes;
 
 /**
@@ -75,27 +78,19 @@
  */
 public class OutputStreamWriter extends Writer
 {
-  BufferedOutputStream out;
+  // Our implementation simply wraps another Writer -- either a
+  // ConvertWriter or a java.nio Writer.
+  private Writer out;
 
-  /**
-   * This is the byte-character encoder class that does the writing and
-   * translation of characters to bytes before writing to the underlying
-   * class.
-   */
-  UnicodeToBytes converter;
-
-  /* Temporary buffer. */
-  private char[] work;
-  private int wcount;
+  // Name of the encoding.
+  private String encoding;
 
   private OutputStreamWriter(OutputStream out, UnicodeToBytes encoder)
   {
-    this.out = out instanceof BufferedOutputStream 
-	       ? (BufferedOutputStream) out
-	       : new BufferedOutputStream(out, 250);
-    /* Don't need to call super(out) here as long as the lock gets set. */
-    this.lock = out;
-    this.converter = encoder;
+    this.out = new ConvertWriter(out, encoder);
+    this.encoding = encoder.getName();
+    // Don't need to call super(out) here as long as the lock gets set.
+    this.lock = this.out;
   }
 
   /**
@@ -129,6 +124,41 @@
   }
 
   /**
+   * This method initializes a new instance of <code>OutputStreamWriter</code>
+   * to write to the specified stream using the given character set.
+   *
+   * @param out The <code>OutputStream</code> to write to
+   * @param c The <code>Charset</code> to use
+   *
+   * @since 1.5
+   */
+  public OutputStreamWriter (OutputStream out, Charset c)
+  {
+    this.out = Channels.newWriter(Channels.newChannel(out), c.newEncoder(),
+				  -1);
+    this.encoding = c.name();
+    // Don't need to call super(out) here as long as the lock gets set.
+    this.lock = this.out;
+  }
+
+  /**
+   * This method initializes a new instance of <code>OutputStreamWriter</code>
+   * to write to the specified stream using the given charset encoder.
+   *
+   * @param out The <code>OutputStream</code> to write to
+   * @param c The <code>Charset</code> to use
+   *
+   * @since 1.5
+   */
+  public OutputStreamWriter (OutputStream out, CharsetEncoder encoder)
+  {
+    this.out = Channels.newWriter(Channels.newChannel(out), encoder, -1);
+    this.encoding = encoder.charset().name();
+    // Don't need to call super(out) here as long as the lock gets set.
+    this.lock = this.out;
+  }
+
+  /**
    * This method closes this stream, and the underlying 
    * <code>OutputStream</code>
    *
@@ -144,7 +174,6 @@
 	    out.close();
 	    out = null;
 	  }
-	work = null;
       }
   }
 
@@ -157,7 +186,7 @@
    */
   public String getEncoding ()
   {
-    return out != null ? converter.getName() : null;
+    return encoding;
   }
 
   /**
@@ -171,12 +200,6 @@
       {
 	if (out == null)
 	  throw new IOException("Stream closed");
-
-	if (wcount > 0)
-	  {
-	    writeChars(work, 0, wcount);
-	    wcount = 0;
-	  }
 	out.flush();
       }
   }
@@ -198,46 +221,7 @@
       {
 	if (out == null)
 	  throw new IOException("Stream closed");
-
-	if (wcount > 0)
-	  {
-	    writeChars(work, 0, wcount);
-	    wcount = 0;
-	  }
-	writeChars(buf, offset, count);
-      }
-  }
-
-  /*
-   * Writes characters through to the inferior BufferedOutputStream.
-   * Ignores wcount and the work buffer.
-   */
-  private void writeChars(char[] buf, int offset, int count)
-    throws IOException
-  {
-    while (count > 0 || converter.havePendingBytes())
-      {
-	// We must flush if out.count == out.buf.length.
-	// It is probably a good idea to flush if out.buf is almost full.
-	// This test is an approximation for "almost full".
-	if (out.count + count >= out.buf.length)
-	  {
-	    out.flush();
-	    if (out.count != 0)
-	      throw new IOException("unable to flush output byte buffer");
-	  }
-	converter.setOutput(out.buf, out.count);
-	int converted = converter.write(buf, offset, count);
-	// Flush if we cannot make progress.
-	if (converted == 0 && out.count == converter.count)
-	  {
-	    out.flush();
-	    if (out.count != 0)
-	      throw new IOException("unable to flush output byte buffer");
-	  }
-	offset += converted;
-	count -= converted;
-	out.count = converter.count;
+	out.write(buf, offset, count);
       }
   }
 
@@ -259,28 +243,7 @@
       {
 	if (out == null)
 	  throw new IOException("Stream closed");
-
-	if (work == null)
-	  work = new char[100];
-	int wlength = work.length;
-	while (count > 0)
-	  {
-	    int size = count;
-	    if (wcount + size > wlength)
-	      {
-		if (2*wcount > wlength)
-		  {
-		    writeChars(work, 0, wcount);
-		    wcount = 0;
-		  }
-		if (wcount + size > wlength)
-		  size = wlength - wcount;
-	      }
-	    str.getChars(offset, offset+size, work, wcount);
-	    offset += size;
-	    count -= size;
-	    wcount += size;
-	  }
+	out.write(str, offset, count);
       }
   }
 
@@ -297,17 +260,165 @@
       {
 	if (out == null)
 	  throw new IOException("Stream closed");
-
-	if (work == null)
-	  work = new char[100];
-	if (wcount >= work.length)
-	  {
-	    writeChars(work, 0, wcount);
-	    wcount = 0;
-	  }
-	work[wcount++] = (char) ch;
+	out.write(ch);
       }
   }
 
-} // class OutputStreamWriter
+  // This is here and not in gnu.gcj.convert because it accesses
+  // package-private fields of BufferedOutputStream.  This is a helper
+  // that does the work of writing via a UnicodeToBytes object.
+  static class ConvertWriter extends Writer
+  {
+    BufferedOutputStream out;
 
+    /**
+     * This is the byte-character encoder class that does the writing and
+     * translation of characters to bytes before writing to the underlying
+     * class.
+     */
+    UnicodeToBytes converter;
+
+    /* Temporary buffer. */
+    private char[] work;
+    private int wcount;
+
+    private ConvertWriter(OutputStream out, UnicodeToBytes encoder)
+    {
+      // Note that we don't set or use a lock.  Our enclosing class
+      // always acquires its own lock.
+      this.out = out instanceof BufferedOutputStream 
+	? (BufferedOutputStream) out
+	: new BufferedOutputStream(out, 250);
+      this.converter = encoder;
+    }
+
+    ConvertWriter (OutputStream out, String encoding_scheme) 
+      throws UnsupportedEncodingException
+    {
+      this(out, UnicodeToBytes.getEncoder(encoding_scheme));
+    }
+
+    ConvertWriter (OutputStream out)
+    {
+      this(out, UnicodeToBytes.getDefaultEncoder());
+    }
+
+    ConvertWriter (OutputStream out, Charset c)
+    {
+      this(out, UnicodeToBytes.getDefaultEncoder());
+    }
+
+    public void close () throws IOException
+    {
+      if (out != null)
+	{
+	  flush();
+	  out.close();
+	  out = null;
+	}
+      work = null;
+    }
+
+    public void flush () throws IOException
+    {
+      if (out == null)
+	throw new IOException("Stream closed");
+
+      if (wcount > 0)
+	{
+	  writeChars(work, 0, wcount);
+	  wcount = 0;
+	}
+      out.flush();
+    }
+
+    public void write (char[] buf, int offset, int count) throws IOException
+    {
+      if (out == null)
+	throw new IOException("Stream closed");
+
+      if (wcount > 0)
+	{
+	  writeChars(work, 0, wcount);
+	  wcount = 0;
+	}
+      writeChars(buf, offset, count);
+    }
+
+    /*
+     * Writes characters through to the inferior BufferedOutputStream.
+     * Ignores wcount and the work buffer.
+     */
+    private void writeChars(char[] buf, int offset, int count)
+      throws IOException
+    {
+      while (count > 0 || converter.havePendingBytes())
+	{
+	  // We must flush if out.count == out.buf.length.
+	  // It is probably a good idea to flush if out.buf is almost full.
+	  // This test is an approximation for "almost full".
+	  if (out.count + count >= out.buf.length)
+	    {
+	      out.flush();
+	      if (out.count != 0)
+		throw new IOException("unable to flush output byte buffer");
+	    }
+	  converter.setOutput(out.buf, out.count);
+	  int converted = converter.write(buf, offset, count);
+	  // Flush if we cannot make progress.
+	  if (converted == 0 && out.count == converter.count)
+	    {
+	      out.flush();
+	      if (out.count != 0)
+		throw new IOException("unable to flush output byte buffer");
+	    }
+	  offset += converted;
+	  count -= converted;
+	  out.count = converter.count;
+	}
+    }
+
+    public void write (String str, int offset, int count) throws IOException
+    {
+      if (out == null)
+	throw new IOException("Stream closed");
+
+      if (work == null)
+	work = new char[100];
+      int wlength = work.length;
+      while (count > 0)
+	{
+	  int size = count;
+	  if (wcount + size > wlength)
+	    {
+	      if (2*wcount > wlength)
+		{
+		  writeChars(work, 0, wcount);
+		  wcount = 0;
+		}
+	      if (wcount + size > wlength)
+		size = wlength - wcount;
+	    }
+	  str.getChars(offset, offset+size, work, wcount);
+	  offset += size;
+	  count -= size;
+	  wcount += size;
+	}
+    }
+
+    public void write (int ch) throws IOException
+    {
+      if (out == null)
+	throw new IOException("Stream closed");
+
+      if (work == null)
+	work = new char[100];
+      if (wcount >= work.length)
+	{
+	  writeChars(work, 0, wcount);
+	  wcount = 0;
+	}
+      work[wcount++] = (char) ch;
+    }
+  }
+}


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]