// 
// Decompiled by Procyon v0.6.0
// 

package io.netty.channel.nio;

import java.nio.channels.CancelledKeyException;
import io.netty.channel.IoEvent;
import io.netty.util.concurrent.GenericFutureListener;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import java.util.concurrent.TimeUnit;
import io.netty.channel.ConnectTimeoutException;
import java.nio.channels.ConnectionPendingException;
import io.netty.util.internal.logging.InternalLoggerFactory;
import java.nio.channels.ClosedChannelException;
import io.netty.util.ReferenceCounted;
import io.netty.buffer.ByteBufAllocator;
import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.Unpooled;
import io.netty.util.ReferenceCountUtil;
import io.netty.buffer.ByteBuf;
import io.netty.channel.IoHandle;
import io.netty.channel.IoEventLoopGroup;
import io.netty.channel.IoEventLoop;
import io.netty.channel.EventLoop;
import java.nio.channels.SelectionKey;
import io.netty.channel.IoOps;
import io.netty.channel.ChannelException;
import java.io.IOException;
import io.netty.util.internal.ObjectUtil;
import io.netty.channel.Channel;
import java.net.SocketAddress;
import io.netty.util.concurrent.Future;
import io.netty.channel.ChannelPromise;
import io.netty.channel.IoRegistration;
import java.nio.channels.SelectableChannel;
import io.netty.util.internal.logging.InternalLogger;
import io.netty.channel.AbstractChannel;

public abstract class AbstractNioChannel extends AbstractChannel
{
    private static final InternalLogger logger;
    private final SelectableChannel ch;
    protected final int readInterestOp;
    protected final NioIoOps readOps;
    volatile IoRegistration registration;
    boolean readPending;
    private final Runnable clearReadPendingRunnable;
    private ChannelPromise connectPromise;
    private Future<?> connectTimeoutFuture;
    private SocketAddress requestedRemoteAddress;
    
    protected AbstractNioChannel(final Channel parent, final SelectableChannel ch, final int readOps) {
        this(parent, ch, NioIoOps.valueOf(readOps));
    }
    
    protected AbstractNioChannel(final Channel parent, final SelectableChannel ch, final NioIoOps readOps) {
        super(parent);
        this.clearReadPendingRunnable = new Runnable() {
            @Override
            public void run() {
                AbstractNioChannel.this.clearReadPending0();
            }
        };
        this.ch = ch;
        this.readInterestOp = ObjectUtil.checkNotNull(readOps, "readOps").value;
        this.readOps = readOps;
        try {
            ch.configureBlocking(false);
        }
        catch (final IOException e) {
            try {
                ch.close();
            }
            catch (final IOException e2) {
                AbstractNioChannel.logger.warn("Failed to close a partially initialized socket.", e2);
            }
            throw new ChannelException("Failed to enter non-blocking mode.", e);
        }
    }
    
    protected void addAndSubmit(final NioIoOps addOps) {
        final int interestOps = this.selectionKey().interestOps();
        if (!addOps.isIncludedIn(interestOps)) {
            try {
                this.registration().submit(NioIoOps.valueOf(interestOps).with(addOps));
            }
            catch (final Exception e) {
                throw new ChannelException(e);
            }
        }
    }
    
    protected void removeAndSubmit(final NioIoOps removeOps) {
        final int interestOps = this.selectionKey().interestOps();
        if (removeOps.isIncludedIn(interestOps)) {
            try {
                this.registration().submit(NioIoOps.valueOf(interestOps).without(removeOps));
            }
            catch (final Exception e) {
                throw new ChannelException(e);
            }
        }
    }
    
    @Override
    public boolean isOpen() {
        return this.ch.isOpen();
    }
    
    @Override
    public NioUnsafe unsafe() {
        return (NioUnsafe)super.unsafe();
    }
    
    protected SelectableChannel javaChannel() {
        return this.ch;
    }
    
    @Deprecated
    protected SelectionKey selectionKey() {
        return this.registration().attachment();
    }
    
    protected IoRegistration registration() {
        assert this.registration != null;
        return this.registration;
    }
    
    @Deprecated
    protected boolean isReadPending() {
        return this.readPending;
    }
    
    @Deprecated
    protected void setReadPending(final boolean readPending) {
        if (this.isRegistered()) {
            final EventLoop eventLoop = this.eventLoop();
            if (eventLoop.inEventLoop()) {
                this.setReadPending0(readPending);
            }
            else {
                eventLoop.execute(new Runnable() {
                    @Override
                    public void run() {
                        AbstractNioChannel.this.setReadPending0(readPending);
                    }
                });
            }
        }
        else {
            this.readPending = readPending;
        }
    }
    
    protected final void clearReadPending() {
        if (this.isRegistered()) {
            final EventLoop eventLoop = this.eventLoop();
            if (eventLoop.inEventLoop()) {
                this.clearReadPending0();
            }
            else {
                eventLoop.execute(this.clearReadPendingRunnable);
            }
        }
        else {
            this.readPending = false;
        }
    }
    
