/* 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.nio.tests.java.nio.channels;

import dalvik.annotation.TestTargetNew;
import dalvik.annotation.TestLevel;
import dalvik.annotation.TestTargetClass;
import dalvik.annotation.TestTargets;
import dalvik.annotation.AndroidOnly;

import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousCloseException;
import java.nio.channels.ClosedByInterruptException;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.Pipe;
import java.nio.channels.SelectionKey;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.channels.Pipe.SinkChannel;
import java.nio.channels.spi.SelectorProvider;

import junit.framework.TestCase;

/**
 * Tests for Pipe.SinkChannel class
 */
@TestTargetClass(
    value = java.nio.channels.Pipe.SinkChannel.class,
    untestedMethods = {
        @TestTargetNew(
            level = TestLevel.SUFFICIENT,
            notes = "AsynchronousCloseException can not easily be tested",
            method = "write",
            args = {java.nio.ByteBuffer[].class}
        ),
        @TestTargetNew(
            level = TestLevel.SUFFICIENT,
            notes = "ClosedByInterruptException can not easily be tested",
            method = "write",
            args = {java.nio.ByteBuffer[].class}
        )
    }
)
public class SinkChannelTest extends TestCase {

    private static final int BUFFER_SIZE = 5;

    private static final String ISO8859_1 = "ISO8859-1";

    private Pipe pipe;

    private Pipe.SinkChannel sink;

    private Pipe.SourceChannel source;

    private ByteBuffer buffer;

    private ByteBuffer positionedBuffer;

    protected void setUp() throws Exception {
        super.setUp();
        pipe = Pipe.open();
        sink = pipe.sink();
        source = pipe.source();
        buffer = ByteBuffer.wrap("bytes".getBytes(ISO8859_1));
        positionedBuffer = ByteBuffer.wrap("12345bytes".getBytes(ISO8859_1));
        positionedBuffer.position(BUFFER_SIZE);
    }

    /**
     * @tests java.nio.channels.Pipe.SinkChannel#validOps()
     */
    @TestTargetNew(
        level = TestLevel.COMPLETE,
        notes = "",
        method = "validOps",
        args = {}
    )
    public void test_validOps() {
        assertEquals(SelectionKey.OP_WRITE, sink.validOps());
    }

    /**
     * @tests java.nio.channels.Pipe.SinkChannel#write(ByteBuffer [])
     */
    @TestTargetNew(
        level = TestLevel.PARTIAL_COMPLETE,
        notes = "",
        method = "write",
        args = {java.nio.ByteBuffer[].class}
    )
    public void test_write_LByteBuffer() throws IOException {
        ByteBuffer[] bufArray = { buffer, positionedBuffer };
        boolean[] sinkBlockingMode = { true, true, false, false };
        boolean[] sourceBlockingMode = { true, false, true, false };
        int oldPosition;
        int currentPosition;
        for (int i = 0; i < sinkBlockingMode.length; ++i) {
            sink.configureBlocking(sinkBlockingMode[i]);
            source.configureBlocking(sourceBlockingMode[i]);
            // if sink and source both are blocking mode, source only needs read
            // once to get what sink write.
            boolean isBlocking = sinkBlockingMode[i] && sourceBlockingMode[i];
            for (ByteBuffer buf : bufArray) {
                buf.mark();
                oldPosition = buf.position();
                sink.write(buf);
                ByteBuffer readBuf = ByteBuffer.allocate(BUFFER_SIZE);
                int totalCount = 0;
                do {
                    int count = source.read(readBuf);
                    if (count > 0) {
                        totalCount += count;
                    }
                } while (totalCount != BUFFER_SIZE && !isBlocking);
                currentPosition = buf.position();
                assertEquals(BUFFER_SIZE, currentPosition - oldPosition);
                assertEquals("bytes", new String(readBuf.array(), ISO8859_1));
                buf.reset();
            }
        }
    }

