Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Initial commit.

  • Loading branch information...
commit d910fcae55ae19f456f896dfa981e1ff2f8bb4d7 0 parents
@kohanyirobert authored
Showing with 2,903 additions and 0 deletions.
  1. +1 −0  .gitignore
  2. +19 −0 LICENSE.txt
  3. +107 −0 README.md
  4. +184 −0 pom.xml
  5. +178 −0 src/main/java/com/github/kohanyirobert/ebson/BsonBinary.java
  6. +168 −0 src/main/java/com/github/kohanyirobert/ebson/BsonBytes.java
  7. +250 −0 src/main/java/com/github/kohanyirobert/ebson/BsonDocument.java
  8. +259 −0 src/main/java/com/github/kohanyirobert/ebson/BsonDocuments.java
  9. +292 −0 src/main/java/com/github/kohanyirobert/ebson/BsonObject.java
  10. +28 −0 src/main/java/com/github/kohanyirobert/ebson/BsonPreconditions.java
  11. +30 −0 src/main/java/com/github/kohanyirobert/ebson/BsonReader.java
  12. +78 −0 src/main/java/com/github/kohanyirobert/ebson/BsonToken.java
  13. +27 −0 src/main/java/com/github/kohanyirobert/ebson/BsonWriter.java
  14. +63 −0 src/main/java/com/github/kohanyirobert/ebson/DefaultDocument.java
  15. +44 −0 src/main/java/com/github/kohanyirobert/ebson/DefaultDocumentBuilder.java
  16. +132 −0 src/main/java/com/github/kohanyirobert/ebson/DefaultPredicate.java
  17. +200 −0 src/main/java/com/github/kohanyirobert/ebson/DefaultReader.java
  18. +179 −0 src/main/java/com/github/kohanyirobert/ebson/DefaultWriter.java
  19. +8 −0 src/main/java/com/github/kohanyirobert/ebson/package-info.java
  20. +17 −0 src/test/java/com/github/kohanyirobert/ebson/AbstractBsonTest.java
  21. +28 −0 src/test/java/com/github/kohanyirobert/ebson/AbstractReaderWriterTest.java
  22. +48 −0 src/test/java/com/github/kohanyirobert/ebson/BsonDocumentTest.java
  23. +135 −0 src/test/java/com/github/kohanyirobert/ebson/BsonDocumentsTest.java
  24. +73 −0 src/test/java/com/github/kohanyirobert/ebson/BsonEncodingTest.java
  25. +62 −0 src/test/java/com/github/kohanyirobert/ebson/DefaultArrayReaderWriterTest.java
  26. +26 −0 src/test/java/com/github/kohanyirobert/ebson/DefaultBinaryReaderWriterTest.java
  27. +22 −0 src/test/java/com/github/kohanyirobert/ebson/DefaultBooleanReaderWriterTest.java
  28. +22 −0 src/test/java/com/github/kohanyirobert/ebson/DefaultDoubleReaderWriterTest.java
  29. +19 −0 src/test/java/com/github/kohanyirobert/ebson/DefaultFieldReaderWriterTest.java
  30. +22 −0 src/test/java/com/github/kohanyirobert/ebson/DefaultInt32ReaderWriterTest.java
  31. +22 −0 src/test/java/com/github/kohanyirobert/ebson/DefaultInt64ReaderWriterTest.java
  32. +25 −0 src/test/java/com/github/kohanyirobert/ebson/DefaultKeyReaderWriterTest.java
  33. +17 −0 src/test/java/com/github/kohanyirobert/ebson/DefaultNullReaderWriterTest.java
  34. +99 −0 src/test/java/com/github/kohanyirobert/ebson/DefaultRegularExpressionReaderWriterTest.java
  35. +19 −0 src/test/java/com/github/kohanyirobert/ebson/DefaultUtcDateTimeReaderWriterTest.java