    private void setReadPending0(final boolean readPending) {
        if (!(this.readPending = readPending)) {
            ((AbstractNioUnsafe)this.unsafe()).removeReadOp();
        }
    }
    
    private void clearReadPending0() {
        this.readPending = false;
        ((AbstractNioUnsafe)this.unsafe()).removeReadOp();
    }
    
    @Override
    protected boolean isCompatible(final EventLoop loop) {
        return loop instanceof IoEventLoop && ((IoEventLoopGroup)loop).isCompatible(AbstractNioUnsafe.class);
    }
    
    @Override
    protected void doRegister(final ChannelPromise promise) {
        assert this.registration == null;
        ((IoEventLoop)this.eventLoop()).register((IoHandle)this.unsafe()).addListener(f -> {
            if (f.isSuccess()) {
                this.registration = f.getNow();
                promise.setSuccess();
            }
            else {
                promise.setFailure(f.cause());
            }
        });
    }
    
    @Override
    protected void doDeregister() throws Exception {
        final IoRegistration registration = this.registration;
        if (registration != null) {
            this.registration = null;
            registration.cancel();
        }
    }
    
    @Override
    protected void doBeginRead() throws Exception {
        final IoRegistration registration = this.registration;
        if (registration == null || !registration.isValid()) {
            return;
        }
        this.readPending = true;
        this.addAndSubmit(this.readOps);
    }
    
    protected abstract boolean doConnect(final SocketAddress p0, final SocketAddress p1) throws Exception;
    
    protected abstract void doFinishConnect() throws Exception;
    
    protected final ByteBuf newDirectBuffer(final ByteBuf buf) {
        final int readableBytes = buf.readableBytes();
        if (readableBytes == 0) {
            ReferenceCountUtil.safeRelease(buf);
            return Unpooled.EMPTY_BUFFER;
        }
        final ByteBufAllocator alloc = this.alloc();
        if (alloc.isDirectBufferPooled()) {
            final ByteBuf directBuf = alloc.directBuffer(readableBytes);
            directBuf.writeBytes(buf, buf.readerIndex(), readableBytes);
            ReferenceCountUtil.safeRelease(buf);
            return directBuf;
        }
        final ByteBuf directBuf = ByteBufUtil.threadLocalDirectBuffer();
        if (directBuf != null) {
            directBuf.writeBytes(buf, buf.readerIndex(), readableBytes);
            ReferenceCountUtil.safeRelease(buf);
            return directBuf;
        }
        return buf;
    }
    
    protected final ByteBuf newDirectBuffer(final ReferenceCounted holder, final ByteBuf buf) {
        final int readableBytes = buf.readableBytes();
        if (readableBytes == 0) {
            ReferenceCountUtil.safeRelease(holder);
            return Unpooled.EMPTY_BUFFER;
        }
        final ByteBufAllocator alloc = this.alloc();
        if (alloc.isDirectBufferPooled()) {
            final ByteBuf directBuf = alloc.directBuffer(readableBytes);
            directBuf.writeBytes(buf, buf.readerIndex(), readableBytes);
            ReferenceCountUtil.safeRelease(holder);
            return directBuf;
        }
        final ByteBuf directBuf = ByteBufUtil.threadLocalDirectBuffer();
        if (directBuf != null) {
            directBuf.writeBytes(buf, buf.readerIndex(), readableBytes);
            ReferenceCountUtil.safeRelease(holder);
            return directBuf;
        }
        if (holder != buf) {
            buf.retain();
            ReferenceCountUtil.safeRelease(holder);
        }
        return buf;
    }
    
    @Override
    protected void doClose() throws Exception {
        final ChannelPromise promise = this.connectPromise;
        if (promise != null) {
            promise.tryFailure(new ClosedChannelException());
            this.connectPromise = null;
        }
        final Future<?> future = this.connectTimeoutFuture;
        if (future != null) {
            future.cancel(false);
            this.connectTimeoutFuture = null;
        }
    }
    
    static {
        logger = InternalLoggerFactory.getInstance(AbstractNioChannel.class);
    }
    
    protected abstract class AbstractNioUnsafe extends AbstractUnsafe implements NioUnsafe, NioIoHandle
    {
        @Override
        public void close() {
            this.close(this.voidPromise());
        }
        
        @Override
        public SelectableChannel selectableChannel() {
            return this.ch();
        }
        
        Channel channel() {
            return AbstractNioChannel.this;
        }
        
        protected final void removeReadOp() {
            final IoRegistration registration = AbstractNioChannel.this.registration();
            if (!registration.isValid()) {
                return;
            }
            AbstractNioChannel.this.removeAndSubmit(AbstractNioChannel.this.readOps);
        }
        
        @Override
        public final SelectableChannel ch() {
            return AbstractNioChannel.this.javaChannel();
        }
        
