private void replace0(
      DefaultChannelHandlerContext ctx,
      String newName,
      DefaultChannelHandlerContext newCtx,
      boolean forward) {
    boolean sameName = ctx.name().equals(newName);

    DefaultChannelHandlerContext prev = ctx.prev;
    DefaultChannelHandlerContext next = ctx.next;
    newCtx.prev = prev;
    newCtx.next = next;

    callBeforeRemove(ctx);
    callBeforeAdd(newCtx);

    prev.next = newCtx;
    next.prev = newCtx;

    if (!sameName) {
      name2ctx.remove(ctx.name());
    }
    name2ctx.put(newName, newCtx);

    ChannelPipelineException removeException = null;
    ChannelPipelineException addException = null;
    boolean removed = false;
    try {
      callAfterRemove(ctx, forward);
      removed = true;
    } catch (ChannelPipelineException e) {
      removeException = e;
    }

    boolean added = false;
    try {
      callAfterAdd(newCtx);
      added = true;
    } catch (ChannelPipelineException e) {
      addException = e;
    }

    if (!removed && !added) {
      logger.warn(removeException.getMessage(), removeException);
      logger.warn(addException.getMessage(), addException);
      throw new ChannelPipelineException(
          "Both "
              + ctx.handler().getClass().getName()
              + ".afterRemove() and "
              + newCtx.handler().getClass().getName()
              + ".afterAdd() failed; see logs.");
    } else if (!removed) {
      throw removeException;
    } else if (!added) {
      throw addException;
    }
  }
  /** Returns the {@link String} representation of this pipeline. */
  @Override
  public String toString() {
    StringBuilder buf = new StringBuilder();
    buf.append(getClass().getSimpleName());
    buf.append('{');
    DefaultChannelHandlerContext ctx = head.next;
    for (; ; ) {
      if (ctx == tail) {
        break;
      }

      buf.append('(');
      buf.append(ctx.name());
      buf.append(" = ");
      buf.append(ctx.handler().getClass().getName());
      buf.append(')');

      ctx = ctx.next;
      if (ctx == tail) {
        break;
      }

      buf.append(", ");
    }
    buf.append('}');
    return buf.toString();
  }
 @Override
 public ChannelHandler last() {
   DefaultChannelHandlerContext last = tail.prev;
   if (last == head) {
     return null;
   }
   return last.handler();
 }
 @Override
 public ChannelHandler first() {
   DefaultChannelHandlerContext first = head.next;
   if (first == null) {
     return null;
   }
   return first.handler();
 }
  private ChannelHandler replace(
      final DefaultChannelHandlerContext ctx,
      final String newName,
      ChannelHandler newHandler,
      final boolean forward) {

    assert ctx != head && ctx != tail;

    Future<?> future;
    synchronized (this) {
      boolean sameName = ctx.name().equals(newName);
      if (!sameName) {
        checkDuplicateName(newName);
      }

      final DefaultChannelHandlerContext newCtx =
          new DefaultChannelHandlerContext(this, ctx.executor, newName, newHandler);

      if (!newCtx.channel().isRegistered() || newCtx.executor().inEventLoop()) {
        replace0(ctx, newName, newCtx, forward);

        return ctx.handler();
      } else {
        future =
            newCtx
                .executor()
                .submit(
                    new Runnable() {
                      @Override
                      public void run() {
                        synchronized (DefaultChannelPipeline.this) {
                          replace0(ctx, newName, newCtx, forward);
                        }
                      }
                    });
      }
    }

    // Run the following 'waiting' code outside of the above synchronized block
    // in order to avoid deadlock

    waitForFuture(future);

    return ctx.handler();
  }
 @Override
 public Map<String, ChannelHandler> toMap() {
   Map<String, ChannelHandler> map = new LinkedHashMap<String, ChannelHandler>();
   DefaultChannelHandlerContext ctx = head.next;
   for (; ; ) {
     if (ctx == tail) {
       return map;
     }
     map.put(ctx.name(), ctx.handler());
     ctx = ctx.next;
   }
 }
  private void callAfterRemove(final DefaultChannelHandlerContext ctx, boolean forward) {
    final ChannelHandler handler = ctx.handler();

    // Notify the complete removal.
    try {
      handler.afterRemove(ctx);
    } catch (Throwable t) {
      throw new ChannelPipelineException(
          ctx.handler().getClass().getName() + ".afterRemove() has thrown an exception.", t);
    }

    if (forward) {
      ctx.forwardBufferContent();
    } else {
      ctx.clearBuffer();
    }

    ctx.removed = true;

    // Free all buffers before completing removal.
    if (!channel.isRegistered()) {
      ctx.freeHandlerBuffersAfterRemoval();
    }
  }
  @Override
  public ChannelHandlerContext context(Class<? extends ChannelHandler> handlerType) {
    if (handlerType == null) {
      throw new NullPointerException("handlerType");
    }

    DefaultChannelHandlerContext ctx = head.next;
    for (; ; ) {
      if (ctx == null) {
        return null;
      }
      if (handlerType.isAssignableFrom(ctx.handler().getClass())) {
        return ctx;
      }
      ctx = ctx.next;
    }
  }
  @Override
  public ChannelHandlerContext context(ChannelHandler handler) {
    if (handler == null) {
      throw new NullPointerException("handler");
    }

    DefaultChannelHandlerContext ctx = head.next;
    for (; ; ) {

      if (ctx == null) {
        return null;
      }

      if (ctx.handler() == handler) {
        return ctx;
      }

      ctx = ctx.next;
    }
  }