Hi,

Ingo has done some work to clean up the encoding/decoding in
InputStreamReader and OutputStreamWriter. Basically it uses NIO buffers
now at both ends of the encoder or decoder which makes the code more
clean and hopefully more efficient.

It also improves the read() and write() methods to avoid allocating
small temporary arrays on each read/write operation.

This still passes all Mauve tests for these classes (ok, not so many
tests there..)

Any comments? If nobody complains I'll check this in tomorrow.

2007-01-04  Ingo Proetel  <[EMAIL PROTECTED]>

        * java/io/InputStreamReader.java
        (BUFFER_SIZE): New constant.
        (byteBuffer): Removed.
        (bytesCache): Removed.
        (cacheLock): Removed.
        (hasSavedSurrogate): Removed.
        (inputBuffer): New field.
        (lastCoderResult): New field.
        (outputBuffer): New field.
        (maxBytesPerChar): Removed.
        (savedSurrogate): Removed.
        (InputStreamReader(InputStream,Charset)): Use unified
        initializeBufferAndDecoder() method.
        (InputStreamReader(InputStream,CharsetDecoder)): Use unified
        initializeBufferAndDecoder() method.
        (InputStreamReader(InputStream)): Use unified
        initializeBufferAndDecoder() method.
        (InputStreamReader(InputStream)): Use unified
        initializeBufferAndDecoder() method.
        (initializeDecoderAndBuffers): New helper method for initialization.
        (read): Read directly from buffer and avoid creating a new
        array.
        (read(char[],int,int)): Cleaned up. Use refillBuffer() for the
        datatransfer using NIO charsets.
        (ready): Also consider if the output buffer has remaining data.
        (refillBuffer): New helper method. Refills the output buffer by
        decoding the input buffer.
        (skip): Re-position the output buffer.
        * java/io/OutputStreamWriter.java
        (inputBuffer): Renamed from inputBuffer.
        (outputBuffer): New field.
        (OutputStreamWriter(OutputStream,Charset)): Initialize the buffers.
        (OutputStreamWriter(OutputStream,CharsetEncoder)): Initialize the
        buffers.
        (OutputStreamWriter(OutputStream,String)): Clean up.
        (OutputStreamWriter(OutputStream)): Clean up.
        (flush): Use NIO charsets for encoding the buffers.
        (write(char[],int,int)): Use in and out buffers to encode using
        NIO.
        (write(String,int,int)): Use in and out buffers to encode using
        NIO.
        (write(int)): Write char into input buffer.
        (writeConvert): Removed.

/Roman

Index: java/io/InputStreamReader.java
===================================================================
RCS file: /cvsroot/classpath/classpath/java/io/InputStreamReader.java,v
retrieving revision 1.31
diff -u -1 -5 -r1.31 InputStreamReader.java
--- java/io/InputStreamReader.java	14 Sep 2006 08:03:56 -0000	1.31
+++ java/io/InputStreamReader.java	4 Jan 2007 14:47:58 -0000
@@ -86,226 +86,186 @@
  * <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 size of the in/out buffers.
+   */
+  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;
 
   /**
-   * 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.
+   * The buffer for the ingoing stream.
    */
-  private char savedSurrogate;
-  private boolean hasSavedSurrogate = false;
+  private ByteBuffer inputBuffer;
 
   /**
-   * A byte array to be reused in read(byte[], int, int).
+   * The buffer for the outgoing stream.
    */
-  private byte[] bytesCache;
+  private CharBuffer outputBuffer;
 
   /**
-   * Locks the bytesCache above in read(byte[], int, int).
+   * The last result of the decoder.
    */
