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

package io.netty.buffer;

import io.netty.util.ByteProcessor;
import io.netty.util.CharsetUtil;
import java.nio.charset.Charset;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.ScatteringByteChannel;
import java.io.InputStream;
import java.nio.channels.FileChannel;
import java.nio.channels.GatheringByteChannel;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteOrder;
import io.netty.util.IllegalReferenceCountException;
import java.nio.ByteBuffer;
import java.util.function.IntSupplier;
import io.netty.util.concurrent.MpscIntQueue;
import io.netty.util.internal.RefCnt;
import java.util.concurrent.atomic.LongAdder;
import io.netty.util.internal.ObjectPool;
import io.netty.util.Recycler;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.locks.StampedLock;
import java.util.Arrays;
import io.netty.util.NettyRuntime;
import io.netty.util.internal.SystemPropertyUtil;
import io.netty.util.concurrent.FastThreadLocalThread;
import io.netty.util.internal.PlatformDependent;
import java.util.Queue;
import io.netty.util.internal.ThreadExecutorMap;
import io.netty.util.internal.ObjectUtil;
import io.netty.util.concurrent.FastThreadLocal;

final class AdaptivePoolingAllocator
{
    private static final int LOW_MEM_THRESHOLD = 536870912;
    private static final boolean IS_LOW_MEM;
    private static final boolean DISABLE_THREAD_LOCAL_MAGAZINES_ON_LOW_MEM;
    static final int MIN_CHUNK_SIZE = 131072;
    private static final int EXPANSION_ATTEMPTS = 3;
    private static final int INITIAL_MAGAZINES = 1;
    private static final int RETIRE_CAPACITY = 256;
    private static final int MAX_STRIPES;
    private static final int BUFS_PER_CHUNK = 8;
    private static final int MAX_CHUNK_SIZE;
    private static final int MAX_POOLED_BUF_SIZE;
    private static final int CHUNK_REUSE_QUEUE;
    private static final int MAGAZINE_BUFFER_QUEUE_CAPACITY;
    private static final int[] SIZE_CLASSES;
    private static final int SIZE_CLASSES_COUNT;
    private static final byte[] SIZE_INDEXES;
    private final ChunkAllocator chunkAllocator;
    private final ChunkRegistry chunkRegistry;
    private final MagazineGroup[] sizeClassedMagazineGroups;
    private final MagazineGroup largeBufferMagazineGroup;
    private final FastThreadLocal<MagazineGroup[]> threadLocalGroup;
    
    AdaptivePoolingAllocator(final ChunkAllocator chunkAllocator, final boolean useCacheForNonEventLoopThreads) {
        this.chunkAllocator = ObjectUtil.checkNotNull(chunkAllocator, "chunkAllocator");
        this.chunkRegistry = new ChunkRegistry();
        this.sizeClassedMagazineGroups = createMagazineGroupSizeClasses(this, false);
        this.largeBufferMagazineGroup = new MagazineGroup(this, chunkAllocator, new HistogramChunkControllerFactory(true), false);
        final boolean disableThreadLocalGroups = AdaptivePoolingAllocator.IS_LOW_MEM && AdaptivePoolingAllocator.DISABLE_THREAD_LOCAL_MAGAZINES_ON_LOW_MEM;
        this.threadLocalGroup = (disableThreadLocalGroups ? null : new FastThreadLocal<MagazineGroup[]>() {
            @Override
            protected MagazineGroup[] initialValue() {
                if (useCacheForNonEventLoopThreads || ThreadExecutorMap.currentExecutor() != null) {
                    return createMagazineGroupSizeClasses(AdaptivePoolingAllocator.this, true);
                }
                return null;
            }
            
            @Override
            protected void onRemoval(final MagazineGroup[] groups) throws Exception {
                if (groups != null) {
                    for (final MagazineGroup group : groups) {
                        group.free();
                    }
                }
            }
        });
    }
    
    private static MagazineGroup[] createMagazineGroupSizeClasses(final AdaptivePoolingAllocator allocator, final boolean isThreadLocal) {
        final MagazineGroup[] groups = new MagazineGroup[AdaptivePoolingAllocator.SIZE_CLASSES.length];
        for (int i = 0; i < AdaptivePoolingAllocator.SIZE_CLASSES.length; ++i) {
            final int segmentSize = AdaptivePoolingAllocator.SIZE_CLASSES[i];
            groups[i] = new MagazineGroup(allocator, allocator.chunkAllocator, new SizeClassChunkControllerFactory(segmentSize), isThreadLocal);
        }
        return groups;
    }
    
    private static Queue<Chunk> createSharedChunkQueue() {
        return PlatformDependent.newFixedMpmcQueue(AdaptivePoolingAllocator.CHUNK_REUSE_QUEUE);
    }
    
    ByteBuf allocate(final int size, final int maxCapacity) {
        return this.allocate(size, maxCapacity, Thread.currentThread(), null);
    }
    
    private AdaptiveByteBuf allocate(final int size, final int maxCapacity, final Thread currentThread, final AdaptiveByteBuf buf) {
        AdaptiveByteBuf allocated = null;
        if (size <= AdaptivePoolingAllocator.MAX_POOLED_BUF_SIZE) {
            final int index = sizeClassIndexOf(size);
            MagazineGroup[] magazineGroups;
            if (!FastThreadLocalThread.currentThreadWillCleanupFastThreadLocals() || AdaptivePoolingAllocator.IS_LOW_MEM || (magazineGroups = this.threadLocalGroup.get()) == null) {
                magazineGroups = this.sizeClassedMagazineGroups;
            }
            if (index < magazineGroups.length) {
                allocated = magazineGroups[index].allocate(size, maxCapacity, currentThread, buf);
            }
            else if (!AdaptivePoolingAllocator.IS_LOW_MEM) {
                allocated = this.largeBufferMagazineGroup.allocate(size, maxCapacity, currentThread, buf);
            }
        }
        if (allocated == null) {
            allocated = this.allocateFallback(size, maxCapacity, currentThread, buf);
        }
        return allocated;
    }
    
    private static int sizeIndexOf(final int size) {
        return size + 31 >> 5;
    }
    
    static int sizeClassIndexOf(final int size) {
        final int sizeIndex = sizeIndexOf(size);
        if (sizeIndex < AdaptivePoolingAllocator.SIZE_INDEXES.length) {
            return AdaptivePoolingAllocator.SIZE_INDEXES[sizeIndex];
        }
        return AdaptivePoolingAllocator.SIZE_CLASSES_COUNT;
    }
    
    static int[] getSizeClasses() {
        return AdaptivePoolingAllocator.SIZE_CLASSES.clone();
    }
    
    private AdaptiveByteBuf allocateFallback(final int size, final int maxCapacity, final Thread currentThread, AdaptiveByteBuf buf) {
        Magazine magazine;
        if (buf != null) {
            final Chunk chunk = buf.chunk;
            if (chunk == null || chunk == Magazine.MAGAZINE_FREED || (magazine = chunk.currentMagazine()) == null) {
                magazine = this.getFallbackMagazine(currentThread);
            }
        }
        else {
            magazine = this.getFallbackMagazine(currentThread);
            buf = magazine.newBuffer();
        }
        final AbstractByteBuf innerChunk = this.chunkAllocator.allocate(size, maxCapacity);
        final Chunk chunk2 = new Chunk(innerChunk, magazine, false, chunkSize -> true);
        this.chunkRegistry.add(chunk2);
        try {
            chunk2.readInitInto(buf, size, size, maxCapacity);
        }
        finally {
            chunk2.release();
        }
        return buf;
    }
    
