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

package io.netty.channel.embedded;

import java.util.ArrayList;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.RecvByteBufAllocator;
import java.util.Objects;
import io.netty.util.internal.logging.InternalLoggerFactory;
import io.netty.channel.ChannelOutboundInvoker;
import io.netty.channel.ChannelOutboundBuffer;
import io.netty.channel.EventLoop;
import java.nio.channels.ClosedChannelException;
import java.util.concurrent.TimeUnit;
import io.netty.util.concurrent.Ticker;
import io.netty.util.concurrent.GenericFutureListener;
import io.netty.util.internal.RecyclableArrayList;
import io.netty.channel.ChannelPromise;
import io.netty.util.ReferenceCountUtil;
import java.util.ArrayDeque;
import io.netty.channel.DefaultChannelPipeline;
import io.netty.util.internal.PlatformDependent;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.DefaultChannelConfig;
import io.netty.util.concurrent.Future;
import io.netty.channel.ChannelFuture;
import io.netty.channel.Channel;
import io.netty.channel.ChannelId;
import java.util.Queue;
import io.netty.channel.ChannelConfig;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelMetadata;
import io.netty.util.internal.logging.InternalLogger;
import io.netty.channel.ChannelHandler;
import java.net.SocketAddress;
import io.netty.channel.AbstractChannel;

public class EmbeddedChannel extends AbstractChannel
{
    private static final SocketAddress LOCAL_ADDRESS;
    private static final SocketAddress REMOTE_ADDRESS;
    private static final ChannelHandler[] EMPTY_HANDLERS;
    private static final InternalLogger logger;
    private static final ChannelMetadata METADATA_NO_DISCONNECT;
    private static final ChannelMetadata METADATA_DISCONNECT;
    private final EmbeddedEventLoop loop;
    private final ChannelFutureListener recordExceptionListener;
    private final ChannelMetadata metadata;
    private final ChannelConfig config;
    private Queue<Object> inboundMessages;
    private Queue<Object> outboundMessages;
    private Throwable lastException;
    private State state;
    private int executingStackCnt;
    private boolean cancelRemainingScheduledTasks;
    
    public EmbeddedChannel() {
        this(builder());
    }
    
    public EmbeddedChannel(final ChannelId channelId) {
        this(builder().channelId(channelId));
    }
    
    public EmbeddedChannel(final ChannelHandler... handlers) {
        this(builder().handlers(handlers));
    }
    
    public EmbeddedChannel(final boolean hasDisconnect, final ChannelHandler... handlers) {
        this(builder().hasDisconnect(hasDisconnect).handlers(handlers));
    }
    
    public EmbeddedChannel(final boolean register, final boolean hasDisconnect, final ChannelHandler... handlers) {
        this(builder().register(register).hasDisconnect(hasDisconnect).handlers(handlers));
    }
    
    public EmbeddedChannel(final ChannelId channelId, final ChannelHandler... handlers) {
        this(builder().channelId(channelId).handlers(handlers));
    }
    
    public EmbeddedChannel(final ChannelId channelId, final boolean hasDisconnect, final ChannelHandler... handlers) {
        this(builder().channelId(channelId).hasDisconnect(hasDisconnect).handlers(handlers));
    }
    
    public EmbeddedChannel(final ChannelId channelId, final boolean register, final boolean hasDisconnect, final ChannelHandler... handlers) {
        this(builder().channelId(channelId).register(register).hasDisconnect(hasDisconnect).handlers(handlers));
    }
    
    public EmbeddedChannel(final Channel parent, final ChannelId channelId, final boolean register, final boolean hasDisconnect, final ChannelHandler... handlers) {
        this(builder().parent(parent).channelId(channelId).register(register).hasDisconnect(hasDisconnect).handlers(handlers));
    }
    
    public EmbeddedChannel(final ChannelId channelId, final boolean hasDisconnect, final ChannelConfig config, final ChannelHandler... handlers) {
        this(builder().channelId(channelId).hasDisconnect(hasDisconnect).config(config).handlers(handlers));
    }
    
