/**
   * <tt>in</tt> の内容を内部のバッファに蓄積し、 デコード要求を {@link #doDecode(IoSession, IoBuffer,
   * ProtocolDecoderOutput)} に送ります。 <tt>doDecode()</tt> は <tt>false</tt> を返すまで繰り返し呼び出され、
   * デコードが終わると累積バッファのサイズが縮められます。
   *
   * @throws IllegalStateException <tt>doDecode()</tt> が累積バッファの内容を 消費せずに <tt>true</tt> を返した場合
   */
  public void decode(IoSession session, IoBuffer in, ProtocolDecoderOutput out) throws Exception {
    if (!session.getTransportMetadata().hasFragmentation()) {
      while (in.hasRemaining()) {
        if (!doDecode(session, in, out)) {
          break;
        }
      }

      return;
    }

    boolean usingSessionBuffer = true;
    IoBuffer buf = (IoBuffer) session.getAttribute(BUFFER);
    // If we have a session buffer, append data to that; otherwise
    // use the buffer read from the network directly.
    if (buf != null) {
      boolean appended = false;
      // Make sure that the buffer is auto-expanded.
      if (buf.isAutoExpand()) {
        try {
          buf.put(in);
          appended = true;
        } catch (IllegalStateException e) {
          // A user called derivation method (e.g. slice()),
          // which disables auto-expansion of the parent buffer.
        } catch (IndexOutOfBoundsException e) {
          // A user disabled auto-expansion.
        }
      }

      if (appended) {
        buf.flip();
      } else {
        // Reallocate the buffer if append operation failed due to
        // derivation or disabled auto-expansion.
        buf.flip();
        IoBuffer newBuf = IoBuffer.allocate(buf.remaining() + in.remaining()).setAutoExpand(true);
        newBuf.order(buf.order());
        newBuf.put(buf);
        newBuf.put(in);
        newBuf.flip();
        buf = newBuf;

        // Update the session attribute.
        session.setAttribute(BUFFER, buf);
      }
    } else {
      buf = in;
      usingSessionBuffer = false;
    }

    for (; ; ) {
      int oldPos = buf.position();
      boolean decoded = doDecode(session, buf, out);
      if (decoded) {
        if (buf.position() == oldPos) {
          throw new IllegalStateException(
              "doDecode() can't return true when buffer is not consumed.");
        }

        if (!buf.hasRemaining()) {
          break;
        }
      } else {
        break;
      }
    }

    // if there is any data left that cannot be decoded, we store
    // it in a buffer in the session and next time this decoder is
    // invoked the session buffer gets appended to
    if (buf.hasRemaining()) {
      if (usingSessionBuffer && buf.isAutoExpand()) {
        buf.compact();
      } else {
        storeRemainingInSession(buf, session);
      }
    } else {
      if (usingSessionBuffer) {
        removeSessionBuffer(session);
      }
    }
  }