Ok, I'm reverting this for now. It shows problems in Mauve, I was able
to fix those, but even then it still seems to break Dacapo's jython
benchmark. Right now I have no time to fix that, so I revert this. I'll
look at that later when I have more time (e.g. next week after my thesis
is finished...)

2007-03-28  Roman Kennke  <[EMAIL PROTECTED]>

        * java/io/OutputStreamWriter.java,
        * java/io/InputStreamReader.java: Revert big stream patch due to
        problems.

/Roman

-- 
http://kennke.org/blog/
Index: java/io/InputStreamReader.java
===================================================================
RCS file: /cvsroot/classpath/classpath/java/io/InputStreamReader.java,v
retrieving revision 1.32
diff -u -1 -5 -r1.32 InputStreamReader.java
--- java/io/InputStreamReader.java	27 Mar 2007 09:51:26 -0000	1.32
+++ java/io/InputStreamReader.java	28 Mar 2007 20:27:53 -0000
@@ -86,203 +86,226 @@
  * <p>
  * Due to a deficiency the Java class library design, there is no standard
  * way for an application to install its own byte-character encoding.
  *
  * @see BufferedReader
  * @see InputStream
  *
  * @author Robert Schuster
  * @author Aaron M. Renn ([EMAIL PROTECTED])
  * @author Per Bothner ([EMAIL PROTECTED])
  * @date April 22, 1998.  
  */
 public class InputStreamReader extends Reader
 {
   /**
-   * The default buffer size.
-   */
-  private final static int BUFFER_SIZE = 1024;
-
-  /**
    * The input stream.
    */
   private InputStream in;
 
   /**
    * The charset decoder.
    */
   private CharsetDecoder decoder;
 
   /**
    * End of stream reached.
    */
   private boolean isDone = false;
 
   /**
+   * Need this.
+   */
+  private float maxBytesPerChar;
+
+  /**
    * Buffer holding surplus loaded bytes (if any)
    */
   private ByteBuffer byteBuffer;
 
   /**
    * java.io canonical name of the encoding.
    */
   private String encoding;
 
   /**
-   * One char as array to be used in [EMAIL PROTECTED] #read()}.
+   * We might decode to a 2-char UTF-16 surrogate, which won't fit in the
+   * output buffer. In this case we need to save the surrogate char.
    */
-  private char[] oneChar = new char[1];
+  private char savedSurrogate;
+  private boolean hasSavedSurrogate = false;
 
   /**
-   * The last char array that has been passed to read(char[],int,int). This
-   * is used to cache the associated CharBuffer because read(char[],int,int)
-   * is usually called with the same array repeatedly and we don't want to
-   * allocate a new CharBuffer object on each call.
+   * A byte array to be reused in read(byte[], int, int).
    */
-  private char[] lastArray;
+  private byte[] bytesCache;
 
   /**
-   * The cached CharBuffer associated with the above array.
+   * Locks the bytesCache above in read(byte[], int, int).
    */
-  private CharBuffer lastBuffer;
+  private Object cacheLock = new Object();
 
   /**
    * This method initializes a new instance of <code>InputStreamReader</code>
    * to read from the specified stream using the default encoding.
    *
    * @param in The <code>InputStream</code> to read from 
    */
   public InputStreamReader(InputStream in)
   {
     if (in == null)
       throw new NullPointerException();
-
     this.in = in;
-
-    String encodingName = SystemProperties.getProperty("file.encoding");
-    try
-      {
-        Charset cs = EncodingHelper.getCharset(encodingName);
-        decoder = cs.newDecoder();
-        // The encoding should be the old name, if such exists.
-        encoding = EncodingHelper.getOldCanonical(cs.name());
-      }
-    catch(RuntimeException e)
-      {
-        // For bootstrapping problems only.
-        decoder = null;
-        encoding = "ISO8859_1";
-      }
-    catch (UnsupportedEncodingException ex)
-      {
-        Charset cs = EncodingHelper.getDefaultCharset();
-        decoder = cs.newDecoder();
-        // The encoding should be the old name, if such exists.
-        encoding = EncodingHelper.getOldCanonical(cs.name());
-      }    
-    initDecoderAndBuffer();
+    try 
+	{ 
+	  encoding = SystemProperties.getProperty("file.encoding");
+	  // Don't use NIO if avoidable
+	  if(EncodingHelper.isISOLatin1(encoding))
+	    {
+	      encoding = "ISO8859_1";
+	      maxBytesPerChar = 1f;
+	      decoder = null;
+	      return;
+	    }
+	  Charset cs = EncodingHelper.getCharset(encoding);
+	  decoder = cs.newDecoder();
+	  encoding = EncodingHelper.getOldCanonical(cs.name());
+	  try {
+	      maxBytesPerChar = cs.newEncoder().maxBytesPerChar();
+	  } catch(UnsupportedOperationException _){
+	      maxBytesPerChar = 1f;
+	  } 
+	  decoder.onMalformedInput(CodingErrorAction.REPLACE);
+	  decoder.onUnmappableCharacter(CodingErrorAction.REPLACE);
+	  decoder.reset();
+	} catch(RuntimeException e) {
+	  encoding = "ISO8859_1";
+	  maxBytesPerChar = 1f;
+	  decoder = null;
+	} catch(UnsupportedEncodingException e) {
+	  encoding = "ISO8859_1";
+	  maxBytesPerChar = 1f;
+	  decoder = null;
+	}
   }
 
   /**
    * This method initializes a new instance of <code>InputStreamReader</code>
    * to read from the specified stream using a caller supplied character
    * encoding scheme.  Note that due to a deficiency in the Java language
    * design, there is no way to determine which encodings are supported.
    * 
    * @param in The <code>InputStream</code> to read from
    * @param encoding_name The name of the encoding scheme to use
    *
    * @exception UnsupportedEncodingException If the encoding scheme 
    * requested is not available.
    */
   public InputStreamReader(InputStream in, String encoding_name)
     throws UnsupportedEncodingException
   {
-    if (in == null || encoding_name == null)
+    if (in == null
+        || encoding_name == null)
       throw new NullPointerException();
-
+    
     this.in = in;
-
-    try
+    // Don't use NIO if avoidable
+    if(EncodingHelper.isISOLatin1(encoding_name))
       {
-        Charset cs = EncodingHelper.getCharset(encoding_name);
-        decoder = cs.newDecoder();
-        // The encoding should be the old name, if such exists.
-        encoding = EncodingHelper.getOldCanonical(cs.name());
-      }
-    catch(RuntimeException e)
-      {
-        // For bootstrapping problems only.
-        decoder = null;
-        encoding = "ISO8859_1";
+	encoding = "ISO8859_1";
+	maxBytesPerChar = 1f;
+	decoder = null;
+	return;
       }
-
-    initDecoderAndBuffer();
+    try {
+      Charset cs = EncodingHelper.getCharset(encoding_name);
+      try {
+        maxBytesPerChar = cs.newEncoder().maxBytesPerChar();
+      } catch(UnsupportedOperationException _){
+	maxBytesPerChar = 1f;
+      } 
+
+      decoder = cs.newDecoder();
+      decoder.onMalformedInput(CodingErrorAction.REPLACE);
+      decoder.onUnmappableCharacter(CodingErrorAction.REPLACE);
+      decoder.reset();
+
+      // The encoding should be the old name, if such exists.
+      encoding = EncodingHelper.getOldCanonical(cs.name());
+    } catch(RuntimeException e) {
+      encoding = "ISO8859_1";
+      maxBytesPerChar = 1f;
+      decoder = null;
+    }
   }
 
   /**
    * Creates an InputStreamReader that uses a decoder of the given
    * charset to decode the bytes in the InputStream into
    * characters.
    * 
    * @since 1.4
    */
-  public InputStreamReader(InputStream in, Charset charset)
-  {
-    if (in == null || charset == null)
+  public InputStreamReader(InputStream in, Charset charset) {
+    if (in == null)
       throw new NullPointerException();
-
     this.in = in;
     decoder = charset.newDecoder();
+
+    try {
+      maxBytesPerChar = charset.newEncoder().maxBytesPerChar();
+    } catch(UnsupportedOperationException _){
+      maxBytesPerChar = 1f;
+    }
+
+    decoder.onMalformedInput(CodingErrorAction.REPLACE);
+    decoder.onUnmappableCharacter(CodingErrorAction.REPLACE);
+    decoder.reset();
     encoding = EncodingHelper.getOldCanonical(charset.name());
-    initDecoderAndBuffer();
   }
 
   /**
    * 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)
-  {
-    if (in == null || decoder == null)
+  public InputStreamReader(InputStream in, CharsetDecoder decoder) {
+    if (in == null)
       throw new NullPointerException();
-
     this.in = in;
     this.decoder = decoder;
-    encoding = EncodingHelper.getOldCanonical(decoder.charset().name());
-    initDecoderAndBuffer();
-  }
 
-  /**
-   * Initializes the decoder and the input buffer.
-   */
-  private void initDecoderAndBuffer()
-  {
-    if (decoder != null)
-      {
-        decoder.onMalformedInput(CodingErrorAction.REPLACE);
-        decoder.onUnmappableCharacter(CodingErrorAction.REPLACE);
-        decoder.reset();
-      }
-
-    byteBuffer = ByteBuffer.allocate(BUFFER_SIZE);
-    // No bytes available initially.
-    byteBuffer.position(byteBuffer.limit());
+    Charset charset = decoder.charset();
+    try {
+      if (charset == null)
+        maxBytesPerChar = 1f;
+      else
+        maxBytesPerChar = charset.newEncoder().maxBytesPerChar();
+    } catch(UnsupportedOperationException _){
+	maxBytesPerChar = 1f;
+    } 
+
+    decoder.onMalformedInput(CodingErrorAction.REPLACE);
+    decoder.onUnmappableCharacter(CodingErrorAction.REPLACE);
+    decoder.reset();
+    if (charset == null)
+      encoding = "US-ASCII";
+    else
+      encoding = EncodingHelper.getOldCanonical(decoder.charset().name());      
   }
-
+  
   /**
    * This method closes this stream, as well as the underlying 
    * <code>InputStream</code>.
    *
    * @exception IOException If an error occurs
    */
   public void close() throws IOException
   {
     synchronized (lock)
       {
 	// Makes sure all intermediate data is released by the decoder.
 	if (decoder != null)
 	   decoder.reset();
 	if (in != null)
 	   in.close();
@@ -307,217 +330,180 @@
   /**
    * This method checks to see if the stream is ready to be read.  It
    * will return <code>true</code> if is, or <code>false</code> if it is not.
    * If the stream is not ready to be read, it could (although is not required
    * to) block on the next read attempt.
    *
    * @return <code>true</code> if the stream is ready to be read, 
    * <code>false</code> otherwise
    *
    * @exception IOException If an error occurs
    */
   public boolean ready() throws IOException
   {
     if (in == null)
       throw new IOException("Reader has been closed");
-    return byteBuffer.hasRemaining() || in.available() != 0;
+    
+    return in.available() != 0;
   }
 
   /**
    * This method reads up to <code>length</code> characters from the stream into
    * the specified array starting at index <code>offset</code> into the
    * array.
    *
    * @param buf The character array to recieve the data read
    * @param offset The offset into the array to start storing characters
    * @param length The requested number of characters to read.
    *
    * @return The actual number of characters read, or -1 if end of stream.
    *
    * @exception IOException If an error occurs
    */
   public int read(char[] buf, int offset, int length) throws IOException
   {
     if (in == null)
       throw new IOException("Reader has been closed");
     if (isDone)
       return -1;
-
-    CharBuffer outBuffer = getCharBuffer(buf, offset, length);
-    int startPos = outBuffer.position();
-    int remaining = outBuffer.remaining();
-    int start = remaining;
-    CoderResult cr = null;
-    // Try to decode as long as the output buffer can hold more data.
-    // Decode at least one character (block when necessary).
-    boolean moreAvailable = true;
-    while (remaining > 0 && moreAvailable)
+    if(decoder != null)
       {
-        if (byteBuffer.remaining() == 0
-            || (cr != null && (cr.isUnderflow())))
+	int totalBytes = (int)((double) length * maxBytesPerChar);
+        if (byteBuffer != null)
+          totalBytes = Math.max(totalBytes, byteBuffer.remaining());
+	byte[] bytes;
+        // Fetch cached bytes array if available and big enough.
+        synchronized(cacheLock)
+          {
+            bytes = bytesCache;
+            if (bytes == null || bytes.length < totalBytes)
+              bytes = new byte[totalBytes];
+            else
+              bytesCache = null;
+          }
+
+	int remaining = 0;
+	if(byteBuffer != null)
+	{
+	    remaining = byteBuffer.remaining();
+	    byteBuffer.get(bytes, 0, remaining);
+	}
+	int read;
+	if(totalBytes - remaining > 0)
+	  {
+	    read = in.read(bytes, remaining, totalBytes - remaining);
+	    if(read == -1){
+	      read = remaining;
+	      isDone = true;
+	    } else
+	      read += remaining;
+	  } else 
+            read = remaining;
+	byteBuffer = ByteBuffer.wrap(bytes, 0, read);	
+	CharBuffer cb = CharBuffer.wrap(buf, offset, length);
+	int startPos = cb.position();
+
+ 	if(hasSavedSurrogate){
+ 	    hasSavedSurrogate = false;
+ 	    cb.put(savedSurrogate);
+	    read++;
+ 	}
+
+	CoderResult cr = decoder.decode(byteBuffer, cb, isDone);
+	decoder.reset();
+	// 1 char remains which is the first half of a surrogate pair.
+	if(cr.isOverflow() && cb.hasRemaining()){
+	    CharBuffer overflowbuf = CharBuffer.allocate(2);
+	    cr = decoder.decode(byteBuffer, overflowbuf, isDone);
+	    overflowbuf.flip();
+	    if(overflowbuf.hasRemaining())
+	    {
+	      cb.put(overflowbuf.get());
+	      savedSurrogate = overflowbuf.get();
+	      hasSavedSurrogate = true;	    
+	      isDone = false;
+	    }
+	}
+
+	if(byteBuffer.hasRemaining()) {
+	    byteBuffer.compact();
+	    byteBuffer.flip();	  
+	    isDone = false;
+	} else
+	    byteBuffer = null;
+
+	read = cb.position() - startPos;
+
+        // Put cached bytes array back if we are finished and the cache
+        // is null or smaller than the used bytes array.
+        synchronized (cacheLock)
           {
-            // Block when we have not yet decoded at least one character.
-            boolean block = remaining == start;
-            moreAvailable = refillInputBuffer(block);
+            if (byteBuffer == null
+                && (bytesCache == null || bytesCache.length < bytes.length))
+              bytesCache = bytes;
           }
-        cr = decode(outBuffer);
-        remaining = outBuffer.remaining();
+        return (read <= 0) ? -1 : read;
       }
-    return outBuffer.position() - startPos;
+    else
+      {
+	byte[] bytes;
+        // Fetch cached bytes array if available and big enough.
+        synchronized (cacheLock)
+          {
+            bytes = bytesCache;
+            if (bytes == null || length < bytes.length)
+              bytes = new byte[length];
+            else
+              bytesCache = null;
+          }
+
+	int read = in.read(bytes);
+	for(int i=0;i<read;i++)
+          buf[offset+i] = (char)(bytes[i]&0xFF);
+
+        // Put back byte array into cache if appropriate.
+        synchronized (cacheLock)
+          {
+            if (bytesCache == null || bytesCache.length < bytes.length)
+              bytesCache = bytes;
+          }
+	return read;
+    }
   }
 
   /**
    * Reads an char from the input stream and returns it
    * as an int in the range of 0-65535.  This method also will return -1 if
    * the end of the stream has been reached.
    * <p>
    * This method will block until the char can be read.
    *
    * @return The char read or -1 if end of stream
    *
    * @exception IOException If an error occurs
    */
   public int read() throws IOException
   {
-    int count = read(oneChar, 0, 1);
-    return count > 0 ? oneChar[0] : -1;
+    char[] buf = new char[1];
+    int count = read(buf, 0, 1);
+    return count > 0 ? buf[0] : -1;
   }
 
   /**
    * Skips the specified number of chars in the stream.  It
    * returns the actual number of chars skipped, which may be less than the
    * requested amount.
    *
    * @param count The requested number of chars to skip
    *
    * @return The actual number of chars skipped.
    *
    * @exception IOException If an error occurs
    */
    public long skip(long count) throws IOException
    {
      if (in == null)
        throw new IOException("Reader has been closed");
-
+     
      return super.skip(count);
    }
-
-  /**
-   * Returns a CharBuffer that wraps the specified char array. This tries
-   * to return a cached instance because usually the read() method is called
-   * repeatedly with the same char array instance, or the no-arg read
-   * method is called repeatedly which uses the oneChar field of this class
-   * over and over again.
-   *
-   * @param buf the array to wrap
-   * @param offset the offset
-   * @param length the length
-   *
-   * @return a prepared CharBuffer to write to
-   */
-  private final CharBuffer getCharBuffer(char[] buf, int offset, int length)
-  {
-    CharBuffer outBuffer;
-    if (lastArray == buf)
-      {
-        outBuffer = lastBuffer;
-        outBuffer.position(offset);
-        outBuffer.limit(offset + length);
-      }
-    else
-      {
-        lastArray = buf;
-        lastBuffer = CharBuffer.wrap(buf, offset, length);
-        outBuffer = lastBuffer;
-      }
-    return outBuffer;
-  }
-
-  /**
-   * Refills the input buffer by reading a chunk of bytes from the underlying
-   * input stream
-   *
-   * @param block true when this method is allowed to block when necessary,
-   *        false otherwise
-   *
-   * @return true when data has been read, false when no data has been
-   *         available without blocking
-   *
-   * @throws IOException from the underlying stream
-   */
-  private final boolean refillInputBuffer(boolean block)
-    throws IOException
-  {
-    boolean refilled = false;
-
-    // Refill input buffer.
-    byteBuffer.compact();
-    if (byteBuffer.hasArray())
-      {
-        byte[] buffer = byteBuffer.array();
-        int offs = byteBuffer.arrayOffset();
-        int pos = byteBuffer.position();
-        int rem = byteBuffer.remaining();
-        int avail = in.available();
-        int readBytes = 0;
-        int len;
-        // Try to not block.
-        if (block)
-          len = avail != 0 ? Math.min(avail, rem) : rem;
-        else
-          len = Math.min(avail, rem);
-        readBytes = in.read(buffer, offs + pos, len);
-
-        if (readBytes > 0)
-          {
-            byteBuffer.position(pos + readBytes);
-            byteBuffer.limit(pos + readBytes);
-            refilled = true;
-          }
-        isDone = readBytes == -1;
-      }
-    else
-      {
-        assert false;
-        // Shouldn't happen, but anyway...
-        byte[] buffer = new byte[byteBuffer.limit()
-                                 - byteBuffer.position()];
-        int readBytes = in.read(buffer);
-        isDone = readBytes == -1;
-        byteBuffer.put(buffer);
-      }
-    byteBuffer.flip();
-    return refilled;
-  }
-
-  /**
-   * Decodes the current byteBuffer into the specified outBuffer. This takes
-   * care of the corner case when we have no decoder (i.e. bootstrap problems)
-   * and performs a primitive Latin1 decoding in this case.
-   *
-   * @param outBuffer the buffer to decode to
-   *
-   * @return the coder result
-   */
-  private CoderResult decode(CharBuffer outBuffer)
-  {
-    CoderResult cr;
-    if (decoder != null)
-      {
-        cr = decoder.decode(byteBuffer, outBuffer, false);
-      }
-    else
-      {
-        // Perform primitive Latin1 decoding.
-        while (outBuffer.hasRemaining() && byteBuffer.hasRemaining())
-          {
-            outBuffer.put((char) (0xff & byteBuffer.get()));
-          }
-        // One of the buffers must be drained.
-        if (! outBuffer.hasRemaining())
-          cr = CoderResult.OVERFLOW;
-        else
-          cr = CoderResult.UNDERFLOW;
-      }
-    return cr;
-  }
 }
Index: java/io/OutputStreamWriter.java
===================================================================
RCS file: /cvsroot/classpath/classpath/java/io/OutputStreamWriter.java,v
retrieving revision 1.23
diff -u -1 -5 -r1.23 OutputStreamWriter.java
--- java/io/OutputStreamWriter.java	27 Mar 2007 09:51:26 -0000	1.23
+++ java/io/OutputStreamWriter.java	28 Mar 2007 20:27:53 -0000
@@ -30,34 +30,35 @@
 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.io;
 
 import gnu.java.nio.charset.EncodingHelper;
 
 import java.nio.ByteBuffer;
 import java.nio.CharBuffer;
+import java.nio.charset.CharacterCodingException;
 import java.nio.charset.Charset;
 import java.nio.charset.CharsetEncoder;
-import java.nio.charset.CoderResult;
 import java.nio.charset.CodingErrorAction;
+import java.nio.charset.MalformedInputException;
 
 /**
  * This class writes characters to an output stream that is byte oriented
  * It converts the chars that are written to bytes using an encoding layer,
  * which is specific to a particular encoding standard.  The desired
  * encoding can either be specified by name, or if no encoding is specified,
  * the system default encoding will be used.  The system default encoding
  * name is determined from the system property <code>file.encoding</code>.
  * The only encodings that are guaranteed to be available are "8859_1"
  * (the Latin-1 character set) and "UTF8".  Unfortunately, Java does not
  * provide a mechanism for listing the encodings that are supported in
  * a given implementation.
  * <p>
  * Here is a list of standard encoding names that may be available:
  * <p>
@@ -71,210 +72,192 @@
  * <li>8859_7 (ISO-8859-7/Latin-7)
  * <li>8859_8 (ISO-8859-8/Latin-8)
  * <li>8859_9 (ISO-8859-9/Latin-9)
  * <li>ASCII (7-bit ASCII)
  * <li>UTF8 (UCS Transformation Format-8)
  * <li>More Later
  * </ul>
  *
  * @author Aaron M. Renn ([EMAIL PROTECTED])
  * @author Per Bothner ([EMAIL PROTECTED])
  * @date April 17, 1998.  
  */
 public class OutputStreamWriter extends Writer
 {
   /**
-   * The default buffer size.
-   */
-  private final static int BUFFER_SIZE = 1024;
-
-  /**
    * The output stream.
    */
   private OutputStream out;
 
   /**
    * The charset encoder.
    */
   private CharsetEncoder encoder;
 
   /**
    * java.io canonical name of the encoding.
    */
   private String encodingName;
 
   /**
-   * This buffer receives the encoded data and is flushed to the underlying
-   * stream when it gets too full.
-   */
-  private ByteBuffer outputBuffer;
-
-  /**
-   * A one-char array to be reused in read().
-   */
-  private char[] oneChar = new char[1];
-
-  /**
-   * The last char array that has been passed to write(char[],int,int). This
-   * is used to cache the associated CharBuffer because write(char[],int,int)
-   * is usually called with the same array repeatedly and we don't want to
-   * allocate a new CharBuffer object on each call.
-   */
-  private Object lastArray;
-
-  /**
-   * The cached char buffer.
+   * Buffer output before character conversion as it has costly overhead.
    */
-  private CharBuffer lastBuffer;
+  private CharBuffer outputBuffer;
+  private final static int BUFFER_SIZE = 1024;
 
   /**
    * This method initializes a new instance of <code>OutputStreamWriter</code>
    * to write to the specified stream using a caller supplied character
    * encoding scheme.  Note that due to a deficiency in the Java language
    * design, there is no way to determine which encodings are supported.
    *
    * @param out The <code>OutputStream</code> to write to
    * @param encoding_scheme The name of the encoding scheme to use for 
    * character to byte translation
    *
    * @exception UnsupportedEncodingException If the named encoding is 
    * not available.
    */
   public OutputStreamWriter (OutputStream out, String encoding_scheme) 
     throws UnsupportedEncodingException
   {
-    if (out == null || encoding_scheme == null)
-      throw new NullPointerException();
-
     this.out = out;
     try 
       {
-        /*
-         * Workraround for encodings with a byte-order-mark.
-         * We only want to write it once per stream.
-         */
-        try 
-          {
-            if(encoding_scheme.equalsIgnoreCase("UnicodeBig")
-                ||encoding_scheme.equalsIgnoreCase("UTF-16") ||
-                encoding_scheme.equalsIgnoreCase("UTF16"))
-              {
-                encoding_scheme = "UTF-16BE";	  
-                out.write((byte)0xFE);
-                out.write((byte)0xFF);
-              } 
-            else if(encoding_scheme.equalsIgnoreCase("UnicodeLittle")){
-              encoding_scheme = "UTF-16LE";
-              out.write((byte)0xFF);
-              out.write((byte)0xFE);
-            }
-          }
-        catch(IOException ioe)
-          {
-          }
-
-        Charset cs = EncodingHelper.getCharset(encoding_scheme);
-        encoder = cs.newEncoder();
-        encodingName = EncodingHelper.getOldCanonical(cs.name());
+	// Don't use NIO if avoidable
+	if(EncodingHelper.isISOLatin1(encoding_scheme))
+	  {
+	    encodingName = "ISO8859_1";
+	    encoder = null;
+	    return;
+	  }
+
+	/*
+	 * Workraround for encodings with a byte-order-mark.
+	 * We only want to write it once per stream.
+	 */
+	try 
+	  {
+	    if(encoding_scheme.equalsIgnoreCase("UnicodeBig") || 
+	       encoding_scheme.equalsIgnoreCase("UTF-16") ||
+	       encoding_scheme.equalsIgnoreCase("UTF16"))
+	      {
+		encoding_scheme = "UTF-16BE";	  
+		out.write((byte)0xFE);
+		out.write((byte)0xFF);
+	      } 
+	    else if(encoding_scheme.equalsIgnoreCase("UnicodeLittle")){
+	      encoding_scheme = "UTF-16LE";
+	      out.write((byte)0xFF);
+	      out.write((byte)0xFE);
+	    }
+	  }
+	catch(IOException ioe)
+	  {
+	  }
+      
+	outputBuffer = CharBuffer.allocate(BUFFER_SIZE);
+
+	Charset cs = EncodingHelper.getCharset(encoding_scheme);
+	if(cs == null)
+	  throw new UnsupportedEncodingException("Encoding "+encoding_scheme+
+						 " unknown");
+	encoder = cs.newEncoder();
+	encodingName = EncodingHelper.getOldCanonical(cs.name());
 
+	encoder.onMalformedInput(CodingErrorAction.REPLACE);
+	encoder.onUnmappableCharacter(CodingErrorAction.REPLACE);
       } 
     catch(RuntimeException e) 
       {
-        // Default to ISO Latin-1, will happen if this is called, for instance,
-        // before the NIO provider is loadable.
-        encoder = null; 
-        encodingName = "ISO8859_1";
+	// Default to ISO Latin-1, will happen if this is called, for instance,
+	//  before the NIO provider is loadable.
+	encoder = null; 
+	encodingName = "ISO8859_1";
       }
-    initEncoderAndBuffer();
   }
 
   /**
    * This method initializes a new instance of <code>OutputStreamWriter</code>
    * to write to the specified stream using the default encoding.
    *
    * @param out The <code>OutputStream</code> to write to
    */
   public OutputStreamWriter (OutputStream out)
   {
     this.out = out;
+    outputBuffer = null;
     try 
       {
-        String encoding = System.getProperty("file.encoding");
-        Charset cs = Charset.forName(encoding);
-        encoder = cs.newEncoder();
-        encodingName =  EncodingHelper.getOldCanonical(cs.name());
+	String encoding = System.getProperty("file.encoding");
+	Charset cs = Charset.forName(encoding);
+	encoder = cs.newEncoder();
+	encodingName =  EncodingHelper.getOldCanonical(cs.name());
       } 
     catch(RuntimeException e) 
       {
-        // For bootstrap problems.
-        encoder = null;
-        encodingName = "ISO8859_1";
+	encoder = null; 
+	encodingName = "ISO8859_1";
       }
 
-    initEncoderAndBuffer();
+    if(encoder != null)
+      {
+	encoder.onMalformedInput(CodingErrorAction.REPLACE);
+	encoder.onUnmappableCharacter(CodingErrorAction.REPLACE);
+	outputBuffer = CharBuffer.allocate(BUFFER_SIZE);
+      }
   }
 
   /**
    * This method initializes a new instance of <code>OutputStreamWriter</code>
    * to write to the specified stream using a given <code>Charset</code>.
    *
    * @param out The <code>OutputStream</code> to write to
    * @param cs The <code>Charset</code> of the encoding to use
    * 
    * @since 1.5
    */
   public OutputStreamWriter(OutputStream out, Charset cs)
   {
     this.out = out;
     encoder = cs.newEncoder();
+    encoder.onMalformedInput(CodingErrorAction.REPLACE);
+    encoder.onUnmappableCharacter(CodingErrorAction.REPLACE);
+    outputBuffer = CharBuffer.allocate(BUFFER_SIZE);
     encodingName = EncodingHelper.getOldCanonical(cs.name());
-    initEncoderAndBuffer();
   }
   
   /**
    * This method initializes a new instance of <code>OutputStreamWriter</code>
    * to write to the specified stream using a given
    * <code>CharsetEncoder</code>.
    *
    * @param out The <code>OutputStream</code> to write to
    * @param enc The <code>CharsetEncoder</code> to encode the output with
    * 
    * @since 1.5
    */
   public OutputStreamWriter(OutputStream out, CharsetEncoder enc)
   {
     this.out = out;
     encoder = enc;
+    outputBuffer = CharBuffer.allocate(BUFFER_SIZE);
     Charset cs = enc.charset();
     if (cs == null)
       encodingName = "US-ASCII";
     else
       encodingName = EncodingHelper.getOldCanonical(cs.name());
-    initEncoderAndBuffer();
-  }
-
-  /**
-   * Initializes the encoder and the output buffer.
-   */
-  private void initEncoderAndBuffer()
-  {
-    if (encoder != null)
-      {
-        encoder.onMalformedInput(CodingErrorAction.REPLACE);
-        encoder.onUnmappableCharacter(CodingErrorAction.REPLACE);
-      }
-    outputBuffer = ByteBuffer.allocate(BUFFER_SIZE);
   }
 
   /**
    * This method closes this stream, and the underlying 
    * <code>OutputStream</code>
    *
    * @exception IOException If an error occurs
    */
   public void close () throws IOException
   {
     if(out == null)
       return;
     flush();
     out.close ();
     out = null;
@@ -287,191 +270,135 @@
    *
    * @return The encoding scheme name
    */
   public String getEncoding ()
   {
     return out != null ? encodingName : null;
   }
 
   /**
    * This method flushes any buffered bytes to the underlying output sink.
    *
    * @exception IOException If an error occurs
    */
   public void flush () throws IOException
   {
-    int len = outputBuffer.position();
-    if (len > 0)
-      {
-        outputBuffer.flip();
-        if (outputBuffer.hasArray())
-          {
-            byte[] bytes = outputBuffer.array();
-            int p = outputBuffer.arrayOffset();
-            out.write(bytes, p, len);
-          }
-        else
-          {
-            // Shouldn't happen for normal (non-direct) ByteBuffers.
-            byte[] bytes = new byte[len];
-            outputBuffer.get(bytes);
-            out.write(bytes, 0, len);
-          }
-        outputBuffer.clear();
+      if(out != null){	  
+	  if(outputBuffer != null){
+	      char[] buf = new char[outputBuffer.position()];
+	      if(buf.length > 0){
+		  outputBuffer.flip();
+		  outputBuffer.get(buf);
+		  writeConvert(buf, 0, buf.length);
+		  outputBuffer.clear();
+	      }
+	  }
+	  out.flush ();
       }
-    out.flush ();
   }
 
   /**
    * This method writes <code>count</code> characters from the specified
    * array to the output stream starting at position <code>offset</code>
    * into the array.
    *
    * @param buf The array of character to write from
    * @param offset The offset into the array to start writing chars from
    * @param count The number of chars to write.
    *
    * @exception IOException If an error occurs
    */
   public void write (char[] buf, int offset, int count) throws IOException
   {
     if(out == null)
       throw new IOException("Stream is closed.");
     if(buf == null)
       throw new IOException("Buffer is null.");
 
-    CharBuffer charBuffer = getCharBuffer(buf, offset, count);
-    encodeChars(charBuffer);
-    flush();
+    if(outputBuffer != null)
+	{
+	    if(count >= outputBuffer.remaining())
+		{
+		    int r = outputBuffer.remaining();
+		    outputBuffer.put(buf, offset, r);
+		    writeConvert(outputBuffer.array(), 0, BUFFER_SIZE);
+		    outputBuffer.clear();
+		    offset += r;
+		    count -= r;
+		    // if the remaining bytes is larger than the whole buffer, 
+		    // just don't buffer.
+		    if(count >= outputBuffer.remaining()){
+                      writeConvert(buf, offset, count);
+		      return;
+		    }
+		}
+	    outputBuffer.put(buf, offset, count);
+	} else writeConvert(buf, offset, count);
+  }
+
+ /**
+  * Converts and writes characters.
+  */
+  private void writeConvert (char[] buf, int offset, int count) 
+      throws IOException
+  {
+    if(encoder == null)
+    {
+      byte[] b = new byte[count];
+      for(int i=0;i<count;i++)
+	b[i] = (byte)((buf[offset+i] <= 0xFF)?buf[offset+i]:'?');
+      out.write(b);
+    } else {
+      try  {
+	ByteBuffer output = encoder.encode(CharBuffer.wrap(buf,offset,count));
+	encoder.reset();
+	if(output.hasArray())
+	  out.write(output.array());
+	else
+	  {
+	    byte[] outbytes = new byte[output.remaining()];
+	    output.get(outbytes);
+	    out.write(outbytes);
+	  }
+      } catch(IllegalStateException e) {
+	throw new IOException("Internal error.");
+      } catch(MalformedInputException e) {
+	throw new IOException("Invalid character sequence.");
+      } catch(CharacterCodingException e) {
+	throw new IOException("Unmappable character.");
+      }
+    }
   }
 
   /**
    * This method writes <code>count</code> bytes from the specified 
    * <code>String</code> starting at position <code>offset</code> into the
    * <code>String</code>.
    *
    * @param str The <code>String</code> to write chars from
    * @param offset The position in the <code>String</code> to start 
    * writing chars from
    * @param count The number of chars to write
    *
    * @exception IOException If an error occurs
    */
   public void write (String str, int offset, int count) throws IOException
   {
-    if (out == null)
-      throw new IOException("Stream is closed.");
-    if (str == null)
-      throw new IOException("Buffer is null.");
+    if(str == null)
+      throw new IOException("String is null.");
 
-    // Don't call str.toCharArray() here to avoid allocation.
-    // TODO: CharBuffer.wrap(String) should not allocate a char array either.
-    CharBuffer charBuffer = getCharBuffer(str, offset, count);
-    encodeChars(charBuffer);
-    flush();
+    write(str.toCharArray(), offset, count);
   }
 
   /**
    * This method writes a single character to the output stream.
    *
    * @param ch The char to write, passed as an int.
    *
    * @exception IOException If an error occurs
    */
   public void write (int ch) throws IOException
   {
-    oneChar[0] = (char) ch;
-    write(oneChar, 0, 1);
-  }
-
-  /**
-   * Encodes the specified buffer of characters. The encoded data is stored
-   * in an intermediate buffer and only flushed when this buffer gets full.
-   *
-   * @param chars the characters to encode
-   *
-   * @throws IOException if something goes wrong on the underlying stream
-   */
-  private void encodeChars(CharBuffer chars)
-    throws IOException
-  {
-    assert out != null;
-    assert encoder != null;
-    int remaining = chars.remaining();
-    while (remaining > 0)
-      {
-        CoderResult cr = encode(chars);
-        remaining = chars.remaining();
-        // Flush when the output buffer has no more space or when the
-        // space is not enough to hold more encoded data (that when the
-        // input buffer does not change).
-        if (cr.isOverflow())
-          flush();
-      }
-  }
-
-  /**
-   * Encodes the specified CharBuffer into the output buffer. This takes
-   * care for the seldom case when we have no decoder, i.e. bootstrapping
-   * problems.
-   *
-   * @param chars the char buffer to encode
-   */
-  private CoderResult encode(CharBuffer chars)
-  {
-    CoderResult cr;
-    if (encoder != null)
-      {
-        cr = encoder.encode(chars, outputBuffer, false);
-      }
-    else
-      {
-        // For bootstrapping weirdness.
-        // Perform primitive Latin1 decoding.
-        while (chars.hasRemaining() && outputBuffer.hasRemaining())
-          {
-            outputBuffer.put((byte) (chars.get()));
-          }
-        // One of the buffers must be drained.
-        if (! outputBuffer.hasRemaining())
-          cr = CoderResult.OVERFLOW;
-        else
-          cr = CoderResult.UNDERFLOW;
-      }
-    return cr;
-  }
-
-  /**
-   * Returns a CharBuffer that wraps the specified char array. This tries
-   * to return a cached instance because usually the read() method is called
-   * repeatedly with the same char array instance, or the no-arg read
-   * method is called repeatedly which uses the oneChar field of this class
-   * over and over again.
-   *
-   * @param buf the array to wrap
-   * @param offset the offset
-   * @param length the length
-   *
-   * @return a prepared CharBuffer to write to
-   */
-  private final CharBuffer getCharBuffer(Object buf, int offset, int length)
-  {
-    CharBuffer outBuffer;
-    if (lastArray == buf)
-      {
-        outBuffer = lastBuffer;
-        outBuffer.position(offset);
-        outBuffer.limit(offset + length);
-      }
-    else
-      {
-        lastArray = buf;
-        if (buf instanceof String)
-          lastBuffer = CharBuffer.wrap((String) buf, offset, length);
-        else
-          lastBuffer = CharBuffer.wrap((char[]) buf, offset, length);
-        outBuffer = lastBuffer;
-      }
-    return outBuffer;
+    write(new char[]{ (char)ch }, 0, 1);
   }
 } // class OutputStreamWriter
 

Reply via email to