@Override
  public CompositeByteBuf discardReadComponents() {
    assert !freed;
    final int readerIndex = readerIndex();
    if (readerIndex == 0) {
      return this;
    }

    // Discard everything if (readerIndex = writerIndex = capacity).
    int writerIndex = writerIndex();
    if (readerIndex == writerIndex && writerIndex == capacity()) {
      for (Component c : components) {
        c.freeIfNecessary();
      }
      components.clear();
      setIndex(0, 0);
      adjustMarkers(readerIndex);
      return this;
    }

    // Remove read components.
    int firstComponentId = toComponentIndex(readerIndex);
    for (int i = 0; i < firstComponentId; i++) {
      components.get(i).freeIfNecessary();
    }
    components.subList(0, firstComponentId).clear();

    // Update indexes and markers.
    Component first = components.get(0);
    updateComponentOffsets(0);
    setIndex(readerIndex - first.offset, writerIndex - first.offset);
    adjustMarkers(first.offset);
    return this;
  }
  @Override
  public void free() {
    if (freed) {
      return;
    }

    freed = true;
    resumeIntermediaryDeallocations();
    for (Component c : components) {
      c.freeIfNecessary();
    }

    leak.close();
  }
  @Override
  public CompositeByteBuf discardReadBytes() {
    assert !freed;
    final int readerIndex = readerIndex();
    if (readerIndex == 0) {
      return this;
    }

    // Discard everything if (readerIndex = writerIndex = capacity).
    int writerIndex = writerIndex();
    if (readerIndex == writerIndex && writerIndex == capacity()) {
      for (Component c : components) {
        c.freeIfNecessary();
      }
      components.clear();
      setIndex(0, 0);
      adjustMarkers(readerIndex);
      return this;
    }

    // Remove read components.
    int firstComponentId = toComponentIndex(readerIndex);
    for (int i = 0; i < firstComponentId; i++) {
      components.get(i).freeIfNecessary();
    }
    components.subList(0, firstComponentId).clear();

    // Remove or replace the first readable component with a new slice.
    Component c = components.get(0);
    int adjustment = readerIndex - c.offset;
    if (adjustment == c.length) {
      // new slice would be empty, so remove instead
      components.remove(0);
    } else {
      Component newC =
          new Component(c.buf.slice(adjustment, c.length - adjustment), c.allocatedBySelf);
      components.set(0, newC);
    }

    // Update indexes and markers.
    updateComponentOffsets(0);
    setIndex(0, writerIndex - readerIndex);
    adjustMarkers(readerIndex);
    return this;
  }
  /**
   * This should only be called as last operation from a method as this may adjust the underlying
   * array of components and so affect the index etc.
   */
  private void consolidateIfNeeded() {
    // Consolidate if the number of components will exceed the allowed maximum by the current
    // operation.
    final int numComponents = components.size();
    if (numComponents > maxNumComponents) {
      final int capacity = components.get(numComponents - 1).endOffset;

      ByteBuf consolidated = allocBuffer(capacity);

      // We're not using foreach to avoid creating an iterator.
      // noinspection ForLoopReplaceableByForEach
      for (int i = 0; i < numComponents; i++) {
        Component c = components.get(i);
        ByteBuf b = c.buf;
        consolidated.writeBytes(b);
        c.freeIfNecessary();
      }
      Component c = new Component(consolidated, true);
      c.endOffset = c.length;
      components.clear();
      components.add(c);
    }
  }
  @Override
  public CompositeByteBuf consolidate(int cIndex, int numComponents) {
    checkComponentIndex(cIndex, numComponents);
    if (numComponents <= 1) {
      return this;
    }

    final int endCIndex = cIndex + numComponents;
    final Component last = components.get(endCIndex - 1);
    final int capacity = last.endOffset - components.get(cIndex).offset;
    final ByteBuf consolidated = allocBuffer(capacity);

    for (int i = cIndex; i < endCIndex; i++) {
      Component c = components.get(i);
      ByteBuf b = c.buf;
      consolidated.writeBytes(b);
      c.freeIfNecessary();
    }

    components.subList(cIndex + 1, endCIndex).clear();
    components.set(cIndex, new Component(consolidated, true));
    updateComponentOffsets(cIndex);
    return this;
  }
  @Override
  public CompositeByteBuf consolidate() {
    assert !freed;
    final int numComponents = numComponents();
    if (numComponents <= 1) {
      return this;
    }

    final Component last = components.get(numComponents - 1);
    final int capacity = last.endOffset;
    final ByteBuf consolidated = allocBuffer(capacity);

    for (int i = 0; i < numComponents; i++) {
      Component c = components.get(i);
      ByteBuf b = c.buf;
      consolidated.writeBytes(b);
      c.freeIfNecessary();
    }

    components.clear();
    components.add(new Component(consolidated, true));
    updateComponentOffsets(0);
    return this;
  }