1818
1919import org .bson .ByteBuf ;
2020import org .bson .ByteBufNIO ;
21+ import org .bson .types .ObjectId ;
2122
2223import java .io .IOException ;
2324import java .io .OutputStream ;
25+ import java .nio .Buffer ;
2426import java .nio .ByteBuffer ;
2527import java .util .Arrays ;
28+ import java .util .Collections ;
2629import java .util .List ;
2730
2831import static java .lang .String .format ;
3235 * A BSON output stream that stores the output in a single, un-pooled byte array.
3336 */
3437public class BasicOutputBuffer extends OutputBuffer {
35- private byte [] buffer ;
36- private int position ;
38+
39+ /**
40+ * This ByteBuffer allows us to write ObjectIDs without allocating a temporary array per object, and enables us
41+ * to leverage JVM intrinsics for writing little-endian numeric values.
42+ */
43+ private ByteBuffer buffer ;
3744
3845 /**
3946 * Construct an instance with a default initial byte array size.
@@ -48,7 +55,8 @@ public BasicOutputBuffer() {
4855 * @param initialSize the initial size of the byte array
4956 */
5057 public BasicOutputBuffer (final int initialSize ) {
51- buffer = new byte [initialSize ];
58+ // Allocate heap buffer to ensure we can access underlying array
59+ buffer = ByteBuffer .allocate (initialSize ).order (LITTLE_ENDIAN );
5260 }
5361
5462 /**
@@ -58,50 +66,76 @@ public BasicOutputBuffer(final int initialSize) {
5866 * @since 3.3
5967 */
6068 public byte [] getInternalBuffer () {
61- return buffer ;
69+ return buffer . array () ;
6270 }
6371
6472 @ Override
6573 public void write (final byte [] b ) {
74+ writeBytes (b , 0 , b .length );
75+ }
76+
77+ @ Override
78+ public byte [] toByteArray () {
79+ ensureOpen ();
80+ return Arrays .copyOf (buffer .array (), buffer .position ());
81+ }
82+
83+ @ Override
84+ public void writeInt32 (final int value ) {
85+ ensureOpen ();
86+ ensure (4 );
87+ buffer .putInt (value );
88+ }
89+
90+ @ Override
91+ public void writeInt32 (final int position , final int value ) {
92+ ensureOpen ();
93+ checkPosition (position , 4 );
94+ buffer .putInt (position , value );
95+ }
96+
97+ @ Override
98+ public void writeInt64 (final long value ) {
99+ ensureOpen ();
100+ ensure (8 );
101+ buffer .putLong (value );
102+ }
103+
104+ @ Override
105+ public void writeObjectId (final ObjectId value ) {
66106 ensureOpen ();
67- write (b , 0 , b .length );
107+ ensure (12 );
108+ value .putToByteBuffer (buffer );
68109 }
69110
70111 @ Override
71112 public void writeBytes (final byte [] bytes , final int offset , final int length ) {
72113 ensureOpen ();
73114
74115 ensure (length );
75- System .arraycopy (bytes , offset , buffer , position , length );
76- position += length ;
116+ buffer .put (bytes , offset , length );
77117 }
78118
79119 @ Override
80120 public void writeByte (final int value ) {
81121 ensureOpen ();
82122
83123 ensure (1 );
84- buffer [ position ++] = ( byte ) (0xFF & value );
124+ buffer . put (( byte ) (0xFF & value ) );
85125 }
86126
87127 @ Override
88128 protected void write (final int absolutePosition , final int value ) {
89129 ensureOpen ();
130+ checkPosition (absolutePosition , 1 );
90131
91- if (absolutePosition < 0 ) {
92- throw new IllegalArgumentException (format ("position must be >= 0 but was %d" , absolutePosition ));
93- }
94- if (absolutePosition > position - 1 ) {
95- throw new IllegalArgumentException (format ("position must be <= %d but was %d" , position - 1 , absolutePosition ));
96- }
97-
98- buffer [absolutePosition ] = (byte ) (0xFF & value );
132+ buffer .put (absolutePosition , (byte ) (0xFF & value ));
99133 }
100134
101135 @ Override
102136 public int getPosition () {
103137 ensureOpen ();
104- return position ;
138+ return buffer . position () ;
105139 }
106140
107141 /**
@@ -110,29 +144,32 @@ public int getPosition() {
110144 @ Override
111145 public int getSize () {
112146 ensureOpen ();
113- return position ;
147+ return buffer . position () ;
114148 }
115149
116150 @ Override
117151 public int pipe (final OutputStream out ) throws IOException {
118152 ensureOpen ();
119- out .write (buffer , 0 , position );
120- return position ;
153+ out .write (buffer . array () , 0 , buffer . position () );
154+ return buffer . position () ;
121155 }
122156
123157 @ Override
124158 public void truncateToPosition (final int newPosition ) {
125159 ensureOpen ();
126- if (newPosition > position || newPosition < 0 ) {
160+ if (newPosition > buffer . position () || newPosition < 0 ) {
127161 throw new IllegalArgumentException ();
128162 }
129- position = newPosition ;
163+ // The cast is required for compatibility with JDK 9+ where ByteBuffer's position method is inherited from Buffer.
164+ ((Buffer ) buffer ).position (newPosition );
130165 }
131166
132167 @ Override
133168 public List <ByteBuf > getByteBuffers () {
134169 ensureOpen ();
135- return Arrays .asList (new ByteBufNIO (ByteBuffer .wrap (buffer , 0 , position ).duplicate ().order (LITTLE_ENDIAN )));
170+ // Create a flipped copy of the buffer for reading. Note that ByteBufNIO overwrites the endian-ness.
171+ ByteBuffer flipped = ByteBuffer .wrap (buffer .array (), 0 , buffer .position ());
172+ return Collections .singletonList (new ByteBufNIO (flipped ));
136173 }
137174
138175 @ Override
@@ -147,19 +184,32 @@ private void ensureOpen() {
147184 }
148185
149186 private void ensure (final int more ) {
150- int need = position + more ;
151- if (need <= buffer .length ) {
187+ int length = buffer .position ();
188+ int need = length + more ;
189+ if (need <= buffer .capacity ()) {
152190 return ;
153191 }
154192
155- int newSize = buffer . length * 2 ;
193+ int newSize = length * 2 ;
156194 if (newSize < need ) {
157195 newSize = need + 128 ;
158196 }
159197
160- byte [] n = new byte [ newSize ] ;
161- System . arraycopy (buffer , 0 , n , 0 , position );
162- buffer = n ;
198+ ByteBuffer tmp = ByteBuffer . allocate ( newSize ). order ( LITTLE_ENDIAN ) ;
199+ tmp . put (buffer . array () , 0 , length ); // Avoids covariant call to flip on jdk8
200+ this . buffer = tmp ;
163201 }
164202
203+ /**
204+ * Ensures that `absolutePosition` is a valid index in `this.buffer` and there is room to write at
205+ * least `bytesToWrite` bytes.
206+ */
207+ private void checkPosition (final int absolutePosition , final int bytesToWrite ) {
208+ if (absolutePosition < 0 ) {
209+ throw new IllegalArgumentException (format ("position must be >= 0 but was %d" , absolutePosition ));
210+ }
211+ if (absolutePosition > buffer .position () - bytesToWrite ) {
212+ throw new IllegalArgumentException (format ("position must be <= %d but was %d" , buffer .position () - bytesToWrite , absolutePosition ));
213+ }
214+ }
165215}
0 commit comments