    protected EmbeddedChannel(final Builder builder) {
        super(builder.parent, builder.channelId);
        this.recordExceptionListener = new ChannelFutureListener() {
            @Override
            public void operationComplete(final ChannelFuture future) throws Exception {
                EmbeddedChannel.this.recordException(future);
            }
        };
        this.loop = new EmbeddedEventLoop((builder.ticker == null) ? new EmbeddedEventLoop.FreezableTicker() : builder.ticker);
        this.metadata = metadata(builder.hasDisconnect);
        this.config = ((builder.config == null) ? new DefaultChannelConfig(this) : builder.config);
        if (builder.handler == null) {
            this.setup(builder.register, builder.handlers);
        }
        else {
            this.setup(builder.register, builder.handler);
        }
    }
    
    private static ChannelMetadata metadata(final boolean hasDisconnect) {
        return hasDisconnect ? EmbeddedChannel.METADATA_DISCONNECT : EmbeddedChannel.METADATA_NO_DISCONNECT;
    }
    
    private void setup(final boolean register, final ChannelHandler... handlers) {
        final ChannelPipeline p = this.pipeline();
        p.addLast(new ChannelInitializer<Channel>() {
            @Override
            protected void initChannel(final Channel ch) {
                final ChannelPipeline pipeline = ch.pipeline();
                for (final ChannelHandler h : handlers) {
                    if (h == null) {
                        break;
                    }
                    pipeline.addLast(h);
                }
            }
        });
        if (register) {
            final ChannelFuture future = this.loop.register(this);
            assert future.isDone();
        }
    }
    
    private void setup(final boolean register, final ChannelHandler handler) {
        final ChannelPipeline p = this.pipeline();
        p.addLast(handler);
        if (register) {
            final ChannelFuture future = this.loop.register(this);
            assert future.isDone();
        }
    }
    
    public void register() throws Exception {
        final ChannelFuture future = this.loop.register(this);
        assert future.isDone();
        final Throwable cause = future.cause();
        if (cause != null) {
            PlatformDependent.throwException(cause);
        }
    }
    
    @Override
    protected final DefaultChannelPipeline newChannelPipeline() {
        return new EmbeddedChannelPipeline(this);
    }
    
    @Override
    public ChannelMetadata metadata() {
        return this.metadata;
    }
    
    @Override
    public ChannelConfig config() {
        return this.config;
    }
    
    @Override
    public boolean isOpen() {
        return this.state != State.CLOSED;
    }
    
    @Override
    public boolean isActive() {
        return this.state == State.ACTIVE;
    }
    
    public Queue<Object> inboundMessages() {
        if (this.inboundMessages == null) {
            this.inboundMessages = new ArrayDeque<Object>();
        }
        return this.inboundMessages;
    }
    
    @Deprecated
    public Queue<Object> lastInboundBuffer() {
        return this.inboundMessages();
    }
    
    public Queue<Object> outboundMessages() {
        if (this.outboundMessages == null) {
            this.outboundMessages = new ArrayDeque<Object>();
        }
        return this.outboundMessages;
    }
    
    @Deprecated
    public Queue<Object> lastOutboundBuffer() {
        return this.outboundMessages();
    }
    
    public <T> T readInbound() {
        final T message = (T)poll(this.inboundMessages);
        if (message != null) {
            ReferenceCountUtil.touch(message, "Caller of readInbound() will handle the message from this point");
        }
        return message;
    }
    
    public <T> T readOutbound() {
        final T message = (T)poll(this.outboundMessages);
        if (message != null) {
            ReferenceCountUtil.touch(message, "Caller of readOutbound() will handle the message from this point.");
        }
        return message;
    }
    
    public boolean writeInbound(final Object... msgs) {
        this.ensureOpen();
        if (msgs.length == 0) {
            return isNotEmpty(this.inboundMessages);
        }
        ++this.executingStackCnt;
        try {
            final ChannelPipeline p = this.pipeline();
            for (final Object m : msgs) {
                p.fireChannelRead(m);
            }
            this.flushInbound(false, this.voidPromise());
        }
        finally {
            --this.executingStackCnt;
            this.maybeRunPendingTasks();
        }
        return isNotEmpty(this.inboundMessages);
    }
    
    public ChannelFuture writeOneInbound(final Object msg) {
        return this.writeOneInbound(msg, this.newPromise());
    }
    
    public ChannelFuture writeOneInbound(final Object msg, final ChannelPromise promise) {
        ++this.executingStackCnt;
        try {
            if (this.checkOpen(true)) {
                this.pipeline().fireChannelRead(msg);
            }
        }
        finally {
            --this.executingStackCnt;
            this.maybeRunPendingTasks();
        }
        return this.checkException(promise);
    }
    