    private Magazine getFallbackMagazine(final Thread currentThread) {
        final Magazine[] mags = this.largeBufferMagazineGroup.magazines;
        return mags[(int)currentThread.getId() & mags.length - 1];
    }
    
    void reallocate(final int size, final int maxCapacity, final AdaptiveByteBuf into) {
        final AdaptiveByteBuf result = this.allocate(size, maxCapacity, Thread.currentThread(), into);
        assert result == into : "Re-allocation created separate buffer instance";
    }
    
    long usedMemory() {
        return this.chunkRegistry.totalCapacity();
    }
    
    @Override
    protected void finalize() throws Throwable {
        try {
            super.finalize();
        }
        finally {
            this.free();
        }
    }
    
    private void free() {
        this.largeBufferMagazineGroup.free();
    }
    
    static int sizeToBucket(final int size) {
        return HistogramChunkController.sizeToBucket(size);
    }
    
    static {
        IS_LOW_MEM = (Runtime.getRuntime().maxMemory() <= 536870912L);
        DISABLE_THREAD_LOCAL_MAGAZINES_ON_LOW_MEM = SystemPropertyUtil.getBoolean("io.netty.allocator.disableThreadLocalMagazinesOnLowMemory", true);
        MAX_STRIPES = (AdaptivePoolingAllocator.IS_LOW_MEM ? 1 : (NettyRuntime.availableProcessors() * 2));
        MAX_CHUNK_SIZE = (AdaptivePoolingAllocator.IS_LOW_MEM ? 2097152 : 8388608);
        MAX_POOLED_BUF_SIZE = AdaptivePoolingAllocator.MAX_CHUNK_SIZE / 8;
        CHUNK_REUSE_QUEUE = Math.max(2, SystemPropertyUtil.getInt("io.netty.allocator.chunkReuseQueueCapacity", NettyRuntime.availableProcessors() * 2));
        MAGAZINE_BUFFER_QUEUE_CAPACITY = SystemPropertyUtil.getInt("io.netty.allocator.magazineBufferQueueCapacity", 1024);
        SIZE_CLASSES = new int[] { 32, 64, 128, 256, 512, 640, 1024, 1152, 2048, 2304, 4096, 4352, 8192, 8704, 16384, 16896, 32768, 65536 };
        SIZE_CLASSES_COUNT = AdaptivePoolingAllocator.SIZE_CLASSES.length;
        SIZE_INDEXES = new byte[AdaptivePoolingAllocator.SIZE_CLASSES[AdaptivePoolingAllocator.SIZE_CLASSES_COUNT - 1] / 32 + 1];
        if (AdaptivePoolingAllocator.MAGAZINE_BUFFER_QUEUE_CAPACITY < 2) {
            throw new IllegalArgumentException("MAGAZINE_BUFFER_QUEUE_CAPACITY: " + AdaptivePoolingAllocator.MAGAZINE_BUFFER_QUEUE_CAPACITY + " (expected: >= " + 2 + ')');
        }
        int lastIndex = 0;
        for (int i = 0; i < AdaptivePoolingAllocator.SIZE_CLASSES_COUNT; ++i) {
            final int sizeClass = AdaptivePoolingAllocator.SIZE_CLASSES[i];
            assert (sizeClass & 0x5) == 0x0 : "Size class must be a multiple of 32";
            final int sizeIndex = sizeIndexOf(sizeClass);
            Arrays.fill(AdaptivePoolingAllocator.SIZE_INDEXES, lastIndex + 1, sizeIndex + 1, (byte)i);
            lastIndex = sizeIndex;
        }
    }
    
    private static final class MagazineGroup
    {
        private final AdaptivePoolingAllocator allocator;
        private final ChunkAllocator chunkAllocator;
        private final ChunkControllerFactory chunkControllerFactory;
        private final Queue<Chunk> chunkReuseQueue;
        private final StampedLock magazineExpandLock;
        private final Magazine threadLocalMagazine;
        private volatile Magazine[] magazines;
        private volatile boolean freed;
        
        MagazineGroup(final AdaptivePoolingAllocator allocator, final ChunkAllocator chunkAllocator, final ChunkControllerFactory chunkControllerFactory, final boolean isThreadLocal) {
            this.allocator = allocator;
            this.chunkAllocator = chunkAllocator;
            this.chunkControllerFactory = chunkControllerFactory;
            this.chunkReuseQueue = createSharedChunkQueue();
            if (isThreadLocal) {
                this.magazineExpandLock = null;
                this.threadLocalMagazine = new Magazine(this, false, this.chunkReuseQueue, chunkControllerFactory.create(this));
            }
            else {
                this.magazineExpandLock = new StampedLock();
                this.threadLocalMagazine = null;
                final Magazine[] mags = { null };
                for (int i = 0; i < mags.length; ++i) {
                    mags[i] = new Magazine(this, true, this.chunkReuseQueue, chunkControllerFactory.create(this));
                }
                this.magazines = mags;
            }
        }
        
        public AdaptiveByteBuf allocate(final int size, final int maxCapacity, final Thread currentThread, AdaptiveByteBuf buf) {
            final boolean reallocate = buf != null;
            final Magazine tlMag = this.threadLocalMagazine;
            if (tlMag == null) {
                final long threadId = currentThread.getId();
                int expansions = 0;
                Magazine[] mags;
                do {
                    mags = this.magazines;
                    final int mask = mags.length - 1;
                    final int index = (int)(threadId & (long)mask);
                    for (int i = 0, m = mags.length << 1; i < m; ++i) {
                        final Magazine mag = mags[index + i & mask];
                        if (buf == null) {
                            buf = mag.newBuffer();
                        }
                        if (mag.tryAllocate(size, maxCapacity, buf, reallocate)) {
                            return buf;
                        }
                    }
                } while (++expansions <= 3 && this.tryExpandMagazines(mags.length));
                if (!reallocate && buf != null) {
                    buf.release();
                }
                return null;
            }
            if (buf == null) {
                buf = tlMag.newBuffer();
            }
            final boolean allocated = tlMag.tryAllocate(size, maxCapacity, buf, reallocate);
            assert allocated : "Allocation of threadLocalMagazine must always succeed";
            return buf;
        }
        
        private boolean tryExpandMagazines(final int currentLength) {
            if (currentLength >= AdaptivePoolingAllocator.MAX_STRIPES) {
                return true;
            }
            final long writeLock = this.magazineExpandLock.tryWriteLock();
            if (writeLock != 0L) {
                Magazine[] mags;
                try {
                    mags = this.magazines;
                    if (mags.length >= AdaptivePoolingAllocator.MAX_STRIPES || mags.length > currentLength || this.freed) {
                        return true;
                    }
                    final Magazine firstMagazine = mags[0];
                    final Magazine[] expanded = new Magazine[mags.length * 2];
                    for (int i = 0, l = expanded.length; i < l; ++i) {
                        final Magazine m = new Magazine(this, true, this.chunkReuseQueue, this.chunkControllerFactory.create(this));
                        firstMagazine.initializeSharedStateIn(m);
                        expanded[i] = m;
                    }
                    this.magazines = expanded;
                }
                finally {
                    this.magazineExpandLock.unlockWrite(writeLock);
                }
                for (final Magazine magazine : mags) {
                    magazine.free();
                }
            }
            return true;
        }
        
