Clean up CharsetDecoder and CharsetEncoder.

This patch also fixes a few bugs, and a few test bugs, and then the bugs
exposed by fixing those tests.

(cherry picked from commit 6ad37f500b023ef09fd177ad8cd8e2ba0b842cae)

Change-Id: Ia58d30e414cc59c27a1d259e8056523d6df2c6bc
diff --git a/harmony-tests/src/test/java/org/apache/harmony/nio_char/tests/java/nio/charset/ASCIICharsetEncoderTest.java b/harmony-tests/src/test/java/org/apache/harmony/nio_char/tests/java/nio/charset/ASCIICharsetEncoderTest.java
index 6f59190..a85576c 100644
--- a/harmony-tests/src/test/java/org/apache/harmony/nio_char/tests/java/nio/charset/ASCIICharsetEncoderTest.java
+++ b/harmony-tests/src/test/java/org/apache/harmony/nio_char/tests/java/nio/charset/ASCIICharsetEncoderTest.java
@@ -4,9 +4,9 @@
  * The ASF licenses this file to You under the Apache License, Version 2.0
  * (the "License"); you may not use this file except in compliance with
  * the License.  You may obtain a copy of the License at
- * 
+ *
  *     http://www.apache.org/licenses/LICENSE-2.0
- * 
+ *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -33,7 +33,7 @@
 	// charset for ascii
 	private static final Charset cs = Charset.forName("ascii");
     private static final CharsetEncoder encoder = cs.newEncoder();
-    private static final int MAXCODEPOINT = 0x7F; 
+    private static final int MAXCODEPOINT = 0x7F;
 	/*
 	 * @see CharsetEncoderTest#setUp()
 	 */
@@ -96,21 +96,21 @@
 
     public void testEncodeMapping() throws CharacterCodingException {
         encoder.reset();
-        
+
         for (int i =0; i <= MAXCODEPOINT; i++) {
             char[] chars = Character.toChars(i);
             CharBuffer cb = CharBuffer.wrap(chars);
             ByteBuffer bb = encoder.encode(cb);
             assertEquals(i, bb.get(0));
         }
-        
+
         CharBuffer cb = CharBuffer.wrap("\u0080");
         try {
             encoder.encode(cb);
         } catch (UnmappableCharacterException e) {
             //expected
         }
-        
+
         cb = CharBuffer.wrap("\ud800");
         try {
             encoder.encode(cb);
@@ -128,11 +128,11 @@
             //expected
         }
     }
-    
+
     public void testInternalState() {
         CharBuffer in = CharBuffer.wrap("A");
         ByteBuffer out = ByteBuffer.allocate(0x10);
-        
+
         //normal encoding process
         encoder.reset();
         encoder.encode(in, out, false);
@@ -140,13 +140,13 @@
         encoder.encode(in, out, true);
         encoder.flush(out);
     }
-    
+
     //reset could be called at any time
     public void testInternalState_Reset() {
         CharsetEncoder newEncoder = cs.newEncoder();
         //Init - > reset
         newEncoder.reset();
-        
+
         //reset - > reset
         newEncoder.reset();
 
@@ -174,7 +174,7 @@
             newEncoder.reset();
         }
     }
-    
+
     public void testInternalState_Encoding() {
         CharsetEncoder newEncoder = cs.newEncoder();
         //Init - > encoding
@@ -183,27 +183,27 @@
             ByteBuffer out = ByteBuffer.allocate(0x10);
             newEncoder.encode(in, out, false);
         }
-        
+
         //reset - > encoding
         {
             CharBuffer in = CharBuffer.wrap("A");
             ByteBuffer out = ByteBuffer.allocate(0x10);
-            newEncoder.reset();            
+            newEncoder.reset();
             newEncoder.encode(in, out, false);
         }
         //reset - > encoding - > encoding
         {
-            newEncoder.reset();            
+            newEncoder.reset();
             CharBuffer in = CharBuffer.wrap("A");
             ByteBuffer out = ByteBuffer.allocate(0x10);
             newEncoder.encode(in, out, false);
             in = CharBuffer.wrap("BC");
             newEncoder.encode(in, out, false);
         }
-        
+
         //encoding_end - > encoding
         {
-            newEncoder.reset();            
+            newEncoder.reset();
             CharBuffer in = CharBuffer.wrap("A");
             ByteBuffer out = ByteBuffer.allocate(0x10);
             newEncoder.encode(in, out, true);
@@ -217,7 +217,7 @@
         }
         //flushed - > encoding
         {
-            newEncoder.reset();            
+            newEncoder.reset();
             CharBuffer in = CharBuffer.wrap("A");
             ByteBuffer out = ByteBuffer.allocate(0x10);
             newEncoder.encode(in, out, true);
@@ -231,7 +231,7 @@
             }
         }
     }
-    
+
     public void testInternalState_Encoding_END() {
         CharsetEncoder newEncoder = cs.newEncoder();
 
@@ -241,7 +241,7 @@
             ByteBuffer out = ByteBuffer.allocate(0x10);
             newEncoder.encode(in, out, true);
         }
