Skip to content

Commit 53e7e9b

Browse files
Tom Dyasjyemin
authored andcommitted
JAVA-2300: add conversions of ObjectId to/from java.nio.ByteBuffer
Add conversions to/from java.nio.ByteBuffer via the new putToByteBuffer method and a new constructor. This is useful for users who want to avoid the GC overhead of allocating a byte array when serializing an ObjectId to bytes.
1 parent 07a5291 commit 53e7e9b

File tree

2 files changed

+82
-34
lines changed

2 files changed

+82
-34
lines changed

bson/src/main/org/bson/types/ObjectId.java

Lines changed: 58 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -244,17 +244,10 @@ public ObjectId(final String hexString) {
244244
* @throws IllegalArgumentException if array is null or not of length 12
245245
*/
246246
public ObjectId(final byte[] bytes) {
247-
if (bytes == null) {
248-
throw new IllegalArgumentException();
249-
}
250-
if (bytes.length != 12) {
251-
throw new IllegalArgumentException("need 12 bytes");
252-
}
253-
254-
timestamp = makeInt(bytes[0], bytes[1], bytes[2], bytes[3]);
255-
machineIdentifier = makeInt((byte) 0, bytes[4], bytes[5], bytes[6]);
256-
processIdentifier = (short) makeInt((byte) 0, (byte) 0, bytes[7], bytes[8]);
257-
counter = makeInt((byte) 0, bytes[9], bytes[10], bytes[11]);
247+
// This null check allows the delegate constructor to throw the expected IllegalArgumentException.
248+
// (ByteBuffer.wrap throws NullPointerException if bytes is null, which violates ObjectId's contract
249+
// to callers.)
250+
this(bytes != null ? ByteBuffer.wrap(bytes) : null);
258251
}
259252

260253
/**
@@ -268,6 +261,28 @@ public ObjectId(final byte[] bytes) {
268261
this(legacyToBytes(timestamp, machineAndProcessIdentifier, counter));
269262
}
270263

264+
/**
265+
* Constructs a new instance from the given ByteBuffer
266+
*
267+
* @param buffer the ByteBuffer
268+
* @throws IllegalArgumentException if the buffer is null or does not have at least 12 bytes remaining
269+
*/
270+
public ObjectId(final ByteBuffer buffer) {
271+
if (buffer == null) {
272+
throw new IllegalArgumentException();
273+
}
274+
if (buffer.remaining() < 12) {
275+
throw new IllegalArgumentException("need 12 bytes");
276+
}
277+
278+
// Note: Cannot use ByteBuffer.getInt because it depends on tbe buffer's byte order
279+
// and ObjectId's are always in big-endian order.
280+
timestamp = makeInt(buffer.get(), buffer.get(), buffer.get(), buffer.get());
281+
machineIdentifier = makeInt((byte) 0, buffer.get(), buffer.get(), buffer.get());
282+
processIdentifier = (short) makeInt((byte) 0, (byte) 0, buffer.get(), buffer.get());
283+
counter = makeInt((byte) 0, buffer.get(), buffer.get(), buffer.get());
284+
}
285+
271286
private static byte[] legacyToBytes(final int timestamp, final int machineAndProcessIdentifier, final int counter) {
272287
byte[] bytes = new byte[12];
273288
bytes[0] = int3(timestamp);
@@ -291,22 +306,40 @@ private static byte[] legacyToBytes(final int timestamp, final int machineAndPro
291306
* @return the byte array
292307
*/
293308
public byte[] toByteArray() {
294-
byte[] bytes = new byte[12];
295-
bytes[0] = int3(timestamp);
296-
bytes[1] = int2(timestamp);
297-
bytes[2] = int1(timestamp);
298-
bytes[3] = int0(timestamp);
299-
bytes[4] = int2(machineIdentifier);
300-
bytes[5] = int1(machineIdentifier);
301-
bytes[6] = int0(machineIdentifier);
302-
bytes[7] = short1(processIdentifier);
303-
bytes[8] = short0(processIdentifier);
304-
bytes[9] = int2(counter);
305-
bytes[10] = int1(counter);
306-
bytes[11] = int0(counter);
307-
return bytes;
309+
ByteBuffer buffer = ByteBuffer.allocate(12);
310+
putToByteBuffer(buffer);
311+
return buffer.array(); // using .allocate ensures there is a backing array that can be returned
308312
}
309313

314+
/**
315+
* Convert to bytes and put those bytes to the provided ByteBuffer.
316+
* Note that the numbers are stored in big-endian order.
317+
*
318+
* @param buffer the ByteBuffer
319+
* @throws IllegalArgumentException if the buffer is null or does not have at least 12 bytes remaining
320+
*/
321+
public void putToByteBuffer(final ByteBuffer buffer) {
322+
if (buffer == null) {
323+
throw new IllegalArgumentException();
324+
}
325+
if (buffer.remaining() < 12) {
326+
throw new IllegalArgumentException("need 12 bytes");
327+
}
328+
329+
buffer.put(int3(timestamp));
330+
buffer.put(int2(timestamp));
331+
buffer.put(int1(timestamp));
332+
buffer.put(int0(timestamp));
333+
buffer.put(int2(machineIdentifier));
334+
buffer.put(int1(machineIdentifier));
335+
buffer.put(int0(machineIdentifier));
336+
buffer.put(short1(processIdentifier));
337+
buffer.put(short0(processIdentifier));
338+
buffer.put(int2(counter));
339+
buffer.put(int1(counter));
340+
buffer.put(int0(counter));
341+
}
342+
310343
/**
311344
* Gets the timestamp (number of seconds since the Unix epoch).
312345
*

bson/src/test/unit/org/bson/types/ObjectIdTest.java

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,29 +20,44 @@
2020

2121
import java.util.Date;
2222
import java.util.Random;
23+
import java.nio.ByteBuffer;
2324

2425
import static org.junit.Assert.assertArrayEquals;
2526
import static org.junit.Assert.assertEquals;
2627
import static org.junit.Assert.assertTrue;
2728

2829
public class ObjectIdTest {
2930
@Test
30-
public void testToByteArray() {
31+
public void testToBytes() {
3132
ObjectId objectId = new ObjectId(0x5106FC9A, 0x00BC8237, (short) 0x5581, 0x0036D289);
32-
assertArrayEquals(new byte[]{81, 6, -4, -102, -68, -126, 55, 85, -127, 54, -46, -119}, objectId.toByteArray());
33+
byte[] expectedBytes = new byte[]{81, 6, -4, -102, -68, -126, 55, 85, -127, 54, -46, -119};
34+
35+
assertArrayEquals(expectedBytes, objectId.toByteArray());
36+
37+
ByteBuffer buffer = ByteBuffer.allocate(12);
38+
objectId.putToByteBuffer(buffer);
39+
assertArrayEquals(expectedBytes, buffer.array());
3340
}
3441

3542
@Test
36-
public void testFromByteArray() {
37-
ObjectId objectId = new ObjectId(new byte[]{81, 6, -4, -102, -68, -126, 55, 85, -127, 54, -46, -119});
38-
assertEquals(0x5106FC9A, objectId.getTimestamp());
39-
assertEquals(0x00BC8237, objectId.getMachineIdentifier());
40-
assertEquals((short) 0x5581, objectId.getProcessIdentifier());
41-
assertEquals(0x0036D289, objectId.getCounter());
43+
public void testFromBytes() {
44+
byte[] bytes = new byte[]{81, 6, -4, -102, -68, -126, 55, 85, -127, 54, -46, -119};
45+
46+
ObjectId objectId1 = new ObjectId(bytes);
47+
assertEquals(0x5106FC9A, objectId1.getTimestamp());
48+
assertEquals(0x00BC8237, objectId1.getMachineIdentifier());
49+
assertEquals((short) 0x5581, objectId1.getProcessIdentifier());
50+
assertEquals(0x0036D289, objectId1.getCounter());
51+
52+
ObjectId objectId2 = new ObjectId(ByteBuffer.wrap(bytes));
53+
assertEquals(0x5106FC9A, objectId2.getTimestamp());
54+
assertEquals(0x00BC8237, objectId2.getMachineIdentifier());
55+
assertEquals((short) 0x5581, objectId2.getProcessIdentifier());
56+
assertEquals(0x0036D289, objectId2.getCounter());
4257
}
4358

4459
@Test
45-
public void testBytes() {
60+
public void testBytesRoundtrip() {
4661
ObjectId expected = new ObjectId();
4762
ObjectId actual = new ObjectId(expected.toByteArray());
4863
assertEquals(expected, actual);

0 commit comments

Comments
 (0)