/* Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * 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.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.harmony.tests.java.nio.charset;

import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CharsetEncoder;
import java.nio.charset.CoderResult;
import java.nio.charset.CodingErrorAction;
import java.nio.charset.MalformedInputException;
import java.nio.charset.UnmappableCharacterException;
import java.nio.charset.UnsupportedCharsetException;
import java.util.Arrays;

import junit.framework.TestCase;

/**
 * API unit test for java.nio.charset.CharsetEncoder
 */
public class CharsetEncoderTest extends TestCase {

	static final int MAX_BYTES = 3;

	static final float AVER_BYTES = 0.5f;

	// charset for mock class
	private static final Charset MOCKCS = new MockCharset("CharsetEncoderTest_mock", new String[0]);

	Charset cs = MOCKCS;

	// default encoder
	CharsetEncoder encoder;

	// default for Charset abstract class
	byte[] defaultReplacement = new byte[] { 63 };

	// specific for Charset implementation subclass
	byte[] specifiedReplacement = new byte[] { 63 };

	static final String unistr = " buffer";// \u8000\u8001\u00a5\u3000\r\n";

	byte[] unibytes = new byte[] { 32, 98, 117, 102, 102, 101, 114 };

	byte[] unibytesWithRep = null;

	byte[] surrogate = new byte[0];

	protected void setUp() throws Exception {
		super.setUp();
		encoder = cs.newEncoder();
		if (null == unibytesWithRep) {
			byte[] replacement = encoder.replacement();
			unibytesWithRep = new byte[replacement.length + unibytes.length];
			System.arraycopy(replacement, 0, unibytesWithRep, 0,
					replacement.length);
			System.arraycopy(unibytes, 0, unibytesWithRep, replacement.length,
					unibytes.length);
		}
	}

	/*
	 * @see TestCase#tearDown()
	 */
	protected void tearDown() throws Exception {
		super.tearDown();
	}

	public void testSpecificDefaultValue() {
		assertTrue(encoder.averageBytesPerChar() == AVER_BYTES);
		assertTrue(encoder.maxBytesPerChar() == MAX_BYTES);
	}

	public void testDefaultValue() {
		assertEquals(CodingErrorAction.REPORT, encoder.malformedInputAction());
		assertEquals(CodingErrorAction.REPORT, encoder.unmappableCharacterAction());
		assertSame(encoder, encoder.onMalformedInput(CodingErrorAction.IGNORE));
		assertSame(encoder, encoder.onUnmappableCharacter(CodingErrorAction.IGNORE));
		if (encoder instanceof MockCharsetEncoder) {
			assertTrue(Arrays.equals(encoder.replacement(), defaultReplacement));
		} else {
			assertTrue(Arrays.equals(encoder.replacement(), specifiedReplacement));
		}

	}

	/*
	 * Class under test for constructor CharsetEncoder(Charset, float, float)
	 */
	public void testCharsetEncoderCharsetfloatfloat() {
		// default value
		encoder = new MockCharsetEncoder(cs, (float) AVER_BYTES, MAX_BYTES);
		assertSame(encoder.charset(), cs);
		assertTrue(encoder.averageBytesPerChar() == AVER_BYTES);
		assertTrue(encoder.maxBytesPerChar() == MAX_BYTES);
		assertEquals(CodingErrorAction.REPORT, encoder.malformedInputAction());
		assertEquals(CodingErrorAction.REPORT, encoder
				.unmappableCharacterAction());
		assertEquals(new String(encoder.replacement()), new String(
				defaultReplacement));
		assertSame(encoder, encoder.onMalformedInput(CodingErrorAction.IGNORE));
		assertSame(encoder, encoder
				.onUnmappableCharacter(CodingErrorAction.IGNORE));

		// normal case
		CharsetEncoder ec = new MockCharsetEncoder(cs, 1, MAX_BYTES);
		assertSame(ec.charset(), cs);
		assertEquals(1.0, ec.averageBytesPerChar(), 0);
		assertTrue(ec.maxBytesPerChar() == MAX_BYTES);

		/*
		 * ------------------------ Exceptional cases -------------------------
		 */
		// NullPointerException: null charset
		try {
			ec = new MockCharsetEncoder(null, 1, MAX_BYTES);
			fail("should throw null pointer exception");
		} catch (NullPointerException e) {
		}

		ec = new MockCharsetEncoder(new MockCharset("mock", new String[0]), 1,
				MAX_BYTES);

                // Commented out since the comment is wrong since MAX_BYTES > 1
		// // OK: average length less than max length
		// ec = new MockCharsetEncoder(cs, MAX_BYTES, 1);
		// assertTrue(ec.averageBytesPerChar() == MAX_BYTES);
		// assertTrue(ec.maxBytesPerChar() == 1);

		// Illegal Argument: zero length
		try {
			ec = new MockCharsetEncoder(cs, 0, MAX_BYTES);
			fail("should throw IllegalArgumentException");
		} catch (IllegalArgumentException e) {
		}
		try {
			ec = new MockCharsetEncoder(cs, 1, 0);
			fail("should throw IllegalArgumentException");
		} catch (IllegalArgumentException e) {
		}

		// Illegal Argument: negative length
		try {
			ec = new MockCharsetEncoder(cs, -1, MAX_BYTES);
			fail("should throw IllegalArgumentException");
		} catch (IllegalArgumentException e) {
		}
		try {
			ec = new MockCharsetEncoder(cs, 1, -1);
			fail("should throw IllegalArgumentException");
		} catch (IllegalArgumentException e) {
		}
	}

