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

package io.netty.buffer;

import io.netty.util.internal.CleanableDirectBuffer;
import java.util.concurrent.atomic.AtomicReference;
import io.netty.util.internal.PlatformDependent;
import io.netty.util.internal.StringUtil;
import java.util.Iterator;
import java.nio.ByteBuffer;
import java.util.Collections;
import java.util.ArrayList;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.LongAdder;
import java.util.List;

abstract class PoolArena<T> implements PoolArenaMetric
{
    private static final boolean HAS_UNSAFE;
    final PooledByteBufAllocator parent;
    final PoolSubpage<T>[] smallSubpagePools;
    private final PoolChunkList<T> q050;
    private final PoolChunkList<T> q025;
    private final PoolChunkList<T> q000;
    private final PoolChunkList<T> qInit;
    private final PoolChunkList<T> q075;
    private final PoolChunkList<T> q100;
    private final List<PoolChunkListMetric> chunkListMetrics;
    private long allocationsNormal;
    private final LongAdder allocationsSmall;
    private final LongAdder allocationsHuge;
    private final LongAdder activeBytesHuge;
    private long deallocationsSmall;
    private long deallocationsNormal;
    private long pooledChunkAllocations;
    private long pooledChunkDeallocations;
    private final LongAdder deallocationsHuge;
    final AtomicInteger numThreadCaches;
    private final ReentrantLock lock;
    final SizeClasses sizeClass;
    
    protected PoolArena(final PooledByteBufAllocator parent, final SizeClasses sizeClass) {
        this.allocationsSmall = new LongAdder();
        this.allocationsHuge = new LongAdder();
        this.activeBytesHuge = new LongAdder();
        this.deallocationsHuge = new LongAdder();
        this.numThreadCaches = new AtomicInteger();
        this.lock = new ReentrantLock();
        assert null != sizeClass;
        this.parent = parent;
        this.sizeClass = sizeClass;
        this.smallSubpagePools = this.newSubpagePoolArray(sizeClass.nSubpages);
        for (int i = 0; i < this.smallSubpagePools.length; ++i) {
            this.smallSubpagePools[i] = this.newSubpagePoolHead(i);
        }
        this.q100 = new PoolChunkList<T>(this, null, 100, Integer.MAX_VALUE, sizeClass.chunkSize);
        this.q075 = new PoolChunkList<T>(this, this.q100, 75, 100, sizeClass.chunkSize);
        this.q050 = new PoolChunkList<T>(this, this.q100, 50, 100, sizeClass.chunkSize);
        this.q025 = new PoolChunkList<T>(this, this.q050, 25, 75, sizeClass.chunkSize);
        this.q000 = new PoolChunkList<T>(this, this.q025, 1, 50, sizeClass.chunkSize);
        this.qInit = new PoolChunkList<T>(this, this.q000, Integer.MIN_VALUE, 25, sizeClass.chunkSize);
        this.q100.prevList(this.q075);
        this.q075.prevList(this.q050);
        this.q050.prevList(this.q025);
        this.q025.prevList(this.q000);
        this.q000.prevList(null);
        this.qInit.prevList(this.qInit);
        final List<PoolChunkListMetric> metrics = new ArrayList<PoolChunkListMetric>(6);
        metrics.add(this.qInit);
        metrics.add(this.q000);
        metrics.add(this.q025);
        metrics.add(this.q050);
        metrics.add(this.q075);
        metrics.add(this.q100);
        this.chunkListMetrics = Collections.unmodifiableList((List<? extends PoolChunkListMetric>)metrics);
    }
    
    private PoolSubpage<T> newSubpagePoolHead(final int index) {
        final PoolSubpage<T> head = new PoolSubpage<T>(index);
        head.prev = head;
        return head.next = head;
    }
    
    private PoolSubpage<T>[] newSubpagePoolArray(final int size) {
        return new PoolSubpage[size];
    }
    
    abstract boolean isDirect();
    
    PooledByteBuf<T> allocate(final PoolThreadCache cache, final int reqCapacity, final int maxCapacity) {
        final PooledByteBuf<T> buf = this.newByteBuf(maxCapacity);
        this.allocate(cache, buf, reqCapacity);
        return buf;
    }
    
