net.tomp2p.storage.AlternativeCompositeByteBuf.java Source code

Java tutorial

Introduction

Here is the source code for net.tomp2p.storage.AlternativeCompositeByteBuf.java

Source

/*
 * Copyright 2012 The Netty Project
 *
 * The Netty Project licenses this file to you under the Apache License,
 * version 2.0 (the "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at:
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations
 * under the License.
 * 
 */

package net.tomp2p.storage;

import io.netty.buffer.AbstractByteBufAllocator;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.buffer.ByteBufProcessor;
import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.DuplicatedByteBuf;
import io.netty.buffer.PooledByteBufAllocator;
import io.netty.buffer.SlicedByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.buffer.UnpooledByteBufAllocator;
import io.netty.util.CharsetUtil;
import io.netty.util.IllegalReferenceCountException;
import io.netty.util.ResourceLeak;
import io.netty.util.ResourceLeakDetector;
import io.netty.util.internal.EmptyArrays;
import io.netty.util.internal.PlatformDependent;
import io.netty.util.internal.StringUtil;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.CharBuffer;
import java.nio.channels.GatheringByteChannel;
import java.nio.channels.ScatteringByteChannel;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CoderResult;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;

/**
 * Heavily inspired by CompositeByteBuf, but with a slight different behavior.
 * 
 * @author Thomas Bocek
 *
 */
public class AlternativeCompositeByteBuf extends ByteBuf {

    public static final PooledByteBufAllocator POOLED_DIRECT = new PooledByteBufAllocator(true);
    public static final PooledHeapByteBufAlloc POOLED_HEAP = new AlternativeCompositeByteBuf.PooledHeapByteBufAlloc();
    public static final UnpooledByteBufAllocator UNPOOLED_DIRECT = new UnpooledByteBufAllocator(true);
    public static final UnpooledHeapByteBufAlloc UNPOOLED_HEAP = new AlternativeCompositeByteBuf.UnpooledHeapByteBufAlloc();

    private static final ByteBuffer FULL_BYTEBUFFER = (ByteBuffer) ByteBuffer.allocate(1).position(1);

    private final List<Component> components = new ArrayList<Component>();
    private final Component EMPTY_COMPONENT = new Component(Unpooled.EMPTY_BUFFER);
    private final ByteBufAllocator alloc;

    private int readerIndex;
    private int writerIndex;
    private int markedReaderIndex;
    private int markedWriterIndex;

    // ref counting
    private volatile int refCnt = 1;
    private static final AtomicIntegerFieldUpdater<AlternativeCompositeByteBuf> refCntUpdater = AtomicIntegerFieldUpdater
            .newUpdater(AlternativeCompositeByteBuf.class, "refCnt");
    private boolean freed;
    private final ResourceLeak leak;
    private static final ResourceLeakDetector<ByteBuf> leakDetector = new ResourceLeakDetector<ByteBuf>(
            AlternativeCompositeByteBuf.class);

    private final class Component {
        final ByteBuf buf;
        int offset;

        Component(ByteBuf buf) {
            this.buf = buf;
        }

        int endOffset() {
            return offset + buf.readableBytes();
        }
    }

    public AlternativeCompositeByteBuf(ByteBufAllocator alloc) {
        this.alloc = alloc;
        leak = leakDetector.open(this);
    }

    public AlternativeCompositeByteBuf(ByteBufAllocator alloc, ByteBuf... buffers) {
        this.alloc = alloc;
        addComponent(buffers);
        leak = leakDetector.open(this);
    }

    @Override
    public int refCnt() {
        return refCnt;
    }

    @Override
    public boolean release() {
        for (;;) {
            int refCnt = this.refCnt;
            if (refCnt == 0) {
                throw new IllegalReferenceCountException(0, -1);
            }

            if (refCntUpdater.compareAndSet(this, refCnt, refCnt - 1)) {
                if (refCnt == 1) {
                    deallocate();
                    return true;
                }
                return false;
            }
        }
    }

    @Override
    public boolean release(int decrement) {
        if (decrement <= 0) {
            throw new IllegalArgumentException("decrement: " + decrement + " (expected: > 0)");
        }

        for (;;) {
            int refCnt = this.refCnt;
            if (refCnt < decrement) {
                throw new IllegalReferenceCountException(refCnt, -decrement);
            }

            if (refCntUpdater.compareAndSet(this, refCnt, refCnt - decrement)) {
                if (refCnt == decrement) {
                    deallocate();
                    return true;
                }
                return false;
            }
        }
    }

    @Override
    public AlternativeCompositeByteBuf retain(int increment) {
        if (increment <= 0) {
            throw new IllegalArgumentException("increment: " + increment + " (expected: > 0)");
        }

        for (;;) {
            int refCnt = this.refCnt;
            if (refCnt == 0) {
                throw new IllegalReferenceCountException(0, increment);
            }
            if (refCnt > Integer.MAX_VALUE - increment) {
                throw new IllegalReferenceCountException(refCnt, increment);
            }
            if (refCntUpdater.compareAndSet(this, refCnt, refCnt + increment)) {
                break;
            }
        }
        return this;
    }

    @Override
    public AlternativeCompositeByteBuf retain() {
        for (;;) {
            int refCnt = this.refCnt;
            if (refCnt == 0) {
                throw new IllegalReferenceCountException(0, 1);
            }
            if (refCnt == Integer.MAX_VALUE) {
                throw new IllegalReferenceCountException(Integer.MAX_VALUE, 1);
            }
            if (refCntUpdater.compareAndSet(this, refCnt, refCnt + 1)) {
                break;
            }
        }
        return this;
    }

    private void deallocate() {
        if (freed) {
            return;
        }

        freed = true;
        for (Component c : components) {
            c.buf.release();
        }
        components.clear();

        if (leak != null) {
            leak.close();
        }
    }

    private Component last() {
        if (components.isEmpty()) {
            return EMPTY_COMPONENT;
        }
        return components.get(components.size() - 1);
    }

    @Override
    public int capacity() {
        Component last = last();
        return last.offset + last.buf.capacity();
    }

    @Override
    public int maxCapacity() {
        return Integer.MAX_VALUE;
    }

    @Override
    public AlternativeCompositeByteBuf capacity(int newCapacity) {
        return capacity(newCapacity, false);
    }

    public AlternativeCompositeByteBuf capacity(int newCapacity, boolean fillBuffer) {
        if (newCapacity < 0 || newCapacity > maxCapacity()) {
            throw new IllegalArgumentException("newCapacity: " + newCapacity);
        }

        int oldCapacity = capacity();
        if (newCapacity > oldCapacity) {
            // need more storage
            final int paddingLength = newCapacity - oldCapacity;
            //since we allocate, refCnt is set to 1, don't increase it
            addComponent0(fillBuffer, allocBuffer(paddingLength));
        } else if (newCapacity < oldCapacity) {
            // remove storage
            int bytesToTrim = oldCapacity - newCapacity;
            for (ListIterator<Component> i = components.listIterator(components.size()); i.hasPrevious();) {
                Component c = i.previous();
                if (bytesToTrim >= c.buf.capacity()) {
                    bytesToTrim -= c.buf.capacity();
                    c.buf.release();
                    i.remove();
                    continue;
                }
                Component newC = new Component(c.buf.slice(0, c.buf.capacity() - bytesToTrim));
                newC.offset = c.offset;
                i.set(newC);
                break;
            }
        }

        if (readerIndex() > newCapacity) {
            setIndex(newCapacity, newCapacity);
        } else if (writerIndex() > newCapacity) {
            writerIndex(newCapacity);
        }

        return this;
    }