-        
+
         //Reset -> encoding_end
         {
             CharBuffer in = CharBuffer.wrap("A");
@@ -259,7 +259,7 @@
             in = CharBuffer.wrap("BC");
             newEncoder.encode(in, out, true);
         }
-        
+
         //Reset -> encoding_end
         {
             newEncoder.reset();
@@ -269,7 +269,7 @@
             in = CharBuffer.wrap("BC");
             newEncoder.encode(in, out, true);
         }
-        
+
         //Flushed -> encoding_end
         {
             newEncoder.reset();
@@ -286,10 +286,10 @@
             }
         }
     }
-    
+
     public void testInternalState_Flushed() {
         CharsetEncoder newEncoder = cs.newEncoder();
-        
+
         // init -> flushed
 		{
 			ByteBuffer out = ByteBuffer.allocate(0x10);
@@ -316,7 +316,7 @@
                 //expected
             }
         }
-        
+
         //encoding - > flushed
         {
             newEncoder.reset();
@@ -331,7 +331,7 @@
                 // expected
             }
         }
-        
+
         //encoding_end -> flushed
         {
             newEncoder.reset();
@@ -340,7 +340,7 @@
             newEncoder.encode(in, out, true);
             newEncoder.flush(out);
         }
-        
+
         //flushd - > flushed
         {
             newEncoder.reset();
@@ -348,15 +348,10 @@
             ByteBuffer out = ByteBuffer.allocate(0x10);
             newEncoder.encode(in, out, true);
             newEncoder.flush(out);
-            try {
-                newEncoder.flush(out);
-                fail("Should throw IllegalStateException");
-            } catch (IllegalStateException e) {
-                // expected
-            }
+            newEncoder.flush(out);
         }
     }
-    
+
     public void testInternalState_Encode() throws CharacterCodingException {
         CharsetEncoder newEncoder = cs.newEncoder();
         //Init - > encode
@@ -364,14 +359,14 @@
             CharBuffer in = CharBuffer.wrap("A");
             newEncoder.encode(in);
         }
-        
+
         //Reset - > encode
         {
             newEncoder.reset();
             CharBuffer in = CharBuffer.wrap("A");
             newEncoder.encode(in);
         }
-        
+
         //Encoding -> encode
         {
             newEncoder.reset();
@@ -381,7 +376,7 @@
             in = CharBuffer.wrap("BC");
             newEncoder.encode(in);
         }
-        
+
         //Encoding_end -> encode
         {
             newEncoder.reset();
@@ -391,7 +386,7 @@
             in = CharBuffer.wrap("BC");
             newEncoder.encode(in);
         }
-        
+
         //Flushed -> reset
         {
             newEncoder.reset();
@@ -403,17 +398,17 @@
             out = newEncoder.encode(in);
         }
     }
-    
+
     public void testInternalState_from_Encode() throws CharacterCodingException {
         CharsetEncoder newEncoder = cs.newEncoder();
-        
+
         //Encode -> Reset
         {
             CharBuffer in = CharBuffer.wrap("A");
             newEncoder.encode(in);
             newEncoder.reset();
         }
-        
+
         // Encode -> encoding
         {
             CharBuffer in = CharBuffer.wrap("A");
@@ -426,12 +421,13 @@
                 // expected
             }
         }
-        
+
         //Encode -> Encoding_end
         {
             CharBuffer in = CharBuffer.wrap("A");
-            newEncoder.encode(in);
             ByteBuffer out = ByteBuffer.allocate(0x10);
+            newEncoder.reset();
+            newEncoder.encode(in, out, false);
             newEncoder.encode(in, out, true);
         }
 
@@ -441,7 +437,7 @@
             ByteBuffer out = newEncoder.encode(in);
             newEncoder.flush(out);
         }
-        
+
         //Encode - > encode
         {
             CharBuffer in = CharBuffer.wrap("A");
diff --git a/harmony-tests/src/test/java/org/apache/harmony/nio_char/tests/java/nio/charset/CharsetDecoderTest.java b/harmony-tests/src/test/java/org/apache/harmony/nio_char/tests/java/nio/charset/CharsetDecoderTest.java
index 4ed4ab9..1a7b984 100644
--- a/harmony-tests/src/test/java/org/apache/harmony/nio_char/tests/java/nio/charset/CharsetDecoderTest.java
+++ b/harmony-tests/src/test/java/org/apache/harmony/nio_char/tests/java/nio/charset/CharsetDecoderTest.java
@@ -4,9 +4,9 @@
  * The ASF licenses this file to You under the Apache License, Version 2.0
  * (the "License"); you may not use this file except in compliance with
  * the License.  You may obtain a copy of the License at
- * 
+ *
  *     http://www.apache.org/licenses/LICENSE-2.0
- * 
+ *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -62,7 +62,7 @@
 			return null;
 		}
 	}
- 
+
 	/**
 	 * @tests java.nio.charset.CharsetDecoder#decode(java.nio.ByteBuffer)
 	 */
@@ -86,7 +86,7 @@
 //
 //		charbuf = Charset.forName("UTF-16LE").decode(buf);
 //		assertEquals("Assert 2: charset UTF16LE", 0, charbuf.length());
-		
+
 		// Regression for HARMONY-99
 		CharsetDecoder decoder2 = Charset.forName("UTF-16").newDecoder();
 		decoder2.onMalformedInput(CodingErrorAction.REPORT);