    private void allocate(final PoolThreadCache cache, final PooledByteBuf<T> buf, final int reqCapacity) {
        final int sizeIdx = this.sizeClass.size2SizeIdx(reqCapacity);
        if (sizeIdx <= this.sizeClass.smallMaxSizeIdx) {
            this.tcacheAllocateSmall(cache, buf, reqCapacity, sizeIdx);
        }
        else if (sizeIdx < this.sizeClass.nSizes) {
            this.tcacheAllocateNormal(cache, buf, reqCapacity, sizeIdx);
        }
        else {
            final int normCapacity = (this.sizeClass.directMemoryCacheAlignment > 0) ? this.sizeClass.normalizeSize(reqCapacity) : reqCapacity;
            this.allocateHuge(buf, normCapacity);
        }
    }
    
    private void tcacheAllocateSmall(final PoolThreadCache cache, final PooledByteBuf<T> buf, final int reqCapacity, final int sizeIdx) {
        if (cache.allocateSmall(this, buf, reqCapacity, sizeIdx)) {
            return;
        }
        final PoolSubpage<T> head = this.smallSubpagePools[sizeIdx];
        head.lock();
        boolean needsNormalAllocation;
        try {
            final PoolSubpage<T> s = head.next;
            needsNormalAllocation = (s == head);
            if (!needsNormalAllocation) {
                assert s.doNotDestroy && s.elemSize == this.sizeClass.sizeIdx2size(sizeIdx) : "doNotDestroy=" + s.doNotDestroy + ", elemSize=" + s.elemSize + ", sizeIdx=" + sizeIdx;
                final long handle = s.allocate();
                assert handle >= 0L;
                s.chunk.initBufWithSubpage(buf, null, handle, reqCapacity, cache, false);
            }
        }
        finally {
            head.unlock();
        }
        if (needsNormalAllocation) {
            this.lock();
            try {
                this.allocateNormal(buf, reqCapacity, sizeIdx, cache);
            }
            finally {
                this.unlock();
            }
        }
        this.incSmallAllocation();
    }
    
    private void tcacheAllocateNormal(final PoolThreadCache cache, final PooledByteBuf<T> buf, final int reqCapacity, final int sizeIdx) {
        if (cache.allocateNormal(this, buf, reqCapacity, sizeIdx)) {
            return;
        }
        this.lock();
        try {
            this.allocateNormal(buf, reqCapacity, sizeIdx, cache);
            ++this.allocationsNormal;
        }
        finally {
            this.unlock();
        }
    }
    
    private void allocateNormal(final PooledByteBuf<T> buf, final int reqCapacity, final int sizeIdx, final PoolThreadCache threadCache) {
        assert this.lock.isHeldByCurrentThread();
        if (this.q050.allocate(buf, reqCapacity, sizeIdx, threadCache) || this.q025.allocate(buf, reqCapacity, sizeIdx, threadCache) || this.q000.allocate(buf, reqCapacity, sizeIdx, threadCache) || this.qInit.allocate(buf, reqCapacity, sizeIdx, threadCache) || this.q075.allocate(buf, reqCapacity, sizeIdx, threadCache)) {
            return;
        }
        final PoolChunk<T> c = this.newChunk(this.sizeClass.pageSize, this.sizeClass.nPSizes, this.sizeClass.pageShifts, this.sizeClass.chunkSize);
        PooledByteBufAllocator.onAllocateChunk(c, true);
        final boolean success = c.allocate(buf, reqCapacity, sizeIdx, threadCache);
        assert success;
        this.qInit.add(c);
        ++this.pooledChunkAllocations;
    }
    
    private void incSmallAllocation() {
        this.allocationsSmall.increment();
    }
    
    private void allocateHuge(final PooledByteBuf<T> buf, final int reqCapacity) {
        final PoolChunk<T> chunk = this.newUnpooledChunk(reqCapacity);
        PooledByteBufAllocator.onAllocateChunk(chunk, false);
        this.activeBytesHuge.add(chunk.chunkSize());
        buf.initUnpooled(chunk, reqCapacity);
        this.allocationsHuge.increment();
    }
    
    void free(final PoolChunk<T> chunk, final ByteBuffer nioBuffer, final long handle, final int normCapacity, final PoolThreadCache cache) {
        chunk.decrementPinnedMemory(normCapacity);
        if (chunk.unpooled) {
            final int size = chunk.chunkSize();
            this.destroyChunk(chunk);
            this.activeBytesHuge.add(-size);
            this.deallocationsHuge.increment();
        }
        else {
            final SizeClass sizeClass = sizeClass(handle);
            if (cache != null && cache.add(this, chunk, nioBuffer, handle, normCapacity, sizeClass)) {
                return;
            }
            this.freeChunk(chunk, handle, normCapacity, sizeClass, nioBuffer, false);
        }
    }
    
