/*
 * Copyright (c) 2018, 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 6350055
 * @run testng AtomicUpdates
 * @summary Unit test for SelectionKey interestOpsOr and interestOpsAnd
 */

import java.io.Closeable;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.nio.channels.CancelledKeyException;
import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import org.testng.annotations.Test;

import static java.nio.channels.SelectionKey.OP_READ;
import static java.nio.channels.SelectionKey.OP_WRITE;
import static java.nio.channels.SelectionKey.OP_CONNECT;
import static java.nio.channels.SelectionKey.OP_ACCEPT;
import static org.testng.Assert.*;

@Test
public class AtomicUpdates {

    private SelectionKey keyFor(SocketChannel sc) {
        return new SelectionKey() {
            private int ops;
            private boolean invalid;
            private void ensureValid() {
                if (!isValid())
                    throw new CancelledKeyException();
            }
            @Override
            public SelectableChannel channel() {
                return sc;
            }
            @Override
            public Selector selector() {
                throw new RuntimeException();
            }
            @Override
            public boolean isValid() {
                return !invalid;
            }
            @Override
            public void cancel() {
                invalid = true;
            }
            @Override
            public int interestOps() {
                ensureValid();
                return ops;
            }
            @Override
            public SelectionKey interestOps(int ops) {
                ensureValid();
                if ((ops & ~channel().validOps()) != 0)
                    throw new IllegalArgumentException();
                this.ops = ops;
                return this;
            }
            @Override
            public int readyOps() {
                ensureValid();
                return 0;
            }
        };
    }

    private void test(SelectionKey key) {
        assertTrue(key.channel() instanceof SocketChannel);
        key.interestOps(0);

        // 0 -> 0
        int previous = key.interestOpsOr(0);
        assertTrue(previous == 0);
        assertTrue(key.interestOps() == 0);

        // 0 -> OP_CONNECT
        previous = key.interestOpsOr(OP_CONNECT);
        assertTrue(previous == 0);
        assertTrue(key.interestOps() == OP_CONNECT);

        // OP_CONNECT -> OP_CONNECT
        previous = key.interestOpsOr(0);
        assertTrue(previous == OP_CONNECT);
        assertTrue(key.interestOps() == OP_CONNECT);

        // OP_CONNECT -> OP_CONNECT | OP_READ | OP_WRITE
        previous = key.interestOpsOr(OP_READ | OP_WRITE);
        assertTrue(previous == OP_CONNECT);
        assertTrue(key.interestOps() == (OP_CONNECT | OP_READ | OP_WRITE));

        // OP_CONNECT | OP_READ | OP_WRITE -> OP_CONNECT
        previous = key.interestOpsAnd(~(OP_READ | OP_WRITE));
        assertTrue(previous == (OP_CONNECT | OP_READ | OP_WRITE));
        assertTrue(key.interestOps() == OP_CONNECT);

        // OP_CONNECT -> 0
        previous = key.interestOpsAnd(~OP_CONNECT);
        assertTrue(previous == OP_CONNECT);
        assertTrue(key.interestOps() == 0);

        // OP_READ | OP_WRITE -> OP_READ | OP_WRITE
        key.interestOps(OP_READ | OP_WRITE);
        previous = key.interestOpsAnd(~OP_ACCEPT);
        assertTrue(previous == (OP_READ | OP_WRITE));
        assertTrue(key.interestOps() == (OP_READ | OP_WRITE));

        // OP_READ | OP_WRITE -> 0
        previous = key.interestOpsAnd(0);
        assertTrue(previous == (OP_READ | OP_WRITE));
        assertTrue(key.interestOps() == 0);

        // 0 -> 0
        previous = key.interestOpsAnd(0);
        assertTrue(previous == 0);
        assertTrue(key.interestOps() == 0);

        try {
            key.interestOpsOr(OP_ACCEPT);
            fail("IllegalArgumentException expected");
        } catch (IllegalArgumentException expected) { }

        key.cancel();
        try {
            key.interestOpsOr(OP_READ);
            fail("CancelledKeyException expected");
        } catch (CancelledKeyException expected) { }
        try {
            key.interestOpsAnd(~OP_READ);
            fail("CancelledKeyException expected");
        } catch (CancelledKeyException expected) { }
    }

    /**
     * Test default implementation of interestOpsOr/interestOpsAnd
     */
    public void testDefaultImplementation() throws Exception {
        try (SocketChannel sc = SocketChannel.open()) {
            SelectionKey key = keyFor(sc);
            test(key);
        }
    }

    /**
     * Test the default provider implementation of SelectionKey.
     */
    public void testNioImplementation() throws Exception {
        try (SocketChannel sc = SocketChannel.open();
             Selector sel = Selector.open()) {
            sc.configureBlocking(false);
            SelectionKey key = sc.register(sel, 0);
            test(key);
        }
    }
}