@@ -97,9 +97,9 @@
 			fail("Assert 3: MalformedInputException should have thrown");
 		} catch (MalformedInputException e) {
 			//expected
-		} 
+		}
 	}
-	
+
     /*
      * Test malfunction decode(ByteBuffer)
      */
@@ -124,7 +124,7 @@
 			// expected
 		}
 	}
-    
+
 	/*
 	 * Mock charset class with malfunction decode & encode.
 	 */
@@ -173,8 +173,8 @@
 		protected CoderResult encodeLoop(CharBuffer in, ByteBuffer out) {
 			throw new BufferOverflowException();
 		}
-	} 
-	
+	}
+
 	/*
 	 * Test the method decode(ByteBuffer) .
 	 */
@@ -249,16 +249,16 @@
 
         CharsetDecoder decoder = Charset.forName("UTF-8").newDecoder();
         decoder.onMalformedInput(CodingErrorAction.REPORT);
+        decoder.onUnmappableCharacter(CodingErrorAction.REPORT);
 
         /*
          * When bytebuffer has a backing array...
          */
         for (byte[] bytes : invalidSequences) {
             try {
-                decoder.decode(ByteBuffer.wrap(bytes));
-                fail("No exception thrown on " + Arrays.toString(bytes));
-            } catch (MalformedInputException e) {
-                // expected
+                CharBuffer cb = decoder.decode(ByteBuffer.wrap(bytes));
+                fail("No exception thrown on " + Arrays.toString(bytes) + " '" + cb + "'");
+            } catch (MalformedInputException expected) {
             }
         }
 
@@ -269,10 +269,9 @@
             try {
                 ByteBuffer bb = ByteBuffer.allocateDirect(8);
                 bb.put(bytes).flip();
-                decoder.decode(bb);
-                fail("No exception thrown on " + Arrays.toString(bytes));
-            } catch (MalformedInputException e) {
-                // expected
+                CharBuffer cb = decoder.decode(bb);
+                fail("No exception thrown on " + Arrays.toString(bytes) + " '" + cb + "'");
+            } catch (MalformedInputException expected) {
             }
         }
     }
diff --git a/harmony-tests/src/test/java/org/apache/harmony/nio_char/tests/java/nio/charset/CharsetEncoderTest.java b/harmony-tests/src/test/java/org/apache/harmony/nio_char/tests/java/nio/charset/CharsetEncoderTest.java
index dd514da..c3f1a8d 100644
--- a/harmony-tests/src/test/java/org/apache/harmony/nio_char/tests/java/nio/charset/CharsetEncoderTest.java
+++ b/harmony-tests/src/test/java/org/apache/harmony/nio_char/tests/java/nio/charset/CharsetEncoderTest.java
@@ -157,26 +157,36 @@
     public void test_EncodeLjava_nio_CharBufferLjava_nio_ByteBufferB() throws Exception {
         Charset utf8 = Charset.forName("utf-8");
         CharsetEncoder encoder = utf8.newEncoder();
-        CharBuffer in1 = CharBuffer.wrap("\ud800");
-        CharBuffer in2 = CharBuffer.wrap("\udc00");
-        ByteBuffer out = ByteBuffer.allocate(4);
+        CharBuffer char1 = CharBuffer.wrap("\ud800");
+        CharBuffer char2 = CharBuffer.wrap("\udc00");
+        ByteBuffer bytes = ByteBuffer.allocate(4);
         encoder.reset();
 
         // If we supply just the high surrogate...
-        CoderResult result = encoder.encode(in1, out, false);
+        CoderResult result = encoder.encode(char1, bytes, false);
         // ...we're not done...
         assertTrue(result.isUnderflow());
-        assertEquals(4, out.remaining());
+        assertEquals(4, bytes.remaining());
         // ...but if we then supply the low surrogate...
-        result = encoder.encode(in2, out, true);
+        result = encoder.encode(char2, bytes, true);
+        assertTrue(result.isUnderflow());
         // ...we're done. Note that the RI loses its state in
         // between the two characters, so it can't do this.
-        assertEquals(0, out.remaining());
+        assertEquals(0, bytes.remaining());
+
+        // Did we get the UTF-8 for U+10000?
+        assertEquals(4, bytes.limit());
+        assertEquals((byte) 0xf0, bytes.get(0));
+        assertEquals((byte) 0x90, bytes.get(1));
+        assertEquals((byte) 0x80, bytes.get(2));
+        assertEquals((byte) 0x80, bytes.get(3));
 
         // See what we got in the output buffer by decoding and checking that we
         // get back the same surrogate pair.
-        out.flip();
-        CharBuffer chars = utf8.newDecoder().decode(out);
+        bytes.flip();
+        CharBuffer chars = utf8.newDecoder().decode(bytes);
+        assertEquals(0, bytes.remaining());
+        assertEquals(2, chars.limit());
         assertEquals(0xd800, chars.get(0));
         assertEquals(0xdc00, chars.get(1));
     }