    private static SizeClass sizeClass(final long handle) {
        return PoolChunk.isSubpage(handle) ? SizeClass.Small : SizeClass.Normal;
    }
    
    void freeChunk(final PoolChunk<T> chunk, final long handle, final int normCapacity, final SizeClass sizeClass, final ByteBuffer nioBuffer, final boolean finalizer) {
        this.lock();
        boolean destroyChunk;
        try {
            if (!finalizer) {
                switch (sizeClass) {
                    case Normal: {
                        ++this.deallocationsNormal;
                        break;
                    }
                    case Small: {
                        ++this.deallocationsSmall;
                        break;
                    }
                    default: {
                        throw new Error("Unexpected size class: " + sizeClass);
                    }
                }
            }
            destroyChunk = !chunk.parent.free(chunk, handle, normCapacity, nioBuffer);
            if (destroyChunk) {
                ++this.pooledChunkDeallocations;
            }
        }
        finally {
            this.unlock();
        }
        if (destroyChunk) {
            this.destroyChunk(chunk);
        }
    }
    
    void reallocate(final PooledByteBuf<T> buf, final int newCapacity) {
        assert newCapacity >= 0 && newCapacity <= buf.maxCapacity();
        final int oldCapacity;
        final PoolChunk<T> oldChunk;
        final ByteBuffer oldNioBuffer;
        final long oldHandle;
        final T oldMemory;
        final int oldOffset;
        final int oldMaxLength;
        final PoolThreadCache oldCache;
        synchronized (buf) {
            oldCapacity = buf.length;
            if (oldCapacity == newCapacity) {
                return;
            }
            oldChunk = buf.chunk;
            oldNioBuffer = buf.tmpNioBuf;
            oldHandle = buf.handle;
            oldMemory = buf.memory;
            oldOffset = buf.offset;
            oldMaxLength = buf.maxLength;
            oldCache = buf.cache;
            this.allocate(this.parent.threadCache(), buf, newCapacity);
        }
        int bytesToCopy;
        if (newCapacity > oldCapacity) {
            bytesToCopy = oldCapacity;
        }
        else {
            buf.trimIndicesToCapacity(newCapacity);
            bytesToCopy = newCapacity;
        }
        this.memoryCopy(oldMemory, oldOffset, buf, bytesToCopy);
        this.free(oldChunk, oldNioBuffer, oldHandle, oldMaxLength, oldCache);
    }
    
    @Override
    public int numThreadCaches() {
        return this.numThreadCaches.get();
    }
    
    @Override
    public int numTinySubpages() {
        return 0;
    }
    
    @Override
    public int numSmallSubpages() {
        return this.smallSubpagePools.length;
    }
    
    @Override
    public int numChunkLists() {
        return this.chunkListMetrics.size();
    }
    
    @Override
    public List<PoolSubpageMetric> tinySubpages() {
        return Collections.emptyList();
    }
    
    @Override
    public List<PoolSubpageMetric> smallSubpages() {
        return subPageMetricList(this.smallSubpagePools);
    }
    
    @Override
    public List<PoolChunkListMetric> chunkLists() {
        return this.chunkListMetrics;
    }
    
    private static List<PoolSubpageMetric> subPageMetricList(final PoolSubpage<?>[] pages) {
        final List<PoolSubpageMetric> metrics = new ArrayList<PoolSubpageMetric>();
        for (final PoolSubpage<?> head : pages) {
            if (head.next != head) {
                PoolSubpage<?> s = head.next;
                do {
                    metrics.add(s);
                    s = s.next;
                } while (s != head);
            }
        }
        return metrics;
    }
    
    @Override
    public long numAllocations() {
        this.lock();
        long allocsNormal;
        try {
            allocsNormal = this.allocationsNormal;
        }
        finally {
            this.unlock();
        }
        return this.allocationsSmall.sum() + allocsNormal + this.allocationsHuge.sum();
    }
    
    @Override
    public long numTinyAllocations() {
        return 0L;
    }
    
    @Override
    public long numSmallAllocations() {
        return this.allocationsSmall.sum();
    }
    
    @Override
    public long numNormalAllocations() {
        this.lock();
        try {
            return this.allocationsNormal;
        }
        finally {
            this.unlock();
        }
    }
    
    @Override
    public long numChunkAllocations() {
        this.lock();
        try {
            return this.pooledChunkAllocations;
        }
        finally {
            this.unlock();
        }
    }
    
