BufferRecyclerPoolTest.java

package com.fasterxml.jackson.core.io;

import java.io.IOException;
import java.io.OutputStream;

import org.junit.jupiter.api.Test;

import com.fasterxml.jackson.core.JUnit5TestBase;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.base.GeneratorBase;
import com.fasterxml.jackson.core.util.BufferRecycler;
import com.fasterxml.jackson.core.util.JsonRecyclerPools;
import com.fasterxml.jackson.core.util.RecyclerPool;

import static org.junit.jupiter.api.Assertions.*;

// Tests for [core#1064] wrt custom `BufferRecycler`
class BufferRecyclerPoolTest extends JUnit5TestBase
{
    @Test
    void noOp() throws Exception {
        // no-op pool doesn't actually pool anything, so avoid checking it
        checkBufferRecyclerPoolImpl(JsonRecyclerPools.nonRecyclingPool(), false, true);
    }

    @Test
    void threadLocal() throws Exception {
        checkBufferRecyclerPoolImpl(JsonRecyclerPools.threadLocalPool(), true, false);
    }

    @Test
    @Deprecated
    void lockFree() throws Exception {
        checkBufferRecyclerPoolImpl(JsonRecyclerPools.newLockFreePool(), true, true);
    }

    @Test
    void concurrentDequeue() throws Exception {
        checkBufferRecyclerPoolImpl(JsonRecyclerPools.newConcurrentDequePool(), true, true);
    }

    @Test
    void bounded() throws Exception {
        checkBufferRecyclerPoolImpl(JsonRecyclerPools.newBoundedPool(1), true, true);
    }

    @Test
    void pluggingPool() throws Exception {
        checkBufferRecyclerPoolImpl(new TestPool(), true, true);
    }

    private void checkBufferRecyclerPoolImpl(RecyclerPool<BufferRecycler> pool,
            boolean checkPooledResource,
            boolean implementsClear)
        throws Exception
    {
        JsonFactory jsonFactory = JsonFactory.builder()
                .recyclerPool(pool)
                .build();
        BufferRecycler usedBufferRecycler = write("test", jsonFactory, 6);

        if (checkPooledResource) {
            // acquire the pooled BufferRecycler again and check if it is the same instance used before
            BufferRecycler pooledBufferRecycler = pool.acquireAndLinkPooled();
            assertSame(usedBufferRecycler, pooledBufferRecycler);
            // might as well return it back
            pooledBufferRecycler.releaseToPool();
        }

        // Also: check `clear()` method -- optional, but supported by all impls
        // except for ThreadLocal-based one
        if (implementsClear) {
            assertTrue(pool.clear());
    
            // cannot easily verify anything else except that we do NOT get the same recycled instance
            BufferRecycler br2 = pool.acquireAndLinkPooled();
            assertNotNull(br2);
            assertNotSame(usedBufferRecycler, br2);
        } else {
            assertFalse(pool.clear());
        }
    }

    private BufferRecycler write(Object value, JsonFactory jsonFactory, int expectedSize) throws Exception {
        BufferRecycler bufferRecycler;
        NopOutputStream out = new NopOutputStream();
        try (JsonGenerator gen = jsonFactory.createGenerator(out)) {
            bufferRecycler = ((GeneratorBase) gen).ioContext().bufferRecycler();
            gen.writeObject(value);
        }
        assertEquals(expectedSize, out.size);
        return bufferRecycler;
    }

    private static class NopOutputStream extends OutputStream {
        protected int size = 0;

        NopOutputStream() { }

        @Override
        public void write(int b) throws IOException { ++size; }

        @Override
        public void write(byte[] b) throws IOException { size += b.length; }

        @Override
        public void write(byte[] b, int offset, int len) throws IOException { size += len; }
    }


    @SuppressWarnings("serial")
    class TestPool implements RecyclerPool<BufferRecycler>
    {
        private BufferRecycler bufferRecycler;

        @Override
        public BufferRecycler acquirePooled() {
            if (bufferRecycler != null) {
                BufferRecycler tmp = bufferRecycler;
                this.bufferRecycler = null;
                return tmp;
            }
            return new BufferRecycler();
        }

        @Override
        public void releasePooled(BufferRecycler r) {
            if (bufferRecycler == r) { // just sanity check for this test
                throw new IllegalStateException("BufferRecyler released more than once");
            }
            bufferRecycler = r;
        }

        @Override
        public int pooledCount() {
            return (bufferRecycler == null) ? 0 : 1;
        }

        @Override
        public boolean clear() {
            bufferRecycler = null;
            return true;
        }
    }
}