    /**
     * @tests java.nio.channels.Pipe.SinkChannel#write(ByteBuffer)
     */
    @TestTargetNew(
        level = TestLevel.PARTIAL_COMPLETE,
        notes = "",
        method = "write",
        args = {java.nio.ByteBuffer[].class}
    )
    public void test_write_LByteBuffer_mutliThread() throws IOException,
            InterruptedException {
        final int THREAD_NUM = 20;
        final byte[] strbytes = "bytes".getBytes(ISO8859_1);
        Thread[] thread = new Thread[THREAD_NUM];
        for (int i = 0; i < THREAD_NUM; i++) {
            thread[i] = new Thread() {
                public void run() {
                    try {
                        sink.write(ByteBuffer.wrap(strbytes));
                    } catch (IOException e) {
                        throw new RuntimeException(e);
                    }
                }
            };
        }
        for (int i = 0; i < THREAD_NUM; i++) {
            thread[i].start();
        }
        for (int i = 0; i < THREAD_NUM; i++) {
            thread[i].join();
        }
        ByteBuffer readBuf = ByteBuffer.allocate(THREAD_NUM * BUFFER_SIZE);

        long totalCount = 0;
        do {
            long count = source.read(readBuf);
            if (count < 0) {
                break;
            }
            totalCount += count;
        } while (totalCount != (THREAD_NUM * BUFFER_SIZE));

        StringBuffer buf = new StringBuffer();
        for (int i = 0; i < THREAD_NUM; i++) {
            buf.append("bytes");
        }
        String readString = buf.toString();
        assertEquals(readString, new String(readBuf.array(), ISO8859_1));
    }

    /**
     * @tests java.nio.channels.Pipe.SinkChannel#write(ByteBuffer)
     */
    public void disabled_test_read_LByteBuffer_mutliThread_close() throws Exception {

        ByteBuffer sourceBuf = ByteBuffer.allocate(1000);
        sink.configureBlocking(true);

        new Thread() {
            public void run() {
                try {
                    Thread.currentThread().sleep(500);
                    sink.close();
                } catch (Exception e) {
                    //ignore
                }
            }
        }.start();
        try {
            sink.write(sourceBuf);
            fail("should throw AsynchronousCloseException");
        } catch (AsynchronousCloseException e) {
            // ok
        }
    }

    /**
     * @tests java.nio.channels.Pipe.SinkChannel#write(ByteBuffer)
     */
    public void disabled_test_read_LByteBuffer_mutliThread_interrupt() throws Exception {

        sink.configureBlocking(true);

        Thread thread = new Thread() {
            public void run() {
                try {
                    sink.write(ByteBuffer.allocate(10));
                    fail("should have thrown a ClosedByInterruptException.");
                } catch (ClosedByInterruptException e) {
                    // expected
                    return;
                } catch (IOException e) {
                    fail("should throw a ClosedByInterruptException but " +
                            "threw " + e.getClass() + ": " + e.getMessage());
                }
            }
        };

        thread.start();
        Thread.currentThread().sleep(500);
        thread.interrupt();

    }

    /**
     * @tests java.nio.channels.Pipe.SinkChannel#write(ByteBuffer)
     */
    @TestTargetNew(
        level = TestLevel.PARTIAL_COMPLETE,
        notes = "Verifies NullPointerException.",
        method = "write",
        args = {java.nio.ByteBuffer[].class}
    )
    public void test_write_LByteBuffer_Exception() throws IOException {
        // write null ByteBuffer
        ByteBuffer nullBuf = null;
        try {
            sink.write(nullBuf);
            fail("should throw NullPointerException");
        } catch (NullPointerException e) {
            // expected
        }
    }

    /**
     * @tests java.nio.channels.Pipe.SinkChannel#write(ByteBuffer)
     */
    @TestTargetNew(
        level = TestLevel.PARTIAL_COMPLETE,
        notes = "",
        method = "write",
        args = {java.nio.ByteBuffer[].class}
    )
    @AndroidOnly("seems to run on newer RI versions")
    public void test_write_LByteBuffer_SourceClosed() throws IOException {
        source.close();
        int written = sink.write(buffer);
        assertEquals(BUFFER_SIZE, written);
    }