    @Override
    public long numDeallocations() {
        this.lock();
        long deallocs;
        try {
            deallocs = this.deallocationsSmall + this.deallocationsNormal;
        }
        finally {
            this.unlock();
        }
        return deallocs + this.deallocationsHuge.sum();
    }
    
    @Override
    public long numTinyDeallocations() {
        return 0L;
    }
    
    @Override
    public long numSmallDeallocations() {
        this.lock();
        try {
            return this.deallocationsSmall;
        }
        finally {
            this.unlock();
        }
    }
    
    @Override
    public long numNormalDeallocations() {
        this.lock();
        try {
            return this.deallocationsNormal;
        }
        finally {
            this.unlock();
        }
    }
    
    @Override
    public long numChunkDeallocations() {
        this.lock();
        try {
            return this.pooledChunkDeallocations;
        }
        finally {
            this.unlock();
        }
    }
    
    @Override
    public long numHugeAllocations() {
        return this.allocationsHuge.sum();
    }
    
    @Override
    public long numHugeDeallocations() {
        return this.deallocationsHuge.sum();
    }
    
    @Override
    public long numActiveAllocations() {
        long val = this.allocationsSmall.sum() + this.allocationsHuge.sum() - this.deallocationsHuge.sum();
        this.lock();
        try {
            val += this.allocationsNormal - (this.deallocationsSmall + this.deallocationsNormal);
        }
        finally {
            this.unlock();
        }
        return Math.max(val, 0L);
    }
    
    @Override
    public long numActiveTinyAllocations() {
        return 0L;
    }
    
    @Override
    public long numActiveSmallAllocations() {
        return Math.max(this.numSmallAllocations() - this.numSmallDeallocations(), 0L);
    }
    
    @Override
    public long numActiveNormalAllocations() {
        this.lock();
        long val;
        try {
            val = this.allocationsNormal - this.deallocationsNormal;
        }
        finally {
            this.unlock();
        }
        return Math.max(val, 0L);
    }
    
    @Override
    public long numActiveChunks() {
        this.lock();
        long val;
        try {
            val = this.pooledChunkAllocations - this.pooledChunkDeallocations;
        }
        finally {
            this.unlock();
        }
        return Math.max(val, 0L);
    }
    
    @Override
    public long numActiveHugeAllocations() {
        return Math.max(this.numHugeAllocations() - this.numHugeDeallocations(), 0L);
    }
    
    @Override
    public long numActiveBytes() {
        long val = this.activeBytesHuge.sum();
        this.lock();
        try {
            for (int i = 0; i < this.chunkListMetrics.size(); ++i) {
                for (final PoolChunkMetric m : this.chunkListMetrics.get(i)) {
                    val += m.chunkSize();
                }
            }
        }
        finally {
            this.unlock();
        }
        return Math.max(0L, val);
    }
    
    public long numPinnedBytes() {
        long val = this.activeBytesHuge.sum();
        for (int i = 0; i < this.chunkListMetrics.size(); ++i) {
            for (final PoolChunkMetric m : this.chunkListMetrics.get(i)) {
                val += ((PoolChunk)m).pinnedBytes();
            }
        }
        return Math.max(0L, val);
    }
    
    protected abstract PoolChunk<T> newChunk(final int p0, final int p1, final int p2, final int p3);
    
    protected abstract PoolChunk<T> newUnpooledChunk(final int p0);
    
    protected abstract PooledByteBuf<T> newByteBuf(final int p0);
    
    protected abstract void memoryCopy(final T p0, final int p1, final PooledByteBuf<T> p2, final int p3);
    
    protected abstract void destroyChunk(final PoolChunk<T> p0);
    
    @Override
    public String toString() {
        this.lock();
        try {
            final StringBuilder buf = new StringBuilder().append("Chunk(s) at 0~25%:").append(StringUtil.NEWLINE).append(this.qInit).append(StringUtil.NEWLINE).append("Chunk(s) at 0~50%:").append(StringUtil.NEWLINE).append(this.q000).append(StringUtil.NEWLINE).append("Chunk(s) at 25~75%:").append(StringUtil.NEWLINE).append(this.q025).append(StringUtil.NEWLINE).append("Chunk(s) at 50~100%:").append(StringUtil.NEWLINE).append(this.q050).append(StringUtil.NEWLINE).append("Chunk(s) at 75~100%:").append(StringUtil.NEWLINE).append(this.q075).append(StringUtil.NEWLINE).append("Chunk(s) at 100%:").append(StringUtil.NEWLINE).append(this.q100).append(StringUtil.NEWLINE).append("small subpages:");
            appendPoolSubPages(buf, this.smallSubpagePools);
            buf.append(StringUtil.NEWLINE);
            return buf.toString();
        }
        finally {
            this.unlock();
        }
    }
    