        boolean offerToQueue(final Chunk buffer) {
            if (this.freed) {
                return false;
            }
            final boolean isAdded = this.chunkReuseQueue.offer(buffer);
            if (this.freed && isAdded) {
                this.freeChunkReuseQueue();
            }
            return isAdded;
        }
        
        private void free() {
            this.freed = true;
            if (this.threadLocalMagazine != null) {
                this.threadLocalMagazine.free();
            }
            else {
                final long stamp = this.magazineExpandLock.writeLock();
                try {
                    final Magazine[] magazines;
                    final Magazine[] mags = magazines = this.magazines;
                    for (final Magazine magazine : magazines) {
                        magazine.free();
                    }
                }
                finally {
                    this.magazineExpandLock.unlockWrite(stamp);
                }
            }
            this.freeChunkReuseQueue();
        }
        
        private void freeChunkReuseQueue() {
            while (true) {
                final Chunk chunk = this.chunkReuseQueue.poll();
                if (chunk == null) {
                    break;
                }
                chunk.release();
            }
        }
    }
    
    private static final class SizeClassChunkControllerFactory implements ChunkControllerFactory
    {
        private static final int MIN_SEGMENTS_PER_CHUNK = 32;
        private final int segmentSize;
        private final int chunkSize;
        private final int[] segmentOffsets;
        
        private SizeClassChunkControllerFactory(final int segmentSize) {
            this.segmentSize = ObjectUtil.checkPositive(segmentSize, "segmentSize");
            this.chunkSize = Math.max(131072, segmentSize * 32);
            final int segmentsCount = this.chunkSize / segmentSize;
            this.segmentOffsets = new int[segmentsCount];
            for (int i = 0; i < segmentsCount; ++i) {
                this.segmentOffsets[i] = i * segmentSize;
            }
        }
        
        @Override
        public ChunkController create(final MagazineGroup group) {
            return new SizeClassChunkController(group, this.segmentSize, this.chunkSize, this.segmentOffsets);
        }
    }
    
    private static final class SizeClassChunkController implements ChunkController
    {
        private final ChunkAllocator chunkAllocator;
        private final int segmentSize;
        private final int chunkSize;
        private final ChunkRegistry chunkRegistry;
        private final int[] segmentOffsets;
        
        private SizeClassChunkController(final MagazineGroup group, final int segmentSize, final int chunkSize, final int[] segmentOffsets) {
            this.chunkAllocator = group.chunkAllocator;
            this.segmentSize = segmentSize;
            this.chunkSize = chunkSize;
            this.chunkRegistry = group.allocator.chunkRegistry;
            this.segmentOffsets = segmentOffsets;
        }
        
        @Override
        public int computeBufferCapacity(final int requestedSize, final int maxCapacity, final boolean isReallocation) {
            return Math.min(this.segmentSize, maxCapacity);
        }
        
        @Override
        public void initializeSharedStateIn(final ChunkController chunkController) {
        }
        
        @Override
        public Chunk newChunkAllocation(final int promptingSize, final Magazine magazine) {
            final AbstractByteBuf chunkBuffer = this.chunkAllocator.allocate(this.chunkSize, this.chunkSize);
            assert chunkBuffer.capacity() == this.chunkSize;
            final SizeClassedChunk chunk = new SizeClassedChunk(chunkBuffer, magazine, true, this.segmentSize, this.segmentOffsets, size -> false);
            this.chunkRegistry.add(chunk);
            return chunk;
        }
    }
    
    private static final class HistogramChunkControllerFactory implements ChunkControllerFactory
    {
        private final boolean shareable;
        
        private HistogramChunkControllerFactory(final boolean shareable) {
            this.shareable = shareable;
        }
        
        @Override
        public ChunkController create(final MagazineGroup group) {
            return new HistogramChunkController(group, this.shareable);
        }
    }
    
    private static final class HistogramChunkController implements ChunkController, ChunkReleasePredicate
    {
        private static final int MIN_DATUM_TARGET = 1024;
        private static final int MAX_DATUM_TARGET = 65534;
        private static final int INIT_DATUM_TARGET = 9;
        private static final int HISTO_BUCKET_COUNT = 16;
        private static final int[] HISTO_BUCKETS;
        private final MagazineGroup group;
        private final boolean shareable;
        private final short[][] histos;
        private final ChunkRegistry chunkRegistry;
        private short[] histo;
        private final int[] sums;
        private int histoIndex;
        private int datumCount;
        private int datumTarget;
        private boolean hasHadRotation;
        private volatile int sharedPrefChunkSize;
        private volatile int localPrefChunkSize;
        private volatile int localUpperBufSize;
        
        private HistogramChunkController(final MagazineGroup group, final boolean shareable) {
            this.histos = new short[][] { new short[16], new short[16], new short[16], new short[16] };
            this.histo = this.histos[0];
            this.sums = new int[16];
            this.datumTarget = 9;
            this.sharedPrefChunkSize = 131072;
            this.localPrefChunkSize = 131072;
            this.group = group;
            this.shareable = shareable;
            this.chunkRegistry = group.allocator.chunkRegistry;
        }
        
        @Override
        public int computeBufferCapacity(final int requestedSize, final int maxCapacity, final boolean isReallocation) {
            if (!isReallocation) {
                this.recordAllocationSize(requestedSize);
            }
            int startCapLimits;
            if (requestedSize <= 32768) {
                startCapLimits = 65536;
            }
            else {
                startCapLimits = requestedSize * 2;
            }
            int startingCapacity = Math.min(startCapLimits, this.localUpperBufSize);
            startingCapacity = Math.max(requestedSize, Math.min(maxCapacity, startingCapacity));
            return startingCapacity;
        }
        
        private void recordAllocationSize(final int bufferSizeToRecord) {
            if (bufferSizeToRecord == 0) {
                return;
            }
            final int bucket = sizeToBucket(bufferSizeToRecord);
            final short[] histo = this.histo;
            final int n = bucket;
            ++histo[n];
            if (this.datumCount++ == this.datumTarget) {
                this.rotateHistograms();
            }
        }
        
        static int sizeToBucket(final int size) {
            final int index = binarySearchInsertionPoint(Arrays.binarySearch(HistogramChunkController.HISTO_BUCKETS, size));
            return (index >= HistogramChunkController.HISTO_BUCKETS.length) ? (HistogramChunkController.HISTO_BUCKETS.length - 1) : index;
        }
        
        private static int binarySearchInsertionPoint(int index) {
            if (index < 0) {
                index = -(index + 1);
            }
            return index;
        }
        
        static int bucketToSize(final int sizeBucket) {
            return HistogramChunkController.HISTO_BUCKETS[sizeBucket];
        }
        