    public EmbeddedChannel flushInbound() {
        this.flushInbound(true, this.voidPromise());
        return this;
    }
    
    private ChannelFuture flushInbound(final boolean recordException, final ChannelPromise promise) {
        ++this.executingStackCnt;
        try {
            if (this.checkOpen(recordException)) {
                this.pipeline().fireChannelReadComplete();
                this.runPendingTasks();
            }
        }
        finally {
            --this.executingStackCnt;
            this.maybeRunPendingTasks();
        }
        return this.checkException(promise);
    }
    
    public boolean writeOutbound(final Object... msgs) {
        this.ensureOpen();
        if (msgs.length == 0) {
            return isNotEmpty(this.outboundMessages);
        }
        ++this.executingStackCnt;
        final RecyclableArrayList futures = RecyclableArrayList.newInstance(msgs.length);
        try {
            try {
                for (final Object m : msgs) {
                    if (m == null) {
                        break;
                    }
                    futures.add(this.write(m));
                }
                this.flushOutbound0();
                for (int size = futures.size(), i = 0; i < size; ++i) {
                    final ChannelFuture future = ((ArrayList<ChannelFuture>)futures).get(i);
                    if (future.isDone()) {
                        this.recordException(future);
                    }
                    else {
                        future.addListener((GenericFutureListener<? extends Future<? super Void>>)this.recordExceptionListener);
                    }
                }
            }
            finally {
                --this.executingStackCnt;
                this.maybeRunPendingTasks();
            }
            this.checkException();
            return isNotEmpty(this.outboundMessages);
        }
        finally {
            futures.recycle();
        }
    }
    
    public ChannelFuture writeOneOutbound(final Object msg) {
        return this.writeOneOutbound(msg, this.newPromise());
    }
    
    public ChannelFuture writeOneOutbound(final Object msg, final ChannelPromise promise) {
        ++this.executingStackCnt;
        try {
            if (this.checkOpen(true)) {
                return this.write(msg, promise);
            }
        }
        finally {
            --this.executingStackCnt;
            this.maybeRunPendingTasks();
        }
        return this.checkException(promise);
    }
    
    public EmbeddedChannel flushOutbound() {
        ++this.executingStackCnt;
        try {
            if (this.checkOpen(true)) {
                this.flushOutbound0();
            }
        }
        finally {
            --this.executingStackCnt;
            this.maybeRunPendingTasks();
        }
        this.checkException(this.voidPromise());
        return this;
    }
    
    private void flushOutbound0() {
        this.runPendingTasks();
        this.flush();
    }
    
    public boolean finish() {
        return this.finish(false);
    }
    
    public boolean finishAndReleaseAll() {
        return this.finish(true);
    }
    
    private boolean finish(final boolean releaseAll) {
        ++this.executingStackCnt;
        try {
            this.close();
        }
        finally {
            --this.executingStackCnt;
            this.maybeRunPendingTasks();
        }
        try {
            this.checkException();
            return isNotEmpty(this.inboundMessages) || isNotEmpty(this.outboundMessages);
        }
        finally {
            if (releaseAll) {
                releaseAll(this.inboundMessages);
                releaseAll(this.outboundMessages);
            }
        }
    }
    
    public boolean releaseInbound() {
        return releaseAll(this.inboundMessages);
    }
    
    public boolean releaseOutbound() {
        return releaseAll(this.outboundMessages);
    }
    
    private static boolean releaseAll(final Queue<Object> queue) {
        if (isNotEmpty(queue)) {
            while (true) {
                final Object msg = queue.poll();
                if (msg == null) {
                    break;
                }
                ReferenceCountUtil.release(msg);
            }
            return true;
        }
        return false;
    }
    
    @Override
    public final ChannelFuture close() {
        return this.close(this.newPromise());
    }
    
    @Override
    public final ChannelFuture disconnect() {
        return this.disconnect(this.newPromise());
    }
    
    @Override
    public final ChannelFuture close(final ChannelPromise promise) {
        ++this.executingStackCnt;
        ChannelFuture future;
        try {
            this.runPendingTasks();
            future = super.close(promise);
            this.cancelRemainingScheduledTasks = true;
        }
        finally {
            --this.executingStackCnt;
            this.maybeRunPendingTasks();
        }
        return future;
    }
    