	/*
	 * Class under test for constructor CharsetEncoder(Charset, float, float,
	 * byte[])
	 */
	public void testCharsetEncoderCharsetfloatfloatbyteArray() {
		byte[] ba = getLegalByteArray();
		// normal case
		CharsetEncoder ec = new MockCharsetEncoder(cs, 1, MAX_BYTES, ba);
		assertSame(ec.charset(), cs);
		assertEquals(1.0, ec.averageBytesPerChar(), 0.0);
		assertTrue(ec.maxBytesPerChar() == MAX_BYTES);
		assertTrue(Arrays.equals(ba, ec.replacement()));

		/*
		 * ------------------------ Exceptional cases -------------------------
		 */
		// NullPointerException: null charset
		try {
			ec = new MockCharsetEncoder(null, 1, MAX_BYTES, ba);
			fail("should throw null pointer exception");
		} catch (NullPointerException e) {
		}

		// Illegal Argument: null byte array
		try {
			ec = new MockCharsetEncoder(cs, 1, MAX_BYTES, null);
			fail("should throw IllegalArgumentException");
		} catch (IllegalArgumentException e) {
		}
		// Illegal Argument: empty byte array
		try {
			ec = new MockCharsetEncoder(cs, 1, MAX_BYTES, new byte[0]);
			fail("should throw IllegalArgumentException");
		} catch (IllegalArgumentException e) {
		}
		// Illegal Argument: byte array is longer than max length
		try {
			ec = new MockCharsetEncoder(cs, 1, MAX_BYTES, new byte[] { 1, 2,
					MAX_BYTES, 4 });
			fail("should throw IllegalArgumentException");
		} catch (IllegalArgumentException e) {
		}

                // Commented out since the comment is wrong since MAX_BYTES > 1
                // This test throws IllegalArgumentException on Harmony and RI
		// // OK: average length less than max length
		// ec = new MockCharsetEncoder(cs, MAX_BYTES, ba.length, ba);
		// assertTrue(ec.averageBytesPerChar() == MAX_BYTES);
		// assertTrue(ec.maxBytesPerChar() == ba.length);

		// Illegal Argument: zero length
		try {
			ec = new MockCharsetEncoder(cs, 0, MAX_BYTES, ba);
			fail("should throw IllegalArgumentException");
		} catch (IllegalArgumentException e) {
		}
		try {
			ec = new MockCharsetEncoder(cs, 1, 0, ba);
			fail("should throw IllegalArgumentException");
		} catch (IllegalArgumentException e) {
		}

		// Illegal Argument: negative length
		try {
			ec = new MockCharsetEncoder(cs, -1, MAX_BYTES, ba);
			fail("should throw IllegalArgumentException");
		} catch (IllegalArgumentException e) {
		}
		try {
			ec = new MockCharsetEncoder(cs, 1, -1, ba);
			fail("should throw IllegalArgumentException");
		} catch (IllegalArgumentException e) {
		}
	}

	/*
	 * Class under test for boolean canEncode(char)
	 */
	public void testCanEncodechar() throws CharacterCodingException {
		// for non-mapped char
		assertTrue(encoder.canEncode('\uc2c0'));
		// surrogate char for unicode
		// 1st byte: d800-dbff
		// 2nd byte: dc00-dfff
		assertTrue(encoder.canEncode('\ud800'));
		// valid surrogate pair
		assertTrue(encoder.canEncode('\udc00'));
	}

	/*-----------------------------------------
	 * Class under test for illegal state case
	 * methods which can change internal states are two encode, flush, two canEncode, reset
	 * -----------------------------------------
	 */