    private static void appendPoolSubPages(final StringBuilder buf, final PoolSubpage<?>[] subpages) {
        for (int i = 0; i < subpages.length; ++i) {
            final PoolSubpage<?> head = subpages[i];
            if (head.next != head) {
                if (head.next != null) {
                    buf.append(StringUtil.NEWLINE).append(i).append(": ");
                    PoolSubpage<?> s = head.next;
                    while (s != null) {
                        buf.append(s);
                        s = s.next;
                        if (s == head) {
                            break;
                        }
                    }
                }
            }
        }
    }
    
    @Override
    protected final void finalize() throws Throwable {
        try {
            super.finalize();
        }
        finally {
            destroyPoolSubPages(this.smallSubpagePools);
            this.destroyPoolChunkLists(this.qInit, this.q000, this.q025, this.q050, this.q075, this.q100);
        }
    }
    
    private static void destroyPoolSubPages(final PoolSubpage<?>[] pages) {
        for (final PoolSubpage<?> page : pages) {
            page.destroy();
        }
    }
    
    private void destroyPoolChunkLists(final PoolChunkList<T>... chunkLists) {
        for (final PoolChunkList<T> chunkList : chunkLists) {
            chunkList.destroy(this);
        }
    }
    
    void lock() {
        this.lock.lock();
    }
    
    void unlock() {
        this.lock.unlock();
    }
    
    @Override
    public int sizeIdx2size(final int sizeIdx) {
        return this.sizeClass.sizeIdx2size(sizeIdx);
    }
    
    @Override
    public int sizeIdx2sizeCompute(final int sizeIdx) {
        return this.sizeClass.sizeIdx2sizeCompute(sizeIdx);
    }
    
    @Override
    public long pageIdx2size(final int pageIdx) {
        return this.sizeClass.pageIdx2size(pageIdx);
    }
    
    @Override
    public long pageIdx2sizeCompute(final int pageIdx) {
        return this.sizeClass.pageIdx2sizeCompute(pageIdx);
    }
    
    @Override
    public int size2SizeIdx(final int size) {
        return this.sizeClass.size2SizeIdx(size);
    }
    
    @Override
    public int pages2pageIdx(final int pages) {
        return this.sizeClass.pages2pageIdx(pages);
    }
    
    @Override
    public int pages2pageIdxFloor(final int pages) {
        return this.sizeClass.pages2pageIdxFloor(pages);
    }
    
    @Override
    public int normalizeSize(final int size) {
        return this.sizeClass.normalizeSize(size);
    }
    
    static {
        HAS_UNSAFE = PlatformDependent.hasUnsafe();
    }
    
    enum SizeClass
    {
        Small, 
        Normal;
    }
    
    static final class HeapArena extends PoolArena<byte[]>
    {
        private final AtomicReference<PoolChunk<byte[]>> lastDestroyedChunk;
        
        HeapArena(final PooledByteBufAllocator parent, final SizeClasses sizeClass) {
            super(parent, sizeClass);
            this.lastDestroyedChunk = new AtomicReference<PoolChunk<byte[]>>();
        }
        
        private static byte[] newByteArray(final int size) {
            return PlatformDependent.allocateUninitializedArray(size);
        }
        
        @Override
        boolean isDirect() {
            return false;
        }
        
        @Override
        protected PoolChunk<byte[]> newChunk(final int pageSize, final int maxPageIdx, final int pageShifts, final int chunkSize) {
            final PoolChunk<byte[]> chunk = this.lastDestroyedChunk.getAndSet(null);
            if (chunk == null) {
                return new PoolChunk<byte[]>(this, null, null, newByteArray(chunkSize), pageSize, pageShifts, chunkSize, maxPageIdx);
            }
            assert chunk.chunkSize == chunkSize && chunk.pageSize == pageSize && chunk.maxPageIdx == maxPageIdx && chunk.pageShifts == pageShifts;
            return chunk;
        }
        
        @Override
        protected PoolChunk<byte[]> newUnpooledChunk(final int capacity) {
            return new PoolChunk<byte[]>(this, null, null, newByteArray(capacity), capacity);
        }
        