    @Override
    public final ChannelFuture disconnect(final ChannelPromise promise) {
        ++this.executingStackCnt;
        ChannelFuture future;
        try {
            future = super.disconnect(promise);
            if (!this.metadata.hasDisconnect()) {
                this.cancelRemainingScheduledTasks = true;
            }
        }
        finally {
            --this.executingStackCnt;
            this.maybeRunPendingTasks();
        }
        return future;
    }
    
    @Override
    public ChannelFuture bind(final SocketAddress localAddress) {
        ++this.executingStackCnt;
        try {
            return super.bind(localAddress);
        }
        finally {
            --this.executingStackCnt;
            this.maybeRunPendingTasks();
        }
    }
    
    @Override
    public ChannelFuture connect(final SocketAddress remoteAddress) {
        ++this.executingStackCnt;
        try {
            return super.connect(remoteAddress);
        }
        finally {
            --this.executingStackCnt;
            this.maybeRunPendingTasks();
        }
    }
    
    @Override
    public ChannelFuture connect(final SocketAddress remoteAddress, final SocketAddress localAddress) {
        ++this.executingStackCnt;
        try {
            return super.connect(remoteAddress, localAddress);
        }
        finally {
            --this.executingStackCnt;
            this.maybeRunPendingTasks();
        }
    }
    
    @Override
    public ChannelFuture deregister() {
        ++this.executingStackCnt;
        try {
            return super.deregister();
        }
        finally {
            --this.executingStackCnt;
            this.maybeRunPendingTasks();
        }
    }
    
    @Override
    public Channel flush() {
        ++this.executingStackCnt;
        try {
            return super.flush();
        }
        finally {
            --this.executingStackCnt;
            this.maybeRunPendingTasks();
        }
    }
    
    @Override
    public ChannelFuture bind(final SocketAddress localAddress, final ChannelPromise promise) {
        ++this.executingStackCnt;
        try {
            return super.bind(localAddress, promise);
        }
        finally {
            --this.executingStackCnt;
            this.maybeRunPendingTasks();
        }
    }
    
    @Override
    public ChannelFuture connect(final SocketAddress remoteAddress, final ChannelPromise promise) {
        ++this.executingStackCnt;
        try {
            return super.connect(remoteAddress, promise);
        }
        finally {
            --this.executingStackCnt;
            this.maybeRunPendingTasks();
        }
    }
    
    @Override
    public ChannelFuture connect(final SocketAddress remoteAddress, final SocketAddress localAddress, final ChannelPromise promise) {
        ++this.executingStackCnt;
        try {
            return super.connect(remoteAddress, localAddress, promise);
        }
        finally {
            --this.executingStackCnt;
            this.maybeRunPendingTasks();
        }
    }
    
    @Override
    public ChannelFuture deregister(final ChannelPromise promise) {
        ++this.executingStackCnt;
        try {
            return super.deregister(promise);
        }
        finally {
            --this.executingStackCnt;
            this.maybeRunPendingTasks();
        }
    }
    
    @Override
    public Channel read() {
        ++this.executingStackCnt;
        try {
            return super.read();
        }
        finally {
            --this.executingStackCnt;
            this.maybeRunPendingTasks();
        }
    }
    
    @Override
    public ChannelFuture write(final Object msg) {
        ++this.executingStackCnt;
        try {
            return super.write(msg);
        }
        finally {
            --this.executingStackCnt;
            this.maybeRunPendingTasks();
        }
    }
    
    @Override
    public ChannelFuture write(final Object msg, final ChannelPromise promise) {
        ++this.executingStackCnt;
        try {
            return super.write(msg, promise);
        }
        finally {
            --this.executingStackCnt;
            this.maybeRunPendingTasks();
        }
    }
    
    @Override
    public ChannelFuture writeAndFlush(final Object msg) {
        ++this.executingStackCnt;
        try {
            return super.writeAndFlush(msg);
        }
        finally {
            --this.executingStackCnt;
            this.maybeRunPendingTasks();
        }
    }
    