    private ByteBuf allocBuffer(int capacity) {
        return alloc().buffer(capacity);
    }

    public AlternativeCompositeByteBuf addComponent(ByteBuf... buffers) {
        return addComponent(false, buffers);
    }

    public AlternativeCompositeByteBuf addComponent(boolean fillBuffer, ByteBuf... buffers) {
        if (buffers == null) {
            throw new NullPointerException("buffers");
        }
        for (ByteBuf b : buffers) {
            if (b == null) {
                break;
            }
            //We want to use this buffer, so mark is as used
            b.retain();
            addComponentElement(fillBuffer, b);
        }
        return this;
    }

    private AlternativeCompositeByteBuf addComponent0(boolean fillBuffer, ByteBuf... buffers) {
        for (ByteBuf b : buffers) {
            if (b == null) {
                break;
            }
            //this is called from internally only for buffers we already have a refCnt++
            addComponentElement(fillBuffer, b);
        }
        return this;
    }

    private void addComponentElement(final boolean fillBuffer, final ByteBuf b) {
        final Component c = new Component(b.order(ByteOrder.BIG_ENDIAN).duplicate());
        final int size = components.size();
        components.add(c);
        if (size != 0) {
            Component prev = components.get(size - 1);
            if (fillBuffer) {
                // we plan to fill the buffer
                c.offset = prev.offset + prev.buf.capacity();
            } else {
                // the buffer may not get filled
                c.offset = prev.endOffset();
            }
        }
        writerIndex0(writerIndex() + c.buf.writerIndex());
    }

    @Override
    public ByteBufAllocator alloc() {
        return alloc;
    }

    @Override
    public ByteOrder order() {
        return ByteOrder.BIG_ENDIAN;
    }

    @Override
    public AlternativeCompositeByteBuf order(ByteOrder endianness) {
        if (endianness == order()) {
            return this;
        } else {
            throw new UnsupportedOperationException("not implemented");
        }
    }

    @Override
    public boolean isDirect() {
        //this is stored in ByteBufAllocator, but I cannot access it
        if (alloc == UNPOOLED_DIRECT || alloc == POOLED_DIRECT) {
            return true;
        } else if (alloc == UNPOOLED_HEAP || alloc == POOLED_HEAP) {
            return false;
        }
        throw new RuntimeException("don't know what to report, Netty does not expose this");
    }

    @Override
    public int readerIndex() {
        return readerIndex;
    }

    @Override
    public AlternativeCompositeByteBuf readerIndex(int readerIndex) {
        if (readerIndex < 0 || readerIndex > writerIndex) {
            throw new IndexOutOfBoundsException(String.format(
                    "readerIndex: %d (expected: 0 <= readerIndex <= writerIndex(%d))", readerIndex, writerIndex));
        }
        this.readerIndex = readerIndex;
        return this;
    }

    @Override
    public int writerIndex() {
        return writerIndex;
    }

    @Override
    public AlternativeCompositeByteBuf writerIndex(int writerIndex) {
        if (writerIndex < readerIndex || writerIndex > capacity()) {
            throw new IndexOutOfBoundsException(
                    String.format("writerIndex: %d (expected: readerIndex(%d) <= writerIndex <= capacity(%d))",
                            writerIndex, readerIndex, capacity()));
        }
        setComponentWriterIndex(writerIndex);
        return this;
    }

    private AlternativeCompositeByteBuf writerIndex0(int writerIndex) {
        if (writerIndex < readerIndex || writerIndex > capacity()) {
            throw new IndexOutOfBoundsException(
                    String.format("writerIndex: %d (expected: readerIndex(%d) <= writerIndex <= capacity(%d))",
                            writerIndex, readerIndex, capacity()));
        }
        this.writerIndex = writerIndex;
        return this;
    }

    @Override
    public AlternativeCompositeByteBuf setIndex(int readerIndex, int writerIndex) {
        if (readerIndex < 0 || readerIndex > writerIndex || writerIndex > capacity()) {
            throw new IndexOutOfBoundsException(String.format(
                    "readerIndex: %d, writerIndex: %d (expected: 0 <= readerIndex <= writerIndex <= capacity(%d))",
                    readerIndex, writerIndex, capacity()));
        }
        this.readerIndex = readerIndex;
        setComponentWriterIndex(writerIndex);
        return this;
    }

    private AlternativeCompositeByteBuf setIndex0(int readerIndex, int writerIndex) {
        if (readerIndex < 0 || readerIndex > writerIndex || writerIndex > capacity()) {
            throw new IndexOutOfBoundsException(String.format(
                    "readerIndex: %d, writerIndex: %d (expected: 0 <= readerIndex <= writerIndex <= capacity(%d))",
                    readerIndex, writerIndex, capacity()));
        }
        this.readerIndex = readerIndex;
        this.writerIndex = writerIndex;
        return this;
    }

    @Override
    public int readableBytes() {
        return writerIndex - readerIndex;
    }

    @Override
    public int writableBytes() {
        return capacity() - writerIndex;
    }

    @Override
    public int maxWritableBytes() {
        return maxCapacity() - writerIndex;
    }

    @Override
    public boolean isReadable() {
        return writerIndex > readerIndex;
    }

    @Override
    public boolean isReadable(int numBytes) {
        return writerIndex - readerIndex >= numBytes;
    }

    @Override
    public boolean isWritable() {
        return capacity() > writerIndex;
    }

    @Override
    public boolean isWritable(int numBytes) {
        return capacity() - writerIndex >= numBytes;
    }

    @Override
    public ByteBuf unwrap() {
        return null;
    }

    @Override
    public AlternativeCompositeByteBuf clear() {
        readerIndex = 0;
        setComponentWriterIndex(0);
        return this;
    }

    @Override
    public AlternativeCompositeByteBuf markReaderIndex() {
        markedReaderIndex = readerIndex;
        return this;
    }

    @Override
    public AlternativeCompositeByteBuf resetReaderIndex() {
        readerIndex(markedReaderIndex);
        return this;
    }

    @Override
    public AlternativeCompositeByteBuf markWriterIndex() {
        markedWriterIndex = writerIndex;
        return this;
    }

    @Override
    public AlternativeCompositeByteBuf resetWriterIndex() {
        setComponentWriterIndex(markedWriterIndex);
        return this;
    }