-  private Object cacheLock = new Object();
+  private CoderResult lastCoderResult = CoderResult.UNDERFLOW;
 
   /**
    * 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;
     try 
 	{ 
-	  encoding = SystemProperties.getProperty("file.encoding");
+	  encoding = SystemProperties.getProperty("file.encoding", "ISO8859_1");
 	  // 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;
+      initializeDecoderAndBuffers();
+    } catch(UnsupportedEncodingException e) {
+      throw new InternalError("missing ISO8859_1");
 	}
   }
 
   /**
    * 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)
       throw new NullPointerException();
     
     this.in = in;
-    // Don't use NIO if avoidable
-    if(EncodingHelper.isISOLatin1(encoding_name))
-      {
-	encoding = "ISO8859_1";
-	maxBytesPerChar = 1f;
-	decoder = null;
-	return;
-      }
-    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;
-    }
+    Charset cs = EncodingHelper.getCharset(encoding_name);
+
+    decoder = cs.newDecoder();
+    initializeDecoderAndBuffers();
+
+    // The encoding should be the old name, if such exists.
+    encoding = EncodingHelper.getOldCanonical(cs.name());
+
   }
 
   /**
    * 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)
       throw new NullPointerException();
     this.in = in;
     decoder = charset.newDecoder();
 
-    try {
-      maxBytesPerChar = charset.newEncoder().maxBytesPerChar();
-    } catch(UnsupportedOperationException _){
-      maxBytesPerChar = 1f;
-    }
+    initializeDecoderAndBuffers();
 
-    decoder.onMalformedInput(CodingErrorAction.REPLACE);
-    decoder.onUnmappableCharacter(CodingErrorAction.REPLACE);
-    decoder.reset();
     encoding = EncodingHelper.getOldCanonical(charset.name());
   }
 
   /**
    * 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)
       throw new NullPointerException();
     this.in = in;
     this.decoder = decoder;
 
     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();
+    initializeDecoderAndBuffers();
+
     if (charset == null)
       encoding = "US-ASCII";
     else
       encoding = EncodingHelper.getOldCanonical(decoder.charset().name());      
   }
-  
+
+ 
+  /**
+   * Initialize the decoder and allocate the internal buffers.
+   *
+   */
+  private void initializeDecoderAndBuffers()
+  {
+    // Set up decoder.
+    decoder.onMalformedInput(CodingErrorAction.REPLACE);
+    decoder.onUnmappableCharacter(CodingErrorAction.REPLACE);
+    decoder.reset();
+
+    // Allocate a byte buffer to hold incomming data.
+    inputBuffer = ByteBuffer.allocate(BUFFER_SIZE);
+    inputBuffer.flip();
+
+    // Allocate a char buffer to hold outgoing char data.
+    int size = (int) (BUFFER_SIZE * decoder.averageCharsPerByte()) + 1;
+    outputBuffer = CharBuffer.allocate(size);
+    outputBuffer.flip();
+    outputBuffer.position(outputBuffer.limit());
+  }
+
   /**
    * 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();
@@ -331,179 +291,170 @@
    * 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 in.available() != 0;
+    return outputBuffer.hasRemaining() || 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;
-    if(decoder != null)
+
+    int charsRead = 0;
+    int remaining; 
+    synchronized (outputBuffer)
       {
-	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;
-	    }
-	}
+        do
+          {             
+            remaining = outputBuffer.remaining();
+            if (remaining == 0)
+              {
+                refillBuffer();
+                remaining = outputBuffer.remaining();
+                if (remaining == 0 && length != 0)
+                  {
+                    isDone = true;
+                    return (charsRead > 0) ? charsRead : -1;
+                  }
+              }
+
+            int charsToRead = remaining < length ? remaining : length;
+            outputBuffer.get(buf, offset, charsToRead);
+            length -= charsToRead;
+            offset += charsToRead;
+            charsRead += charsToRead;
 
-	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)
-          {
-            if (byteBuffer == null
-                && (bytesCache == null || bytesCache.length < bytes.length))
-              bytesCache = bytes;
-          }
-        return (read <= 0) ? -1 : read;
+          } while (length > 0 && ready());
       }
-    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;
-    }
+    return charsRead;
   }
 
   /**
    * 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
   {
-    char[] buf = new char[1];
-    int count = read(buf, 0, 1);
-    return count > 0 ? buf[0] : -1;
+    synchronized (outputBuffer)
+    {
+      int remaining = outputBuffer.remaining();
+      if (remaining == 0)
+        {
+          refillBuffer();
+          remaining = outputBuffer.remaining();
+        }
+      int ch;
+      if (remaining == 0)
+        {
+          isDone = true;
+          ch = -1;
+        }
+      else
+        {
+          ch = outputBuffer.get();
+        }
+      return ch;
+    }
   }
 
   /**
    * 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);
+
+     long skipped = 0;
+
+     synchronized(outputBuffer)
+       {
+         int remaining = outputBuffer.remaining();
+         if (count < remaining)
+           {
+             outputBuffer.position(outputBuffer.position()
+                                      + (int) count);
+             skipped += count;
+             count = 0;
+           }
+         else
+           {
+             count -= remaining;
+             skipped += remaining;
+             refillBuffer();
+             remaining = outputBuffer.remaining();
+           }
+       }
+     return skipped;
+   }
+
+   /**
+    * Refill the outgoing char buffer. This method assumes that the data in the
+    * char buffer is either completly drained or of no interest any more. 
+    * If inputCharBuffer.remaining() is zero after this call, it means that no
+    * more data can be read from the input stream.
+    * @throws IOException
+    */
+   private void refillBuffer() throws IOException
+   {
+     int bytesRead = 0;
+     if (lastCoderResult == CoderResult.UNDERFLOW)
+       {
+         inputBuffer.compact();
+         bytesRead = in.read(inputBuffer.array(), inputBuffer.position(),
+                             inputBuffer.remaining());
+         if (bytesRead > -1)
+           {
+             inputBuffer.position(inputBuffer.position() + bytesRead);
+           }
+         inputBuffer.flip();
+       }
+     outputBuffer.clear();
+     lastCoderResult = decoder.decode(inputBuffer, outputBuffer,
+                                      bytesRead == -1);
+     if (bytesRead == -1)
+       decoder.flush(outputBuffer);
+     outputBuffer.flip();
    }
 }
