diff --git a/bson/src/main/org/bson/BsonBinaryWriter.java b/bson/src/main/org/bson/BsonBinaryWriter.java index 20e73d97d4..aada1ecc12 100644 --- a/bson/src/main/org/bson/BsonBinaryWriter.java +++ b/bson/src/main/org/bson/BsonBinaryWriter.java @@ -334,6 +334,17 @@ public void pipe(final BsonReader reader) { pipeDocument(reader, null); } + @Override + public void pipe(final byte[] bytes, final int offset, final int length) { + if (getState() == State.VALUE) { + bsonOutput.writeByte(BsonType.DOCUMENT.getValue()); + writeCurrentName(); + } + int pipedDocumentStartPosition = bsonOutput.getPosition(); + bsonOutput.writeBytes(bytes, offset, length); + completePipeDocument(pipedDocumentStartPosition); + } + @Override public void pipe(final BsonReader reader, final List extraElements) { notNull("reader", reader); @@ -355,9 +366,7 @@ private void pipeDocument(final BsonReader reader, final List extra } int pipedDocumentStartPosition = bsonOutput.getPosition(); bsonOutput.writeInt32(size); - byte[] bytes = new byte[size - 4]; - bsonInput.readBytes(bytes); - bsonOutput.writeBytes(bytes); + bsonInput.pipe(bsonOutput, size - 4); binaryReader.setState(AbstractBsonReader.State.TYPE); @@ -371,17 +380,7 @@ private void pipeDocument(final BsonReader reader, final List extra setContext(getContext().getParentContext()); } - if (getContext() == null) { - setState(State.DONE); - } else { - if (getContext().getContextType() == BsonContextType.JAVASCRIPT_WITH_SCOPE) { - backpatchSize(); // size of the JavaScript with scope value - setContext(getContext().getParentContext()); - } - setState(getNextState()); - } - - validateSize(bsonOutput.getPosition() - pipedDocumentStartPosition); + completePipeDocument(pipedDocumentStartPosition); } else if (extraElements != null) { super.pipe(reader, extraElements); } else { @@ -389,6 +388,19 @@ private void pipeDocument(final BsonReader reader, final List extra } } + private void completePipeDocument(final int pipedDocumentStartPosition) { + if (getContext() == null) { + setState(State.DONE); + } else { + if (getContext().getContextType() == BsonContextType.JAVASCRIPT_WITH_SCOPE) { + backpatchSize(); // size of the JavaScript with scope value + setContext(getContext().getParentContext()); + } + setState(getNextState()); + } + validateSize(bsonOutput.getPosition() - pipedDocumentStartPosition); + } + /** * Sets a maximum size for documents from this point. * diff --git a/bson/src/main/org/bson/BsonWriter.java b/bson/src/main/org/bson/BsonWriter.java index c3da5dc605..0043ea8ecb 100644 --- a/bson/src/main/org/bson/BsonWriter.java +++ b/bson/src/main/org/bson/BsonWriter.java @@ -16,9 +16,13 @@ package org.bson; +import org.bson.io.ByteBufferBsonInput; import org.bson.types.Decimal128; import org.bson.types.ObjectId; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + /** * An interface for writing a logical BSON document using a push-oriented API. * @@ -357,4 +361,25 @@ public interface BsonWriter { */ void pipe(BsonReader reader); + /** + * Pipes a raw BSON document from the given byte array to this writer. + * + *

The default implementation wraps the bytes in a {@linkplain BsonBinaryReader} + * and calls {@link #pipe(BsonReader)}. Implementations may override this + * to write the raw bytes directly without intermediate object allocation.

+ * + * @param bytes the byte array containing the BSON document + * @param offset the offset into the byte array + * @param length the length of the BSON document + * @since 5.7 + */ + default void pipe(byte[] bytes, int offset, int length) { + try (BsonBinaryReader reader = new BsonBinaryReader( + new ByteBufferBsonInput( + new ByteBufNIO(ByteBuffer.wrap(bytes, offset, length) + .order(ByteOrder.LITTLE_ENDIAN))))) { + pipe(reader); + } + } + } diff --git a/bson/src/main/org/bson/RawBsonDocument.java b/bson/src/main/org/bson/RawBsonDocument.java index eb672bcef8..4d478234a3 100644 --- a/bson/src/main/org/bson/RawBsonDocument.java +++ b/bson/src/main/org/bson/RawBsonDocument.java @@ -144,6 +144,36 @@ public ByteBuf getByteBuffer() { return new ByteBufNIO(buffer); } + /** + * Returns the byte array backing this document. Changes to the returned array will be reflected in this document. + * + * @return the backing byte array + * @since 5.7 + */ + public byte[] getByteBacking() { + return bytes; + } + + /** + * Returns the offset into the {@linkplain #getByteBacking() backing byte array} where this document starts. + * + * @return the offset + * @since 5.7 + */ + public int getByteOffset() { + return offset; + } + + /** + * Returns the length of this document within the {@linkplain #getByteBacking() backing byte array}. + * + * @return the length + * @since 5.7 + */ + public int getByteLength() { + return length; + } + /** * Decode this into a document. * diff --git a/bson/src/main/org/bson/codecs/RawBsonDocumentCodec.java b/bson/src/main/org/bson/codecs/RawBsonDocumentCodec.java index 4d81b7f97a..2b45c58de1 100644 --- a/bson/src/main/org/bson/codecs/RawBsonDocumentCodec.java +++ b/bson/src/main/org/bson/codecs/RawBsonDocumentCodec.java @@ -16,13 +16,11 @@ package org.bson.codecs; -import org.bson.BsonBinaryReader; import org.bson.BsonBinaryWriter; import org.bson.BsonReader; import org.bson.BsonWriter; import org.bson.RawBsonDocument; import org.bson.io.BasicOutputBuffer; -import org.bson.io.ByteBufferBsonInput; /** * A simple BSONDocumentBuffer codec. It does not attempt to validate the contents of the underlying ByteBuffer. It assumes that it @@ -40,9 +38,7 @@ public RawBsonDocumentCodec() { @Override public void encode(final BsonWriter writer, final RawBsonDocument value, final EncoderContext encoderContext) { - try (BsonBinaryReader reader = new BsonBinaryReader(new ByteBufferBsonInput(value.getByteBuffer()))) { - writer.pipe(reader); - } + writer.pipe(value.getByteBacking(), value.getByteOffset(), value.getByteLength()); } @Override diff --git a/bson/src/main/org/bson/io/BsonInput.java b/bson/src/main/org/bson/io/BsonInput.java index 823355fe3e..012d9ec4a0 100644 --- a/bson/src/main/org/bson/io/BsonInput.java +++ b/bson/src/main/org/bson/io/BsonInput.java @@ -127,6 +127,15 @@ public interface BsonInput extends Closeable { */ boolean hasRemaining(); + /** + * Pipes the specified number of bytes from {@linkplain BsonInput this} input to the given {@linkplain BsonOutput output}. + * + * @param output the output to pipe to + * @param numBytes the number of bytes to pipe + * @since 5.7 + */ + void pipe(BsonOutput output, int numBytes); + @Override void close(); } diff --git a/bson/src/main/org/bson/io/ByteBufferBsonInput.java b/bson/src/main/org/bson/io/ByteBufferBsonInput.java index 2819bdcb09..1ab5ac9f5b 100644 --- a/bson/src/main/org/bson/io/ByteBufferBsonInput.java +++ b/bson/src/main/org/bson/io/ByteBufferBsonInput.java @@ -275,6 +275,24 @@ public boolean hasRemaining() { return buffer.hasRemaining(); } + @Override + public void pipe(final BsonOutput output, final int numBytes) { + ensureOpen(); + ensureAvailable(numBytes); + + if (buffer.isBackedByArray()) { + int position = buffer.position(); + int arrayOffset = buffer.arrayOffset(); + output.writeBytes(buffer.array(), arrayOffset + position, numBytes); + buffer.position(position + numBytes); + } else { + // Fallback: use temporary buffer for non-array-backed buffers + byte[] temp = new byte[numBytes]; + buffer.get(temp); + output.writeBytes(temp); + } + } + @Override public void close() { buffer.release();