        private void rotateHistograms() {
            final short[][] hs = this.histos;
            for (int i = 0; i < 16; ++i) {
                this.sums[i] = (hs[0][i] & 0xFFFF) + (hs[1][i] & 0xFFFF) + (hs[2][i] & 0xFFFF) + (hs[3][i] & 0xFFFF);
            }
            int sum = 0;
            for (final int count : this.sums) {
                sum += count;
            }
            int targetPercentile;
            int sizeBucket;
            for (targetPercentile = (int)(sum * 0.99), sizeBucket = 0; sizeBucket < this.sums.length && this.sums[sizeBucket] <= targetPercentile; targetPercentile -= this.sums[sizeBucket], ++sizeBucket) {}
            this.hasHadRotation = true;
            final int percentileSize = bucketToSize(sizeBucket);
            int prefChunkSize = Math.max(percentileSize * 8, 131072);
            this.localUpperBufSize = percentileSize;
            this.localPrefChunkSize = prefChunkSize;
            if (this.shareable) {
                for (final Magazine mag : this.group.magazines) {
                    final HistogramChunkController statistics = (HistogramChunkController)mag.chunkController;
                    prefChunkSize = Math.max(prefChunkSize, statistics.localPrefChunkSize);
                }
            }
            if (this.sharedPrefChunkSize != prefChunkSize) {
                this.datumTarget = Math.max(this.datumTarget >> 1, 1024);
                this.sharedPrefChunkSize = prefChunkSize;
            }
            else {
                this.datumTarget = Math.min(this.datumTarget << 1, 65534);
            }
            this.histoIndex = (this.histoIndex + 1 & 0x3);
            this.histo = this.histos[this.histoIndex];
            this.datumCount = 0;
            Arrays.fill(this.histo, (short)0);
        }
        
        int preferredChunkSize() {
            return this.sharedPrefChunkSize;
        }
        
        @Override
        public void initializeSharedStateIn(final ChunkController chunkController) {
            final HistogramChunkController statistics = (HistogramChunkController)chunkController;
            final int sharedPrefChunkSize = this.sharedPrefChunkSize;
            statistics.localPrefChunkSize = sharedPrefChunkSize;
            statistics.sharedPrefChunkSize = sharedPrefChunkSize;
        }
        
        @Override
        public Chunk newChunkAllocation(final int promptingSize, final Magazine magazine) {
            int size = Math.max(promptingSize * 8, this.preferredChunkSize());
            final int minChunks = size / 131072;
            if (131072 * minChunks < size) {
                size = 131072 * (1 + minChunks);
            }
            size = Math.min(size, AdaptivePoolingAllocator.MAX_CHUNK_SIZE);
            if (!this.hasHadRotation && this.sharedPrefChunkSize == 131072) {
                this.sharedPrefChunkSize = size;
            }
            final ChunkAllocator chunkAllocator = this.group.chunkAllocator;
            final Chunk chunk = new Chunk(chunkAllocator.allocate(size, size), magazine, true, this);
            this.chunkRegistry.add(chunk);
            return chunk;
        }
        
        @Override
        public boolean shouldReleaseChunk(final int chunkSize) {
            final int preferredSize = this.preferredChunkSize();
            final int givenChunks = chunkSize / 131072;
            final int preferredChunks = preferredSize / 131072;
            final int deviation = Math.abs(givenChunks - preferredChunks);
            return deviation != 0 && ThreadLocalRandom.current().nextDouble() * 20.0 < deviation;
        }
        
        static {
            HISTO_BUCKETS = new int[] { 16384, 24576, 32768, 49152, 65536, 98304, 131072, 196608, 262144, 393216, 524288, 786432, 1048576, 1835008, 2097152, 3145728 };
        }
    }
    
    private static final class Magazine
    {
        private static final AtomicReferenceFieldUpdater<Magazine, Chunk> NEXT_IN_LINE;
        private static final Chunk MAGAZINE_FREED;
        private static final Recycler<AdaptiveByteBuf> EVENT_LOOP_LOCAL_BUFFER_POOL;
        private Chunk current;
        private volatile Chunk nextInLine;
        private final MagazineGroup group;
        private final ChunkController chunkController;
        private final StampedLock allocationLock;
        private final Queue<AdaptiveByteBuf> bufferQueue;
        private final ObjectPool.Handle<AdaptiveByteBuf> handle;
        private final Queue<Chunk> sharedChunkQueue;
        
        Magazine(final MagazineGroup group, final boolean shareable, final Queue<Chunk> sharedChunkQueue, final ChunkController chunkController) {
            this.group = group;
            this.chunkController = chunkController;
            if (shareable) {
                this.allocationLock = new StampedLock();
                this.bufferQueue = PlatformDependent.newFixedMpmcQueue(AdaptivePoolingAllocator.MAGAZINE_BUFFER_QUEUE_CAPACITY);
                this.handle = new ObjectPool.Handle<AdaptiveByteBuf>() {
                    @Override
                    public void recycle(final AdaptiveByteBuf self) {
                        Magazine.this.bufferQueue.offer(self);
                    }
                };
            }
            else {
                this.allocationLock = null;
                this.bufferQueue = null;
                this.handle = null;
            }
            this.sharedChunkQueue = sharedChunkQueue;
        }
        
        public boolean tryAllocate(final int size, final int maxCapacity, final AdaptiveByteBuf buf, final boolean reallocate) {
            if (this.allocationLock == null) {
                return this.allocate(size, maxCapacity, buf, reallocate);
            }
            final long writeLock = this.allocationLock.tryWriteLock();
            if (writeLock != 0L) {
                try {
                    return this.allocate(size, maxCapacity, buf, reallocate);
                }
                finally {
                    this.allocationLock.unlockWrite(writeLock);
                }
            }
            return this.allocateWithoutLock(size, maxCapacity, buf);
        }
        
        private boolean allocateWithoutLock(final int size, final int maxCapacity, final AdaptiveByteBuf buf) {
            Chunk curr = Magazine.NEXT_IN_LINE.getAndSet(this, null);
            if (curr == Magazine.MAGAZINE_FREED) {
                this.restoreMagazineFreed();
                return false;
            }
            if (curr == null) {
                curr = this.sharedChunkQueue.poll();
                if (curr == null) {
                    return false;
                }
                curr.attachToMagazine(this);
            }
            boolean allocated = false;
            final int remainingCapacity = curr.remainingCapacity();
            final int startingCapacity = this.chunkController.computeBufferCapacity(size, maxCapacity, true);
            if (remainingCapacity >= size) {
                curr.readInitInto(buf, size, Math.min(remainingCapacity, startingCapacity), maxCapacity);
                allocated = true;
            }
            try {
                if (remainingCapacity >= 256) {
                    this.transferToNextInLineOrRelease(curr);
                    curr = null;
                }
            }
            finally {
                if (curr != null) {
                    curr.releaseFromMagazine();
                }
            }
            return allocated;
        }
        