    @Override
    public ChannelFuture writeAndFlush(final Object msg, final ChannelPromise promise) {
        ++this.executingStackCnt;
        try {
            return super.writeAndFlush(msg, promise);
        }
        finally {
            --this.executingStackCnt;
            this.maybeRunPendingTasks();
        }
    }
    
    private static boolean isNotEmpty(final Queue<Object> queue) {
        return queue != null && !queue.isEmpty();
    }
    
    private static Object poll(final Queue<Object> queue) {
        return (queue != null) ? queue.poll() : null;
    }
    
    private void maybeRunPendingTasks() {
        if (this.executingStackCnt == 0) {
            this.runPendingTasks();
            if (this.cancelRemainingScheduledTasks) {
                this.embeddedEventLoop().cancelScheduledTasks();
            }
        }
    }
    
    public void runPendingTasks() {
        try {
            this.embeddedEventLoop().runTasks();
        }
        catch (final Exception e) {
            this.recordException(e);
        }
        try {
            this.embeddedEventLoop().runScheduledTasks();
        }
        catch (final Exception e) {
            this.recordException(e);
        }
    }
    
    public boolean hasPendingTasks() {
        return this.embeddedEventLoop().hasPendingNormalTasks() || this.embeddedEventLoop().nextScheduledTask() == 0L;
    }
    
    public long runScheduledPendingTasks() {
        try {
            return this.embeddedEventLoop().runScheduledTasks();
        }
        catch (final Exception e) {
            this.recordException(e);
            return this.embeddedEventLoop().nextScheduledTask();
        }
    }
    
    private void recordException(final ChannelFuture future) {
        if (!future.isSuccess()) {
            this.recordException(future.cause());
        }
    }
    
    private void recordException(final Throwable cause) {
        if (this.lastException == null) {
            this.lastException = cause;
        }
        else {
            EmbeddedChannel.logger.warn("More than one exception was raised. Will report only the first one and log others.", cause);
        }
    }
    
    private EmbeddedEventLoop.FreezableTicker freezableTicker() {
        final Ticker ticker = this.eventLoop().ticker();
        if (ticker instanceof EmbeddedEventLoop.FreezableTicker) {
            return (EmbeddedEventLoop.FreezableTicker)ticker;
        }
        throw new IllegalStateException("EmbeddedChannel constructed with custom ticker, time manipulation methods are unavailable.");
    }
    
    public void advanceTimeBy(final long duration, final TimeUnit unit) {
        this.freezableTicker().advance(duration, unit);
    }
    
    public void freezeTime() {
        this.freezableTicker().freezeTime();
    }
    
    public void unfreezeTime() {
        this.freezableTicker().unfreezeTime();
    }
    
    private ChannelFuture checkException(final ChannelPromise promise) {
        final Throwable t = this.lastException;
        if (t != null) {
            this.lastException = null;
            if (promise.isVoid()) {
                PlatformDependent.throwException(t);
            }
            return promise.setFailure(t);
        }
        return promise.setSuccess();
    }
    
    public void checkException() {
        this.checkException(this.voidPromise());
    }
    
    private boolean checkOpen(final boolean recordException) {
        if (!this.isOpen()) {
            if (recordException) {
                this.recordException(new ClosedChannelException());
            }
            return false;
        }
        return true;
    }
    
    private EmbeddedEventLoop embeddedEventLoop() {
        if (this.isRegistered()) {
            return (EmbeddedEventLoop)super.eventLoop();
        }
        return this.loop;
    }
    
    protected final void ensureOpen() {
        if (!this.checkOpen(true)) {
            this.checkException();
        }
    }
    
    @Override
    protected boolean isCompatible(final EventLoop loop) {
        return loop instanceof EmbeddedEventLoop;
    }
    
    @Override
    protected SocketAddress localAddress0() {
        return this.isActive() ? EmbeddedChannel.LOCAL_ADDRESS : null;
    }
    
    @Override
    protected SocketAddress remoteAddress0() {
        return this.isActive() ? EmbeddedChannel.REMOTE_ADDRESS : null;
    }
    
    @Override
    protected void doRegister() throws Exception {
        this.state = State.ACTIVE;
    }
    
    @Override
    protected void doBind(final SocketAddress localAddress) throws Exception {
    }
    
    @Override
    protected void doDisconnect() throws Exception {
        if (!this.metadata.hasDisconnect()) {
            this.doClose();
        }
    }
    
