diff --git a/bson/src/main/org/bson/ByteBuf.java b/bson/src/main/org/bson/ByteBuf.java index e44a97dfc67..089bb67885c 100644 --- a/bson/src/main/org/bson/ByteBuf.java +++ b/bson/src/main/org/bson/ByteBuf.java @@ -106,6 +106,54 @@ public interface ByteBuf { */ ByteBuf put(byte b); + /** + * Writes the given int value into this buffer at the current position, + * using the current byte order, and increments the position by 4. + * + * @param b the int value to be written + * @return this buffer + * @throws java.nio.BufferOverflowException if there are fewer than 4 bytes remaining in this buffer + * @throws java.nio.ReadOnlyBufferException if this buffer is read-only + * @since 5.4 + */ + ByteBuf putInt(int b); + + /** + * Writes the given int value into this buffer at the current position, + * using the current byte order, and increments the position by 4. + * + * @param b the int value to be written + * @return this buffer + * @throws java.nio.BufferOverflowException if there are fewer than 4 bytes remaining in this buffer + * @throws java.nio.ReadOnlyBufferException if this buffer is read-only + * @since 5.4 + */ + ByteBuf putInt(int index, int b); + + /** + * Writes the given double value into this buffer at the current position, + * using the current byte order, and increments the position by 8. + * + * @param b the double value to be written + * @return this buffer + * @throws java.nio.BufferOverflowException if there are fewer than 8 bytes remaining in this buffer + * @throws java.nio.ReadOnlyBufferException if this buffer is read-only + * @since 5.4 + */ + ByteBuf putDouble(double b); + + /** + * Writes the given long value into this buffer at the current position, + * using the current byte order, and increments the position by 8. + * + * @param b the long value to be written + * @return this buffer + * @throws java.nio.BufferOverflowException if there are fewer than 8 bytes remaining in this buffer + * @throws java.nio.ReadOnlyBufferException if this buffer is read-only + * @since 5.4 + */ + ByteBuf putLong(long b); + /** *

Flips this buffer. The limit is set to the current position and then the position is set to zero. If the mark is defined then it * is discarded.

diff --git a/bson/src/main/org/bson/ByteBufNIO.java b/bson/src/main/org/bson/ByteBufNIO.java index 83bfa7d893a..ffb6584ac64 100644 --- a/bson/src/main/org/bson/ByteBufNIO.java +++ b/bson/src/main/org/bson/ByteBufNIO.java @@ -97,6 +97,30 @@ public ByteBuf put(final byte b) { return this; } + @Override + public ByteBuf putInt(final int b) { + buf.putInt(b); + return this; + } + + @Override + public ByteBuf putInt(final int index, final int b) { + buf.putInt(index, b); + return this; + } + + @Override + public ByteBuf putDouble(final double b) { + buf.putDouble(b); + return this; + } + + @Override + public ByteBuf putLong(final long b) { + buf.putLong(b); + return this; + } + @Override public ByteBuf flip() { ((Buffer) buf).flip(); @@ -160,8 +184,13 @@ public ByteBuf get(final byte[] bytes, final int offset, final int length) { @Override public ByteBuf get(final int index, final byte[] bytes, final int offset, final int length) { - for (int i = 0; i < length; i++) { - bytes[offset + i] = buf.get(index + i); + if (buf.hasArray()) { + System.arraycopy(buf.array(), index, bytes, offset, length); + } else { + // Fallback to per-byte copying if no backing array is available. + for (int i = 0; i < length; i++) { + bytes[offset + i] = buf.get(index + i); + } } return this; } diff --git a/bson/src/main/org/bson/io/OutputBuffer.java b/bson/src/main/org/bson/io/OutputBuffer.java index 00f88cea706..7c1a64b2f85 100644 --- a/bson/src/main/org/bson/io/OutputBuffer.java +++ b/bson/src/main/org/bson/io/OutputBuffer.java @@ -70,6 +70,7 @@ public void writeInt32(final int value) { } @Override + @Deprecated public void writeInt32(final int position, final int value) { write(position, value >> 0); write(position + 1, value >> 8); diff --git a/driver-core/src/main/com/mongodb/internal/connection/ByteBufferBsonOutput.java b/driver-core/src/main/com/mongodb/internal/connection/ByteBufferBsonOutput.java index 40df1b867fd..d53ffe7c683 100644 --- a/driver-core/src/main/com/mongodb/internal/connection/ByteBufferBsonOutput.java +++ b/driver-core/src/main/com/mongodb/internal/connection/ByteBufferBsonOutput.java @@ -82,6 +82,84 @@ public void writeBytes(final byte[] bytes, final int offset, final int length) { position += length; } + @Override + public void writeInt32(final int value) { + ensureOpen(); + ByteBuf buf = getCurrentByteBuffer(); + if (buf.remaining() >= 4) { + buf.putInt(value); + position += 4; + } else { + // fallback for edge cases + super.writeInt32(value); + } + } + + + @Override + public void writeInt32(final int absolutePosition, final int value) { + ensureOpen(); + + if (absolutePosition < 0) { + throw new IllegalArgumentException(String.format("position must be >= 0 but was %d", absolutePosition)); + } + + if (absolutePosition + 3 > position - 1) { + throw new IllegalArgumentException(String.format("Cannot write 4 bytes starting at position %d: current size is %d bytes", + position - 1, + absolutePosition + 3)); + } + + BufferPositionPair bufferPositionPair = getBufferPositionPair(absolutePosition); + ByteBuf byteBuffer = getByteBufferAtIndex(bufferPositionPair.bufferIndex); + int capacity = byteBuffer.position() - bufferPositionPair.position; + + if (capacity >= 4) { + byteBuffer.putInt(bufferPositionPair.position, value); + } else { + // fallback for edge cases + int valueToWrite = value; + int pos = bufferPositionPair.position; + int bufferIndex = bufferPositionPair.bufferIndex; + + for (int i = 0; i < 4; i++) { + byteBuffer.put(pos++, (byte) valueToWrite); + valueToWrite = valueToWrite >> 8; + if (--capacity == 0) { + byteBuffer = getByteBufferAtIndex(++bufferIndex); + pos = 0; + capacity = byteBuffer.position(); + } + } + } + } + + @Override + public void writeDouble(final double value) { + ensureOpen(); + ByteBuf buf = getCurrentByteBuffer(); + if (buf.remaining() >= 8) { + buf.putDouble(value); + position += 8; + } else { + // fallback for edge cases + writeInt64(Double.doubleToRawLongBits(value)); + } + } + + @Override + public void writeInt64(final long value) { + ensureOpen(); + ByteBuf buf = getCurrentByteBuffer(); + if (buf.remaining() >= 8) { + buf.putLong(value); + position += 8; + } else { + // fallback for edge cases + super.writeInt64(value); + } + } + @Override public void writeByte(final int value) { ensureOpen(); diff --git a/driver-core/src/main/com/mongodb/internal/connection/CompositeByteBuf.java b/driver-core/src/main/com/mongodb/internal/connection/CompositeByteBuf.java index fa8cde2e517..47545753367 100644 --- a/driver-core/src/main/com/mongodb/internal/connection/CompositeByteBuf.java +++ b/driver-core/src/main/com/mongodb/internal/connection/CompositeByteBuf.java @@ -237,6 +237,26 @@ public ByteBuf put(final byte b) { throw new UnsupportedOperationException(); } + @Override + public ByteBuf putInt(final int b) { + throw new UnsupportedOperationException(); + } + + @Override + public ByteBuf putInt(final int index, final int b) { + throw new UnsupportedOperationException(); + } + + @Override + public ByteBuf putDouble(final double b) { + throw new UnsupportedOperationException(); + } + + @Override + public ByteBuf putLong(final long b) { + throw new UnsupportedOperationException(); + } + @Override public ByteBuf flip() { throw new UnsupportedOperationException(); diff --git a/driver-core/src/main/com/mongodb/internal/connection/netty/NettyByteBuf.java b/driver-core/src/main/com/mongodb/internal/connection/netty/NettyByteBuf.java index 074e77de04f..cb6ba587419 100644 --- a/driver-core/src/main/com/mongodb/internal/connection/netty/NettyByteBuf.java +++ b/driver-core/src/main/com/mongodb/internal/connection/netty/NettyByteBuf.java @@ -89,6 +89,30 @@ public ByteBuf put(final byte b) { return this; } + @Override + public ByteBuf putInt(final int b) { + proxied.writeInt(b); + return this; + } + + @Override + public ByteBuf putInt(final int index, final int b) { + proxied.setInt(index, b); + return this; + } + + @Override + public ByteBuf putDouble(final double b) { + proxied.writeDouble(b); + return this; + } + + @Override + public ByteBuf putLong(final long b) { + proxied.writeLong(b); + return this; + } + @Override public ByteBuf flip() { isWriting = !isWriting; diff --git a/driver-core/src/test/unit/com/mongodb/internal/connection/ByteBufTest.java b/driver-core/src/test/unit/com/mongodb/internal/connection/ByteBufTest.java new file mode 100644 index 00000000000..722d7d62fa4 --- /dev/null +++ b/driver-core/src/test/unit/com/mongodb/internal/connection/ByteBufTest.java @@ -0,0 +1,101 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.mongodb.internal.connection; + + +import org.bson.ByteBuf; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.assertEquals; + + +class ByteBufTest { + + static Stream bufferProviders() { + return Stream.of(new ByteBufSpecification.NettyBufferProvider(), new SimpleBufferProvider()); + } + + @ParameterizedTest + @MethodSource("bufferProviders") + void shouldPutInt(final BufferProvider provider) { + ByteBuf buffer = provider.getBuffer(1024); + try { + buffer.putInt(42); + buffer.flip(); + assertEquals(42, buffer.getInt()); + } finally { + buffer.release(); + } + } + + @ParameterizedTest + @MethodSource("bufferProviders") + void shouldPutLong(final BufferProvider provider) { + ByteBuf buffer = provider.getBuffer(1024); + try { + buffer.putLong(42L); + buffer.flip(); + assertEquals(42L, buffer.getLong()); + } finally { + buffer.release(); + } + } + + @ParameterizedTest + @MethodSource("bufferProviders") + void shouldPutDouble(final BufferProvider provider) { + ByteBuf buffer = provider.getBuffer(1024); + try { + buffer.putDouble(42.0D); + buffer.flip(); + assertEquals(42.0D, buffer.getDouble()); + } finally { + buffer.release(); + } + } + + @ParameterizedTest + @MethodSource("bufferProviders") + void shouldPutIntAtIndex(final BufferProvider provider) { + ByteBuf buffer = provider.getBuffer(1024); + try { + buffer.putInt(0); + buffer.putInt(0); + buffer.putInt(0); + buffer.putInt(0); + buffer.put((byte) 43); + buffer.put((byte) 44); + buffer.putInt(0, 22); + buffer.putInt(4, 23); + buffer.putInt(8, 24); + buffer.putInt(12, 25); + buffer.flip(); + + assertEquals(22, buffer.getInt()); + assertEquals(23, buffer.getInt()); + assertEquals(24, buffer.getInt()); + assertEquals(25, buffer.getInt()); + assertEquals(43, buffer.get()); + assertEquals(44, buffer.get()); + } finally { + buffer.release(); + } + } +} diff --git a/driver-core/src/test/unit/com/mongodb/internal/connection/ByteBufferBsonOutputTest.java b/driver-core/src/test/unit/com/mongodb/internal/connection/ByteBufferBsonOutputTest.java index 3a8a2c83acb..560e3177360 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/connection/ByteBufferBsonOutputTest.java +++ b/driver-core/src/test/unit/com/mongodb/internal/connection/ByteBufferBsonOutputTest.java @@ -16,14 +16,17 @@ package com.mongodb.internal.connection; -import com.mongodb.assertions.Assertions; import org.bson.BsonSerializationException; import org.bson.ByteBuf; +import org.bson.ByteBufNIO; import org.bson.types.ObjectId; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.MethodSource; import org.junit.jupiter.params.provider.ValueSource; import java.io.ByteArrayOutputStream; @@ -31,10 +34,12 @@ import java.nio.ByteBuffer; import java.nio.charset.CharacterCodingException; import java.nio.charset.StandardCharsets; +import java.util.List; import java.util.concurrent.ThreadLocalRandom; import java.util.function.BiConsumer; import java.util.function.Consumer; +import static com.mongodb.assertions.Assertions.fail; import static com.mongodb.internal.connection.ByteBufferBsonOutput.INITIAL_BUFFER_SIZE; import static com.mongodb.internal.connection.ByteBufferBsonOutput.MAX_BUFFER_SIZE; import static java.util.Arrays.asList; @@ -82,7 +87,7 @@ void positionAndSizeShouldBe0AfterConstructor(final String branchState) { break; } default: { - throw Assertions.fail(branchState); + throw fail(branchState); } } assertEquals(0, out.getPosition()); @@ -109,6 +114,51 @@ void shouldWriteByte(final boolean useBranch) { } } + @DisplayName("should write byte at position") + @ParameterizedTest + @ValueSource(booleans = {false, true}) + void shouldWriteByteAtPosition(final boolean useBranch) { + for (int offset = 0; offset < 5; offset++) { + try (ByteBufferBsonOutput out = new ByteBufferBsonOutput(new SimpleBufferProvider())) { + byte v = 11; + byte[] byteToWrite = {1, 2, 3, 4, 5}; + if (useBranch) { + try (ByteBufferBsonOutput.Branch branch = out.branch()) { + branch.writeBytes(byteToWrite); + branch.write(offset, v); + } + } else { + out.writeBytes(byteToWrite); + out.write(offset, v); + } + byteToWrite[offset] = v; + assertArrayEquals(byteToWrite, out.toByteArray()); + assertEquals(5, out.getPosition()); + assertEquals(5, out.size()); + + } + } + } + + @DisplayName("should throw exception when writing byte at invalid position") + @ParameterizedTest + @ValueSource(booleans = {false, true}) + void shouldThrowExceptionWhenWriteByteAtInvalidPosition(final boolean useBranch) { + try (ByteBufferBsonOutput out = new ByteBufferBsonOutput(new SimpleBufferProvider())) { + byte v = 11; + byte[] byteToWrite = {1, 2, 3, 4, 5}; + if (useBranch) { + try (ByteBufferBsonOutput.Branch branch = out.branch()) { + out.writeBytes(byteToWrite); + assertThrows(IllegalArgumentException.class, () -> branch.write(-1, v)); + } + } else { + out.writeBytes(byteToWrite); + assertThrows(IllegalArgumentException.class, () -> out.write(-1, v)); + } + } + } + @DisplayName("should write a bytes") @ParameterizedTest @ValueSource(booleans = {false, true}) @@ -622,4 +672,270 @@ void shouldHandleMixedBranchingAndTruncating(final int reps) throws CharacterCod assertEquals(expected.toString(), StandardCharsets.UTF_8.newDecoder().decode(ByteBuffer.wrap(out.toByteArray())).toString()); } } + + @Test + @DisplayName("should throw exception when calling writeInt32 at absolute position where integer would not fit") + void shouldThrowExceptionWhenIntegerDoesNotFitWriteInt32() { + try (ByteBufferBsonOutput output = new ByteBufferBsonOutput(new SimpleBufferProvider())) { + // Write 10 bytes (position becomes 10) + for (int i = 0; i < 10; i++) { + output.writeByte(0); + } + + // absolutePosition = 7 would require bytes at positions 7,8,9,10, but the last written element was at 9. + assertThrows(IllegalArgumentException.class, () -> + output.writeInt32(7, 5678) + ); + } + } + + @Test + @DisplayName("should throw exception when calling writeInt32 with negative absolute position") + void shouldThrowExceptionWhenAbsolutePositionIsNegative() { + try (ByteBufferBsonOutput output = new ByteBufferBsonOutput(new SimpleBufferProvider())) { + Assertions.assertThrows(IllegalArgumentException.class, () -> + output.writeInt32(-1, 5678) + ); + } + } + + static java.util.stream.Stream shouldWriteInt32AbsoluteValueWithinSpanningBuffers() { + return java.util.stream.Stream.of( + Arguments.of( + 0, // absolute position + 0x09080706, // int value + asList( + // initial data + new byte[]{0, 1, 2, 3}, + new byte[]{4, 5, 6, 7}), + asList( + // expected BsonByteBufferOutput data + new byte[]{0x06, 0x07, 0x08, 0x09}, + new byte[]{4, 5, 6, 7})), + Arguments.of(1, 0x09080706, + asList(new byte[]{0, 1, 2, 3}, new byte[]{4, 5, 6, 7}), + asList(new byte[]{0, 0x06, 0x07, 0x08}, new byte[]{0x09, 5, 6, 7})), + Arguments.of(2, 0x09080706, + asList(new byte[]{0, 1, 2, 3}, new byte[]{4, 5, 6, 7}), + asList(new byte[]{0, 1, 0x06, 0x07}, new byte[]{0x08, 0x09, 6, 7}) + ), + Arguments.of(3, 0x09080706, + asList(new byte[]{0, 1, 2, 3}, new byte[]{4, 5, 6, 7}), + asList(new byte[]{0, 1, 2, 0x06}, new byte[]{0x07, 0x08, 0x09, 7}) + ), + Arguments.of(4, 0x09080706, + asList(new byte[]{0, 1, 2, 3}, new byte[]{4, 5, 6, 7}), + asList(new byte[]{0, 1, 2, 3}, new byte[]{0x06, 0x07, 0x08, 0x09}) + )); + } + + @ParameterizedTest + @MethodSource + void shouldWriteInt32AbsoluteValueWithinSpanningBuffers( + final int absolutePosition, + final int intValue, + final List initialData, + final List expectedBuffers) { + + try (ByteBufferBsonOutput output = + new ByteBufferBsonOutput(size -> new ByteBufNIO(ByteBuffer.allocate(4)))) { + + //given + initialData.forEach(output::writeBytes); + + //when + output.writeInt32(absolutePosition, intValue); + + //then + List buffers = output.getByteBuffers(); + assertEquals(expectedBuffers.size(), buffers.size(), "Number of buffers mismatch"); + for (int i = 0; i < expectedBuffers.size(); i++) { + assertArrayEquals(expectedBuffers.get(i), buffers.get(i).array(), + "Buffer " + i + " contents mismatch"); + } + } + } + + static java.util.stream.Stream int32SpanningBuffersData() { + return java.util.stream.Stream.of( + // Test case 1: No initial data; entire int written into one buffer. + Arguments.of(0x09080706, + asList( + // No initial data + ), + asList( + // expected BsonByteBufferOutput data + new byte[]{0x06, 0x07, 0x08, 0x09}), + 4, // expected overall position after write (0 + 4) + 4 // expected last buffer position (buffer fully written) + ), + Arguments.of(0x09080706, + asList(new byte[]{0}), + asList(new byte[]{0, 0x06, 0x07, 0x08}, new byte[]{0x09, 0, 0, 0}), 5, 1 + ), + Arguments.of(0x09080706, + asList(new byte[]{0, 1}), + asList(new byte[]{0, 1, 0x06, 0x07}, new byte[]{0x08, 0x09, 0, 0}), 6, 2 + ), + Arguments.of(0x09080706, + asList(new byte[]{0, 1, 2}), + asList(new byte[]{0, 1, 2, 0x06}, new byte[]{0x07, 0x08, 0x09, 0}), 7, 3 + ), + Arguments.of(0x09080706, + asList(new byte[]{0, 1, 2, 3}), + asList(new byte[]{0, 1, 2, 3}, new byte[]{0x06, 0x07, 0x08, 0x09}), 8, 4 + )); + } + + static java.util.stream.Stream int64SpanningBuffersData() { + return java.util.stream.Stream.of( + // Test case 1: No initial data; entire long written into one buffer. + Arguments.of(0x0A0B0C0D0E0F1011L, + asList( + // No initial data + ), + asList( + // expected BsonByteBufferOutput data + new byte[]{0x11, 0x10, 0x0F, 0x0E, 0x0D, 0x0C, 0x0B, 0x0A} + ), + 8, // expected overall position after write (0 + 8) + 8 // expected last buffer position (buffer fully written) + ), + Arguments.of(0x0A0B0C0D0E0F1011L, + asList(new byte[]{0}), + asList(new byte[]{0, 0x11, 0x10, 0x0F, 0x0E, 0x0D, 0x0C, 0x0B}, new byte[]{0x0A, 0, 0, 0, 0, 0, 0, 0}), + 9, 1 + ), + Arguments.of(0x0A0B0C0D0E0F1011L, + asList(new byte[]{0, 1}), + asList(new byte[]{0, 1, 0x11, 0x10, 0x0F, 0x0E, 0x0D, 0x0C}, new byte[]{0x0B, 0x0A, 0, 0, 0, 0, 0, 0}), + 10, 2 + ), + Arguments.of(0x0A0B0C0D0E0F1011L, + asList(new byte[]{0, 1, 2}), + asList(new byte[]{0, 1, 2, 0x11, 0x10, 0x0F, 0x0E, 0x0D}, new byte[]{0x0C, 0x0B, 0x0A, 0, 0, 0, 0, 0}), + 11, 3 + ), + Arguments.of(0x0A0B0C0D0E0F1011L, + asList(new byte[]{0, 1, 2, 3}), + asList(new byte[]{0, 1, 2, 3, 0x11, 0x10, 0x0F, 0x0E}, new byte[]{0x0D, 0x0C, 0x0B, 0x0A, 0, 0, 0, 0}), + 12, 4 + ), + Arguments.of(0x0A0B0C0D0E0F1011L, + asList(new byte[]{0, 1, 2, 3, 4}), + asList(new byte[]{0, 1, 2, 3, 4, 0x11, 0x10, 0x0F}, new byte[]{0x0E, 0x0D, 0x0C, 0x0B, 0x0A, 0, 0, 0}), + 13, 5 + ), + Arguments.of(0x0A0B0C0D0E0F1011L, + asList(new byte[]{0, 1, 2, 3, 4, 5}), + asList(new byte[]{0, 1, 2, 3, 4, 5, 0x11, 0x10}, new byte[]{0x0F, 0x0E, 0x0D, 0x0C, 0x0B, 0x0A, 0, 0}), + 14, 6 + ), Arguments.of(0x0A0B0C0D0E0F1011L, + asList(new byte[]{0, 1, 2, 3, 4, 5, 6}), + asList(new byte[]{0, 1, 2, 3, 4, 5, 6, 0x11}, new byte[]{0x10, 0x0F, 0x0E, 0x0D, 0x0C, 0x0B, 0x0A, 0}), + 15, 7 + ), + Arguments.of(0x0A0B0C0D0E0F1011L, + asList(new byte[]{0, 1, 2, 3, 4, 5, 6, 7}), + asList(new byte[]{0, 1, 2, 3, 4, 5, 6, 7}, new byte[]{0x11, 0x10, 0x0F, 0x0E, 0x0D, 0x0C, 0x0B, 0x0A}), + 16, 8 + ) + ); + } + + @ParameterizedTest + @MethodSource("int32SpanningBuffersData") + void shouldWriteInt32WithinSpanningBuffers( + final int intValue, + final List initialData, + final List expectedBuffers, + final int expectedOutputPosition, + final int expectedLastBufferPosition) { + + try (ByteBufferBsonOutput output = + new ByteBufferBsonOutput(size -> new ByteBufNIO(ByteBuffer.allocate(4)))) { + + //given + initialData.forEach(output::writeBytes); + + //when + output.writeInt32(intValue); + + //then + //getByteBuffers returns ByteBuffers with limit() set to position, position set to 0. + List buffers = output.getByteBuffers(); + assertEquals(expectedBuffers.size(), buffers.size(), "Number of buffers mismatch"); + for (int i = 0; i < expectedBuffers.size(); i++) { + assertArrayEquals(expectedBuffers.get(i), buffers.get(i).array(), + "Buffer " + i + " contents mismatch"); + } + + assertEquals(expectedLastBufferPosition, buffers.get(buffers.size() - 1).limit()); + assertEquals(expectedOutputPosition, output.getPosition()); + } + } + + @ParameterizedTest + @MethodSource("int64SpanningBuffersData") + void shouldWriteInt64WithinSpanningBuffers( + final long intValue, + final List initialData, + final List expectedBuffers, + final int expectedOutputPosition, + final int expectedLastBufferPosition) { + + try (ByteBufferBsonOutput output = + new ByteBufferBsonOutput(size -> new ByteBufNIO(ByteBuffer.allocate(8)))) { + + //given + initialData.forEach(output::writeBytes); + + //when + output.writeInt64(intValue); + + //then + //getByteBuffers returns ByteBuffers with limit() set to position, position set to 0. + List buffers = output.getByteBuffers(); + assertEquals(expectedBuffers.size(), buffers.size(), "Number of buffers mismatch"); + for (int i = 0; i < expectedBuffers.size(); i++) { + assertArrayEquals(expectedBuffers.get(i), buffers.get(i).array(), + "Buffer " + i + " contents mismatch"); + } + + assertEquals(expectedLastBufferPosition, buffers.get(buffers.size() - 1).limit()); + assertEquals(expectedOutputPosition, output.getPosition()); + } + } + + @ParameterizedTest + @MethodSource("int64SpanningBuffersData") + void shouldWriteDoubleWithinSpanningBuffers( + final long intValue, + final List initialData, + final List expectedBuffers, + final int expectedOutputPosition, + final int expectedLastBufferPosition) { + + try (ByteBufferBsonOutput output = + new ByteBufferBsonOutput(size -> new ByteBufNIO(ByteBuffer.allocate(8)))) { + + //given + initialData.forEach(output::writeBytes); + + //when + output.writeDouble(Double.longBitsToDouble(intValue)); + + //then + //getByteBuffers returns ByteBuffers with limit() set to position, position set to 0. + List buffers = output.getByteBuffers(); + assertEquals(expectedBuffers.size(), buffers.size(), "Number of buffers mismatch"); + for (int i = 0; i < expectedBuffers.size(); i++) { + assertArrayEquals(expectedBuffers.get(i), buffers.get(i).array(), + "Buffer " + i + " contents mismatch"); + } + + assertEquals(expectedLastBufferPosition, buffers.get(buffers.size() - 1).limit()); + assertEquals(expectedOutputPosition, output.getPosition()); + } + } }