        private boolean allocate(final int size, final int maxCapacity, final AdaptiveByteBuf buf, final boolean reallocate) {
            final int startingCapacity = this.chunkController.computeBufferCapacity(size, maxCapacity, reallocate);
            Chunk curr = this.current;
            if (curr != null) {
                final int remainingCapacity = curr.remainingCapacity();
                if (remainingCapacity > startingCapacity) {
                    curr.readInitInto(buf, size, startingCapacity, maxCapacity);
                    return true;
                }
                this.current = null;
                if (remainingCapacity >= size) {
                    try {
                        curr.readInitInto(buf, size, remainingCapacity, maxCapacity);
                        return true;
                    }
                    finally {
                        curr.releaseFromMagazine();
                    }
                }
                if (remainingCapacity < 256) {
                    curr.releaseFromMagazine();
                }
                else {
                    this.transferToNextInLineOrRelease(curr);
                }
            }
            assert this.current == null;
            curr = Magazine.NEXT_IN_LINE.getAndSet(this, null);
            if (curr != null) {
                if (curr == Magazine.MAGAZINE_FREED) {
                    this.restoreMagazineFreed();
                    return false;
                }
                final int remainingCapacity = curr.remainingCapacity();
                if (remainingCapacity > startingCapacity) {
                    curr.readInitInto(buf, size, startingCapacity, maxCapacity);
                    this.current = curr;
                    return true;
                }
                if (remainingCapacity >= size) {
                    try {
                        curr.readInitInto(buf, size, remainingCapacity, maxCapacity);
                        return true;
                    }
                    finally {
                        curr.releaseFromMagazine();
                    }
                }
                curr.releaseFromMagazine();
            }
            curr = this.sharedChunkQueue.poll();
            if (curr == null) {
                curr = this.chunkController.newChunkAllocation(size, this);
            }
            else {
                curr.attachToMagazine(this);
                final int remainingCapacity = curr.remainingCapacity();
                if (remainingCapacity == 0 || remainingCapacity < size) {
                    if (remainingCapacity < 256) {
                        curr.releaseFromMagazine();
                    }
                    else {
                        this.transferToNextInLineOrRelease(curr);
                    }
                    curr = this.chunkController.newChunkAllocation(size, this);
                }
            }
            this.current = curr;
            try {
                final int remainingCapacity = curr.remainingCapacity();
                assert remainingCapacity >= size;
                if (remainingCapacity > startingCapacity) {
                    curr.readInitInto(buf, size, startingCapacity, maxCapacity);
                    curr = null;
                }
                else {
                    curr.readInitInto(buf, size, remainingCapacity, maxCapacity);
                }
            }
            finally {
                if (curr != null) {
                    curr.releaseFromMagazine();
                    this.current = null;
                }
            }
            return true;
        }
        
        private void restoreMagazineFreed() {
            final Chunk next = Magazine.NEXT_IN_LINE.getAndSet(this, Magazine.MAGAZINE_FREED);
            if (next != null && next != Magazine.MAGAZINE_FREED) {
                next.releaseFromMagazine();
            }
        }
        
        private void transferToNextInLineOrRelease(final Chunk chunk) {
            if (Magazine.NEXT_IN_LINE.compareAndSet(this, null, chunk)) {
                return;
            }
            final Chunk nextChunk = Magazine.NEXT_IN_LINE.get(this);
            if (nextChunk != null && nextChunk != Magazine.MAGAZINE_FREED && chunk.remainingCapacity() > nextChunk.remainingCapacity() && Magazine.NEXT_IN_LINE.compareAndSet(this, nextChunk, chunk)) {
                nextChunk.releaseFromMagazine();
                return;
            }
            chunk.releaseFromMagazine();
        }
        
        boolean trySetNextInLine(final Chunk chunk) {
            return Magazine.NEXT_IN_LINE.compareAndSet(this, null, chunk);
        }
        
        void free() {
            this.restoreMagazineFreed();
            final long stamp = (this.allocationLock != null) ? this.allocationLock.writeLock() : 0L;
            try {
                if (this.current != null) {
                    this.current.releaseFromMagazine();
                    this.current = null;
                }
            }
            finally {
                if (this.allocationLock != null) {
                    this.allocationLock.unlockWrite(stamp);
                }
            }
        }
        
        public AdaptiveByteBuf newBuffer() {
            AdaptiveByteBuf buf;
            if (this.handle == null) {
                buf = Magazine.EVENT_LOOP_LOCAL_BUFFER_POOL.get();
            }
            else {
                buf = this.bufferQueue.poll();
                if (buf == null) {
                    buf = new AdaptiveByteBuf(this.handle);
                }
            }
            buf.resetRefCnt();
            buf.discardMarks();
            return buf;
        }
        
        boolean offerToQueue(final Chunk chunk) {
            return this.group.offerToQueue(chunk);
        }
        
        public void initializeSharedStateIn(final Magazine other) {
            this.chunkController.initializeSharedStateIn(other.chunkController);
        }
        
        static {
            NEXT_IN_LINE = AtomicReferenceFieldUpdater.newUpdater(Magazine.class, Chunk.class, "nextInLine");
            MAGAZINE_FREED = new Chunk();
            EVENT_LOOP_LOCAL_BUFFER_POOL = new Recycler<AdaptiveByteBuf>() {
                @Override
                protected AdaptiveByteBuf newObject(final Handle<AdaptiveByteBuf> handle) {
                    return new AdaptiveByteBuf(handle);
                }
            };
        }
    }
    
    private static final class ChunkRegistry
    {
        private final LongAdder totalCapacity;
        
        private ChunkRegistry() {
            this.totalCapacity = new LongAdder();
        }
        
        public long totalCapacity() {
            return this.totalCapacity.sum();
        }
        
        public void add(final Chunk chunk) {
            this.totalCapacity.add(chunk.capacity());
        }
        
        public void remove(final Chunk chunk) {
            this.totalCapacity.add(-chunk.capacity());
        }
    }
    
    private static class Chunk implements ChunkInfo
    {
        protected final AbstractByteBuf delegate;
        protected Magazine magazine;
        private final AdaptivePoolingAllocator allocator;
        private final ChunkReleasePredicate chunkReleasePredicate;
        private final RefCnt refCnt;
        private final int capacity;
        private final boolean pooled;
        protected int allocatedBytes;
        
        Chunk() {
            this.refCnt = new RefCnt();
            this.delegate = null;
            this.magazine = null;
            this.allocator = null;
            this.chunkReleasePredicate = null;
            this.capacity = 0;
            this.pooled = false;
        }
        
        Chunk(final AbstractByteBuf delegate, final Magazine magazine, final boolean pooled, final ChunkReleasePredicate chunkReleasePredicate) {
            this.refCnt = new RefCnt();
            this.delegate = delegate;
            this.pooled = pooled;
            this.capacity = delegate.capacity();
            this.attachToMagazine(magazine);
            this.allocator = magazine.group.allocator;
            this.chunkReleasePredicate = chunkReleasePredicate;
            if (PlatformDependent.isJfrEnabled() && AllocateChunkEvent.isEventEnabled()) {
                final AllocateChunkEvent event = new AllocateChunkEvent();
                if (event.shouldCommit()) {
                    event.fill(this, AdaptiveByteBufAllocator.class);
                    event.pooled = pooled;
                    event.threadLocal = (magazine.allocationLock == null);
                    event.commit();
                }
            }
        }
        
        Magazine currentMagazine() {
            return this.magazine;
        }
        
        void detachFromMagazine() {
            if (this.magazine != null) {
                this.magazine = null;
            }
        }
        
        void attachToMagazine(final Magazine magazine) {
            assert this.magazine == null;
            this.magazine = magazine;
        }
        
