import java.nio.*;
import java.util.function.Function;
import java.util.function.ObjIntConsumer;
import java.util.function.ToIntFunction;

public class BufferSliceBug
{
    public static void main( String[] args )
    {
        class BufferKind< T extends Buffer>
        {
            String name;
            Function<ByteBuffer, T > asTypedBuffer;
            ToIntFunction< T > get;
            ObjIntConsumer< T > put;

            BufferKind( String name, Function< ByteBuffer, T > asTypedBuffer, ToIntFunction< T > get, ObjIntConsumer< T > put )
            {
                this.name = name;
                this.asTypedBuffer = asTypedBuffer;
                this.get = get;
                this.put = put;
            }

            void heapLE()
            {
                test( this.asTypedBuffer.apply( ByteBuffer.allocate( 32 ).order( ByteOrder.LITTLE_ENDIAN ) ) );
            }

            void heapBE()
            {
                test( this.asTypedBuffer.apply( ByteBuffer.allocate( 32 ).order( ByteOrder.BIG_ENDIAN ) ) );
            }

            void directLE()
            {
                test( this.asTypedBuffer.apply( ByteBuffer.allocateDirect( 32 ).order( ByteOrder.LITTLE_ENDIAN ) ) );
            }

            void directBE()
            {
                test( this.asTypedBuffer.apply( ByteBuffer.allocateDirect( 32 ).order( ByteOrder.BIG_ENDIAN ) ) );
            }

            private void test( T buffer )
            {
                // Fill buffer with primitives of values [0..]
                fill( buffer );

                // Take a slice and verify.
                T slice = (T) buffer.slice( 1, buffer.capacity() - 1 );
                verify( slice, 1 );
            }

            private void fill( T buffer )
            {
                int i = 0;
                while ( buffer.hasRemaining() )
                {
                    this.put.accept( buffer, i++ );
                }
                buffer.clear();
            }

            private void verify( T buffer, int from )
            {
                int i = from;
                while ( buffer.hasRemaining() )
                {
                    if ( this.get.applyAsInt( buffer ) != i )
                    {
                        System.out.println( "Broken : " + buffer.getClass().getSimpleName() );
                        return;
                    }
                    i++;
                }
                System.out.println( "Correct : " + buffer.getClass().getSimpleName() );
            }
        }

        BufferKind< ? >[] kinds =
                {
                        new BufferKind<>( "ByteBuffer", ByteBuffer::duplicate, ByteBuffer::get, ( buffer, value ) -> buffer.put( (byte) value ) ),
                        new BufferKind<>( "ShortBuffer", ByteBuffer::asShortBuffer, ShortBuffer::get, (buffer, value ) -> buffer.put( (short) value ) ),
                        new BufferKind<>( "IntBuffer", ByteBuffer::asIntBuffer, IntBuffer::get, IntBuffer::put ),
                        new BufferKind<>( "LongBuffer", ByteBuffer::asLongBuffer, buffer -> (int) buffer.get(), LongBuffer::put ),
                        new BufferKind<>( "FloatBuffer", ByteBuffer::asFloatBuffer, buffer -> (int) buffer.get(), FloatBuffer::put ),
                        new BufferKind<>( "DoubleBuffer", ByteBuffer::asDoubleBuffer, buffer -> (int) buffer.get(), DoubleBuffer::put ),
                        new BufferKind<>( "CharBuffer", ByteBuffer::asCharBuffer, CharBuffer::get, ( buffer, value ) -> buffer.put( (char) value ) ),
                };

        for ( BufferKind< ? > kind : kinds )
        {
            System.out.println( kind.name );
            kind.heapLE();
            kind.heapBE();
            kind.directLE();
            kind.directBE();
            System.out.println();
        }
    }
}