diff --git a/harmony-tests/src/test/java/tests/api/java/nio/charset/CharsetDecoderTest.java b/harmony-tests/src/test/java/tests/api/java/nio/charset/CharsetDecoderTest.java
index 9cd59ea..7fbe9e7 100644
--- a/harmony-tests/src/test/java/tests/api/java/nio/charset/CharsetDecoderTest.java
+++ b/harmony-tests/src/test/java/tests/api/java/nio/charset/CharsetDecoderTest.java
@@ -4,9 +4,9 @@
  * The ASF licenses this file to You under the Apache License, Version 2.0
  * (the "License"); you may not use this file except in compliance with
  * the License.  You may obtain a copy of the License at
- * 
+ *
  *     http://www.apache.org/licenses/LICENSE-2.0
- * 
+ *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -600,6 +600,15 @@
     public void testFlushIllegalState() throws CharacterCodingException {
         ByteBuffer in = ByteBuffer.wrap(new byte[] { 98, 98 });
         CharBuffer out = CharBuffer.allocate(5);
+
+        // Illegal state: after reset.
+        decoder.reset();
+        try {
+            decoder.flush(out);
+            fail();
+        } catch (IllegalStateException expected) {
+        }
+
         // Normal case: after decode with endOfInput is true
         decoder.reset();
         decoder.decode(in, out, true);
@@ -607,23 +616,19 @@
         CoderResult result = decoder.flush(out);
         assertSame(result, CoderResult.UNDERFLOW);
 
-        // Illegal state: flush twice
-        try {
-            decoder.flush(out);
-            fail("should throw IllegalStateException");
-        } catch (IllegalStateException e) {
-        }
+        // Good state: flush twice
+        decoder.flush(out);
 
         // Illegal state: flush after decode with endOfInput is false
         decoder.reset();
         decoder.decode(in, out, false);
         try {
             decoder.flush(out);
-            fail("should throw IllegalStateException");
-        } catch (IllegalStateException e) {
+            fail();
+        } catch (IllegalStateException expected) {
         }
     }
-    
+
     // test illegal states for decode facade
     public void testDecodeFacadeIllegalState() throws CharacterCodingException {
         // decode facade can be execute in anywhere
diff --git a/harmony-tests/src/test/java/tests/api/java/nio/charset/CharsetEncoderTest.java b/harmony-tests/src/test/java/tests/api/java/nio/charset/CharsetEncoderTest.java
index ef219be..b63f4d9 100644
--- a/harmony-tests/src/test/java/tests/api/java/nio/charset/CharsetEncoderTest.java
+++ b/harmony-tests/src/test/java/tests/api/java/nio/charset/CharsetEncoderTest.java
@@ -278,34 +278,36 @@
 		assertSame(encoder, encoder.reset());
 	}
 