    /**
     * @tests java.nio.channels.Pipe.SinkChannel#write(ByteBuffer)
     */
    @TestTargetNew(
        level = TestLevel.PARTIAL_COMPLETE,
        notes = "Verifies ClosedChannelException, NullPointerException.",
        method = "write",
        args = {java.nio.ByteBuffer[].class}
    )
    public void test_write_LByteBuffer_SinkClosed() throws IOException {
        sink.close();
        try {
            sink.write(buffer);
            fail("should throw ClosedChannelException");
        } catch (ClosedChannelException e) {
            // expected
        }

        // write null ByteBuffer
        ByteBuffer nullBuf = null;
        try {
            sink.write(nullBuf);
            fail("should throw NullPointerException");
        } catch (ClosedChannelException e) {
            // expected on RI
        } catch (NullPointerException e) {
            // expected on Harmony/Android
        }
    }

    /**
     * @tests java.nio.channels.Pipe.SinkChannel#write(ByteBuffer[])
     */
    @TestTargetNew(
        level = TestLevel.PARTIAL_COMPLETE,
        notes = "",
        method = "write",
        args = {java.nio.ByteBuffer[].class}
    )
    public void test_write_$LByteBuffer() throws IOException {
        ByteBuffer[] bufArray = { buffer, positionedBuffer };
        boolean[] sinkBlockingMode = { true, true, false, false };
        boolean[] sourceBlockingMode = { true, false, true, false };
        for (int i = 0; i < sinkBlockingMode.length; ++i) {
            sink.configureBlocking(sinkBlockingMode[i]);
            source.configureBlocking(sourceBlockingMode[i]);
            buffer.position(0);
            positionedBuffer.position(BUFFER_SIZE);
            sink.write(bufArray);
            // if sink and source both are blocking mode, source only needs read
            // once to get what sink write.
            boolean isBlocking = sinkBlockingMode[i] && sourceBlockingMode[i];
            for (int j = 0; j < bufArray.length; ++j) {
                ByteBuffer readBuf = ByteBuffer.allocate(BUFFER_SIZE);
                int totalCount = 0;
                do {
                    int count = source.read(readBuf);
                    if (count < 0) {
                        break;
                    }
                    totalCount += count;
                } while (totalCount != BUFFER_SIZE && !isBlocking);
                assertEquals("bytes", new String(readBuf.array(), ISO8859_1));
            }
            assertEquals(BUFFER_SIZE, buffer.position());
            assertEquals(10, positionedBuffer.position());
        }
    }

    /**
     * @tests java.nio.channels.Pipe.SinkChannel#write(ByteBuffer[])
     */
    @TestTargetNew(
        level = TestLevel.PARTIAL_COMPLETE,
        notes = "Verifies NullPointerException.",
        method = "write",
        args = {java.nio.ByteBuffer[].class}
    )
    public void test_write_$LByteBuffer_Exception() throws IOException {
        // write null ByteBuffer[]
        ByteBuffer[] nullBufArrayRef = null;
        try {
            sink.write(nullBufArrayRef);
            fail("should throw NullPointerException");
        } catch (NullPointerException e) {
            // expected
        }

        // write ByteBuffer[] contains null element
        ByteBuffer nullBuf = null;
        ByteBuffer[] nullBufArray = { buffer, nullBuf };
        try {
            sink.write(nullBufArray);
            fail("should throw NullPointerException");
        } catch (NullPointerException e) {
            // expected
        }
    }

    /**
     * @tests java.nio.channels.Pipe.SinkChannel#write(ByteBuffer[])
     */
    @TestTargetNew(
        level = TestLevel.PARTIAL_COMPLETE,
        notes = "",
        method = "write",
        args = {java.nio.ByteBuffer[].class}
    )
    @AndroidOnly("seems to run on newer RI versions")
    public void test_write_$LByteBuffer_SourceClosed() throws IOException {
        ByteBuffer[] bufArray = { buffer };
        source.close();
        long written = sink.write(bufArray);
        assertEquals(BUFFER_SIZE, written);
    }