	// Normal case: just after reset, and it also means reset can be done
	// anywhere
	public void testResetIllegalState() throws CharacterCodingException {
		assertSame(encoder, encoder.reset());
		encoder.canEncode('\ud901');
		assertSame(encoder, encoder.reset());
		encoder.canEncode("\ud901\udc00");
		assertSame(encoder, encoder.reset());
		encoder.encode(CharBuffer.wrap("aaa"));
		assertSame(encoder, encoder.reset());
		encoder.encode(CharBuffer.wrap("aaa"), ByteBuffer.allocate(3), false);
		assertSame(encoder, encoder.reset());
		encoder.encode(CharBuffer.wrap("aaa"), ByteBuffer.allocate(3), true);
		assertSame(encoder, encoder.reset());
	}

  public void testFlushIllegalState() throws CharacterCodingException {
    CharBuffer in = CharBuffer.wrap("aaa");
    ByteBuffer out = ByteBuffer.allocate(5);

    // Illegal state: after reset.
    encoder.reset();
    try {
      encoder.flush(out);
      fail();
    } catch (IllegalStateException 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);

    // 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);

		//Illegal state: flush after instance created
		try {
			encoder.flush(out);
			fail("should throw IllegalStateException");
		} catch (IllegalStateException e) {
			// Expected
		}

	}

	// test illegal states for encode facade
	public void testEncodeFacadeIllegalState() throws CharacterCodingException {
		// encode facade can be execute in anywhere
		CharBuffer in = CharBuffer.wrap("aaa");
		// Normal case: just created
		encoder.encode(in);
		in.rewind();

		// Normal case: just after encode facade
		encoder.encode(in);
		in.rewind();

		// Normal case: just after canEncode
		assertSame(encoder, encoder.reset());
		encoder.canEncode("\ud902\udc00");
		encoder.encode(in);
		in.rewind();
		assertSame(encoder, encoder.reset());
		encoder.canEncode('\ud902');
		encoder.encode(in);
		in.rewind();

		// Normal case: just after encode with that endOfInput is true
		assertSame(encoder, encoder.reset());
		encoder.encode(CharBuffer.wrap("testCanEncodeIllegalState2"),
				ByteBuffer.allocate(30), true);
		encoder.encode(in);
		in.rewind();

		// Normal case:just after encode with that endOfInput is false
		assertSame(encoder, encoder.reset());
		encoder.encode(CharBuffer.wrap("testCanEncodeIllegalState3"),
				ByteBuffer.allocate(30), false);
		encoder.encode(in);
		in.rewind();

		// Normal case: just after flush
		assertSame(encoder, encoder.reset());
		encoder.encode(CharBuffer.wrap("testCanEncodeIllegalState4"),
				ByteBuffer.allocate(30), true);
		encoder.flush(ByteBuffer.allocate(10));
		encoder.encode(in);
		in.rewind();
	}

	// test illegal states for two encode method with endOfInput is true
	public void testEncodeTrueIllegalState() throws CharacterCodingException {
		CharBuffer in = CharBuffer.wrap("aaa");
		ByteBuffer out = ByteBuffer.allocate(5);
		// Normal case: just created
		encoder.encode(in, out, true);
		in.rewind();
		out.rewind();

		in.rewind();
		out.rewind();

		// Normal case: just after encode with that endOfInput is true
		assertSame(encoder, encoder.reset());
		encoder.encode(CharBuffer.wrap("testCanEncodeIllegalState2"),
				ByteBuffer.allocate(30), true);
		encoder.encode(in, out, true);
		in.rewind();
		out.rewind();

		// Normal case:just after encode with that endOfInput is false
		assertSame(encoder, encoder.reset());
		encoder.encode(CharBuffer.wrap("testCanEncodeIllegalState3"),
				ByteBuffer.allocate(30), false);
		encoder.encode(in, out, true);
		in.rewind();
		out.rewind();

		// Illegal state: just after flush
		assertSame(encoder, encoder.reset());
		encoder.encode(CharBuffer.wrap("testCanEncodeIllegalState4"),
				ByteBuffer.allocate(30), true);
		encoder.flush(ByteBuffer.allocate(10));
		try {
			encoder.encode(in, out, true);
			fail("should illegal state");
		} catch (IllegalStateException e) {
		}

		// Normal case: after canEncode
		assertSame(encoder, encoder.reset());
		encoder.canEncode("\ud906\udc00");
		encoder.encode(in, out, true);
		in.rewind();
		out.rewind();
		assertSame(encoder, encoder.reset());
		encoder.canEncode('\ud905');
		encoder.encode(in, out, true);
	}