Index: java/io/OutputStreamWriter.java
===================================================================
RCS file: /cvsroot/classpath/classpath/java/io/OutputStreamWriter.java,v
retrieving revision 1.22
diff -u -1 -5 -r1.22 OutputStreamWriter.java
--- java/io/OutputStreamWriter.java	8 Nov 2006 16:54:22 -0000	1.22
+++ java/io/OutputStreamWriter.java	4 Jan 2007 14:47:58 -0000
@@ -30,35 +30,33 @@
 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.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>
@@ -72,187 +70,181 @@
  * <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;
 
   /**
    * Buffer output before character conversion as it has costly overhead.
    */
-  private CharBuffer outputBuffer;
-  private final static int BUFFER_SIZE = 1024;
+  private CharBuffer inputBuffer;
+  
+  /**
+   * The buffer for outgoing (encoded) data.
+   */
+  private ByteBuffer outputBuffer;
 
   /**
    * 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
   {
     this.out = out;
+    
+    /*
+     * Workraround for encodings with a byte-order-mark.
+     * We only want to write it once per stream.
+     */
     try 
       {
-	// 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) 
+        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)
       {
-	// Default to ISO Latin-1, will happen if this is called, for instance,
-	//  before the NIO provider is loadable.
-	encoder = null; 
-	encodingName = "ISO8859_1";
       }
+
+    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);
+
+    inputBuffer = CharBuffer.allocate(BUFFER_SIZE);
+    int size = (int) (BUFFER_SIZE * encoder.averageBytesPerChar()) + 1;
+    outputBuffer = ByteBuffer.allocate(size);
   }