        boolean releaseFromMagazine() {
            return this.release();
        }
        
        boolean releaseSegment(final int ignoredSegmentId) {
            return this.release();
        }
        
        private void retain() {
            RefCnt.retain(this.refCnt);
        }
        
        protected boolean release() {
            final boolean deallocate = RefCnt.release(this.refCnt);
            if (deallocate) {
                this.deallocate();
            }
            return deallocate;
        }
        
        protected void deallocate() {
            final Magazine mag = this.magazine;
            final int chunkSize = this.delegate.capacity();
            if (!this.pooled || this.chunkReleasePredicate.shouldReleaseChunk(chunkSize) || mag == null) {
                this.detachFromMagazine();
                this.onRelease();
                this.allocator.chunkRegistry.remove(this);
                this.delegate.release();
            }
            else {
                RefCnt.resetRefCnt(this.refCnt);
                this.delegate.setIndex(0, 0);
                this.allocatedBytes = 0;
                if (!mag.trySetNextInLine(this)) {
                    this.detachFromMagazine();
                    if (!mag.offerToQueue(this)) {
                        final boolean released = RefCnt.release(this.refCnt);
                        this.onRelease();
                        this.allocator.chunkRegistry.remove(this);
                        this.delegate.release();
                        assert released;
                    }
                    else {
                        this.onReturn(false);
                    }
                }
                else {
                    this.onReturn(true);
                }
            }
        }
        
        private void onReturn(final boolean returnedToMagazine) {
            if (PlatformDependent.isJfrEnabled() && ReturnChunkEvent.isEventEnabled()) {
                final ReturnChunkEvent event = new ReturnChunkEvent();
                if (event.shouldCommit()) {
                    event.fill(this, AdaptiveByteBufAllocator.class);
                    event.returnedToMagazine = returnedToMagazine;
                    event.commit();
                }
            }
        }
        
        private void onRelease() {
            if (PlatformDependent.isJfrEnabled() && FreeChunkEvent.isEventEnabled()) {
                final FreeChunkEvent event = new FreeChunkEvent();
                if (event.shouldCommit()) {
                    event.fill(this, AdaptiveByteBufAllocator.class);
                    event.pooled = this.pooled;
                    event.commit();
                }
            }
        }
        
        public void readInitInto(final AdaptiveByteBuf buf, final int size, final int startingCapacity, final int maxCapacity) {
            final int startIndex = this.allocatedBytes;
            this.allocatedBytes = startIndex + startingCapacity;
            Chunk chunk = this;
            chunk.retain();
            try {
                buf.init(this.delegate, chunk, 0, 0, startIndex, size, startingCapacity, maxCapacity);
                chunk = null;
            }
            finally {
                if (chunk != null) {
                    this.allocatedBytes = startIndex;
                    chunk.release();
                }
            }
        }
        
        public int remainingCapacity() {
            return this.capacity - this.allocatedBytes;
        }
        
        @Override
        public int capacity() {
            return this.capacity;
        }
        
        @Override
        public boolean isDirect() {
            return this.delegate.isDirect();
        }
        
        @Override
        public long memoryAddress() {
            return this.delegate._memoryAddress();
        }
    }
    
    private static final class SizeClassedChunk extends Chunk
    {
        private static final int FREE_LIST_EMPTY = -1;
        private final int segmentSize;
        private final MpscIntQueue freeList;
        
        SizeClassedChunk(final AbstractByteBuf delegate, final Magazine magazine, final boolean pooled, final int segmentSize, final int[] segmentOffsets, final ChunkReleasePredicate shouldReleaseChunk) {
            super(delegate, magazine, pooled, shouldReleaseChunk);
            this.segmentSize = segmentSize;
            final int segmentCount = segmentOffsets.length;
            assert delegate.capacity() / segmentSize == segmentCount;
            assert segmentCount > 0 : "Chunk must have a positive number of segments";
            (this.freeList = MpscIntQueue.create(segmentCount, -1)).fill(segmentCount, new IntSupplier() {
                int counter;
                
                @Override
                public int getAsInt() {
                    return segmentOffsets[this.counter++];
                }
            });
        }
        
        @Override
        public void readInitInto(final AdaptiveByteBuf buf, final int size, final int startingCapacity, final int maxCapacity) {
            final int startIndex = this.freeList.poll();
            if (startIndex == -1) {
                throw new IllegalStateException("Free list is empty");
            }
            this.allocatedBytes += this.segmentSize;
            Chunk chunk = this;
            chunk.retain();
            try {
                buf.init(this.delegate, chunk, 0, 0, startIndex, size, startingCapacity, maxCapacity);
                chunk = null;
            }
            finally {
                if (chunk != null) {
                    this.allocatedBytes -= this.segmentSize;
                    chunk.releaseSegment(startIndex);
                }
            }
        }
        
        @Override
        public int remainingCapacity() {
            final int remainingCapacity = super.remainingCapacity();
            if (remainingCapacity > this.segmentSize) {
                return remainingCapacity;
            }
            final int updatedRemainingCapacity = this.freeList.size() * this.segmentSize;
            if (updatedRemainingCapacity == remainingCapacity) {
                return remainingCapacity;
            }
            this.allocatedBytes = this.capacity() - updatedRemainingCapacity;
            return updatedRemainingCapacity;
        }
        
        @Override
        boolean releaseFromMagazine() {
            final Magazine mag = this.magazine;
            this.detachFromMagazine();
            return !mag.offerToQueue(this) && super.releaseFromMagazine();
        }
        
        @Override
        boolean releaseSegment(final int startIndex) {
            final boolean released = this.release();
            final boolean segmentReturned = this.freeList.offer(startIndex);
            assert segmentReturned : "Unable to return segment " + startIndex + " to free list";
            return released;
        }
    }
    
    static final class AdaptiveByteBuf extends AbstractReferenceCountedByteBuf
    {
        private final ObjectPool.Handle<AdaptiveByteBuf> handle;
        private int startIndex;
        private AbstractByteBuf rootParent;
        Chunk chunk;
        private int length;
        private int maxFastCapacity;
        private ByteBuffer tmpNioBuf;
        private boolean hasArray;
        private boolean hasMemoryAddress;
        
        AdaptiveByteBuf(final ObjectPool.Handle<AdaptiveByteBuf> recyclerHandle) {
            super(0);
            this.handle = ObjectUtil.checkNotNull(recyclerHandle, "recyclerHandle");
        }
        
        void init(final AbstractByteBuf unwrapped, final Chunk wrapped, final int readerIndex, final int writerIndex, final int startIndex, final int size, final int capacity, final int maxCapacity) {
            this.startIndex = startIndex;
            this.chunk = wrapped;
            this.length = size;
            this.maxFastCapacity = capacity;
            this.maxCapacity(maxCapacity);
            this.setIndex0(readerIndex, writerIndex);
            this.hasArray = unwrapped.hasArray();
            this.hasMemoryAddress = unwrapped.hasMemoryAddress();
            this.rootParent = unwrapped;
            this.tmpNioBuf = null;
            if (PlatformDependent.isJfrEnabled() && AllocateBufferEvent.isEventEnabled()) {
                final AllocateBufferEvent event = new AllocateBufferEvent();
                if (event.shouldCommit()) {
                    event.fill(this, AdaptiveByteBufAllocator.class);
                    event.chunkPooled = wrapped.pooled;
                    final Magazine m = wrapped.magazine;
                    event.chunkThreadLocal = (m != null && m.allocationLock == null);
                    event.commit();
                }
            }
        }
        