	// test illegal states for two encode method with endOfInput is false
	public void testEncodeFalseIllegalState() throws CharacterCodingException {
		CharBuffer in = CharBuffer.wrap("aaa");
		ByteBuffer out = ByteBuffer.allocate(5);
		// Normal case: just created
		encoder.encode(in, out, false);
		in.rewind();
		out.rewind();

		// Illegal state: just after encode facade
		assertSame(encoder, encoder.reset());
		encoder.encode(CharBuffer.wrap("testCanEncodeIllegalState1"));
		try {
			encoder.encode(in, out, false);
			fail("should illegal state");
		} catch (IllegalStateException e) {
		}

		// Illegal state: just after encode with that endOfInput is true
		assertSame(encoder, encoder.reset());
		encoder.encode(CharBuffer.wrap("testCanEncodeIllegalState2"),
				ByteBuffer.allocate(30), true);
		try {
			encoder.encode(in, out, false);
			fail("should illegal state");
		} catch (IllegalStateException e) {
		}

		// Normal case:just after encode with that endOfInput is false
		assertSame(encoder, encoder.reset());
		encoder.encode(CharBuffer.wrap("testCanEncodeIllegalState3"),
				ByteBuffer.allocate(30), false);
		encoder.encode(in, out, false);
		in.rewind();
		out.rewind();

		// Illegal state: just after flush
		assertSame(encoder, encoder.reset());
		encoder.encode(CharBuffer.wrap("testCanEncodeIllegalState4"),
				ByteBuffer.allocate(30), true);
		encoder.flush(ByteBuffer.allocate(10));
		try {
			encoder.encode(in, out, false);
			fail("should illegal state");
		} catch (IllegalStateException e) {
		}

		// Normal case: after canEncode
		assertSame(encoder, encoder.reset());
		encoder.canEncode("\ud906\udc00");
		encoder.encode(in, out, false);
		in.rewind();
		out.rewind();
		assertSame(encoder, encoder.reset());
		encoder.canEncode('\ud905');
		encoder.encode(in, out, false);
	}

	// test illegal states for two canEncode methods
	public void testCanEncodeIllegalState() throws CharacterCodingException {
		// Normal case: just created
		encoder.canEncode("\ud900\udc00");
		encoder.canEncode('\ud900');

		// Illegal state: just after encode with that endOfInput is true
		assertSame(encoder, encoder.reset());
		encoder.encode(CharBuffer.wrap("testCanEncodeIllegalState2"),
				ByteBuffer.allocate(30), true);
		try {
			encoder.canEncode("\ud903\udc00");
			fail("should throw illegal state exception");
		} catch (IllegalStateException e) {
		}

		// Illegal state:just after encode with that endOfInput is false
		assertSame(encoder, encoder.reset());
		encoder.encode(CharBuffer.wrap("testCanEncodeIllegalState3"),
				ByteBuffer.allocate(30), false);
		try {
			encoder.canEncode("\ud904\udc00");
			fail("should throw illegal state exception");
		} catch (IllegalStateException e) {
		}

		// Normal case: just after flush
		encoder.encode(CharBuffer.wrap("testCanEncodeIllegalState4"),
				ByteBuffer.allocate(30), true);
		encoder.flush(ByteBuffer.allocate(10));
		encoder.canEncode("\ud905\udc00");
		encoder.canEncode('\ud906');

		// Normal case: after reset again
		assertSame(encoder, encoder.reset());
		encoder.canEncode("\ud906\udc00");
		encoder.canEncode('\ud905');
	}

	/*
	 * --------------------------------- illegal state test end
	 * ---------------------------------
	 */

	/*
	 * Class under test for boolean canEncode(CharSequence)
	 */
	public void testCanEncodeCharSequence() {
		// for non-mapped char
		assertTrue(encoder.canEncode("\uc2c0"));
		// surrogate char for unicode
		// 1st byte: d800-dbff
		// 2nd byte: dc00-dfff
		// valid surrogate pair
		assertTrue(encoder.canEncode("\ud800\udc00"));
		// invalid surrogate pair
		assertTrue(encoder.canEncode("\ud800\udb00"));
	}

    public void test_canEncode_empty() throws Exception {
        assertTrue(encoder.canEncode(""));
    }

    public void test_canEncode_null() throws Exception {
        try {
            encoder.canEncode(null);
            fail();
        } catch (NullPointerException e) {
        }
    }

	/*
	 * Class under test for Charset charset()
	 */
	public void testCharset() {
		try {
			encoder = new MockCharsetEncoder(Charset.forName("gbk"), 1,
					MAX_BYTES);
			// assertSame(encoder.charset(), Charset.forName("gbk"));
		} catch (UnsupportedCharsetException e) {
			System.err
					.println("Don't support GBK encoding, ignore current test");
		}
	}