-	public void testFlushIllegalState() throws CharacterCodingException {
-		CharBuffer in = CharBuffer.wrap("aaa");
-		ByteBuffer out = ByteBuffer.allocate(5);
+  public void testFlushIllegalState() throws CharacterCodingException {
+    CharBuffer in = CharBuffer.wrap("aaa");
+    ByteBuffer out = ByteBuffer.allocate(5);
 
-		// Normal case: after encode with endOfInput is true
-		assertSame(encoder, encoder.reset());
-		encoder.encode(in, out, true);
-		out.rewind();
-		CoderResult result = encoder.flush(out);
+    // Illegal state: after reset.
+    encoder.reset();
+    try {
+      encoder.flush(out);
+      fail();
+    } catch (IllegalStateException expected) {
+    }
 
-		// Illegal state: flush twice
-		try {
-			encoder.flush(out);
-			fail("should throw IllegalStateException");
-		} catch (IllegalStateException e) {
-		    // Expected
-		}
+    // Normal case: after encode with endOfInput is true
+    assertSame(encoder, encoder.reset());
+    encoder.encode(in, out, true);
+    out.rewind();
+    CoderResult result = encoder.flush(out);
 
-		// Illegal state: flush after encode with endOfInput is false
-		assertSame(encoder, encoder.reset());
-		encoder.encode(in, out, false);
-		try {
-			encoder.flush(out);
-			fail("should throw IllegalStateException");
-		} catch (IllegalStateException e) {
-		    // Expected
-		}
-	}
+    // Good state: flush twice
+    encoder.flush(out);
+
+    // Illegal state: flush after encode with endOfInput is false
+    assertSame(encoder, encoder.reset());
+    encoder.encode(in, out, false);
+    try {
+      encoder.flush(out);
+      fail();
+    } catch (IllegalStateException expected) {
+    }
+  }
 
 	public void testFlushAfterConstructing() {
 		ByteBuffer out = ByteBuffer.allocate(5);
diff --git a/harmony-tests/src/test/java/tests/api/java/nio/charset/CharsetTest.java b/harmony-tests/src/test/java/tests/api/java/nio/charset/CharsetTest.java
index adee76e..92f230e 100644
--- a/harmony-tests/src/test/java/tests/api/java/nio/charset/CharsetTest.java
+++ b/harmony-tests/src/test/java/tests/api/java/nio/charset/CharsetTest.java
@@ -136,17 +136,24 @@
   }
 
   public void test_EUC_JP_replacement_character() throws Exception {
-    assertEncodes(Charset.forName("EUC-JP"), "\ufffd", 0xf4, 0xfe);
+    // We have text either side of the replacement character, because all kinds of errors
+    // could lead to a replacement character being returned.
+    assertEncodes(Charset.forName("EUC-JP"), " \ufffd ", ' ', 0xf4, 0xfe, ' ');
+    assertDecodes(Charset.forName("EUC-JP"), " \ufffd ", ' ', 0xf4, 0xfe, ' ');
   }
 
   public void test_SCSU_replacement_character() throws Exception {
-    assertDecodes(Charset.forName("SCSU"), "\ufffd", 14, 0xff);
-    assertEncodes(Charset.forName("SCSU"), "\ufffd", 14, 0xff);
+    // We have text either side of the replacement character, because all kinds of errors
+    // could lead to a replacement character being returned.
+    assertEncodes(Charset.forName("SCSU"), " \ufffd ", ' ', 14, 0xff, 0xfd, ' ');
+    assertDecodes(Charset.forName("SCSU"), " \ufffd ", ' ', 14, 0xff, 0xfd, ' ');
   }
 
   public void test_Shift_JIS_replacement_character() throws Exception {
-    assertDecodes(Charset.forName("Shift_JIS"), "\ufffd", 0xfc);
-    assertEncodes(Charset.forName("Shift_JIS"), "\ufffd", 0xfc);
+    // We have text either side of the replacement character, because all kinds of errors
+    // could lead to a replacement character being returned.
+    assertEncodes(Charset.forName("Shift_JIS"), " \ufffd ", ' ', 0xfc, 0xfc, ' ');
+    assertDecodes(Charset.forName("Shift_JIS"), " \ufffd ", ' ', 0xfc, 0xfc, ' ');
   }
 
   public void test_UTF_16() throws Exception {
diff --git a/luni/src/main/java/java/nio/charset/CharsetDecoder.java b/luni/src/main/java/java/nio/charset/CharsetDecoder.java
index 7aa546c..7b53ceb 100644
--- a/luni/src/main/java/java/nio/charset/CharsetDecoder.java
+++ b/luni/src/main/java/java/nio/charset/CharsetDecoder.java
@@ -82,22 +82,22 @@
  * @see java.nio.charset.CharsetEncoder
  */
 public abstract class CharsetDecoder {
-    private static final int INIT = 0;
-    private static final int ONGOING = 1;
-    private static final int END = 2;
-    private static final int FLUSH = 3;
+    private static final String RESET = "RESET";
+    private static final String ONGOING = "ONGOING";
+    private static final String END_OF_INPUT = "END_OF_INPUT";
+    private static final String FLUSHED = "FLUSHED";
+
+    private final Charset charset;
 
     private final float averageCharsPerByte;
     private final float maxCharsPerByte;
 
-    private final Charset cs;
+    private String replacementChars = "\ufffd";
 
-    private CodingErrorAction malformedInputAction;
-    private CodingErrorAction unmappableCharacterAction;
+    private String state = RESET;
 
-    private String replacementChars;
-
-    private int status;
+    private CodingErrorAction malformedInputAction = CodingErrorAction.REPORT;
+    private CodingErrorAction unmappableCharacterAction = CodingErrorAction.REPORT;
 
     /**
      * Constructs a new <code>CharsetDecoder</code> using the given
@@ -114,8 +114,7 @@
      *            the maximum number of characters created by this decoder for
      *            one input byte, must be positive.
      * @throws IllegalArgumentException
-     *             if <code>averageCharsPerByte</code> or
-     *             <code>maxCharsPerByte</code> is negative.
+     *     if {@code averageCharsPerByte <= 0 || maxCharsPerByte <= 0 || averageCharsPerByte > maxCharsPerByte}.
      */
     protected CharsetDecoder(Charset charset, float averageCharsPerByte, float maxCharsPerByte) {
         if (averageCharsPerByte <= 0 || maxCharsPerByte <= 0) {
@@ -126,11 +125,7 @@
         }
         this.averageCharsPerByte = averageCharsPerByte;
         this.maxCharsPerByte = maxCharsPerByte;
-        cs = charset;
-        status = INIT;
-        malformedInputAction = CodingErrorAction.REPORT;
-        unmappableCharacterAction = CodingErrorAction.REPORT;
-        replacementChars = "\ufffd";
+        this.charset = charset;
     }
 
     /**
@@ -145,7 +140,7 @@
      * Returns the {@link Charset} which this decoder uses.
      */
     public final Charset charset() {
-        return cs;
+        return charset;
     }
 
     /**
@@ -182,35 +177,30 @@
      *             if another exception happened during the decode operation.
      */
     public final CharBuffer decode(ByteBuffer in) throws CharacterCodingException {
-        reset();
         int length = (int) (in.remaining() * averageCharsPerByte);
-        CharBuffer output = CharBuffer.allocate(length);
-        CoderResult result = null;
-        while (true) {
-            result = decode(in, output, false);
-            checkCoderResult(result);
-            if (result.isUnderflow()) {
-                break;
-            } else if (result.isOverflow()) {
-                output = allocateMore(output);
-            }
-        }
-        result = decode(in, output, true);
-        checkCoderResult(result);
+        CharBuffer out = CharBuffer.allocate(length);
 
-        while (true) {
-            result = flush(output);
-            checkCoderResult(result);
-            if (result.isOverflow()) {
-                output = allocateMore(output);
+        reset();
+
+        while (state != FLUSHED) {
+            CoderResult result = decode(in, out, true);
+            if (result == CoderResult.OVERFLOW) {
+                out = allocateMore(out);
+                continue; // No point trying to flush to an already-full buffer.
             } else {
-                break;
+                checkCoderResult(result);
+            }
+
+            result = flush(out);
+            if (result == CoderResult.OVERFLOW) {
+                out = allocateMore(out);
+            } else {
+                checkCoderResult(result);
             }
         }
 
-        output.flip();
-        status = FLUSH;
-        return output;
+        out.flip();
+        return out;
     }
 
     /*
@@ -304,54 +294,42 @@
      *             <code>BufferOverflowException</code>.
      */
     public final CoderResult decode(ByteBuffer in, CharBuffer out, boolean endOfInput) {
-        if (status == FLUSH || (!endOfInput && status == END)) {
-            throw new IllegalStateException();
+        if (state != RESET && state != ONGOING && !(endOfInput && state == END_OF_INPUT)) {
+            throw illegalStateException();
         }
 
-        CoderResult result = null;
+        state = endOfInput ? END_OF_INPUT : ONGOING;
 
-        // begin to decode
         while (true) {
-            CodingErrorAction action = null;
+            CoderResult result;
             try {
                 result = decodeLoop(in, out);
             } catch (BufferOverflowException ex) {
-                // unexpected exception
                 throw new CoderMalfunctionError(ex);
             } catch (BufferUnderflowException ex) {
-                // unexpected exception
                 throw new CoderMalfunctionError(ex);
             }
 
-            /*
-             * result handling
-             */
-            if (result.isUnderflow()) {
-                int remaining = in.remaining();
-                status = endOfInput ? END : ONGOING;
-                if (endOfInput && remaining > 0) {
-                    result = CoderResult.malformedForLength(remaining);
+            if (result == CoderResult.UNDERFLOW) {
+                if (endOfInput && in.hasRemaining()) {
+                    result = CoderResult.malformedForLength(in.remaining());
                 } else {
                     return result;
                 }
-            }
-            if (result.isOverflow()) {
+            } else if (result == CoderResult.OVERFLOW) {
                 return result;
             }
-            // set coding error handle action
-            action = malformedInputAction;
-            if (result.isUnmappable()) {
-                action = unmappableCharacterAction;
-            }
-            // If the action is IGNORE or REPLACE, we should continue decoding.
-            if (action == CodingErrorAction.REPLACE) {
+
+            // We have a real error, so do what the appropriate action tells us what to do...
+            CodingErrorAction action =
+                    result.isUnmappable() ? unmappableCharacterAction : malformedInputAction;
+            if (action == CodingErrorAction.REPORT) {
+                return result;
+            } else if (action == CodingErrorAction.REPLACE) {
                 if (out.remaining() < replacementChars.length()) {
                     return CoderResult.OVERFLOW;
                 }
                 out.put(replacementChars);
-            } else {
-                if (action != CodingErrorAction.IGNORE)
-                    return result;
             }
             in.position(in.position() + result.length());
         }
@@ -442,20 +420,15 @@
      * @return <code>CoderResult.UNDERFLOW</code> or
      *         <code>CoderResult.OVERFLOW</code>.
      * @throws IllegalStateException
-     *             if this decoder hasn't read all input bytes during one
-     *             decoding process, which means neither after calling
-     *             {@link #decode(ByteBuffer) decode(ByteBuffer)} nor after
-     *             calling {@link #decode(ByteBuffer, CharBuffer, boolean)
-     *             decode(ByteBuffer, CharBuffer, boolean)} with true as value
-     *             for the last boolean parameter.
+     *             if this decoder isn't already flushed or at end of input.
      */
     public final CoderResult flush(CharBuffer out) {
-        if (status != END && status != INIT) {
-            throw new IllegalStateException();
+        if (state != FLUSHED && state != END_OF_INPUT) {
+            throw illegalStateException();
         }
         CoderResult result = implFlush(out);
         if (result == CoderResult.UNDERFLOW) {
-            status = FLUSH;
+            state = FLUSHED;
         }
         return result;
     }
@@ -582,7 +555,7 @@
      *            the new action on malformed input error.
      * @return this decoder.
      * @throws IllegalArgumentException
-     *             if {@code newAction} is {@code null}.
+     *             if {@code newAction == null}.
      */
     public final CharsetDecoder onMalformedInput(CodingErrorAction newAction) {
         if (newAction == null) {
@@ -604,7 +577,7 @@
      *            the new action on unmappable character error.
      * @return this decoder.
      * @throws IllegalArgumentException
-     *             if {@code newAction} is {@code null}.
+     *             if {@code newAction == null}.
      */
     public final CharsetDecoder onUnmappableCharacter(CodingErrorAction newAction) {
         if (newAction == null) {
@@ -631,8 +604,8 @@
      * new replacement as argument.
      *
      * @param replacement
-     *            the replacement string, cannot be null or empty. Its length
-     *            cannot be larger than {@link #maxCharsPerByte()}.
+     *            the replacement string cannot be null, empty, or longer
+     *            than {@link #maxCharsPerByte()}.
      * @return this decoder.
      * @throws IllegalArgumentException
      *             if the given replacement cannot satisfy the requirement
@@ -655,14 +628,12 @@
     }
 
     /**
-     * Resets this decoder. This method will reset the internal status, and then
-     * calls <code>implReset()</code> to reset any status related to the
+     * Resets this decoder. This method will reset the internal state, and then
+     * calls {@link #implReset} to reset any state related to the
      * specific charset.
-     *
-     * @return this decoder.
      */
     public final CharsetDecoder reset() {
-        status = INIT;
+        state = RESET;
         implReset();
         return this;
     }
@@ -674,4 +645,8 @@
     public CodingErrorAction unmappableCharacterAction() {
         return unmappableCharacterAction;
     }
+
+    private IllegalStateException illegalStateException() {
+        throw new IllegalStateException("State: " + state);
+    }
 }
diff --git a/luni/src/main/java/java/nio/charset/CharsetEncoder.java b/luni/src/main/java/java/nio/charset/CharsetEncoder.java
index 5c4d3b3..9217bba 100644
--- a/luni/src/main/java/java/nio/charset/CharsetEncoder.java
+++ b/luni/src/main/java/java/nio/charset/CharsetEncoder.java
@@ -76,25 +76,22 @@
  * @see java.nio.charset.CharsetDecoder
  */
 public abstract class CharsetEncoder {
-    private static final int READY = 0;
-    private static final int ONGOING = 1;
-    private static final int END = 2;
-    private static final int FLUSH = 3;
-    private static final int INIT = 4;
+    private static final String RESET = "RESET";
+    private static final String ONGOING = "ONGOING";
+    private static final String END_OF_INPUT = "END_OF_INPUT";
+    private static final String FLUSHED = "FLUSHED";
 
-    private final Charset cs;
+    private final Charset charset;
 
     private final float averageBytesPerChar;
     private final float maxBytesPerChar;
 
     private byte[] replacementBytes;
 
-    private int status;
-    // internal status indicates encode(CharBuffer) operation is finished
-    private boolean finished;
+    private String state = RESET;
 
-    private CodingErrorAction malformedInputAction;
-    private CodingErrorAction unmappableCharacterAction;
+    private CodingErrorAction malformedInputAction = CodingErrorAction.REPORT;
+    private CodingErrorAction unmappableCharacterAction = CodingErrorAction.REPORT;
 
     // decoder instance for this encoder's charset, used for replacement value checking
     private CharsetDecoder decoder;
@@ -139,12 +136,9 @@
         if (averageBytesPerChar > maxBytesPerChar) {
             throw new IllegalArgumentException("averageBytesPerChar is greater than maxBytesPerChar");
         }
-        this.cs = cs;
+        this.charset = cs;
         this.averageBytesPerChar = averageBytesPerChar;
         this.maxBytesPerChar = maxBytesPerChar;
-        status = INIT;
-        malformedInputAction = CodingErrorAction.REPORT;
-        unmappableCharacterAction = CodingErrorAction.REPORT;
         if (trusted) {
             // The RI enforces unnecessary restrictions on the replacement bytes. We trust ICU to
             // know what it's doing. Doing so lets us support ICU's EUC-JP, SCSU, and Shift_JIS.
@@ -165,7 +159,7 @@
     /**
      * Tests whether the given character can be encoded by this encoder.
      *
-     * <p>Note that this method may change the internal status of this encoder, so
+     * <p>Note that this method may change the internal state of this encoder, so
      * it should not be called when another encoding process is ongoing,
      * otherwise it will throw an <code>IllegalStateException</code>.
      *
@@ -179,7 +173,7 @@
      * Tests whether the given <code>CharSequence</code> can be encoded by this
      * encoder.
      *
-     * <p>Note that this method may change the internal status of this encoder, so
+     * <p>Note that this method may change the internal state of this encoder, so
      * it should not be called when another encode process is ongoing, otherwise
      * it will throw an <code>IllegalStateException</code>.
      *
@@ -193,12 +187,13 @@
             cb = CharBuffer.wrap(sequence);
         }
 
-        if (status == FLUSH || status == INIT) {
-            status = READY;
+        if (state == FLUSHED) {
+            reset();
         }
-        if (status != READY) {
-            throw new IllegalStateException();
+        if (state != RESET) {
+            throw illegalStateException();
         }
+
         CodingErrorAction originalMalformedInputAction = malformedInputAction;
         CodingErrorAction originalUnmappableCharacterAction = unmappableCharacterAction;
         onMalformedInput(CodingErrorAction.REPORT);
@@ -219,7 +214,7 @@
      * Returns the {@link Charset} which this encoder uses.
      */
     public final Charset charset() {
-        return cs;
+        return charset;
     }
 
     /**
@@ -256,37 +251,27 @@
     public final ByteBuffer encode(CharBuffer in) throws CharacterCodingException {
         int length = (int) (in.remaining() * averageBytesPerChar);
         ByteBuffer out = ByteBuffer.allocate(length);
-        if (in.hasRemaining() == false) {
-            return out;
-        }
 
         reset();
 
-        while (in.hasRemaining()) {
+        while (state != FLUSHED) {
             CoderResult result = encode(in, out, true);
-            if (result == CoderResult.UNDERFLOW) {
-                break;
-            } else if (result == CoderResult.OVERFLOW) {
+            if (result == CoderResult.OVERFLOW) {
                 out = allocateMore(out);
-                continue;
+                continue; // No point trying to flush to an already-full buffer.
             } else {
                 checkCoderResult(result);
             }
 
             result = flush(out);
-            if (result == CoderResult.UNDERFLOW) {
-                break;
-            } else if (result == CoderResult.OVERFLOW) {
+            if (result == CoderResult.OVERFLOW) {
                 out = allocateMore(out);
-                continue;
             } else {
                 checkCoderResult(result);
             }
         }
 
         out.flip();
-        status = READY;
-        finished = true;
         return out;
     }
 
@@ -371,26 +356,22 @@
      *             <code>BufferUnderflowException</code>.
      */
     public final CoderResult encode(CharBuffer in, ByteBuffer out, boolean endOfInput) {
-        // If the previous step is encode(CharBuffer), then no more input is needed
-        // thus endOfInput should not be false
-        if (status == READY && finished && !endOfInput) {
-            throw new IllegalStateException();
-        }
-        if (status == FLUSH || (!endOfInput && status == END)) {
-            throw new IllegalStateException();
+        if (state != RESET && state != ONGOING && !(endOfInput && state == END_OF_INPUT)) {
+            throw illegalStateException();
         }
 
-        status = endOfInput ? END : ONGOING;
+        state = endOfInput ? END_OF_INPUT : ONGOING;
 
         while (true) {
             CoderResult result;
             try {
                 result = encodeLoop(in, out);
-            } catch (BufferOverflowException e) {
-                throw new CoderMalfunctionError(e);
-            } catch (BufferUnderflowException e) {
-                throw new CoderMalfunctionError(e);
+            } catch (BufferOverflowException ex) {
+                throw new CoderMalfunctionError(ex);
+            } catch (BufferUnderflowException ex) {
+                throw new CoderMalfunctionError(ex);
             }
+
             if (result == CoderResult.UNDERFLOW) {
                 if (endOfInput && in.hasRemaining()) {
                     result = CoderResult.malformedForLength(in.remaining());
@@ -400,12 +381,13 @@
             } else if (result == CoderResult.OVERFLOW) {
                 return result;
             }
+
+            // We have a real error, so do what the appropriate action tells us what to do...
             CodingErrorAction action =
                     result.isUnmappable() ? unmappableCharacterAction : malformedInputAction;
             if (action == CodingErrorAction.REPORT) {
                 return result;
-            }
-            if (action == CodingErrorAction.REPLACE) {
+            } else if (action == CodingErrorAction.REPLACE) {
                 if (out.remaining() < replacementBytes.length) {
                     return CoderResult.OVERFLOW;
                 }
@@ -473,20 +455,15 @@
      * @return <code>CoderResult.UNDERFLOW</code> or
      *         <code>CoderResult.OVERFLOW</code>.
      * @throws IllegalStateException
-     *             if this encoder hasn't read all input characters during one
-     *             encoding process, which means neither after calling
-     *             {@link #encode(CharBuffer) encode(CharBuffer)} nor after
-     *             calling {@link #encode(CharBuffer, ByteBuffer, boolean)
-     *             encode(CharBuffer, ByteBuffer, boolean)} with {@code true}
-     *             for the last boolean parameter.
+     *             if this encoder isn't already flushed or at end of input.
      */
     public final CoderResult flush(ByteBuffer out) {
-        if (status != END && status != READY) {
-            throw new IllegalStateException();
+        if (state != FLUSHED && state != END_OF_INPUT) {
+            throw illegalStateException();
         }
         CoderResult result = implFlush(out);
         if (result == CoderResult.UNDERFLOW) {
-            status = FLUSH;
+            state = FLUSHED;
         }
         return result;
     }
@@ -555,7 +532,7 @@
      */
     public boolean isLegalReplacement(byte[] replacement) {
         if (decoder == null) {
-            decoder = cs.newDecoder();
+            decoder = charset.newDecoder();
             decoder.onMalformedInput(CodingErrorAction.REPORT);
             decoder.onUnmappableCharacter(CodingErrorAction.REPORT);
         }
@@ -671,14 +648,12 @@
     }
 
     /**
-     * Resets this encoder. This method will reset the internal status and then
-     * calls <code>implReset()</code> to reset any status related to the
+     * Resets this encoder. This method will reset the internal state and then
+     * calls {@link #implReset} to reset any state related to the
      * specific charset.
-     *
-     * @return this encoder.
      */
     public final CharsetEncoder reset() {
-        status = INIT;
+        state = RESET;
         implReset();
         return this;
     }
@@ -690,4 +665,8 @@
     public CodingErrorAction unmappableCharacterAction() {
         return unmappableCharacterAction;
     }
+
+    private IllegalStateException illegalStateException() {
+        throw new IllegalStateException("State: " + state);
+    }
 }