/*
 * Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

/* @test
 * @bug 7200742
 * @summary Test that Selector doesn't spin when changing interest ops
 */

import java.net.*;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import static java.nio.channels.SelectionKey.*;
import java.io.IOException;

public class ChangingInterests {

    static int OPS[] = { 0, OP_WRITE, OP_READ, (OP_WRITE|OP_READ) };

    static String toOpsString(int ops) {
        String s = "";
        if ((ops & OP_READ) > 0)
            s += "POLLIN";
        if ((ops & OP_WRITE) > 0) {
            if (s.length() > 0)
                s += "|";
            s += "POLLOUT";
        }
        if (s.length() == 0)
            s = "0";
        return "(" + s + ")";
    }

    static void write1(SocketChannel peer) throws IOException {
        peer.write(ByteBuffer.wrap(new byte[1]));
        // give time for other end to be readable
        try {
            Thread.sleep(50);
        } catch (InterruptedException ignore) { }
    }

    static void drain(SocketChannel sc) throws IOException {
        ByteBuffer buf = ByteBuffer.allocate(100);
        int n;
        while ((n = sc.read(buf)) > 0) {
            buf.rewind();
        }
    }

    /**
     * Changes the given key's interest set from one set to another and then
     * checks the selected key set and the key's channel.
     */
    static void testChange(SelectionKey key, int from, int to) throws IOException {
        Selector sel = key.selector();
        assertTrue(sel.keys().size() == 1, "Only one channel should be registered");

        // ensure that channel is registered with the "from" interest set
        key.interestOps(from);
        sel.selectNow();
        sel.selectedKeys().clear();

        // change to the "to" interest set
        key.interestOps(to);
        System.out.println("select...");
        int selected = sel.selectNow();
        System.out.println("" + selected + " channel(s) selected");

        int expected = (to == 0) ? 0 : 1;
        assertTrue(selected == expected, "Expected " + expected);

        // check selected keys
        for (SelectionKey k: sel.selectedKeys()) {
            assertTrue(k == key, "Unexpected key selected");

            boolean readable = k.isReadable();
            boolean writable = k.isWritable();

            System.out.println("key readable: " + readable);
            System.out.println("key writable: " + writable);

            if ((to & OP_READ) == 0) {
                assertTrue(!readable, "Not expected to be readable");
            } else {
                assertTrue(readable, "Expected to be readable");
            }

            if ((to & OP_WRITE) == 0) {
                assertTrue(!writable, "Not expected to be writable");
            } else {
                assertTrue(writable, "Expected to be writable");
            }

            sel.selectedKeys().clear();
        }
    }

    /**
     * Tests that given Selector's select method blocks.
     */
    static void testForSpin(Selector sel) throws IOException {
        System.out.println("Test for spin...");
        long start = System.currentTimeMillis();
        int count = 3;
        while (count-- > 0) {
            int selected = sel.select(1000);
            System.out.println("" + selected + " channel(s) selected");
            assertTrue(selected == 0, "Channel should not be selected");
        }
        long dur = System.currentTimeMillis() - start;
        assertTrue(dur > 1000, "select was too short");
    }

    public static void main(String[] args) throws IOException {
        InetAddress lh = InetAddress.getLocalHost();

        // create loopback connection
        ServerSocketChannel ssc =
            ServerSocketChannel.open().bind(new InetSocketAddress(0));

        final SocketChannel sc = SocketChannel.open();
        sc.setOption(StandardSocketOptions.TCP_NODELAY, true);
        sc.connect(new InetSocketAddress(lh, ssc.socket().getLocalPort()));
        SocketChannel peer = ssc.accept();
        peer.setOption(StandardSocketOptions.TCP_NODELAY, true);

        sc.configureBlocking(false);

        // ensure that channel "sc" is readable
        write1(peer);

        try (Selector sel = Selector.open()) {
            SelectionKey key = sc.register(sel, 0);
            sel.selectNow();

            // test all transitions
            for (int from: OPS) {
                for (int to: OPS) {

                    System.out.println(toOpsString(from) + " -> " + toOpsString(to));

                    testChange(key, from, to);

                    // if the interst ops is now 0 then Selector should not spin
                    if (to == 0)
                        testForSpin(sel);

                    // if interest ops is now OP_READ then make non-readable
                    // and test that Selector does not spin.
                    if (to == OP_READ) {
                        System.out.println("Drain channel...");
                        drain(sc);
                        testForSpin(sel);
                        System.out.println("Make channel readable again");
                        write1(peer);
                    }

                    System.out.println();
                }
            }

        } finally {
            sc.close();
            peer.close();
            ssc.close();
        }
    }

    static void assertTrue(boolean v, String msg) {
        if (!v) throw new RuntimeException(msg);
    }

}