	/*
	 * Class under test for ByteBuffer encode(CharBuffer)
	 */
	public void testEncodeCharBuffer() throws CharacterCodingException {
		// Null pointer
		try {
			encoder.encode(null);
			fail("should throw null pointer exception");
		} catch (NullPointerException e) {
		}

		// empty input buffer
		ByteBuffer out = encoder.encode(CharBuffer.wrap(""));
		assertEquals(out.position(), 0);
		assertByteArray(out, new byte[0]);
		// assertByteArray(out, surrogate);

		// normal case
		out = encoder.encode(CharBuffer.wrap(unistr));
		assertEquals(out.position(), 0);
		assertByteArray(out, addSurrogate(unibytes));

        // Regression test for harmony-3378
        Charset cs = Charset.forName("UTF-8");
        CharsetEncoder encoder = cs.newEncoder();
        encoder.onMalformedInput(CodingErrorAction.REPLACE);
        encoder = encoder.replaceWith(new byte[] { (byte) 0xef, (byte) 0xbf,
                (byte) 0xbd, });
        CharBuffer in = CharBuffer.wrap("\ud800");
        out = encoder.encode(in);
        assertNotNull(out);
	}

	private byte[] addSurrogate(byte[] expected) {
		if (surrogate.length > 0) {
			byte[] temp = new byte[surrogate.length + expected.length];
			System.arraycopy(surrogate, 0, temp, 0, surrogate.length);
			System.arraycopy(expected, 0, temp, surrogate.length,
					expected.length);
			expected = temp;
		}
		return expected;
	}

	/**
	 * @return
	 */
	protected byte[] getEmptyByteArray() {
		return new byte[0];
	}

	CharBuffer getMalformedCharBuffer() {
		return CharBuffer.wrap("malform buffer");
	}

	CharBuffer getUnmapCharBuffer() {
		return CharBuffer.wrap("unmap buffer");
	}

	CharBuffer getExceptionCharBuffer() {
		return CharBuffer.wrap("runtime buffer");
	}

	public void testEncodeCharBufferException() throws CharacterCodingException {
		ByteBuffer out;
		CharBuffer in;
		// MalformedException:
		in = getMalformedCharBuffer();
		encoder.onMalformedInput(CodingErrorAction.REPORT);
		encoder.onUnmappableCharacter(CodingErrorAction.REPORT);
		if (in != null) {
			try {
                // regression test for Harmony-1379
				encoder.encode(in);
				fail("should throw MalformedInputException");
			} catch (MalformedInputException e) {
			}

			encoder.reset();
			in.rewind();
			encoder.onMalformedInput(CodingErrorAction.IGNORE);
			out = encoder.encode(in);
			assertByteArray(out, addSurrogate(unibytes));

			encoder.reset();
			in.rewind();
			encoder.onMalformedInput(CodingErrorAction.REPLACE);
			out = encoder.encode(in);
			assertByteArray(out, addSurrogate(unibytesWithRep));
		}

		// Unmapped Exception:
		in = getUnmapCharBuffer();
		encoder.onMalformedInput(CodingErrorAction.REPORT);
		encoder.onUnmappableCharacter(CodingErrorAction.REPORT);
		if (in != null) {
			encoder.reset();
			try {
				encoder.encode(in);
				fail("should throw UnmappableCharacterException");
			} catch (UnmappableCharacterException e) {
			}

			encoder.reset();
			in.rewind();
			encoder.onUnmappableCharacter(CodingErrorAction.IGNORE);
			out = encoder.encode(in);
			assertByteArray(out, unibytes);

			encoder.reset();
			in.rewind();
			encoder.onUnmappableCharacter(CodingErrorAction.REPLACE);
			out = encoder.encode(in);
			assertByteArray(out, unibytesWithRep);
		}

		// RuntimeException
		try {
			encoder.encode(getExceptionCharBuffer());
			fail("should throw runtime exception");
		} catch (RuntimeException e) {
		}
	}

	/*
	 * utility method, extract given bytebuffer to a string and compare with
	 * give string
	 */
	void assertByteArray(ByteBuffer out, byte[] expected) {
		out = out.duplicate();
		if (out.position() != 0) {
			out.flip();
		}
		byte[] ba = new byte[out.limit() - out.position()];
		out.get(ba);
		// byte[] ba = out.array();
		assertTrue(Arrays.equals(ba, expected));
	}