        @Override
        public final void connect(final SocketAddress remoteAddress, final SocketAddress localAddress, final ChannelPromise promise) {
            if (promise.isDone() || !this.ensureOpen(promise)) {
                return;
            }
            try {
                if (AbstractNioChannel.this.connectPromise != null) {
                    throw new ConnectionPendingException();
                }
                final boolean wasActive = AbstractNioChannel.this.isActive();
                if (AbstractNioChannel.this.doConnect(remoteAddress, localAddress)) {
                    this.fulfillConnectPromise(promise, wasActive);
                }
                else {
                    AbstractNioChannel.this.connectPromise = promise;
                    AbstractNioChannel.this.requestedRemoteAddress = remoteAddress;
                    final int connectTimeoutMillis = AbstractNioChannel.this.config().getConnectTimeoutMillis();
                    if (connectTimeoutMillis > 0) {
                        AbstractNioChannel.this.connectTimeoutFuture = AbstractNioChannel.this.eventLoop().schedule((Runnable)new Runnable() {
                            @Override
                            public void run() {
                                final ChannelPromise connectPromise = AbstractNioChannel.this.connectPromise;
                                if (connectPromise != null && !connectPromise.isDone() && connectPromise.tryFailure(new ConnectTimeoutException("connection timed out after " + connectTimeoutMillis + " ms: " + remoteAddress))) {
                                    AbstractNioUnsafe.this.close(AbstractNioUnsafe.this.voidPromise());
                                }
                            }
                        }, (long)connectTimeoutMillis, TimeUnit.MILLISECONDS);
                    }
                    promise.addListener((GenericFutureListener<? extends Future<? super Void>>)new ChannelFutureListener() {
                        @Override
                        public void operationComplete(final ChannelFuture future) {
                            if (future.isCancelled()) {
                                if (AbstractNioChannel.this.connectTimeoutFuture != null) {
                                    AbstractNioChannel.this.connectTimeoutFuture.cancel(false);
                                }
                                AbstractNioChannel.this.connectPromise = null;
                                AbstractNioUnsafe.this.close(AbstractNioUnsafe.this.voidPromise());
                            }
                        }
                    });
                }
            }
            catch (final Throwable t) {
                promise.tryFailure(this.annotateConnectException(t, remoteAddress));
                this.closeIfClosed();
            }
        }
        
        private void fulfillConnectPromise(final ChannelPromise promise, final boolean wasActive) {
            if (promise == null) {
                return;
            }
            final boolean active = AbstractNioChannel.this.isActive();
            final boolean promiseSet = promise.trySuccess();
            if (!wasActive && active) {
                AbstractNioChannel.this.pipeline().fireChannelActive();
            }
            if (!promiseSet) {
                this.close(this.voidPromise());
            }
        }
        
        private void fulfillConnectPromise(final ChannelPromise promise, final Throwable cause) {
            if (promise == null) {
                return;
            }
            promise.tryFailure(cause);
            this.closeIfClosed();
        }
        
        @Override
        public final void finishConnect() {
            assert AbstractNioChannel.this.eventLoop().inEventLoop();
            try {
                final boolean wasActive = AbstractNioChannel.this.isActive();
                AbstractNioChannel.this.doFinishConnect();
                this.fulfillConnectPromise(AbstractNioChannel.this.connectPromise, wasActive);
            }
            catch (final Throwable t) {
                this.fulfillConnectPromise(AbstractNioChannel.this.connectPromise, this.annotateConnectException(t, AbstractNioChannel.this.requestedRemoteAddress));
            }
            finally {
                if (AbstractNioChannel.this.connectTimeoutFuture != null) {
                    AbstractNioChannel.this.connectTimeoutFuture.cancel(false);
                }
                AbstractNioChannel.this.connectPromise = null;
            }
        }
        
        @Override
        protected final void flush0() {
            if (!this.isFlushPending()) {
                super.flush0();
            }
        }
        
        @Override
        public final void forceFlush() {
            super.flush0();
        }
        
        private boolean isFlushPending() {
            final IoRegistration registration = AbstractNioChannel.this.registration();
            return registration.isValid() && NioIoOps.WRITE.isIncludedIn(registration.attachment().interestOps());
        }
        
        @Override
        public void handle(final IoRegistration registration, final IoEvent event) {
            try {
                final NioIoEvent nioEvent = (NioIoEvent)event;
                final NioIoOps nioReadyOps = nioEvent.ops();
                if (nioReadyOps.contains(NioIoOps.CONNECT)) {
                    AbstractNioChannel.this.removeAndSubmit(NioIoOps.CONNECT);
                    AbstractNioChannel.this.unsafe().finishConnect();
                }
                if (nioReadyOps.contains(NioIoOps.WRITE)) {
                    this.forceFlush();
                }
                if (nioReadyOps.contains(NioIoOps.READ_AND_ACCEPT) || nioReadyOps.equals(NioIoOps.NONE)) {
                    this.read();
                }
            }
            catch (final CancelledKeyException ignored) {
                this.close(this.voidPromise());
            }
        }
    }
    
    public interface NioUnsafe extends Channel.Unsafe
    {
        SelectableChannel ch();
        
        void finishConnect();
        
        void read();
        
        void forceFlush();
    }
}