        private AbstractByteBuf rootParent() {
            final AbstractByteBuf rootParent = this.rootParent;
            if (rootParent != null) {
                return rootParent;
            }
            throw new IllegalReferenceCountException();
        }
        
        @Override
        public int capacity() {
            return this.length;
        }
        
        @Override
        public int maxFastWritableBytes() {
            return Math.min(this.maxFastCapacity, this.maxCapacity()) - this.writerIndex;
        }
        
        @Override
        public ByteBuf capacity(final int newCapacity) {
            if (this.length <= newCapacity && newCapacity <= this.maxFastCapacity) {
                this.ensureAccessible();
                this.length = newCapacity;
                return this;
            }
            this.checkNewCapacity(newCapacity);
            if (newCapacity < this.capacity()) {
                this.trimIndicesToCapacity(this.length = newCapacity);
                return this;
            }
            if (PlatformDependent.isJfrEnabled() && ReallocateBufferEvent.isEventEnabled()) {
                final ReallocateBufferEvent event = new ReallocateBufferEvent();
                if (event.shouldCommit()) {
                    event.fill(this, AdaptiveByteBufAllocator.class);
                    event.newCapacity = newCapacity;
                    event.commit();
                }
            }
            final Chunk chunk = this.chunk;
            final AdaptivePoolingAllocator allocator = chunk.allocator;
            final int readerIndex = this.readerIndex;
            final int writerIndex = this.writerIndex;
            final int baseOldRootIndex = this.startIndex;
            final int oldCapacity = this.length;
            final AbstractByteBuf oldRoot = this.rootParent();
            allocator.reallocate(newCapacity, this.maxCapacity(), this);
            oldRoot.getBytes(baseOldRootIndex, this, 0, oldCapacity);
            chunk.releaseSegment(baseOldRootIndex);
            this.readerIndex = readerIndex;
            this.writerIndex = writerIndex;
            return this;
        }
        
        @Override
        public ByteBufAllocator alloc() {
            return this.rootParent().alloc();
        }
        
        @Override
        public ByteOrder order() {
            return this.rootParent().order();
        }
        
        @Override
        public ByteBuf unwrap() {
            return null;
        }
        
        @Override
        public boolean isDirect() {
            return this.rootParent().isDirect();
        }
        
        @Override
        public int arrayOffset() {
            return this.idx(this.rootParent().arrayOffset());
        }
        
        @Override
        public boolean hasMemoryAddress() {
            return this.hasMemoryAddress;
        }
        
        @Override
        public long memoryAddress() {
            this.ensureAccessible();
            return this._memoryAddress();
        }
        
        @Override
        long _memoryAddress() {
            final AbstractByteBuf root = this.rootParent;
            return (root != null) ? (root._memoryAddress() + this.startIndex) : 0L;
        }
        
        @Override
        public ByteBuffer nioBuffer(final int index, final int length) {
            this.checkIndex(index, length);
            return this.rootParent().nioBuffer(this.idx(index), length);
        }
        
        @Override
        public ByteBuffer internalNioBuffer(final int index, final int length) {
            this.checkIndex(index, length);
            return (ByteBuffer)this.internalNioBuffer().position().limit(index + length);
        }
        
        private ByteBuffer internalNioBuffer() {
            if (this.tmpNioBuf == null) {
                this.tmpNioBuf = this.rootParent().nioBuffer(this.startIndex, this.maxFastCapacity);
            }
            return (ByteBuffer)this.tmpNioBuf.clear();
        }
        
        @Override
        public ByteBuffer[] nioBuffers(final int index, final int length) {
            this.checkIndex(index, length);
            return this.rootParent().nioBuffers(this.idx(index), length);
        }
        
        @Override
        public boolean hasArray() {
            return this.hasArray;
        }
        
        @Override
        public byte[] array() {
            this.ensureAccessible();
            return this.rootParent().array();
        }
        
        @Override
        public ByteBuf copy(final int index, final int length) {
            this.checkIndex(index, length);
            return this.rootParent().copy(this.idx(index), length);
        }
        
        @Override
        public int nioBufferCount() {
            return this.rootParent().nioBufferCount();
        }
        
        @Override
        protected byte _getByte(final int index) {
            return this.rootParent()._getByte(this.idx(index));
        }
        
        @Override
        protected short _getShort(final int index) {
            return this.rootParent()._getShort(this.idx(index));
        }
        
        @Override
        protected short _getShortLE(final int index) {
            return this.rootParent()._getShortLE(this.idx(index));
        }
        
        @Override
        protected int _getUnsignedMedium(final int index) {
            return this.rootParent()._getUnsignedMedium(this.idx(index));
        }
        
        @Override
        protected int _getUnsignedMediumLE(final int index) {
            return this.rootParent()._getUnsignedMediumLE(this.idx(index));
        }
        
        @Override
        protected int _getInt(final int index) {
            return this.rootParent()._getInt(this.idx(index));
        }
        
        @Override
        protected int _getIntLE(final int index) {
            return this.rootParent()._getIntLE(this.idx(index));
        }
        
        @Override
        protected long _getLong(final int index) {
            return this.rootParent()._getLong(this.idx(index));
        }
        
        @Override
        protected long _getLongLE(final int index) {
            return this.rootParent()._getLongLE(this.idx(index));
        }
        
        @Override
        public ByteBuf getBytes(final int index, final ByteBuf dst, final int dstIndex, final int length) {
            this.checkIndex(index, length);
            this.rootParent().getBytes(this.idx(index), dst, dstIndex, length);
            return this;
        }
        
        @Override
        public ByteBuf getBytes(final int index, final byte[] dst, final int dstIndex, final int length) {
            this.checkIndex(index, length);
            this.rootParent().getBytes(this.idx(index), dst, dstIndex, length);
            return this;
        }
        
        @Override
        public ByteBuf getBytes(final int index, final ByteBuffer dst) {
            this.checkIndex(index, dst.remaining());
            this.rootParent().getBytes(this.idx(index), dst);
            return this;
        }
        
        @Override
        protected void _setByte(final int index, final int value) {
            this.rootParent()._setByte(this.idx(index), value);
        }
        
        @Override
        protected void _setShort(final int index, final int value) {
            this.rootParent()._setShort(this.idx(index), value);
        }
        
        @Override
        protected void _setShortLE(final int index, final int value) {
            this.rootParent()._setShortLE(this.idx(index), value);
        }
        
        @Override
        protected void _setMedium(final int index, final int value) {
            this.rootParent()._setMedium(this.idx(index), value);
        }
        
        @Override
        protected void _setMediumLE(final int index, final int value) {
            this.rootParent()._setMediumLE(this.idx(index), value);
        }
        
        @Override
        protected void _setInt(final int index, final int value) {
            this.rootParent()._setInt(this.idx(index), value);
        }
        
        @Override
        protected void _setIntLE(final int index, final int value) {
            this.rootParent()._setIntLE(this.idx(index), value);
        }
        