    @Override
    public AlternativeCompositeByteBuf discardReadBytes() {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public AlternativeCompositeByteBuf discardSomeReadBytes() {
        boolean isOffsetAdjustment = false;
        int offsetAdjustment = 0;
        for (Iterator<Component> iterator = components.iterator(); iterator.hasNext();) {
            Component c = iterator.next();

            if (isOffsetAdjustment) {
                c.offset = c.offset - offsetAdjustment;
            }

            if (readerIndex >= c.endOffset()) {
                c.buf.release();
                iterator.remove();
                isOffsetAdjustment = true;
                int adjust = c.endOffset() - c.offset;
                setIndex0(readerIndex - adjust, writerIndex - adjust);
                offsetAdjustment += adjust;
            } else {
                if (!isOffsetAdjustment) {
                    break;
                }
            }
        }
        return this;
    }

    @Override
    public ByteBuf ensureWritable(int minWritableBytes) {
        return ensureWritable0(minWritableBytes, false);
    }

    public AlternativeCompositeByteBuf ensureWritable0(int minWritableBytes, boolean fillBuffer) {
        if (minWritableBytes < 0) {
            throw new IllegalArgumentException(
                    String.format("minWritableBytes: %d (expected: >= 0)", minWritableBytes));
        }

        if (minWritableBytes <= writableBytes()) {
            return this;
        }

        if (minWritableBytes > maxCapacity() - writerIndex) {
            throw new IndexOutOfBoundsException(
                    String.format("writerIndex(%d) + minWritableBytes(%d) exceeds maxCapacity(%d): %s", writerIndex,
                            minWritableBytes, maxCapacity(), this));
        }

        // Normalize the current capacity to the power of 2.
        int newCapacity = calculateNewCapacity(writerIndex + minWritableBytes);

        // Adjust to the new capacity.
        capacity(newCapacity, fillBuffer);
        return this;
    }

    private int calculateNewCapacity(int minNewCapacity) {
        final int maxCapacity = maxCapacity();
        final int threshold = 1048576 * 4; // 4 MiB page

        if (minNewCapacity == threshold) {
            return threshold;
        }

        // If over threshold, do not double but just increase by threshold.
        if (minNewCapacity > threshold) {
            int newCapacity = minNewCapacity / threshold * threshold;
            if (newCapacity > maxCapacity - threshold) {
                newCapacity = maxCapacity;
            } else {
                newCapacity += threshold;
            }
            return newCapacity;
        }

        // Not over threshold. Double up to 4 MiB, starting from 64.
        int newCapacity = 64;
        while (newCapacity < minNewCapacity) {
            newCapacity <<= 1;
        }

        return Math.min(newCapacity, maxCapacity);
    }

    @Override
    public int ensureWritable(int minWritableBytes, boolean force) {
        if (minWritableBytes < 0) {
            throw new IllegalArgumentException(
                    String.format("minWritableBytes: %d (expected: >= 0)", minWritableBytes));
        }

        if (minWritableBytes <= writableBytes()) {
            return 0;
        }

        if (minWritableBytes > maxCapacity() - writerIndex) {
            if (force) {
                if (capacity() == maxCapacity()) {
                    return 1;
                }

                capacity(maxCapacity());
                return 3;
            }
        }

        // Normalize the current capacity to the power of 2.
        int newCapacity = calculateNewCapacity(writerIndex + minWritableBytes);

        // Adjust to the new capacity.
        capacity(newCapacity);
        return 2;
    }

    private final void checkIndex(int index) {
        if (index < 0 || index > capacity()) {
            throw new IndexOutOfBoundsException(
                    String.format("index: %d (expected: range(0, %d))", index, capacity()));
        }
    }

    private final void checkIndex(int index, int fieldLength) {
        if (fieldLength < 0) {
            throw new IllegalArgumentException("length: " + fieldLength + " (expected: >= 0)");
        }
        if (index < 0 || index > capacity() - fieldLength) {
            throw new IndexOutOfBoundsException(String.format("index: %d, length: %d (expected: range(0, %d))",
                    index, fieldLength, capacity()));
        }
    }

    private Component findComponent(int offset) {
        checkIndex(offset);

        Component last = last();
        if (offset >= last.offset) {
            return last;
        }

        for (ListIterator<Component> i = components.listIterator(components.size() - 1); i.hasPrevious();) {
            Component c = i.previous();
            if (offset >= c.offset) {
                return c;
            }
        }

        throw new Error("should not happen");
    }

    private int findIndex(int offset) {
        checkIndex(offset);

        Component last = last();
        if (offset >= last.offset) {
            return components.size() - 1;
        }

        int index = components.size() - 2;
        for (ListIterator<Component> i = components.listIterator(components.size() - 1); i.hasPrevious(); index--) {
            Component c = i.previous();
            if (offset >= c.offset) {
                return index;
            }
        }

        throw new Error("should not happen");
    }

    @Override
    public boolean getBoolean(int index) {
        return getByte(index) != 0;
    }

    @Override
    public byte getByte(int index) {
        Component c = findComponent(index);
        return c.buf.getByte(index - c.offset);
    }

    @Override
    public short getUnsignedByte(int index) {
        return (short) (getByte(index) & 0xFF);
    }

    @Override
    public short getShort(int index) {
        Component c = findComponent(index);
        if (index + 2 <= c.endOffset()) {
            return c.buf.getShort(index - c.offset);
        } else if (order() == ByteOrder.BIG_ENDIAN) {
            return (short) ((getByte(index) & 0xff) << 8 | getByte(index + 1) & 0xff);
        } else {
            return (short) (getByte(index) & 0xff | (getByte(index + 1) & 0xff) << 8);
        }
    }

    @Override
    public int getUnsignedShort(int index) {
        return getShort(index) & 0xFFFF;
    }

    @Override
    public int getMedium(int index) {
        int value = getUnsignedMedium(index);
        if ((value & 0x800000) != 0) {
            value |= 0xff000000;
        }
        return value;
    }

    @Override
    public int getUnsignedMedium(int index) {
        Component c = findComponent(index);
        if (index + 3 <= c.endOffset()) {
            return c.buf.getUnsignedMedium(index - c.offset);
        } else if (order() == ByteOrder.BIG_ENDIAN) {
            return (getShort(index) & 0xffff) << 8 | getByte(index + 2) & 0xff;
        } else {
            return getShort(index) & 0xFFFF | (getByte(index + 2) & 0xFF) << 16;
        }
    }

    @Override
    public int getInt(int index) {
        Component c = findComponent(index);
        if (index + 4 <= c.endOffset()) {
            return c.buf.getInt(index - c.offset);
        } else if (order() == ByteOrder.BIG_ENDIAN) {
            return (getShort(index) & 0xffff) << 16 | getShort(index + 2) & 0xffff;
        } else {
            return getShort(index) & 0xFFFF | (getShort(index + 2) & 0xFFFF) << 16;
        }
    }

    @Override
    public long getUnsignedInt(int index) {
        return getInt(index) & 0xFFFFFFFFL;
    }

    @Override
    public long getLong(int index) {
        Component c = findComponent(index);
        if (index + 8 <= c.endOffset()) {
            return c.buf.getLong(index - c.offset);
        } else if (order() == ByteOrder.BIG_ENDIAN) {
            return (getInt(index) & 0xffffffffL) << 32 | getInt(index + 4) & 0xffffffffL;
        } else {
            return getInt(index) & 0xFFFFFFFFL | (getInt(index + 4) & 0xFFFFFFFFL) << 32;
        }
    }

    @Override
    public char getChar(int index) {
        return (char) getShort(index);
    }

    @Override
    public float getFloat(int index) {
        return Float.intBitsToFloat(getInt(index));
    }

    @Override
    public double getDouble(int index) {
        return Double.longBitsToDouble(getLong(index));
    }

    private final void checkDstIndex(int index, int length, int dstIndex, int dstCapacity) {
        checkIndex(index, length);
        if (dstIndex < 0 || dstIndex > dstCapacity - length) {
            throw new IndexOutOfBoundsException(String.format("dstIndex: %d, length: %d (expected: range(0, %d))",
                    dstIndex, length, dstCapacity));
        }
    }

    private final void checkSrcIndex(int index, int length, int srcIndex, int srcCapacity) {
        checkIndex(index, length);
        if (srcIndex < 0 || srcIndex > srcCapacity - length) {
            throw new IndexOutOfBoundsException(String.format("srcIndex: %d, length: %d (expected: range(0, %d))",
                    srcIndex, length, srcCapacity));
        }
    }

    @Override
    public AlternativeCompositeByteBuf getBytes(int index, ByteBuf dst) {
        getBytes(index, dst, dst.writableBytes());
        return this;
    }

    @Override
    public AlternativeCompositeByteBuf getBytes(int index, ByteBuf dst, int length) {
        getBytes(index, dst, dst.writerIndex(), length);
        dst.writerIndex(dst.writerIndex() + length);
        return this;
    }

    @Override
    public AlternativeCompositeByteBuf getBytes(int index, ByteBuf dst, int dstIndex, int length) {
        checkDstIndex(index, length, dstIndex, dst.capacity());
        if (length == 0) {
            return this;
        }

        int i = findIndex(index);
        while (length > 0) {
            Component c = components.get(i);
            ByteBuf s = c.buf;
            int adjustment = c.offset;
            int localLength = Math.min(length, s.readableBytes() - (index - adjustment));
            s.getBytes(index - adjustment, dst, dstIndex, localLength);
            index += localLength;
            dstIndex += localLength;
            length -= localLength;
            i++;
        }
        return this;
    }

    @Override
    public AlternativeCompositeByteBuf getBytes(int index, byte[] dst) {
        getBytes(index, dst, 0, dst.length);
        return this;
    }

    @Override
    public AlternativeCompositeByteBuf getBytes(int index, byte[] dst, int dstIndex, int length) {
        checkDstIndex(index, length, dstIndex, dst.length);
        if (length == 0) {
            return this;
        }

        int i = findIndex(index);
        while (length > 0) {
            Component c = components.get(i);
            ByteBuf s = c.buf;
            int adjustment = c.offset;
            int localLength = Math.min(length, s.readableBytes() - (index - adjustment));
            s.getBytes(index - adjustment, dst, dstIndex, localLength);
            index += localLength;
            dstIndex += localLength;
            length -= localLength;
            i++;
        }
        return this;
    }

    @Override
    public AlternativeCompositeByteBuf getBytes(int index, ByteBuffer dst) {
        int limit = dst.limit();
        int length = dst.remaining();

        checkIndex(index, length);
        if (length == 0) {
            return this;
        }

        int i = findIndex(index);
        try {
            while (length > 0) {
                Component c = components.get(i);
                ByteBuf s = c.buf;
                int adjustment = c.offset;
                int localLength = Math.min(length, s.readableBytes() - (index - adjustment));
                dst.limit(dst.position() + localLength);
                s.getBytes(index - adjustment, dst);
                index += localLength;
                length -= localLength;
                i++;
            }
        } finally {
            dst.limit(limit);
        }
        return this;
    }

    @Override
    public AlternativeCompositeByteBuf getBytes(int index, OutputStream out, int length) throws IOException {
        checkIndex(index, length);
        if (length == 0) {
            return this;
        }

        int i = findIndex(index);
        while (length > 0) {
            Component c = components.get(i);
            ByteBuf s = c.buf;
            int adjustment = c.offset;
            int localLength = Math.min(length, s.readableBytes() - (index - adjustment));
            s.getBytes(index - adjustment, out, localLength);
            index += localLength;
            length -= localLength;
            i++;
        }
        return this;
    }

    @Override
    public int getBytes(int index, GatheringByteChannel out, int length) throws IOException {
        int count = nioBufferCount();
        if (count == 1) {
            return out.write(internalNioBuffer(index, length));
        } else {
            long writtenBytes = out.write(nioBuffers(index, length));
            if (writtenBytes > Integer.MAX_VALUE) {
                return Integer.MAX_VALUE;
            } else {
                return (int) writtenBytes;
            }
        }
    }

    @Override
    public AlternativeCompositeByteBuf setBoolean(int index, boolean value) {
        setByte(index, value ? 1 : 0);
        return this;
    }

    @Override
    public AlternativeCompositeByteBuf setByte(int index, int value) {
        Component c = findComponent(index);
        c.buf.setByte(index - c.offset, value);
        return this;
    }

    @Override
    public AlternativeCompositeByteBuf setShort(int index, int value) {
        Component c = findComponent(index);
        if (index + 2 <= c.endOffset()) {
            c.buf.setShort(index - c.offset, value);
        } else if (order() == ByteOrder.BIG_ENDIAN) {
            setByte(index, (byte) (value >>> 8));
            setByte(index + 1, (byte) value);
        } else {
            setByte(index, (byte) value);
            setByte(index + 1, (byte) (value >>> 8));
        }
        return this;
    }

    @Override
    public AlternativeCompositeByteBuf setMedium(int index, int value) {
        Component c = findComponent(index);
        if (index + 3 <= c.endOffset()) {
            c.buf.setMedium(index - c.offset, value);
        } else if (order() == ByteOrder.BIG_ENDIAN) {
            setShort(index, (short) (value >> 8));
            setByte(index + 2, (byte) value);
        } else {
            setShort(index, (short) value);
            setByte(index + 2, (byte) (value >>> 16));
        }
        return this;
    }

    @Override
    public AlternativeCompositeByteBuf setInt(int index, int value) {
        Component c = findComponent(index);
        if (index + 4 <= c.endOffset()) {
            c.buf.setInt(index - c.offset, value);
        } else if (order() == ByteOrder.BIG_ENDIAN) {
            setShort(index, (short) (value >>> 16));
            setShort(index + 2, (short) value);
        } else {
            setShort(index, (short) value);
            setShort(index + 2, (short) (value >>> 16));
        }
        return this;
    }

    @Override
    public AlternativeCompositeByteBuf setLong(int index, long value) {
        Component c = findComponent(index);
        if (index + 8 <= c.endOffset()) {
            c.buf.setLong(index - c.offset, value);
        } else if (order() == ByteOrder.BIG_ENDIAN) {
            setInt(index, (int) (value >>> 32));
            setInt(index + 4, (int) value);
        } else {
            setInt(index, (int) value);
            setInt(index + 4, (int) (value >>> 32));
        }
        return this;
    }

    @Override
    public AlternativeCompositeByteBuf setChar(int index, int value) {
        setShort(index, value);
        return this;
    }

    @Override
    public AlternativeCompositeByteBuf setFloat(int index, float value) {
        setInt(index, Float.floatToRawIntBits(value));
        return this;
    }

    @Override
    public AlternativeCompositeByteBuf setDouble(int index, double value) {
        setLong(index, Double.doubleToRawLongBits(value));
        return this;
    }

    @Override
    public AlternativeCompositeByteBuf setBytes(int index, ByteBuf src) {
        setBytes(index, src, src.readableBytes());
        return this;
    }

    @Override
    public AlternativeCompositeByteBuf setBytes(int index, ByteBuf src, int length) {
        checkIndex(index, length);
        if (src == null) {
            throw new NullPointerException("src");
        }
        if (length > src.readableBytes()) {
            throw new IndexOutOfBoundsException(String.format(
                    "length(%d) exceeds src.readableBytes(%d) where src is: %s", length, src.readableBytes(), src));
        }

        setBytes(index, src, src.readerIndex(), length);
        src.readerIndex(src.readerIndex() + length);
        return this;
    }

    @Override
    public AlternativeCompositeByteBuf setBytes(int index, ByteBuf src, int srcIndex, int length) {
        checkSrcIndex(index, length, srcIndex, src.capacity());
        if (length == 0) {
            return this;
        }

        int i = findIndex(index);
        while (length > 0) {
            Component c = components.get(i);
            ByteBuf s = c.buf;
            int adjustment = c.offset;
            int localLength = Math.min(length, s.writableBytes());
            s.setBytes(index - adjustment, src, srcIndex, localLength);
            index += localLength;
            srcIndex += localLength;
            length -= localLength;
            i++;
        }
        return this;
    }

    @Override
    public AlternativeCompositeByteBuf setBytes(int index, byte[] src) {
        setBytes(index, src, 0, src.length);
        return this;
    }

    @Override
    public AlternativeCompositeByteBuf setBytes(int index, byte[] src, int srcIndex, int length) {
        checkSrcIndex(index, length, srcIndex, src.length);
        if (length == 0) {
            return this;
        }

        int i = findIndex(index);
        while (length > 0) {
            Component c = components.get(i);
            ByteBuf s = c.buf;
            int adjustment = c.offset;
            int localLength = Math.min(length, s.writableBytes());
            s.setBytes(index - adjustment, src, srcIndex, localLength);
            index += localLength;
            srcIndex += localLength;
            length -= localLength;
            i++;
        }
        return this;
    }

    public boolean sync() {
        int counter = 0;

        for (Component c : components) {
            counter += c.buf.writerIndex();
        }
        return counter == writerIndex;
    }

    @Override
    public AlternativeCompositeByteBuf setBytes(int index, ByteBuffer src) {
        int limit = src.limit();
        int length = src.remaining();

        checkIndex(index, length);
        if (length == 0) {
            return this;
        }

        int i = findIndex(index);
        try {
            while (length > 0) {
                Component c = components.get(i);
                ByteBuf s = c.buf;
                int adjustment = c.offset;
                int localLength = Math.min(length, s.writableBytes());
                src.limit(src.position() + localLength);
                s.setBytes(index - adjustment, src);
                index += localLength;
                length -= localLength;
                i++;
            }
        } finally {
            src.limit(limit);
        }
        return this;
    }

    @Override
    public int setBytes(int index, InputStream in, int length) throws IOException {
        checkIndex(index, length);
        if (length == 0) {
            return in.read(EmptyArrays.EMPTY_BYTES);
        }

        int i = findIndex(index);
        int readBytes = 0;

        do {
            Component c = components.get(i);
            ByteBuf s = c.buf;
            int adjustment = c.offset;
            int localLength = Math.min(length, s.writableBytes());
            int localReadBytes = s.setBytes(index - adjustment, in, localLength);
            if (localReadBytes < 0) {
                if (readBytes == 0) {
                    return -1;
                } else {
                    break;
                }
            }

            if (localReadBytes == localLength) {
                index += localLength;
                length -= localLength;
                readBytes += localLength;
                i++;
            } else {
                index += localReadBytes;
                length -= localReadBytes;
                readBytes += localReadBytes;
            }
        } while (length > 0);

        return readBytes;
    }

    @Override
    public int setBytes(int index, ScatteringByteChannel in, int length) throws IOException {
        checkIndex(index, length);
        if (length == 0) {
            return in.read(FULL_BYTEBUFFER);
        }

        int i = findIndex(index);
        int readBytes = 0;
        do {
            Component c = components.get(i);
            ByteBuf s = c.buf;
            int adjustment = c.offset;
            int localLength = Math.min(length, s.writableBytes());
            int localReadBytes = s.setBytes(index - adjustment, in, localLength);

            if (localReadBytes == 0) {
                break;
            }

            if (localReadBytes < 0) {
                if (readBytes == 0) {
                    return -1;
                } else {
                    break;
                }
            }

            if (localReadBytes == localLength) {
                index += localLength;
                length -= localLength;
                readBytes += localLength;
                i++;
            } else {
                index += localReadBytes;
                length -= localReadBytes;
                readBytes += localReadBytes;
            }
        } while (length > 0);

        return readBytes;
    }

    @Override
    public AlternativeCompositeByteBuf setZero(int index, int length) {
        if (length == 0) {
            return this;
        }

        checkIndex(index, length);

        int nLong = length >>> 3;
        int nBytes = length & 7;
        for (int i = nLong; i > 0; i--) {
            setLong(index, 0);
            index += 8;
        }
        if (nBytes == 4) {
            setInt(index, 0);
        } else if (nBytes < 4) {
            for (int i = nBytes; i > 0; i--) {
                setByte(index, (byte) 0);
                index++;
            }
        } else {
            setInt(index, 0);
            index += 4;
            for (int i = nBytes - 4; i > 0; i--) {
                setByte(index, (byte) 0);
                index++;
            }
        }
        return this;
    }

    /**
     * Throws an {@link IndexOutOfBoundsException} if the current
     * {@linkplain #readableBytes() readable bytes} of this buffer is less than
     * the specified value.
     */
    private final void checkReadableBytes(int minimumReadableBytes) {
        if (minimumReadableBytes < 0) {
            throw new IllegalArgumentException(
                    "minimumReadableBytes: " + minimumReadableBytes + " (expected: >= 0)");
        }
        if (readerIndex > writerIndex - minimumReadableBytes) {
            throw new IndexOutOfBoundsException(
                    String.format("readerIndex(%d) + length(%d) exceeds writerIndex(%d): %s", readerIndex,
                            minimumReadableBytes, writerIndex, this));
        }
    }

    @Override
    public boolean readBoolean() {
        return readByte() != 0;
    }

    @Override
    public byte readByte() {
        checkReadableBytes(1);
        int i = readerIndex;
        byte b = getByte(i);
        readerIndex = i + 1;
        return b;
    }

    @Override
    public short readUnsignedByte() {
        return (short) (readByte() & 0xFF);
    }

    @Override
    public short readShort() {
        checkReadableBytes(2);
        short v = getShort(readerIndex);
        readerIndex += 2;
        return v;
    }

    @Override
    public int readUnsignedShort() {
        return readShort() & 0xFFFF;
    }

    @Override
    public int readMedium() {
        int value = readUnsignedMedium();
        if ((value & 0x800000) != 0) {
            value |= 0xff000000;
        }
        return value;
    }

    @Override
    public int readUnsignedMedium() {
        checkReadableBytes(3);
        int v = getUnsignedMedium(readerIndex);
        readerIndex += 3;
        return v;
    }

    @Override
    public int readInt() {
        checkReadableBytes(4);
        int v = getInt(readerIndex);
        readerIndex += 4;
        return v;
    }

    @Override
    public long readUnsignedInt() {
        return readInt() & 0xFFFFFFFFL;
    }

    @Override
    public long readLong() {
        checkReadableBytes(8);
        long v = getLong(readerIndex);
        readerIndex += 8;
        return v;
    }

    @Override
    public char readChar() {
        return (char) readShort();
    }

    @Override
    public float readFloat() {
        return Float.intBitsToFloat(readInt());
    }

    @Override
    public double readDouble() {
        return Double.longBitsToDouble(readLong());
    }

    @Override
    public ByteBuf readBytes(int length) {
        checkReadableBytes(length);
        if (length == 0) {
            return Unpooled.EMPTY_BUFFER;
        }

        // Use an unpooled heap buffer because there's no way to mandate a user
        // to free the returned buffer.
        ByteBuf buf = Unpooled.buffer(length, maxCapacity());
        buf.writeBytes(this, readerIndex, length);
        readerIndex += length;
        return buf;
    }

    @Override
    public ByteBuf readSlice(int length) {
        ByteBuf slice = slice(readerIndex, length);
        readerIndex += length;
        return slice;
    }

    @Override
    public ByteBuf readBytes(ByteBuf dst) {
        readBytes(dst, dst.writableBytes());
        return this;
    }

    @Override
    public ByteBuf readBytes(ByteBuf dst, int length) {
        if (length > dst.writableBytes()) {
            throw new IndexOutOfBoundsException(String.format(
                    "length(%d) exceeds dst.writableBytes(%d) where dst is: %s", length, dst.writableBytes(), dst));
        }
        readBytes(dst, dst.writerIndex(), length);
        dst.writerIndex(dst.writerIndex() + length);
        return this;
    }

    @Override
    public ByteBuf readBytes(ByteBuf dst, int dstIndex, int length) {
        checkReadableBytes(length);
        getBytes(readerIndex, dst, dstIndex, length);
        readerIndex += length;
        return this;
    }

    @Override
    public ByteBuf readBytes(byte[] dst) {
        readBytes(dst, 0, dst.length);
        return this;
    }

    @Override
    public ByteBuf readBytes(byte[] dst, int dstIndex, int length) {
        checkReadableBytes(length);
        getBytes(readerIndex, dst, dstIndex, length);
        readerIndex += length;
        return this;
    }

    @Override
    public ByteBuf readBytes(ByteBuffer dst) {
        int length = dst.remaining();
        checkReadableBytes(length);
        getBytes(readerIndex, dst);
        readerIndex += length;
        return this;
    }

    @Override
    public ByteBuf readBytes(OutputStream out, int length) throws IOException {
        checkReadableBytes(length);
        getBytes(readerIndex, out, length);
        readerIndex += length;
        return this;
    }

    @Override
    public int readBytes(GatheringByteChannel out, int length) throws IOException {
        checkReadableBytes(length);
        int readBytes = getBytes(readerIndex, out, length);
        readerIndex += readBytes;
        return readBytes;
    }

    @Override
    public ByteBuf skipBytes(int length) {
        checkReadableBytes(length);

        int newReaderIndex = readerIndex + length;
        if (newReaderIndex > writerIndex) {
            throw new IndexOutOfBoundsException(
                    String.format("length: %d (expected: readerIndex(%d) + length <= writerIndex(%d))", length,
                            readerIndex, writerIndex));
        }
        readerIndex = newReaderIndex;
        return this;
    }

    private void setComponentWriterIndex(int writerIndex) {
        if (this.writerIndex == writerIndex) {
            //nothing to do
            return;
        }
        int index = findIndex(writerIndex);
        if (index < 0) {
            //no component found, make sure we can write, thus adding a compontent. TODO: check fillbuffer
            ensureWritable(writerIndex);
            index = findIndex(writerIndex);
        }
        int to = findIndex(this.writerIndex);
        Component c = components.get(index);
        int relWriterIndex = writerIndex - c.offset;
        c.buf.writerIndex(relWriterIndex);

        if (this.writerIndex < writerIndex) {
            //new writer index is larger than the old one
            // assuming full buffers
            for (int i = index - 1; i > to; i--) {
                c = components.get(i);
                c.buf.writerIndex(c.buf.capacity());
            }
            this.writerIndex = writerIndex;

        } else {
            //we go back in the buffer
            for (int i = index + 1; i < to; i++) {
                components.get(i).buf.writerIndex(0);
            }
            this.writerIndex = writerIndex;
        }
    }

    private void increaseComponentWriterIndex(final int increase) {
        int maxIncrease = 0;
        int currentIncrease = increase;
        int index = findIndex(writerIndex);
        while (maxIncrease < increase) {
            Component c = components.get(index);
            int writable = c.buf.writableBytes();
            writable = Math.min(writable, currentIncrease);
            c.buf.writerIndex(c.buf.writerIndex() + writable);
            currentIncrease -= writable;
            maxIncrease += writable;
            index++;
        }
        writerIndex += increase;
    }

    @Override
    public ByteBuf writeBoolean(boolean value) {
        writeByte(value ? 1 : 0);
        return this;
    }

    @Override
    public ByteBuf writeByte(int value) {
        ensureWritable0(1, true);
        setByte(writerIndex, value);
        increaseComponentWriterIndex(1);
        return this;
    }

    @Override
    public ByteBuf writeShort(int value) {
        ensureWritable0(2, true);
        setShort(writerIndex, value);
        increaseComponentWriterIndex(2);
        return this;
    }

    @Override
    public ByteBuf writeMedium(int value) {
        ensureWritable0(3, true);
        setMedium(writerIndex, value);
        increaseComponentWriterIndex(3);
        return this;
    }

    @Override
    public ByteBuf writeInt(int value) {
        ensureWritable0(4, true);
        setInt(writerIndex, value);
        increaseComponentWriterIndex(4);
        return this;
    }

    @Override
    public ByteBuf writeLong(long value) {
        ensureWritable0(8, true);
        setLong(writerIndex, value);
        increaseComponentWriterIndex(8);
        return this;
    }

    @Override
    public ByteBuf writeChar(int value) {
        writeShort(value);
        return this;
    }

    @Override
    public ByteBuf writeFloat(float value) {
        writeInt(Float.floatToRawIntBits(value));
        return this;
    }

    @Override
    public ByteBuf writeDouble(double value) {
        writeLong(Double.doubleToRawLongBits(value));
        return this;
    }

    @Override
    public ByteBuf writeBytes(ByteBuf src) {
        writeBytes(src, src.readableBytes());
        return this;
    }

    @Override
    public ByteBuf writeBytes(ByteBuf src, int length) {
        if (length > src.readableBytes()) {
            throw new IndexOutOfBoundsException(String.format(
                    "length(%d) exceeds src.readableBytes(%d) where src is: %s", length, src.readableBytes(), src));
        }
        writeBytes(src, src.readerIndex(), length);
        src.readerIndex(src.readerIndex() + length);
        return this;
    }

    @Override
    public ByteBuf writeBytes(ByteBuf src, int srcIndex, int length) {
        ensureWritable0(length, true);
        setBytes(writerIndex, src, srcIndex, length);
        increaseComponentWriterIndex(length);
        return this;
    }

    @Override
    public ByteBuf writeBytes(byte[] src) {
        writeBytes(src, 0, src.length);
        return this;
    }

    @Override
    public ByteBuf writeBytes(byte[] src, int srcIndex, int length) {
        ensureWritable0(length, true);
        setBytes(writerIndex, src, srcIndex, length);
        increaseComponentWriterIndex(length);
        return this;
    }

    @Override
    public ByteBuf writeBytes(ByteBuffer src) {
        int length = src.remaining();
        ensureWritable0(length, true);
        setBytes(writerIndex, src);
        increaseComponentWriterIndex(length);
        return this;
    }

    @Override
    public int writeBytes(InputStream in, int length) throws IOException {
        ensureWritable0(length, true);
        int writtenBytes = setBytes(writerIndex, in, length);
        if (writtenBytes > 0) {
            increaseComponentWriterIndex(writtenBytes);
        }
        return writtenBytes;
    }

    @Override
    public int writeBytes(ScatteringByteChannel in, int length) throws IOException {
        ensureWritable0(length, true);
        int writtenBytes = setBytes(writerIndex, in, length);
        if (writtenBytes > 0) {
            increaseComponentWriterIndex(writtenBytes);
        }
        return writtenBytes;
    }

    @Override
    public ByteBuf writeZero(int length) {
        if (length == 0) {
            return this;
        }

        ensureWritable0(length, true);
        checkIndex(writerIndex, length);

        int nLong = length >>> 3;
        int nBytes = length & 7;
        for (int i = nLong; i > 0; i--) {
            writeLong(0);
        }
        if (nBytes == 4) {
            writeInt(0);
        } else if (nBytes < 4) {
            for (int i = nBytes; i > 0; i--) {
                writeByte((byte) 0);
            }
        } else {
            writeInt(0);
            for (int i = nBytes - 4; i > 0; i--) {
                writeByte((byte) 0);
            }
        }
        return this;
    }

    @Override
    public int indexOf(int fromIndex, int toIndex, byte value) {
        return ByteBufUtil.indexOf(this, fromIndex, toIndex, value);
    }

    @Override
    public int bytesBefore(byte value) {
        return bytesBefore(readerIndex(), readableBytes(), value);
    }

    @Override
    public int bytesBefore(int length, byte value) {
        checkReadableBytes(length);
        return bytesBefore(readerIndex(), length, value);
    }

    @Override
    public int bytesBefore(int index, int length, byte value) {
        int endIndex = indexOf(index, index + length, value);
        if (endIndex < 0) {
            return -1;
        }
        return endIndex - index;
    }

    @Override
    public int forEachByte(ByteBufProcessor processor) {
        int index = readerIndex;
        int length = writerIndex - index;
        return forEachByteAsc0(index, length, processor);
    }

    @Override
    public int forEachByte(int index, int length, ByteBufProcessor processor) {
        checkIndex(index, length);
        return forEachByteAsc0(index, length, processor);
    }

    private int forEachByteAsc0(int index, int length, ByteBufProcessor processor) {
        if (processor == null) {
            throw new NullPointerException("processor");
        }

        if (length == 0) {
            return -1;
        }

        final int endIndex = index + length;
        int i = index;
        try {
            do {
                if (processor.process(getByte(i))) {
                    i++;
                } else {
                    return i;
                }
            } while (i < endIndex);
        } catch (Exception e) {
            PlatformDependent.throwException(e);
        }

        return -1;
    }

    @Override
    public int forEachByteDesc(ByteBufProcessor processor) {
        int index = readerIndex;
        int length = writerIndex - index;
        return forEachByteDesc0(index, length, processor);
    }

    @Override
    public int forEachByteDesc(int index, int length, ByteBufProcessor processor) {
        checkIndex(index, length);
        return forEachByteDesc0(index, length, processor);
    }

    private int forEachByteDesc0(int index, int length, ByteBufProcessor processor) {
        if (processor == null) {
            throw new NullPointerException("processor");
        }

        if (length == 0) {
            return -1;
        }

        int i = index + length - 1;
        try {
            do {
                if (processor.process(getByte(i))) {
                    i--;
                } else {
                    return i;
                }
            } while (i >= index);
        } catch (Exception e) {
            PlatformDependent.throwException(e);
        }

        return -1;
    }

    @Override
    public ByteBuf copy() {
        return copy(readerIndex, readableBytes());
    }

    @Override
    public ByteBuf copy(int index, int length) {
        checkIndex(index, length);
        ByteBuf dst = Unpooled.buffer(length);
        if (length != 0) {
            copyTo(index, length, findIndex(index), dst);
        }
        return dst;
    }

    private void copyTo(int index, int length, int componentId, ByteBuf dst) {
        int dstIndex = 0;
        int i = componentId;

        while (length > 0) {
            Component c = components.get(i);
            ByteBuf s = c.buf;
            int adjustment = c.offset;
            int localLength = Math.min(length, s.readableBytes() - (index - adjustment));
            s.getBytes(index - adjustment, dst, dstIndex, localLength);
            index += localLength;
            dstIndex += localLength;
            length -= localLength;
            i++;
        }

        dst.writerIndex(dst.capacity());
    }

    @Override
    public ByteBuf slice() {
        return slice(readerIndex, readableBytes());
    }

    @Override
    public ByteBuf slice(int index, int length) {
        if (length == 0) {
            return Unpooled.EMPTY_BUFFER;
        }

        return new SlicedByteBuf(this, index, length);
    }

    @Override
    public ByteBuf duplicate() {
        return new DuplicatedByteBuf(this);
    }

    @Override
    public int nioBufferCount() {
        if (components.size() == 1) {
            return components.get(0).buf.nioBufferCount();
        } else {
            int count = 0;
            int componentsCount = components.size();
            // noinspection ForLoopReplaceableByForEach
            for (int i = 0; i < componentsCount; i++) {
                Component c = components.get(i);
                count += c.buf.nioBufferCount();
            }
            return count;
        }
    }

    @Override
    public ByteBuffer nioBuffer() {
        return nioBuffer(readerIndex, readableBytes());
    }

    @Override
    public ByteBuffer nioBuffer(int index, int length) {
        if (components.size() == 1) {
            ByteBuf buf = components.get(0).buf;
            if (buf.nioBufferCount() == 1) {
                return components.get(0).buf.nioBuffer(index, length);
            }
        }
        ByteBuffer merged = ByteBuffer.allocate(length).order(order());
        ByteBuffer[] buffers = nioBuffers(index, length);

        // noinspection ForLoopReplaceableByForEach
        for (int i = 0; i < buffers.length; i++) {
            merged.put(buffers[i]);
        }

        merged.flip();
        return merged;
    }

    @Override
    public ByteBuffer internalNioBuffer(int index, int length) {
        if (components.size() == 1) {
            return components.get(0).buf.internalNioBuffer(index, length);
        }
        throw new UnsupportedOperationException();
    }

    @Override
    public ByteBuffer[] nioBuffers() {
        return nioBuffers(readerIndex, readableBytes());
    }

    @Override
    public ByteBuffer[] nioBuffers(int index, int length) {
        checkIndex(index, length);
        if (length == 0) {
            return EmptyArrays.EMPTY_BYTE_BUFFERS;
        }

        List<ByteBuffer> buffers = new ArrayList<ByteBuffer>(components.size());
        int i = findIndex(index);
        while (length > 0) {
            Component c = components.get(i);
            ByteBuf s = c.buf;
            int adjustment = c.offset;
            int localLength = Math.min(length, s.readableBytes() - (index - adjustment));
            switch (s.nioBufferCount()) {
            case 0:
                throw new UnsupportedOperationException();
            case 1:
                buffers.add(s.nioBuffer(index - adjustment, localLength));
                break;
            default:
                Collections.addAll(buffers, s.nioBuffers(index - adjustment, localLength));
            }

            index += localLength;
            length -= localLength;
            i++;
        }

        return buffers.toArray(new ByteBuffer[buffers.size()]);
    }

    @Override
    public boolean hasArray() {
        if (components.size() == 1) {
            return components.get(0).buf.hasArray();
        }
        return false;
    }

    @Override
    public byte[] array() {
        if (components.size() == 1) {
            return components.get(0).buf.array();
        }
        throw new UnsupportedOperationException();
    }

    @Override
    public int arrayOffset() {
        if (components.size() == 1) {
            return components.get(0).buf.arrayOffset();
        }
        throw new UnsupportedOperationException();
    }

    @Override
    public boolean hasMemoryAddress() {
        if (components.size() == 1) {
            return components.get(0).buf.hasMemoryAddress();
        }
        return false;
    }

    @Override
    public long memoryAddress() {
        if (components.size() == 1) {
            return components.get(0).buf.memoryAddress();
        }
        throw new UnsupportedOperationException();
    }

    @Override
    public String toString(Charset charset) {
        return toString(readerIndex, readableBytes(), charset);
    }

    @Override
    public String toString(int index, int length, Charset charset) {
        if (length == 0) {
            return "";
        }

        ByteBuffer nioBuffer;
        if (nioBufferCount() == 1) {
            nioBuffer = nioBuffer(index, length);
        } else {
            nioBuffer = ByteBuffer.allocate(length);
            getBytes(index, nioBuffer);
            nioBuffer.flip();
        }

        return decodeString(nioBuffer, charset);
    }

    @Override
    public int hashCode() {
        return ByteBufUtil.hashCode(this);
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o instanceof ByteBuf) {
            return ByteBufUtil.equals(this, (ByteBuf) o);
        }
        return false;
    }