    /**
     * @tests java.nio.channels.Pipe.SinkChannel#write(ByteBuffer[])
     */
    @TestTargetNew(
        level = TestLevel.PARTIAL_COMPLETE,
        notes = "Verifies ClosedChannelException, NullPointerException.",
        method = "write",
        args = {java.nio.ByteBuffer[].class}
    )
    public void test_write_$LByteBuffer_SinkClosed() throws IOException {
        ByteBuffer[] bufArray = { buffer };
        sink.close();
        try {
            sink.write(bufArray);
            fail("should throw ClosedChannelException");
        } catch (ClosedChannelException e) {
            // expected
        }

        ByteBuffer[] nullBufArrayRef = null;
        try {
            sink.write(nullBufArrayRef);
            fail("should throw NullPointerException");
        } catch (NullPointerException e) {
            // expected
        }

        ByteBuffer nullBuf = null;
        ByteBuffer[] nullBufArray = { nullBuf };
        // write ByteBuffer[] contains null element
        try {
            sink.write(nullBufArray);
            fail("should throw ClosedChannelException");
        } catch (ClosedChannelException e) {
            // expected
        }
    }

    /**
     * @tests java.nio.channels.Pipe.SinkChannel#write(ByteBuffer[], int, int)
     */
    @TestTargetNew(
        level = TestLevel.PARTIAL_COMPLETE,
        notes = "",
        method = "write",
        args = {java.nio.ByteBuffer[].class, int.class, int.class}
    )
    public void test_write_$LByteBufferII() throws IOException {
        ByteBuffer[] bufArray = { buffer, positionedBuffer };
        boolean[] sinkBlockingMode = { true, true, false, false };
        boolean[] sourceBlockingMode = { true, false, true, false };
        for (int i = 0; i < sinkBlockingMode.length; ++i) {
            sink.configureBlocking(sinkBlockingMode[i]);
            source.configureBlocking(sourceBlockingMode[i]);
            positionedBuffer.position(BUFFER_SIZE);
            sink.write(bufArray, 1, 1);
            // if sink and source both are blocking mode, source only needs read
            // once to get what sink write.
            boolean isBlocking = sinkBlockingMode[i] && sourceBlockingMode[i];
            ByteBuffer readBuf = ByteBuffer.allocate(BUFFER_SIZE);
            int totalCount = 0;
            do {
                int count = source.read(readBuf);
                if (count < 0) {
                    break;
                }
                totalCount += count;
            } while (totalCount != BUFFER_SIZE && !isBlocking);
            assertEquals("bytes", new String(readBuf.array(), ISO8859_1));
            assertEquals(10, positionedBuffer.position());
        }
    }