1  .gitignore
@@ -0,0 +1 @@
+*
19 LICENSE.txt
@@ -0,0 +1,19 @@
+Copyright (C) 2011-2012 by Kohányi Róbert <kohanyi.robert@gmail.com>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
107 README.md
@@ -0,0 +1,107 @@
+# ebson
+`ebson` is an extensible [BSON][] encoder/decoder library written in Java. The
+library is extensible in the sense that the mappings between Java and BSON types
+are configurable and the logic to serialize custom Java types is pluggable. Its
+single dependency is the [Guava libraries][] by Google.
+
+## License
+Released under the permissive [MIT License][].
+
+## Author
+[Kohányi Róbert][].
+
+## Download
+Add the library as a dependency in your project's *pom.xml* like this.
+
+```xml
+<dependency>
+ <groupId>com.github.kohanyirobert</groupId>
+ <artifactId>ebson</artifactId>
+ <version>...</version>
+</dependency>
+```
+
+Releases and snapshots are deployed to [Sonatype's][] [OSS repository][] (and
+synced to the [Central Maven Repository][] from there). To download JARs from
+Sonatype's repository include the following repository tag inside your Maven
+installation's *settings.xml* or your project's *pom.xml*.
+
+```xml
+<repository>
+ <id>sonatype-oss<id>
+ <url>https://oss.sonatype.org/content/groups/public</url>
+</repository>
+```
+
+## Build
+As the project is managed with [Maven][] you simply clone it and issue *mvn
+install* or *mvn package* inside the clone's directory.
+
+```
+git clone git://github.com/kohanyirobert/ebson.git
+cd ebson/
+mvn package
+# and/or
+mvn install
+```
+
+## Usage
+### Serialization
+```java
+// create documents to serialize
+BsonDocument document = BsonDocuments.of("key", new Date());
+
+// grab a little-endian byte buffer
+ByteBuffer buffer = ByteBuffer.allocate(32).order(ByteOrder.LITTLE_ENDIAN);
+
+// use the documents utility class to write the document into the buffer
+BsonDocuments.writeTo(buffer, document);
+
+// use the serialized data
+buffer.flip();
+```
+
+### Deserialization
+```java
+// given the previous buffer
+BsonDocument newDocument = BsonDocuments.readFrom(buffer);
+
+// prints true
+System.out.println(document.equals(newDocument));
+```
+
+### Extensibility
+```java
+// to use joda-time's date-time instead of java's date supply
+// a predicate (to test whether an input class is compatible with
+// date-time or not) for the appropriate bson type
+BsonObject.UTC_DATE_TIME.predicate(new Predicate<Class<?>>() {
+ @Override public boolean apply(Class<?> input) {
+ return input == null ? false : DateTime.class.isAssignableFrom(input);
+ }
+});
+
+// register a writer with the same bson type which is
+// able to serialize date-times into byte buffers
+BsonObject.UTC_DATE_TIME.writer(new BsonWriter() {
+ @Override public void writeTo(ByteBuffer buffer, Object reference) {
+ buffer.putLong(((DateTime) reference).getMillis());
+ }
+});
+
+// finally register a reader to do all this ass backwards
+BsonObject.UTC_DATE_TIME.reader(new BsonReader() {
+ @Override public Object readFrom(ByteBuffer buffer) {
+ return new DateTime(buffer.getLong());
+ }
+});
+```
+
+[BSON]: http://bsonspec.org
+[Guava libraries]: http://code.google.com/p/guava-libraries
+[Kohányi Róbert]: http://kohanyirobert.github.com
+[MIT License]: https://raw.github.com/kohanyirobert/ebson/master/LICENSE.txt
+[Sonatype's]: http://sonatype.com
+[OSS repository]: https://oss.sonatype.org
+[Central Maven Repository]: http://search.maven.org
+[Maven]: http://maven.apache.org
184 pom.xml
@@ -0,0 +1,184 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <groupId>com.github.kohanyirobert</groupId>
+ <artifactId>ebson</artifactId>
+ <version>0.1</version>
+ <packaging>jar</packaging>
+
+ <name>Extensible Java BSON encoder/decoder library.</name>
+ <description>
+ Extensible BSON encoder/decoder library written
+ in Java with pluggable Java-to-BSON type mappings.
+ </description>
+ <url>https://github.com/kohanyirobert/ebson</url>
+
+ <licenses>
+ <license>
+ <name>MIT License</name>
+ <url>LICENSE.txt</url>
+ <distribution>repo</distribution>
+ </license>
+ </licenses>
+
+ <scm>
+ <connection>scm:git:git@github.com:kohanyirobert/ebson.git</connection>
+ <developerConnection>scm:git:git@github.com:kohanyirobert/ebson.git</developerConnection>
+ <url>git@github.com:kohanyirobert/ebson.git</url>
+ </scm>
+
+ <distributionManagement>
+ <repository>
+ <id>sonatype-nexus-staging</id>
+ <url>https://oss.sonatype.org/service/local/staging/deploy/maven2/</url>
+ </repository>
+ <snapshotRepository>
+ <id>sonatype-nexus-snapshots</id>
+ <url>https://oss.sonatype.org/content/repositories/snapshots/</url>
+ </snapshotRepository>
+ </distributionManagement>
+
+ <developers>
+ <developer>
+ <id>kohanyi.robert</id>
+ <name>Kohányi Róbert</name>
+ <email>kohanyi.robert@gmail.com</email>
+ <roles>
+ <role>owner</role>
+ <role>developer</role>
+ </roles>
+ <timezone>+1</timezone>
+ </developer>
+ </developers>
+
+ <properties>
+ <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+ <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
+ </properties>
+
+ <dependencies>
+ <dependency>
+ <groupId>com.google.guava</groupId>
+ <artifactId>guava</artifactId>
+ <version>11.0.1</version>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <version>4.10</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.mongodb</groupId>
+ <artifactId>mongo-java-driver</artifactId>
+ <version>2.7.2</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <resources>
+ <resource>
+ <directory>${basedir}</directory>
+ <targetPath>${project.build.outputDirectory}/META-INF</targetPath>
+ <filtering>false</filtering>
+ <includes>
+ <include>LICENSE.txt</include>
+ <include>README.md</include>
+ </includes>
+ </resource>
+ </resources>
+
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <version>2.3.2</version>
+ <configuration>
+ <source>1.6</source>
+ <target>1.6</target>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-source-plugin</artifactId>
+ <version>2.1.2</version>
+ <executions>
+ <execution>
+ <id>attach-sources</id>
+ <goals>
+ <goal>jar</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-javadoc-plugin</artifactId>
+ <version>2.8</version>
+ <executions>
+ <execution>
+ <id>attach-javadocs</id>
+ <goals>
+ <goal>jar</goal>
+ </goals>
+ </execution>
+ </executions>
+ <configuration>
+ <links>
+ <link>http://docs.guava-libraries.googlecode.com/git/javadoc</link>
+ </links>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-checkstyle-plugin</artifactId>
+ <version>2.8</version>
+ <executions>
+ <execution>
+ <id>checkstyle-main</id>
+ <phase>verify</phase>
+ <goals>
+ <goal>check</goal>
+ </goals>
+ <configuration>
+ <configLocation>https://raw.github.com/kohanyirobert/checkstyle/master/checkstyle-main.xml</configLocation>
+ </configuration>
+ </execution>
+ <execution>
+ <id>checkstyle-test</id>
+ <phase>verify</phase>
+ <goals>
+ <goal>check</goal>
+ </goals>
+ <configuration>
+ <configLocation>https://raw.github.com/kohanyirobert/checkstyle/master/checkstyle-test.xml</configLocation>
+ <includeTestSourceDirectory>true</includeTestSourceDirectory>
+ </configuration>
+ </execution>
+ </executions>
+ <configuration>
+ <consoleOutput>true</consoleOutput>
+ <failsOnError>true</failsOnError>
+ <linkXRef>false</linkXRef>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-gpg-plugin</artifactId>
+ <version>1.4</version>
+ <executions>
+ <execution>
+ <id>sign-artifacts</id>
+ <phase>verify</phase>
+ <goals>
+ <goal>sign</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+</project>
178 src/main/java/com/github/kohanyirobert/ebson/BsonBinary.java
@@ -0,0 +1,178 @@
+package com.github.kohanyirobert.ebson;
+
+import com.google.common.base.Preconditions;
+import com.google.common.base.Predicate;
+import com.google.common.base.Predicates;
+
+import javax.annotation.Nullable;
+
+/**
+ * Representation of a <a href="http://bsonspec.org/">BSON</a> binary
+ * (sub-)type.
+ */
+public enum BsonBinary {
+
+ /**
+ * Generic binary.
+ * <p>
+ * <b>Note:</b> the most commonly used binary sub-type and should be the
+ * 'default' for drivers and tools.
+ * </p>
+ */
+ GENERIC(BsonBytes.GENERIC, DefaultPredicate.GENERIC, DefaultReader.GENERIC,
+ DefaultWriter.GENERIC),
+
+ /**
+ * Function binary.
+ */
+ FUNCTION(BsonBytes.FUNCTION),
+
+ /**
+ * Old binary.
+ * <p>
+ * <b>Note:</b> this used to be the default subtype, but was deprecated in
+ * favor of the generic binary type.
+ * </p>
+ */
+ OLD(BsonBytes.OLD),
+
+ /**
+ * UUID binary.
+ */
+ UUID(BsonBytes.UUID),
+
+ /**
+ * MD5 binary.
+ */
+ MD5(BsonBytes.MD5),
+
+ /**
+ * User-defined binary.
+ */
+ USER(BsonBytes.USER);
+
+ private final byte terminal;
+
+ private Predicate<Class<?>> predicate;
+ private BsonReader reader;
+ private BsonWriter writer;
+
+ private BsonBinary(byte terminal) {
+ this(terminal, Predicates.<Class<?>>alwaysFalse(), null, null);
+ }
+
+ private BsonBinary(byte terminal, Predicate<Class<?>> predicate,
+ BsonReader reader, BsonWriter writer) {
+ this.terminal = terminal;
+ this.predicate = predicate;
+ this.reader = reader;
+ this.writer = writer;
+ }
+
+ /**
+ * Returns this binary's associated terminal.
+ *
+ * @return this binary's associated terminal
+ */
+ public byte terminal() {
+ return terminal;
+ }
+
+ /**
+ * Returns this binary's associated {@linkplain Predicate predicate}.
+ *
+ * @return this binary's associated predicate
+ * @throws IllegalStateException if this binary does not have an associated
+ * predicate
+ */
+ public Predicate<Class<?>> predicate() {
+ Preconditions.checkState(predicate != null, "'%s' does not have an associated predicate", this);
+ return predicate;
+ }
+
+ /**
+ * Associates {@link Predicate predicate} with this binary.
+ *
+ * @param predicate the predicate to be associated with this binary
+ */
+ public void predicate(Predicate<Class<?>> predicate) {
+ Preconditions.checkNotNull(predicate, "cannot associate a null predicate with '%s'", this);
+ this.predicate = predicate;
+ }
+
+ /**
+ * Returns this binary's associated {@linkplain BsonReader reader}.
+ *
+ * @return this binary's associated reader
+ * @throws IllegalStateException if this binary does not have an associated
+ * reader
+ */
+ public BsonReader reader() {
+ Preconditions.checkState(reader != null, "'%s' does not have an associated reader", this);
+ return reader;
+ }
+
+ /**
+ * Associates {@link BsonReader reader} with this binary.
+ *
+ * @param reader the reader to be associated with this binary
+ */
+ public void reader(BsonReader reader) {
+ Preconditions.checkNotNull(reader, "cannot associate a null reader with '%s'", this);
+ this.reader = reader;
+ }
+
+ /**
+ * Returns this binary's associated {@linkplain BsonWriter writer}.
+ *
+ * @return this binary's associated writer
+ * @throws IllegalStateException if this binary does not have an associated
+ * writer
+ */
+ public BsonWriter writer() {
+ Preconditions.checkState(writer != null, "'%s' does not have an associated writer", this);
+ return writer;
+ }
+
+ /**
+ * Associates {@link BsonWriter writer} with this binary.
+ *
+ * @param writer the writer to be associated with this binary
+ */
+ public void writer(BsonWriter writer) {
+ Preconditions.checkNotNull(writer, "cannot associate a null writer with '%s'", this);
+ this.writer = writer;
+ }
+
+ /**
+ * Returns the binary representing {@code clazz}.
+ *
+ * @param clazz the class to return a binary representation for
+ * @return the binary representing {@code clazz}
+ * @throws IllegalArgumentException if no binary representing {@code clazz}
+ * was found
+ */
+ public static BsonBinary find(@Nullable Class<?> clazz) {
+ for (BsonBinary binary : values())
+ if (binary.predicate().apply(clazz))
+ return binary;
+ throw new IllegalArgumentException(String.format("no binary "
+ + "representing the '%s' type value was found", clazz));
+ }
+
+ /**
+ * Returns the binary representing {@code terminal}.
+ *
+ * @param terminal the terminal to return a binary representation for
+ * @return the binary representing {@code terminal}
+ * @throws IllegalArgumentException if no binary representing {@code terminal}
+ * was found
+ */
+ public static BsonBinary find(byte terminal) {
+ for (BsonBinary binary : values())
+ if (binary.terminal() - terminal == 0)
+ return binary;
+ throw new IllegalArgumentException(String.format("no binary representing "
+ + "the '%s' terminal value was found", Byte.valueOf(terminal)));
+ }
+}
168 src/main/java/com/github/kohanyirobert/ebson/BsonBytes.java
@@ -0,0 +1,168 @@
+package com.github.kohanyirobert.ebson;
+
+/**
+ * Frequently used byte values.
+ */
+public final class BsonBytes {
+
+ /**
+ * EOF (-1).
+ */
+ public static final byte EOF = -1;
+
+ /**
+ * EOO (0).
+ */
+ public static final byte EOO = 0;
+
+ /**
+ * DOUBLE (1).
+ */
+ public static final byte DOUBLE = 1;
+
+ /**
+ * STRING (2).
+ */
+ public static final byte STRING = 2;
+
+ /**
+ * EMBEDDED (3).
+ */
+ public static final byte EMBEDDED = 3;
+
+ /**
+ * ARRAY (4).
+ */
+ public static final byte ARRAY = 4;
+
+ /**
+ * BINARY (5).
+ */
+ public static final byte BINARY = 5;
+
+ /**
+ * GENERIC (0).
+ */
+ public static final byte GENERIC = 0;
+
+ /**
+ * FUNCTION (1).
+ */
+ public static final byte FUNCTION = 1;
+
+ /**
+ * OLD (2).
+ */
+ public static final byte OLD = 2;
+
+ /**
+ * UUID (3).
+ */
+ public static final byte UUID = 3;
+
+ /**
+ * MD5 (5).
+ */
+ public static final byte MD5 = 5;
+
+ /**
+ * USER (128).
+ */
+ public static final byte USER = (byte) 128;
+
+ /**
+ * UNDEFINED (6).
+ *
+ * @deprecated See the <a href="http://bsonspec.org/#/">BSON</a> specification
+ * for details.
+ */
+ @Deprecated
+ public static final byte UNDEFINED = 6;
+
+ /**
+ * OBJECT_ID (7).
+ */
+ public static final byte OBJECT_ID = 7;
+
+ /**
+ * BOOLEAN (8).
+ */
+ public static final byte BOOLEAN = 8;
+
+ /**
+ * FALSE (0).
+ */
+ public static final byte FALSE = 0;
+
+ /**
+ * TRUE (1).
+ */
+ public static final byte TRUE = 1;
+
+ /**
+ * UTC_DATE_TIME (9).
+ */
+ public static final byte UTC_DATE_TIME = 9;
+
+ /**
+ * NULL (10).
+ */
+ public static final byte NULL = 10;
+
+ /**
+ * REGULAR_EXPRESSION (11).
+ */
+ public static final byte REGULAR_EXPRESSION = 11;
+
+ /**
+ * DB_POINTER (12).
+ *
+ * @deprecated See the <a
+ * href="http://api.mongodb.org/java/1.3/com/mongodb/DBPointer.html">following
+ * link</a> for more details.
+ */
+ @Deprecated
+ public static final byte DB_POINTER = 12;
+
+ /**
+ * JAVASCRIPT_CODE (13).
+ */
+ public static final byte JAVASCRIPT_CODE = 13;
+
+ /**
+ * SYMBOL (14).
+ */
+ public static final byte SYMBOL = 14;
+
+ /**
+ * JAVASCRIPT_CODE_WITH_SCOPE (15).
+ */
+ public static final byte JAVASCRIPT_CODE_WITH_SCOPE = 15;
+
+ /**
+ * INT32 (16).
+ */
+ public static final byte INT32 = 16;
+
+ /**
+ * TIMESTAMP (17).
+ */
+ public static final byte TIMESTAMP = 17;
+
+ /**
+ * INT64 (18).
+ */
+ public static final byte INT64 = 18;
+
+ /**
+ * MAX_KEY (127).
+ */
+ public static final byte MAX_KEY = 127;
+
+ /**
+ * MIN_KEY (255).
+ */
+ public static final byte MIN_KEY = (byte) 255;
+
+ private BsonBytes() {}
+}
250 src/main/java/com/github/kohanyirobert/ebson/BsonDocument.java
@@ -0,0 +1,250 @@
+package com.github.kohanyirobert.ebson;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.Set;
+
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+
+/**
+ * Immutable representation of a <a href="http://bsonspec.org/">BSON</a>
+ * document.
+ * <p>
+ * <b>Notes:</b>
+ * <ul>
+ * <li>None of the {@linkplain Map map interface's} optional operations are
+ * supported.</li>
+ * <li>Use the {@linkplain BsonDocuments documents utility class} to create new
+ * documents.</li>
+ * </ul>
+ * </p>
+ */
+public interface BsonDocument extends Map<String, Object> {
+
+ /**
+ * Returns this document's size (the number of its keys).
+ *
+ * @return this document's size
+ */
+ @Override
+ int size();
+
+ /**
+ * Returns <em>true</em> if this document contains no key-value pairs;
+ * <em>false</em> otherwise.
+ *
+ * @return <em>true</em> if this document contains no key-value pairs;
+ * <em>false</em> otherwise
+ */
+ @Override
+ boolean isEmpty();
+
+ /**
+ * Returns an immutable view of this document's keys.
+ *
+ * @return an immutable view of this document's keys.
+ */
+ @Override
+ Set<String> keySet();
+
+ /**
+ * Returns an immutable view of this document's values.
+ *
+ * @return an immutable view of this document's values
+ */
+ @Override
+ Collection<Object> values();
+
+ /**
+ * Returns an immutable view of this document's key-value pairs.
+ *
+ * @return an immutable view of this document's key-value pairs
+ */
+ @Override
+ Set<Entry<String, Object>> entrySet();
+
+ /**
+ * Returns the value associated with {@code key} in a type-safe manner.
+ * <p>
+ * <b>Note:</b> {@code type} can and should be null only <em>iff</em> the
+ * value associated with {@code key} is expected to be null.
+ * </p>
+ *
+ * @param key the key whose associated value is to be returned
+ * @param type the expected type of the value associated with {@code key}
+ * @param <T> the expected type of the value associated with {@code key}
+ * @return the value associated with {@code key} in a type-safe manner
+ * @throws NullPointerException if {@code key} is null
+ * @throws IllegalArgumentException if this document does not contain it or it
+ * is illegal (starts with {@code '$'} or contains {@code '.'}) or if the
+ * value to be returned is not an instance of with {@code type}
+ * @throws ClassCastException if {@code key} is not a string
+ */
+ @Nullable
+ <T> T get(Object key, @Nullable Class<T> type);
+
+ /**
+ * Returns the value associated with {@code key}.
+ *
+ * @param key the key whose associated value is to be returned
+ * @return the value associated with {@code key}
+ * @throws NullPointerException if {@code key} is null
+ * @throws IllegalArgumentException if this document does not contain it or it
+ * is illegal (starts with {@code '$'} or contains {@code '.'})
+ * @throws ClassCastException if {@code key} is not a string
+ */
+ @Override
+ @CheckForNull
+ Object get(Object key);
+
+ /**
+ * Returns <em>true</em> if {@code key} is contained by this document;
+ * <em>false</em> otherwise.
+ *
+ * @param key the key to be tested if it's contained by this document
+ * @return <em>true</em> if {@code key} is contained by this document;
+ * <em>false</em> otherwise
+ * @throws NullPointerException if {@code key} is null
+ * @throws IllegalArgumentException if {@code key} is illegal (starts with
+ * {@code '$'} or contains {@code '.'})
+ * @throws ClassCastException if {@code key} is not a string
+ */
+ @Override
+ boolean containsKey(Object key);
+
+ /**
+ * Returns <em>true</em> if {@code value} is contained by this document;
+ * <em>false</em> otherwise.
+ *
+ * @param value the value to be tested if it's contained by this document
+ * @return <em>true</em> if {@code value} is contained by this document;
+ * <em>false</em> otherwise
+ */
+ @Override
+ boolean containsValue(@Nullable Object value);
+
+ /**
+ * Returns this document's hash code (calculated using its
+ * {@linkplain #entrySet() key-value pairs}).
+ *
+ * @return this document's hash code
+ */
+ @Override
+ int hashCode();
+
+ /**
+ * Returns <em>true</em> if {@code object} is the same as this document;
+ * <em>false</em> otherwise.
+ * <p>
+ * {@code object} is the same as this document <em>iff</em>:
+ * <ul>
+ * <li>{@code this == object} or</li>
+ * <li>{@code object instanceof Map} <em>and</em></li>
+ * <li>{@code this.entrySet().equals(object.entrySet())}.</li>
+ * </ul>
+ * </p>
+ *
+ * @param object the reference object with which to compare
+ * @return <em>true</em> if {@code object} is the same as this document;
+ * <em>false</em> otherwise
+ */
+ @Override
+ boolean equals(@CheckForNull Object object);
+
+ /**
+ * Returns this document's textual representation.
+ * <p>
+ * The general format is the following:
+ * <ul>
+ * <li>Empty: <code>{}</code></li>
+ * <li>Single key-value pair: <code>{key: value}</code></li>
+ * <li>Multiple key-value pair: <code>{key1: value1, key2: value2, ...}</code>
+ * </li>
+ * <li>Embedded: <code>{key: {key: value}}</code></li>
+ * <li>...</li>
+ * </ul>
+ * </p>
+ *
+ * @return this document's textual representation
+ */
+ @Override
+ String toString();
+
+ /**
+ * Not supported.
+ *
+ * @param key not supported
+ * @return not supported
+ * @throws UnsupportedOperationException on every invocation of this method
+ */
+ @Override
+ @Nullable
+ Object remove(Object key);
+
+ /**
+ * Not supported.
+ *
+ * @throws UnsupportedOperationException on every invocation of this method
+ */
+ @Override
+ void clear();
+
+ /**
+ * Not supported.
+ *
+ * @param map not supported
+ * @throws UnsupportedOperationException on every invocation of this method
+ */
+ @Override
+ void putAll(Map<? extends String, ? extends Object> map);
+
+ /**
+ * {@linkplain BsonDocument Document} builder.
+ * <p>
+ * <b>Notes:</b>
+ * <ul>
+ * <li>Calling a builder's {@linkplain #build} method <em>does not clear its
+ * state</em>, however subsequent invocations of it returns new immutable
+ * documents.</li>
+ * <li>Document builders can be acquired via the {@linkplain BsonDocuments
+ * documents utility class}.</li>
+ * </ul>
+ * </p>
+ */
+ public interface Builder {
+
+ /**
+ * Adds {@code key} and its associated {@code value} to the document being
+ * built.
+ *
+ * @param key the key to be added to the document being built
+ * @param value the value associated with {@code key}
+ * @return this builder
+ * @throws NullPointerException if {@code key} is null
+ * @throws IllegalArgumentException if {@code key} is illegal (starts with
+ * {@code '$'} or contains {@code '.'}) or if it is already present
+ */
+ Builder put(String key, @Nullable Object value);
+
+ /**
+ * Adds {@code map}'s key-value pairs to the document being built.
+ *
+ * @param map the map whose key-value pairs are to be added to the document
+ * being built
+ * @return this builder
+ * @throws NullPointerException if {@code map} or any of its keys are null
+ * @throws IllegalArgumentException if {@code map} contains keys that are
+ * illegal (starts with {@code '$'} or contains {@code '.'}) or already
+ * present
+ */
+ Builder putAll(Map<String, Object> map);
+
+ /**
+ * Returns a new document using the contents of this builder.
+ *
+ * @return a new document using the contents of this builder
+ */
+ BsonDocument build();
+ }
+}
259 src/main/java/com/github/kohanyirobert/ebson/BsonDocuments.java
@@ -0,0 +1,259 @@
+package com.github.kohanyirobert.ebson;
+
+import java.nio.ByteBuffer;
+import java.util.Map;
+
+import javax.annotation.Nullable;
+
+/**
+ * Utility class for working with {@linkplain BsonDocument documents}.
+ */
+public final class BsonDocuments {
+
+ private BsonDocuments() {}
+
+ /**
+ * Reads a new document from {@code buffer}.
+ *
+ * @param buffer the buffer that contains a document's serialized data
+ * @return a new document from {@code buffer}
+ * @throws NullPointerException if {@code buffer} is null
+ * @throws IllegalArgumentException if {@code buffer} is not using
+ * little-endian byte ordering
+ */
+ @SuppressWarnings("unchecked")
+ public static BsonDocument readFrom(ByteBuffer buffer) {
+ return copyOf((Map<String, Object>) BsonToken.DOCUMENT.reader().readFrom(buffer));
+ }
+
+ /**
+ * Writes {@code document} to {@code buffer}.
+ *
+ * @param buffer the buffer to write to
+ * @param document the document to be written into {@code buffer}
+ * @throws NullPointerException if {@code buffer} or {@code document} is null
+ * @throws IllegalArgumentException if {@code buffer} is not using
+ * little-endian byte ordering
+ */
+ public static void writeTo(ByteBuffer buffer, BsonDocument document) {
+ BsonToken.DOCUMENT.writer().writeTo(buffer, document);
+ }
+
+ /**
+ * Returns a new document containing {@code map}'s key-value pairs.
+ *
+ * @param map the map whose key-value pairs will be used to initialize the new
+ * document
+ * @return a new document containing {@code map}'s key-value pairs
+ * @throws NullPointerException if {@code map} or any of its keys are
+ * null
+ * @throws IllegalArgumentException if {@code map} contains keys that are
+ * illegal (starts with {@code '$'} or contains {@code '.'})
+ */
+ public static BsonDocument copyOf(Map<String, Object> map) {
+ return builder().putAll(map).build();
+ }
+
+ // @checkstyle:off ParameterNumber|JavadocMethod
+
+ /**
+ * Returns a new document containing the key-value pairs:
+ * <em>k1: v1, k2: v2, etc.</em>
+ *
+ * @return a new document containing the key-value pairs:
+ * <em>k1: v1, k2: v2 etc.</em>
+ * @throws NullPointerException if any key-value pair's key is null
+ * @throws IllegalArgumentException if any key-value pair's key is illegal
+ * (starts with {@code '$'} or contains {@code '.'}) or if there are duplicate
+ * keys
+ */
+ public static BsonDocument of(String k1, @Nullable Object v1,
+ String k2, @Nullable Object v2, String k3, @Nullable Object v3,
+ String k4, @Nullable Object v4, String k5, @Nullable Object v5,
+ String k6, @Nullable Object v6, String k7, @Nullable Object v7,
+ String k8, @Nullable Object v8, String k9, @Nullable Object v9,
+ String k10, @Nullable Object v10) {
+ return builder().put(k1, v1).put(k2, v2).put(k3, v3).put(k4, v4).put(k5, v5)
+ .put(k6, v6).put(k7, v7).put(k8, v8).put(k9, v9).put(k10, v10).build();
+ }
+
+ /**
+ * Returns a new document containing the key-value pairs:
+ * <em>k1: v1, k2: v2, etc.</em>
+ *
+ * @return a new document containing the key-value pairs:
+ * <em>k1: v1, k2: v2 etc.</em>
+ * @throws NullPointerException if any key-value pair's key is null
+ * @throws IllegalArgumentException if any key-value pair's key is illegal
+ * (starts with {@code '$'} or contains {@code '.'}) or if there are duplicate
+ * keys
+ */
+ public static BsonDocument of(String k1, @Nullable Object v1,
+ String k2, @Nullable Object v2, String k3, @Nullable Object v3,
+ String k4, @Nullable Object v4, String k5, @Nullable Object v5,
+ String k6, @Nullable Object v6, String k7, @Nullable Object v7,
+ String k8, @Nullable Object v8, String k9, @Nullable Object v9) {
+ return builder().put(k1, v1).put(k2, v2).put(k3, v3).put(k4, v4).put(k5, v5)
+ .put(k6, v6).put(k7, v7).put(k8, v8).put(k9, v9).build();
+ }
+
+ /**
+ * Returns a new document containing the key-value pairs:
+ * <em>k1: v1, k2: v2, etc.</em>
+ *
+ * @return a new document containing the key-value pairs:
+ * <em>k1: v1, k2: v2 etc.</em>
+ * @throws NullPointerException if any key-value pair's key is null
+ * @throws IllegalArgumentException if any key-value pair's key is illegal
+ * (starts with {@code '$'} or contains {@code '.'}) or if there are duplicate
+ * keys
+ */
+ public static BsonDocument of(String k1, @Nullable Object v1,
+ String k2, @Nullable Object v2, String k3, @Nullable Object v3,
+ String k4, @Nullable Object v4, String k5, @Nullable Object v5,
+ String k6, @Nullable Object v6, String k7, @Nullable Object v7,
+ String k8, @Nullable Object v8) {
+ return builder().put(k1, v1).put(k2, v2).put(k3, v3).put(k4, v4)
+ .put(k5, v5).put(k6, v6).put(k7, v7).put(k8, v8).build();
+ }
+
+ /**
+ * Returns a new document containing the key-value pairs:
+ * <em>k1: v1, k2: v2, etc.</em>
+ *
+ * @return a new document containing the key-value pairs:
+ * <em>k1: v1, k2: v2 etc.</em>
+ * @throws NullPointerException if any key-value pair's key is null
+ * @throws IllegalArgumentException if any key-value pair's key is illegal
+ * (starts with {@code '$'} or contains {@code '.'}) or if there are duplicate
+ * keys
+ */
+ public static BsonDocument of(String k1, @Nullable Object v1,
+ String k2, @Nullable Object v2, String k3, @Nullable Object v3,
+ String k4, @Nullable Object v4, String k5, @Nullable Object v5,
+ String k6, @Nullable Object v6, String k7, @Nullable Object v7) {
+ return builder().put(k1, v1).put(k2, v2).put(k3, v3).put(k4, v4)
+ .put(k5, v5).put(k6, v6).put(k7, v7).build();
+ }
+
+ /**
+ * Returns a new document containing the key-value pairs:
+ * <em>k1: v1, k2: v2, etc.</em>
+ *
+ * @return a new document containing the key-value pairs:
+ * <em>k1: v1, k2: v2 etc.</em>
+ * @throws NullPointerException if any key-value pair's key is null
+ * @throws IllegalArgumentException if any key-value pair's key is illegal
+ * (starts with {@code '$'} or contains {@code '.'}) or if there are duplicate
+ * keys
+ */
+ public static BsonDocument of(String k1, @Nullable Object v1,
+ String k2, @Nullable Object v2, String k3, @Nullable Object v3,
+ String k4, @Nullable Object v4, String k5, @Nullable Object v5,
+ String k6, @Nullable Object v6) {
+ return builder().put(k1, v1).put(k2, v2).put(k3, v3).put(k4, v4)
+ .put(k5, v5).put(k6, v6).build();
+ }
+
+ /**
+ * Returns a new document containing the key-value pairs:
+ * <em>k1: v1, k2: v2, etc.</em>
+ *
+ * @return a new document containing the key-value pairs:
+ * <em>k1: v1, k2: v2 etc.</em>
+ * @throws NullPointerException if any key-value pair's key is null
+ * @throws IllegalArgumentException if any key-value pair's key is illegal
+ * (starts with {@code '$'} or contains {@code '.'}) or if there are duplicate
+ * keys
+ */
+ public static BsonDocument of(String k1, @Nullable Object v1,
+ String k2, @Nullable Object v2, String k3, @Nullable Object v3,
+ String k4, @Nullable Object v4, String k5, @Nullable Object v5) {
+ return builder().put(k1, v1).put(k2, v2).put(k3, v3).put(k4, v4).put(k5, v5).build();
+ }
+
+ /**
+ * Returns a new document containing the key-value pairs:
+ * <em>k1: v1, k2: v2, etc.</em>
+ *
+ * @return a new document containing the key-value pairs:
+ * <em>k1: v1, k2: v2 etc.</em>
+ * @throws NullPointerException if any key-value pair's key is null
+ * @throws IllegalArgumentException if any key-value pair's key is illegal
+ * (starts with {@code '$'} or contains {@code '.'}) or if there are duplicate
+ * keys
+ */
+ public static BsonDocument of(String k1, @Nullable Object v1,
+ String k2, @Nullable Object v2, String k3, @Nullable Object v3,
+ String k4, @Nullable Object v4) {
+ return builder().put(k1, v1).put(k2, v2).put(k3, v3).put(k4, v4).build();
+ }
+
+ /**
+ * Returns a new document containing the key-value pairs:
+ * <em>k1: v1, k2: v2, etc.</em>
+ *
+ * @return a new document containing the key-value pairs:
+ * <em>k1: v1, k2: v2 etc.</em>
+ * @throws NullPointerException if any key-value pair's key is null
+ * @throws IllegalArgumentException if any key-value pair's key is illegal
+ * (starts with {@code '$'} or contains {@code '.'}) or if there are duplicate
+ * keys
+ */
+ public static BsonDocument of(String k1, @Nullable Object v1,
+ String k2, @Nullable Object v2, String k3, @Nullable Object v3) {
+ return builder().put(k1, v1).put(k2, v2).put(k3, v3).build();
+ }
+
+ /**
+ * Returns a new document containing the key-value pairs:
+ * <em>k1: v1, k2: v2</em>.
+ *
+ * @return a new document containing the key-value pairs:
+ * <em>k1: v1, k2: v2</em>
+ * @throws NullPointerException if any key-value pair's key is null
+ * @throws IllegalArgumentException if any key-value pair's key is illegal
+ * (starts with {@code '$'} or contains {@code '.'}) or if there are duplicate
+ * keys
+ */
+ public static BsonDocument of(String k1, @Nullable Object v1,
+ String k2, @Nullable Object v2) {
+ return builder().put(k1, v1).put(k2, v2).build();
+ }
+
+ // @checkstyle:on ParameterNumber|JavadocMethod
+
+ /**
+ * Returns a new document containing {@code key} and its associated
+ * {@code value}.
+ *
+ * @param key the key that will be used to initialize the new document
+ * @param value the value associated with {@code key}
+ * @return a new document containing {@code key} and its associated
+ * {@code value}
+ * @throws NullPointerException if {@code key} is null
+ * @throws IllegalArgumentException if {@code key} is illegal (starts with
+ * {@code '$'} or contains {@code '.'})
+ */
+ public static BsonDocument of(String key, @Nullable Object value) {
+ return builder().put(key, value).build();
+ }
+
+ /**
+ * Returns the empty document.
+ *
+ * @return the empty document
+ */
+ public static BsonDocument of() {
+ return builder().build();
+ }
+
+ /**
+ * Returns a new {@linkplain BsonDocument.Builder document builder}.
+ *
+ * @return a new document builder
+ */
+ public static BsonDocument.Builder builder() {
+ return new DefaultDocumentBuilder();
+ }
+}
292 src/main/java/com/github/kohanyirobert/ebson/BsonObject.java
@@ -0,0 +1,292 @@
+package com.github.kohanyirobert.ebson;
+
+import com.google.common.base.Preconditions;
+import com.google.common.base.Predicate;
+import com.google.common.base.Predicates;
+
+import javax.annotation.Nullable;
+
+/**
+ * Representation of a <a href="http://bsonspec.org/">BSON</a> object type.
+ *
+ * @see BsonToken
+ * @see BsonBinary
+ */
+public enum BsonObject {
+
+ /**
+ * 64-bit IEEE 754 floating point.
+ */
+ DOUBLE(BsonBytes.DOUBLE, DefaultPredicate.DOUBLE, DefaultReader.DOUBLE,
+ DefaultWriter.DOUBLE),
+
+ /**
+ * UTF-8 string.
+ */
+ STRING(BsonBytes.STRING, DefaultPredicate.STRING, DefaultReader.STRING,
+ DefaultWriter.STRING),
+ /**
+ * Embedded {@linkplain BsonToken#DOCUMENT document}.
+ */
+ EMBEDDED(BsonBytes.EMBEDDED, DefaultPredicate.EMBEDDED,
+ BsonToken.DOCUMENT.reader(), BsonToken.DOCUMENT.writer()),
+
+ /**
+ * Special embedded {@linkplain #EMBEDDED document}.
+ * <p>
+ * <b>Note:</b> an array is a document whose keys are integer values starting
+ * with 0 and continuing sequentially.
+ * </p>
+ */
+ ARRAY(BsonBytes.ARRAY, DefaultPredicate.ARRAY, DefaultReader.ARRAY,
+ DefaultWriter.ARRAY),
+
+ /**
+ * Binary data.
+ */
+ BINARY(BsonBytes.BINARY, DefaultPredicate.BINARY, DefaultReader.BINARY,
+ DefaultWriter.BINARY),
+
+ /**
+ * Undefined.
+ *
+ * @deprecated See the <a href="http://bsonspec.org/#/">BSON</a> specification
+ * for details.
+ */
+ @Deprecated
+ UNDEFINED(BsonBytes.UNDEFINED),
+
+ /**
+ * <a href="http://www.mongodb.org/display/DOCS/Object+IDs">Object ID</a>.
+ * <p>
+ * <b>Note:</b> special <a href="http://mongodb.org">MongoDB</a> related type.
+ * </p>
+ */
+ OBJECT_ID(BsonBytes.OBJECT_ID),
+
+ /**
+ * Boolean.
+ * <p>
+ * <b>Note:</b> 0 is <em>false</em>; 1 is <em>true</em>.
+ * </p>
+ */
+ BOOLEAN(BsonBytes.BOOLEAN, DefaultPredicate.BOOLEAN, DefaultReader.BOOLEAN,
+ DefaultWriter.BOOLEAN),
+
+ /**
+ * UTC date-time.
+ * <p>
+ * <b>Note</b>: milliseconds since the Unix epoch.
+ * </p>
+ */
+ UTC_DATE_TIME(BsonBytes.UTC_DATE_TIME, DefaultPredicate.UTC_DATE_TIME,
+ DefaultReader.UTC_DATE_TIME, DefaultWriter.UTC_DATE_TIME),
+
+ /**
+ * <em>null</em>.
+ */
+ NULL(BsonBytes.NULL, DefaultPredicate.NULL, DefaultReader.NULL,
+ DefaultWriter.NULL),
+
+ /**
+ * Regular expression.
+ */
+ REGULAR_EXPRESSION(BsonBytes.REGULAR_EXPRESSION, DefaultPredicate.REGULAR_EXPRESSION,
+ DefaultReader.REGULAR_EXPRESSION, DefaultWriter.REGULAR_EXPRESSION),
+
+ /**
+ * DB pointer.
+ *
+ * @deprecated See the <a
+ * href="http://api.mongodb.org/java/1.3/com/mongodb/DBPointer.html">following
+ * link</a> for more details.
+ */
+ @Deprecated
+ DB_POINTER(BsonBytes.DB_POINTER),
+
+ /**
+ * JavaScript code.
+ */
+ JAVASCRIPT_CODE(BsonBytes.JAVASCRIPT_CODE),
+
+ /**
+ * Symbol.
+ * <p>
+ * <b>Note:</b> similar to a string but for languages with a distinct symbol
+ * type.
+ * </p>
+ */
+ SYMBOL(BsonBytes.SYMBOL),
+
+ /**
+ * JavaScript code with scope.
+ */
+ JAVASCRIPT_CODE_WITH_SCOPE(BsonBytes.JAVASCRIPT_CODE_WITH_SCOPE),
+
+ /**
+ * 32-bit signed integer.
+ */
+ INT32(BsonBytes.INT32, DefaultPredicate.INT32, DefaultReader.INT32,
+ DefaultWriter.INT32),
+
+ /**
+ * <a href="http://www.mongodb.org/display/DOCS/Timestamp+data+type"
+ * >Timestamp</a>.
+ * <p>
+ * <b>Note:</b> special internal type used by MongoDB replication and
+ * sharding.
+ * </p>
+ */
+ TIMESTAMP(BsonBytes.TIMESTAMP),
+
+ /**
+ * 64-bit signed integer.
+ */
+ INT64(BsonBytes.INT64, DefaultPredicate.INT64, DefaultReader.INT64,
+ DefaultWriter.INT64),
+
+ /**
+ * <a href="http://www.mongodb.org/display/DOCS/min+and+max+Query+Specifiers">
+ * Max key</a>.
+ * <p>
+ * <b>Note:</b> special type which compares higher than all other possible
+ * {@code BSON} element values.
+ * </p>
+ */
+ MAX_KEY(BsonBytes.MAX_KEY),
+
+ /**
+ * <a href="http://www.mongodb.org/display/DOCS/min+and+max+Query+Specifiers">
+ * Min key</a>.
+ * <p>
+ * <b>Note:</b> special type which compares lower than all other possible
+ * {@code BSON} element values.
+ * </p>
+ */
+ MIN_KEY(BsonBytes.MIN_KEY);
+
+ private final byte terminal;
+
+ private Predicate<Class<?>> predicate;
+ private BsonReader reader;
+ private BsonWriter writer;
+
+ private BsonObject(byte terminal) {
+ this(terminal, Predicates.<Class<?>>alwaysFalse(), null, null);
+ }
+
+ private BsonObject(byte terminal, Predicate<Class<?>> predicate,
+ BsonReader reader, BsonWriter writer) {
+ this.terminal = terminal;
+ this.predicate = predicate;
+ this.reader = reader;
+ this.writer = writer;
+ }
+
+ /**
+ * Returns this object's associated terminal.
+ *
+ * @return this object's associated terminal
+ */
+ public byte terminal() {
+ return terminal;
+ }
+
+ /**
+ * Returns this object's associated {@linkplain Predicate predicate}.
+ *
+ * @return this object's associated predicate
+ * @throws IllegalStateException if this object does not have an associated
+ * predicate
+ */
+ public Predicate<Class<?>> predicate() {
+ Preconditions.checkState(predicate != null, "'%s' does not have an associated predicate", this);
+ return predicate;
+ }
+
+ /**
+ * Associates {@link Predicate predicate} with this object.
+ *
+ * @param predicate the predicate to be associated with this object
+ */
+ public void predicate(Predicate<Class<?>> predicate) {
+ Preconditions.checkNotNull(predicate, "cannot associate a null predicate with '%s'", this);
+ this.predicate = predicate;
+ }
+
+ /**
+ * Returns this object's associated {@linkplain BsonReader reader}.
+ *
+ * @return this object's associated reader
+ * @throws IllegalStateException if this object does not have an associated
+ * reader
+ */
+ public BsonReader reader() {
+ Preconditions.checkState(reader != null, "'%s' does not have an associated reader", this);
+ return reader;
+ }
+
+ /**
+ * Associates {@link BsonReader reader} with this object.
+ *
+ * @param reader the reader to be associated with this object
+ */
+ public void reader(BsonReader reader) {
+ Preconditions.checkNotNull(predicate, "cannot associate a null reader with '%s'", this);
+ this.reader = reader;
+ }
+
+ /**
+ * Returns this object's associated {@linkplain BsonWriter writer}.
+ *
+ * @return this object's associated writer
+ * @throws IllegalStateException if this object does not have an associated
+ * writer
+ */
+ public BsonWriter writer() {
+ Preconditions.checkState(writer != null, "'%s' does not have an associated writer", this);
+ return writer;
+ }
+
+ /**
+ * Associates {@link BsonWriter writer} with this object.
+ *
+ * @param writer the writer to be associated with this object
+ */
+ public void writer(BsonWriter writer) {
+ Preconditions.checkNotNull(writer, "cannot associate a null writer with '%s'", this);
+ this.writer = writer;
+ }
+
+ /**
+ * Returns the object representing {@code clazz}.
+ *
+ * @param clazz the class to return a object representation for
+ * @return the object representing {@code clazz}
+ * @throws IllegalArgumentException if no object representing {@code clazz}
+ * was found
+ */
+ public static BsonObject find(@Nullable Class<?> clazz) {
+ for (BsonObject object : values())
+ if (object.predicate().apply(clazz))
+ return object;
+ throw new IllegalArgumentException(String.format("no object "
+ + "representing the '%s' type value was found", clazz));
+ }
+
+ /**
+ * Returns the object representing {@code terminal}.
+ *
+ * @param terminal the terminal to return a object representation for
+ * @return the object representing {@code terminal}
+ * @throws IllegalArgumentException if no object representing {@code terminal}
+ * was found
+ */
+ public static BsonObject find(byte terminal) {
+ for (BsonObject object : values())
+ if (object.terminal() - terminal == 0)
+ return object;
+ throw new IllegalArgumentException(String.format("no object representing "
+ + "the '%s' terminal value was found", Byte.valueOf(terminal)));
+ }
+}
28 src/main/java/com/github/kohanyirobert/ebson/BsonPreconditions.java
@@ -0,0 +1,28 @@
+package com.github.kohanyirobert.ebson;
+
+import javax.annotation.Nullable;
+
+final class BsonPreconditions {
+
+ private BsonPreconditions() {}
+
+ static <T> T checkIsInstance(Object reference, Class<T> clazz) {
+ if (!clazz.isInstance(reference))
+ throw new ClassCastException();
+ return clazz.cast(reference);
+ }
+
+ static <T> T checkIsInstance(Object reference, Class<T> clazz,
+ @Nullable Object errorMessage) {
+ if (!clazz.isInstance(reference))
+ throw new ClassCastException(String.valueOf(errorMessage));
+ return clazz.cast(reference);
+ }
+
+ static <T> T checkIsInstance(Object reference, Class<T> clazz,
+ @Nullable String errorMessageTemplate, @Nullable Object... errorMessageArgs) {
+ if (!clazz.isInstance(reference))
+ throw new ClassCastException(String.format(errorMessageTemplate, errorMessageArgs));
+ return clazz.cast(reference);
+ }
+}
30 src/main/java/com/github/kohanyirobert/ebson/BsonReader.java
@@ -0,0 +1,30 @@
+package com.github.kohanyirobert.ebson;
+
+import java.nio.ByteBuffer;
+
+import javax.annotation.Nullable;
+
+/**
+ * Reads Java object(s) from {@linkplain ByteBuffer buffers}, deserialized from
+ * bytes as specified by the <a href="http://bsonspec.org/">BSON</a>
+ * specification.
+ * <p>
+ * <b>Note:</b> buffers supplied to {@linkplain #readFrom} must use
+ * little-endian byte ordering.
+ * </p>
+ */
+public interface BsonReader {
+
+ /**
+ * Reads an arbitrary amount of bytes from {@code buffer} and
+ * constructs a new object from what was read.
+ *
+ * @param buffer the buffer to read from
+ * @return a new object from the bytes read from {@code buffer}
+ * @throws NullPointerException if {@code buffer} is null
+ * @throws IllegalArgumentException if {@code buffer} is not using
+ * little-endian byte ordering
+ */
+ @Nullable
+ Object readFrom(ByteBuffer buffer);
+}
78 src/main/java/com/github/kohanyirobert/ebson/BsonToken.java
@@ -0,0 +1,78 @@
+package com.github.kohanyirobert.ebson;
+
+import com.google.common.base.Preconditions;
+
+/**
+ * Representation of a <a href="http://bsonspec.org/">BSON</a> token type.
+ */
+public enum BsonToken {
+
+ /**
+ * <a href="http://bsonspec.org/">BSON</a> document.
+ */
+ DOCUMENT(DefaultReader.DOCUMENT, DefaultWriter.DOCUMENT),
+
+ /**
+ * Key-value pair in a {@linkplain #DOCUMENT document}.
+ */
+ FIELD(DefaultReader.FIELD, DefaultWriter.FIELD),
+
+ /**
+ * Key in a {@linkplain #FIELD field} ({@code \0} delimited UTF-8 string).
+ */
+ KEY(DefaultReader.KEY, DefaultWriter.KEY);
+
+ private BsonReader reader;
+ private BsonWriter writer;
+
+ private BsonToken(BsonReader reader, BsonWriter writer) {
+ this.reader = reader;
+ this.writer = writer;
+ }
+
+ private BsonToken() {}
+
+ /**
+ * Returns this token's associated {@linkplain BsonReader reader}.
+ *
+ * @return this token's associated reader
+ * @throws IllegalStateException if this token does not have an associated
+ * reader
+ */
+ public BsonReader reader() {
+ Preconditions.checkState(reader != null, "'%s' does not have an associated reader", this);
+ return reader;
+ }
+
+ /**
+ * Associates {@link BsonReader reader} with this token.
+ *
+ * @param reader the reader to be associated with this token
+ */
+ public void reader(BsonReader reader) {
+ Preconditions.checkNotNull(reader, "cannot associate a null reader with '%s'", this);
+ this.reader = reader;
+ }
+
+ /**
+ * Returns this token's associated {@linkplain BsonWriter writer}.
+ *
+ * @return this token's associated writer
+ * @throws IllegalStateException if this token does not have an associated
+ * writer
+ */
+ public BsonWriter writer() {
+ Preconditions.checkState(writer != null, "'%s' does not have an associated writer", this);
+ return writer;
+ }
+
+ /**
+ * Associates {@link BsonWriter writer} with this token.
+ *
+ * @param writer the writer to be associated with this token
+ */
+ public void writer(BsonWriter writer) {
+ Preconditions.checkNotNull(reader, "cannot associate a null writer with '%s'", this);
+ this.writer = writer;
+ }
+}
27 src/main/java/com/github/kohanyirobert/ebson/BsonWriter.java
@@ -0,0 +1,27 @@
+package com.github.kohanyirobert.ebson;
+
+import java.nio.ByteBuffer;
+
+import javax.annotation.Nullable;
+
+/**
+ * Writes Java object(s) to {@linkplain ByteBuffer buffers}, serialized to bytes
+ * as specified by the <a href="http://bsonspec.org/">BSON</a> specification.
+ * <p>
+ * <b>Note:</b> buffers supplied to {@linkplain #writeTo} must use little-endian
+ * byte ordering.
+ * </p>
+ */
+public interface BsonWriter {
+
+ /**
+ * Writes {@code reference} to {@code buffer}.
+ *
+ * @param buffer the buffer {@code reference} will be written to
+ * @param reference the reference to be written into {@code buffer}
+ * @throws NullPointerException if {@code buffer} is null
+ * @throws IllegalArgumentException if {@code buffer} is not using
+ * little-endian byte ordering
+ */
+ void writeTo(ByteBuffer buffer, @Nullable Object reference);
+}
63 src/main/java/com/github/kohanyirobert/ebson/DefaultDocument.java
@@ -0,0 +1,63 @@
+package com.github.kohanyirobert.ebson;
+
+import com.google.common.base.Joiner;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ForwardingMap;
+import com.google.common.collect.Maps;
+
+import java.util.Collections;
+import java.util.Map;
+
+final class DefaultDocument extends ForwardingMap<String, Object> implements BsonDocument {
+
+ private final Map<String, Object> delegate;
+
+ DefaultDocument(Map<String, Object> map) {
+ delegate = Collections.unmodifiableMap(Maps.newLinkedHashMap(map));
+ }
+
+ @Override
+ public <T> T get(Object key, Class<T> type) {
+ Object value = get(key);
+ Preconditions.checkArgument(
+ type == null
+ ? value == null
+ : type.isInstance(value),
+ "expected '%s' instead of '%s'",
+ value == null
+ ? null
+ : value.getClass(), type);
+ return type == null ? null : type.cast(value);
+ }
+
+ @Override
+ public Object get(Object key) {
+ Preconditions.checkArgument(containsKey(key), "key: '%s' is missing", key);
+ return super.get(key);
+ }
+
+ @Override
+ public boolean containsKey(Object key) {
+ Preconditions.checkNotNull(key, "null key");
+ BsonPreconditions.checkIsInstance(key, String.class, "key: '%s' is not a string", key);
+ Preconditions.checkArgument(!((String) key).startsWith("$"), "key: '%s' starts with '$'", key);
+ Preconditions.checkArgument(!((String) key).contains("."), "key: '%s' contains '.'", key);
+ return super.containsKey(key);
+ }
+
+ @Override
+ public String toString() {
+ return new StringBuilder("{")
+ .append(Joiner.on(", ")
+ .withKeyValueSeparator(": ")
+ .useForNull("null")
+ .join(this))
+ .append("}")
+ .toString();
+ }
+
+ @Override
+ protected Map<String, Object> delegate() {
+ return delegate;
+ }
+}
44 src/main/java/com/github/kohanyirobert/ebson/DefaultDocumentBuilder.java
@@ -0,0 +1,44 @@
+package com.github.kohanyirobert.ebson;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Maps;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import javax.annotation.Nullable;
+
+final class DefaultDocumentBuilder implements BsonDocument.Builder {
+
+ private final Map<String, Object> builder;
+
+ DefaultDocumentBuilder() {
+ builder = Maps.newLinkedHashMap();
+ }
+
+ @Override
+ public BsonDocument.Builder putAll(Map<String, Object> map) {
+ Preconditions.checkNotNull(map, "null map");
+ for (Entry<String, Object> entry : map.entrySet())
+ put(entry.getKey(), entry.getValue());
+ return this;
+ }
+
+ @Override
+ public BsonDocument.Builder put(String key, @Nullable Object value) {
+ Preconditions.checkNotNull(key, "null key");
+ Preconditions.checkArgument(!key.startsWith("$"), "key: '%s' starts with '$'", key);
+ Preconditions.checkArgument(!key.contains("."), "key: '%s' contains '.'", key);
+ Preconditions.checkArgument(!builder.containsKey(key), "key: '%s' is already present", key);
+ builder.put(key, value);
+ return this;
+ }
+
+ @Override
+ public BsonDocument build() {
+ return new DefaultDocument(builder.isEmpty()
+ ? Collections.<String, Object>emptyMap()
+ : builder);
+ }
+}
132 src/main/java/com/github/kohanyirobert/ebson/DefaultPredicate.java
@@ -0,0 +1,132 @@
+package com.github.kohanyirobert.ebson;
+
+import com.google.common.base.Predicate;
+
+import java.util.Collection;
+import java.util.Date;
+import java.util.Map;
+import java.util.regex.Pattern;
+
+enum DefaultPredicate implements Predicate<Class<?>> {
+
+ DOUBLE {
+
+ @Override
+ public boolean apply(Class<?> input) {
+ return input == null
+ ? false
+ : Double.class.isAssignableFrom(input);
+ }
+ },
+
+ STRING {
+
+ @Override
+ public boolean apply(Class<?> input) {
+ return input == null
+ ? false
+ : String.class.isAssignableFrom(input);
+ }
+ },
+
+ EMBEDDED {
+
+ @Override
+ public boolean apply(Class<?> input) {
+ return input == null
+ ? false
+ : Map.class.isAssignableFrom(input);
+ }
+ },
+
+ ARRAY {
+
+ @Override
+ public boolean apply(Class<?> input) {
+ return input == null
+ ? false
+ : byte[].class.isAssignableFrom(input)
+ ? false
+ : Collection.class.isAssignableFrom(input) || input.isArray();
+ }
+ },
+
+ BINARY {
+
+ @Override
+ public boolean apply(Class<?> input) {
+ return GENERIC.apply(input);
+ }
+ },
+
+ GENERIC {
+
+ @Override
+ public boolean apply(Class<?> input) {
+ return input == null
+ ? false
+ : byte[].class.isAssignableFrom(input);
+ }
+ },
+
+ BOOLEAN {
+
+ @Override
+ public boolean apply(Class<?> input) {
+ return input == null
+ ? false
+ : Boolean.class.isAssignableFrom(input);
+ }
+ },
+
+ UTC_DATE_TIME {
+
+ @Override
+ public boolean apply(Class<?> input) {
+ return input == null
+ ? false
+ : Date.class.isAssignableFrom(input);
+ }
+ },
+
+ NULL {
+
+ @Override
+ public boolean apply(Class<?> input) {
+ return input == null;
+ }
+ },
+
+ REGULAR_EXPRESSION {
+
+ @Override
+ public boolean apply(Class<?> input) {
+ return input == null
+ ? false
+ : Pattern.class.isAssignableFrom(input);
+ }
+ },
+
+ INT32 {
+
+ @Override
+ public boolean apply(Class<?> input) {
+ return input == null
+ ? false
+ : Integer.class.isAssignableFrom(input);
+ }
+ },
+
+ INT64 {
+
+ @Override
+ public boolean apply(Class<?> input) {
+ return input == null
+ ? false
+ : Long.class.isAssignableFrom(input);
+ }
+ };
+
+ @Override
+ public abstract boolean apply(Class<?> input);
+}
200 src/main/java/com/github/kohanyirobert/ebson/DefaultReader.java
@@ -0,0 +1,200 @@
+package com.github.kohanyirobert.ebson;
+
+import com.google.common.base.Charsets;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Maps;
+import com.google.common.primitives.Bytes;
+import com.google.common.primitives.Ints;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.Date;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.regex.Pattern;
+
+enum DefaultReader implements BsonReader {
+
+ DOCUMENT {
+
+ @Override
+ public Object checkedReadFrom(ByteBuffer buffer) {
+ int documentLength = buffer.getInt();
+ BsonReader fieldReader = BsonToken.FIELD.reader();
+ Map<Object, Object> document = Maps.newLinkedHashMap();
+ if (documentLength > Ints.BYTES + 1)
+ do {
+ Entry<?, ?> entry = (Entry<?, ?>) fieldReader.readFrom(buffer);
+ document.put(entry.getKey(), entry.getValue());
+ } while (buffer.get(buffer.position()) != BsonBytes.EOO);
+ buffer.get();
+ return document;
+ }
+ },
+
+ FIELD {
+
+ @Override
+ public Object checkedReadFrom(ByteBuffer buffer) {
+ BsonObject bsonObject = BsonObject.find(buffer.get());
+ BsonReader keyReader = BsonToken.KEY.reader();
+ BsonReader valueReader = bsonObject.reader();
+ return Maps.immutableEntry(keyReader.readFrom(buffer),
+ valueReader.readFrom(buffer));
+ }
+ },
+
+ KEY {
+
+ @Override
+ public Object checkedReadFrom(ByteBuffer buffer) {
+ byte[] bytes = new byte[] {};
+ byte[] read = new byte[] {BsonBytes.EOF};
+ while ((read[0] = buffer.get()) != BsonBytes.EOO)
+ bytes = Bytes.concat(bytes, read);
+ return new String(bytes, Charsets.UTF_8);
+ }
+ },
+
+ DOUBLE {
+
+ @Override
+ public Double checkedReadFrom(ByteBuffer buffer) {
+ return Double.valueOf(buffer.getDouble());
+ }
+ },
+
+ STRING {
+
+ @Override
+ public Object checkedReadFrom(ByteBuffer buffer) {
+ int stringLength = buffer.getInt();
+ byte[] bytes = new byte[stringLength - 1];
+ buffer.get(bytes).get();
+ return new String(bytes, Charsets.UTF_8);
+ }
+ },
+
+ ARRAY {
+
+ @Override
+ public Object checkedReadFrom(ByteBuffer buffer) {
+ return BsonToken.DOCUMENT.reader().readFrom(buffer);
+ }
+ },
+
+ BINARY {
+
+ @Override
+ public Object checkedReadFrom(ByteBuffer buffer) {
+ int binaryLength = buffer.getInt();
+ byte terminal = buffer.get();
+ BsonBinary bsonBinary = BsonBinary.find(terminal);
+ int oldLimit = buffer.limit();
+ buffer.limit(buffer.position() + binaryLength);
+ byte[] binary = (byte[]) bsonBinary.reader().readFrom(buffer);
+ buffer.limit(oldLimit);
+ return binary;
+ }
+ },
+
+ GENERIC {
+
+ @Override
+ public Object checkedReadFrom(ByteBuffer buffer) {
+ byte[] binary = new byte[buffer.remaining()];
+ buffer.get(binary);
+ return binary;
+ }
+ },
+
+ UTC_DATE_TIME {
+
+ @Override
+ public Object checkedReadFrom(ByteBuffer buffer) {
+ return new Date(buffer.getLong());
+ }
+ },
+
+ BOOLEAN {
+
+ @Override
+ public Object checkedReadFrom(ByteBuffer buffer) {
+ return Boolean.valueOf(buffer.get() == BsonBytes.TRUE);
+ }
+ },
+
+ NULL {
+
+ @Override
+ public Object checkedReadFrom(ByteBuffer buffer) {
+ return null;
+ }
+ },
+
+ REGULAR_EXPRESSION {
+
+ @Override
+ public Object checkedReadFrom(ByteBuffer buffer) {
+ BsonReader keyReader = BsonToken.KEY.reader();
+ String pattern = (String) keyReader.readFrom(buffer);
+ String options = (String) keyReader.readFrom(buffer);
+ return Pattern.compile(pattern, optionsToFlags(options));
+ }
+
+ private int optionsToFlags(String options) {
+ int flags = 0;
+ for (char option : options.toCharArray())
+ flags |= flags + optionToFlag(option);
+ return flags;
+ }
+
+ // @do-not-check CyclomaticComplexity
+ private int optionToFlag(char option) {
+ int flag = 0;
+ switch (option) {
+ case 'i':
+ flag = Pattern.CASE_INSENSITIVE;
+ break;
+ case 'm':
+ flag = Pattern.MULTILINE;
+ break;
+ case 's':
+ flag = Pattern.DOTALL;
+ break;
+ case 'x':
+ flag = Pattern.COMMENTS;
+ break;
+ default:
+ break;
+ }
+ return flag;
+ }
+ },
+
+ INT32 {
+
+ @Override
+ public Object checkedReadFrom(ByteBuffer buffer) {
+ return Integer.valueOf(buffer.getInt());
+ }
+ },
+
+ INT64 {
+
+ @Override
+ public Object checkedReadFrom(ByteBuffer buffer) {
+ return Long.valueOf(buffer.getLong());
+ }
+ };
+
+ @Override
+ public final Object readFrom(ByteBuffer buffer) {
+ Preconditions.checkNotNull(buffer, "null buffer");
+ Preconditions.checkArgument(buffer.order() == ByteOrder.LITTLE_ENDIAN,
+ "buffer has big-endian byte order; expected little-endian");
+ return checkedReadFrom(buffer);
+ }
+
+ abstract Object checkedReadFrom(ByteBuffer buffer);
+}
179 src/main/java/com/github/kohanyirobert/ebson/DefaultWriter.java
@@ -0,0 +1,179 @@
+package com.github.kohanyirobert.ebson;
+
+import com.google.common.base.Charsets;
+import com.google.common.base.Joiner;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import com.google.common.primitives.Ints;
+
+import java.lang.reflect.Array;
+import java.nio.ByteBuffer;
+import java.util.Collection;
+import java.util.Date;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.SortedSet;
+import java.util.regex.Pattern;
+
+enum DefaultWriter implements BsonWriter {
+
+ DOCUMENT {
+
+ @Override
+ public void writeTo(ByteBuffer buffer, Object reference) {
+ int markedPosition = buffer.position();
+ buffer.position(markedPosition + Ints.BYTES);
+ BsonWriter fieldWriter = BsonToken.FIELD.writer();
+ for (Entry<?, ?> entry : ((Map<?, ?>) reference).entrySet())
+ fieldWriter.writeTo(buffer, entry);
+ buffer.put(BsonBytes.EOO);
+ buffer.putInt(markedPosition, buffer.position() - markedPosition);
+ }
+ },
+
+ FIELD {
+
+ @Override
+ public void writeTo(ByteBuffer buffer, Object reference) {
+ Entry<?, ?> entry = (Entry<?, ?>) reference;
+ BsonObject bsonObject = BsonObject.find(entry.getValue() == null
+ ? null
+ : entry.getValue().getClass());
+ buffer.put(bsonObject.terminal());
+ BsonToken.KEY.writer().writeTo(buffer, entry.getKey());
+ bsonObject.writer().writeTo(buffer, entry.getValue());
+ }
+ },
+
+ KEY {
+
+ @Override
+ public void writeTo(ByteBuffer buffer, Object reference) {
+ buffer.put(((String) reference).getBytes(Charsets.UTF_8)).put(BsonBytes.EOO);
+ }
+ },
+
+ DOUBLE {
+
+ @Override
+ public void writeTo(ByteBuffer buffer, Object reference) {
+ buffer.putDouble(((Double) reference).doubleValue());
+ }
+ },
+
+ STRING {
+
+ @Override
+ public void writeTo(ByteBuffer buffer, Object reference) {
+ byte[] bytes = ((String) reference).getBytes(Charsets.UTF_8);
+ buffer.putInt(bytes.length + 1).put(bytes).put(BsonBytes.EOO);
+ }
+ },
+
+ ARRAY {
+
+ @Override
+ public void writeTo(ByteBuffer buffer, Object reference) {
+ Object array = reference instanceof Collection
+ ? ((Collection<?>) reference).toArray()
+ : reference;
+ Map<Object, Object> document = Maps.newLinkedHashMap();
+ for (int i = 0; i < Array.getLength(array); i++)
+ document.put(String.valueOf(i), Array.get(array, i));
+ BsonToken.DOCUMENT.writer().writeTo(buffer, document);
+ }
+ },
+
+ BINARY {
+
+ @Override
+ public void writeTo(ByteBuffer buffer, Object reference) {
+ byte[] binary = (byte[]) reference;
+ buffer.putInt(binary.length);
+ BsonBinary bsonBinary = BsonBinary.find(binary.getClass());
+ buffer.put(bsonBinary.terminal());
+ bsonBinary.writer().writeTo(buffer, binary);
+ }
+ },
+
+ GENERIC {
+
+ @Override
+ public void writeTo(ByteBuffer buffer, Object reference) {
+ buffer.put((byte[]) reference);
+ }
+ },
+
+ UTC_DATE_TIME {
+
+ @Override
+ public void writeTo(ByteBuffer buffer, Object reference) {
+ buffer.putLong(((Date) reference).getTime());
+ }
+ },
+
+ BOOLEAN {
+
+ @Override
+ public void writeTo(ByteBuffer buffer, Object reference) {
+ buffer.put(((Boolean) reference).booleanValue()
+ ? BsonBytes.TRUE
+ : BsonBytes.FALSE);
+ }
+ },
+
+ NULL {
+
+ @Override
+ public void writeTo(ByteBuffer buffer, Object reference) {}
+ },
+
+ REGULAR_EXPRESSION {
+
+ @Override
+ public void writeTo(ByteBuffer buffer, Object reference) {
+ Pattern regularExpression = (Pattern) reference;
+ BsonWriter keyWriter = BsonToken.KEY.writer();
+ keyWriter.writeTo(buffer, regularExpression.pattern());
+ keyWriter.writeTo(buffer, flagsToOptions(regularExpression.flags()));
+ }
+
+ // @do-not-check CyclomaticComplexity
+ private String flagsToOptions(int flags) {
+ SortedSet<Character> options = Sets.newTreeSet();
+ if (hasFlag(flags, Pattern.CASE_INSENSITIVE))
+ options.add(Character.valueOf('i'));
+
+ if (hasFlag(flags, Pattern.COMMENTS))
+ options.add(Character.valueOf('x'));
+
+ if (hasFlag(flags, Pattern.DOTALL))
+ options.add(Character.valueOf('s'));
+
+ if (hasFlag(flags, Pattern.MULTILINE))
+ options.add(Character.valueOf('m'));
+
+ return Joiner.on("").join(options);
+ }
+
+ private boolean hasFlag(int flags, int flag) {
+ return (flags & flag) != 0;
+ }
+ },
+
+ INT32 {
+
+ @Override
+ public void writeTo(ByteBuffer buffer, Object reference) {
+ buffer.putInt(((Integer) reference).intValue());
+ }
+ },
+
+ INT64 {
+
+ @Override
+ public void writeTo(ByteBuffer buffer, Object reference) {
+ buffer.putLong(((Long) reference).longValue());
+ }
+ };
+}
8 src/main/java/com/github/kohanyirobert/ebson/package-info.java
@@ -0,0 +1,8 @@
+/**
+ * <a href="http://bsonspec.org/">BSON</a> encoder/decoder.
+ */
+@ParametersAreNonnullByDefault
+package com.github.kohanyirobert.ebson;
+
+import javax.annotation.ParametersAreNonnullByDefault;
+
17 src/test/java/com/github/kohanyirobert/ebson/AbstractBsonTest.java
@@ -0,0 +1,17 @@
+package com.github.kohanyirobert.ebson;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+public abstract class AbstractBsonTest {
+
+ protected static final int MAX_BSON_SIZE = 16 * 1024 * 1024;
+
+ protected static final ThreadLocal<ByteBuffer> BUFFER = new ThreadLocal<ByteBuffer>() {
+
+ @Override
+ protected ByteBuffer initialValue() {
+ return ByteBuffer.allocate(MAX_BSON_SIZE).order(ByteOrder.LITTLE_ENDIAN);
+ }
+ };
+}
28 src/test/java/com/github/kohanyirobert/ebson/AbstractReaderWriterTest.java
@@ -0,0 +1,28 @@
+package com.github.kohanyirobert.ebson;
+
+import java.nio.ByteBuffer;
+
+import javax.annotation.Nullable;
+
+public abstract class AbstractReaderWriterTest extends AbstractBsonTest {
+
+ protected final BsonReader reader;
+ protected final BsonWriter writer;
+
+ protected AbstractReaderWriterTest(BsonReader reader, BsonWriter writer) {
+ this.reader = reader;
+ this.writer = writer;
+ }
+
+ protected final Object writeTo(@Nullable Object object) {
+ ByteBuffer buffer = BUFFER.get();
+ buffer.clear();
+ writer.writeTo(buffer, object);
+ buffer.flip();
+ return object;
+ }
+
+ protected final Object readFrom() {
+ return reader.readFrom(BUFFER.get());
+ }
+}
48 src/test/java/com/github/kohanyirobert/ebson/BsonDocumentTest.java
@@ -0,0 +1,48 @@
+package com.github.kohanyirobert.ebson;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+
+public final class BsonDocumentTest {
+
+ private static final String STRING_KEY = "string-key";
+ private static final String NULL_KEY = "null-key";
+ private static final String MISSING_KEY = "string-key-not-present";
+
+ private static final Object STRING_VALUE = "string-value";
+
+ private final BsonDocument document;
+
+ public BsonDocumentTest() {
+ document = BsonDocuments.of(
+ STRING_KEY, STRING_VALUE,
+ NULL_KEY, null);
+ }
+
+ @Test
+ public void containsKey_withStringKey() {
+ assertTrue(document.containsKey(STRING_KEY));
+ }
+
+ @Test(expected = ClassCastException.class)
+ public void containsKey_withNotStringKey() {
+ document.containsKey(new Object());
+ }
+
+ @Test
+ public void get_keyPresent_withStringKey() {
+ assertEquals(STRING_VALUE, document.get(STRING_KEY));
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void get_keyNotPresent_withStringKey() {
+ document.get(MISSING_KEY);
+ }
+
+ @Test(expected = ClassCastException.class)
+ public void get_keyPresent_withNotStringKey() {
+ document.get(new Object());
+ }
+}
135 src/test/java/com/github/kohanyirobert/ebson/BsonDocumentsTest.java
@@ -0,0 +1,135 @@
+package com.github.kohanyirobert.ebson;
+
+import com.google.common.collect.Maps;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@SuppressWarnings(