/*
* Copyright (c) 2008-2014 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 org.bson;
import com.mongodb.BasicDBObject;
import org.bson.io.BasicOutputBuffer;
import org.bson.io.OutputBuffer;
import org.bson.types.CodeWScope;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import static com.mongodb.util.Util.hexMD5;
import static java.util.Arrays.asList;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.Matchers.contains;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
public class BSONTest {
@Before
public void setUp() {
BSON.clearAllHooks();
}
@After
public void tearDown() {
BSON.clearAllHooks();
}
@Test
public void testSimpleDocuments() throws IOException {
checkEncodingAndDecoding(new BasicBSONObject("x", true), 9, "6fe24623e4efc5cf07f027f9c66b5456");
checkEncodingAndDecoding(new BasicBSONObject("x", null), 8, "12d43430ff6729af501faf0638e68888");
checkEncodingAndDecoding(new BasicBSONObject("x", 5.2), 16, "aaeeac4a58e9c30eec6b0b0319d0dff2");
checkEncodingAndDecoding(new BasicBSONObject("x", "eliot"), 18, "331a3b8b7cbbe0706c80acdb45d4ebbe");
checkEncodingAndDecoding(new BasicBSONObject("x", 5.2).append("y", "truth")
.append("z", 1.1),
40, "7c77b3a6e63e2f988ede92624409da58");
checkEncodingAndDecoding(new BasicBSONObject("a", new BasicBSONObject("b", 1.1)), 24, "31887a4b9d55cd9f17752d6a8a45d51f");
checkEncodingAndDecoding(new BasicBSONObject("x", 5.2).append("y", new BasicBSONObject("a", "eliot").append("b", true))
.append("z", null),
44, "b3de8a0739ab329e7aea138d87235205");
checkEncodingAndDecoding(new BasicBSONObject("x", 5.2).append("y", new Object[]{"a", "eliot", "b", true})
.append("z", null),
62, "cb7bad5697714ba0cbf51d113b6a0ee8");
checkEncodingAndDecoding(new BasicBSONObject("x", 4), 12, "d1ed8dbf79b78fa215e2ded74548d89d");
}
@Test
public void testArray() throws IOException {
checkEncodingAndDecoding(new BasicBSONObject("x", new int[]{1, 2, 3, 4}), 41, "e63397fe37de1349c50e1e4377a45e2d");
}
@Test
public void testCode() throws IOException {
BSONObject scope = new BasicBSONObject("x", 1);
CodeWScope c = new CodeWScope("function() { x += 1; }", scope);
BSONObject document = new BasicBSONObject("map", c);
checkEncodingAndDecoding(document, 53, "52918d2367533165bfc617df50335cbb");
}
@Test
public void testBinary() throws IOException {
byte[] data = new byte[10000];
for (int i = 0; i < 10000; i++) {
data[i] = 1;
}
BSONObject document = new BasicBSONObject("bin", data);
checkEncodingAndDecoding(document, 10015, "1d439ba5b959ecfe297a7862bf95bc10");
}
private void checkEncodingAndDecoding(final BSONObject toEncodeAndDecode,
final int expectedEncodedSize,
final String expectedHash) throws IOException {
// check encoding
BSONEncoder bsonEncoder = new BasicBSONEncoder();
OutputBuffer buf = new BasicOutputBuffer();
bsonEncoder.set(buf);
bsonEncoder.putObject(toEncodeAndDecode);
assertEquals(expectedEncodedSize, buf.size());
assertEquals(expectedHash, hexMD5(buf.toByteArray()));
bsonEncoder.done();
// check decoding
BSONDecoder bsonDecoder = new BasicBSONDecoder();
BSONCallback callback = new BasicBSONCallback();
int numberOfBytesDecoded = bsonDecoder.decode(new ByteArrayInputStream(buf.toByteArray()), callback);
assertEquals(expectedEncodedSize, numberOfBytesDecoded);
assertEquals(callback.get(), toEncodeAndDecode);
// I believe this is an obscure way of checking the decoded object is the the one we expect
OutputBuffer buf2 = new BasicOutputBuffer();
bsonEncoder.set(buf2);
bsonEncoder.putObject((BSONObject) callback.get());
assertEquals(expectedEncodedSize, buf2.size());
assertEquals(expectedHash, hexMD5(buf2.toByteArray()));
}
@Test
public void testOBBig1() {
BasicOutputBuffer a = new BasicOutputBuffer();
StringBuilder b = new StringBuilder();
for (final String x : prepareData()) {
a.write(x.getBytes());
b.append(x);
}
assertEquals(new String(a.toByteArray(), Charset.forName("UTF-8")), b.toString());
}
private List<String> prepareData() {
List<String> data = new ArrayList<String>();
for (int x = 8; x < 2048; x *= 2) {
StringBuilder buf = new StringBuilder();
while (buf.length() < x) {
buf.append(x);
}
data.add(buf.toString());
}
return data;
}
@Test(expected = IllegalArgumentException.class)
public void shouldClearCustomEncoders() throws IOException {
// given
BSON.addEncodingHook(TestDate.class, new TestDateTransformer());
BSONEncoder encoder = new BasicBSONEncoder();
encoder.set(new BasicOutputBuffer());
// when
BSON.clearEncodingHooks();
encoder.putObject(new BasicBSONObject("date", new TestDate(2009, 1, 23, 10, 53, 42)));
}
@Test
public void shouldTransformTestDateToUtilDateWithTestDateTransformer() throws IOException {
// given
Transformer transformer = new TestDateTransformer();
// when
Object transformedDate = transformer.transform(new TestDate(2009, 1, 23, 10, 53, 42));
// then
assertThat(transformedDate, is(instanceOf(java.util.Date.class)));
}
@Test
public void shouldUseCustomEncodersWhenDecodingObjectOfRegisteredClass() throws IOException {
// given
StubTransformer stubTransformer = new StubTransformer();
BSON.addEncodingHook(TestDate.class, stubTransformer);
BSONEncoder encoder = new BasicBSONEncoder();
encoder.set(new BasicOutputBuffer());
BSONObject document = new BasicBSONObject("date", new TestDate(2009, 1, 23, 10, 53, 42));
// when
encoder.putObject(document);
encoder.done();
// then
assertThat(stubTransformer.transformCalled, is(true));
}
@Test
public void shouldReturnRegisteredCustomEncoders() throws IOException {
// when
Transformer transformer = new TestDateTransformer();
BSON.addEncodingHook(TestDate.class, transformer);
// then
assertThat(BSON.hasEncodeHooks(), is(true));
List<Transformer> encodingHooks = BSON.getEncodingHooks(TestDate.class);
assertThat(encodingHooks, is(notNullValue()));
assertThat(encodingHooks, is(asList(transformer)));
}
@Test
public void shouldRemoveSpecificRegisteredCustomEncoders() throws IOException {
Transformer transformer = new TestDateTransformer();
BSON.addEncodingHook(TestDate.class, transformer);
// when
BSON.removeEncodingHook(TestDate.class, transformer);
// then
assertThat(BSON.getEncodingHooks(TestDate.class), not(contains(transformer)));
}
@Test
public void shouldClearCustomDecoders() throws IOException {
// given
BSON.addDecodingHook(Date.class, new TestDateTransformer());
byte[] encodedDocument = encodeDocumentToByteArray(new BasicBSONObject("date", new Date()));
BSONCallback bsonCallback = new BasicBSONCallback();
// when
BSON.clearDecodingHooks();
new BasicBSONDecoder().decode(new ByteArrayInputStream(encodedDocument), bsonCallback);
// then
BSONObject decodedDocument = (BSONObject) bsonCallback.get();
assertThat(decodedDocument.get("date"), is(instanceOf(java.util.Date.class)));
}
@Test
public void shouldUseCustomDecodersWhenDecodingObjectOfRegisteredClass() throws IOException {
// given
@SuppressWarnings("deprecation")
byte[] encodedDocument = encodeDocumentToByteArray(new BasicBSONObject("date", new Date(2009, 01, 23, 10, 53, 42)));
BSONCallback bsonCallback = new BasicBSONCallback();
// when
BSON.addDecodingHook(Date.class, new TestDateTransformer());
new BasicBSONDecoder().decode(new ByteArrayInputStream(encodedDocument), bsonCallback);
// then
BSONObject decodedDocument = (BSONObject) bsonCallback.get();
assertThat(decodedDocument.get("date"), is(instanceOf(TestDate.class)));
assertThat((TestDate) decodedDocument.get("date"), is(new TestDate(2009, 01, 23, 10, 53, 42)));
}
@Test
public void shouldReturnRegisteredCustomDecoders() throws IOException {
// when
Transformer transformer = new TestDateTransformer();
BSON.addDecodingHook(Date.class, transformer);
// then
assertThat(BSON.hasDecodeHooks(), is(true));
List<Transformer> decodingHooks = BSON.getDecodingHooks(Date.class);
assertThat(decodingHooks, is(notNullValue()));
assertThat(decodingHooks, is(asList(transformer)));
}
@Test
public void shouldRemoveSpecificRegisteredCustomDecoders() throws IOException {
// given
Transformer transformer = new TestDateTransformer();
BSON.addDecodingHook(Date.class, transformer);
// when
BSON.removeDecodingHook(Date.class, transformer);
// expect
assertThat(BSON.getDecodingHooks(Date.class), not(contains(transformer)));
}
@Test
public void testEquals() {
assertThat(new BasicBSONObject("a", 1111111111111111111L), is(not(new BasicBSONObject("a", 1111111111111111112L))));
assertThat(new BasicBSONObject("a", 100.1D), is(not(new BasicBSONObject("a", 100.2D))));
assertThat(new BasicBSONObject("a", 100.1F), is(not(new BasicBSONObject("a", 100.2F))));
assertEquals(new BasicBSONObject("a", 100.1D), new BasicBSONObject("a", 100.1D));
assertEquals(new BasicBSONObject("a", 100.1F), new BasicBSONObject("a", 100.1F));
assertEquals(new BasicBSONObject("a", 100L), new BasicBSONObject("a", 100L));
}
@Test
public void testRandomRoundTrips() {
roundTrip(new BasicBSONObject("a", ""));
roundTrip(new BasicBSONObject("a", "a"));
roundTrip(new BasicBSONObject("a", "b"));
}
private byte[] encodeDocumentToByteArray(final BSONObject document) {
OutputBuffer outputBuffer = new BasicOutputBuffer();
BSONEncoder encoder = new BasicBSONEncoder();
encoder.set(outputBuffer);
encoder.putObject(document);
encoder.done();
return outputBuffer.toByteArray();
}
private void roundTrip(final BSONObject o) {
assertEquals(o, BSON.decode(BSON.encode(o)));
}
@Test
public void testEncodingDecode() {
BasicDBObject inputDoc = new BasicDBObject("_id", 1);
byte[] encoded = BSON.encode(inputDoc);
assertEquals(inputDoc, BSON.decode(encoded));
}
@Test
public void testToInt() {
assertEquals(1, BSON.toInt(Boolean.TRUE));
assertEquals(0, BSON.toInt(Boolean.FALSE));
assertEquals(12, BSON.toInt(12.23f));
assertEquals(21, BSON.toInt(21.32d));
assertEquals(13, BSON.toInt(13));
}
private static class StubTransformer implements Transformer {
private boolean transformCalled = false;
@Override
public Object transform(final Object objectToTransform) {
transformCalled = true;
return true;
}
}
private class TestDateTransformer implements Transformer {
@SuppressWarnings("deprecation")
public Object transform(final Object objectToTransform) {
if (objectToTransform instanceof TestDate) {
TestDate td = (TestDate) objectToTransform;
return new java.util.Date(td.year, td.month, td.date, td.hour, td.minute, td.second);
} else if (objectToTransform instanceof java.util.Date) {
Date d = (Date) objectToTransform;
return new TestDate(d.getYear(), d.getMonth(), d.getDate(), d.getHours(), d.getMinutes(), d.getSeconds());
} else {
return objectToTransform;
}
}
}
private class TestDate {
private final int year;
private final int month;
private final int date;
private final int hour;
private final int minute;
private final int second;
TestDate(final int year, final int month, final int date, final int hour, final int minute, final int second) {
this.year = year;
this.month = month;
this.date = date;
this.hour = hour;
this.minute = minute;
this.second = second;
}
@Override
public boolean equals(final Object other) {
if (this == other) {
return true;
}
if (!(other instanceof TestDate)) {
return false;
}
TestDate otherTestDate = (TestDate) other;
return (otherTestDate.year == this.year
&& otherTestDate.month == this.month
&& otherTestDate.date == this.date
&& otherTestDate.hour == this.hour
&& otherTestDate.minute == this.minute
&& otherTestDate.second == this.second
);
}
@Override
public int hashCode() {
int result = year;
result = 31 * result + month;
result = 31 * result + date;
result = 31 * result + hour;
result = 31 * result + minute;
result = 31 * result + second;
return result;
}
@Override
public String toString() {
return year + "-" + month + "-" + date + " " + hour + ":" + minute + ":" + second;
}
}
}