        @Override
        protected void _setLong(final int index, final long value) {
            this.rootParent()._setLong(this.idx(index), value);
        }
        
        @Override
        protected void _setLongLE(final int index, final long value) {
            this.rootParent().setLongLE(this.idx(index), value);
        }
        
        @Override
        public ByteBuf setBytes(final int index, final byte[] src, final int srcIndex, final int length) {
            this.checkIndex(index, length);
            final ByteBuffer tmp = (ByteBuffer)this.internalNioBuffer().clear().position(index);
            tmp.put(src, srcIndex, length);
            return this;
        }
        
        @Override
        public ByteBuf setBytes(final int index, final ByteBuf src, final int srcIndex, final int length) {
            this.checkIndex(index, length);
            final ByteBuffer tmp = (ByteBuffer)this.internalNioBuffer().clear().position(index);
            tmp.put(src.nioBuffer(srcIndex, length));
            return this;
        }
        
        @Override
        public ByteBuf setBytes(final int index, final ByteBuffer src) {
            this.checkIndex(index, src.remaining());
            final ByteBuffer tmp = (ByteBuffer)this.internalNioBuffer().clear().position(index);
            tmp.put(src);
            return this;
        }
        
        @Override
        public ByteBuf getBytes(final int index, final OutputStream out, final int length) throws IOException {
            this.checkIndex(index, length);
            if (length != 0) {
                ByteBufUtil.readBytes(this.alloc(), this.internalNioBuffer().duplicate(), index, length, out);
            }
            return this;
        }
        
        @Override
        public int getBytes(final int index, final GatheringByteChannel out, final int length) throws IOException {
            final ByteBuffer buf = this.internalNioBuffer().duplicate();
            buf.clear().position(index).limit(index + length);
            return out.write(buf);
        }
        
        @Override
        public int getBytes(final int index, final FileChannel out, final long position, final int length) throws IOException {
            final ByteBuffer buf = this.internalNioBuffer().duplicate();
            buf.clear().position(index).limit(index + length);
            return out.write(buf, position);
        }
        
        @Override
        public int setBytes(final int index, final InputStream in, final int length) throws IOException {
            this.checkIndex(index, length);
            final AbstractByteBuf rootParent = this.rootParent();
            if (rootParent.hasArray()) {
                return rootParent.setBytes(this.idx(index), in, length);
            }
            final byte[] tmp = ByteBufUtil.threadLocalTempArray(length);
            final int readBytes = in.read(tmp, 0, length);
            if (readBytes <= 0) {
                return readBytes;
            }
            this.setBytes(index, tmp, 0, readBytes);
            return readBytes;
        }
        
        @Override
        public int setBytes(final int index, final ScatteringByteChannel in, final int length) throws IOException {
            try {
                return in.read(this.internalNioBuffer(index, length));
            }
            catch (final ClosedChannelException ignored) {
                return -1;
            }
        }
        
        @Override
        public int setBytes(final int index, final FileChannel in, final long position, final int length) throws IOException {
            try {
                return in.read(this.internalNioBuffer(index, length), position);
            }
            catch (final ClosedChannelException ignored) {
                return -1;
            }
        }
        
        @Override
        public int setCharSequence(final int index, final CharSequence sequence, final Charset charset) {
            return this.setCharSequence0(index, sequence, charset, false);
        }
        
        private int setCharSequence0(final int index, final CharSequence sequence, final Charset charset, final boolean expand) {
            if (charset.equals(CharsetUtil.UTF_8)) {
                final int length = ByteBufUtil.utf8MaxBytes(sequence);
                if (expand) {
                    this.ensureWritable0(length);
                    this.checkIndex0(index, length);
                }
                else {
                    this.checkIndex(index, length);
                }
                return ByteBufUtil.writeUtf8(this, index, length, sequence, sequence.length());
            }
            if (charset.equals(CharsetUtil.US_ASCII) || charset.equals(CharsetUtil.ISO_8859_1)) {
                final int length = sequence.length();
                if (expand) {
                    this.ensureWritable0(length);
                    this.checkIndex0(index, length);
                }
                else {
                    this.checkIndex(index, length);
                }
                return ByteBufUtil.writeAscii(this, index, sequence, length);
            }
            final byte[] bytes = sequence.toString().getBytes(charset);
            if (expand) {
                this.ensureWritable0(bytes.length);
            }
            this.setBytes(index, bytes);
            return bytes.length;
        }
        
        @Override
        public int writeCharSequence(final CharSequence sequence, final Charset charset) {
            final int written = this.setCharSequence0(this.writerIndex, sequence, charset, true);
            this.writerIndex += written;
            return written;
        }
        
        @Override
        public int forEachByte(final int index, final int length, final ByteProcessor processor) {
            this.checkIndex(index, length);
            final int ret = this.rootParent().forEachByte(this.idx(index), length, processor);
            return this.forEachResult(ret);
        }
        
        @Override
        public int forEachByteDesc(final int index, final int length, final ByteProcessor processor) {
            this.checkIndex(index, length);
            final int ret = this.rootParent().forEachByteDesc(this.idx(index), length, processor);
            return this.forEachResult(ret);
        }
        
        @Override
        public ByteBuf setZero(final int index, final int length) {
            this.checkIndex(index, length);
            this.rootParent().setZero(this.idx(index), length);
            return this;
        }
        
        @Override
        public ByteBuf writeZero(final int length) {
            this.ensureWritable(length);
            this.rootParent().setZero(this.idx(this.writerIndex), length);
            this.writerIndex += length;
            return this;
        }
        
        private int forEachResult(final int ret) {
            if (ret < this.startIndex) {
                return -1;
            }
            return ret - this.startIndex;
        }
        
        @Override
        public boolean isContiguous() {
            return this.rootParent().isContiguous();
        }
        
        private int idx(final int index) {
            return index + this.startIndex;
        }
        
        @Override
        protected void deallocate() {
            if (PlatformDependent.isJfrEnabled() && FreeBufferEvent.isEventEnabled()) {
                final FreeBufferEvent event = new FreeBufferEvent();
                if (event.shouldCommit()) {
                    event.fill(this, AdaptiveByteBufAllocator.class);
                    event.commit();
                }
            }
            if (this.chunk != null) {
                this.chunk.releaseSegment(this.startIndex);
            }
            this.tmpNioBuf = null;
            this.chunk = null;
            this.rootParent = null;
            if (this.handle instanceof Recycler.EnhancedHandle) {
                final Recycler.EnhancedHandle<AdaptiveByteBuf> enhancedHandle = (Recycler.EnhancedHandle)this.handle;
                enhancedHandle.unguardedRecycle(this);
            }
            else {
                this.handle.recycle(this);
            }
        }
    }
    
    interface ChunkAllocator
    {
        AbstractByteBuf allocate(final int p0, final int p1);
    }
    
    private interface ChunkControllerFactory
    {
        ChunkController create(final MagazineGroup p0);
    }
    
    private interface ChunkController
    {
        int computeBufferCapacity(final int p0, final int p1, final boolean p2);
        
        void initializeSharedStateIn(final ChunkController p0);
        
        Chunk newChunkAllocation(final int p0, final Magazine p1);
    }
    
    private interface ChunkReleasePredicate
    {
        boolean shouldReleaseChunk(final int p0);
    }
}