    /**
     * @tests java.nio.channels.Pipe.SinkChannel#write(ByteBuffer[], int, int)
     */
    @TestTargetNew(
        level = TestLevel.PARTIAL_COMPLETE,
        notes = "Verifies NullPointerException, IndexOutOfBoundsException.",
        method = "write",
        args = {java.nio.ByteBuffer[].class, int.class, int.class}
    )
    public void test_write_$LByteBufferII_Exception() throws IOException {
        // write null ByteBuffer[]
        ByteBuffer[] nullBufArrayRef = null;
        try {
            sink.write(nullBufArrayRef, 0, 1);
            fail("should throw NullPointerException");
        } catch (NullPointerException e) {
            // expected
        }

        try {
            sink.write(nullBufArrayRef, 0, -1);
            fail("should throw IndexOutOfBoundsException");
        } catch (IndexOutOfBoundsException e) {
            // expected
        }

        // write ByteBuffer[] contains null element
        ByteBuffer nullBuf = null;
        ByteBuffer[] nullBufArray = { nullBuf };
        try {
            sink.write(nullBufArray, 0, 1);
            fail("should throw NullPointerException");
        } catch (NullPointerException e) {
            // expected
        }

        try {
            sink.write(nullBufArray, 0, -1);
            fail("should throw IndexOutOfBoundsException");
        } catch (IndexOutOfBoundsException e) {
            // expected
        }

        ByteBuffer[] bufArray = { buffer, nullBuf };
        try {
            sink.write(bufArray, 0, -1);
            fail("should throw IndexOutOfBoundsException");
        } catch (IndexOutOfBoundsException e) {
            // expected
        }

        try {
            sink.write(bufArray, -1, 0);
            fail("should throw IndexOutOfBoundsException");
        } catch (IndexOutOfBoundsException e) {
            // expected
        }

        try {
            sink.write(bufArray, -1, 1);
            fail("should throw IndexOutOfBoundsException");
        } catch (IndexOutOfBoundsException e) {
            // expected
        }

        try {
            sink.write(bufArray, 0, 3);
            fail("should throw IndexOutOfBoundsException");
        } catch (IndexOutOfBoundsException e) {
            // expected
        }

        try {
            sink.write(bufArray, 0, 2);
            fail("should throw NullPointerException");
        } catch (NullPointerException e) {
            // expected
        }
    }

    /**
     * @tests java.nio.channels.Pipe.SinkChannel#write(ByteBuffer[], int, int)
     */
    @TestTargetNew(
        level = TestLevel.PARTIAL_COMPLETE,
        notes = "",
        method = "write",
        args = {java.nio.ByteBuffer[].class, int.class, int.class}
    )
    @AndroidOnly("seems to run on newer RI versions")
    public void test_write_$LByteBufferII_SourceClosed() throws IOException {
        ByteBuffer[] bufArray = { buffer };
        source.close();
        long written = sink.write(bufArray, 0, 1);
        assertEquals(BUFFER_SIZE, written);
    }

    /**
     * @tests java.nio.channels.Pipe.SinkChannel#write(ByteBuffer[], int, int)
     */
    @TestTargetNew(
        level = TestLevel.PARTIAL_COMPLETE,
        notes = "Verifies ClosedChannelException, NullPointerException, IndexOutOfBoundsException.",
        method = "write",
        args = {java.nio.ByteBuffer[].class, int.class, int.class}
    )
    public void test_write_$LByteBufferII_SinkClosed() throws IOException {
        ByteBuffer[] bufArray = { buffer };
        sink.close();
        try {
            sink.write(bufArray, 0, 1);
            fail("should throw ClosedChannelException");
        } catch (ClosedChannelException e) {
            // expected
        }

        // write null ByteBuffer[]
        ByteBuffer[] nullBufArrayRef = null;
        try {
            sink.write(nullBufArrayRef, 0, 1);
            fail("should throw NullPointerException");
        } catch (NullPointerException e) {
            // expected
        }
        // illegal array index
        try {
            sink.write(nullBufArrayRef, 0, -1);
            fail("should throw IndexOutOfBoundsException");
        } catch (IndexOutOfBoundsException e) {
            // expected
        }

        // write ByteBuffer[] contains null element
        ByteBuffer nullBuf = null;
        ByteBuffer[] nullBufArray = { nullBuf };
        try {
            sink.write(nullBufArray, 0, 1);
            fail("should throw ClosedChannelException");
        } catch (ClosedChannelException e) {
            // expected
        }
        // illegal array index
        try {
            sink.write(nullBufArray, 0, -1);
            fail("should throw IndexOutOfBoundsException");
        } catch (IndexOutOfBoundsException e) {
            // expected
        }

        ByteBuffer[] bufArray2 = { buffer, nullBuf };
        // illegal array index
        try {
            sink.write(bufArray2, 0, -1);
            fail("should throw IndexOutOfBoundsException");
        } catch (IndexOutOfBoundsException e) {
            // expected
        }

        try {
            sink.write(bufArray2, -1, 0);
            fail("should throw IndexOutOfBoundsException");
        } catch (IndexOutOfBoundsException e) {
            // expected
        }

        try {
            sink.write(bufArray2, -1, 1);
            fail("should throw IndexOutOfBoundsException");
        } catch (IndexOutOfBoundsException e) {
            // expected
        }

        try {
            sink.write(bufArray2, 0, 3);
            fail("should throw IndexOutOfBoundsException");
        } catch (IndexOutOfBoundsException e) {
            // expected
        }

        try {
            sink.write(bufArray2, 0, 2);
            fail("should throw ClosedChannelException");
        } catch (ClosedChannelException e) {
            // expected
        }
    }