    @Override
    public int compareTo(ByteBuf that) {
        return ByteBufUtil.compare(this, that);
    }

    @Override
    public String toString() {
        StringBuilder buf = new StringBuilder();
        buf.append(StringUtil.simpleClassName(this));
        buf.append("(ridx: ");
        buf.append(readerIndex);
        buf.append(", widx: ");
        buf.append(writerIndex);
        buf.append(", cap: ");
        buf.append(capacity());
        buf.append(", comp: ");
        buf.append(components.size());
        buf.append(')');
        return buf.toString();
    }

    public List<ByteBuf> decompose(int offset, int length) {
        checkIndex(offset, length);
        if (length == 0) {
            return Collections.emptyList();
        }

        int componentId = findIndex(offset);
        List<ByteBuf> slice = new ArrayList<ByteBuf>(components.size());

        // The first component
        Component firstC = components.get(componentId);
        ByteBuf first = firstC.buf.duplicate();
        first.readerIndex(offset - firstC.offset);

        ByteBuf buf = first;
        int bytesToSlice = length;
        do {
            int readableBytes = buf.readableBytes();
            if (bytesToSlice <= readableBytes) {
                // Last component
                buf.writerIndex(buf.readerIndex() + bytesToSlice);
                slice.add(buf);
                break;
            } else {
                // Not the last component
                slice.add(buf);
                bytesToSlice -= readableBytes;
                componentId++;

                // Fetch the next component.
                buf = components.get(componentId).buf.duplicate();
            }
        } while (bytesToSlice > 0);

        // Slice all components because only readable bytes are interesting.
        for (int i = 0; i < slice.size(); i++) {
            slice.set(i, slice.get(i).slice());
        }

        return slice;
    }