    @Override
    protected void doClose() throws Exception {
        this.state = State.CLOSED;
    }
    
    @Override
    protected void doBeginRead() throws Exception {
    }
    
    @Override
    protected AbstractUnsafe newUnsafe() {
        return new EmbeddedUnsafe();
    }
    
    @Override
    public Channel.Unsafe unsafe() {
        return ((EmbeddedUnsafe)super.unsafe()).wrapped;
    }
    
    @Override
    protected void doWrite(final ChannelOutboundBuffer in) throws Exception {
        while (true) {
            final Object msg = in.current();
            if (msg == null) {
                break;
            }
            ReferenceCountUtil.retain(msg);
            this.handleOutboundMessage(msg);
            in.remove();
        }
    }
    
    protected void handleOutboundMessage(final Object msg) {
        this.outboundMessages().add(msg);
    }
    
    protected void handleInboundMessage(final Object msg) {
        this.inboundMessages().add(msg);
    }
    
    public static Builder builder() {
        return new Builder();
    }
    
    static {
        LOCAL_ADDRESS = new EmbeddedSocketAddress();
        REMOTE_ADDRESS = new EmbeddedSocketAddress();
        EMPTY_HANDLERS = new ChannelHandler[0];
        logger = InternalLoggerFactory.getInstance(EmbeddedChannel.class);
        METADATA_NO_DISCONNECT = new ChannelMetadata(false);
        METADATA_DISCONNECT = new ChannelMetadata(true);
    }
    
    private enum State
    {
        OPEN, 
        ACTIVE, 
        CLOSED;
    }
    
    public static final class Builder
    {
        Channel parent;
        ChannelId channelId;
        boolean register;
        boolean hasDisconnect;
        ChannelHandler[] handlers;
        ChannelHandler handler;
        ChannelConfig config;
        Ticker ticker;
        
        private Builder() {
            this.channelId = EmbeddedChannelId.INSTANCE;
            this.register = true;
            this.handlers = EmbeddedChannel.EMPTY_HANDLERS;
        }
        
        public Builder parent(final Channel parent) {
            this.parent = parent;
            return this;
        }
        
        public Builder channelId(final ChannelId channelId) {
            this.channelId = Objects.requireNonNull(channelId, "channelId");
            return this;
        }
        
        public Builder register(final boolean register) {
            this.register = register;
            return this;
        }
        
        public Builder hasDisconnect(final boolean hasDisconnect) {
            this.hasDisconnect = hasDisconnect;
            return this;
        }
        
        public Builder handlers(final ChannelHandler... handlers) {
            this.handlers = Objects.requireNonNull(handlers, "handlers");
            this.handler = null;
            return this;
        }
        
        public Builder handlers(final ChannelHandler handler) {
            this.handler = Objects.requireNonNull(handler, "handler");
            this.handlers = null;
            return this;
        }
        
        public Builder config(final ChannelConfig config) {
            this.config = Objects.requireNonNull(config, "config");
            return this;
        }
        
        public Builder ticker(final Ticker ticker) {
            this.ticker = ticker;
            return this;
        }
        
        public EmbeddedChannel build() {
            return new EmbeddedChannel(this);
        }
    }
    
    private final class EmbeddedUnsafe extends AbstractUnsafe
    {
        final Channel.Unsafe wrapped;
        
