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

import java.io.IOException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * {@code AbstractSelector} is the base implementation class for selectors.
 * It realizes the interruption of selection by {@code begin} and
 * {@code end}. It also holds the cancellation and the deletion of the key
 * set.
 */
public abstract class AbstractSelector extends Selector {
    private final AtomicBoolean isOpen = new AtomicBoolean(true);

    private SelectorProvider provider = null;

    /*
     * Set of cancelled keys.
     */
    private Set<SelectionKey> cancelledKeysSet = new HashSet<SelectionKey>();

    /**
     * Constructs a new {@code AbstractSelector}.
     *
     * @param selectorProvider
     *            the selector provider that creates this selector.
     */
    protected AbstractSelector(SelectorProvider selectorProvider) {
        provider = selectorProvider;
    }

    /**
     * Closes this selector. This method does nothing if this selector is
     * already closed. The actual closing must be implemented by subclasses in
     * {@code implCloseSelector()}.
     *
     * @throws IOException
     *             if an I/O error occurs.
     */
    @Override
    public final void close() throws IOException {
        if (isOpen.getAndSet(false)) {
            implCloseSelector();
        }
    }

    /**
     * Implements the closing of this channel.
     *
     * @throws IOException
     *             if an I/O error occurs.
     */
    protected abstract void implCloseSelector() throws IOException;

    /**
     * Indicates whether this selector is open.
     *
     * @return {@code true} if this selector is not closed, {@code false}
     *         otherwise.
     */
    @Override
    public final boolean isOpen() {
        return isOpen.get();
    }

    /**
     * Gets this selector's provider.
     *
     * @return the provider of this selector.
     */
    @Override
    public final SelectorProvider provider() {
        return provider;
    }

    /**
     * Returns this channel's set of canceled selection keys.
     *
     * @return the set of canceled selection keys.
     */
    protected final Set<SelectionKey> cancelledKeys() {
        return cancelledKeysSet;
    }

    /**
     * Registers a channel with this selector.
     *
     * @param channel
     *            the channel to be registered.
     * @param operations
     *            the {@link SelectionKey interest set} of {@code channel}.
     * @param attachment
     *            the attachment for the selection key.
     * @return the key related to the channel and this selector.
     */
    protected abstract SelectionKey register(AbstractSelectableChannel channel,
            int operations, Object attachment);

    /**
     * Deletes the key from the channel's key set.
     *
     * @param key
     *            the key.
     */
    protected final void deregister(AbstractSelectionKey key) {
        ((AbstractSelectableChannel) key.channel()).deregister(key);
        key.isValid = false;
    }

    /**
     * Indicates the beginning of a code section that includes an I/O operation
     * that is potentially blocking. After this operation, the application
     * should invoke the corresponding {@code end(boolean)} method.
     */
    protected final void begin() {
        // FIXME: be accommodate before VM actually provides
        // setInterruptAction method
        if (AbstractInterruptibleChannel.setInterruptAction != null) {
            try {
                AbstractInterruptibleChannel.setInterruptAction.invoke(Thread
                        .currentThread(), new Object[] { new Runnable() {
                    public void run() {
                        AbstractSelector.this.wakeup();
                    }
                } });
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    }

    /**
     * Indicates the end of a code section that has been started with
     * {@code begin()} and that includes a potentially blocking I/O operation.
     */
    protected final void end() {
        // FIXME: be accommodate before VM actually provides
        // setInterruptAction method
        if (AbstractInterruptibleChannel.setInterruptAction != null) {
            try {
                AbstractInterruptibleChannel.setInterruptAction.invoke(Thread
                        .currentThread(), new Object[] { null });
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    }

    /*
     * package private method for AbstractSelectionKey.cancel()
     */
    void cancel(SelectionKey key) {
        synchronized (cancelledKeysSet) {
            cancelledKeysSet.add(key);
        }
    }
}