	/*
	 * Class under test for CoderResult encode(CharBuffer, ByteBuffer, boolean)
	 */
	public void testEncodeCharBufferByteBufferboolean()
			throws CharacterCodingException {
		ByteBuffer out = ByteBuffer.allocate(200);
		CharBuffer in = CharBuffer.wrap(unistr);
		// Null pointer
		try {
			encoder.encode(null, out, true);
			fail("should throw null pointer exception");
		} catch (NullPointerException e) {
		}
		try {
			encoder.encode(in, null, true);
			fail("should throw null pointer exception");
		} catch (NullPointerException e) {
		}

		// normal case, one complete operation
		assertSame(encoder, encoder.reset());
		in.rewind();
		out.rewind();
		assertSame(CoderResult.UNDERFLOW, encoder.encode(in, out, true));
		assertEquals(out.limit(), 200);
		assertTrue(out.position() > 0);
		assertTrue(out.remaining() > 0);
		assertEquals(out.capacity(), 200);
		assertByteArray(out, addSurrogate(unibytes));
		in.rewind();

		encoder.flush(out);

		// normal case, one complete operation, but call twice, first time set
		// endOfInput to false
		assertSame(encoder, encoder.reset());
		in.rewind();
		out = ByteBuffer.allocate(200);
		assertSame(CoderResult.UNDERFLOW, encoder.encode(in, out, false));
		assertEquals(out.limit(), 200);
		assertTrue(out.position() > 0);
		assertTrue(out.remaining() > 0);
		assertEquals(out.capacity(), 200);
		assertByteArray(out, addSurrogate(unibytes));

		in.rewind();
		assertSame(CoderResult.UNDERFLOW, encoder.encode(in, out, false));
		in.rewind();
		assertSame(CoderResult.UNDERFLOW, encoder.encode(in, out, true));
		assertEquals(out.limit(), 200);
		assertTrue(out.position() > 0);
		assertTrue(out.remaining() > 0);
		assertEquals(out.capacity(), 200);

		assertByteArray(out, addSurrogate(duplicateByteArray(unibytes, 3)));

		// overflow
		out = ByteBuffer.allocate(4);
		assertSame(encoder, encoder.reset());
		in.rewind();
		out.rewind();
		assertSame(CoderResult.OVERFLOW, encoder.encode(in, out, true));
		assertEquals(out.limit(), 4);
		assertEquals(out.position(), 4);
		assertEquals(out.remaining(), 0);
		assertEquals(out.capacity(), 4);
		ByteBuffer temp = ByteBuffer.allocate(200);
		out.flip();
		temp.put(out);
		out = temp;
		assertSame(CoderResult.UNDERFLOW, encoder.encode(in, out, true));
		assertEquals(out.limit(), 200);
		assertTrue(out.position() > 0);
		assertTrue(out.remaining() > 0);
		assertEquals(out.capacity(), 200);
		assertByteArray(out, addSurrogate(unibytes));

		assertSame(encoder, encoder.reset());
		in.rewind();
		out = ByteBuffer.allocate(4);
		assertSame(CoderResult.OVERFLOW, encoder.encode(in, out, false));
		assertEquals(out.limit(), 4);
		assertEquals(out.position(), 4);
		assertEquals(out.remaining(), 0);
		assertEquals(out.capacity(), 4);
		temp = ByteBuffer.allocate(200);
		out.flip();
		temp.put(out);
		out = temp;
		assertSame(CoderResult.UNDERFLOW, encoder.encode(in, out, false));
		assertEquals(out.limit(), 200);
		assertTrue(out.position() > 0);
		assertTrue(out.remaining() > 0);
		assertEquals(out.capacity(), 200);
		assertByteArray(out, addSurrogate(unibytes));
	}

//	void printByteBuffer(ByteBuffer buffer) {
//		System.out.println("print buffer");
//		if (buffer.position() != 0) {
//			buffer.flip();
//		}
//		byte[] ba = buffer.array();
//		for (int i = 0; i < ba.length; i++) {
//			System.out.println(Integer.toHexString(ba[i]));
//		}
//	}

	public void testEncodeCharBufferByteBufferbooleanExceptionFalse()
			throws CharacterCodingException {
		implTestEncodeCharBufferByteBufferbooleanException(false);
	}

	public void testEncodeCharBufferByteBufferbooleanExceptionTrue()
			throws CharacterCodingException {
		implTestEncodeCharBufferByteBufferbooleanException(true);
	}

	private byte[] duplicateByteArray(byte[] ba, int times) {
		byte[] result = new byte[ba.length * times];
		for (int i = 0; i < times; i++) {
			System.arraycopy(ba, 0, result, i * ba.length, ba.length);
		}
		return result;
	}