+    
 
   /**
    * 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());
-      } 
-    catch(RuntimeException e) 
-      {
-	encoder = null; 
-	encodingName = "ISO8859_1";
-      }
+    inputBuffer = null;
 
-    if(encoder != null)
-      {
-	encoder.onMalformedInput(CodingErrorAction.REPLACE);
-	encoder.onUnmappableCharacter(CodingErrorAction.REPLACE);
-	outputBuffer = CharBuffer.allocate(BUFFER_SIZE);
-      }
+    String encoding = System.getProperty("file.encoding", "ISO8859_1");
+    Charset cs = Charset.forName(encoding);
+    encoder = cs.newEncoder();
+    encodingName =  EncodingHelper.getOldCanonical(cs.name());
+
+    encoder.onMalformedInput(CodingErrorAction.REPLACE);
+    encoder.onUnmappableCharacter(CodingErrorAction.REPLACE);
+
+    inputBuffer = CharBuffer.allocate(BUFFER_SIZE);
+    int size = (int) (BUFFER_SIZE * encoder.averageBytesPerChar()) + 1;
+    outputBuffer = ByteBuffer.allocate(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);
+    inputBuffer = CharBuffer.allocate(BUFFER_SIZE);
+    int size = (int) (BUFFER_SIZE * encoder.averageBytesPerChar()) + 1;
+    outputBuffer = ByteBuffer.allocate(size);
     encodingName = EncodingHelper.getOldCanonical(cs.name());
   }
   
   /**
    * 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);
+    inputBuffer = CharBuffer.allocate(BUFFER_SIZE);
+    int size = (int) (BUFFER_SIZE * encoder.averageBytesPerChar()) + 1;
+    outputBuffer = ByteBuffer.allocate(size);
     Charset cs = enc.charset();
     if (cs == null)
       encodingName = "US-ASCII";
     else
       encodingName = EncodingHelper.getOldCanonical(cs.name());
   }
 
   /**
    * This method closes this stream, and the underlying 
    * <code>OutputStream</code>
    *
    * @exception IOException If an error occurs
    */
   public void close () throws IOException
   {
@@ -270,135 +262,138 @@
    *
    * @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
   {
-      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();
-	      }
+    if(out != null)
+      {       
+        synchronized (inputBuffer)
+          {
+            inputBuffer.flip();
+            while (inputBuffer.hasRemaining())
+              {
+                outputBuffer.clear();
+                encoder.encode(inputBuffer, outputBuffer, false);
+                outputBuffer.flip();
+                out.write(outputBuffer.array(), 0,
+                          outputBuffer.remaining());
+                out.flush();
+              }
+            inputBuffer.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.");
 
-    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);
-  }
+    synchronized (inputBuffer) 
+      {                  
+        do
+          {
+            int remaining = inputBuffer.remaining();
+            if (remaining == 0)
+              {
+                flush();
+                remaining = inputBuffer.remaining();
+              }
+            int length = remaining < count ? remaining : count;
+            inputBuffer.put(buf, offset, length);
+            count -= length;
+            offset += length;
 
- /**
-  * 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.");
+          } while (count > 0);
       }
-    }
   }
 
+
   /**
    * 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(str == null)
+    if (str == null)
       throw new IOException("String is null.");
+    if (out == null)
+      throw new IOException("Stream is closed.");
 
-    write(str.toCharArray(), offset, count);
+    synchronized (inputBuffer)
+      {
+        do
+          {
+            int remaining = inputBuffer.remaining();
+            if (remaining == 0)
+              {
+                flush();
+                remaining = inputBuffer.remaining();
+              }
+            int length = remaining < count ? remaining : count;
+            int position = inputBuffer.position();
+            char[] array = inputBuffer.array();
+            for (int i = 0; i < length; i++)
+              {
+                array[position + i] = str.charAt(offset + i);
+              }
+            inputBuffer.position(position + length);
+            count -= length;
+            offset += length;
+
+          } while (count > 0);
+      }
   }
 
   /**
    * 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
   {
-    write(new char[]{ (char)ch }, 0, 1);
+    synchronized (inputBuffer) 
+      {
+        int remaining = inputBuffer.remaining();
+        if (remaining == 0)
+          {
+            flush();
+          }
+        inputBuffer.put((char) ch);
+      }
   }
 } // class OutputStreamWriter
 

Reply via email to