private static Arbitrary<Encoder> encodedTypeEncoder(
        final Encoding encoding,
        final CharGenerationMode charGenerationMode)
    {
        final PrimitiveValue minValue = encoding.applicableMinValue();
        final PrimitiveValue maxValue = encoding.applicableMaxValue();

        switch (encoding.primitiveType())
        {
            case CHAR:
                assert minValue.longValue() <= maxValue.longValue();
                return chars(charGenerationMode).map(c ->
                    (builder, buffer, offset, limit) ->
                    {
                        builder.appendLine().append(c).append(" @ ").append(offset)
                            .append("[").append(BitUtil.SIZE_OF_BYTE).append("]");
                        buffer.putChar(offset, c, encoding.byteOrder());
                    });

            case UINT8:
            case INT8:
                assert (short)minValue.longValue() <= (short)maxValue.longValue();
                return Arbitraries.shorts()
                    .between((short)minValue.longValue(), (short)maxValue.longValue())
                    .map(b -> (builder, buffer, offset, limit) ->
                    {
                        builder.appendLine().append((byte)(short)b).append(" @ ").append(offset)
                            .append("[").append(BitUtil.SIZE_OF_BYTE).append("]");
                        buffer.putByte(offset, (byte)(short)b);
                    });

            case UINT16:
            case INT16:
                assert (int)minValue.longValue() <= (int)maxValue.longValue();
                return Arbitraries.integers()
                    .between((int)minValue.longValue(), (int)maxValue.longValue())
                    .map(s -> (builder, buffer, offset, limit) ->
                    {
                        builder.appendLine().append((short)(int)s).append(" @ ").append(offset)
                            .append("[").append(BitUtil.SIZE_OF_SHORT).append("]");
                        buffer.putShort(offset, (short)(int)s, encoding.byteOrder());
                    });

            case UINT32:
            case INT32:
                assert minValue.longValue() <= maxValue.longValue();
                return Arbitraries.longs()
                    .between(minValue.longValue(), maxValue.longValue())
                    .map(i -> (builder, buffer, offset, limit) ->
                    {
                        builder.appendLine().append((int)(long)i).append(" @ ").append(offset)
                            .append("[").append(BitUtil.SIZE_OF_INT).append("]");
                        buffer.putInt(offset, (int)(long)i, encoding.byteOrder());
                    });

            case UINT64:
                return Arbitraries.longs()
                    .map(l -> (builder, buffer, offset, limit) ->
                    {
                        final long nullValue = encoding.applicableNullValue().longValue();
                        final long nonNullValue = l == nullValue ? minValue.longValue() : l;
                        builder.appendLine().append(nonNullValue).append(" @ ").append(offset)
                            .append("[").append(BitUtil.SIZE_OF_LONG).append("]");
                        buffer.putLong(offset, nonNullValue, encoding.byteOrder());
                    });

            case INT64:
                assert minValue.longValue() <= maxValue.longValue();
                return Arbitraries.longs()
                    .between(minValue.longValue(), maxValue.longValue())
                    .map(l -> (builder, buffer, offset, limit) ->
                    {
                        builder.appendLine().append(l).append(" @ ").append(offset)
                            .append("[").append(BitUtil.SIZE_OF_LONG).append("]");
                        buffer.putLong(offset, l, encoding.byteOrder());
                    });

            case FLOAT:
                return Arbitraries.floats()
                    .map(f -> (builder, buffer, offset, limit) ->
                    {
                        builder.appendLine().append(f).append(" @ ").append(offset)
                            .append("[").append(BitUtil.SIZE_OF_FLOAT).append("]");
                        buffer.putFloat(offset, f, encoding.byteOrder());
                    });

            case DOUBLE:
                return Arbitraries.doubles()
                    .map(d -> (builder, buffer, offset, limit) ->
                    {
                        builder.appendLine().append(d).append(" @ ").append(offset)
                            .append("[").append(BitUtil.SIZE_OF_DOUBLE).append("]");
                        buffer.putDouble(offset, d, encoding.byteOrder());
                    });

            default:
                throw new IllegalArgumentException("Unsupported type: " + encoding.primitiveType());
        }
    }
This combinator adds duplicates to an arbitrary list. We prefer this to JQwik's built-in functionality,
as that is inefficient and dominates test runs.
<p>
This method works by generating a list of integers, which represent, in an alternating manner,
the number of items to skip before <i>selecting</i> an item to duplicate
and the number of items to skip before <i>inserting</i> the duplicate.

@param maxDuplicates the maximum number of duplicates to add
@param arbitrary     the arbitrary list to duplicate items in
@param <T>           the type of the list
@return an arbitrary list with duplicates
sbe-tool/src/propertyTest/java/uk/co/real_logic/sbe/properties/arbitraries/SbeArbitraries.java