	protected void implTestEncodeCharBufferByteBufferbooleanException(
			boolean endOfInput) throws CharacterCodingException {
		ByteBuffer out = ByteBuffer.allocate(100);

		// MalformedException:
		CharBuffer in = getMalformedCharBuffer();
		encoder.onMalformedInput(CodingErrorAction.REPORT);
		encoder.onUnmappableCharacter(CodingErrorAction.REPORT);
		if (in != null) {
			encoder.reset();
			CoderResult r = encoder.encode(in, out, endOfInput);
			assertTrue(r.isMalformed());

			encoder.reset();
			out.clear();
			in.rewind();
			encoder.onMalformedInput(CodingErrorAction.IGNORE);
			assertSame(CoderResult.UNDERFLOW, encoder.encode(in, out,
					endOfInput));
			assertCodingErrorAction(endOfInput, out, in, unibytes);

			encoder.reset();
			out.clear();
			in.rewind();
			encoder.onMalformedInput(CodingErrorAction.REPLACE);
			assertSame(CoderResult.UNDERFLOW, encoder.encode(in, out,
					endOfInput));
			assertCodingErrorAction(endOfInput, out, in, unibytesWithRep);
//		} else {
//			System.out.println("Cannot find malformed char buffer for "
//					+ cs.name());
		}

		// Unmapped Exception:
		in = getUnmapCharBuffer();
		encoder.onMalformedInput(CodingErrorAction.REPORT);
		encoder.onUnmappableCharacter(CodingErrorAction.REPORT);
		if (in != null) {
			encoder.reset();
			out.clear();
			assertTrue(encoder.encode(in, out, endOfInput).isUnmappable());

			encoder.reset();
			out.clear();
			in.rewind();
			encoder.onUnmappableCharacter(CodingErrorAction.IGNORE);
			assertSame(CoderResult.UNDERFLOW, encoder.encode(in, out,
					endOfInput));
			assertCodingErrorAction(endOfInput, out, in, unibytes);

			encoder.reset();
			out.clear();
			in.rewind();
			encoder.onUnmappableCharacter(CodingErrorAction.REPLACE);
			assertSame(CoderResult.UNDERFLOW, encoder.encode(in, out,
					endOfInput));
			assertCodingErrorAction(endOfInput, out, in, unibytesWithRep);
//		} else {
//			System.out.println("Cannot find unmapped char buffer for "
//					+ cs.name());
		}

		// RuntimeException
		try {
			encoder.encode(getExceptionCharBuffer());
			fail("should throw runtime exception");
		} catch (RuntimeException e) {
		}
	}

	private void assertCodingErrorAction(boolean endOfInput, ByteBuffer out,
			CharBuffer in, byte[] expect) {
		if (endOfInput) {
			assertByteArray(out, addSurrogate(expect));
		} else {
			in.rewind();
			assertSame(CoderResult.UNDERFLOW, encoder.encode(in, out,
					endOfInput));
			in.rewind();
			assertSame(CoderResult.UNDERFLOW, encoder.encode(in, out, true));
			assertByteArray(out, addSurrogate(duplicateByteArray(expect, 3)));
		}
	}

	/*
	 * Class under test for CoderResult flush(ByteBuffer)
	 */
	public void testFlush() throws CharacterCodingException {
		ByteBuffer out = ByteBuffer.allocate(6);
		CharBuffer in = CharBuffer.wrap("aaa");
		assertEquals(in.remaining(), 3);

		// by encode facade, so that internal state will be wrong
		encoder.encode(CharBuffer.wrap("testFlush"), ByteBuffer.allocate(20),
				true);
		assertSame(CoderResult.UNDERFLOW, encoder
				.flush(ByteBuffer.allocate(50)));
	}

	/*
	 * test isLegalReplacement(byte[])
	 */
	public void test_isLegalReplacement_null() {
		try {
			encoder.isLegalReplacement(null);
			fail("should throw null pointer exception");
		} catch (NullPointerException e) {
		}
	}

	public void test_isLegalReplacement_good() {
		assertTrue(encoder.isLegalReplacement(specifiedReplacement));
	}

	public void test_isLegalReplacement_bad() {
		assertTrue(encoder.isLegalReplacement(new byte[200]));
		byte[] ba = getIllegalByteArray();
		if (ba != null) {
			assertFalse(encoder.isLegalReplacement(ba));
		}
	}

	public void test_isLegalReplacement_empty_array() {
		// ISO, ASC, GB, UTF8 encoder will throw exception in RI
		// others will pass
		assertTrue(encoder.isLegalReplacement(new byte[0]));
	}

	public void testOnMalformedInput() {
		assertSame(CodingErrorAction.REPORT, encoder.malformedInputAction());
		try {
			encoder.onMalformedInput(null);
			fail("should throw null pointer exception");
		} catch (IllegalArgumentException e) {
		}
		encoder.onMalformedInput(CodingErrorAction.IGNORE);
		assertSame(CodingErrorAction.IGNORE, encoder.malformedInputAction());
	}