    /**
     * @tests java.nio.channels.Pipe.SinkChannel#close()
     * @tests {@link java.nio.channels.Pipe.SinkChannel#isOpen()}
     */
    @TestTargets({
        @TestTargetNew(
            level = TestLevel.PARTIAL_COMPLETE,
            notes = "",
            method = "close",
            args = {}
        ),@TestTargetNew(
            level = TestLevel.PARTIAL_COMPLETE,
            notes = "",
            method = "isOpen",
            args = {}
        )
    })
    public void test_close() throws IOException {
        assertTrue(sink.isOpen());
        sink.close();
        assertFalse(sink.isOpen());
    }

    @TestTargetNew(
        level = TestLevel.COMPLETE,
        notes = "",
        method = "SinkChannel",
        args = {java.nio.channels.spi.SelectorProvider.class}
    )
    public void testConstructor() throws IOException {
        SinkChannel channel =
                SelectorProvider.provider().openPipe().sink();
        assertNotNull(channel);
        assertSame(SelectorProvider.provider(),channel.provider());
        channel = Pipe.open().sink();
        assertNotNull(channel);
        assertSame(SelectorProvider.provider(),channel.provider());
    }

    @TestTargetNew(
        level = TestLevel.PARTIAL_COMPLETE,
        notes = "Verifies that NullPointerException is thrown if write" +
                "method is called for closed channel.",
        method = "write",
        args = {java.nio.ByteBuffer.class}
    )
    public void test_socketChannel_closed() throws Exception {
        ServerSocketChannel ssc = ServerSocketChannel.open();
        ssc.socket().bind(new InetSocketAddress(InetAddress.getLocalHost(),49999));
        SocketChannel sc = SocketChannel.open();
        ByteBuffer buf = null;
        try{
            sc.write(buf);
            fail("should throw NPE");
        }catch (NullPointerException e){
            // expected
        }
        sc.connect(new InetSocketAddress(InetAddress.getLocalHost(),49999));
        SocketChannel sock = ssc.accept();
        ssc.close();
        sc.close();
        try{
            sc.write(buf);
            fail("should throw NPE");
        }catch (NullPointerException e){
            // expected
        }
        sock.close();
    }

    @TestTargetNew(
        level = TestLevel.PARTIAL_COMPLETE,
        notes = "Verifies NullPointerException.",
        method = "write",
        args = {java.nio.ByteBuffer[].class, int.class, int.class}
    )
    public void test_socketChannel_empty() throws Exception {
        ServerSocketChannel ssc = ServerSocketChannel.open();
        ssc.socket().bind(new InetSocketAddress(InetAddress.getLocalHost(),49999));
        SocketChannel sc = SocketChannel.open();
        sc.connect(new InetSocketAddress(InetAddress.getLocalHost(),49999));
        SocketChannel sock = ssc.accept();
        ByteBuffer[] buf = {ByteBuffer.allocate(10),null};
        try{
            sc.write(buf,0,2);
            fail("should throw NPE");
        }catch (NullPointerException e){
            // expected
        }
        ssc.close();
        sc.close();
        ByteBuffer target = ByteBuffer.allocate(10);
        assertEquals(-1, sock.read(target));
    }
}