    private static String decodeString(ByteBuffer src, Charset charset) {
        final CharsetDecoder decoder = CharsetUtil.getDecoder(charset);
        final CharBuffer dst = CharBuffer.allocate((int) ((double) src.remaining() * decoder.maxCharsPerByte()));
        try {
            CoderResult cr = decoder.decode(src, dst, true);
            if (!cr.isUnderflow()) {
                cr.throwException();
            }
            cr = decoder.flush(dst);
            if (!cr.isUnderflow()) {
                cr.throwException();
            }
        } catch (CharacterCodingException x) {
            throw new IllegalStateException(x);
        }
        return dst.flip().toString();
    }

    public static AlternativeCompositeByteBuf compBuffer(ByteBufAllocator alloc, ByteBuf... buffers) {
        return new AlternativeCompositeByteBuf(alloc, buffers);
    }

    private static class UnpooledHeapByteBufAlloc extends AbstractByteBufAllocator {

        private final static UnpooledByteBufAllocator UNPOOLED_HEAP_ORIG = new UnpooledByteBufAllocator(false);

        @Override
        protected ByteBuf newHeapBuffer(int initialCapacity, int maxCapacity) {
            return UNPOOLED_HEAP_ORIG.heapBuffer(initialCapacity, maxCapacity);
        }