	public void testOnUnmappableCharacter() {
		assertSame(CodingErrorAction.REPORT, encoder
				.unmappableCharacterAction());
		try {
			encoder.onUnmappableCharacter(null);
			fail("should throw null pointer exception");
		} catch (IllegalArgumentException e) {
		}
		encoder.onUnmappableCharacter(CodingErrorAction.IGNORE);
		assertSame(CodingErrorAction.IGNORE, encoder
				.unmappableCharacterAction());
	}

	public void testReplacement() {
		try {
			encoder.replaceWith(null);
			fail("should throw null pointer exception");
		} catch (IllegalArgumentException e) {
		}
		try {
			encoder.replaceWith(new byte[0]);
			fail("should throw null pointer exception");
		} catch (IllegalArgumentException e) {
		}
		try {
			encoder.replaceWith(new byte[100]);
			fail("should throw null pointer exception");
		} catch (IllegalArgumentException e) {
		}

		byte[] nr = getLegalByteArray();
		assertSame(encoder, encoder.replaceWith(nr));
		assertTrue(Arrays.equals(nr, encoder.replacement()));

		nr = getIllegalByteArray();
		try {
			encoder.replaceWith(new byte[100]);
			fail();
		} catch (IllegalArgumentException e) {
		}
	}

	protected byte[] getLegalByteArray() {
		return new byte[] { 'a' };
	}

	protected byte[] getIllegalByteArray() {
		return new byte[155];
	}

	/*
	 * Mock subclass of CharsetEncoder For protected method test
	 */
	public static class MockCharsetEncoder extends CharsetEncoder {

		boolean flushed = false;

		public boolean isFlushed() {
			boolean result = flushed;
			flushed = false;
			return result;
		}

		public boolean isLegalReplacement(byte[] ba) {
			if (ba.length == 155) {// specified magic number, return false
				return false;
			}
			return super.isLegalReplacement(ba);
		}

		public MockCharsetEncoder(Charset cs, float aver, float max) {
			super(cs, aver, max);
		}

		public MockCharsetEncoder(Charset cs, float aver, float max,
				byte[] replacement) {
			super(cs, aver, max, replacement);
		}

		protected CoderResult encodeLoop(CharBuffer in, ByteBuffer out) {
			int inPosition = in.position();
			char[] input = new char[in.remaining()];
			in.get(input);
			String result = new String(input);
			if (result.startsWith("malform")) {
				// reset the cursor to the error position
				in.position(inPosition);
				// in.position(0);
				// set the error length
				return CoderResult.malformedForLength("malform".length());
			} else if (result.startsWith("unmap")) {
				// reset the cursor to the error position
				in.position(inPosition);
				// in.position(0);
				// set the error length
				return CoderResult.unmappableForLength("unmap".length());
			} else if (result.startsWith("runtime")) {
				// reset the cursor to the error position
				in.position(0);
				// set the error length
				throw new RuntimeException("runtime");
			}
			int inLeft = input.length;
			int outLeft = out.remaining();
			CoderResult r = CoderResult.UNDERFLOW;
			int length = inLeft;
			if (outLeft < inLeft) {
				r = CoderResult.OVERFLOW;
				length = outLeft;
				in.position(inPosition + outLeft);
			}
			for (int i = 0; i < length; i++) {
				out.put((byte) input[i]);
			}
			return r;
		}

		protected CoderResult implFlush(ByteBuffer out) {
			CoderResult result = super.implFlush(out);
			int length = 0;
			if (out.remaining() >= 5) {
				length = 5;
				result = CoderResult.UNDERFLOW;
				flushed = true;
				// for (int i = 0; i < length; i++) {
				// out.put((byte)'f');
				// }
			} else {
				length = out.remaining();
				result = CoderResult.OVERFLOW;
			}
			return result;
		}

		protected void implReplaceWith(byte[] ba) {
			assertTrue(Arrays.equals(ba, replacement()));
		}

	}

	/*
	 * mock charset for test encoder initialization
	 */
	public static class MockCharset extends Charset {
		protected MockCharset(String arg0, String[] arg1) {
			super(arg0, arg1);
		}

		public boolean contains(Charset arg0) {
			return false;
		}

		public CharsetDecoder newDecoder() {
			return new CharsetDecoderTest.MockCharsetDecoder(this,
					(float) AVER_BYTES, MAX_BYTES);
		}

		public CharsetEncoder newEncoder() {
			return new MockCharsetEncoder(this, (float) AVER_BYTES, MAX_BYTES);
		}
	}

}