        @Override
        protected void destroyChunk(final PoolChunk<byte[]> chunk) {
            PooledByteBufAllocator.onDeallocateChunk(chunk, !chunk.unpooled);
            if (!chunk.unpooled && this.lastDestroyedChunk.get() == null) {
                this.lastDestroyedChunk.set(chunk);
            }
        }
        
        @Override
        protected PooledByteBuf<byte[]> newByteBuf(final int maxCapacity) {
            return PoolArena.HAS_UNSAFE ? PooledUnsafeHeapByteBuf.newUnsafeInstance(maxCapacity) : PooledHeapByteBuf.newInstance(maxCapacity);
        }
        
        @Override
        protected void memoryCopy(final byte[] src, final int srcOffset, final PooledByteBuf<byte[]> dst, final int length) {
            if (length == 0) {
                return;
            }
            System.arraycopy(src, srcOffset, dst.memory, dst.offset, length);
        }
    }
    
    static final class DirectArena extends PoolArena<ByteBuffer>
    {
        DirectArena(final PooledByteBufAllocator parent, final SizeClasses sizeClass) {
            super(parent, sizeClass);
        }
        
        @Override
        boolean isDirect() {
            return true;
        }
        
        @Override
        protected PoolChunk<ByteBuffer> newChunk(final int pageSize, final int maxPageIdx, final int pageShifts, final int chunkSize) {
            if (this.sizeClass.directMemoryCacheAlignment == 0) {
                final CleanableDirectBuffer cleanableDirectBuffer = allocateDirect(chunkSize);
                final ByteBuffer memory = cleanableDirectBuffer.buffer();
                return new PoolChunk<ByteBuffer>(this, cleanableDirectBuffer, memory, memory, pageSize, pageShifts, chunkSize, maxPageIdx);
            }
            final CleanableDirectBuffer cleanableDirectBuffer = allocateDirect(chunkSize + this.sizeClass.directMemoryCacheAlignment);
            final ByteBuffer base = cleanableDirectBuffer.buffer();
            final ByteBuffer memory2 = PlatformDependent.alignDirectBuffer(base, this.sizeClass.directMemoryCacheAlignment);
            return new PoolChunk<ByteBuffer>(this, cleanableDirectBuffer, base, memory2, pageSize, pageShifts, chunkSize, maxPageIdx);
        }
        
        @Override
        protected PoolChunk<ByteBuffer> newUnpooledChunk(final int capacity) {
            if (this.sizeClass.directMemoryCacheAlignment == 0) {
                final CleanableDirectBuffer cleanableDirectBuffer = allocateDirect(capacity);
                final ByteBuffer memory = cleanableDirectBuffer.buffer();
                return new PoolChunk<ByteBuffer>(this, cleanableDirectBuffer, memory, memory, capacity);
            }
            final CleanableDirectBuffer cleanableDirectBuffer = allocateDirect(capacity + this.sizeClass.directMemoryCacheAlignment);
            final ByteBuffer base = cleanableDirectBuffer.buffer();
            final ByteBuffer memory2 = PlatformDependent.alignDirectBuffer(base, this.sizeClass.directMemoryCacheAlignment);
            return new PoolChunk<ByteBuffer>(this, cleanableDirectBuffer, base, memory2, capacity);
        }
        
        private static CleanableDirectBuffer allocateDirect(final int capacity) {
            return PlatformDependent.allocateDirect(capacity);
        }
        
        @Override
        protected void destroyChunk(final PoolChunk<ByteBuffer> chunk) {
            PooledByteBufAllocator.onDeallocateChunk(chunk, !chunk.unpooled);
            chunk.cleanable.clean();
        }
        
        @Override
        protected PooledByteBuf<ByteBuffer> newByteBuf(final int maxCapacity) {
            if (PoolArena.HAS_UNSAFE) {
                return PooledUnsafeDirectByteBuf.newInstance(maxCapacity);
            }
            return PooledDirectByteBuf.newInstance(maxCapacity);
        }
        
        @Override
        protected void memoryCopy(ByteBuffer src, final int srcOffset, final PooledByteBuf<ByteBuffer> dstBuf, final int length) {
            if (length == 0) {
                return;
            }
            if (PoolArena.HAS_UNSAFE) {
                PlatformDependent.copyMemory(PlatformDependent.directBufferAddress(src) + srcOffset, PlatformDependent.directBufferAddress(dstBuf.memory) + dstBuf.offset, length);
            }
            else {
                src = src.duplicate();
                final ByteBuffer dst = dstBuf.internalNioBuffer();
                src.position().limit(srcOffset + length);
                dst.position();
                dst.put(src);
            }
        }
    }
}