        @Override
        protected ByteBuf newDirectBuffer(int initialCapacity, int maxCapacity) {
            return UNPOOLED_HEAP_ORIG.directBuffer(initialCapacity, maxCapacity);
        }

        @Override
        public boolean isDirectBufferPooled() {
            return false;
        }

        @Override
        public ByteBuf ioBuffer() {
            return UNPOOLED_HEAP_ORIG.heapBuffer();
        }

        @Override
        public ByteBuf ioBuffer(int initialCapacity) {
            return UNPOOLED_HEAP_ORIG.heapBuffer(initialCapacity);
        }

        @Override
        public ByteBuf ioBuffer(int initialCapacity, int maxCapacity) {
            return UNPOOLED_HEAP_ORIG.heapBuffer(initialCapacity, maxCapacity);
        }
    }

    private static class PooledHeapByteBufAlloc extends AbstractByteBufAllocator {

        private final static PooledByteBufAllocator POOLED_HEAP_ORIG = new PooledByteBufAllocator(false);

        @Override
        protected ByteBuf newHeapBuffer(int initialCapacity, int maxCapacity) {
            return POOLED_HEAP_ORIG.heapBuffer(initialCapacity, maxCapacity);
        }

        @Override
        protected ByteBuf newDirectBuffer(int initialCapacity, int maxCapacity) {
            return POOLED_HEAP_ORIG.directBuffer(initialCapacity, maxCapacity);
        }

        @Override
        public boolean isDirectBufferPooled() {
            return true;
        }

        @Override
        public ByteBuf ioBuffer() {
            return POOLED_HEAP_ORIG.heapBuffer();
        }

        @Override
        public ByteBuf ioBuffer(int initialCapacity) {
            return POOLED_HEAP_ORIG.heapBuffer(initialCapacity);
        }

        @Override
        public ByteBuf ioBuffer(int initialCapacity, int maxCapacity) {
            return POOLED_HEAP_ORIG.heapBuffer(initialCapacity, maxCapacity);
        }
    }
}