        private EmbeddedUnsafe() {
            this.wrapped = new Channel.Unsafe() {
                @Override
                public RecvByteBufAllocator.Handle recvBufAllocHandle() {
                    return EmbeddedUnsafe.this.recvBufAllocHandle();
                }
                
                @Override
                public SocketAddress localAddress() {
                    return EmbeddedUnsafe.this.localAddress();
                }
                
                @Override
                public SocketAddress remoteAddress() {
                    return EmbeddedUnsafe.this.remoteAddress();
                }
                
                @Override
                public void register(final EventLoop eventLoop, final ChannelPromise promise) {
                    EmbeddedChannel.this.executingStackCnt++;
                    try {
                        EmbeddedUnsafe.this.register(eventLoop, promise);
                    }
                    finally {
                        EmbeddedChannel.this.executingStackCnt--;
                        EmbeddedChannel.this.maybeRunPendingTasks();
                    }
                }
                
                @Override
                public void bind(final SocketAddress localAddress, final ChannelPromise promise) {
                    EmbeddedChannel.this.executingStackCnt++;
                    try {
                        EmbeddedUnsafe.this.bind(localAddress, promise);
                    }
                    finally {
                        EmbeddedChannel.this.executingStackCnt--;
                        EmbeddedChannel.this.maybeRunPendingTasks();
                    }
                }
                
                @Override
                public void connect(final SocketAddress remoteAddress, final SocketAddress localAddress, final ChannelPromise promise) {
                    EmbeddedChannel.this.executingStackCnt++;
                    try {
                        EmbeddedUnsafe.this.connect(remoteAddress, localAddress, promise);
                    }
                    finally {
                        EmbeddedChannel.this.executingStackCnt--;
                        EmbeddedChannel.this.maybeRunPendingTasks();
                    }
                }
                
                @Override
                public void disconnect(final ChannelPromise promise) {
                    EmbeddedChannel.this.executingStackCnt++;
                    try {
                        EmbeddedUnsafe.this.disconnect(promise);
                    }
                    finally {
                        EmbeddedChannel.this.executingStackCnt--;
                        EmbeddedChannel.this.maybeRunPendingTasks();
                    }
                }
                
                @Override
                public void close(final ChannelPromise promise) {
                    EmbeddedChannel.this.executingStackCnt++;
                    try {
                        EmbeddedUnsafe.this.close(promise);
                    }
                    finally {
                        EmbeddedChannel.this.executingStackCnt--;
                        EmbeddedChannel.this.maybeRunPendingTasks();
                    }
                }
                
                @Override
                public void closeForcibly() {
                    EmbeddedChannel.this.executingStackCnt++;
                    try {
                        EmbeddedUnsafe.this.closeForcibly();
                    }
                    finally {
                        EmbeddedChannel.this.executingStackCnt--;
                        EmbeddedChannel.this.maybeRunPendingTasks();
                    }
                }
                
                @Override
                public void deregister(final ChannelPromise promise) {
                    EmbeddedChannel.this.executingStackCnt++;
                    try {
                        EmbeddedUnsafe.this.deregister(promise);
                    }
                    finally {
                        EmbeddedChannel.this.executingStackCnt--;
                        EmbeddedChannel.this.maybeRunPendingTasks();
                    }
                }
                
                @Override
                public void beginRead() {
                    EmbeddedChannel.this.executingStackCnt++;
                    try {
                        EmbeddedUnsafe.this.beginRead();
                    }
                    finally {
                        EmbeddedChannel.this.executingStackCnt--;
                        EmbeddedChannel.this.maybeRunPendingTasks();
                    }
                }
                
                @Override
                public void write(final Object msg, final ChannelPromise promise) {
                    EmbeddedChannel.this.executingStackCnt++;
                    try {
                        EmbeddedUnsafe.this.write(msg, promise);
                    }
                    finally {
                        EmbeddedChannel.this.executingStackCnt--;
                        EmbeddedChannel.this.maybeRunPendingTasks();
                    }
                }
                
                @Override
                public void flush() {
                    EmbeddedChannel.this.executingStackCnt++;
                    try {
                        EmbeddedUnsafe.this.flush();
                    }
                    finally {
                        EmbeddedChannel.this.executingStackCnt--;
                        EmbeddedChannel.this.maybeRunPendingTasks();
                    }
                }
                
                @Override
                public ChannelPromise voidPromise() {
                    return EmbeddedUnsafe.this.voidPromise();
                }
                
                @Override
                public ChannelOutboundBuffer outboundBuffer() {
                    return EmbeddedUnsafe.this.outboundBuffer();
                }
            };
        }
        
        @Override
        public void connect(final SocketAddress remoteAddress, final SocketAddress localAddress, final ChannelPromise promise) {
            this.safeSetSuccess(promise);
        }
    }
    
    private final class EmbeddedChannelPipeline extends DefaultChannelPipeline
    {
        EmbeddedChannelPipeline(final EmbeddedChannel channel) {
            super(channel);
        }
        
        @Override
        protected void onUnhandledInboundException(final Throwable cause) {
            EmbeddedChannel.this.recordException(cause);
        }
        
        @Override
        protected void onUnhandledInboundMessage(final ChannelHandlerContext ctx, final Object msg) {
            EmbeddedChannel.this.handleInboundMessage(msg);
        }
    }
}
