/*
 *  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.lang.reflect.Method;
import java.nio.channels.AsynchronousCloseException;
import java.nio.channels.Channel;
import java.nio.channels.ClosedByInterruptException;
import java.nio.channels.InterruptibleChannel;
import java.security.AccessController;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;

/**
 * {@code AbstractInterruptibleChannel} is the root class for interruptible
 * channels.
 * <p>
 * The basic usage pattern for an interruptible channel is to invoke
 * {@code begin()} before any I/O operation that potentially blocks
 * indefinitely, then {@code end(boolean)} after completing the operation. The
 * argument to the {@code end} method should indicate if the I/O operation has
 * actually completed so that any change may be visible to the invoker.
*/
public abstract class AbstractInterruptibleChannel implements Channel,
        InterruptibleChannel {

    static Method setInterruptAction = null;

    static {
        try {
            setInterruptAction = AccessController
                    .doPrivileged(new PrivilegedExceptionAction<Method>() {
                        public Method run() throws Exception {
                            return Thread.class.getDeclaredMethod(
                                    "setInterruptAction",
                                    new Class[] { Runnable.class });

                        }
                    });
            setInterruptAction.setAccessible(true);
        } catch (PrivilegedActionException e) {
            // FIXME: be accommodate before VM actually provides
            // setInterruptAction method
            // throw new Error(e);
        }
    }

    private volatile boolean closed = false;

    volatile boolean interrupted = false;

    /**
     * Default constructor.
     */
    protected AbstractInterruptibleChannel() {
        super();
    }

    /**
     * Indicates whether this channel is open.
     *
     * @return {@code true} if this channel is open, {@code false} if it is
     *         closed.
     * @see java.nio.channels.Channel#isOpen()
     */
    public synchronized final boolean isOpen() {
        return !closed;
    }

    /**
     * Closes an open channel. If the channel is already closed then this method
     * has no effect, otherwise it closes the receiver via the
     * {@code implCloseChannel} method.
     * <p>
     * If an attempt is made to perform an operation on a closed channel then a
     * {@link java.nio.channels.ClosedChannelException} is thrown.
     * <p>
     * If multiple threads attempt to simultaneously close a channel, then only
     * one thread will run the closure code and the others will be blocked until
     * the first one completes.
     *
     * @throws IOException
     *             if a problem occurs while closing this channel.
     * @see java.nio.channels.Channel#close()
     */
    public final void close() throws IOException {
        if (!closed) {
            synchronized (this) {
                if (!closed) {
                    closed = true;
                    implCloseChannel();
                }
            }
        }
    }

    /**
     * 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 (setInterruptAction != null) {
            try {
                setInterruptAction.invoke(Thread.currentThread(),
                        new Object[] { new Runnable() {
                            public void run() {
                                try {
                                    interrupted = true;
                                    AbstractInterruptibleChannel.this.close();
                                } catch (IOException e) {
                                    // ignore
                                }
                            }
                        } });
            } 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.
     *
     * @param success
     *            pass {@code true} if the blocking operation has succeeded and
     *            has had a noticeable effect; {@code false} otherwise.
     * @throws AsynchronousCloseException
     *             if this channel is closed by another thread while this method
     *             is executing.
     * @throws ClosedByInterruptException
     *             if another thread interrupts the calling thread while this
     *             method is executing.
     */
    protected final void end(boolean success) throws AsynchronousCloseException {
        // FIXME: be accommodate before VM actually provides
        // setInterruptAction method
        if (setInterruptAction != null) {
            try {
                setInterruptAction.invoke(Thread.currentThread(),
                        new Object[] { null });
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
            if (interrupted) {
                interrupted = false;
                throw new ClosedByInterruptException();
            }
        }
        if (!success && closed) {
            throw new AsynchronousCloseException();
        }
    }

    /**
     * Implements the channel closing behavior.
     * <p>
     * Closes the channel with a guarantee that the channel is not currently
     * closed through another invocation of {@code close()} and that the method
     * is thread-safe.
     * <p>
     * Any outstanding threads blocked on I/O operations on this channel must be
     * released with either a normal return code, or by throwing an
     * {@code AsynchronousCloseException}.
     *
     * @throws IOException
     *             if a problem occurs while closing the channel.
     */
    protected abstract void implCloseChannel() throws IOException;
}
