From 0dc8a972e0fd8349f82c2350bea388a85d5e3567 Mon Sep 17 00:00:00 2001
From: Olof Larsson
Date: Tue, 14 Aug 2012 17:24:52 +0200
Subject: [PATCH] Update for Minecraft 1.3.1
---
src/com/massivecraft/mcore3/MCore.java | 3 +
.../mcore3/gson/MongoURIAdapter.java | 55 +
.../massivecraft/mcore3/lib/bson/BSON.java | 353 ++++
.../mcore3/lib/bson/BSONCallback.java | 70 +
.../mcore3/lib/bson/BSONDecoder.java | 34 +
.../mcore3/lib/bson/BSONEncoder.java | 27 +
.../mcore3/lib/bson/BSONException.java | 73 +
.../mcore3/lib/bson/BSONLazyDecoder.java | 24 +
.../mcore3/lib/bson/BSONObject.java | 93 +
.../mcore3/lib/bson/BasicBSONCallback.java | 206 +++
.../mcore3/lib/bson/BasicBSONDecoder.java | 527 ++++++
.../mcore3/lib/bson/BasicBSONEncoder.java | 530 ++++++
.../mcore3/lib/bson/BasicBSONObject.java | 355 ++++
.../mcore3/lib/bson/EmptyBSONCallback.java | 143 ++
.../lib/bson/KeyCachingLazyBSONObject.java | 70 +
.../mcore3/lib/bson/LazyBSONCallback.java | 88 +
.../mcore3/lib/bson/LazyBSONDecoder.java | 70 +
.../mcore3/lib/bson/LazyBSONList.java | 177 ++
.../mcore3/lib/bson/LazyBSONObject.java | 691 ++++++++
.../mcore3/lib/bson/LazyDBList.java | 42 +
.../mcore3/lib/bson/NewBSONDecoder.java | 304 ++++
.../mcore3/lib/bson/Transformer.java | 27 +
.../mcore3/lib/bson/io/BSONByteBuffer.java | 143 ++
.../mcore3/lib/bson/io/BasicOutputBuffer.java | 119 ++
.../massivecraft/mcore3/lib/bson/io/Bits.java | 115 ++
.../mcore3/lib/bson/io/OutputBuffer.java | 159 ++
.../mcore3/lib/bson/io/PoolOutputBuffer.java | 239 +++
.../mcore3/lib/bson/io/UTF8Encoding.java | 202 +++
.../mcore3/lib/bson/io/package.html | 3 +
.../massivecraft/mcore3/lib/bson/package.html | 3 +
.../mcore3/lib/bson/types/BSONTimestamp.java | 76 +
.../mcore3/lib/bson/types/BasicBSONList.java | 165 ++
.../mcore3/lib/bson/types/Binary.java | 67 +
.../mcore3/lib/bson/types/Code.java | 58 +
.../mcore3/lib/bson/types/CodeWScope.java | 53 +
.../mcore3/lib/bson/types/MaxKey.java | 47 +
.../mcore3/lib/bson/types/MinKey.java | 47 +
.../mcore3/lib/bson/types/ObjectId.java | 401 +++++
.../mcore3/lib/bson/types/Symbol.java | 74 +
.../mcore3/lib/bson/types/package.html | 3 +
.../lib/bson/util/AbstractCopyOnWriteMap.java | 631 +++++++
.../mcore3/lib/bson/util/Assertions.java | 48 +
.../mcore3/lib/bson/util/ClassAncestry.java | 72 +
.../mcore3/lib/bson/util/ClassMap.java | 100 ++
.../mcore3/lib/bson/util/ComputingMap.java | 109 ++
.../mcore3/lib/bson/util/CopyOnWriteMap.java | 273 +++
.../mcore3/lib/bson/util/Function.java | 5 +
.../mcore3/lib/bson/util/SimplePool.java | 58 +
.../mcore3/lib/bson/util/StringRangeSet.java | 130 ++
.../lib/bson/util/annotations/GuardedBy.java | 54 +
.../lib/bson/util/annotations/Immutable.java | 35 +
.../bson/util/annotations/NotThreadSafe.java | 27 +
.../lib/bson/util/annotations/ThreadSafe.java | 27 +
.../mcore3/lib/bson/util/package.html | 3 +
.../mcore3/lib/mongodb/BasicDBList.java | 67 +
.../mcore3/lib/mongodb/BasicDBObject.java | 110 ++
.../lib/mongodb/BasicDBObjectBuilder.java | 142 ++
.../mcore3/lib/mongodb/Bytes.java | 227 +++
.../mcore3/lib/mongodb/CommandResult.java | 153 ++
.../massivecraft/mcore3/lib/mongodb/DB.java | 729 ++++++++
.../mcore3/lib/mongodb/DBAddress.java | 186 ++
.../mcore3/lib/mongodb/DBApiLayer.java | 575 +++++++
.../mcore3/lib/mongodb/DBCallback.java | 30 +
.../mcore3/lib/mongodb/DBCallbackFactory.java | 27 +
.../mcore3/lib/mongodb/DBCollection.java | 1522 +++++++++++++++++
.../mcore3/lib/mongodb/DBConnector.java | 106 ++
.../mcore3/lib/mongodb/DBCursor.java | 761 +++++++++
.../mcore3/lib/mongodb/DBDecoder.java | 32 +
.../mcore3/lib/mongodb/DBDecoderFactory.java | 25 +
.../mcore3/lib/mongodb/DBEncoder.java | 21 +
.../mcore3/lib/mongodb/DBEncoderFactory.java | 25 +
.../mcore3/lib/mongodb/DBObject.java | 40 +
.../mcore3/lib/mongodb/DBPointer.java | 60 +
.../mcore3/lib/mongodb/DBPort.java | 345 ++++
.../mcore3/lib/mongodb/DBPortPool.java | 252 +++
.../mcore3/lib/mongodb/DBRef.java | 65 +
.../mcore3/lib/mongodb/DBRefBase.java | 105 ++
.../mcore3/lib/mongodb/DBTCPConnector.java | 581 +++++++
.../mcore3/lib/mongodb/DefaultDBCallback.java | 142 ++
.../mcore3/lib/mongodb/DefaultDBDecoder.java | 60 +
.../mcore3/lib/mongodb/DefaultDBEncoder.java | 83 +
.../mcore3/lib/mongodb/GroupCommand.java | 51 +
.../mcore3/lib/mongodb/LazyDBCallback.java | 57 +
.../mcore3/lib/mongodb/LazyDBDecoder.java | 58 +
.../mcore3/lib/mongodb/LazyDBEncoder.java | 44 +
.../mcore3/lib/mongodb/LazyDBObject.java | 50 +
.../lib/mongodb/LazyWriteableDBCallback.java | 47 +
.../lib/mongodb/LazyWriteableDBDecoder.java | 36 +
.../lib/mongodb/LazyWriteableDBObject.java | 120 ++
.../mcore3/lib/mongodb/MapReduceCommand.java | 327 ++++
.../mcore3/lib/mongodb/MapReduceOutput.java | 112 ++
.../mcore3/lib/mongodb/Mongo.java | 742 ++++++++
.../mcore3/lib/mongodb/MongoException.java | 171 ++
.../lib/mongodb/MongoInternalException.java | 47 +
.../mcore3/lib/mongodb/MongoOptions.java | 515 ++++++
.../mcore3/lib/mongodb/MongoURI.java | 290 ++++
.../mcore3/lib/mongodb/OutMessage.java | 166 ++
.../mcore3/lib/mongodb/QueryBuilder.java | 396 +++++
.../mcore3/lib/mongodb/QueryOperators.java | 39 +
.../mcore3/lib/mongodb/RawDBObject.java | 366 ++++
.../mcore3/lib/mongodb/ReadPreference.java | 70 +
.../lib/mongodb/ReflectionDBObject.java | 292 ++++
.../mcore3/lib/mongodb/ReplicaSetStatus.java | 834 +++++++++
.../mcore3/lib/mongodb/Response.java | 199 +++
.../mcore3/lib/mongodb/ServerAddress.java | 207 +++
.../mcore3/lib/mongodb/ServerError.java | 103 ++
.../mcore3/lib/mongodb/WriteConcern.java | 453 +++++
.../mcore3/lib/mongodb/WriteResult.java | 161 ++
.../mcore3/lib/mongodb/gridfs/CLI.java | 162 ++
.../mcore3/lib/mongodb/gridfs/GridFS.java | 355 ++++
.../lib/mongodb/gridfs/GridFSDBFile.java | 200 +++
.../mcore3/lib/mongodb/gridfs/GridFSFile.java | 305 ++++
.../lib/mongodb/gridfs/GridFSInputFile.java | 419 +++++
.../mcore3/lib/mongodb/gridfs/package.html | 3 +
.../lib/mongodb/io/ByteBufferFactory.java | 37 +
.../lib/mongodb/io/ByteBufferHolder.java | 127 ++
.../lib/mongodb/io/ByteBufferInputStream.java | 120 ++
.../mongodb/io/ByteBufferOutputStream.java | 95 +
.../mcore3/lib/mongodb/io/ByteStream.java | 28 +
.../mcore3/lib/mongodb/package.html | 3 +
.../util/AbstractObjectSerializer.java | 27 +
.../mcore3/lib/mongodb/util/Args.java | 81 +
.../mcore3/lib/mongodb/util/Base64Codec.java | 117 ++
.../util/ClassMapBasedObjectSerializer.java | 85 +
.../mcore3/lib/mongodb/util/FastStack.java | 55 +
.../mcore3/lib/mongodb/util/Hash.java | 249 +++
.../mcore3/lib/mongodb/util/IdentitySet.java | 81 +
.../mcore3/lib/mongodb/util/JSON.java | 560 ++++++
.../mcore3/lib/mongodb/util/JSONCallback.java | 161 ++
.../lib/mongodb/util/JSONParseException.java | 58 +
.../lib/mongodb/util/JSONSerializers.java | 471 +++++
.../mcore3/lib/mongodb/util/MyAsserts.java | 203 +++
.../lib/mongodb/util/ObjectSerializer.java | 37 +
.../mcore3/lib/mongodb/util/OptionMap.java | 31 +
.../mcore3/lib/mongodb/util/SimplePool.java | 360 ++++
.../lib/mongodb/util/StringBuilderPool.java | 55 +
.../lib/mongodb/util/StringParseUtil.java | 267 +++
.../mcore3/lib/mongodb/util/ThreadPool.java | 145 ++
.../mcore3/lib/mongodb/util/ThreadUtil.java | 76 +
.../lib/mongodb/util/TimeConstants.java | 39 +
.../mcore3/lib/mongodb/util/UniqueList.java | 41 +
.../mcore3/lib/mongodb/util/Util.java | 72 +
.../mcore3/lib/mongodb/util/WeakBag.java | 126 ++
.../mcore3/lib/mongodb/util/package.html | 3 +
.../mcore3/mongodb/InventoryTypeAdapter.java | 76 +
.../mcore3/mongodb/ItemStackAdapter.java | 99 ++
.../massivecraft/mcore3/util/DiscUtil.java | 17 +
.../massivecraft/mcore3/util/PlayerUtil.java | 7 +-
.../massivecraft/mcore3/util/SmokeUtil.java | 58 +-
149 files changed, 25394 insertions(+), 49 deletions(-)
create mode 100644 src/com/massivecraft/mcore3/gson/MongoURIAdapter.java
create mode 100644 src/com/massivecraft/mcore3/lib/bson/BSON.java
create mode 100644 src/com/massivecraft/mcore3/lib/bson/BSONCallback.java
create mode 100644 src/com/massivecraft/mcore3/lib/bson/BSONDecoder.java
create mode 100644 src/com/massivecraft/mcore3/lib/bson/BSONEncoder.java
create mode 100644 src/com/massivecraft/mcore3/lib/bson/BSONException.java
create mode 100644 src/com/massivecraft/mcore3/lib/bson/BSONLazyDecoder.java
create mode 100644 src/com/massivecraft/mcore3/lib/bson/BSONObject.java
create mode 100644 src/com/massivecraft/mcore3/lib/bson/BasicBSONCallback.java
create mode 100644 src/com/massivecraft/mcore3/lib/bson/BasicBSONDecoder.java
create mode 100644 src/com/massivecraft/mcore3/lib/bson/BasicBSONEncoder.java
create mode 100644 src/com/massivecraft/mcore3/lib/bson/BasicBSONObject.java
create mode 100644 src/com/massivecraft/mcore3/lib/bson/EmptyBSONCallback.java
create mode 100644 src/com/massivecraft/mcore3/lib/bson/KeyCachingLazyBSONObject.java
create mode 100644 src/com/massivecraft/mcore3/lib/bson/LazyBSONCallback.java
create mode 100644 src/com/massivecraft/mcore3/lib/bson/LazyBSONDecoder.java
create mode 100644 src/com/massivecraft/mcore3/lib/bson/LazyBSONList.java
create mode 100644 src/com/massivecraft/mcore3/lib/bson/LazyBSONObject.java
create mode 100644 src/com/massivecraft/mcore3/lib/bson/LazyDBList.java
create mode 100644 src/com/massivecraft/mcore3/lib/bson/NewBSONDecoder.java
create mode 100644 src/com/massivecraft/mcore3/lib/bson/Transformer.java
create mode 100644 src/com/massivecraft/mcore3/lib/bson/io/BSONByteBuffer.java
create mode 100644 src/com/massivecraft/mcore3/lib/bson/io/BasicOutputBuffer.java
create mode 100644 src/com/massivecraft/mcore3/lib/bson/io/Bits.java
create mode 100644 src/com/massivecraft/mcore3/lib/bson/io/OutputBuffer.java
create mode 100644 src/com/massivecraft/mcore3/lib/bson/io/PoolOutputBuffer.java
create mode 100644 src/com/massivecraft/mcore3/lib/bson/io/UTF8Encoding.java
create mode 100644 src/com/massivecraft/mcore3/lib/bson/io/package.html
create mode 100644 src/com/massivecraft/mcore3/lib/bson/package.html
create mode 100644 src/com/massivecraft/mcore3/lib/bson/types/BSONTimestamp.java
create mode 100644 src/com/massivecraft/mcore3/lib/bson/types/BasicBSONList.java
create mode 100644 src/com/massivecraft/mcore3/lib/bson/types/Binary.java
create mode 100644 src/com/massivecraft/mcore3/lib/bson/types/Code.java
create mode 100644 src/com/massivecraft/mcore3/lib/bson/types/CodeWScope.java
create mode 100644 src/com/massivecraft/mcore3/lib/bson/types/MaxKey.java
create mode 100644 src/com/massivecraft/mcore3/lib/bson/types/MinKey.java
create mode 100644 src/com/massivecraft/mcore3/lib/bson/types/ObjectId.java
create mode 100644 src/com/massivecraft/mcore3/lib/bson/types/Symbol.java
create mode 100644 src/com/massivecraft/mcore3/lib/bson/types/package.html
create mode 100644 src/com/massivecraft/mcore3/lib/bson/util/AbstractCopyOnWriteMap.java
create mode 100644 src/com/massivecraft/mcore3/lib/bson/util/Assertions.java
create mode 100644 src/com/massivecraft/mcore3/lib/bson/util/ClassAncestry.java
create mode 100644 src/com/massivecraft/mcore3/lib/bson/util/ClassMap.java
create mode 100644 src/com/massivecraft/mcore3/lib/bson/util/ComputingMap.java
create mode 100644 src/com/massivecraft/mcore3/lib/bson/util/CopyOnWriteMap.java
create mode 100644 src/com/massivecraft/mcore3/lib/bson/util/Function.java
create mode 100644 src/com/massivecraft/mcore3/lib/bson/util/SimplePool.java
create mode 100644 src/com/massivecraft/mcore3/lib/bson/util/StringRangeSet.java
create mode 100644 src/com/massivecraft/mcore3/lib/bson/util/annotations/GuardedBy.java
create mode 100644 src/com/massivecraft/mcore3/lib/bson/util/annotations/Immutable.java
create mode 100644 src/com/massivecraft/mcore3/lib/bson/util/annotations/NotThreadSafe.java
create mode 100644 src/com/massivecraft/mcore3/lib/bson/util/annotations/ThreadSafe.java
create mode 100644 src/com/massivecraft/mcore3/lib/bson/util/package.html
create mode 100644 src/com/massivecraft/mcore3/lib/mongodb/BasicDBList.java
create mode 100644 src/com/massivecraft/mcore3/lib/mongodb/BasicDBObject.java
create mode 100644 src/com/massivecraft/mcore3/lib/mongodb/BasicDBObjectBuilder.java
create mode 100644 src/com/massivecraft/mcore3/lib/mongodb/Bytes.java
create mode 100644 src/com/massivecraft/mcore3/lib/mongodb/CommandResult.java
create mode 100644 src/com/massivecraft/mcore3/lib/mongodb/DB.java
create mode 100644 src/com/massivecraft/mcore3/lib/mongodb/DBAddress.java
create mode 100644 src/com/massivecraft/mcore3/lib/mongodb/DBApiLayer.java
create mode 100644 src/com/massivecraft/mcore3/lib/mongodb/DBCallback.java
create mode 100644 src/com/massivecraft/mcore3/lib/mongodb/DBCallbackFactory.java
create mode 100644 src/com/massivecraft/mcore3/lib/mongodb/DBCollection.java
create mode 100644 src/com/massivecraft/mcore3/lib/mongodb/DBConnector.java
create mode 100644 src/com/massivecraft/mcore3/lib/mongodb/DBCursor.java
create mode 100644 src/com/massivecraft/mcore3/lib/mongodb/DBDecoder.java
create mode 100644 src/com/massivecraft/mcore3/lib/mongodb/DBDecoderFactory.java
create mode 100644 src/com/massivecraft/mcore3/lib/mongodb/DBEncoder.java
create mode 100644 src/com/massivecraft/mcore3/lib/mongodb/DBEncoderFactory.java
create mode 100644 src/com/massivecraft/mcore3/lib/mongodb/DBObject.java
create mode 100644 src/com/massivecraft/mcore3/lib/mongodb/DBPointer.java
create mode 100644 src/com/massivecraft/mcore3/lib/mongodb/DBPort.java
create mode 100644 src/com/massivecraft/mcore3/lib/mongodb/DBPortPool.java
create mode 100644 src/com/massivecraft/mcore3/lib/mongodb/DBRef.java
create mode 100644 src/com/massivecraft/mcore3/lib/mongodb/DBRefBase.java
create mode 100644 src/com/massivecraft/mcore3/lib/mongodb/DBTCPConnector.java
create mode 100644 src/com/massivecraft/mcore3/lib/mongodb/DefaultDBCallback.java
create mode 100644 src/com/massivecraft/mcore3/lib/mongodb/DefaultDBDecoder.java
create mode 100644 src/com/massivecraft/mcore3/lib/mongodb/DefaultDBEncoder.java
create mode 100644 src/com/massivecraft/mcore3/lib/mongodb/GroupCommand.java
create mode 100644 src/com/massivecraft/mcore3/lib/mongodb/LazyDBCallback.java
create mode 100644 src/com/massivecraft/mcore3/lib/mongodb/LazyDBDecoder.java
create mode 100644 src/com/massivecraft/mcore3/lib/mongodb/LazyDBEncoder.java
create mode 100644 src/com/massivecraft/mcore3/lib/mongodb/LazyDBObject.java
create mode 100644 src/com/massivecraft/mcore3/lib/mongodb/LazyWriteableDBCallback.java
create mode 100644 src/com/massivecraft/mcore3/lib/mongodb/LazyWriteableDBDecoder.java
create mode 100644 src/com/massivecraft/mcore3/lib/mongodb/LazyWriteableDBObject.java
create mode 100644 src/com/massivecraft/mcore3/lib/mongodb/MapReduceCommand.java
create mode 100644 src/com/massivecraft/mcore3/lib/mongodb/MapReduceOutput.java
create mode 100644 src/com/massivecraft/mcore3/lib/mongodb/Mongo.java
create mode 100644 src/com/massivecraft/mcore3/lib/mongodb/MongoException.java
create mode 100644 src/com/massivecraft/mcore3/lib/mongodb/MongoInternalException.java
create mode 100644 src/com/massivecraft/mcore3/lib/mongodb/MongoOptions.java
create mode 100644 src/com/massivecraft/mcore3/lib/mongodb/MongoURI.java
create mode 100644 src/com/massivecraft/mcore3/lib/mongodb/OutMessage.java
create mode 100644 src/com/massivecraft/mcore3/lib/mongodb/QueryBuilder.java
create mode 100644 src/com/massivecraft/mcore3/lib/mongodb/QueryOperators.java
create mode 100644 src/com/massivecraft/mcore3/lib/mongodb/RawDBObject.java
create mode 100644 src/com/massivecraft/mcore3/lib/mongodb/ReadPreference.java
create mode 100644 src/com/massivecraft/mcore3/lib/mongodb/ReflectionDBObject.java
create mode 100644 src/com/massivecraft/mcore3/lib/mongodb/ReplicaSetStatus.java
create mode 100644 src/com/massivecraft/mcore3/lib/mongodb/Response.java
create mode 100644 src/com/massivecraft/mcore3/lib/mongodb/ServerAddress.java
create mode 100644 src/com/massivecraft/mcore3/lib/mongodb/ServerError.java
create mode 100644 src/com/massivecraft/mcore3/lib/mongodb/WriteConcern.java
create mode 100644 src/com/massivecraft/mcore3/lib/mongodb/WriteResult.java
create mode 100644 src/com/massivecraft/mcore3/lib/mongodb/gridfs/CLI.java
create mode 100644 src/com/massivecraft/mcore3/lib/mongodb/gridfs/GridFS.java
create mode 100644 src/com/massivecraft/mcore3/lib/mongodb/gridfs/GridFSDBFile.java
create mode 100644 src/com/massivecraft/mcore3/lib/mongodb/gridfs/GridFSFile.java
create mode 100644 src/com/massivecraft/mcore3/lib/mongodb/gridfs/GridFSInputFile.java
create mode 100644 src/com/massivecraft/mcore3/lib/mongodb/gridfs/package.html
create mode 100644 src/com/massivecraft/mcore3/lib/mongodb/io/ByteBufferFactory.java
create mode 100644 src/com/massivecraft/mcore3/lib/mongodb/io/ByteBufferHolder.java
create mode 100644 src/com/massivecraft/mcore3/lib/mongodb/io/ByteBufferInputStream.java
create mode 100644 src/com/massivecraft/mcore3/lib/mongodb/io/ByteBufferOutputStream.java
create mode 100644 src/com/massivecraft/mcore3/lib/mongodb/io/ByteStream.java
create mode 100644 src/com/massivecraft/mcore3/lib/mongodb/package.html
create mode 100644 src/com/massivecraft/mcore3/lib/mongodb/util/AbstractObjectSerializer.java
create mode 100644 src/com/massivecraft/mcore3/lib/mongodb/util/Args.java
create mode 100644 src/com/massivecraft/mcore3/lib/mongodb/util/Base64Codec.java
create mode 100644 src/com/massivecraft/mcore3/lib/mongodb/util/ClassMapBasedObjectSerializer.java
create mode 100644 src/com/massivecraft/mcore3/lib/mongodb/util/FastStack.java
create mode 100644 src/com/massivecraft/mcore3/lib/mongodb/util/Hash.java
create mode 100644 src/com/massivecraft/mcore3/lib/mongodb/util/IdentitySet.java
create mode 100644 src/com/massivecraft/mcore3/lib/mongodb/util/JSON.java
create mode 100644 src/com/massivecraft/mcore3/lib/mongodb/util/JSONCallback.java
create mode 100644 src/com/massivecraft/mcore3/lib/mongodb/util/JSONParseException.java
create mode 100644 src/com/massivecraft/mcore3/lib/mongodb/util/JSONSerializers.java
create mode 100644 src/com/massivecraft/mcore3/lib/mongodb/util/MyAsserts.java
create mode 100644 src/com/massivecraft/mcore3/lib/mongodb/util/ObjectSerializer.java
create mode 100644 src/com/massivecraft/mcore3/lib/mongodb/util/OptionMap.java
create mode 100644 src/com/massivecraft/mcore3/lib/mongodb/util/SimplePool.java
create mode 100644 src/com/massivecraft/mcore3/lib/mongodb/util/StringBuilderPool.java
create mode 100644 src/com/massivecraft/mcore3/lib/mongodb/util/StringParseUtil.java
create mode 100644 src/com/massivecraft/mcore3/lib/mongodb/util/ThreadPool.java
create mode 100644 src/com/massivecraft/mcore3/lib/mongodb/util/ThreadUtil.java
create mode 100644 src/com/massivecraft/mcore3/lib/mongodb/util/TimeConstants.java
create mode 100644 src/com/massivecraft/mcore3/lib/mongodb/util/UniqueList.java
create mode 100644 src/com/massivecraft/mcore3/lib/mongodb/util/Util.java
create mode 100644 src/com/massivecraft/mcore3/lib/mongodb/util/WeakBag.java
create mode 100644 src/com/massivecraft/mcore3/lib/mongodb/util/package.html
create mode 100644 src/com/massivecraft/mcore3/mongodb/InventoryTypeAdapter.java
create mode 100644 src/com/massivecraft/mcore3/mongodb/ItemStackAdapter.java
diff --git a/src/com/massivecraft/mcore3/MCore.java b/src/com/massivecraft/mcore3/MCore.java
index c2dc2e7d..52745835 100644
--- a/src/com/massivecraft/mcore3/MCore.java
+++ b/src/com/massivecraft/mcore3/MCore.java
@@ -14,7 +14,9 @@ import org.bukkit.plugin.java.JavaPlugin;
import com.massivecraft.mcore3.cmd.Cmd;
import com.massivecraft.mcore3.gson.InventoryTypeAdapter;
import com.massivecraft.mcore3.gson.ItemStackAdapter;
+import com.massivecraft.mcore3.gson.MongoURIAdapter;
import com.massivecraft.mcore3.lib.gson.GsonBuilder;
+import com.massivecraft.mcore3.lib.mongodb.MongoURI;
import com.massivecraft.mcore3.persist.One;
import com.massivecraft.mcore3.persist.Persist;
import com.massivecraft.mcore3.util.LibLoader;
@@ -113,6 +115,7 @@ public class MCore extends JavaPlugin
.setPrettyPrinting()
.disableHtmlEscaping()
.excludeFieldsWithModifiers(Modifier.TRANSIENT)
+ .registerTypeAdapter(MongoURI.class, MongoURIAdapter.get())
.registerTypeAdapter(ItemStack.class, new ItemStackAdapter())
.registerTypeAdapter(Inventory.class, new InventoryTypeAdapter());
}
diff --git a/src/com/massivecraft/mcore3/gson/MongoURIAdapter.java b/src/com/massivecraft/mcore3/gson/MongoURIAdapter.java
new file mode 100644
index 00000000..c5aa1213
--- /dev/null
+++ b/src/com/massivecraft/mcore3/gson/MongoURIAdapter.java
@@ -0,0 +1,55 @@
+package com.massivecraft.mcore3.gson;
+
+import java.lang.reflect.Type;
+
+import com.massivecraft.mcore3.lib.gson.JsonDeserializationContext;
+import com.massivecraft.mcore3.lib.gson.JsonDeserializer;
+import com.massivecraft.mcore3.lib.gson.JsonElement;
+import com.massivecraft.mcore3.lib.gson.JsonParseException;
+import com.massivecraft.mcore3.lib.gson.JsonPrimitive;
+import com.massivecraft.mcore3.lib.gson.JsonSerializationContext;
+import com.massivecraft.mcore3.lib.gson.JsonSerializer;
+import com.massivecraft.mcore3.lib.mongodb.MongoURI;
+
+public class MongoURIAdapter implements JsonDeserializer, JsonSerializer
+{
+ // -------------------------------------------- //
+ // IMPLEMENTATION
+ // -------------------------------------------- //
+
+ @Override
+ public JsonElement serialize(MongoURI mongoURI, Type typeOfSrc, JsonSerializationContext context)
+ {
+ return serialize(mongoURI);
+ }
+
+ @Override
+ public MongoURI deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException
+ {
+ return deserialize(json);
+ }
+
+ // -------------------------------------------- //
+ // STATIC LOGIC
+ // -------------------------------------------- //
+
+ public static JsonElement serialize(MongoURI mongoURI)
+ {
+ return new JsonPrimitive(mongoURI.toString());
+ }
+
+ public static MongoURI deserialize(JsonElement json)
+ {
+ return new MongoURI(json.getAsString());
+ }
+
+ // -------------------------------------------- //
+ // INSTANCE
+ // -------------------------------------------- //
+
+ protected static MongoURIAdapter instance = new MongoURIAdapter();
+ public static MongoURIAdapter get()
+ {
+ return instance;
+ }
+}
diff --git a/src/com/massivecraft/mcore3/lib/bson/BSON.java b/src/com/massivecraft/mcore3/lib/bson/BSON.java
new file mode 100644
index 00000000..4029ef3e
--- /dev/null
+++ b/src/com/massivecraft/mcore3/lib/bson/BSON.java
@@ -0,0 +1,353 @@
+// BSON.java
+
+/**
+ * Copyright (C) 2008 10gen 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 com.massivecraft.mcore3.lib.bson;
+
+import java.nio.charset.Charset;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.logging.Logger;
+import java.util.regex.Pattern;
+
+import com.massivecraft.mcore3.lib.bson.util.ClassMap;
+
+public class BSON {
+
+ static final Logger LOGGER = Logger.getLogger( "org.bson.BSON" );
+
+ // ---- basics ----
+
+ public static final byte EOO = 0;
+ public static final byte NUMBER = 1;
+ public static final byte STRING = 2;
+ public static final byte OBJECT = 3;
+ public static final byte ARRAY = 4;
+ public static final byte BINARY = 5;
+ public static final byte UNDEFINED = 6;
+ public static final byte OID = 7;
+ public static final byte BOOLEAN = 8;
+ public static final byte DATE = 9;
+ public static final byte NULL = 10;
+ public static final byte REGEX = 11;
+ public static final byte REF = 12;
+ public static final byte CODE = 13;
+ public static final byte SYMBOL = 14;
+ public static final byte CODE_W_SCOPE = 15;
+ public static final byte NUMBER_INT = 16;
+ public static final byte TIMESTAMP = 17;
+ public static final byte NUMBER_LONG = 18;
+
+ public static final byte MINKEY = -1;
+ public static final byte MAXKEY = 127;
+
+ // --- binary types
+ /*
+ these are binary types
+ so the format would look like
+ <...>
+ */
+
+ public static final byte B_GENERAL = 0;
+ public static final byte B_FUNC = 1;
+ public static final byte B_BINARY = 2;
+ public static final byte B_UUID = 3;
+
+ // ---- regular expression handling ----
+
+ /** Converts a string of regular expression flags from the database in Java regular
+ * expression flags.
+ * @param flags flags from database
+ * @return the Java flags
+ */
+ public static int regexFlags( String flags ){
+ int fint = 0;
+ if ( flags == null || flags.length() == 0 )
+ return fint;
+
+ flags = flags.toLowerCase();
+
+ for( int i=0; i 0 ) {
+ buf.append( flag.flagChar );
+ flags -= flag.javaFlag;
+ }
+ }
+
+ if( flags > 0 )
+ throw new IllegalArgumentException( "some flags could not be recognized." );
+
+ return buf.toString();
+ }
+
+ private static enum RegexFlag {
+ CANON_EQ( Pattern.CANON_EQ, 'c', "Pattern.CANON_EQ" ),
+ UNIX_LINES(Pattern.UNIX_LINES, 'd', "Pattern.UNIX_LINES" ),
+ GLOBAL( GLOBAL_FLAG, 'g', null ),
+ CASE_INSENSITIVE( Pattern.CASE_INSENSITIVE, 'i', null ),
+ MULTILINE(Pattern.MULTILINE, 'm', null ),
+ DOTALL( Pattern.DOTALL, 's', "Pattern.DOTALL" ),
+ LITERAL( Pattern.LITERAL, 't', "Pattern.LITERAL" ),
+ UNICODE_CASE( Pattern.UNICODE_CASE, 'u', "Pattern.UNICODE_CASE" ),
+ COMMENTS( Pattern.COMMENTS, 'x', null );
+
+ private static final Map byCharacter = new HashMap();
+
+ static {
+ for (RegexFlag flag : values()) {
+ byCharacter.put(flag.flagChar, flag);
+ }
+ }
+
+ public static RegexFlag getByCharacter(char ch) {
+ return byCharacter.get(ch);
+ }
+ public final int javaFlag;
+ public final char flagChar;
+ public final String unsupported;
+
+ RegexFlag( int f, char ch, String u ) {
+ javaFlag = f;
+ flagChar = ch;
+ unsupported = u;
+ }
+ }
+
+ private static void _warnUnsupportedRegex( String flag ) {
+ LOGGER.info( "flag " + flag + " not supported by db." );
+ }
+
+ private static final int GLOBAL_FLAG = 256;
+
+ // --- (en|de)coding hooks -----
+
+ public static boolean hasDecodeHooks() { return _decodeHooks; }
+
+ @SuppressWarnings("rawtypes")
+ public static void addEncodingHook( Class c , Transformer t ){
+ _encodeHooks = true;
+ List l = _encodingHooks.get( c );
+ if ( l == null ){
+ l = new CopyOnWriteArrayList();
+ _encodingHooks.put( c , l );
+ }
+ l.add( t );
+ }
+
+ @SuppressWarnings("rawtypes")
+ public static void addDecodingHook( Class c , Transformer t ){
+ _decodeHooks = true;
+ List l = _decodingHooks.get( c );
+ if ( l == null ){
+ l = new CopyOnWriteArrayList();
+ _decodingHooks.put( c , l );
+ }
+ l.add( t );
+ }
+
+ public static Object applyEncodingHooks( Object o ){
+ if ( ! _anyHooks() )
+ return o;
+
+ if ( _encodingHooks.size() == 0 || o == null )
+ return o;
+ List l = _encodingHooks.get( o.getClass() );
+ if ( l != null )
+ for ( Transformer t : l )
+ o = t.transform( o );
+ return o;
+ }
+
+ public static Object applyDecodingHooks( Object o ){
+ if ( ! _anyHooks() || o == null )
+ return o;
+
+ List l = _decodingHooks.get( o.getClass() );
+ if ( l != null )
+ for ( Transformer t : l )
+ o = t.transform( o );
+ return o;
+ }
+
+ /**
+ * Returns the encoding hook(s) associated with the specified class
+ *
+ */
+ @SuppressWarnings("rawtypes")
+ public static List getEncodingHooks( Class c ){
+ return _encodingHooks.get( c );
+ }
+
+ /**
+ * Clears *all* encoding hooks.
+ */
+ public static void clearEncodingHooks(){
+ _encodeHooks = false;
+ _encodingHooks.clear();
+ }
+
+ /**
+ * Remove all encoding hooks for a specific class.
+ */
+ @SuppressWarnings("rawtypes")
+ public static void removeEncodingHooks( Class c ){
+ _encodingHooks.remove( c );
+ }
+
+ /**
+ * Remove a specific encoding hook for a specific class.
+ */
+ @SuppressWarnings("rawtypes")
+ public static void removeEncodingHook( Class c , Transformer t ){
+ getEncodingHooks( c ).remove( t );
+ }
+
+ /**
+ * Returns the decoding hook(s) associated with the specific class
+ */
+ @SuppressWarnings("rawtypes")
+ public static List getDecodingHooks( Class c ){
+ return _decodingHooks.get( c );
+ }
+
+ /**
+ * Clears *all* decoding hooks.
+ */
+ public static void clearDecodingHooks(){
+ _decodeHooks = false;
+ _decodingHooks.clear();
+ }
+
+ /**
+ * Remove all decoding hooks for a specific class.
+ */
+ @SuppressWarnings("rawtypes")
+ public static void removeDecodingHooks( Class c ){
+ _decodingHooks.remove( c );
+ }
+
+ /**
+ * Remove a specific encoding hook for a specific class.
+ */
+ @SuppressWarnings("rawtypes")
+ public static void removeDecodingHook( Class c , Transformer t ){
+ getDecodingHooks( c ).remove( t );
+ }
+
+
+ public static void clearAllHooks(){
+ clearEncodingHooks();
+ clearDecodingHooks();
+ }
+
+ /**
+ * Returns true if any encoding or decoding hooks are loaded.
+ */
+ private static boolean _anyHooks(){
+ return _encodeHooks || _decodeHooks;
+ }
+
+ private static boolean _encodeHooks = false;
+ private static boolean _decodeHooks = false;
+ static ClassMap> _encodingHooks =
+ new ClassMap>();
+
+ static ClassMap> _decodingHooks =
+ new ClassMap>();
+
+ static protected Charset _utf8 = Charset.forName( "UTF-8" );
+
+ // ----- static encode/decode -----
+
+ public static byte[] encode( BSONObject o ){
+ BSONEncoder e = _staticEncoder.get();
+ try {
+ return e.encode( o );
+ }
+ finally {
+ e.done();
+ }
+ }
+
+ public static BSONObject decode( byte[] b ){
+ BSONDecoder d = _staticDecoder.get();
+ return d.readObject( b );
+ }
+
+ static ThreadLocal _staticEncoder = new ThreadLocal(){
+ protected BSONEncoder initialValue(){
+ return new BasicBSONEncoder();
+ }
+ };
+
+ static ThreadLocal _staticDecoder = new ThreadLocal(){
+ protected BSONDecoder initialValue(){
+ return new BasicBSONDecoder();
+ }
+ };
+
+ // --- coercing ---
+
+ public static int toInt( Object o ){
+ if ( o == null )
+ throw new NullPointerException( "can't be null" );
+
+ if ( o instanceof Number )
+ return ((Number)o).intValue();
+
+ if ( o instanceof Boolean )
+ return ((Boolean)o) ? 1 : 0;
+
+ throw new IllegalArgumentException( "can't convert: " + o.getClass().getName() + " to int" );
+ }
+}
diff --git a/src/com/massivecraft/mcore3/lib/bson/BSONCallback.java b/src/com/massivecraft/mcore3/lib/bson/BSONCallback.java
new file mode 100644
index 00000000..b4e9ef48
--- /dev/null
+++ b/src/com/massivecraft/mcore3/lib/bson/BSONCallback.java
@@ -0,0 +1,70 @@
+// BSONCallback.java
+
+/**
+ * Copyright (C) 2008 10gen 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 com.massivecraft.mcore3.lib.bson;
+
+import com.massivecraft.mcore3.lib.bson.types.ObjectId;
+
+public interface BSONCallback {
+
+ void objectStart();
+ void objectStart(String name);
+ void objectStart(boolean array);
+ Object objectDone();
+
+ void reset();
+ Object get();
+ BSONCallback createBSONCallback();
+
+ void arrayStart();
+ void arrayStart(String name);
+ Object arrayDone();
+
+ void gotNull( String name );
+ void gotUndefined( String name );
+ void gotMinKey( String name );
+ void gotMaxKey( String name );
+
+ void gotBoolean( String name , boolean v );
+ void gotDouble( String name , double v );
+ void gotInt( String name , int v );
+ void gotLong( String name , long v );
+
+ void gotDate( String name , long millis );
+ void gotString( String name , String v );
+ void gotSymbol( String name , String v );
+ void gotRegex( String name , String pattern , String flags );
+
+ void gotTimestamp( String name , int time , int inc );
+ void gotObjectId( String name , ObjectId id );
+ void gotDBRef( String name , String ns , ObjectId id );
+
+ /**
+ *
+ */
+ @Deprecated
+ void gotBinaryArray( String name , byte[] data );
+ void gotBinary( String name , byte type , byte[] data );
+ /**
+ * subtype 3
+ */
+ void gotUUID( String name , long part1, long part2);
+
+ void gotCode( String name , String code );
+ void gotCodeWScope( String name , String code , Object scope );
+}
diff --git a/src/com/massivecraft/mcore3/lib/bson/BSONDecoder.java b/src/com/massivecraft/mcore3/lib/bson/BSONDecoder.java
new file mode 100644
index 00000000..795ab63b
--- /dev/null
+++ b/src/com/massivecraft/mcore3/lib/bson/BSONDecoder.java
@@ -0,0 +1,34 @@
+// BSONDecoder.java
+
+/**
+ * Copyright (C) 2008 10gen 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 com.massivecraft.mcore3.lib.bson;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+public interface BSONDecoder {
+
+ public BSONObject readObject( byte[] b );
+
+ public BSONObject readObject( InputStream in ) throws IOException;
+
+ public int decode( byte[] b , BSONCallback callback );
+
+ public int decode( InputStream in , BSONCallback callback ) throws IOException;
+
+}
diff --git a/src/com/massivecraft/mcore3/lib/bson/BSONEncoder.java b/src/com/massivecraft/mcore3/lib/bson/BSONEncoder.java
new file mode 100644
index 00000000..d6cd1228
--- /dev/null
+++ b/src/com/massivecraft/mcore3/lib/bson/BSONEncoder.java
@@ -0,0 +1,27 @@
+/**
+ * Copyright (c) 2008 - 2011 10gen, 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 com.massivecraft.mcore3.lib.bson;
+
+import com.massivecraft.mcore3.lib.bson.io.*;
+
+
+public interface BSONEncoder {
+ public byte[] encode( BSONObject o );
+
+ public int putObject( BSONObject o );
+
+ public void done();
+
+ void set( OutputBuffer out );
+}
diff --git a/src/com/massivecraft/mcore3/lib/bson/BSONException.java b/src/com/massivecraft/mcore3/lib/bson/BSONException.java
new file mode 100644
index 00000000..a9517333
--- /dev/null
+++ b/src/com/massivecraft/mcore3/lib/bson/BSONException.java
@@ -0,0 +1,73 @@
+/**
+ * Copyright (C) 2011, 10gen 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 com.massivecraft.mcore3.lib.bson;
+
+/**
+ * A general runtime exception raised in BSON processing.
+ */
+public class BSONException extends RuntimeException {
+
+ private static final long serialVersionUID = -4415279469780082174L;
+
+ /**
+ * @param msg The error message.
+ */
+ public BSONException( final String msg ) {
+ super( msg );
+ }
+
+ /**
+ * @param errorCode The error code.
+ * @param msg The error message.
+ */
+ public BSONException( final int errorCode, final String msg ) {
+ super( msg );
+ _errorCode = errorCode;
+ }
+
+ /**
+ * @param msg The error message.
+ * @param t The throwable cause.
+ */
+ public BSONException( final String msg , final Throwable t ) {
+ super( msg, t );
+ }
+
+ /**
+ * @param errorCode The error code.
+ * @param msg The error message.
+ * @param t The throwable cause.
+ */
+ public BSONException( final int errorCode, final String msg, final Throwable t ) {
+ super( msg, t );
+ _errorCode = errorCode;
+ }
+
+ /**
+ * Returns the error code.
+ * @return The error code.
+ */
+ public Integer getErrorCode() { return _errorCode; }
+
+ /**
+ * Returns true if the error code is set (i.e., not null).
+ */
+ public boolean hasErrorCode() { return (_errorCode != null); }
+
+ private Integer _errorCode = null;
+}
+
diff --git a/src/com/massivecraft/mcore3/lib/bson/BSONLazyDecoder.java b/src/com/massivecraft/mcore3/lib/bson/BSONLazyDecoder.java
new file mode 100644
index 00000000..09882463
--- /dev/null
+++ b/src/com/massivecraft/mcore3/lib/bson/BSONLazyDecoder.java
@@ -0,0 +1,24 @@
+/**
+ * Copyright (C) 2008 10gen 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 com.massivecraft.mcore3.lib.bson;
+
+/**
+ *
+ * @author antoine
+ */
+public class BSONLazyDecoder {
+
+}
diff --git a/src/com/massivecraft/mcore3/lib/bson/BSONObject.java b/src/com/massivecraft/mcore3/lib/bson/BSONObject.java
new file mode 100644
index 00000000..95b376f1
--- /dev/null
+++ b/src/com/massivecraft/mcore3/lib/bson/BSONObject.java
@@ -0,0 +1,93 @@
+// BSONObject.java
+
+/**
+ * Copyright (C) 2008 10gen 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 com.massivecraft.mcore3.lib.bson;
+
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * A key-value map that can be saved to the database.
+ */
+public interface BSONObject {
+
+ /**
+ * Sets a name/value pair in this object.
+ * @param key Name to set
+ * @param v Corresponding value
+ * @return v
+ */
+ public Object put( String key , Object v );
+
+ /**
+ * Sets all key/value pairs from an object into this object
+ * @param o the object
+ */
+ public void putAll( BSONObject o );
+
+ /**
+ * Sets all key/value pairs from a map into this object
+ * @param m the map
+ */
+ @SuppressWarnings("rawtypes")
+ public void putAll( Map m );
+
+ /**
+ * Gets a field from this object by a given name.
+ * @param key The name of the field fetch
+ * @return The field, if found
+ */
+ public Object get( String key );
+
+ /**
+ * Returns a map representing this BSONObject.
+ * @return the map
+ */
+ @SuppressWarnings("rawtypes")
+ public Map toMap();
+
+ /**
+ * Removes a field with a given name from this object.
+ * @param key The name of the field to remove
+ * @return The value removed from this object
+ */
+ public Object removeField( String key );
+
+ /**
+ * Deprecated
+ * @param s
+ * @return True if the key is present
+ * @deprecated
+ */
+ @Deprecated
+ public boolean containsKey( String s );
+
+ /**
+ * Checks if this object contains a field with the given name.
+ * @param s Field name for which to check
+ * @return True if the field is present
+ */
+ public boolean containsField(String s);
+
+ /**
+ * Returns this object's fields' names
+ * @return The names of the fields in this object
+ */
+ public Set keySet();
+}
+
diff --git a/src/com/massivecraft/mcore3/lib/bson/BasicBSONCallback.java b/src/com/massivecraft/mcore3/lib/bson/BasicBSONCallback.java
new file mode 100644
index 00000000..3419f79a
--- /dev/null
+++ b/src/com/massivecraft/mcore3/lib/bson/BasicBSONCallback.java
@@ -0,0 +1,206 @@
+// BasicBSONCallback.java
+
+/**
+ * Copyright (C) 2008 10gen 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 com.massivecraft.mcore3.lib.bson;
+
+import java.util.*;
+import java.util.regex.Pattern;
+
+import com.massivecraft.mcore3.lib.bson.types.*;
+
+public class BasicBSONCallback implements BSONCallback {
+
+ public BasicBSONCallback(){
+ reset();
+ }
+
+ public BSONObject create(){
+ return new BasicBSONObject();
+ }
+
+ protected BSONObject createList() {
+ return new BasicBSONList();
+ }
+
+ public BSONCallback createBSONCallback(){
+ return new BasicBSONCallback();
+ }
+
+ public BSONObject create( boolean array , List path ){
+ if ( array )
+ return createList();
+ return create();
+ }
+
+ public void objectStart(){
+ if ( _stack.size() > 0 )
+ throw new IllegalStateException( "something is wrong" );
+
+ objectStart(false);
+ }
+
+ public void objectStart(boolean array){
+ _root = create(array, null);
+ _stack.add( (BSONObject)_root );
+ }
+
+ public void objectStart(String name){
+ objectStart( false , name );
+ }
+
+ public void objectStart(boolean array, String name){
+ _nameStack.addLast( name );
+ final BSONObject o = create( array , _nameStack );
+ _stack.getLast().put( name , o);
+ _stack.addLast( o );
+ }
+
+ public Object objectDone(){
+ final BSONObject o =_stack.removeLast();
+ if ( _nameStack.size() > 0 )
+ _nameStack.removeLast();
+ else if ( _stack.size() > 0 )
+ throw new IllegalStateException( "something is wrong" );
+
+ return !BSON.hasDecodeHooks() ? o : (BSONObject)BSON.applyDecodingHooks(o);
+ }
+
+ public void arrayStart(){
+ objectStart( true );
+ }
+
+ public void arrayStart(String name){
+ objectStart( true , name );
+ }
+
+ public Object arrayDone(){
+ return objectDone();
+ }
+
+ public void gotNull( String name ){
+ cur().put( name , null );
+ }
+
+ public void gotUndefined( String name ) { }
+
+ public void gotMinKey( String name ){
+ cur().put( name , new MinKey() );
+ }
+
+ public void gotMaxKey( String name ){
+ cur().put( name , new MaxKey() );
+ }
+
+ public void gotBoolean( String name , boolean v ){
+ _put( name , v );
+ }
+
+ public void gotDouble( final String name , final double v ){
+ _put( name , v );
+ }
+
+ public void gotInt( final String name , final int v ){
+ _put( name , v );
+ }
+
+ public void gotLong( final String name , final long v ){
+ _put( name , v );
+ }
+
+ public void gotDate( String name , long millis ){
+ _put( name , new Date( millis ) );
+ }
+ public void gotRegex( String name , String pattern , String flags ){
+ _put( name , Pattern.compile( pattern , BSON.regexFlags( flags ) ) );
+ }
+
+ public void gotString( final String name , final String v ){
+ _put( name , v );
+ }
+ public void gotSymbol( String name , String v ){
+ _put( name , v );
+ }
+
+ public void gotTimestamp( String name , int time , int inc ){
+ _put( name , new BSONTimestamp( time , inc ) );
+ }
+ public void gotObjectId( String name , ObjectId id ){
+ _put( name , id );
+ }
+ public void gotDBRef( String name , String ns , ObjectId id ){
+ _put( name , new BasicBSONObject( "$ns" , ns ).append( "$id" , id ) );
+ }
+
+ @Deprecated
+ public void gotBinaryArray( String name , byte[] data ){
+ gotBinary( name, BSON.B_GENERAL, data );
+ }
+
+ public void gotBinary( String name , byte type , byte[] data ){
+ if( type == BSON.B_GENERAL || type == BSON.B_BINARY )
+ _put( name , data );
+ else
+ _put( name , new Binary( type , data ) );
+ }
+
+ public void gotUUID( String name , long part1, long part2){
+ _put( name , new UUID(part1, part2) );
+ }
+
+ public void gotCode( String name , String code ){
+ _put( name , new Code( code ) );
+ }
+
+ public void gotCodeWScope( String name , String code , Object scope ){
+ _put( name , new CodeWScope( code, (BSONObject)scope ) );
+ }
+
+ protected void _put( final String name , final Object o ){
+ cur().put( name , !BSON.hasDecodeHooks() ? o : BSON.applyDecodingHooks( o ) );
+ }
+
+ protected BSONObject cur(){
+ return _stack.getLast();
+ }
+
+ protected String curName(){
+ return (!_nameStack.isEmpty()) ? _nameStack.getLast() : null;
+ }
+
+ public Object get(){
+ return _root;
+ }
+
+ protected void setRoot(Object o) {
+ _root = o;
+ }
+
+ protected boolean isStackEmpty() {
+ return _stack.size() < 1;
+ }
+
+ public void reset(){
+ _root = null;
+ _stack.clear();
+ _nameStack.clear();
+ }
+
+ private Object _root;
+ private final LinkedList _stack = new LinkedList();
+ private final LinkedList _nameStack = new LinkedList();
+}
diff --git a/src/com/massivecraft/mcore3/lib/bson/BasicBSONDecoder.java b/src/com/massivecraft/mcore3/lib/bson/BasicBSONDecoder.java
new file mode 100644
index 00000000..7a5429a0
--- /dev/null
+++ b/src/com/massivecraft/mcore3/lib/bson/BasicBSONDecoder.java
@@ -0,0 +1,527 @@
+/**
+ * Copyright (C) 2008 10gen 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 com.massivecraft.mcore3.lib.bson;
+
+import static com.massivecraft.mcore3.lib.bson.BSON.*;
+
+import java.io.*;
+
+import com.massivecraft.mcore3.lib.bson.io.PoolOutputBuffer;
+import com.massivecraft.mcore3.lib.bson.types.ObjectId;
+
+
+/**
+ * Basic implementation of BSONDecoder interface that creates BasicBSONObject instances
+ */
+public class BasicBSONDecoder implements BSONDecoder {
+ public BSONObject readObject( byte[] b ){
+ try {
+ return readObject( new ByteArrayInputStream( b ) );
+ }
+ catch ( IOException ioe ){
+ throw new BSONException( "should be impossible" , ioe );
+ }
+ }
+
+ public BSONObject readObject( InputStream in )
+ throws IOException {
+ BasicBSONCallback c = new BasicBSONCallback();
+ decode( in , c );
+ return (BSONObject)c.get();
+ }
+
+ public int decode( byte[] b , BSONCallback callback ){
+ try {
+ return _decode( new BSONInput( new ByteArrayInputStream(b) ) , callback );
+ }
+ catch ( IOException ioe ){
+ throw new BSONException( "should be impossible" , ioe );
+ }
+ }
+
+ public int decode( InputStream in , BSONCallback callback )
+ throws IOException {
+ return _decode( new BSONInput( in ) , callback );
+ }
+
+ private int _decode( BSONInput in , BSONCallback callback )
+ throws IOException {
+
+ if ( _in != null || _callback != null )
+ throw new IllegalStateException( "not ready" );
+
+ _in = in;
+ _callback = callback;
+
+ if ( in.numRead() != 0 )
+ throw new IllegalArgumentException( "i'm confused" );
+
+ try {
+
+ final int len = _in.readInt();
+
+ _in.setMax(len);
+
+ _callback.objectStart();
+ while ( decodeElement() );
+ _callback.objectDone();
+
+ if ( _in.numRead() != len )
+ throw new IllegalArgumentException( "bad data. lengths don't match read:" + _in.numRead() + " != len:" + len );
+
+ return len;
+ }
+ finally {
+ _in = null;
+ _callback = null;
+ }
+ }
+
+ int decode( boolean first )
+ throws IOException {
+
+ final int start = _in.numRead();
+
+ final int len = _in.readInt();
+ if ( first )
+ _in.setMax(len);
+
+ _callback.objectStart();
+ while ( decodeElement() );
+ _callback.objectDone();
+
+ final int read = _in.numRead() - start;
+
+ if ( read != len ){
+ //throw new IllegalArgumentException( "bad data. lengths don't match " + read + " != " + len );
+ }
+
+ return len;
+ }
+
+ boolean decodeElement()
+ throws IOException {
+
+ final byte type = _in.read();
+
+ if ( type == EOO )
+ return false;
+
+ String name = _in.readCStr();
+
+ switch ( type ){
+ case NULL:
+ _callback.gotNull( name );
+ break;
+
+ case UNDEFINED:
+ _callback.gotUndefined( name );
+ break;
+
+ case BOOLEAN:
+ _callback.gotBoolean( name , _in.read() > 0 );
+ break;
+
+ case NUMBER:
+ _callback.gotDouble( name , _in.readDouble() );
+ break;
+
+ case NUMBER_INT:
+ _callback.gotInt( name , _in.readInt() );
+ break;
+
+ case NUMBER_LONG:
+ _callback.gotLong( name , _in.readLong() );
+ break;
+
+ case SYMBOL:
+ _callback.gotSymbol( name , _in.readUTF8String() );
+ break;
+
+ case STRING:
+ _callback.gotString(name, _in.readUTF8String() );
+ break;
+
+ case OID:
+ // OID is stored as big endian
+ _callback.gotObjectId( name , new ObjectId( _in.readIntBE() , _in.readIntBE() , _in.readIntBE() ) );
+ break;
+
+ case REF:
+ _in.readInt(); // length of ctring that follows
+ String ns = _in.readCStr();
+ ObjectId theOID = new ObjectId( _in.readInt() , _in.readInt() , _in.readInt() );
+ _callback.gotDBRef( name , ns , theOID );
+ break;
+
+ case DATE:
+ _callback.gotDate( name , _in.readLong() );
+ break;
+
+ case REGEX:
+ _callback.gotRegex( name , _in.readCStr() , _in.readCStr() );
+ break;
+
+ case BINARY:
+ _binary( name );
+ break;
+
+ case CODE:
+ _callback.gotCode( name , _in.readUTF8String() );
+ break;
+
+ case CODE_W_SCOPE:
+ _in.readInt();
+ _callback.gotCodeWScope( name , _in.readUTF8String() , _readBasicObject() );
+
+ break;
+
+ case ARRAY:
+ _in.readInt(); // total size - we don't care....
+
+ _callback.arrayStart( name );
+ while ( decodeElement() );
+ _callback.arrayDone();
+
+ break;
+
+
+ case OBJECT:
+ _in.readInt(); // total size - we don't care....
+
+ _callback.objectStart( name );
+ while ( decodeElement() );
+ _callback.objectDone();
+
+ break;
+
+ case TIMESTAMP:
+ int i = _in.readInt();
+ int time = _in.readInt();
+ _callback.gotTimestamp( name , time , i );
+ break;
+
+ case MINKEY:
+ _callback.gotMinKey( name );
+ break;
+
+ case MAXKEY:
+ _callback.gotMaxKey( name );
+ break;
+
+ default:
+ throw new UnsupportedOperationException( "BSONDecoder doesn't understand type : " + type + " name: " + name );
+ }
+
+ return true;
+ }
+
+ protected void _binary( final String name )
+ throws IOException {
+ final int totalLen = _in.readInt();
+ final byte bType = _in.read();
+
+ switch ( bType ){
+ case B_GENERAL: {
+ final byte[] data = new byte[totalLen];
+ _in.fill( data );
+ _callback.gotBinary( name, bType, data );
+ return;
+ }
+ case B_BINARY:
+ final int len = _in.readInt();
+ if ( len + 4 != totalLen )
+ throw new IllegalArgumentException( "bad data size subtype 2 len: " + len + " totalLen: " + totalLen );
+
+ final byte [] data = new byte[len];
+ _in.fill( data );
+ _callback.gotBinary( name , bType , data );
+ return;
+ case B_UUID:
+ if ( totalLen != 16 )
+ throw new IllegalArgumentException( "bad data size subtype 3 len: " + totalLen + " != 16");
+
+ final long part1 = _in.readLong();
+ final long part2 = _in.readLong();
+ _callback.gotUUID(name, part1, part2);
+ return;
+ }
+
+ final byte [] data = new byte[totalLen];
+ _in.fill( data );
+
+ _callback.gotBinary( name , bType , data );
+ }
+
+ Object _readBasicObject()
+ throws IOException {
+ _in.readInt();
+
+ final BSONCallback save = _callback;
+ final BSONCallback _basic = _callback.createBSONCallback();
+ _callback = _basic;
+ _basic.reset();
+ _basic.objectStart(false);
+
+ while( decodeElement() );
+ _callback = save;
+ return _basic.get();
+ }
+
+ protected class BSONInput {
+
+ public BSONInput(final InputStream in){
+ _raw = in;
+ _read = 0;
+
+ _pos = 0;
+ _len = 0;
+ }
+
+ /**
+ * ensure that there are num bytes to read
+ * _pos is where to start reading from
+ * @return where to start reading from
+ */
+ protected int _need( final int num )
+ throws IOException {
+
+ //System.out.println( "p: " + _pos + " l: " + _len + " want: " + num );
+
+ if ( _len - _pos >= num ){
+ final int ret = _pos;
+ _pos += num;
+ _read += num;
+ return ret;
+ }
+
+ if ( num >= _inputBuffer.length )
+ throw new IllegalArgumentException( "you can't need that much" );
+
+ final int remaining = _len - _pos;
+ if ( _pos > 0 ){
+ System.arraycopy( _inputBuffer , _pos , _inputBuffer , 0 , remaining );
+
+ _pos = 0;
+ _len = remaining;
+ }
+
+ // read as much as possible into buffer
+ int maxToRead = Math.min( _max - _read - remaining , _inputBuffer.length - _len );
+ while ( maxToRead > 0 ){
+ int x = _raw.read( _inputBuffer , _len , maxToRead);
+ if ( x <= 0 )
+ throw new IOException( "unexpected EOF" );
+ maxToRead -= x;
+ _len += x;
+ }
+
+ int ret = _pos;
+ _pos += num;
+ _read += num;
+ return ret;
+ }
+
+ public int readInt()
+ throws IOException {
+ return com.massivecraft.mcore3.lib.bson.io.Bits.readInt( _inputBuffer , _need(4) );
+ }
+
+ public int readIntBE()
+ throws IOException {
+ return com.massivecraft.mcore3.lib.bson.io.Bits.readIntBE( _inputBuffer , _need(4) );
+ }
+
+ public long readLong()
+ throws IOException {
+ return com.massivecraft.mcore3.lib.bson.io.Bits.readLong( _inputBuffer , _need(8) );
+ }
+
+ public double readDouble()
+ throws IOException {
+ return Double.longBitsToDouble( readLong() );
+ }
+
+ public byte read()
+ throws IOException {
+ if ( _pos < _len ){
+ ++_read;
+ return _inputBuffer[_pos++];
+ }
+ return _inputBuffer[_need(1)];
+ }
+
+ public void fill( byte b[] )
+ throws IOException {
+ fill( b , b.length );
+ }
+
+ public void fill( byte b[] , int len )
+ throws IOException {
+ // first use what we have
+ final int have = _len - _pos;
+ final int tocopy = Math.min( len , have );
+ System.arraycopy( _inputBuffer , _pos , b , 0 , tocopy );
+
+ _pos += tocopy;
+ _read += tocopy;
+
+ len -= tocopy;
+
+ int off = tocopy;
+ while ( len > 0 ){
+ final int x = _raw.read( b , off , len );
+ if (x <= 0)
+ throw new IOException( "unexpected EOF" );
+ _read += x;
+ off += x;
+ len -= x;
+ }
+ }
+
+ protected boolean _isAscii( byte b ){
+ return b >=0 && b <= 127;
+ }
+
+ public String readCStr() throws IOException {
+
+ boolean isAscii = true;
+
+ // short circuit 1 byte strings
+ _random[0] = read();
+ if (_random[0] == 0) {
+ return "";
+ }
+
+ _random[1] = read();
+ if (_random[1] == 0) {
+ final String out = ONE_BYTE_STRINGS[_random[0]];
+ return (out != null) ? out : new String(_random, 0, 1, DEFAULT_ENCODING);
+ }
+
+ _stringBuffer.reset();
+ _stringBuffer.write(_random, 0, 2);
+
+ isAscii = _isAscii(_random[0]) && _isAscii(_random[1]);
+
+ byte b;
+ while ((b = read()) != 0) {
+ _stringBuffer.write( b );
+ isAscii = isAscii && _isAscii( b );
+ }
+
+ String out = null;
+ if ( isAscii ){
+ out = _stringBuffer.asAscii();
+ }
+ else {
+ try {
+ out = _stringBuffer.asString( DEFAULT_ENCODING );
+ }
+ catch ( UnsupportedOperationException e ){
+ throw new BSONException( "impossible" , e );
+ }
+ }
+ _stringBuffer.reset();
+ return out;
+ }
+
+ public String readUTF8String()
+ throws IOException {
+ final int size = readInt();
+ // this is just protection in case it's corrupted, to avoid huge strings
+ if ( size <= 0 || size > MAX_STRING )
+ throw new BSONException( "bad string size: " + size );
+
+ if ( size < _inputBuffer.length / 2 ){
+ if ( size == 1 ){
+ read();
+ return "";
+ }
+
+ return new String( _inputBuffer , _need(size) , size - 1 , DEFAULT_ENCODING );
+ }
+
+ final byte [] b = size < _random.length ? _random : new byte[size];
+
+ fill( b , size );
+
+ try {
+ return new String( b , 0 , size - 1 , DEFAULT_ENCODING );
+ }
+ catch ( java.io.UnsupportedEncodingException uee ){
+ throw new BSONException( "impossible" , uee );
+ }
+ }
+
+ public int numRead() {
+ return _read;
+ }
+
+ public int getPos() {
+ return _pos;
+ }
+
+ public int getMax() {
+ return _max;
+ }
+
+ public void setMax(int _max) {
+ this._max = _max;
+ }
+
+ int _read;
+ final InputStream _raw;
+
+ int _max = 4; // max number of total bytes allowed to ready
+
+ }
+
+ protected BSONInput _in;
+ protected BSONCallback _callback;
+
+ private byte [] _random = new byte[1024]; // has to be used within a single function
+ private byte [] _inputBuffer = new byte[1024];
+
+ private PoolOutputBuffer _stringBuffer = new PoolOutputBuffer();
+
+ protected int _pos; // current offset into _inputBuffer
+ protected int _len; // length of valid data in _inputBuffer
+
+ private static final int MAX_STRING = ( 32 * 1024 * 1024 );
+
+ private static final String DEFAULT_ENCODING = "UTF-8";
+
+ @SuppressWarnings("unused")
+ private static final boolean _isAscii( final byte b ){
+ return b >=0 && b <= 127;
+ }
+
+ static final String[] ONE_BYTE_STRINGS = new String[128];
+ static void _fillRange( byte min, byte max ){
+ while ( min < max ){
+ String s = "";
+ s += (char)min;
+ ONE_BYTE_STRINGS[(int)min] = s;
+ min++;
+ }
+ }
+ static {
+ _fillRange( (byte)'0' , (byte)'9' );
+ _fillRange( (byte)'a' , (byte)'z' );
+ _fillRange( (byte)'A' , (byte)'Z' );
+ }
+}
diff --git a/src/com/massivecraft/mcore3/lib/bson/BasicBSONEncoder.java b/src/com/massivecraft/mcore3/lib/bson/BasicBSONEncoder.java
new file mode 100644
index 00000000..23e81975
--- /dev/null
+++ b/src/com/massivecraft/mcore3/lib/bson/BasicBSONEncoder.java
@@ -0,0 +1,530 @@
+// BSONEncoder.java
+
+/**
+ * Copyright (C) 2008 10gen 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 com.massivecraft.mcore3.lib.bson;
+
+import static com.massivecraft.mcore3.lib.bson.BSON.ARRAY;
+import static com.massivecraft.mcore3.lib.bson.BSON.BINARY;
+import static com.massivecraft.mcore3.lib.bson.BSON.BOOLEAN;
+import static com.massivecraft.mcore3.lib.bson.BSON.B_BINARY;
+import static com.massivecraft.mcore3.lib.bson.BSON.B_GENERAL;
+import static com.massivecraft.mcore3.lib.bson.BSON.B_UUID;
+import static com.massivecraft.mcore3.lib.bson.BSON.CODE;
+import static com.massivecraft.mcore3.lib.bson.BSON.CODE_W_SCOPE;
+import static com.massivecraft.mcore3.lib.bson.BSON.DATE;
+import static com.massivecraft.mcore3.lib.bson.BSON.EOO;
+import static com.massivecraft.mcore3.lib.bson.BSON.MAXKEY;
+import static com.massivecraft.mcore3.lib.bson.BSON.MINKEY;
+import static com.massivecraft.mcore3.lib.bson.BSON.NULL;
+import static com.massivecraft.mcore3.lib.bson.BSON.NUMBER;
+import static com.massivecraft.mcore3.lib.bson.BSON.NUMBER_INT;
+import static com.massivecraft.mcore3.lib.bson.BSON.NUMBER_LONG;
+import static com.massivecraft.mcore3.lib.bson.BSON.OBJECT;
+import static com.massivecraft.mcore3.lib.bson.BSON.OID;
+import static com.massivecraft.mcore3.lib.bson.BSON.REGEX;
+import static com.massivecraft.mcore3.lib.bson.BSON.STRING;
+import static com.massivecraft.mcore3.lib.bson.BSON.SYMBOL;
+import static com.massivecraft.mcore3.lib.bson.BSON.TIMESTAMP;
+import static com.massivecraft.mcore3.lib.bson.BSON.UNDEFINED;
+import static com.massivecraft.mcore3.lib.bson.BSON.regexFlags;
+
+import java.lang.reflect.Array;
+import java.nio.Buffer;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.UUID;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.regex.Pattern;
+
+
+import com.massivecraft.mcore3.lib.bson.io.BasicOutputBuffer;
+import com.massivecraft.mcore3.lib.bson.io.OutputBuffer;
+import com.massivecraft.mcore3.lib.bson.types.BSONTimestamp;
+import com.massivecraft.mcore3.lib.bson.types.Binary;
+import com.massivecraft.mcore3.lib.bson.types.Code;
+import com.massivecraft.mcore3.lib.bson.types.CodeWScope;
+import com.massivecraft.mcore3.lib.bson.types.MaxKey;
+import com.massivecraft.mcore3.lib.bson.types.MinKey;
+import com.massivecraft.mcore3.lib.bson.types.ObjectId;
+import com.massivecraft.mcore3.lib.bson.types.Symbol;
+import com.massivecraft.mcore3.lib.mongodb.DBRefBase;
+
+/**
+ * this is meant to be pooled or cached
+ * there is some per instance memory for string conversion, etc...
+ */
+@SuppressWarnings("unchecked")
+public class BasicBSONEncoder implements BSONEncoder {
+
+ static final boolean DEBUG = false;
+
+ public BasicBSONEncoder(){
+
+ }
+
+ public byte[] encode( BSONObject o ){
+ BasicOutputBuffer buf = new BasicOutputBuffer();
+ set( buf );
+ putObject( o );
+ done();
+ return buf.toByteArray();
+ }
+
+ public void set( OutputBuffer out ){
+ if ( _buf != null )
+ throw new IllegalStateException( "in the middle of something" );
+
+ _buf = out;
+ }
+
+ public void done(){
+ _buf = null;
+ }
+
+ /**
+ * @return true if object was handled
+ */
+ protected boolean handleSpecialObjects( String name , BSONObject o ){
+ return false;
+ }
+
+ protected boolean putSpecial( String name , Object o ){
+ return false;
+ }
+
+ /** Encodes a BSONObject
.
+ * This is for the higher level api calls
+ * @param o the object to encode
+ * @return the number of characters in the encoding
+ */
+ public int putObject( BSONObject o ){
+ return putObject( null , o );
+ }
+
+ /**
+ * this is really for embedded objects
+ */
+ @SuppressWarnings("rawtypes")
+ protected int putObject( String name , BSONObject o ){
+
+ if ( o == null )
+ throw new NullPointerException( "can't save a null object" );
+
+ if ( DEBUG ) System.out.println( "putObject : " + name + " [" + o.getClass() + "]" + " # keys " + o.keySet().size() );
+
+ final int start = _buf.getPosition();
+
+ byte myType = OBJECT;
+ if ( o instanceof List )
+ myType = ARRAY;
+
+ if ( handleSpecialObjects( name , o ) )
+ return _buf.getPosition() - start;
+
+ if ( name != null ){
+ _put( myType , name );
+ }
+
+ final int sizePos = _buf.getPosition();
+ _buf.writeInt( 0 ); // leaving space for this. set it at the end
+
+ List transientFields = null;
+ boolean rewriteID = myType == OBJECT && name == null;
+
+
+ if ( myType == OBJECT ) {
+ if ( rewriteID && o.containsField( "_id" ) )
+ _putObjectField( "_id" , o.get( "_id" ) );
+
+ {
+ Object temp = o.get( "_transientFields" );
+ if ( temp instanceof List )
+ transientFields = (List)temp;
+ }
+ }
+
+ //TODO: reduce repeated code below.
+ if ( o instanceof Map ){
+ for ( Entry e : ((Map)o).entrySet() ){
+
+ if ( rewriteID && e.getKey().equals( "_id" ) )
+ continue;
+
+ if ( transientFields != null && transientFields.contains( e.getKey() ) )
+ continue;
+
+ _putObjectField( e.getKey() , e.getValue() );
+
+ }
+ } else {
+ for ( String s : o.keySet() ){
+
+ if ( rewriteID && s.equals( "_id" ) )
+ continue;
+
+ if ( transientFields != null && transientFields.contains( s ) )
+ continue;
+
+ Object val = o.get( s );
+
+ _putObjectField( s , val );
+
+ }
+ }
+ _buf.write( EOO );
+
+ _buf.writeInt( sizePos , _buf.getPosition() - sizePos );
+ return _buf.getPosition() - start;
+ }
+
+ @SuppressWarnings("rawtypes")
+ protected void _putObjectField( String name , Object val ){
+
+ if ( name.equals( "_transientFields" ) )
+ return;
+
+ if ( DEBUG ) System.out.println( "\t put thing : " + name );
+
+ if ( name.equals( "$where") && val instanceof String ){
+ _put( CODE , name );
+ _putValueString( val.toString() );
+ return;
+ }
+
+ val = BSON.applyEncodingHooks( val );
+
+ if ( val == null )
+ putNull(name);
+ else if ( val instanceof Date )
+ putDate( name , (Date)val );
+ else if ( val instanceof Number )
+ putNumber(name, (Number)val );
+ else if ( val instanceof Character )
+ putString(name, val.toString() );
+ else if ( val instanceof String )
+ putString(name, val.toString() );
+ else if ( val instanceof ObjectId )
+ putObjectId(name, (ObjectId)val );
+ else if ( val instanceof BSONObject )
+ putObject(name, (BSONObject)val );
+ else if ( val instanceof Boolean )
+ putBoolean(name, (Boolean)val );
+ else if ( val instanceof Pattern )
+ putPattern(name, (Pattern)val );
+ else if ( val instanceof Map )
+ putMap( name , (Map)val );
+ else if ( val instanceof Iterable)
+ putIterable( name , (Iterable)val );
+ else if ( val instanceof byte[] )
+ putBinary( name , (byte[])val );
+ else if ( val instanceof Binary )
+ putBinary( name , (Binary)val );
+ else if ( val instanceof UUID )
+ putUUID( name , (UUID)val );
+ else if ( val.getClass().isArray() )
+ putArray( name , val );
+
+ else if (val instanceof Symbol) {
+ putSymbol(name, (Symbol) val);
+ }
+ else if (val instanceof BSONTimestamp) {
+ putTimestamp( name , (BSONTimestamp)val );
+ }
+ else if (val instanceof CodeWScope) {
+ putCodeWScope( name , (CodeWScope)val );
+ }
+ else if (val instanceof Code) {
+ putCode( name , (Code)val );
+ }
+ else if (val instanceof DBRefBase) {
+ BSONObject temp = new BasicBSONObject();
+ temp.put("$ref", ((DBRefBase)val).getRef());
+ temp.put("$id", ((DBRefBase)val).getId());
+ putObject( name, temp );
+ }
+ else if ( val instanceof MinKey )
+ putMinKey( name );
+ else if ( val instanceof MaxKey )
+ putMaxKey( name );
+ else if ( putSpecial( name , val ) ){
+ // no-op
+ }
+ else {
+ throw new IllegalArgumentException( "can't serialize " + val.getClass() );
+ }
+
+ }
+
+ private void putArray( String name , Object array ) {
+ _put( ARRAY , name );
+ final int sizePos = _buf.getPosition();
+ _buf.writeInt( 0 );
+
+ int size = Array.getLength(array);
+ for ( int i = 0; i < size; i++ )
+ _putObjectField( String.valueOf( i ) , Array.get( array, i ) );
+
+ _buf.write( EOO );
+ _buf.writeInt( sizePos , _buf.getPosition() - sizePos );
+ }
+
+ @SuppressWarnings("rawtypes")
+ private void putIterable( String name , Iterable l ){
+ _put( ARRAY , name );
+ final int sizePos = _buf.getPosition();
+ _buf.writeInt( 0 );
+
+ int i=0;
+ for ( Object obj: l ) {
+ _putObjectField( String.valueOf( i ) , obj );
+ i++;
+ }
+
+
+ _buf.write( EOO );
+ _buf.writeInt( sizePos , _buf.getPosition() - sizePos );
+ }
+
+ @SuppressWarnings("rawtypes")
+ private void putMap( String name , Map m ){
+ _put( OBJECT , name );
+ final int sizePos = _buf.getPosition();
+ _buf.writeInt( 0 );
+
+ for ( Map.Entry entry : (Set)m.entrySet() )
+ _putObjectField( entry.getKey().toString() , entry.getValue() );
+
+ _buf.write( EOO );
+ _buf.writeInt( sizePos , _buf.getPosition() - sizePos );
+ }
+
+
+ protected void putNull( String name ){
+ _put( NULL , name );
+ }
+
+ protected void putUndefined(String name){
+ _put(UNDEFINED, name);
+ }
+
+ protected void putTimestamp(String name, BSONTimestamp ts ){
+ _put( TIMESTAMP , name );
+ _buf.writeInt( ts.getInc() );
+ _buf.writeInt( ts.getTime() );
+ }
+
+ protected void putCodeWScope( String name , CodeWScope code ){
+ _put( CODE_W_SCOPE , name );
+ int temp = _buf.getPosition();
+ _buf.writeInt( 0 );
+ _putValueString( code.getCode() );
+ putObject( code.getScope() );
+ _buf.writeInt( temp , _buf.getPosition() - temp );
+ }
+
+ @SuppressWarnings("unused")
+ protected void putCode( String name , Code code ){
+ _put( CODE , name );
+ int temp = _buf.getPosition();
+ _putValueString( code.getCode() );
+ }
+
+ protected void putBoolean( String name , Boolean b ){
+ _put( BOOLEAN , name );
+ _buf.write( b ? (byte)0x1 : (byte)0x0 );
+ }
+
+ protected void putDate( String name , Date d ){
+ _put( DATE , name );
+ _buf.writeLong( d.getTime() );
+ }
+
+ protected void putNumber( String name , Number n ){
+ if ( n instanceof Integer ||
+ n instanceof Short ||
+ n instanceof Byte ||
+ n instanceof AtomicInteger ){
+ _put( NUMBER_INT , name );
+ _buf.writeInt( n.intValue() );
+ }
+ else if ( n instanceof Long || n instanceof AtomicLong ) {
+ _put( NUMBER_LONG , name );
+ _buf.writeLong( n.longValue() );
+ }
+ else if ( n instanceof Float || n instanceof Double ) {
+ _put( NUMBER , name );
+ _buf.writeDouble( n.doubleValue() );
+ }
+ else {
+ throw new IllegalArgumentException( "can't serialize " + n.getClass() );
+ }
+ }
+
+ protected void putBinary( String name , byte[] data ){
+ putBinary( name, B_GENERAL, data );
+ }
+
+ protected void putBinary( String name , Binary val ){
+ putBinary( name, val.getType(), val.getData() );
+ }
+
+ private void putBinary( String name , int type , byte[] data ){
+ _put( BINARY , name );
+ int totalLen = data.length;
+
+ if (type == B_BINARY)
+ totalLen += 4;
+
+ _buf.writeInt( totalLen );
+ _buf.write( type );
+ if (type == B_BINARY)
+ _buf.writeInt( totalLen -4 );
+ int before = _buf.getPosition();
+ _buf.write( data );
+ int after = _buf.getPosition();
+ com.massivecraft.mcore3.lib.mongodb.util.MyAsserts.assertEquals( after - before , data.length );
+ }
+
+ protected void putUUID( String name , UUID val ){
+ _put( BINARY , name );
+ _buf.writeInt( 16 );
+ _buf.write( B_UUID );
+ _buf.writeLong( val.getMostSignificantBits());
+ _buf.writeLong( val.getLeastSignificantBits());
+ }
+
+ protected void putSymbol( String name , Symbol s ){
+ _putString(name, s.getSymbol(), SYMBOL);
+ }
+
+ protected void putString(String name, String s) {
+ _putString(name, s, STRING);
+ }
+
+ private void _putString( String name , String s, byte type ){
+ _put( type , name );
+ _putValueString( s );
+ }
+
+ protected void putObjectId( String name , ObjectId oid ){
+ _put( OID , name );
+ // according to spec, values should be stored big endian
+ _buf.writeIntBE( oid._time() );
+ _buf.writeIntBE( oid._machine() );
+ _buf.writeIntBE( oid._inc() );
+ }
+
+ private void putPattern( String name, Pattern p ) {
+ _put( REGEX , name );
+ _put( p.pattern() );
+ _put( regexFlags( p.flags() ) );
+ }
+
+ private void putMinKey( String name ) {
+ _put( MINKEY , name );
+ }
+
+ private void putMaxKey( String name ) {
+ _put( MAXKEY , name );
+ }
+
+
+ // ----------------------------------------------
+
+ /**
+ * Encodes the type and key.
+ *
+ */
+ protected void _put( byte type , String name ){
+ _buf.write( type );
+ _put( name );
+ }
+
+ protected void _putValueString( String s ){
+ int lenPos = _buf.getPosition();
+ _buf.writeInt( 0 ); // making space for size
+ int strLen = _put( s );
+ _buf.writeInt( lenPos , strLen );
+ }
+
+ void _reset( Buffer b ){
+ b.position(0);
+ b.limit( b.capacity() );
+ }
+
+ /**
+ * puts as utf-8 string
+ */
+ protected int _put( String str ){
+
+ final int len = str.length();
+ int total = 0;
+
+ for ( int i=0; i> 6) ) );
+ _buf.write( (byte)(0x80 + (c & 0x3f) ) );
+ total += 2;
+ }
+ else if (c < 0x10000){
+ _buf.write( (byte)(0xe0 + (c >> 12) ) );
+ _buf.write( (byte)(0x80 + ((c >> 6) & 0x3f) ) );
+ _buf.write( (byte)(0x80 + (c & 0x3f) ) );
+ total += 3;
+ }
+ else {
+ _buf.write( (byte)(0xf0 + (c >> 18)) );
+ _buf.write( (byte)(0x80 + ((c >> 12) & 0x3f)) );
+ _buf.write( (byte)(0x80 + ((c >> 6) & 0x3f)) );
+ _buf.write( (byte)(0x80 + (c & 0x3f)) );
+ total += 4;
+ }
+
+ i += Character.charCount(c);
+ }
+
+ _buf.write( (byte)0 );
+ total++;
+ return total;
+ }
+
+ public void writeInt( int x ){
+ _buf.writeInt( x );
+ }
+
+ public void writeLong( long x ){
+ _buf.writeLong( x );
+ }
+
+ public void writeCString( String s ){
+ _put( s );
+ }
+
+ protected OutputBuffer _buf;
+
+}
diff --git a/src/com/massivecraft/mcore3/lib/bson/BasicBSONObject.java b/src/com/massivecraft/mcore3/lib/bson/BasicBSONObject.java
new file mode 100644
index 00000000..f457922b
--- /dev/null
+++ b/src/com/massivecraft/mcore3/lib/bson/BasicBSONObject.java
@@ -0,0 +1,355 @@
+// BasicBSONObject.java
+
+/**
+ * Copyright (C) 2008 10gen 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 com.massivecraft.mcore3.lib.bson;
+
+// BSON
+import com.massivecraft.mcore3.lib.bson.types.ObjectId;
+
+// Java
+import java.util.Map;
+import java.util.Set;
+import java.util.Date;
+import java.util.LinkedHashMap;
+import java.util.regex.Pattern;
+
+/**
+ * A simple implementation of DBObject
.
+ * A DBObject
can be created as follows, using this class:
+ *
+ * DBObject obj = new BasicBSONObject();
+ * obj.put( "foo", "bar" );
+ *
+ */
+public class BasicBSONObject extends LinkedHashMap implements BSONObject {
+
+ private static final long serialVersionUID = -4415279469780082174L;
+
+ /**
+ * Creates an empty object.
+ */
+ public BasicBSONObject(){
+ }
+
+ public BasicBSONObject(int size){
+ super(size);
+ }
+
+ /**
+ * Convenience CTOR
+ * @param key key under which to store
+ * @param value value to stor
+ */
+ public BasicBSONObject(String key, Object value){
+ put(key, value);
+ }
+
+ /**
+ * Creates a DBObject from a map.
+ * @param m map to convert
+ */
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ public BasicBSONObject(Map m) {
+ super(m);
+ }
+
+ /**
+ * Converts a DBObject to a map.
+ * @return the DBObject
+ */
+ @SuppressWarnings("rawtypes")
+ public Map toMap() {
+ return new LinkedHashMap(this);
+ }
+
+ /** Deletes a field from this object.
+ * @param key the field name to remove
+ * @return the object removed
+ */
+ public Object removeField( String key ){
+ return remove( key );
+ }
+
+ /** Checks if this object contains a given field
+ * @param field field name
+ * @return if the field exists
+ */
+ public boolean containsField( String field ){
+ return super.containsKey(field);
+ }
+
+ /**
+ * @deprecated
+ */
+ @Deprecated
+ public boolean containsKey( String key ){
+ return containsField(key);
+ }
+
+ /** Gets a value from this object
+ * @param key field name
+ * @return the value
+ */
+ public Object get( String key ){
+ return super.get(key);
+ }
+
+ /** Returns the value of a field as an int
.
+ * @param key the field to look for
+ * @return the field value (or default)
+ */
+ public int getInt( String key ){
+ Object o = get(key);
+ if ( o == null )
+ throw new NullPointerException( "no value for: " + key );
+
+ return BSON.toInt( o );
+ }
+
+ /** Returns the value of a field as an int
.
+ * @param key the field to look for
+ * @param def the default to return
+ * @return the field value (or default)
+ */
+ public int getInt( String key , int def ){
+ Object foo = get( key );
+ if ( foo == null )
+ return def;
+
+ return BSON.toInt( foo );
+ }
+
+ /**
+ * Returns the value of a field as a long
.
+ *
+ * @param key the field to return
+ * @return the field value
+ */
+ public long getLong( String key){
+ Object foo = get( key );
+ return ((Number)foo).longValue();
+ }
+
+ /**
+ * Returns the value of a field as an long
.
+ * @param key the field to look for
+ * @param def the default to return
+ * @return the field value (or default)
+ */
+ public long getLong( String key , long def ) {
+ Object foo = get( key );
+ if ( foo == null )
+ return def;
+
+ return ((Number)foo).longValue();
+ }
+
+ /**
+ * Returns the value of a field as a double
.
+ *
+ * @param key the field to return
+ * @return the field value
+ */
+ public double getDouble( String key){
+ Object foo = get( key );
+ return ((Number)foo).doubleValue();
+ }
+
+ /**
+ * Returns the value of a field as an double
.
+ * @param key the field to look for
+ * @param def the default to return
+ * @return the field value (or default)
+ */
+ public double getDouble( String key , double def ) {
+ Object foo = get( key );
+ if ( foo == null )
+ return def;
+
+ return ((Number)foo).doubleValue();
+ }
+
+ /** Returns the value of a field as a string
+ * @param key the field to look up
+ * @return the value of the field, converted to a string
+ */
+ public String getString( String key ){
+ Object foo = get( key );
+ if ( foo == null )
+ return null;
+ return foo.toString();
+ }
+
+ /**
+ * Returns the value of a field as a string
+ * @param key the field to look up
+ * @param def the default to return
+ * @return the value of the field, converted to a string
+ */
+ public String getString( String key, final String def ) {
+ Object foo = get( key );
+ if ( foo == null )
+ return def;
+
+ return foo.toString();
+ }
+
+ /** Returns the value of a field as a boolean.
+ * @param key the field to look up
+ * @return the value of the field, or false if field does not exist
+ */
+ public boolean getBoolean( String key ){
+ return getBoolean(key, false);
+ }
+
+ /** Returns the value of a field as a boolean
+ * @param key the field to look up
+ * @param def the default value in case the field is not found
+ * @return the value of the field, converted to a string
+ */
+ public boolean getBoolean( String key , boolean def ){
+ Object foo = get( key );
+ if ( foo == null )
+ return def;
+ if ( foo instanceof Number )
+ return ((Number)foo).intValue() > 0;
+ if ( foo instanceof Boolean )
+ return ((Boolean)foo).booleanValue();
+ throw new IllegalArgumentException( "can't coerce to bool:" + foo.getClass() );
+ }
+
+ /**
+ * Returns the object id or null if not set.
+ * @param field The field to return
+ * @return The field object value or null if not found (or if null :-^).
+ */
+ public ObjectId getObjectId( final String field ) {
+ return (ObjectId) get( field );
+ }
+
+ /**
+ * Returns the object id or def if not set.
+ * @param field The field to return
+ * @param def the default value in case the field is not found
+ * @return The field object value or def if not set.
+ */
+ public ObjectId getObjectId( final String field, final ObjectId def ) {
+ final Object foo = get( field );
+ return (foo != null) ? (ObjectId)foo : def;
+ }
+
+ /**
+ * Returns the date or null if not set.
+ * @param field The field to return
+ * @return The field object value or null if not found.
+ */
+ public Date getDate( final String field ) {
+ return (Date) get( field );
+ }
+
+ /**
+ * Returns the date or def if not set.
+ * @param field The field to return
+ * @param def the default value in case the field is not found
+ * @return The field object value or def if not set.
+ */
+ public Date getDate( final String field, final Date def ) {
+ final Object foo = get( field );
+ return (foo != null) ? (Date)foo : def;
+ }
+
+ /** Add a key/value pair to this object
+ * @param key the field name
+ * @param val the field value
+ * @return the val
parameter
+ */
+ public Object put( String key , Object val ){
+ return super.put( key , val );
+ }
+
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ public void putAll( Map m ){
+ for ( Map.Entry entry : (Set)m.entrySet() ){
+ put( entry.getKey().toString() , entry.getValue() );
+ }
+ }
+
+ public void putAll( BSONObject o ){
+ for ( String k : o.keySet() ){
+ put( k , o.get( k ) );
+ }
+ }
+
+ /** Add a key/value pair to this object
+ * @param key the field name
+ * @param val the field value
+ * @return this
+ */
+ public BasicBSONObject append( String key , Object val ){
+ put( key , val );
+
+ return this;
+ }
+
+ /** Returns a JSON serialization of this object
+ * @return JSON serialization
+ */
+ public String toString(){
+ return com.massivecraft.mcore3.lib.mongodb.util.JSON.serialize( this );
+ }
+
+ public boolean equals( Object o ){
+ if ( ! ( o instanceof BSONObject ) )
+ return false;
+
+ BSONObject other = (BSONObject)o;
+ if ( ! keySet().equals( other.keySet() ) )
+ return false;
+
+ for ( String key : keySet() ){
+ Object a = get( key );
+ Object b = other.get( key );
+
+ if ( a == null ){
+ if ( b != null )
+ return false;
+ }
+ if ( b == null ){
+ if ( a != null )
+ return false;
+ }
+ else if ( a instanceof Number && b instanceof Number ){
+ if ( ((Number)a).doubleValue() !=
+ ((Number)b).doubleValue() )
+ return false;
+ }
+ else if ( a instanceof Pattern && b instanceof Pattern ){
+ Pattern p1 = (Pattern) a;
+ Pattern p2 = (Pattern) b;
+ if (!p1.pattern().equals(p2.pattern()) || p1.flags() != p2.flags())
+ return false;
+ }
+ else {
+ if ( ! a.equals( b ) )
+ return false;
+ }
+ }
+ return true;
+ }
+
+}
diff --git a/src/com/massivecraft/mcore3/lib/bson/EmptyBSONCallback.java b/src/com/massivecraft/mcore3/lib/bson/EmptyBSONCallback.java
new file mode 100644
index 00000000..9db633fc
--- /dev/null
+++ b/src/com/massivecraft/mcore3/lib/bson/EmptyBSONCallback.java
@@ -0,0 +1,143 @@
+/**
+ * Copyright (C) 2011 10gen 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 com.massivecraft.mcore3.lib.bson;
+
+import com.massivecraft.mcore3.lib.bson.types.ObjectId;
+
+public class EmptyBSONCallback implements BSONCallback {
+
+ public void objectStart(){
+ throw new UnsupportedOperationException( "Not supported yet." );
+ }
+
+ public void objectStart( String name ){
+ throw new UnsupportedOperationException( "Not supported yet." );
+ }
+
+ public void objectStart( boolean array ){
+ throw new UnsupportedOperationException( "Not supported yet." );
+ }
+
+ public Object objectDone(){
+ throw new UnsupportedOperationException( "Not supported yet." );
+ }
+
+ public BSONCallback createBSONCallback(){
+ throw new UnsupportedOperationException( "Not supported yet." );
+ }
+
+ public void arrayStart(){
+ throw new UnsupportedOperationException( "Not supported yet." );
+ }
+
+ public void arrayStart( String name ){
+ throw new UnsupportedOperationException( "Not supported yet." );
+ }
+
+ public Object arrayDone(){
+ throw new UnsupportedOperationException( "Not supported yet." );
+ }
+
+ public void gotNull( String name ){
+ throw new UnsupportedOperationException( "Not supported yet." );
+ }
+
+ public void gotUndefined( String name ){
+ throw new UnsupportedOperationException( "Not supported yet." );
+ }
+
+ public void gotMinKey( String name ){
+ throw new UnsupportedOperationException( "Not supported yet." );
+ }
+
+ public void gotMaxKey( String name ){
+ throw new UnsupportedOperationException( "Not supported yet." );
+ }
+
+ public void gotBoolean( String name , boolean v ){
+ throw new UnsupportedOperationException( "Not supported yet." );
+ }
+
+ public void gotDouble( String name , double v ){
+ throw new UnsupportedOperationException( "Not supported yet." );
+ }
+
+ public void gotInt( String name , int v ){
+ throw new UnsupportedOperationException( "Not supported yet." );
+ }
+
+ public void gotLong( String name , long v ){
+ throw new UnsupportedOperationException( "Not supported yet." );
+ }
+
+ public void gotDate( String name , long millis ){
+ throw new UnsupportedOperationException( "Not supported yet." );
+ }
+
+ public void gotString( String name , String v ){
+ throw new UnsupportedOperationException( "Not supported yet." );
+ }
+
+ public void gotSymbol( String name , String v ){
+ throw new UnsupportedOperationException( "Not supported yet." );
+ }
+
+ public void gotRegex( String name , String pattern , String flags ){
+ throw new UnsupportedOperationException( "Not supported yet." );
+ }
+
+ public void gotTimestamp( String name , int time , int inc ){
+ throw new UnsupportedOperationException( "Not supported yet." );
+ }
+
+ public void gotObjectId( String name , ObjectId id ){
+ throw new UnsupportedOperationException( "Not supported yet." );
+ }
+
+ public void gotDBRef( String name , String ns , ObjectId id ){
+ throw new UnsupportedOperationException( "Not supported yet." );
+ }
+
+ @Deprecated
+ public void gotBinaryArray( String name , byte[] data ){
+ throw new UnsupportedOperationException( "Not supported yet." );
+ }
+
+ public void gotUUID( String name , long part1 , long part2 ){
+ throw new UnsupportedOperationException( "Not supported yet." );
+ }
+
+ public void gotCode( String name , String code ){
+ throw new UnsupportedOperationException( "Not supported yet." );
+ }
+
+ public void gotCodeWScope( String name , String code , Object scope ){
+ throw new UnsupportedOperationException( "Not supported yet." );
+ }
+
+ public void reset(){
+ throw new UnsupportedOperationException( "Not supported yet." );
+ }
+
+ public Object get(){
+ throw new UnsupportedOperationException( "Not supported yet." );
+ }
+
+ public void gotBinary( String name , byte type , byte[] data ){
+ throw new UnsupportedOperationException( "Not supported yet." );
+ }
+
+}
\ No newline at end of file
diff --git a/src/com/massivecraft/mcore3/lib/bson/KeyCachingLazyBSONObject.java b/src/com/massivecraft/mcore3/lib/bson/KeyCachingLazyBSONObject.java
new file mode 100644
index 00000000..d54b4d24
--- /dev/null
+++ b/src/com/massivecraft/mcore3/lib/bson/KeyCachingLazyBSONObject.java
@@ -0,0 +1,70 @@
+/**
+ * Copyright (C) 2011 10gen 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 com.massivecraft.mcore3.lib.bson;
+
+import java.util.HashMap;
+
+import com.massivecraft.mcore3.lib.bson.io.BSONByteBuffer;
+
+/**
+ * @author brendan
+ * @author scotthernandez
+ */
+public class KeyCachingLazyBSONObject extends LazyBSONObject {
+
+ public KeyCachingLazyBSONObject(byte[] data , LazyBSONCallback cbk) { super( data , cbk ); }
+ public KeyCachingLazyBSONObject(byte[] data , int offset , LazyBSONCallback cbk) { super( data , offset , cbk ); }
+ public KeyCachingLazyBSONObject( BSONByteBuffer buffer, LazyBSONCallback callback ){ super( buffer, callback ); }
+ public KeyCachingLazyBSONObject( BSONByteBuffer buffer, int offset, LazyBSONCallback callback ){ super( buffer, offset, callback ); }
+
+ @Override
+ public Object get( String key ) {
+ ensureFieldList();
+ return super.get( key );
+ }
+
+ @Override
+ public boolean containsField( String s ) {
+ ensureFieldList();
+ if (! fieldIndex.containsKey( s ) )
+ return false;
+ else
+ return super.containsField( s );
+ }
+
+ synchronized private void ensureFieldList() {
+ //only run once
+ if (fieldIndex == null) return;
+ try {
+ int offset = _doc_start_offset + FIRST_ELMT_OFFSET;
+
+ while ( !isElementEmpty( offset ) ){
+ int fieldSize = sizeCString( offset );
+ int elementSize = getElementBSONSize( offset++ );
+ String name = _input.getCString( offset );
+ ElementRecord _t_record = new ElementRecord( name, offset );
+ fieldIndex.put( name, _t_record );
+ offset += ( fieldSize + elementSize );
+ }
+ } catch (Exception e) {
+ fieldIndex = new HashMap();
+ }
+ }
+
+
+ private HashMap fieldIndex = new HashMap();
+
+}
diff --git a/src/com/massivecraft/mcore3/lib/bson/LazyBSONCallback.java b/src/com/massivecraft/mcore3/lib/bson/LazyBSONCallback.java
new file mode 100644
index 00000000..d3f80160
--- /dev/null
+++ b/src/com/massivecraft/mcore3/lib/bson/LazyBSONCallback.java
@@ -0,0 +1,88 @@
+/**
+ * Copyright (C) 2008 10gen 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 com.massivecraft.mcore3.lib.bson;
+
+import java.util.List;
+import java.util.logging.Logger;
+
+import com.massivecraft.mcore3.lib.bson.types.ObjectId;
+import com.massivecraft.mcore3.lib.mongodb.LazyDBObject;
+
+
+/**
+ *
+ */
+public class LazyBSONCallback extends EmptyBSONCallback {
+
+ public void objectStart(){
+ throw new UnsupportedOperationException( "Not supported yet." );
+ }
+
+ public void objectStart( String name ){
+ throw new UnsupportedOperationException( "Not supported yet." );
+ }
+
+ public void objectStart( boolean array ){
+ throw new UnsupportedOperationException( "Not supported yet." );
+ }
+
+ public Object objectDone(){
+ throw new UnsupportedOperationException( "Not supported yet." );
+ }
+
+ public void reset(){
+ _root = null;
+ }
+
+ public Object get(){
+ return _root;
+ }
+
+ public void gotBinary( String name, byte type, byte[] data ){
+ setRootObject( createObject( data, 0 ) );
+ }
+
+ public void setRootObject( Object root ){
+ _root = root;
+ }
+
+ public Object createObject( byte[] data, int offset ){
+ return new LazyDBObject( data, offset, this );
+ }
+
+ @SuppressWarnings("rawtypes")
+ public List createArray( byte[] data, int offset ){
+ return new LazyDBList( data, offset, this );
+ }
+
+ public Object createDBRef( String ns, ObjectId id ){
+ return new BasicBSONObject( "$ns", ns ).append( "$id", id );
+ }
+
+
+ /* public Object createObject(InputStream input, int offset) {
+ try {
+ return new LazyBSONObject(input, offset, this);
+ }
+ catch ( IOException e ) {
+ e.printStackTrace();
+ return null;
+ }
+ }*/
+ private Object _root;
+ @SuppressWarnings("unused")
+ private static final Logger log = Logger.getLogger( "org.bson.LazyBSONCallback" );
+}
diff --git a/src/com/massivecraft/mcore3/lib/bson/LazyBSONDecoder.java b/src/com/massivecraft/mcore3/lib/bson/LazyBSONDecoder.java
new file mode 100644
index 00000000..3c642ef0
--- /dev/null
+++ b/src/com/massivecraft/mcore3/lib/bson/LazyBSONDecoder.java
@@ -0,0 +1,70 @@
+/**
+ * Copyright (C) 2008 10gen 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 com.massivecraft.mcore3.lib.bson;
+
+import com.massivecraft.mcore3.lib.bson.io.Bits;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.logging.Logger;
+
+/**
+ * implementation of BSONDecoder that creates LazyBSONObject instances
+ */
+public class LazyBSONDecoder implements BSONDecoder {
+ static final Logger LOG = Logger.getLogger( LazyBSONDecoder.class.getName() );
+
+ public BSONObject readObject(byte[] b) {
+ try {
+ return readObject( new ByteArrayInputStream( b ) );
+ }
+ catch ( IOException ioe ){
+ throw new BSONException( "should be impossible" , ioe );
+ }
+ }
+
+ public BSONObject readObject(InputStream in) throws IOException {
+ BSONCallback c = new LazyBSONCallback();
+ decode( in , c );
+ return (BSONObject)c.get();
+ }
+
+ public int decode(byte[] b, BSONCallback callback) {
+ try {
+ return decode( new ByteArrayInputStream( b ), callback );
+ }
+ catch ( IOException ioe ) {
+ throw new BSONException( "should be impossible" , ioe );
+ }
+ }
+
+ public int decode(InputStream in, BSONCallback callback) throws IOException {
+ byte[] objSizeBuffer = new byte[BYTES_IN_INTEGER];
+ Bits.readFully(in, objSizeBuffer, 0, BYTES_IN_INTEGER);
+ int objSize = Bits.readInt(objSizeBuffer);
+ byte[] data = new byte[objSize];
+ System.arraycopy(objSizeBuffer, 0, data, 0, BYTES_IN_INTEGER);
+
+ Bits.readFully(in, data, BYTES_IN_INTEGER, objSize - BYTES_IN_INTEGER);
+
+ // note that we are handing off ownership of the data byte array to the callback
+ callback.gotBinary(null, (byte) 0, data);
+ return objSize;
+ }
+
+ private static int BYTES_IN_INTEGER = 4;
+}
diff --git a/src/com/massivecraft/mcore3/lib/bson/LazyBSONList.java b/src/com/massivecraft/mcore3/lib/bson/LazyBSONList.java
new file mode 100644
index 00000000..68df4fea
--- /dev/null
+++ b/src/com/massivecraft/mcore3/lib/bson/LazyBSONList.java
@@ -0,0 +1,177 @@
+package com.massivecraft.mcore3.lib.bson;
+
+import com.massivecraft.mcore3.lib.bson.io.BSONByteBuffer;
+
+import java.util.*;
+
+@SuppressWarnings( "rawtypes" )
+public class LazyBSONList extends LazyBSONObject implements List {
+
+ public LazyBSONList(byte[] data , LazyBSONCallback callback) { super( data , callback ); }
+ public LazyBSONList(byte[] data , int offset , LazyBSONCallback callback) { super( data , offset , callback ); }
+ public LazyBSONList(BSONByteBuffer buffer , LazyBSONCallback callback) { super( buffer , callback ); }
+ public LazyBSONList(BSONByteBuffer buffer , int offset , LazyBSONCallback callback) { super( buffer , offset , callback ); }
+
+ @Override
+ public boolean contains( Object arg0 ){
+ return indexOf(arg0) > -1;
+ }
+
+ @Override
+ public boolean containsAll( Collection arg0 ){
+ for ( Object obj : arg0 ) {
+ if ( !contains( obj ) )
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public Object get( int pos ){
+ return get("" + pos);
+ }
+
+ @Override
+ public Iterator iterator(){
+ return new LazyBSONListIterator();
+ }
+
+ @Override
+ public int indexOf( Object arg0 ){
+ int pos = 0;
+ Iterator it = iterator();
+ while ( it.hasNext() ) {
+ Object curr = it.next();
+ if ( arg0.equals( curr ) )
+ return pos;
+
+ pos++;
+ }
+ return -1;
+ }
+
+ @Override
+ public int lastIndexOf( Object arg0 ){
+ int pos = 0;
+ int lastFound = -1;
+
+ Iterator it = iterator();
+ while(it.hasNext()) {
+ Object curr = it.next();
+ if(arg0.equals( curr ))
+ lastFound = pos;
+
+ pos++;
+ }
+
+ return lastFound;
+ }
+
+ @Override
+ public int size(){
+ //TODO check the last one and get the key/field name to see the ordinal position in case the array is stored with missing elements.
+ return getElements().size();
+ }
+
+ public class LazyBSONListIterator implements Iterator {
+ List elements;
+ int pos=0;
+
+ public LazyBSONListIterator() {
+ elements = getElements();
+ }
+
+ @Override
+ public boolean hasNext(){
+ return pos < elements.size();
+ }
+
+ @Override
+ public Object next(){
+ return getElementValue(elements.get(pos++));
+ }
+
+ @Override
+ public void remove(){
+ throw new UnsupportedOperationException( "Read Only" );
+ }
+
+ }
+
+ @Override
+ public ListIterator listIterator( int arg0 ){
+ throw new UnsupportedOperationException( "Not Supported" );
+ }
+
+ @Override
+ public ListIterator listIterator(){
+ throw new UnsupportedOperationException( "Not Supported" );
+ }
+
+
+ @Override
+ public boolean add( Object arg0 ){
+ throw new UnsupportedOperationException( "Read Only" );
+ }
+
+ @Override
+ public void add( int arg0 , Object arg1 ){
+ throw new UnsupportedOperationException( "Read Only" );
+ }
+
+ @Override
+ public boolean addAll( Collection arg0 ){
+ throw new UnsupportedOperationException( "Read Only" );
+ }
+
+ @Override
+ public boolean addAll( int arg0 , Collection arg1 ){
+ throw new UnsupportedOperationException( "Read Only" );
+ }
+
+ @Override
+ public void clear(){
+ throw new UnsupportedOperationException( "Read Only" );
+ }
+
+ @Override
+ public boolean remove( Object arg0 ){
+ throw new UnsupportedOperationException( "Read Only" );
+ }
+
+ @Override
+ public Object remove( int arg0 ){
+ throw new UnsupportedOperationException( "Read Only" );
+ }
+
+ @Override
+ public boolean removeAll( Collection arg0 ){
+ throw new UnsupportedOperationException( "Read Only" );
+ }
+
+ @Override
+ public boolean retainAll( Collection arg0 ){
+ throw new UnsupportedOperationException( "Read Only" );
+ }
+
+ @Override
+ public Object set( int arg0 , Object arg1 ){
+ throw new UnsupportedOperationException( "Read Only" );
+ }
+
+ @Override
+ public List subList( int arg0 , int arg1 ){
+ throw new UnsupportedOperationException( "Not Supported" );
+ }
+
+ @Override
+ public Object[] toArray(){
+ throw new UnsupportedOperationException( "Not Supported" );
+ }
+
+ @Override
+ public Object[] toArray( Object[] arg0 ){
+ throw new UnsupportedOperationException( "Not Supported" );
+ }
+
+}
diff --git a/src/com/massivecraft/mcore3/lib/bson/LazyBSONObject.java b/src/com/massivecraft/mcore3/lib/bson/LazyBSONObject.java
new file mode 100644
index 00000000..9ceed018
--- /dev/null
+++ b/src/com/massivecraft/mcore3/lib/bson/LazyBSONObject.java
@@ -0,0 +1,691 @@
+/**
+ * Copyright (C) 2008-2011 10gen 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 com.massivecraft.mcore3.lib.bson;
+
+import com.massivecraft.mcore3.lib.bson.io.BSONByteBuffer;
+import com.massivecraft.mcore3.lib.bson.types.*;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.*;
+import java.util.logging.Logger;
+import java.util.regex.Pattern;
+
+/**
+ * @author antoine
+ * @author brendan
+ * @author scotthernandez
+ * @author Kilroy Wuz Here
+ */
+public class LazyBSONObject implements BSONObject {
+
+ public LazyBSONObject( byte[] data, LazyBSONCallback callback ){
+ this( BSONByteBuffer.wrap( data ), callback );
+ }
+
+ public LazyBSONObject( byte[] data, int offset, LazyBSONCallback callback ){
+ this( BSONByteBuffer.wrap( data, offset, data.length - offset ), offset, callback );
+ }
+
+ public LazyBSONObject( BSONByteBuffer buffer, LazyBSONCallback callback ){
+ this( buffer, 0, callback );
+ }
+
+ public LazyBSONObject( BSONByteBuffer buffer, int offset, LazyBSONCallback callback ){
+ _callback = callback;
+ _input = buffer;
+ _doc_start_offset = offset;
+ }
+
+
+ class ElementRecord {
+ ElementRecord( final String name, final int offset ){
+ this.name = name;
+ this.offset = offset;
+ this.type = getElementType( offset - 1 );
+ this.fieldNameSize = sizeCString( offset );
+ this.valueOffset = offset + fieldNameSize;
+ }
+
+ final String name;
+ /**
+ * The offset the record begins at.
+ */
+ final byte type;
+ final int fieldNameSize;
+ final int valueOffset;
+ final int offset;
+ }
+
+ class LazyBSONKeyIterator implements Iterator {
+
+ public boolean hasNext(){
+ return !isElementEmpty( offset );
+ }
+
+ public String next(){
+ int fieldSize = sizeCString( offset + 1);
+ int elementSize = getElementBSONSize( offset );
+ String key = _input.getCString( offset + 1);
+ offset += fieldSize + elementSize + 1;
+ return key;
+ }
+
+ public void remove(){
+ throw new UnsupportedOperationException( "Read only" );
+ }
+
+ int offset = _doc_start_offset + FIRST_ELMT_OFFSET;
+ }
+
+ public class LazyBSONKeySet extends ReadOnlySet {
+
+ /**
+ * This method runs in time linear to the total size of all keys in the document.
+ *
+ * @return the number of keys in the document
+ */
+ @Override
+ public int size(){
+ int size = 0;
+ Iterator iter = iterator();
+ while(iter.hasNext()) {
+ iter.next();
+ ++size;
+ }
+ return size;
+ }
+
+ @Override
+ public boolean isEmpty(){
+ return LazyBSONObject.this.isEmpty();
+ }
+
+ @Override
+ public boolean contains( Object o ){
+ for ( String key : this ){
+ if ( key.equals( o ) ){
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public Iterator iterator(){
+ return new LazyBSONKeyIterator();
+ }
+
+ @Override
+ public String[] toArray(){
+ String[] a = new String[size()];
+ return toArray(a);
+ }
+
+ @SuppressWarnings( "unchecked" )
+ @Override
+ public T[] toArray(T[] a) {
+ int size = size();
+
+ T[] localArray = a.length >= size ? a :
+ (T[]) java.lang.reflect.Array.newInstance(a.getClass().getComponentType(), size);
+
+ int i = 0;
+ for ( String key : this ){
+ localArray[i++] = (T) key;
+ }
+
+ if (localArray.length > i) {
+ localArray[i] = null;
+ }
+ return localArray;
+ }
+
+ @Override
+ public boolean add( String e ){
+ throw new UnsupportedOperationException( "Not supported yet." );
+ }
+
+ @Override
+ public boolean remove( Object o ){
+ throw new UnsupportedOperationException( "Not supported yet." );
+ }
+
+ @Override
+ public boolean containsAll( Collection> collection ){
+ for ( Object item : collection ){
+ if ( !contains( item ) ){
+ return false;
+ }
+ }
+ return true;
+ }
+ }
+
+ class LazyBSONEntryIterator implements Iterator> {
+
+ public boolean hasNext(){
+ return !isElementEmpty( offset );
+ }
+
+ public Map.Entry next(){
+ int fieldSize = sizeCString(offset + 1);
+ int elementSize = getElementBSONSize(offset);
+ String key = _input.getCString(offset + 1);
+ final ElementRecord nextElementRecord = new ElementRecord(key, ++offset);
+ offset += fieldSize + elementSize;
+ return new Map.Entry() {
+ @Override
+ public String getKey() {
+ return nextElementRecord.name;
+ }
+
+ @Override
+ public Object getValue() {
+ return getElementValue(nextElementRecord);
+ }
+
+ @Override
+ public Object setValue(Object value) {
+ throw new UnsupportedOperationException("Read only");
+ }
+
+ @SuppressWarnings("rawtypes")
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof Map.Entry))
+ return false;
+ Map.Entry e = (Map.Entry) o;
+ return getKey().equals(e.getKey()) && getValue().equals(e.getValue());
+ }
+
+ @Override
+ public int hashCode() {
+ return getKey().hashCode() ^ getValue().hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return getKey() + "=" + getValue();
+ }
+ };
+ }
+
+ public void remove(){
+ throw new UnsupportedOperationException( "Read only" );
+ }
+
+ int offset = _doc_start_offset + FIRST_ELMT_OFFSET;
+ }
+
+ class LazyBSONEntrySet extends ReadOnlySet> {
+ @Override
+ public int size() {
+ return LazyBSONObject.this.keySet().size();
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return LazyBSONObject.this.isEmpty();
+ }
+
+ @Override
+ public boolean contains(Object o) {
+ Iterator> iter = iterator();
+ while (iter.hasNext()) {
+ if (iter.next().equals(o)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean containsAll(Collection> c) {
+ for (Object cur : c) {
+ if (!contains(cur)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ @Override
+ public Iterator> iterator() {
+ return new LazyBSONEntryIterator();
+ }
+
+ @SuppressWarnings("rawtypes")
+ @Override
+ public Object[] toArray() {
+ Map.Entry[] array = new Map.Entry[size()];
+ return toArray(array);
+ }
+
+ @SuppressWarnings( "unchecked" )
+ @Override
+ public T[] toArray(T[] a) {
+ int size = size();
+
+ T[] localArray = a.length >= size ? a :
+ (T[]) java.lang.reflect.Array.newInstance(a.getClass().getComponentType(), size);
+
+ Iterator> iter = iterator();
+ int i = 0;
+ while(iter.hasNext()) {
+ localArray[i++] = (T) iter.next();
+ }
+
+ if (localArray.length > i) {
+ localArray[i] = null;
+ }
+
+ return localArray;
+ }
+ }
+
+ // Base class that throws UnsupportedOperationException for any method that writes to the Set
+ abstract class ReadOnlySet implements Set {
+
+ @Override
+ public boolean add(E e) {
+ throw new UnsupportedOperationException("Read-only Set");
+ }
+
+ @Override
+ public boolean remove(Object o) {
+ throw new UnsupportedOperationException("Read-only Set");
+ }
+
+ @Override
+ public boolean addAll(Collection extends E> c) {
+ throw new UnsupportedOperationException("Read-only Set");
+ }
+
+ @Override
+ public boolean retainAll(Collection> c) {
+ throw new UnsupportedOperationException("Read-only Set");
+ }
+
+ @Override
+ public boolean removeAll(Collection> c) {
+ throw new UnsupportedOperationException("Read-only Set");
+ }
+
+ @Override
+ public void clear() {
+ throw new UnsupportedOperationException("Read-only Set");
+ }
+ }
+
+ public Object put( String key, Object v ){
+ throw new UnsupportedOperationException( "Object is read only" );
+ }
+
+ public void putAll( BSONObject o ){
+ throw new UnsupportedOperationException( "Object is read only" );
+ }
+
+ @SuppressWarnings("rawtypes")
+ public void putAll( Map m ){
+ throw new UnsupportedOperationException( "Object is read only" );
+ }
+
+ public Object get( String key ){
+ //get element up to the key
+ ElementRecord element = getElement(key);
+
+ //no found if null/empty
+ if (element == null) {
+ return null;
+ }
+
+ return getElementValue(element);
+
+ }
+
+ /**
+ * returns the ElementRecord for the given key, or null if not found
+ * @param key the field/key to find
+ * @return ElementRecord for key, or null
+ */
+ ElementRecord getElement(String key){
+ int offset = _doc_start_offset + FIRST_ELMT_OFFSET;
+
+ while ( !isElementEmpty( offset ) ){
+ int fieldSize = sizeCString( offset + 1 );
+ int elementSize = getElementBSONSize( offset );
+ String name = _input.getCString( ++offset);
+
+ if (name.equals(key)) {
+ return new ElementRecord( name, offset );
+ }
+ offset += ( fieldSize + elementSize);
+ }
+
+ return null;
+ }
+
+
+ /**
+ * returns all the ElementRecords in this document
+ * @return list of ElementRecord
+ */
+ List getElements(){
+ int offset = _doc_start_offset + FIRST_ELMT_OFFSET;
+ ArrayList elements = new ArrayList();
+
+ while ( !isElementEmpty( offset ) ){
+ int fieldSize = sizeCString( offset + 1 );
+ int elementSize = getElementBSONSize( offset );
+ String name = _input.getCString( ++offset );
+ ElementRecord rec = new ElementRecord( name, offset );
+ elements.add( rec );
+ offset += ( fieldSize + elementSize );
+ }
+
+ return elements;
+ }
+
+ @SuppressWarnings("rawtypes")
+ public Map toMap(){
+ throw new UnsupportedOperationException( "Not Supported" );
+ }
+
+ public Object removeField( String key ){
+ throw new UnsupportedOperationException( "Object is read only" );
+ }
+
+ @Deprecated
+ public boolean containsKey( String s ){
+ return containsField( s );
+ }
+
+ public boolean containsField( String s ){
+ return keySet().contains( s );
+ }
+
+ /**
+ *
+ * @return the set of all keys in the document
+ */
+ public Set keySet(){
+ return new LazyBSONKeySet();
+ }
+
+ /**
+ * This method will be more efficient than using a combination of keySet() and get(String key)
+ * @return the set of entries (key, value) in the document
+ */
+ public Set> entrySet(){
+ return new LazyBSONEntrySet();
+ }
+
+ protected boolean isElementEmpty( int offset ){
+ return getElementType( offset ) == BSON.EOO;
+ }
+
+ public boolean isEmpty(){
+ return isElementEmpty( _doc_start_offset + FIRST_ELMT_OFFSET );
+ }
+
+ private int getBSONSize( final int offset ){
+ return _input.getInt( offset );
+ }
+
+ public int getBSONSize(){
+ return getBSONSize( _doc_start_offset );
+ }
+
+ public int pipe(OutputStream os) throws IOException {
+ os.write(_input.array(), _doc_start_offset, getBSONSize());
+ return getBSONSize();
+ }
+
+ private String getElementFieldName( final int offset ){
+ return _input.getCString( offset );
+ }
+
+ protected byte getElementType( final int offset ){
+ return _input.get( offset );
+ }
+
+ protected int getElementBSONSize( int offset ){
+ int x = 0;
+ byte type = getElementType( offset++ );
+ int n = sizeCString( offset );
+ int valueOffset = offset + n;
+ switch ( type ){
+ case BSON.EOO:
+ case BSON.UNDEFINED:
+ case BSON.NULL:
+ case BSON.MAXKEY:
+ case BSON.MINKEY:
+ break;
+ case BSON.BOOLEAN:
+ x = 1;
+ break;
+ case BSON.NUMBER_INT:
+ x = 4;
+ break;
+ case BSON.TIMESTAMP:
+ case BSON.DATE:
+ case BSON.NUMBER_LONG:
+ case BSON.NUMBER:
+ x = 8;
+ break;
+ case BSON.OID:
+ x = 12;
+ break;
+ case BSON.SYMBOL:
+ case BSON.CODE:
+ case BSON.STRING:
+ x = _input.getInt( valueOffset ) + 4;
+ break;
+ case BSON.CODE_W_SCOPE:
+ x = _input.getInt( valueOffset );
+ break;
+ case BSON.REF:
+ x = _input.getInt( valueOffset ) + 4 + 12;
+ break;
+ case BSON.OBJECT:
+ case BSON.ARRAY:
+ x = _input.getInt( valueOffset );
+ break;
+ case BSON.BINARY:
+ x = _input.getInt( valueOffset ) + 4 + 1/*subtype*/;
+ break;
+ case BSON.REGEX:
+ // 2 cstrs
+ int part1 = sizeCString( valueOffset );
+ int part2 = sizeCString( valueOffset + part1 );
+ x = part1 + part2;
+ break;
+ default:
+ throw new BSONException( "Invalid type " + type + " for field " + getElementFieldName( offset ) );
+ }
+ return x;
+ }
+
+
+ /**
+ * Returns the size of the BSON cstring at the given offset in the buffer
+ * @param offset the offset into the buffer
+ * @return the size of the BSON cstring, including the null terminator
+ */
+ protected int sizeCString( int offset ){
+ int end = offset;
+ while ( true ){
+ byte b = _input.get( end );
+ if ( b == 0 )
+ break;
+ else
+ end++;
+ }
+ return end - offset + 1;
+ }
+
+ protected Object getElementValue( ElementRecord record ){
+ switch ( record.type ){
+ case BSON.EOO:
+ case BSON.UNDEFINED:
+ case BSON.NULL:
+ return null;
+ case BSON.MAXKEY:
+ return new MaxKey();
+ case BSON.MINKEY:
+ return new MinKey();
+ case BSON.BOOLEAN:
+ return ( _input.get( record.valueOffset ) != 0 );
+ case BSON.NUMBER_INT:
+ return _input.getInt( record.valueOffset );
+ case BSON.TIMESTAMP:
+ int inc = _input.getInt( record.valueOffset );
+ int time = _input.getInt( record.valueOffset + 4 );
+ return new BSONTimestamp( time, inc );
+ case BSON.DATE:
+ return new Date( _input.getLong( record.valueOffset ) );
+ case BSON.NUMBER_LONG:
+ return _input.getLong( record.valueOffset );
+ case BSON.NUMBER:
+ return Double.longBitsToDouble( _input.getLong( record.valueOffset ) );
+ case BSON.OID:
+ return new ObjectId( _input.getIntBE( record.valueOffset ),
+ _input.getIntBE( record.valueOffset + 4 ),
+ _input.getIntBE( record.valueOffset + 8 ) );
+ case BSON.SYMBOL:
+ return new Symbol( _input.getUTF8String( record.valueOffset ) );
+ case BSON.CODE:
+ return new Code( _input.getUTF8String( record.valueOffset ) );
+ case BSON.STRING:
+ return _input.getUTF8String( record.valueOffset );
+ case BSON.CODE_W_SCOPE:
+ int strsize = _input.getInt( record.valueOffset + 4 );
+ String code = _input.getUTF8String( record.valueOffset + 4 );
+ BSONObject scope =
+ (BSONObject) _callback.createObject( _input.array(), record.valueOffset + 4 + 4 + strsize );
+ return new CodeWScope( code, scope );
+ case BSON.REF:
+ int csize = _input.getInt( record.valueOffset );
+ String ns = _input.getCString( record.valueOffset + 4 );
+ int oidOffset = record.valueOffset + csize + 4;
+ ObjectId oid = new ObjectId( _input.getIntBE( oidOffset ),
+ _input.getIntBE( oidOffset + 4 ),
+ _input.getIntBE( oidOffset + 8 ) );
+ return _callback.createDBRef( ns, oid );
+ case BSON.OBJECT:
+ return _callback.createObject( _input.array(), record.valueOffset );
+ case BSON.ARRAY:
+ return _callback.createArray( _input.array(), record.valueOffset );
+ case BSON.BINARY:
+ return readBinary( record.valueOffset );
+ case BSON.REGEX:
+ int patternCStringSize = sizeCString( record.valueOffset );
+ String pattern = _input.getCString( record.valueOffset );
+ String flags = _input.getCString( record.valueOffset + patternCStringSize + 1 );
+ return Pattern.compile( pattern, BSON.regexFlags( flags ) );
+ default:
+ throw new BSONException(
+ "Invalid type " + record.type + " for field " + getElementFieldName( record.offset ) );
+ }
+ }
+
+ private Object readBinary( int valueOffset ){
+ final int totalLen = _input.getInt( valueOffset );
+ valueOffset += 4;
+ final byte bType = _input.get( valueOffset );
+ valueOffset += 1;
+
+ byte[] bin;
+ switch ( bType ){
+ case BSON.B_GENERAL:{
+ bin = new byte[totalLen];
+ for ( int n = 0; n < totalLen; n++ ){
+ bin[n] = _input.get( valueOffset + n );
+ }
+ return bin;
+ }
+ case BSON.B_BINARY:
+ final int len = _input.getInt( valueOffset );
+ if ( len + 4 != totalLen )
+ throw new IllegalArgumentException(
+ "Bad Data Size; Binary Subtype 2. { actual len: " + len + " expected totalLen: " + totalLen
+ + "}" );
+ valueOffset += 4;
+ bin = new byte[len];
+ for ( int n = 0; n < len; n++ ){
+ bin[n] = _input.get( valueOffset + n );
+ }
+ return bin;
+ case BSON.B_UUID:
+ if ( totalLen != 16 )
+ throw new IllegalArgumentException(
+ "Bad Data Size; Binary Subtype 3 (UUID). { total length: " + totalLen + " != 16" );
+
+ long part1 = _input.getLong( valueOffset );
+ valueOffset += 8;
+ long part2 = _input.getLong( valueOffset );
+ return new UUID( part1, part2 );
+ }
+
+ bin = new byte[totalLen];
+ for ( int n = 0; n < totalLen; n++ ){
+ bin[n] = _input.get( valueOffset + n );
+ }
+ return bin;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ LazyBSONObject that = (LazyBSONObject) o;
+
+ return Arrays.equals(this._input.array(), that._input.array());
+ }
+
+ @Override
+ public int hashCode() {
+ return Arrays.hashCode(_input.array());
+ }
+
+ /**
+ * Returns a JSON serialization of this object
+ *
+ * @return JSON serialization
+ */
+ public String toString(){
+ return com.massivecraft.mcore3.lib.mongodb.util.JSON.serialize( this );
+ }
+
+ /**
+ * In a "normal" (aka not embedded) doc, this will be the offset of the first element.
+ *
+ * In an embedded doc because we use ByteBuffers to avoid unecessary copying the offset must be manually set in
+ * _doc_start_offset
+ */
+ final static int FIRST_ELMT_OFFSET = 4;
+
+ protected final int _doc_start_offset;
+
+ protected final BSONByteBuffer _input; // TODO - Guard this with synchronicity?
+ // callback is kept to create sub-objects on the fly
+ protected final LazyBSONCallback _callback;
+ @SuppressWarnings("unused")
+ private static final Logger log = Logger.getLogger( "org.bson.LazyBSONObject" );
+}
diff --git a/src/com/massivecraft/mcore3/lib/bson/LazyDBList.java b/src/com/massivecraft/mcore3/lib/bson/LazyDBList.java
new file mode 100644
index 00000000..655853b0
--- /dev/null
+++ b/src/com/massivecraft/mcore3/lib/bson/LazyDBList.java
@@ -0,0 +1,42 @@
+/**
+ *
+ */
+package com.massivecraft.mcore3.lib.bson;
+
+
+import com.massivecraft.mcore3.lib.bson.io.BSONByteBuffer;
+import com.massivecraft.mcore3.lib.mongodb.DBObject;
+import com.massivecraft.mcore3.lib.mongodb.util.JSON;
+
+/**
+ * @author scotthernandez
+ *
+ */
+public class LazyDBList extends LazyBSONList implements DBObject {
+ @SuppressWarnings("unused")
+ private static final long serialVersionUID = -4415279469780082174L;
+
+ public LazyDBList(byte[] data, LazyBSONCallback callback) { super(data, callback); }
+ public LazyDBList(byte[] data, int offset, LazyBSONCallback callback) { super(data, offset, callback); }
+ public LazyDBList(BSONByteBuffer buffer, LazyBSONCallback callback) { super(buffer, callback); }
+ public LazyDBList(BSONByteBuffer buffer, int offset, LazyBSONCallback callback) { super(buffer, offset, callback); }
+
+ /**
+ * Returns a JSON serialization of this object
+ * @return JSON serialization
+ */
+ @Override
+ public String toString(){
+ return JSON.serialize( this );
+ }
+
+ public boolean isPartialObject(){
+ return _isPartialObject;
+ }
+
+ public void markAsPartialObject(){
+ _isPartialObject = true;
+ }
+
+ private boolean _isPartialObject;
+}
diff --git a/src/com/massivecraft/mcore3/lib/bson/NewBSONDecoder.java b/src/com/massivecraft/mcore3/lib/bson/NewBSONDecoder.java
new file mode 100644
index 00000000..b574ca4f
--- /dev/null
+++ b/src/com/massivecraft/mcore3/lib/bson/NewBSONDecoder.java
@@ -0,0 +1,304 @@
+/**
+ * Copyright (C) 2012 10gen 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 com.massivecraft.mcore3.lib.bson;
+
+import com.massivecraft.mcore3.lib.bson.io.Bits;
+import com.massivecraft.mcore3.lib.bson.types.ObjectId;
+
+import static com.massivecraft.mcore3.lib.bson.BSON.*;
+
+// Java
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.DataInputStream;
+import java.io.UnsupportedEncodingException;
+
+/**
+ * A new implementation of the bson decoder.
+ */
+public class NewBSONDecoder implements BSONDecoder {
+
+ @Override
+ public BSONObject readObject(final byte [] pData) {
+ _length = pData.length;
+ final BasicBSONCallback c = new BasicBSONCallback();
+ decode(pData, c);
+ return (BSONObject)c.get();
+ }
+
+ @Override
+ public BSONObject readObject(final InputStream pIn) throws IOException {
+ // Slurp in the data and convert to a byte array.
+ _length = Bits.readInt(pIn);
+
+ if (_data == null || _data.length < _length) {
+ _data = new byte[_length];
+ }
+
+ (new DataInputStream(pIn)).readFully(_data, 4, (_length - 4));
+
+ return readObject(_data);
+ }
+
+ @Override
+ public int decode(final byte [] pData, final BSONCallback pCallback) {
+ _data = pData;
+ _pos = 4;
+ _callback = pCallback;
+ _decode();
+ return _length;
+ }
+
+ @Override
+ public int decode(final InputStream pIn, final BSONCallback pCallback) throws IOException {
+ _length = Bits.readInt(pIn);
+
+ if (_data == null || _data.length < _length) {
+ _data = new byte[_length];
+ }
+
+ (new DataInputStream(pIn)).readFully(_data, 4, (_length - 4));
+
+ return decode(_data, pCallback);
+ }
+
+ private final void _decode() {
+ _callback.objectStart();
+ while (decodeElement());
+ _callback.objectDone();
+ }
+
+ private final String readCstr() {
+ int length = 0;
+ final int offset = _pos;
+
+ while (_data[_pos++] != 0) length++;
+
+ try {
+ return new String(_data, offset, length, DEFAULT_ENCODING);
+ } catch (final UnsupportedEncodingException uee) {
+ return new String(_data, offset, length);
+ }
+ }
+
+ private final String readUtf8Str() {
+ final int length = Bits.readInt(_data, _pos);
+ _pos += 4;
+
+ if (length <= 0 || length > MAX_STRING) throw new BSONException("String invalid - corruption");
+
+ try {
+ final String str = new String(_data, _pos, (length - 1), DEFAULT_ENCODING);
+ _pos += length;
+ return str;
+
+ } catch (final UnsupportedEncodingException uee) {
+ throw new BSONException("What is in the db", uee);
+ }
+ }
+
+ private final Object _readBasicObject() {
+ _pos += 4;
+
+ final BSONCallback save = _callback;
+ final BSONCallback _basic = _callback.createBSONCallback();
+ _callback = _basic;
+ _basic.reset();
+ _basic.objectStart(false);
+
+ while( decodeElement() );
+ _callback = save;
+ return _basic.get();
+ }
+
+ private final void _binary(final String pName) {
+
+ final int totalLen = Bits.readInt(_data, _pos);
+ _pos += 4;
+
+ final byte bType = _data[_pos];
+ _pos += 1;
+
+ switch ( bType ){
+ case B_GENERAL: {
+ final byte [] data = new byte[totalLen];
+
+ System.arraycopy(_data, _pos, data, 0, totalLen);
+ _pos += totalLen;
+
+ _callback.gotBinary(pName, bType, data);
+ return;
+ }
+
+ case B_BINARY: {
+ final int len = Bits.readInt(_data, _pos);
+ _pos += 4;
+
+ if ( len + 4 != totalLen )
+ throw new IllegalArgumentException( "bad data size subtype 2 len: " + len + " totalLen: " + totalLen );
+
+ final byte [] data = new byte[len];
+ System.arraycopy(_data, _pos, data, 0, len);
+ _pos += len;
+ _callback.gotBinary(pName, bType, data);
+ return;
+ }
+
+ case B_UUID: {
+ if ( totalLen != 16 )
+ throw new IllegalArgumentException( "bad data size subtype 3 len: " + totalLen + " != 16");
+
+ final long part1 = Bits.readLong(_data, _pos);
+ _pos += 8;
+
+ final long part2 = Bits.readLong(_data, _pos);
+ _pos += 8;
+
+ _callback.gotUUID(pName, part1, part2);
+ return;
+ }
+ }
+
+ final byte [] data = new byte[totalLen];
+ System.arraycopy(_data, _pos, data, 0, totalLen);
+ _pos += totalLen;
+
+ _callback.gotBinary(pName, bType, data);
+ }
+
+ private final boolean decodeElement() {
+
+ final byte type = _data[_pos];
+ _pos += 1;
+
+ if (type == EOO) return false;
+
+ final String name = readCstr();
+
+ switch (type) {
+ case NULL: { _callback.gotNull(name); return true; }
+
+ case UNDEFINED: { _callback.gotUndefined(name); return true; }
+
+ case BOOLEAN: { _callback.gotBoolean(name, (_data[_pos] > 0)); _pos += 1; return true; }
+
+ case NUMBER: { _callback.gotDouble(name, Double.longBitsToDouble(Bits.readLong(_data, _pos))); _pos += 8; return true; }
+
+ case NUMBER_INT: { _callback.gotInt(name, Bits.readInt(_data, _pos)); _pos += 4; return true; }
+
+ case NUMBER_LONG: {
+ _callback.gotLong(name, Bits.readLong(_data, _pos));
+ _pos += 8;
+ return true;
+ }
+
+ case SYMBOL: { _callback.gotSymbol(name, readUtf8Str()); return true; }
+ case STRING: { _callback.gotString(name, readUtf8Str()); return true; }
+
+ case OID: {
+ // OID is stored as big endian
+
+ final int p1 = Bits.readIntBE(_data, _pos);
+ _pos += 4;
+
+ final int p2 = Bits.readIntBE(_data, _pos);
+ _pos += 4;
+
+ final int p3 = Bits.readIntBE(_data, _pos);
+ _pos += 4;
+
+ _callback.gotObjectId(name , new ObjectId(p1, p2, p3));
+ return true;
+ }
+
+ case REF: {
+ _pos += 4;
+
+ final String ns = readCstr();
+
+ final int p1 = Bits.readInt(_data, _pos);
+ _pos += 4;
+
+ final int p2 = Bits.readInt(_data, _pos);
+ _pos += 4;
+
+ final int p3 = Bits.readInt(_data, _pos);
+ _pos += 4;
+
+ _callback.gotDBRef(name , ns, new ObjectId(p1, p2, p3));
+
+ return true;
+ }
+
+ case DATE: { _callback.gotDate(name , Bits.readLong(_data, _pos)); _pos += 8; return true; }
+
+
+ case REGEX: {
+ _callback.gotRegex(name, readCstr(), readCstr());
+ return true;
+ }
+
+ case BINARY: { _binary(name); return true; }
+
+ case CODE: { _callback.gotCode(name, readUtf8Str()); return true; }
+
+ case CODE_W_SCOPE: {
+ _pos += 4;
+ _callback.gotCodeWScope(name, readUtf8Str(), _readBasicObject());
+ return true;
+ }
+
+ case ARRAY:
+ _pos += 4;
+ _callback.arrayStart(name);
+ while (decodeElement());
+ _callback.arrayDone();
+ return true;
+
+ case OBJECT:
+ _pos += 4;
+ _callback.objectStart(name);
+ while (decodeElement());
+ _callback.objectDone();
+ return true;
+
+ case TIMESTAMP:
+ int i = Bits.readInt(_data, _pos);
+ _pos += 4;
+
+ int time = Bits.readInt(_data, _pos);
+ _pos += 4;
+
+ _callback.gotTimestamp(name, time, i);
+ return true;
+
+ case MINKEY: _callback.gotMinKey(name); return true;
+ case MAXKEY: _callback.gotMaxKey(name); return true;
+
+ default: throw new UnsupportedOperationException( "BSONDecoder doesn't understand type : " + type + " name: " + name );
+ }
+ }
+
+ private static final int MAX_STRING = ( 32 * 1024 * 1024 );
+ private static final String DEFAULT_ENCODING = "UTF-8";
+
+ private byte [] _data;
+ private int _length;
+ private int _pos = 0;
+ private BSONCallback _callback;
+}
+
diff --git a/src/com/massivecraft/mcore3/lib/bson/Transformer.java b/src/com/massivecraft/mcore3/lib/bson/Transformer.java
new file mode 100644
index 00000000..1bee6960
--- /dev/null
+++ b/src/com/massivecraft/mcore3/lib/bson/Transformer.java
@@ -0,0 +1,27 @@
+// Transformer.java
+
+/**
+ * Copyright (C) 2008 10gen 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 com.massivecraft.mcore3.lib.bson;
+
+public interface Transformer {
+
+ /**
+ * @return the new object. return passed in object if no change
+ */
+ public Object transform( Object o );
+}
diff --git a/src/com/massivecraft/mcore3/lib/bson/io/BSONByteBuffer.java b/src/com/massivecraft/mcore3/lib/bson/io/BSONByteBuffer.java
new file mode 100644
index 00000000..49a1e026
--- /dev/null
+++ b/src/com/massivecraft/mcore3/lib/bson/io/BSONByteBuffer.java
@@ -0,0 +1,143 @@
+/**
+ * Copyright (C) 2008-2011 10gen 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 com.massivecraft.mcore3.lib.bson.io;
+
+import com.massivecraft.mcore3.lib.bson.*;
+
+import java.io.*;
+import java.nio.*;
+
+/**
+ * Pseudo byte buffer, delegates as it is too hard to properly override / extend the ByteBuffer API
+ *
+ * @author brendan
+ */
+public class BSONByteBuffer {
+
+ private BSONByteBuffer( ByteBuffer buf ){
+ this.buf = buf;
+ buf.order( ByteOrder.LITTLE_ENDIAN );
+ }
+
+ public static BSONByteBuffer wrap( byte[] bytes, int offset, int length ){
+ return new BSONByteBuffer( ByteBuffer.wrap( bytes, offset, length ) );
+ }
+
+ public static BSONByteBuffer wrap( byte[] bytes ){
+ return new BSONByteBuffer( ByteBuffer.wrap( bytes ) );
+ }
+
+ public byte get( int i ){
+ return buf.get(i);
+ }
+
+ public ByteBuffer get( byte[] bytes, int offset, int length ){
+ return buf.get(bytes, offset, length);
+ }
+
+ public ByteBuffer get( byte[] bytes ){
+ return buf.get(bytes);
+ }
+
+ public byte[] array(){
+ return buf.array();
+ }
+
+ public String toString(){
+ return buf.toString();
+ }
+
+ public int hashCode(){
+ return buf.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ BSONByteBuffer that = (BSONByteBuffer) o;
+
+ if (buf != null ? !buf.equals(that.buf) : that.buf != null) return false;
+
+ return true;
+ }
+
+ /**
+ * Gets a Little Endian Integer
+ *
+ * @param i Index to read from
+ *
+ * @return
+ */
+ public int getInt( int i ){
+ return getIntLE( i );
+ }
+
+ public int getIntLE( int i ){
+ int x = 0;
+ x |= ( 0xFF & buf.get( i + 0 ) ) << 0;
+ x |= ( 0xFF & buf.get( i + 1 ) ) << 8;
+ x |= ( 0xFF & buf.get( i + 2 ) ) << 16;
+ x |= ( 0xFF & buf.get( i + 3 ) ) << 24;
+ return x;
+ }
+
+ public int getIntBE( int i ){
+ int x = 0;
+ x |= ( 0xFF & buf.get( i + 0 ) ) << 24;
+ x |= ( 0xFF & buf.get( i + 1 ) ) << 16;
+ x |= ( 0xFF & buf.get( i + 2 ) ) << 8;
+ x |= ( 0xFF & buf.get( i + 3 ) ) << 0;
+ return x;
+ }
+
+ public long getLong( int i ){
+ return buf.getLong( i );
+ }
+
+ public String getCString(int offset) {
+ int end = offset;
+ while (get(end) != 0) {
+ ++end;
+ }
+ int len = end - offset;
+ return new String(array(), offset, len);
+ }
+
+ public String getUTF8String(int valueOffset) {
+ int size = getInt(valueOffset) - 1;
+ try {
+ return new String(array(), valueOffset + 4, size, "UTF-8");
+ } catch (UnsupportedEncodingException e) {
+ throw new BSONException( "Cannot decode string as UTF-8." );
+ }
+ }
+
+ public Buffer position( int i ){
+ return buf.position(i);
+ }
+
+ public Buffer reset(){
+ return buf.reset();
+ }
+
+ public int size(){
+ return getInt( 0 );
+ }
+
+ protected ByteBuffer buf;
+}
diff --git a/src/com/massivecraft/mcore3/lib/bson/io/BasicOutputBuffer.java b/src/com/massivecraft/mcore3/lib/bson/io/BasicOutputBuffer.java
new file mode 100644
index 00000000..de215ea9
--- /dev/null
+++ b/src/com/massivecraft/mcore3/lib/bson/io/BasicOutputBuffer.java
@@ -0,0 +1,119 @@
+// BasicOutputBuffer.java
+
+/**
+ * Copyright (C) 2008 10gen 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 com.massivecraft.mcore3.lib.bson.io;
+
+import java.io.*;
+
+public class BasicOutputBuffer extends OutputBuffer {
+
+ @Override
+ public void write(byte[] b){
+ write( b , 0 , b.length );
+ }
+
+ @Override
+ public void write(byte[] b, int off, int len){
+ _ensure( len );
+ System.arraycopy( b , off , _buffer , _cur , len );
+ _cur += len;
+ _size = Math.max( _cur , _size );
+ }
+ @Override
+ public void write(int b){
+ _ensure(1);
+ _buffer[_cur++] = (byte)(0xFF&b);
+ _size = Math.max( _cur , _size );
+ }
+
+ @Override
+ public int getPosition(){
+ return _cur;
+ }
+ @Override
+ public void setPosition( int position ){
+ _cur = position;
+ }
+
+ @Override
+ public void seekEnd(){
+ _cur = _size;
+ }
+ @Override
+ public void seekStart(){
+ _cur = 0;
+ }
+
+ /**
+ * @return size of data so far
+ */
+ @Override
+ public int size(){
+ return _size;
+ }
+
+ /**
+ * @return bytes written
+ */
+ @Override
+ public int pipe( OutputStream out )
+ throws IOException {
+ out.write( _buffer , 0 , _size );
+ return _size;
+ }
+
+ /**
+ * @return bytes written
+ */
+ public int pipe( DataOutput out )
+ throws IOException {
+ out.write( _buffer , 0 , _size );
+ return _size;
+ }
+
+
+ void _ensure( int more ){
+ final int need = _cur + more;
+ if ( need < _buffer.length )
+ return;
+
+ int newSize = _buffer.length*2;
+ if ( newSize <= need )
+ newSize = need + 128;
+
+ byte[] n = new byte[newSize];
+ System.arraycopy( _buffer , 0 , n , 0 , _size );
+ _buffer = n;
+ }
+
+ @Override
+ public String asString(){
+ return new String( _buffer , 0 , _size );
+ }
+
+ @Override
+ public String asString( String encoding )
+ throws UnsupportedEncodingException {
+ return new String( _buffer , 0 , _size , encoding );
+ }
+
+
+ private int _cur;
+ private int _size;
+ private byte[] _buffer = new byte[512];
+}
diff --git a/src/com/massivecraft/mcore3/lib/bson/io/Bits.java b/src/com/massivecraft/mcore3/lib/bson/io/Bits.java
new file mode 100644
index 00000000..afb63c12
--- /dev/null
+++ b/src/com/massivecraft/mcore3/lib/bson/io/Bits.java
@@ -0,0 +1,115 @@
+// Bits.java
+
+/**
+ * Copyright (C) 2008 10gen 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 com.massivecraft.mcore3.lib.bson.io;
+
+import java.io.*;
+
+public class Bits {
+
+ public static void readFully( InputStream in, byte[] b )
+ throws IOException {
+ readFully( in , b , b.length );
+ }
+
+ public static void readFully( InputStream in, byte[] b, int length )
+ throws IOException {
+ readFully(in, b, 0, length);
+ }
+
+ public static void readFully( InputStream in, byte[] b, int startOffset, int length )
+ throws IOException {
+
+ if (b.length - startOffset > length) {
+ throw new IllegalArgumentException("Buffer is too small");
+ }
+
+ int offset = startOffset;
+ int toRead = length;
+ while ( toRead > 0 ){
+ int bytesRead = in.read( b, offset , toRead );
+ if ( bytesRead < 0 )
+ throw new EOFException();
+ toRead -= bytesRead;
+ offset += bytesRead;
+ }
+ }
+
+ public static int readInt( InputStream in )
+ throws IOException {
+ return readInt( in , new byte[4] );
+ }
+
+ public static int readInt( InputStream in , byte[] data )
+ throws IOException {
+ readFully(in, data, 4);
+ return readInt(data);
+ }
+
+ public static int readInt( byte[] data ) {
+ return readInt( data , 0 );
+ }
+
+ public static int readInt( byte[] data , int offset ) {
+ int x = 0;
+ x |= ( 0xFF & data[offset+0] ) << 0;
+ x |= ( 0xFF & data[offset+1] ) << 8;
+ x |= ( 0xFF & data[offset+2] ) << 16;
+ x |= ( 0xFF & data[offset+3] ) << 24;
+ return x;
+ }
+
+ public static int readIntBE( byte[] data , int offset ) {
+ int x = 0;
+ x |= ( 0xFF & data[offset+0] ) << 24;
+ x |= ( 0xFF & data[offset+1] ) << 16;
+ x |= ( 0xFF & data[offset+2] ) << 8;
+ x |= ( 0xFF & data[offset+3] ) << 0;
+ return x;
+ }
+
+ public static long readLong( InputStream in )
+ throws IOException {
+ return readLong( in , new byte[8] );
+ }
+
+
+ public static long readLong( InputStream in , byte[] data )
+ throws IOException {
+ readFully(in, data, 8);
+ return readLong(data);
+ }
+
+ public static long readLong( byte[] data ) {
+ return readLong( data , 0 );
+ }
+
+ public static long readLong( byte[] data , int offset ) {
+ long x = 0;
+ x |= ( 0xFFL & data[offset+0] ) << 0;
+ x |= ( 0xFFL & data[offset+1] ) << 8;
+ x |= ( 0xFFL & data[offset+2] ) << 16;
+ x |= ( 0xFFL & data[offset+3] ) << 24;
+ x |= ( 0xFFL & data[offset+4] ) << 32;
+ x |= ( 0xFFL & data[offset+5] ) << 40;
+ x |= ( 0xFFL & data[offset+6] ) << 48;
+ x |= ( 0xFFL & data[offset+7] ) << 56;
+ return x;
+ }
+}
diff --git a/src/com/massivecraft/mcore3/lib/bson/io/OutputBuffer.java b/src/com/massivecraft/mcore3/lib/bson/io/OutputBuffer.java
new file mode 100644
index 00000000..12b6c07b
--- /dev/null
+++ b/src/com/massivecraft/mcore3/lib/bson/io/OutputBuffer.java
@@ -0,0 +1,159 @@
+// OutputBuffer.java
+
+/**
+ * Copyright (C) 2008 10gen 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 com.massivecraft.mcore3.lib.bson.io;
+
+import java.io.*;
+import java.security.*;
+
+public abstract class OutputBuffer extends OutputStream {
+
+ public abstract void write(byte[] b);
+ public abstract void write(byte[] b, int off, int len);
+ public abstract void write(int b);
+
+ public abstract int getPosition();
+ public abstract void setPosition( int position );
+
+ public abstract void seekEnd();
+ public abstract void seekStart();
+
+ /**
+ * @return size of data so far
+ */
+ public abstract int size();
+
+ /**
+ * @return bytes written
+ */
+ public abstract int pipe( OutputStream out )
+ throws IOException;
+
+ /**
+ * mostly for testing
+ */
+ public byte [] toByteArray(){
+ try {
+ final ByteArrayOutputStream bout = new ByteArrayOutputStream( size() );
+ pipe( bout );
+ return bout.toByteArray();
+ }
+ catch ( IOException ioe ){
+ throw new RuntimeException( "should be impossible" , ioe );
+ }
+ }
+
+ public String asString(){
+ return new String( toByteArray() );
+ }
+
+ public String asString( String encoding )
+ throws UnsupportedEncodingException {
+ return new String( toByteArray() , encoding );
+ }
+
+
+ public String hex(){
+ final StringBuilder buf = new StringBuilder();
+ try {
+ pipe( new OutputStream(){
+ public void write( int b ){
+ String s = Integer.toHexString(0xff & b);
+
+ if (s.length() < 2)
+ buf.append("0");
+ buf.append(s);
+ }
+ }
+ );
+ }
+ catch ( IOException ioe ){
+ throw new RuntimeException( "impossible" );
+ }
+ return buf.toString();
+ }
+
+ public String md5(){
+ final MessageDigest md5 ;
+ try {
+ md5 = MessageDigest.getInstance("MD5");
+ }
+ catch (NoSuchAlgorithmException e) {
+ throw new RuntimeException("Error - this implementation of Java doesn't support MD5.");
+ }
+ md5.reset();
+
+ try {
+ pipe( new OutputStream(){
+ public void write( byte[] b , int off , int len ){
+ md5.update( b , off , len );
+ }
+
+ public void write( int b ){
+ md5.update( (byte)(b&0xFF) );
+ }
+ }
+ );
+ }
+ catch ( IOException ioe ){
+ throw new RuntimeException( "impossible" );
+ }
+
+ return com.massivecraft.mcore3.lib.mongodb.util.Util.toHex( md5.digest() );
+ }
+
+ public void writeInt( int x ){
+ write( x >> 0 );
+ write( x >> 8 );
+ write( x >> 16 );
+ write( x >> 24 );
+ }
+
+ public void writeIntBE( int x ){
+ write( x >> 24 );
+ write( x >> 16 );
+ write( x >> 8 );
+ write( x );
+ }
+
+ public void writeInt( int pos , int x ){
+ final int save = getPosition();
+ setPosition( pos );
+ writeInt( x );
+ setPosition( save );
+ }
+
+ public void writeLong( long x ){
+ write( (byte)(0xFFL & ( x >> 0 ) ) );
+ write( (byte)(0xFFL & ( x >> 8 ) ) );
+ write( (byte)(0xFFL & ( x >> 16 ) ) );
+ write( (byte)(0xFFL & ( x >> 24 ) ) );
+ write( (byte)(0xFFL & ( x >> 32 ) ) );
+ write( (byte)(0xFFL & ( x >> 40 ) ) );
+ write( (byte)(0xFFL & ( x >> 48 ) ) );
+ write( (byte)(0xFFL & ( x >> 56 ) ) );
+ }
+
+ public void writeDouble( double x ){
+ writeLong( Double.doubleToRawLongBits( x ) );
+ }
+
+ public String toString(){
+ return getClass().getName() + " size: " + size() + " pos: " + getPosition() ;
+ }
+}
diff --git a/src/com/massivecraft/mcore3/lib/bson/io/PoolOutputBuffer.java b/src/com/massivecraft/mcore3/lib/bson/io/PoolOutputBuffer.java
new file mode 100644
index 00000000..4062744c
--- /dev/null
+++ b/src/com/massivecraft/mcore3/lib/bson/io/PoolOutputBuffer.java
@@ -0,0 +1,239 @@
+// PoolOutputBuffer.java
+
+/**
+ * Copyright (C) 2008 10gen 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 com.massivecraft.mcore3.lib.bson.io;
+
+
+import java.io.*;
+import java.util.*;
+
+public class PoolOutputBuffer extends OutputBuffer {
+
+ public static final int BUF_SIZE = 1024 * 16;
+
+ public PoolOutputBuffer(){
+ reset();
+ }
+
+ public void reset(){
+ _cur.reset();
+ _end.reset();
+
+ for ( int i=0; i<_fromPool.size(); i++ )
+ _extra.done( _fromPool.get(i) );
+ _fromPool.clear();
+ }
+
+ public int getPosition(){
+ return _cur.pos();
+ }
+
+ public void setPosition( int position ){
+ _cur.reset( position );
+ }
+
+ public void seekEnd(){
+ _cur.reset( _end );
+ }
+
+ public void seekStart(){
+ _cur.reset();
+ }
+
+
+ public int size(){
+ return _end.pos();
+ }
+
+ public void write(byte[] b){
+ write( b , 0 , b.length );
+ }
+
+ public void write(byte[] b, int off, int len){
+ while ( len > 0 ){
+ byte[] bs = _cur();
+ int space = Math.min( bs.length - _cur.y , len );
+ System.arraycopy( b , off , bs , _cur.y , space );
+ _cur.inc( space );
+ len -= space;
+ off += space;
+ _afterWrite();
+ }
+ }
+
+ public void write(int b){
+ byte[] bs = _cur();
+ bs[_cur.getAndInc()] = (byte)(b&0xFF);
+ _afterWrite();
+ }
+
+ void _afterWrite(){
+
+ if ( _cur.pos() < _end.pos() ){
+ // we're in the middle of the total space
+ // just need to make sure we're not at the end of a buffer
+ if ( _cur.y == BUF_SIZE )
+ _cur.nextBuffer();
+ return;
+ }
+
+ _end.reset( _cur );
+
+ if ( _end.y < BUF_SIZE )
+ return;
+
+ _fromPool.add( _extra.get() );
+ _end.nextBuffer();
+ _cur.reset( _end );
+ }
+
+ byte[] _cur(){
+ return _get( _cur.x );
+ }
+
+ byte[] _get( int z ){
+ if ( z < 0 )
+ return _mine;
+ return _fromPool.get(z);
+ }
+
+ public int pipe( final OutputStream out )
+ throws IOException {
+
+ if ( out == null )
+ throw new NullPointerException( "out is null" );
+
+ int total = 0;
+
+ for ( int i=-1; i<_fromPool.size(); i++ ){
+ final byte[] b = _get( i );
+ final int amt = _end.len( i );
+ out.write( b , 0 , amt );
+ total += amt;
+ }
+
+ return total;
+ }
+
+ static class Position {
+ Position(){
+ reset();
+ }
+
+ void reset(){
+ x = -1;
+ y = 0;
+ }
+
+ void reset( Position other ){
+ x = other.x;
+ y = other.y;
+ }
+
+ void reset( int pos ){
+ x = ( pos / BUF_SIZE ) - 1;
+ y = pos % BUF_SIZE;
+ }
+
+ int pos(){
+ return ( ( x + 1 ) * BUF_SIZE ) + y;
+ }
+
+ int getAndInc(){
+ return y++;
+ }
+
+ void inc( int amt ){
+ y += amt;
+ if ( y > BUF_SIZE )
+ throw new IllegalArgumentException( "something is wrong" );
+ }
+
+ void nextBuffer(){
+ if ( y != BUF_SIZE )
+ throw new IllegalArgumentException( "broken" );
+ x++;
+ y = 0;
+ }
+
+ int len( int which ){
+ if ( which < x )
+ return BUF_SIZE;
+ return y;
+ }
+
+ public String toString(){
+ return x + "," + y;
+ }
+
+ int x; // which buffer -1 == _mine
+ int y; // position in buffer
+ }
+
+ public String asAscii(){
+ if ( _fromPool.size() > 0 )
+ return super.asString();
+
+ final int m = size();
+ final char c[] = m < _chars.length ? _chars : new char[m];
+
+ for ( int i=0; i 0 )
+ return super.asString( encoding );
+
+ if ( encoding.equals( DEFAULT_ENCODING_1 ) || encoding.equals( DEFAULT_ENCODING_2) ){
+ try {
+ return _encoding.decode( _mine , 0 , size() );
+ }
+ catch ( IOException ioe ){
+ // we failed, fall back
+ }
+ }
+ return new String( _mine , 0 , size() , encoding );
+ }
+
+
+ final byte[] _mine = new byte[BUF_SIZE];
+ final char[] _chars = new char[BUF_SIZE];
+ final List _fromPool = new ArrayList();
+ final UTF8Encoding _encoding = new UTF8Encoding();
+
+ private static final String DEFAULT_ENCODING_1 = "UTF-8";
+ private static final String DEFAULT_ENCODING_2 = "UTF8";
+
+ private final Position _cur = new Position();
+ private final Position _end = new Position();
+
+ private static com.massivecraft.mcore3.lib.bson.util.SimplePool _extra =
+ new com.massivecraft.mcore3.lib.bson.util.SimplePool( ( 1024 * 1024 * 10 ) / BUF_SIZE ){
+
+ protected byte[] createNew(){
+ return new byte[BUF_SIZE];
+ }
+
+ };
+}
diff --git a/src/com/massivecraft/mcore3/lib/bson/io/UTF8Encoding.java b/src/com/massivecraft/mcore3/lib/bson/io/UTF8Encoding.java
new file mode 100644
index 00000000..bcee4099
--- /dev/null
+++ b/src/com/massivecraft/mcore3/lib/bson/io/UTF8Encoding.java
@@ -0,0 +1,202 @@
+// UTF8Encoding.java
+
+
+/**
+ * from postgresql jdbc driver:
+ * postgresql-jdbc-9.0-801.src
+
+
+Copyright (c) 1997-2008, PostgreSQL Global Development Group
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+2. Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+3. Neither the name of the PostgreSQL Global Development Group nor the names
+ of its contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
+
+ */
+
+/*-------------------------------------------------------------------------
+*
+* Copyright (c) 2003-2008, PostgreSQL Global Development Group
+*
+* IDENTIFICATION
+*
+*
+*-------------------------------------------------------------------------
+*/
+
+//package org.postgresql.core;
+package com.massivecraft.mcore3.lib.bson.io;
+
+import java.io.IOException;
+import java.text.MessageFormat;
+
+class UTF8Encoding {
+
+ private static final int MIN_2_BYTES = 0x80;
+ private static final int MIN_3_BYTES = 0x800;
+ private static final int MIN_4_BYTES = 0x10000;
+ private static final int MAX_CODE_POINT = 0x10ffff;
+
+ private char[] decoderArray = new char[1024];
+
+ // helper for decode
+ private final static void checkByte(int ch, int pos, int len) throws IOException {
+ if ((ch & 0xc0) != 0x80)
+ throw new IOException(MessageFormat.format("Illegal UTF-8 sequence: byte {0} of {1} byte sequence is not 10xxxxxx: {2}",
+ new Object[] { new Integer(pos), new Integer(len), new Integer(ch) }));
+ }
+
+ private final static void checkMinimal(int ch, int minValue) throws IOException {
+ if (ch >= minValue)
+ return;
+
+ int actualLen;
+ switch (minValue) {
+ case MIN_2_BYTES:
+ actualLen = 2;
+ break;
+ case MIN_3_BYTES:
+ actualLen = 3;
+ break;
+ case MIN_4_BYTES:
+ actualLen = 4;
+ break;
+ default:
+ throw new IllegalArgumentException("unexpected minValue passed to checkMinimal: " + minValue);
+ }
+
+ int expectedLen;
+ if (ch < MIN_2_BYTES)
+ expectedLen = 1;
+ else if (ch < MIN_3_BYTES)
+ expectedLen = 2;
+ else if (ch < MIN_4_BYTES)
+ expectedLen = 3;
+ else
+ throw new IllegalArgumentException("unexpected ch passed to checkMinimal: " + ch);
+
+ throw new IOException(MessageFormat.format("Illegal UTF-8 sequence: {0} bytes used to encode a {1} byte value: {2}",
+ new Object[] { new Integer(actualLen), new Integer(expectedLen), new Integer(ch) }));
+ }
+
+ /**
+ * Custom byte[] -> String conversion routine for UTF-8 only.
+ * This is about twice as fast as using the String(byte[],int,int,String)
+ * ctor, at least under JDK 1.4.2. The extra checks for illegal representations
+ * add about 10-15% overhead, but they seem worth it given the number of SQL_ASCII
+ * databases out there.
+ *
+ * @param data the array containing UTF8-encoded data
+ * @param offset the offset of the first byte in data
to decode from
+ * @param length the number of bytes to decode
+ * @return a decoded string
+ * @throws IOException if something goes wrong
+ */
+ public synchronized String decode(byte[] data, int offset, int length) throws IOException {
+ char[] cdata = decoderArray;
+ if (cdata.length < length)
+ cdata = decoderArray = new char[length];
+
+ int in = offset;
+ int out = 0;
+ int end = length + offset;
+
+ try
+ {
+ while (in < end)
+ {
+ int ch = data[in++] & 0xff;
+
+ // Convert UTF-8 to 21-bit codepoint.
+ if (ch < 0x80) {
+ // 0xxxxxxx -- length 1.
+ } else if (ch < 0xc0) {
+ // 10xxxxxx -- illegal!
+ throw new IOException(MessageFormat.format("Illegal UTF-8 sequence: initial byte is {0}: {1}",
+ new Object[] { "10xxxxxx", new Integer(ch) }));
+ } else if (ch < 0xe0) {
+ // 110xxxxx 10xxxxxx
+ ch = ((ch & 0x1f) << 6);
+ checkByte(data[in], 2, 2);
+ ch = ch | (data[in++] & 0x3f);
+ checkMinimal(ch, MIN_2_BYTES);
+ } else if (ch < 0xf0) {
+ // 1110xxxx 10xxxxxx 10xxxxxx
+ ch = ((ch & 0x0f) << 12);
+ checkByte(data[in], 2, 3);
+ ch = ch | ((data[in++] & 0x3f) << 6);
+ checkByte(data[in], 3, 3);
+ ch = ch | (data[in++] & 0x3f);
+ checkMinimal(ch, MIN_3_BYTES);
+ } else if (ch < 0xf8) {
+ // 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
+ ch = ((ch & 0x07) << 18);
+ checkByte(data[in], 2, 4);
+ ch = ch | ((data[in++] & 0x3f) << 12);
+ checkByte(data[in], 3, 4);
+ ch = ch | ((data[in++] & 0x3f) << 6);
+ checkByte(data[in], 4, 4);
+ ch = ch | (data[in++] & 0x3f);
+ checkMinimal(ch, MIN_4_BYTES);
+ } else {
+ throw new IOException(MessageFormat.format("Illegal UTF-8 sequence: initial byte is {0}: {1}",
+ new Object[] { "11111xxx", new Integer(ch) }));
+ }
+
+ if (ch > MAX_CODE_POINT)
+ throw new IOException(MessageFormat.format("Illegal UTF-8 sequence: final value is out of range: {0}",
+ new Integer(ch)));
+
+ // Convert 21-bit codepoint to Java chars:
+ // 0..ffff are represented directly as a single char
+ // 10000..10ffff are represented as a "surrogate pair" of two chars
+ // See: http://java.sun.com/developer/technicalArticles/Intl/Supplementary/
+
+ if (ch > 0xffff) {
+ // Use a surrogate pair to represent it.
+ ch -= 0x10000; // ch is now 0..fffff (20 bits)
+ cdata[out++] = (char) (0xd800 + (ch >> 10)); // top 10 bits
+ cdata[out++] = (char) (0xdc00 + (ch & 0x3ff)); // bottom 10 bits
+ } else if (ch >= 0xd800 && ch < 0xe000) {
+ // Not allowed to encode the surrogate range directly.
+ throw new IOException(MessageFormat.format("Illegal UTF-8 sequence: final value is a surrogate value: {0}",
+ new Integer(ch)));
+ } else {
+ // Normal case.
+ cdata[out++] = (char) ch;
+ }
+ }
+ }
+ catch (ArrayIndexOutOfBoundsException a)
+ {
+ throw new IOException("Illegal UTF-8 sequence: multibyte sequence was truncated");
+ }
+
+ // Check if we ran past the end without seeing an exception.
+ if (in > end)
+ throw new IOException("Illegal UTF-8 sequence: multibyte sequence was truncated");
+
+ return new String(cdata, 0, out);
+ }
+}
diff --git a/src/com/massivecraft/mcore3/lib/bson/io/package.html b/src/com/massivecraft/mcore3/lib/bson/io/package.html
new file mode 100644
index 00000000..8bf6c44f
--- /dev/null
+++ b/src/com/massivecraft/mcore3/lib/bson/io/package.html
@@ -0,0 +1,3 @@
+
+ Contains classes implementing I/O operations used by BSON objects.
+
diff --git a/src/com/massivecraft/mcore3/lib/bson/package.html b/src/com/massivecraft/mcore3/lib/bson/package.html
new file mode 100644
index 00000000..d10d9f8a
--- /dev/null
+++ b/src/com/massivecraft/mcore3/lib/bson/package.html
@@ -0,0 +1,3 @@
+
+ Contains the base BSON classes and Encoder/Decoder.
+
diff --git a/src/com/massivecraft/mcore3/lib/bson/types/BSONTimestamp.java b/src/com/massivecraft/mcore3/lib/bson/types/BSONTimestamp.java
new file mode 100644
index 00000000..a74d9979
--- /dev/null
+++ b/src/com/massivecraft/mcore3/lib/bson/types/BSONTimestamp.java
@@ -0,0 +1,76 @@
+// BSONTimestamp.java
+
+/**
+ * Copyright (C) 2008 10gen 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 com.massivecraft.mcore3.lib.bson.types;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * this is used for internal increment values.
+ * for storing normal dates in MongoDB, you should use java.util.Date
+ * time is seconds since epoch
+ * inc is an ordinal
+ */
+public class BSONTimestamp implements Serializable {
+
+ private static final long serialVersionUID = -3268482672267936464L;
+
+ static final boolean D = Boolean.getBoolean( "DEBUG.DBTIMESTAMP" );
+
+ public BSONTimestamp(){
+ _inc = 0;
+ _time = null;
+ }
+
+ public BSONTimestamp(int time, int inc ){
+ _time = new Date( time * 1000L );
+ _inc = inc;
+ }
+
+ /**
+ * @return get time in seconds since epoch
+ */
+ public int getTime(){
+ if ( _time == null )
+ return 0;
+ return (int)(_time.getTime() / 1000);
+ }
+
+ public int getInc(){
+ return _inc;
+ }
+
+ public String toString(){
+ return "TS time:" + _time + " inc:" + _inc;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == this)
+ return true;
+ if (obj instanceof BSONTimestamp) {
+ BSONTimestamp t2 = (BSONTimestamp) obj;
+ return getTime() == t2.getTime() && getInc() == t2.getInc();
+ }
+ return false;
+ }
+
+ final int _inc;
+ final Date _time;
+}
diff --git a/src/com/massivecraft/mcore3/lib/bson/types/BasicBSONList.java b/src/com/massivecraft/mcore3/lib/bson/types/BasicBSONList.java
new file mode 100644
index 00000000..d2336dee
--- /dev/null
+++ b/src/com/massivecraft/mcore3/lib/bson/types/BasicBSONList.java
@@ -0,0 +1,165 @@
+// BasicBSONList.java
+
+/**
+ * Copyright (C) 2008 10gen 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 com.massivecraft.mcore3.lib.bson.types;
+
+import com.massivecraft.mcore3.lib.bson.*;
+import com.massivecraft.mcore3.lib.bson.util.StringRangeSet;
+
+import java.util.*;
+
+/**
+ * Utility class to allow array DBObject
s to be created.
+ *
+ * Note: MongoDB will also create arrays from java.util.List
s.
+ *
+ *
+ *
+ * DBObject obj = new BasicBSONList();
+ * obj.put( "0", value1 );
+ * obj.put( "4", value2 );
+ * obj.put( 2, value3 );
+ *
+ * This simulates the array [ value1, null, value3, null, value2 ] by creating the
+ * DBObject
{ "0" : value1, "1" : null, "2" : value3, "3" : null, "4" : value2 }
.
+ *
+ *
+ * BasicBSONList only supports numeric keys. Passing strings that cannot be converted to ints will cause an
+ * IllegalArgumentException.
+ *
+ * BasicBSONList list = new BasicBSONList();
+ * list.put("1", "bar"); // ok
+ * list.put("1E1", "bar"); // throws exception
+ *
+ *
+ */
+public class BasicBSONList extends ArrayList implements BSONObject {
+
+ private static final long serialVersionUID = -4415279469780082174L;
+
+ public BasicBSONList() { }
+
+ /**
+ * Puts a value at an index.
+ * For interface compatibility. Must be passed a String that is parsable to an int.
+ * @param key the index at which to insert the value
+ * @param v the value to insert
+ * @return the value
+ * @throws IllegalArgumentException if key
cannot be parsed into an int
+ */
+ public Object put( String key , Object v ){
+ return put(_getInt( key ), v);
+ }
+
+ /**
+ * Puts a value at an index.
+ * This will fill any unset indexes less than index
with null
.
+ * @param key the index at which to insert the value
+ * @param v the value to insert
+ * @return the value
+ */
+ public Object put( int key, Object v ) {
+ while ( key >= size() )
+ add( null );
+ set( key , v );
+ return v;
+ }
+
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ public void putAll( Map m ){
+ for ( Map.Entry entry : (Set)m.entrySet() ){
+ put( entry.getKey().toString() , entry.getValue() );
+ }
+ }
+
+ public void putAll( BSONObject o ){
+ for ( String k : o.keySet() ){
+ put( k , o.get( k ) );
+ }
+ }
+
+ /**
+ * Gets a value at an index.
+ * For interface compatibility. Must be passed a String that is parsable to an int.
+ * @param key the index
+ * @return the value, if found, or null
+ * @throws IllegalArgumentException if key
cannot be parsed into an int
+ */
+ public Object get( String key ){
+ int i = _getInt( key );
+ if ( i < 0 )
+ return null;
+ if ( i >= size() )
+ return null;
+ return get( i );
+ }
+
+ public Object removeField( String key ){
+ int i = _getInt( key );
+ if ( i < 0 )
+ return null;
+ if ( i >= size() )
+ return null;
+ return remove( i );
+ }
+
+ /**
+ * @deprecated
+ */
+ @Deprecated
+ public boolean containsKey( String key ){
+ return containsField(key);
+ }
+
+ public boolean containsField( String key ){
+ int i = _getInt( key , false );
+ if ( i < 0 )
+ return false;
+ return i >= 0 && i < size();
+ }
+
+ public Set keySet(){
+ return new StringRangeSet(size());
+ }
+
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ public Map toMap() {
+ Map m = new HashMap();
+ Iterator i = this.keySet().iterator();
+ while (i.hasNext()) {
+ Object s = i.next();
+ m.put(s, this.get(String.valueOf(s)));
+ }
+ return m;
+ }
+
+ int _getInt( String s ){
+ return _getInt( s , true );
+ }
+
+ int _getInt( String s , boolean err ){
+ try {
+ return Integer.parseInt( s );
+ }
+ catch ( Exception e ){
+ if ( err )
+ throw new IllegalArgumentException( "BasicBSONList can only work with numeric keys, not: [" + s + "]" );
+ return -1;
+ }
+ }
+}
diff --git a/src/com/massivecraft/mcore3/lib/bson/types/Binary.java b/src/com/massivecraft/mcore3/lib/bson/types/Binary.java
new file mode 100644
index 00000000..7f0fdf89
--- /dev/null
+++ b/src/com/massivecraft/mcore3/lib/bson/types/Binary.java
@@ -0,0 +1,67 @@
+// Binary.java
+
+/**
+ * See the NOTICE.txt file distributed with this work for
+ * information regarding copyright ownership.
+ *
+ * The authors license this file to you 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 com.massivecraft.mcore3.lib.bson.types;
+
+import com.massivecraft.mcore3.lib.bson.BSON;
+
+import java.io.Serializable;
+
+/**
+ generic binary holder
+ */
+public class Binary implements Serializable {
+
+ private static final long serialVersionUID = 7902997490338209467L;
+
+ /**
+ * Creates a Binary object with the default binary type of 0
+ * @param data raw data
+ */
+ public Binary( byte[] data ){
+ this(BSON.B_GENERAL, data);
+ }
+
+ /**
+ * Creates a Binary object
+ * @param type type of the field as encoded in BSON
+ * @param data raw data
+ */
+ public Binary( byte type , byte[] data ){
+ _type = type;
+ _data = data;
+ }
+
+ public byte getType(){
+ return _type;
+ }
+
+ public byte[] getData(){
+ return _data;
+ }
+
+ public int length(){
+ return _data.length;
+ }
+
+ final byte _type;
+ final byte[] _data;
+}
diff --git a/src/com/massivecraft/mcore3/lib/bson/types/Code.java b/src/com/massivecraft/mcore3/lib/bson/types/Code.java
new file mode 100644
index 00000000..1192f9f7
--- /dev/null
+++ b/src/com/massivecraft/mcore3/lib/bson/types/Code.java
@@ -0,0 +1,58 @@
+// Code.java
+
+/**
+ * Copyright (C) 2008 10gen 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 com.massivecraft.mcore3.lib.bson.types;
+
+import java.io.Serializable;
+
+/**
+ * for using the Code type
+ */
+public class Code implements Serializable {
+
+ private static final long serialVersionUID = 475535263314046697L;
+
+ public Code( String code ){
+ _code = code;
+ }
+
+ public String getCode(){
+ return _code;
+ }
+
+ public boolean equals( Object o ){
+ if ( ! ( o instanceof Code ) )
+ return false;
+
+ Code c = (Code)o;
+ return _code.equals( c._code );
+ }
+
+ public int hashCode(){
+ return _code.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return getCode();
+ }
+
+ final String _code;
+
+}
+
diff --git a/src/com/massivecraft/mcore3/lib/bson/types/CodeWScope.java b/src/com/massivecraft/mcore3/lib/bson/types/CodeWScope.java
new file mode 100644
index 00000000..4b11cfaf
--- /dev/null
+++ b/src/com/massivecraft/mcore3/lib/bson/types/CodeWScope.java
@@ -0,0 +1,53 @@
+// CodeWScope.java
+
+/**
+ * Copyright (C) 2008 10gen 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 com.massivecraft.mcore3.lib.bson.types;
+
+import com.massivecraft.mcore3.lib.bson.*;
+
+/**
+ * for using the CodeWScope type
+ */
+public class CodeWScope extends Code {
+
+ private static final long serialVersionUID = -6284832275113680002L;
+
+ public CodeWScope( String code , BSONObject scope ){
+ super( code );
+ _scope = scope;
+ }
+
+ public BSONObject getScope(){
+ return _scope;
+ }
+
+ public boolean equals( Object o ){
+ if ( ! ( o instanceof CodeWScope ) )
+ return false;
+
+ CodeWScope c = (CodeWScope)o;
+ return _code.equals( c._code ) && _scope.equals( c._scope );
+ }
+
+ public int hashCode(){
+ return _code.hashCode() ^ _scope.hashCode();
+ }
+
+ final BSONObject _scope;
+}
+
diff --git a/src/com/massivecraft/mcore3/lib/bson/types/MaxKey.java b/src/com/massivecraft/mcore3/lib/bson/types/MaxKey.java
new file mode 100644
index 00000000..4db5f4dd
--- /dev/null
+++ b/src/com/massivecraft/mcore3/lib/bson/types/MaxKey.java
@@ -0,0 +1,47 @@
+
+/**
+ * Copyright (C) 2008 10gen 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 com.massivecraft.mcore3.lib.bson.types;
+
+import java.io.Serializable;
+
+/**
+ * Represent the maximum key value regardless of the key's type
+ */
+public class MaxKey implements Serializable {
+
+ private static final long serialVersionUID = 5123414776151687185L;
+
+ public MaxKey() {
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ return o instanceof MaxKey;
+ }
+
+ @Override
+ public int hashCode() {
+ return 0;
+ }
+
+ @Override
+ public String toString() {
+ return "MaxKey";
+ }
+
+}
diff --git a/src/com/massivecraft/mcore3/lib/bson/types/MinKey.java b/src/com/massivecraft/mcore3/lib/bson/types/MinKey.java
new file mode 100644
index 00000000..06cfb955
--- /dev/null
+++ b/src/com/massivecraft/mcore3/lib/bson/types/MinKey.java
@@ -0,0 +1,47 @@
+
+/**
+ * Copyright (C) 2008 10gen 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 com.massivecraft.mcore3.lib.bson.types;
+
+import java.io.Serializable;
+
+/**
+ * Represent the minimum key value regardless of the key's type
+ */
+public class MinKey implements Serializable {
+
+ private static final long serialVersionUID = 4075901136671855684L;
+
+ public MinKey() {
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ return o instanceof MinKey;
+ }
+
+ @Override
+ public int hashCode() {
+ return 0;
+ }
+
+ @Override
+ public String toString() {
+ return "MinKey";
+ }
+
+}
diff --git a/src/com/massivecraft/mcore3/lib/bson/types/ObjectId.java b/src/com/massivecraft/mcore3/lib/bson/types/ObjectId.java
new file mode 100644
index 00000000..2ce9dc27
--- /dev/null
+++ b/src/com/massivecraft/mcore3/lib/bson/types/ObjectId.java
@@ -0,0 +1,401 @@
+// ObjectId.java
+
+/**
+ * Copyright (C) 2008 10gen 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 com.massivecraft.mcore3.lib.bson.types;
+
+import java.net.*;
+import java.nio.*;
+import java.util.*;
+import java.util.concurrent.atomic.*;
+import java.util.logging.*;
+
+/**
+ * A globally unique identifier for objects.
+ * Consists of 12 bytes, divided as follows:
+ *
+ *
+ * 0 1 2 3 4 5 6
+ * 7 8 9 10 11
+ * time machine
+ * pid inc
+ *
+ *
+ *
+ * @dochub objectids
+ */
+public class ObjectId implements Comparable , java.io.Serializable {
+
+ private static final long serialVersionUID = -4415279469780082174L;
+
+ static final Logger LOGGER = Logger.getLogger( "org.bson.ObjectId" );
+
+ /** Gets a new object id.
+ * @return the new id
+ */
+ public static ObjectId get(){
+ return new ObjectId();
+ }
+
+ /** Checks if a string could be an ObjectId
.
+ * @return whether the string could be an object id
+ */
+ public static boolean isValid( String s ){
+ if ( s == null )
+ return false;
+
+ final int len = s.length();
+ if ( len != 24 )
+ return false;
+
+ for ( int i=0; i= '0' && c <= '9' )
+ continue;
+ if ( c >= 'a' && c <= 'f' )
+ continue;
+ if ( c >= 'A' && c <= 'F' )
+ continue;
+
+ return false;
+ }
+
+ return true;
+ }
+
+ /** Turn an object into an ObjectId
, if possible.
+ * Strings will be converted into ObjectId
s, if possible, and ObjectId
s will
+ * be cast and returned. Passing in null
returns null
.
+ * @param o the object to convert
+ * @return an ObjectId
if it can be massaged, null otherwise
+ */
+ public static ObjectId massageToObjectId( Object o ){
+ if ( o == null )
+ return null;
+
+ if ( o instanceof ObjectId )
+ return (ObjectId)o;
+
+ if ( o instanceof String ){
+ String s = o.toString();
+ if ( isValid( s ) )
+ return new ObjectId( s );
+ }
+
+ return null;
+ }
+
+ public ObjectId( Date time ){
+ this(time, _genmachine, _nextInc.getAndIncrement());
+ }
+
+ public ObjectId( Date time , int inc ){
+ this( time , _genmachine , inc );
+ }
+
+ public ObjectId( Date time , int machine , int inc ){
+ _time = (int)(time.getTime() / 1000);
+ _machine = machine;
+ _inc = inc;
+ _new = false;
+ }
+
+ /** Creates a new instance from a string.
+ * @param s the string to convert
+ * @throws IllegalArgumentException if the string is not a valid id
+ */
+ public ObjectId( String s ){
+ this( s , false );
+ }
+
+ public ObjectId( String s , boolean babble ){
+
+ if ( ! isValid( s ) )
+ throw new IllegalArgumentException( "invalid ObjectId [" + s + "]" );
+
+ if ( babble )
+ s = babbleToMongod( s );
+
+ byte b[] = new byte[12];
+ for ( int i=0; i=0; i-- )
+ buf.append( _pos( b , i ) );
+ for ( int i=11; i>=8; i-- )
+ buf.append( _pos( b , i ) );
+
+ return buf.toString();
+ }
+
+ public String toString(){
+ return toStringMongod();
+ }
+
+ int _compareUnsigned( int i , int j ){
+ long li = 0xFFFFFFFFL;
+ li = i & li;
+ long lj = 0xFFFFFFFFL;
+ lj = j & lj;
+ long diff = li - lj;
+ if (diff < Integer.MIN_VALUE)
+ return Integer.MIN_VALUE;
+ if (diff > Integer.MAX_VALUE)
+ return Integer.MAX_VALUE;
+ return (int) diff;
+ }
+
+ public int compareTo( ObjectId id ){
+ if ( id == null )
+ return -1;
+
+ int x = _compareUnsigned( _time , id._time );
+ if ( x != 0 )
+ return x;
+
+ x = _compareUnsigned( _machine , id._machine );
+ if ( x != 0 )
+ return x;
+
+ return _compareUnsigned( _inc , id._inc );
+ }
+
+ public int getMachine(){
+ return _machine;
+ }
+
+ /**
+ * Gets the time of this ID, in milliseconds
+ */
+ public long getTime(){
+ return _time * 1000L;
+ }
+
+ /**
+ * Gets the time of this ID, in seconds
+ */
+ public int getTimeSecond(){
+ return _time;
+ }
+
+ public int getInc(){
+ return _inc;
+ }
+
+ public int _time(){
+ return _time;
+ }
+ public int _machine(){
+ return _machine;
+ }
+ public int _inc(){
+ return _inc;
+ }
+
+ public boolean isNew(){
+ return _new;
+ }
+
+ public void notNew(){
+ _new = false;
+ }
+
+ /**
+ * Gets the generated machine ID, identifying the machine / process / class loader
+ */
+ public static int getGenMachineId() {
+ return _genmachine;
+ }
+
+ /**
+ * Gets the current value of the auto increment
+ */
+ public static int getCurrentInc() {
+ return _nextInc.get();
+ }
+
+ final int _time;
+ final int _machine;
+ final int _inc;
+
+ boolean _new;
+
+ public static int _flip( int x ){
+ int z = 0;
+ z |= ( ( x << 24 ) & 0xFF000000 );
+ z |= ( ( x << 8 ) & 0x00FF0000 );
+ z |= ( ( x >> 8 ) & 0x0000FF00 );
+ z |= ( ( x >> 24 ) & 0x000000FF );
+ return z;
+ }
+
+ private static AtomicInteger _nextInc = new AtomicInteger( (new java.util.Random()).nextInt() );
+
+ private static final int _genmachine;
+ static {
+
+ try {
+ // build a 2-byte machine piece based on NICs info
+ int machinePiece;
+ {
+ try {
+ StringBuilder sb = new StringBuilder();
+ Enumeration e = NetworkInterface.getNetworkInterfaces();
+ while ( e.hasMoreElements() ){
+ NetworkInterface ni = e.nextElement();
+ sb.append( ni.toString() );
+ }
+ machinePiece = sb.toString().hashCode() << 16;
+ } catch (Throwable e) {
+ // exception sometimes happens with IBM JVM, use random
+ LOGGER.log(Level.WARNING, e.getMessage(), e);
+ machinePiece = (new Random().nextInt()) << 16;
+ }
+ LOGGER.fine( "machine piece post: " + Integer.toHexString( machinePiece ) );
+ }
+
+ // add a 2 byte process piece. It must represent not only the JVM but the class loader.
+ // Since static var belong to class loader there could be collisions otherwise
+ final int processPiece;
+ {
+ int processId = new java.util.Random().nextInt();
+ try {
+ processId = java.lang.management.ManagementFactory.getRuntimeMXBean().getName().hashCode();
+ }
+ catch ( Throwable t ){
+ }
+
+ ClassLoader loader = ObjectId.class.getClassLoader();
+ int loaderId = loader != null ? System.identityHashCode(loader) : 0;
+
+ StringBuilder sb = new StringBuilder();
+ sb.append(Integer.toHexString(processId));
+ sb.append(Integer.toHexString(loaderId));
+ processPiece = sb.toString().hashCode() & 0xFFFF;
+ LOGGER.fine( "process piece: " + Integer.toHexString( processPiece ) );
+ }
+
+ _genmachine = machinePiece | processPiece;
+ LOGGER.fine( "machine : " + Integer.toHexString( _genmachine ) );
+ }
+ catch ( Exception e ){
+ throw new RuntimeException( e );
+ }
+
+ }
+}
+
diff --git a/src/com/massivecraft/mcore3/lib/bson/types/Symbol.java b/src/com/massivecraft/mcore3/lib/bson/types/Symbol.java
new file mode 100644
index 00000000..8e2e43ac
--- /dev/null
+++ b/src/com/massivecraft/mcore3/lib/bson/types/Symbol.java
@@ -0,0 +1,74 @@
+// Symbol.java
+
+/**
+ * Copyright (C) 2009 10gen 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 com.massivecraft.mcore3.lib.bson.types;
+
+import java.io.Serializable;
+
+/**
+ * Class to hold a BSON symbol object, which is an interned string in Ruby
+ */
+public class Symbol implements Serializable {
+
+ private static final long serialVersionUID = 1326269319883146072L;
+
+ public Symbol(String s) {
+ _symbol = s;
+ }
+
+ public String getSymbol(){
+ return _symbol;
+ }
+
+ /**
+ * Will compare equal to a String that is equal to the String that this holds
+ * @param o
+ * @return
+ */
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null) return false;
+
+ String otherSymbol;
+ if (o instanceof Symbol) {
+ otherSymbol = ((Symbol) o)._symbol;
+ }
+ else if (o instanceof String) {
+ otherSymbol = (String) o;
+ }
+ else {
+ return false;
+ }
+
+ if (_symbol != null ? !_symbol.equals(otherSymbol) : otherSymbol != null) return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ return _symbol != null ? _symbol.hashCode() : 0;
+ }
+
+ public String toString(){
+ return _symbol;
+ }
+
+ private final String _symbol;
+}
diff --git a/src/com/massivecraft/mcore3/lib/bson/types/package.html b/src/com/massivecraft/mcore3/lib/bson/types/package.html
new file mode 100644
index 00000000..ed93a43b
--- /dev/null
+++ b/src/com/massivecraft/mcore3/lib/bson/types/package.html
@@ -0,0 +1,3 @@
+
+ Contains classes implementing various BSON types.
+
diff --git a/src/com/massivecraft/mcore3/lib/bson/util/AbstractCopyOnWriteMap.java b/src/com/massivecraft/mcore3/lib/bson/util/AbstractCopyOnWriteMap.java
new file mode 100644
index 00000000..e16cb4d2
--- /dev/null
+++ b/src/com/massivecraft/mcore3/lib/bson/util/AbstractCopyOnWriteMap.java
@@ -0,0 +1,631 @@
+/**
+ * Copyright 2008 Atlassian Pty Ltd
+ *
+ * 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 com.massivecraft.mcore3.lib.bson.util;
+
+import static com.massivecraft.mcore3.lib.bson.util.Assertions.notNull;
+import static java.util.Collections.unmodifiableCollection;
+import static java.util.Collections.unmodifiableSet;
+
+import com.massivecraft.mcore3.lib.bson.util.annotations.GuardedBy;
+import com.massivecraft.mcore3.lib.bson.util.annotations.ThreadSafe;
+
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * Abstract base class for COW {@link Map} implementations that delegate to an
+ * internal map.
+ *
+ * @param The key type
+ * @param The value type
+ * @param the internal {@link Map} or extension for things like sorted and
+ * navigable maps.
+ */
+@ThreadSafe
+abstract class AbstractCopyOnWriteMap> implements ConcurrentMap, Serializable {
+ private static final long serialVersionUID = 4508989182041753878L;
+
+ @GuardedBy("lock")
+ private volatile M delegate;
+
+ // import edu.umd.cs.findbugs.annotations.@SuppressWarnings
+ private final transient Lock lock = new ReentrantLock();
+
+ // private final transient EntrySet entrySet = new EntrySet();
+ // private final transient KeySet keySet = new KeySet();
+ // private final transient Values values = new Values();
+ // private final View.Type viewType;
+ private final View view;
+
+ /**
+ * Create a new {@link CopyOnWriteMap} with the supplied {@link Map} to
+ * initialize the values.
+ *
+ * @param map the initial map to initialize with
+ * @param viewType for writable or read-only key, value and entrySet views
+ */
+ protected > AbstractCopyOnWriteMap(final N map, final View.Type viewType) {
+ this.delegate = notNull("delegate", copy(notNull("map", map)));
+ this.view = notNull("viewType", viewType).get(this);
+ }
+
+ /**
+ * Copy function, implemented by sub-classes.
+ *
+ * @param the map to copy and return.
+ * @param map the initial values of the newly created map.
+ * @return a new map. Will never be modified after construction.
+ */
+ @GuardedBy("lock")
+ abstract > M copy(N map);
+
+ //
+ // mutable operations
+ //
+
+ public final void clear() {
+ lock.lock();
+ try {
+ set(copy(Collections. emptyMap()));
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ public final V remove(final Object key) {
+ lock.lock();
+ try {
+ // short circuit if key doesn't exist
+ if (!delegate.containsKey(key)) {
+ return null;
+ }
+ final M map = copy();
+ try {
+ return map.remove(key);
+ } finally {
+ set(map);
+ }
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ public boolean remove(final Object key, final Object value) {
+ lock.lock();
+ try {
+ if (delegate.containsKey(key) && equals(value, delegate.get(key))) {
+ final M map = copy();
+ map.remove(key);
+ set(map);
+ return true;
+ } else {
+ return false;
+ }
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ public boolean replace(final K key, final V oldValue, final V newValue) {
+ lock.lock();
+ try {
+ if (!delegate.containsKey(key) || !equals(oldValue, delegate.get(key))) {
+ return false;
+ }
+ final M map = copy();
+ map.put(key, newValue);
+ set(map);
+ return true;
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ public V replace(final K key, final V value) {
+ lock.lock();
+ try {
+ if (!delegate.containsKey(key)) {
+ return null;
+ }
+ final M map = copy();
+ try {
+ return map.put(key, value);
+ } finally {
+ set(map);
+ }
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ public final V put(final K key, final V value) {
+ lock.lock();
+ try {
+ final M map = copy();
+ try {
+ return map.put(key, value);
+ } finally {
+ set(map);
+ }
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ public V putIfAbsent(final K key, final V value) {
+ lock.lock();
+ try {
+ if (!delegate.containsKey(key)) {
+ final M map = copy();
+ try {
+ return map.put(key, value);
+ } finally {
+ set(map);
+ }
+ }
+ return delegate.get(key);
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ public final void putAll(final Map extends K, ? extends V> t) {
+ lock.lock();
+ try {
+ final M map = copy();
+ map.putAll(t);
+ set(map);
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ protected M copy() {
+ lock.lock();
+ try {
+ return copy(delegate);
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ @GuardedBy("lock")
+ protected void set(final M map) {
+ delegate = map;
+ }
+
+ //
+ // Collection views
+ //
+
+ public final Set> entrySet() {
+ return view.entrySet();
+ }
+
+ public final Set keySet() {
+ return view.keySet();
+ }
+
+ public final Collection values() {
+ return view.values();
+ }
+
+ //
+ // delegate operations
+ //
+
+ public final boolean containsKey(final Object key) {
+ return delegate.containsKey(key);
+ }
+
+ public final boolean containsValue(final Object value) {
+ return delegate.containsValue(value);
+ }
+
+ public final V get(final Object key) {
+ return delegate.get(key);
+ }
+
+ public final boolean isEmpty() {
+ return delegate.isEmpty();
+ }
+
+ public final int size() {
+ return delegate.size();
+ }
+
+ @Override
+ public final boolean equals(final Object o) {
+ return delegate.equals(o);
+ }
+
+ @Override
+ public final int hashCode() {
+ return delegate.hashCode();
+ }
+
+ protected final M getDelegate() {
+ return delegate;
+ }
+
+ @Override
+ public String toString() {
+ return delegate.toString();
+ }
+
+ //
+ // inner classes
+ //
+
+ private class KeySet extends CollectionView implements Set {
+
+ @Override
+ Collection getDelegate() {
+ return delegate.keySet();
+ }
+
+ //
+ // mutable operations
+ //
+
+ public void clear() {
+ lock.lock();
+ try {
+ final M map = copy();
+ map.keySet().clear();
+ set(map);
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ public boolean remove(final Object o) {
+ return AbstractCopyOnWriteMap.this.remove(o) != null;
+ }
+
+ public boolean removeAll(final Collection> c) {
+ lock.lock();
+ try {
+ final M map = copy();
+ try {
+ return map.keySet().removeAll(c);
+ } finally {
+ set(map);
+ }
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ public boolean retainAll(final Collection> c) {
+ lock.lock();
+ try {
+ final M map = copy();
+ try {
+ return map.keySet().retainAll(c);
+ } finally {
+ set(map);
+ }
+ } finally {
+ lock.unlock();
+ }
+ }
+ }
+
+ private final class Values extends CollectionView {
+
+ @Override
+ Collection getDelegate() {
+ return delegate.values();
+ }
+
+ public void clear() {
+ lock.lock();
+ try {
+ final M map = copy();
+ map.values().clear();
+ set(map);
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ public boolean remove(final Object o) {
+ lock.lock();
+ try {
+ if (!contains(o)) {
+ return false;
+ }
+ final M map = copy();
+ try {
+ return map.values().remove(o);
+ } finally {
+ set(map);
+ }
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ public boolean removeAll(final Collection> c) {
+ lock.lock();
+ try {
+ final M map = copy();
+ try {
+ return map.values().removeAll(c);
+ } finally {
+ set(map);
+ }
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ public boolean retainAll(final Collection> c) {
+ lock.lock();
+ try {
+ final M map = copy();
+ try {
+ return map.values().retainAll(c);
+ } finally {
+ set(map);
+ }
+ } finally {
+ lock.unlock();
+ }
+ }
+ }
+
+ private class EntrySet extends CollectionView> implements Set> {
+
+ @Override
+ Collection> getDelegate() {
+ return delegate.entrySet();
+ }
+
+ public void clear() {
+ lock.lock();
+ try {
+ final M map = copy();
+ map.entrySet().clear();
+ set(map);
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ public boolean remove(final Object o) {
+ lock.lock();
+ try {
+ if (!contains(o)) {
+ return false;
+ }
+ final M map = copy();
+ try {
+ return map.entrySet().remove(o);
+ } finally {
+ set(map);
+ }
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ public boolean removeAll(final Collection> c) {
+ lock.lock();
+ try {
+ final M map = copy();
+ try {
+ return map.entrySet().removeAll(c);
+ } finally {
+ set(map);
+ }
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ public boolean retainAll(final Collection> c) {
+ lock.lock();
+ try {
+ final M map = copy();
+ try {
+ return map.entrySet().retainAll(c);
+ } finally {
+ set(map);
+ }
+ } finally {
+ lock.unlock();
+ }
+ }
+ }
+
+ private static class UnmodifiableIterator implements Iterator {
+ private final Iterator delegate;
+
+ public UnmodifiableIterator(final Iterator delegate) {
+ this.delegate = delegate;
+ }
+
+ public boolean hasNext() {
+ return delegate.hasNext();
+ }
+
+ public T next() {
+ return delegate.next();
+ }
+
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ protected static abstract class CollectionView implements Collection {
+
+ abstract Collection getDelegate();
+
+ //
+ // delegate operations
+ //
+
+ public final boolean contains(final Object o) {
+ return getDelegate().contains(o);
+ }
+
+ public final boolean containsAll(final Collection> c) {
+ return getDelegate().containsAll(c);
+ }
+
+ public final Iterator iterator() {
+ return new UnmodifiableIterator(getDelegate().iterator());
+ }
+
+ public final boolean isEmpty() {
+ return getDelegate().isEmpty();
+ }
+
+ public final int size() {
+ return getDelegate().size();
+ }
+
+ public final Object[] toArray() {
+ return getDelegate().toArray();
+ }
+
+ public final T[] toArray(final T[] a) {
+ return getDelegate().toArray(a);
+ }
+
+ @Override
+ public int hashCode() {
+ return getDelegate().hashCode();
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ return getDelegate().equals(obj);
+ }
+
+ @Override
+ public String toString() {
+ return getDelegate().toString();
+ }
+
+ //
+ // unsupported operations
+ //
+
+ public final boolean add(final E o) {
+ throw new UnsupportedOperationException();
+ }
+
+ public final boolean addAll(final Collection extends E> c) {
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ private boolean equals(final Object o1, final Object o2) {
+ if (o1 == null) {
+ return o2 == null;
+ }
+ return o1.equals(o2);
+ }
+
+ /**
+ * Provides access to the views of the underlying key, value and entry
+ * collections.
+ */
+ public static abstract class View {
+ View() {}
+
+ abstract Set keySet();
+
+ abstract Set> entrySet();
+
+ abstract Collection values();
+
+ /**
+ * The different types of {@link View} available
+ */
+ public enum Type {
+ STABLE {
+ @Override
+ > View get(final AbstractCopyOnWriteMap host) {
+ return host.new Immutable();
+ }
+ },
+ LIVE {
+ @Override
+ > View get(final AbstractCopyOnWriteMap host) {
+ return host.new Mutable();
+ }
+ };
+ abstract > View get(AbstractCopyOnWriteMap host);
+ }
+ }
+
+ final class Immutable extends View implements Serializable {
+
+ private static final long serialVersionUID = -4158727180429303818L;
+
+ @Override
+ public Set keySet() {
+ return unmodifiableSet(delegate.keySet());
+ }
+
+ @Override
+ public Set> entrySet() {
+ return unmodifiableSet(delegate.entrySet());
+ }
+
+ @Override
+ public Collection values() {
+ return unmodifiableCollection(delegate.values());
+ }
+ }
+
+ final class Mutable extends View implements Serializable {
+
+ private static final long serialVersionUID = 1624520291194797634L;
+
+ private final transient KeySet keySet = new KeySet();
+ private final transient EntrySet entrySet = new EntrySet();
+ private final transient Values values = new Values();
+
+ @Override
+ public Set keySet() {
+ return keySet;
+ }
+
+ @Override
+ public Set> entrySet() {
+ return entrySet;
+ }
+
+ @Override
+ public Collection values() {
+ return values;
+ }
+ }
+}
diff --git a/src/com/massivecraft/mcore3/lib/bson/util/Assertions.java b/src/com/massivecraft/mcore3/lib/bson/util/Assertions.java
new file mode 100644
index 00000000..ab5efa6b
--- /dev/null
+++ b/src/com/massivecraft/mcore3/lib/bson/util/Assertions.java
@@ -0,0 +1,48 @@
+/**
+ * Copyright 2008 Atlassian Pty Ltd
+ *
+ * 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 com.massivecraft.mcore3.lib.bson.util;
+
+/**
+ * Design by contract assertions.
+ */
+public class Assertions {
+ public static T notNull(final String name, final T notNull) throws IllegalArgumentException {
+ if (notNull == null) {
+ throw new NullArgumentException(name);
+ }
+ return notNull;
+ }
+
+ public static void isTrue(final String name, final boolean check) throws IllegalArgumentException {
+ if (!check) {
+ throw new IllegalArgumentException(name);
+ }
+ }
+
+ // /CLOVER:OFF
+ private Assertions() {}
+
+ // /CLOVER:ON
+
+ static class NullArgumentException extends IllegalArgumentException {
+ private static final long serialVersionUID = 6178592463723624585L;
+
+ NullArgumentException(final String name) {
+ super(name + " should not be null!");
+ }
+ }
+}
diff --git a/src/com/massivecraft/mcore3/lib/bson/util/ClassAncestry.java b/src/com/massivecraft/mcore3/lib/bson/util/ClassAncestry.java
new file mode 100644
index 00000000..7c895f3b
--- /dev/null
+++ b/src/com/massivecraft/mcore3/lib/bson/util/ClassAncestry.java
@@ -0,0 +1,72 @@
+package com.massivecraft.mcore3.lib.bson.util;
+
+import static com.massivecraft.mcore3.lib.bson.util.CopyOnWriteMap.newHashMap;
+import static java.util.Collections.unmodifiableList;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.ConcurrentMap;
+
+class ClassAncestry {
+
+ /**
+ * getAncestry
+ *
+ * Walks superclass and interface graph, superclasses first, then
+ * interfaces, to compute an ancestry list. Supertypes are visited left to
+ * right. Duplicates are removed such that no Class will appear in the list
+ * before one of its subtypes.
+ *
+ * Does not need to be synchronized, races are harmless as the Class graph
+ * does not change at runtime.
+ */
+ public static List> getAncestry(Class c) {
+ final ConcurrentMap, List>> cache = getClassAncestryCache();
+ while (true) {
+ List> cachedResult = cache.get(c);
+ if (cachedResult != null) {
+ return cachedResult;
+ }
+ cache.putIfAbsent(c, computeAncestry(c));
+ }
+ }
+
+ /**
+ * computeAncestry, starting with children and going back to parents
+ */
+ private static List> computeAncestry(Class> c) {
+ final List> result = new ArrayList>();
+ result.add(Object.class);
+ computeAncestry(c, result);
+ Collections.reverse(result);
+ return unmodifiableList(new ArrayList>(result));
+ }
+
+ private static void computeAncestry(Class c, List> result) {
+ if ((c == null) || (c == Object.class)) {
+ return;
+ }
+
+ // first interfaces (looks backwards but is not)
+ Class>[] interfaces = c.getInterfaces();
+ for (int i = interfaces.length - 1; i >= 0; i--) {
+ computeAncestry(interfaces[i], result);
+ }
+
+ // next superclass
+ computeAncestry(c.getSuperclass(), result);
+
+ if (!result.contains(c))
+ result.add(c);
+ }
+
+ /**
+ * classAncestryCache
+ */
+ private static ConcurrentMap, List>> getClassAncestryCache() {
+ return (_ancestryCache);
+ }
+
+ private static final ConcurrentMap, List>> _ancestryCache = newHashMap();
+}
diff --git a/src/com/massivecraft/mcore3/lib/bson/util/ClassMap.java b/src/com/massivecraft/mcore3/lib/bson/util/ClassMap.java
new file mode 100644
index 00000000..a51c624f
--- /dev/null
+++ b/src/com/massivecraft/mcore3/lib/bson/util/ClassMap.java
@@ -0,0 +1,100 @@
+// ClassMap.java
+
+/**
+ * Copyright (C) 2008 10gen 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 com.massivecraft.mcore3.lib.bson.util;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Maps Class objects to values. A ClassMap is different from a regular Map in
+ * that get(c) does not only look to see if 'c' is a key in the Map, but also
+ * walks the up superclass and interface graph of 'c' to find matches. Derived
+ * matches of this sort are then "cached" in the registry so that matches are
+ * faster on future gets.
+ *
+ * This is a very useful class for Class based registries.
+ *
+ * Example:
+ *
+ * ClassMap m = new ClassMap(); m.put(Animal.class, "Animal");
+ * m.put(Fox.class, "Fox"); m.Fox.class) --> "Fox" m.get(Dog.class) --> "Animal"
+ *
+ * (assuming Dog.class < Animal.class)
+ */
+public class ClassMap {
+ /**
+ * Walks superclass and interface graph, superclasses first, then
+ * interfaces, to compute an ancestry list. Supertypes are visited left to
+ * right. Duplicates are removed such that no Class will appear in the list
+ * before one of its subtypes.
+ */
+ public static List> getAncestry(Class c) {
+ return ClassAncestry.getAncestry(c);
+ }
+
+ private final class ComputeFunction implements Function, T> {
+ @Override
+ public T apply(Class> a) {
+ for (Class> cls : getAncestry(a)) {
+ T result = map.get(cls);
+ if (result != null) {
+ return result;
+ }
+ }
+ return null;
+ }
+ };
+
+ private final Map, T> map = CopyOnWriteMap.newHashMap();
+ private final Map, T> cache = ComputingMap.create(new ComputeFunction());
+
+
+ public T get(Object key) {
+ return cache.get(key);
+ }
+
+ public T put(Class> key, T value) {
+ try {
+ return map.put(key, value);
+ } finally {
+ cache.clear();
+ }
+ }
+
+ public T remove(Object key) {
+ try {
+ return map.remove(key);
+ } finally {
+ cache.clear();
+ }
+ }
+
+ public void clear() {
+ map.clear();
+ cache.clear();
+ }
+
+ public int size() {
+ return map.size();
+ }
+
+ public boolean isEmpty() {
+ return map.isEmpty();
+ }
+}
diff --git a/src/com/massivecraft/mcore3/lib/bson/util/ComputingMap.java b/src/com/massivecraft/mcore3/lib/bson/util/ComputingMap.java
new file mode 100644
index 00000000..a3f1b621
--- /dev/null
+++ b/src/com/massivecraft/mcore3/lib/bson/util/ComputingMap.java
@@ -0,0 +1,109 @@
+package com.massivecraft.mcore3.lib.bson.util;
+
+import static com.massivecraft.mcore3.lib.bson.util.Assertions.notNull;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentMap;
+
+final class ComputingMap implements Map, Function {
+
+ public static Map create(Function function) {
+ return new ComputingMap(CopyOnWriteMap. newHashMap(), function);
+ }
+
+ private final ConcurrentMap map;
+ private final Function function;
+
+ ComputingMap(ConcurrentMap map, Function function) {
+ this.map = notNull("map", map);
+ this.function = notNull("function", function);
+ }
+
+ public V get(Object key) {
+ while (true) {
+ V v = map.get(key);
+ if (v != null)
+ return v;
+ @SuppressWarnings("unchecked")
+ K k = (K) key;
+ V value = function.apply(k);
+ if (value == null)
+ return null;
+ map.putIfAbsent(k, value);
+ }
+ }
+
+ public V apply(K k) {
+ return get(k);
+ }
+
+ public V putIfAbsent(K key, V value) {
+ return map.putIfAbsent(key, value);
+ }
+
+ public boolean remove(Object key, Object value) {
+ return map.remove(key, value);
+ }
+
+ public boolean replace(K key, V oldValue, V newValue) {
+ return map.replace(key, oldValue, newValue);
+ }
+
+ public V replace(K key, V value) {
+ return map.replace(key, value);
+ }
+
+ public int size() {
+ return map.size();
+ }
+
+ public boolean isEmpty() {
+ return map.isEmpty();
+ }
+
+ public boolean containsKey(Object key) {
+ return map.containsKey(key);
+ }
+
+ public boolean containsValue(Object value) {
+ return map.containsValue(value);
+ }
+
+ public V put(K key, V value) {
+ return map.put(key, value);
+ }
+
+ public V remove(Object key) {
+ return map.remove(key);
+ }
+
+ public void putAll(Map extends K, ? extends V> m) {
+ map.putAll(m);
+ }
+
+ public void clear() {
+ map.clear();
+ }
+
+ public Set keySet() {
+ return map.keySet();
+ }
+
+ public Collection values() {
+ return map.values();
+ }
+
+ public Set> entrySet() {
+ return map.entrySet();
+ }
+
+ public boolean equals(Object o) {
+ return map.equals(o);
+ }
+
+ public int hashCode() {
+ return map.hashCode();
+ }
+}
diff --git a/src/com/massivecraft/mcore3/lib/bson/util/CopyOnWriteMap.java b/src/com/massivecraft/mcore3/lib/bson/util/CopyOnWriteMap.java
new file mode 100644
index 00000000..271ee5a9
--- /dev/null
+++ b/src/com/massivecraft/mcore3/lib/bson/util/CopyOnWriteMap.java
@@ -0,0 +1,273 @@
+/**
+ * Copyright 2008 Atlassian Pty Ltd
+ *
+ * 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 com.massivecraft.mcore3.lib.bson.util;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.WeakHashMap;
+
+
+import com.massivecraft.mcore3.lib.bson.util.AbstractCopyOnWriteMap.View.Type;
+import com.massivecraft.mcore3.lib.bson.util.annotations.GuardedBy;
+import com.massivecraft.mcore3.lib.bson.util.annotations.ThreadSafe;
+
+/**
+ * A thread-safe variant of {@link Map} in which all mutative operations (the
+ * "destructive" operations described by {@link Map} put, remove and so on) are
+ * implemented by making a fresh copy of the underlying map.
+ *
+ * This is ordinarily too costly, but may be more efficient than
+ * alternatives when traversal operations vastly out-number mutations, and is
+ * useful when you cannot or don't want to synchronize traversals, yet need to
+ * preclude interference among concurrent threads. The "snapshot" style
+ * iterators on the collections returned by {@link #entrySet()},
+ * {@link #keySet()} and {@link #values()} use a reference to the internal map
+ * at the point that the iterator was created. This map never changes during the
+ * lifetime of the iterator, so interference is impossible and the iterator is
+ * guaranteed not to throw ConcurrentModificationException . The
+ * iterators will not reflect additions, removals, or changes to the list since
+ * the iterator was created. Removing elements via these iterators is not
+ * supported. The mutable operations on these collections (remove, retain etc.)
+ * are supported but as with the {@link Map} interface, add and addAll are not
+ * and throw {@link UnsupportedOperationException}.
+ *
+ * The actual copy is performed by an abstract {@link #copy(Map)} method. The
+ * method is responsible for the underlying Map implementation (for instance a
+ * {@link HashMap}, {@link TreeMap}, {@link LinkedHashMap} etc.) and therefore
+ * the semantics of what this map will cope with as far as null keys and values,
+ * iteration ordering etc. See the note below about suitable candidates for
+ * underlying Map implementations
+ *
+ * There are supplied implementations for the common j.u.c {@link Map}
+ * implementations via the {@link CopyOnWriteMap} static {@link Builder}.
+ *
+ * Collection views of the keys, values and entries are optionally
+ * {@link View.Type.LIVE live} or {@link View.Type.STABLE stable}. Live views
+ * are modifiable will cause a copy if a modifying method is called on them.
+ * Methods on these will reflect the current state of the collection, although
+ * iterators will be snapshot style. If the collection views are stable they are
+ * unmodifiable, and will be a snapshot of the state of the map at the time the
+ * collection was asked for.
+ *
+ * Please note that the thread-safety guarantees are limited to
+ * the thread-safety of the non-mutative (non-destructive) operations of the
+ * underlying map implementation. For instance some implementations such as
+ * {@link WeakHashMap} and {@link LinkedHashMap} with access ordering are
+ * actually structurally modified by the {@link #get(Object)} method and are
+ * therefore not suitable candidates as delegates for this class.
+ *
+ * @param the key type
+ * @param the value type
+ * @author Jed Wesley-Smith
+ */
+@ThreadSafe
+abstract class CopyOnWriteMap extends AbstractCopyOnWriteMap> {
+ private static final long serialVersionUID = 7935514534647505917L;
+
+ /**
+ * Get a {@link Builder} for a {@link CopyOnWriteMap} instance.
+ *
+ * @param key type
+ * @param value type
+ * @return a fresh builder
+ */
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ /**
+ * Build a {@link CopyOnWriteMap} and specify all the options.
+ *
+ * @param key type
+ * @param value type
+ */
+ public static class Builder {
+ private View.Type viewType = View.Type.STABLE;
+ private final Map initialValues = new HashMap();
+
+ Builder() {}
+
+ /**
+ * Views are stable (fixed in time) and unmodifiable.
+ */
+ public Builder stableViews() {
+ viewType = View.Type.STABLE;
+ return this;
+ }
+
+ /**
+ * Views are live (reflecting concurrent updates) and mutator methods
+ * are supported.
+ */
+ public Builder addAll(final Map extends K, ? extends V> values) {
+ initialValues.putAll(values);
+ return this;
+ }
+
+ /**
+ * Views are live (reflecting concurrent updates) and mutator methods
+ * are supported.
+ */
+ public Builder liveViews() {
+ viewType = View.Type.LIVE;
+ return this;
+ }
+
+ public CopyOnWriteMap newHashMap() {
+ return new Hash(initialValues, viewType);
+ }
+
+ public CopyOnWriteMap newLinkedMap() {
+ return new Linked(initialValues, viewType);
+ }
+ }
+
+ /**
+ * Creates a new {@link CopyOnWriteMap} with an underlying {@link HashMap}.
+ *
+ * This map has {@link View.Type.STABLE stable} views.
+ */
+ public static CopyOnWriteMap newHashMap() {
+ final Builder builder = builder();
+ return builder.newHashMap();
+ }
+
+ /**
+ * Creates a new {@link CopyOnWriteMap} with an underlying {@link HashMap}
+ * using the supplied map as the initial values.
+ *
+ * This map has {@link View.Type.STABLE stable} views.
+ */
+ public static CopyOnWriteMap newHashMap(final Map extends K, ? extends V> map) {
+ final Builder builder = builder();
+ return builder.addAll(map).newHashMap();
+ }
+
+ /**
+ * Creates a new {@link CopyOnWriteMap} with an underlying
+ * {@link LinkedHashMap}. Iterators for this map will be return elements in
+ * insertion order.
+ *
+ * This map has {@link View.Type.STABLE stable} views.
+ */
+ public static CopyOnWriteMap newLinkedMap() {
+ final Builder builder = builder();
+ return builder.newLinkedMap();
+ }
+
+ /**
+ * Creates a new {@link CopyOnWriteMap} with an underlying
+ * {@link LinkedHashMap} using the supplied map as the initial values.
+ * Iterators for this map will be return elements in insertion order.
+ *
+ * This map has {@link View.Type.STABLE stable} views.
+ */
+ public static CopyOnWriteMap newLinkedMap(final Map extends K, ? extends V> map) {
+ final Builder builder = builder();
+ return builder.addAll(map).newLinkedMap();
+ }
+
+ //
+ // constructors
+ //
+
+ /**
+ * Create a new {@link CopyOnWriteMap} with the supplied {@link Map} to
+ * initialize the values.
+ *
+ * @param map the initial map to initialize with
+ * @deprecated since 0.0.12 use the versions that explicitly specify
+ * View.Type
+ */
+ @Deprecated
+ protected CopyOnWriteMap(final Map extends K, ? extends V> map) {
+ this(map, View.Type.LIVE);
+ }
+
+ /**
+ * Create a new empty {@link CopyOnWriteMap}.
+ *
+ * @deprecated since 0.0.12 use the versions that explicitly specify
+ * View.Type
+ */
+ @Deprecated
+ protected CopyOnWriteMap() {
+ this(Collections. emptyMap(), View.Type.LIVE);
+ }
+
+ /**
+ * Create a new {@link CopyOnWriteMap} with the supplied {@link Map} to
+ * initialize the values. This map may be optionally modified using any of
+ * the key, entry or value views
+ *
+ * @param map the initial map to initialize with
+ */
+ protected CopyOnWriteMap(final Map extends K, ? extends V> map, final View.Type viewType) {
+ super(map, viewType);
+ }
+
+ /**
+ * Create a new empty {@link CopyOnWriteMap}. This map may be optionally
+ * modified using any of the key, entry or value views
+ */
+ protected CopyOnWriteMap(final View.Type viewType) {
+ super(Collections. emptyMap(), viewType);
+ }
+
+ @Override
+ @GuardedBy("internal-lock")
+ protected abstract > Map copy(N map);
+
+ //
+ // inner classes
+ //
+
+ /**
+ * Uses {@link HashMap} instances as its internal storage.
+ */
+ static class Hash extends CopyOnWriteMap {
+ private static final long serialVersionUID = 5221824943734164497L;
+
+ Hash(final Map extends K, ? extends V> map, final Type viewType) {
+ super(map, viewType);
+ }
+
+ @Override
+ public > Map copy(final N map) {
+ return new HashMap(map);
+ }
+ }
+
+ /**
+ * Uses {@link LinkedHashMap} instances as its internal storage.
+ */
+ static class Linked extends CopyOnWriteMap {
+ private static final long serialVersionUID = -8659999465009072124L;
+
+ Linked(final Map extends K, ? extends V> map, final Type viewType) {
+ super(map, viewType);
+ }
+
+ @Override
+ public > Map copy(final N map) {
+ return new LinkedHashMap(map);
+ }
+ }
+}
diff --git a/src/com/massivecraft/mcore3/lib/bson/util/Function.java b/src/com/massivecraft/mcore3/lib/bson/util/Function.java
new file mode 100644
index 00000000..2405ed31
--- /dev/null
+++ b/src/com/massivecraft/mcore3/lib/bson/util/Function.java
@@ -0,0 +1,5 @@
+package com.massivecraft.mcore3.lib.bson.util;
+
+interface Function {
+ B apply(A a);
+}
diff --git a/src/com/massivecraft/mcore3/lib/bson/util/SimplePool.java b/src/com/massivecraft/mcore3/lib/bson/util/SimplePool.java
new file mode 100644
index 00000000..e518831c
--- /dev/null
+++ b/src/com/massivecraft/mcore3/lib/bson/util/SimplePool.java
@@ -0,0 +1,58 @@
+// SimplePool.java
+
+/**
+ * Copyright (C) 2008 10gen 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 com.massivecraft.mcore3.lib.bson.util;
+
+import java.util.*;
+import java.util.concurrent.*;
+
+public abstract class SimplePool {
+
+ public SimplePool( int max ){
+ _max = max;
+ }
+
+ public SimplePool(){
+ _max = 1000;
+ }
+
+ protected abstract T createNew();
+
+ protected boolean ok( T t ){
+ return true;
+ }
+
+ public T get(){
+ T t = _stored.poll();
+ if ( t != null )
+ return t;
+ return createNew();
+ }
+
+ public void done( T t ){
+ if ( ! ok( t ) )
+ return;
+
+ if ( _stored.size() > _max )
+ return;
+ _stored.add( t );
+ }
+
+ final int _max;
+ private Queue _stored = new ConcurrentLinkedQueue();
+}
diff --git a/src/com/massivecraft/mcore3/lib/bson/util/StringRangeSet.java b/src/com/massivecraft/mcore3/lib/bson/util/StringRangeSet.java
new file mode 100644
index 00000000..0cfec141
--- /dev/null
+++ b/src/com/massivecraft/mcore3/lib/bson/util/StringRangeSet.java
@@ -0,0 +1,130 @@
+/**
+ * Copyright (C) 2010 10gen 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 com.massivecraft.mcore3.lib.bson.util;
+
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.Set;
+
+public class StringRangeSet implements Set {
+
+ private final int size;
+
+ private final static int NUMSTR_LEN = 100;
+ private final static String[] NUMSTRS = new String[100];
+ static {
+ for (int i = 0; i < NUMSTR_LEN; ++i)
+ NUMSTRS[i] = String.valueOf(i);
+ }
+
+ public StringRangeSet(int size) {
+ this.size = size;
+ }
+
+ public int size() {
+ return size;
+ }
+
+ public Iterator iterator() {
+ return new Iterator() {
+
+ int index = 0;
+
+ public boolean hasNext() {
+ return index < size;
+ }
+
+ public String next() {
+ if (index < NUMSTR_LEN)
+ return NUMSTRS[index++];
+ return String.valueOf(index++);
+ }
+
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+ };
+ }
+
+ @Override
+ public boolean add(String e) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean addAll(Collection extends String> c) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void clear() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean contains(Object o) {
+ int t = Integer.parseInt(String.valueOf(o));
+ return t >= 0 && t < size;
+ }
+
+ @Override
+ public boolean containsAll(Collection> c) {
+ for (Object o : c) {
+ if (!contains(o)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return false;
+ }
+
+ @Override
+ public boolean remove(Object o) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean removeAll(Collection> c) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean retainAll(Collection> c) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Object[] toArray() {
+ String[] array = new String[size()];
+ for (int i = 0; i < size; ++i) {
+ if (i < NUMSTR_LEN) {
+ array[i] = NUMSTRS[i];
+ } else {
+ array[i] = String.valueOf(i);
+ }
+ }
+ return array;
+ }
+
+ @Override
+ public T[] toArray(T[] a) {
+ throw new UnsupportedOperationException();
+ }
+}
diff --git a/src/com/massivecraft/mcore3/lib/bson/util/annotations/GuardedBy.java b/src/com/massivecraft/mcore3/lib/bson/util/annotations/GuardedBy.java
new file mode 100644
index 00000000..ac05e7c6
--- /dev/null
+++ b/src/com/massivecraft/mcore3/lib/bson/util/annotations/GuardedBy.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2005 Brian Goetz and Tim Peierls
+ * Released under the Creative Commons Attribution License
+ * (http://creativecommons.org/licenses/by/2.5)
+ * Official home: http://www.jcip.net
+ *
+ * Any republication or derived work distributed in source code form
+ * must include this copyright and license notice.
+ */
+
+package com.massivecraft.mcore3.lib.bson.util.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * The field or method to which this annotation is applied can only be accessed
+ * when holding a particular lock, which may be a built-in (synchronization) lock,
+ * or may be an explicit java.util.concurrent.Lock.
+ *
+ * The argument determines which lock guards the annotated field or method:
+ *
+ *
+ * this
: The intrinsic lock of the object in whose class the field is defined.
+ *
+ *
+ * class-name.this
: For inner classes, it may be necessary to disambiguate 'this';
+ * the class-name.this designation allows you to specify which 'this' reference is intended
+ *
+ *
+ * itself
: For reference fields only; the object to which the field refers.
+ *
+ *
+ * field-name
: The lock object is referenced by the (instance or static) field
+ * specified by field-name .
+ *
+ *
+ * class-name.field-name
: The lock object is reference by the static field specified
+ * by class-name.field-name .
+ *
+ *
+ * method-name()
: The lock object is returned by calling the named nil-ary method.
+ *
+ *
+ * class-name.class
: The Class object for the specified class should be used as the lock object.
+ *
+ */
+@Target({ElementType.FIELD, ElementType.METHOD})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface GuardedBy {
+ String value();
+}
diff --git a/src/com/massivecraft/mcore3/lib/bson/util/annotations/Immutable.java b/src/com/massivecraft/mcore3/lib/bson/util/annotations/Immutable.java
new file mode 100644
index 00000000..bf27f44c
--- /dev/null
+++ b/src/com/massivecraft/mcore3/lib/bson/util/annotations/Immutable.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2005 Brian Goetz and Tim Peierls
+ * Released under the Creative Commons Attribution License
+ * (http://creativecommons.org/licenses/by/2.5)
+ * Official home: http://www.jcip.net
+ *
+ * Any republication or derived work distributed in source code form
+ * must include this copyright and license notice.
+ */
+
+package com.massivecraft.mcore3.lib.bson.util.annotations;
+
+import java.lang.annotation.*;
+
+/**
+ * The class to which this annotation is applied is immutable. This means that
+ * its state cannot be seen to change by callers, which implies that
+ *
+ * all public fields are final,
+ * all public final reference fields refer to other immutable objects, and
+ * constructors and methods do not publish references to any internal state
+ * which is potentially mutable by the implementation.
+ *
+ * Immutable objects may still have internal mutable state for purposes of performance
+ * optimization; some state variables may be lazily computed, so long as they are computed
+ * from immutable state and that callers cannot tell the difference.
+ *
+ * Immutable objects are inherently thread-safe; they may be passed between threads or
+ * published without synchronization.
+ */
+@Documented
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Immutable {
+}
diff --git a/src/com/massivecraft/mcore3/lib/bson/util/annotations/NotThreadSafe.java b/src/com/massivecraft/mcore3/lib/bson/util/annotations/NotThreadSafe.java
new file mode 100644
index 00000000..1b229bed
--- /dev/null
+++ b/src/com/massivecraft/mcore3/lib/bson/util/annotations/NotThreadSafe.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2005 Brian Goetz and Tim Peierls
+ * Released under the Creative Commons Attribution License
+ * (http://creativecommons.org/licenses/by/2.5)
+ * Official home: http://www.jcip.net
+ *
+ * Any republication or derived work distributed in source code form
+ * must include this copyright and license notice.
+ */
+
+package com.massivecraft.mcore3.lib.bson.util.annotations;
+
+import java.lang.annotation.*;
+
+
+/**
+ * The class to which this annotation is applied is not thread-safe.
+ * This annotation primarily exists for clarifying the non-thread-safety of a class
+ * that might otherwise be assumed to be thread-safe, despite the fact that it is a bad
+ * idea to assume a class is thread-safe without good reason.
+ * @see ThreadSafe
+ */
+@Documented
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface NotThreadSafe {
+}
diff --git a/src/com/massivecraft/mcore3/lib/bson/util/annotations/ThreadSafe.java b/src/com/massivecraft/mcore3/lib/bson/util/annotations/ThreadSafe.java
new file mode 100644
index 00000000..3b2cbd6c
--- /dev/null
+++ b/src/com/massivecraft/mcore3/lib/bson/util/annotations/ThreadSafe.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2005 Brian Goetz and Tim Peierls
+ * Released under the Creative Commons Attribution License
+ * (http://creativecommons.org/licenses/by/2.5)
+ * Official home: http://www.jcip.net
+ *
+ * Any republication or derived work distributed in source code form
+ * must include this copyright and license notice.
+ */
+
+package com.massivecraft.mcore3.lib.bson.util.annotations;
+
+import java.lang.annotation.*;
+
+
+/**
+ * The class to which this annotation is applied is thread-safe. This means that
+ * no sequences of accesses (reads and writes to public fields, calls to public methods)
+ * may put the object into an invalid state, regardless of the interleaving of those actions
+ * by the runtime, and without requiring any additional synchronization or coordination on the
+ * part of the caller.
+ */
+@Documented
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface ThreadSafe {
+}
diff --git a/src/com/massivecraft/mcore3/lib/bson/util/package.html b/src/com/massivecraft/mcore3/lib/bson/util/package.html
new file mode 100644
index 00000000..50f680e3
--- /dev/null
+++ b/src/com/massivecraft/mcore3/lib/bson/util/package.html
@@ -0,0 +1,3 @@
+
+ Misc utils used by BSON.
+
diff --git a/src/com/massivecraft/mcore3/lib/mongodb/BasicDBList.java b/src/com/massivecraft/mcore3/lib/mongodb/BasicDBList.java
new file mode 100644
index 00000000..87197c9d
--- /dev/null
+++ b/src/com/massivecraft/mcore3/lib/mongodb/BasicDBList.java
@@ -0,0 +1,67 @@
+// BasicDBList.java
+
+/**
+ * Copyright (C) 2008 10gen 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 com.massivecraft.mcore3.lib.mongodb;
+
+
+import com.massivecraft.mcore3.lib.bson.types.BasicBSONList;
+import com.massivecraft.mcore3.lib.mongodb.util.JSON;
+
+/**
+ * a basic implementation of bson list that is mongo specific
+ * @author antoine
+ */
+public class BasicDBList extends BasicBSONList implements DBObject {
+
+ private static final long serialVersionUID = -4415279469780082174L;
+
+ /**
+ * Returns a JSON serialization of this object
+ * @return JSON serialization
+ */
+ @Override
+ public String toString(){
+ return JSON.serialize( this );
+ }
+
+ public boolean isPartialObject(){
+ return _isPartialObject;
+ }
+
+ public void markAsPartialObject(){
+ _isPartialObject = true;
+ }
+
+ public Object copy() {
+ // copy field values into new object
+ BasicDBList newobj = new BasicDBList();
+ // need to clone the sub obj
+ for (int i = 0; i < size(); ++i) {
+ Object val = get(i);
+ if (val instanceof BasicDBObject) {
+ val = ((BasicDBObject)val).copy();
+ } else if (val instanceof BasicDBList) {
+ val = ((BasicDBList)val).copy();
+ }
+ newobj.add(val);
+ }
+ return newobj;
+ }
+
+ private boolean _isPartialObject;
+}
diff --git a/src/com/massivecraft/mcore3/lib/mongodb/BasicDBObject.java b/src/com/massivecraft/mcore3/lib/mongodb/BasicDBObject.java
new file mode 100644
index 00000000..e8b8c161
--- /dev/null
+++ b/src/com/massivecraft/mcore3/lib/mongodb/BasicDBObject.java
@@ -0,0 +1,110 @@
+// BasicDBObject.java
+
+/**
+ * Copyright (C) 2008 10gen 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 com.massivecraft.mcore3.lib.mongodb;
+
+import java.util.Map;
+
+
+import com.massivecraft.mcore3.lib.bson.BasicBSONObject;
+import com.massivecraft.mcore3.lib.mongodb.util.JSON;
+
+/**
+ * a basic implementation of bson object that is mongo specific.
+ * A DBObject
can be created as follows, using this class:
+ *
+ * DBObject obj = new BasicDBObject();
+ * obj.put( "foo", "bar" );
+ *
+ */
+public class BasicDBObject extends BasicBSONObject implements DBObject {
+
+ private static final long serialVersionUID = -4415279469780082174L;
+
+ /**
+ * Creates an empty object.
+ */
+ public BasicDBObject(){
+ }
+
+ /**
+ * creates an empty object
+ * @param size an estimate of number of fields that will be inserted
+ */
+ public BasicDBObject(int size){
+ super(size);
+ }
+
+ /**
+ * creates an object with the given key/value
+ * @param key key under which to store
+ * @param value value to stor
+ */
+ public BasicDBObject(String key, Object value){
+ super(key, value);
+ }
+
+ /**
+ * Creates an object from a map.
+ * @param m map to convert
+ */
+ @SuppressWarnings("rawtypes")
+ public BasicDBObject(Map m) {
+ super(m);
+ }
+
+ public boolean isPartialObject(){
+ return _isPartialObject;
+ }
+
+ public void markAsPartialObject(){
+ _isPartialObject = true;
+ }
+
+ /**
+ * Returns a JSON serialization of this object
+ * @return JSON serialization
+ */
+ @Override
+ public String toString(){
+ return JSON.serialize( this );
+ }
+
+ @Override
+ public BasicDBObject append( String key , Object val ){
+ put( key , val );
+ return this;
+ }
+
+ public Object copy() {
+ // copy field values into new object
+ BasicDBObject newobj = new BasicDBObject(this.toMap());
+ // need to clone the sub obj
+ for (String field : keySet()) {
+ Object val = get(field);
+ if (val instanceof BasicDBObject) {
+ newobj.put(field, ((BasicDBObject)val).copy());
+ } else if (val instanceof BasicDBList) {
+ newobj.put(field, ((BasicDBList)val).copy());
+ }
+ }
+ return newobj;
+ }
+
+ private boolean _isPartialObject = false;
+}
diff --git a/src/com/massivecraft/mcore3/lib/mongodb/BasicDBObjectBuilder.java b/src/com/massivecraft/mcore3/lib/mongodb/BasicDBObjectBuilder.java
new file mode 100644
index 00000000..28f2b8dc
--- /dev/null
+++ b/src/com/massivecraft/mcore3/lib/mongodb/BasicDBObjectBuilder.java
@@ -0,0 +1,142 @@
+// BasicDBObjectBuilder.java
+
+/**
+ * Copyright (C) 2008 10gen 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 com.massivecraft.mcore3.lib.mongodb;
+
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.Map;
+
+/**
+ * utility for building complex objects
+ * example:
+ * BasicDBObjectBuilder.start().add( "name" , "eliot" ).add( "number" , 17 ).get()
+ */
+public class BasicDBObjectBuilder {
+
+ /**
+ * creates an empty object
+ */
+ public BasicDBObjectBuilder(){
+ _stack = new LinkedList();
+ _stack.add( new BasicDBObject() );
+ }
+
+ /**
+ * Creates an empty object
+ * @return The new empty builder
+ */
+ public static BasicDBObjectBuilder start(){
+ return new BasicDBObjectBuilder();
+ }
+
+ /**
+ * creates an object with the given key/value
+ * @param k The field name
+ * @param val The value
+ */
+ public static BasicDBObjectBuilder start( String k , Object val ){
+ return (new BasicDBObjectBuilder()).add( k , val );
+ }
+
+ /**
+ * Creates an object builder from an existing map.
+ * @param m map to use
+ * @return the new builder
+ */
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ public static BasicDBObjectBuilder start(Map m){
+ BasicDBObjectBuilder b = new BasicDBObjectBuilder();
+ Iterator i = m.entrySet().iterator();
+ while (i.hasNext()) {
+ Map.Entry entry = i.next();
+ b.add(entry.getKey().toString(), entry.getValue());
+ }
+ return b;
+ }
+
+ /**
+ * appends the key/value to the active object
+ * @param key
+ * @param val
+ * @return returns itself so you can chain
+ */
+ public BasicDBObjectBuilder append( String key , Object val ){
+ _cur().put( key , val );
+ return this;
+ }
+
+
+ /**
+ * same as appends
+ * @see #append(String, Object)
+ * @param key
+ * @param val
+ * @return returns itself so you can chain
+ */
+ public BasicDBObjectBuilder add( String key , Object val ){
+ return append( key, val );
+ }
+
+ /**
+ * creates an new empty object and inserts it into the current object with the given key.
+ * The new child object becomes the active one.
+ * @param key
+ * @return returns itself so you can chain
+ */
+ public BasicDBObjectBuilder push( String key ){
+ BasicDBObject o = new BasicDBObject();
+ _cur().put( key , o );
+ _stack.addLast( o );
+ return this;
+ }
+
+ /**
+ * pops the active object, which means that the parent object becomes active
+ * @return returns itself so you can chain
+ */
+ public BasicDBObjectBuilder pop(){
+ if ( _stack.size() <= 1 )
+ throw new IllegalArgumentException( "can't pop last element" );
+ _stack.removeLast();
+ return this;
+ }
+
+ /**
+ * gets the base object
+ * @return The base object
+ */
+ public DBObject get(){
+ return _stack.getFirst();
+ }
+
+ /**
+ * returns true if no key/value was inserted into base object
+ * @return True if empty
+ */
+ public boolean isEmpty(){
+ return ((BasicDBObject) _stack.getFirst()).size() == 0;
+ }
+
+ private DBObject _cur(){
+ return _stack.getLast();
+ }
+
+ private final LinkedList _stack;
+
+}
diff --git a/src/com/massivecraft/mcore3/lib/mongodb/Bytes.java b/src/com/massivecraft/mcore3/lib/mongodb/Bytes.java
new file mode 100644
index 00000000..68fa6667
--- /dev/null
+++ b/src/com/massivecraft/mcore3/lib/mongodb/Bytes.java
@@ -0,0 +1,227 @@
+// Bytes.java
+
+/**
+ * Copyright (C) 2008 10gen 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 com.massivecraft.mcore3.lib.mongodb;
+
+import java.nio.ByteOrder;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import com.massivecraft.mcore3.lib.bson.BSON;
+import com.massivecraft.mcore3.lib.bson.types.BSONTimestamp;
+import com.massivecraft.mcore3.lib.bson.types.Code;
+import com.massivecraft.mcore3.lib.bson.types.CodeWScope;
+import com.massivecraft.mcore3.lib.bson.types.ObjectId;
+
+/**
+ * Class that hold definitions of the wire protocol
+ * @author antoine
+ */
+public class Bytes extends BSON {
+
+ static final Logger LOGGER = Logger.getLogger( "com.mongodb" );
+
+ static final boolean D = Boolean.getBoolean( "DEBUG.MONGO" );
+
+ static {
+ if ( LOGGER.getLevel() == null ){
+ if ( D )
+ LOGGER.setLevel( Level.ALL );
+ else
+ LOGGER.setLevel( Level.WARNING );
+ }
+ }
+
+ /** Little-endian */
+ public static final ByteOrder ORDER = ByteOrder.LITTLE_ENDIAN;
+
+ /** this size is set low to 4MB, but just serves as safe default */
+ static final int MAX_OBJECT_SIZE = 1024 * 1024 * 4;
+
+ /** default target size of an insert batch */
+ static final int BATCH_INSERT_SIZE = 1024 * 1024 * 8;
+
+ static final int CONNECTIONS_PER_HOST = Integer.parseInt( System.getProperty( "MONGO.POOLSIZE" , "10" ) );
+
+
+ // --- network protocol options
+
+ /**
+ * Tailable means cursor is not closed when the last data is retrieved.
+ * Rather, the cursor marks the final object's position.
+ * You can resume using the cursor later, from where it was located, if more data were received.
+ * Like any "latent cursor", the cursor may become invalid at some point (CursorNotFound) – for example if the final object it references were deleted.
+ */
+ public static final int QUERYOPTION_TAILABLE = 1 << 1;
+ /**
+ * When turned on, read queries will be directed to slave servers instead of the primary server.
+ */
+ public static final int QUERYOPTION_SLAVEOK = 1 << 2;
+ /**
+ * Internal replication use only - driver should not set
+ */
+ public static final int QUERYOPTION_OPLOGREPLAY = 1 << 3;
+ /**
+ * The server normally times out idle cursors after an inactivity period (10 minutes) to prevent excess memory use.
+ * Set this option to prevent that.
+ */
+ public static final int QUERYOPTION_NOTIMEOUT = 1 << 4;
+
+ /**
+ * Use with TailableCursor.
+ * If we are at the end of the data, block for a while rather than returning no data.
+ * After a timeout period, we do return as normal.
+ */
+ public static final int QUERYOPTION_AWAITDATA = 1 << 5;
+
+ /**
+ * Stream the data down full blast in multiple "more" packages, on the assumption that the client will fully read all data queried.
+ * Faster when you are pulling a lot of data and know you want to pull it all down.
+ * Note: the client is not allowed to not read all the data unless it closes the connection.
+ */
+ public static final int QUERYOPTION_EXHAUST = 1 << 6;
+
+ /**
+ * Use with sharding (mongos).
+ * Allows partial results from a sharded system if any shards are down/missing from the cluster. If not used an error will be returned
+ * from the mongos server.
+ */
+ public static final int QUERYOPTION_PARTIAL = 1 << 7;
+
+ /**
+ * Set when getMore is called but the cursor id is not valid at the server.
+ * Returned with zero results.
+ */
+ public static final int RESULTFLAG_CURSORNOTFOUND = 1;
+ /**
+ * Set when query failed.
+ * Results consist of one document containing an "$err" field describing the failure.
+ */
+ public static final int RESULTFLAG_ERRSET = 2;
+ /**
+ * Drivers should ignore this.
+ * Only mongos will ever see this set, in which case, it needs to update config from the server.
+ */
+ public static final int RESULTFLAG_SHARDCONFIGSTALE = 4;
+ /**
+ * Set when the server supports the AwaitData Query option.
+ * If it doesn't, a client should sleep a little between getMore's of a Tailable cursor.
+ * Mongod version 1.6 supports AwaitData and thus always sets AwaitCapable.
+ */
+ public static final int RESULTFLAG_AWAITCAPABLE = 8;
+
+
+ static class OptionHolder {
+ OptionHolder( OptionHolder parent ){
+ _parent = parent;
+ }
+
+ void set( int options ){
+ _options = options;
+ _hasOptions = true;
+ }
+
+ int get(){
+ if ( _hasOptions )
+ return _options;
+ if ( _parent == null )
+ return 0;
+ return _parent.get();
+ }
+
+ void add( int option ){
+ set( get() | option );
+ }
+
+ void reset(){
+ _hasOptions = false;
+ }
+
+ final OptionHolder _parent;
+
+ int _options = 0;
+ boolean _hasOptions = false;
+ }
+
+ /**
+ * Gets the type byte for a given object.
+ * @param o the object
+ * @return the byte value associated with the type, or -1 if no type is matched
+ */
+ @SuppressWarnings("deprecation")
+ public static byte getType( Object o ){
+ if ( o == null )
+ return NULL;
+
+ if ( o instanceof DBPointer )
+ return REF;
+
+ if (o instanceof Integer
+ || o instanceof Short
+ || o instanceof Byte
+ || o instanceof AtomicInteger) {
+ return NUMBER_INT;
+ }
+
+ if (o instanceof Long || o instanceof AtomicLong) {
+ return NUMBER_LONG;
+ }
+
+ if ( o instanceof Number )
+ return NUMBER;
+
+ if ( o instanceof String )
+ return STRING;
+
+ if ( o instanceof java.util.List )
+ return ARRAY;
+
+ if ( o instanceof byte[] )
+ return BINARY;
+
+ if ( o instanceof ObjectId )
+ return OID;
+
+ if ( o instanceof Boolean )
+ return BOOLEAN;
+
+ if ( o instanceof java.util.Date )
+ return DATE;
+
+ if ( o instanceof BSONTimestamp )
+ return TIMESTAMP;
+
+ if ( o instanceof java.util.regex.Pattern )
+ return REGEX;
+
+ if ( o instanceof DBObject || o instanceof DBRefBase )
+ return OBJECT;
+
+ if ( o instanceof Code )
+ return CODE;
+
+ if ( o instanceof CodeWScope )
+ return CODE_W_SCOPE;
+
+ return -1;
+ }
+
+ static final ObjectId COLLECTION_REF_ID = new ObjectId( -1 , -1 , -1 );
+}
diff --git a/src/com/massivecraft/mcore3/lib/mongodb/CommandResult.java b/src/com/massivecraft/mcore3/lib/mongodb/CommandResult.java
new file mode 100644
index 00000000..79f0989f
--- /dev/null
+++ b/src/com/massivecraft/mcore3/lib/mongodb/CommandResult.java
@@ -0,0 +1,153 @@
+// CommandResult.java
+/**
+ * Copyright (C) 2008 10gen 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 com.massivecraft.mcore3.lib.mongodb;
+
+
+/**
+ * A simple wrapper for the result of getLastError() calls and other commands
+ */
+public class CommandResult extends BasicDBObject {
+
+ CommandResult(ServerAddress srv) {
+ this(null, srv);
+ }
+
+ CommandResult(DBObject cmd, ServerAddress srv) {
+ if (srv == null) {
+ throw new IllegalArgumentException("server address is null");
+ }
+ _cmd = cmd;
+ _host = srv;
+ //so it is shown in toString/debug
+ put("serverUsed", srv.toString());
+ }
+
+ /**
+ * gets the "ok" field which is the result of the command
+ * @return True if ok
+ */
+ public boolean ok(){
+ Object o = get( "ok" );
+ if ( o == null )
+ throw new IllegalArgumentException( "'ok' should never be null..." );
+
+ if ( o instanceof Boolean )
+ return (Boolean) o;
+
+ if ( o instanceof Number )
+ return ((Number)o).intValue() == 1;
+
+ throw new IllegalArgumentException( "can't figure out what to do with: " + o.getClass().getName() );
+ }
+
+ /**
+ * gets the "errmsg" field which holds the error message
+ * @return The error message or null
+ */
+ public String getErrorMessage(){
+ Object foo = get( "errmsg" );
+ if ( foo == null )
+ return null;
+ return foo.toString();
+ }
+
+ /**
+ * utility method to create an exception with the command name
+ * @return The mongo exception or null
+ */
+ public MongoException getException(){
+ if ( !ok() ) {
+ StringBuilder buf = new StringBuilder();
+
+ String cmdName;
+ if (_cmd != null) {
+ cmdName = _cmd.keySet().iterator().next();
+ buf.append( "command failed [" ).append( cmdName ).append( "]: " );
+ } else {
+ buf.append( "operation failed: ");
+ }
+
+ buf.append( toString() );
+
+ return new CommandFailure( this , buf.toString() );
+ } else {
+ // GLE check
+ if ( hasErr() ) {
+ Object foo = get( "err" );
+
+ int code = getCode();
+
+ String s = foo.toString();
+ if ( code == 11000 || code == 11001 || s.startsWith( "E11000" ) || s.startsWith( "E11001" ) )
+ return new MongoException.DuplicateKey( code , s );
+
+ return new MongoException( code , s );
+ }
+ }
+
+ //all good, should never get here.
+ return null;
+ }
+
+ /**
+ * returns the "code" field, as an int
+ * @return -1 if there is no code
+ */
+ private int getCode(){
+ int code = -1;
+ if ( get( "code" ) instanceof Number )
+ code = ((Number)get("code")).intValue();
+ return code;
+ }
+
+ /**
+ * check the "err" field
+ * @return if it has it, and isn't null
+ */
+ boolean hasErr(){
+ Object o = get( "err" );
+ return (o != null && ( (String) o ).length() > 0 );
+ }
+
+ /**
+ * throws an exception containing the cmd name, in case the command failed, or the "err/code" information
+ * @throws MongoException
+ */
+ public void throwOnError() throws MongoException {
+ if ( !ok() || hasErr() ){
+ throw getException();
+ }
+ }
+
+ public ServerAddress getServerUsed() {
+ return _host;
+ }
+
+ private final DBObject _cmd;
+ private final ServerAddress _host;
+ private static final long serialVersionUID = 1L;
+
+ static class CommandFailure extends MongoException {
+ private static final long serialVersionUID = 1L;
+
+ CommandFailure( CommandResult res , String msg ){
+ super( ServerError.getCode( res ) , msg );
+ }
+ }
+}
diff --git a/src/com/massivecraft/mcore3/lib/mongodb/DB.java b/src/com/massivecraft/mcore3/lib/mongodb/DB.java
new file mode 100644
index 00000000..55ffe0c9
--- /dev/null
+++ b/src/com/massivecraft/mcore3/lib/mongodb/DB.java
@@ -0,0 +1,729 @@
+// DB.java
+
+/**
+ * Copyright (C) 2008 10gen 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 com.massivecraft.mcore3.lib.mongodb;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+
+import com.massivecraft.mcore3.lib.mongodb.DBApiLayer.Result;
+import com.massivecraft.mcore3.lib.mongodb.util.Util;
+
+/**
+ * an abstract class that represents a logical database on a server
+ * @dochub databases
+ */
+public abstract class DB {
+
+ /**
+ * @param mongo the mongo instance
+ * @param name the database name
+ */
+ public DB( Mongo mongo , String name ){
+ _mongo = mongo;
+ _name = name;
+ _options = new Bytes.OptionHolder( _mongo._netOptions );
+ }
+
+ /**
+ * starts a new "consistent request".
+ * Following this call and until requestDone() is called, all db operations should use the same underlying connection.
+ * This is useful to ensure that operations happen in a certain order with predictable results.
+ */
+ public abstract void requestStart();
+
+ /**
+ * ends the current "consistent request"
+ */
+ public abstract void requestDone();
+
+ /**
+ * ensure that a connection is assigned to the current "consistent request" (from primary pool, if connected to a replica set)
+ */
+ public abstract void requestEnsureConnection();
+
+ /**
+ * Returns the collection represented by the string <dbName>.<collectionName>.
+ * @param name the name of the collection
+ * @return the collection
+ */
+ protected abstract DBCollection doGetCollection( String name );
+
+ /**
+ * Gets a collection with a given name.
+ * If the collection does not exist, a new collection is created.
+ * @param name the name of the collection to return
+ * @return the collection
+ */
+ public DBCollection getCollection( String name ){
+ DBCollection c = doGetCollection( name );
+ return c;
+ }
+
+ /**
+ * Creates a collection with a given name and options.
+ * If the collection does not exist, a new collection is created.
+ * Note that if the options parameter is null, the creation will be deferred to when the collection is written to.
+ * Possible options:
+ *
+ * capped boolean : if the collection is capped
+ * size int : collection size (in bytes)
+ * max int : max number of documents
+ *
+ * @param name the name of the collection to return
+ * @param options options
+ * @return the collection
+ */
+ public DBCollection createCollection( String name, DBObject options ){
+ if ( options != null ){
+ DBObject createCmd = new BasicDBObject("create", name);
+ createCmd.putAll(options);
+ CommandResult result = command(createCmd);
+ result.throwOnError();
+ }
+ return getCollection(name);
+ }
+
+
+ /**
+ * Returns a collection matching a given string.
+ * @param s the name of the collection
+ * @return the collection
+ */
+ public DBCollection getCollectionFromString( String s ){
+ DBCollection foo = null;
+
+ int idx = s.indexOf( "." );
+ while ( idx >= 0 ){
+ String b = s.substring( 0 , idx );
+ s = s.substring( idx + 1 );
+ if ( foo == null )
+ foo = getCollection( b );
+ else
+ foo = foo.getCollection( b );
+ idx = s.indexOf( "." );
+ }
+
+ if ( foo != null )
+ return foo.getCollection( s );
+ return getCollection( s );
+ }
+
+ /**
+ * Executes a database command.
+ * This method calls {@link DB#command(com.massivecraft.mcore3.lib.mongodb.DBObject, int) } with 0 as query option.
+ * @see List of Commands
+ * @param cmd dbobject representing the command to execute
+ * @return result of command from the database
+ * @throws MongoException
+ * @dochub commands
+ */
+ public CommandResult command( DBObject cmd ) throws MongoException{
+ return command( cmd, 0 );
+ }
+
+ public CommandResult command( DBObject cmd, DBEncoder encoder ) throws MongoException{
+ return command( cmd, 0, encoder );
+ }
+
+ public CommandResult command( DBObject cmd , int options, DBEncoder encoder )
+ throws MongoException {
+ return command(cmd, options, null, encoder);
+ }
+
+ public CommandResult command( DBObject cmd , int options, ReadPreference readPrefs )
+ throws MongoException {
+ return command(cmd, options, readPrefs, DefaultDBEncoder.FACTORY.create());
+ }
+
+ /**
+ * Executes a database command.
+ * @see List of Commands
+ * @param cmd dbobject representing the command to execute
+ * @param options query options to use
+ * @param readPrefs ReadPreferences for this command (nodes selection is the biggest part of this)
+ * @return result of command from the database
+ * @dochub commands
+ * @throws MongoException
+ */
+ public CommandResult command( DBObject cmd , int options, ReadPreference readPrefs, DBEncoder encoder )
+ throws MongoException {
+
+ Iterator i =
+ getCollection("$cmd").__find(cmd, new BasicDBObject(), 0, -1, 0, options, readPrefs ,
+ DefaultDBDecoder.FACTORY.create(), encoder);
+ if ( i == null || ! i.hasNext() )
+ return null;
+
+ DBObject res = i.next();
+ ServerAddress sa = (i instanceof Result) ? ((Result) i).getServerAddress() : null;
+ CommandResult cr = new CommandResult(cmd, sa);
+ cr.putAll( res );
+ return cr;
+ }
+
+ /**
+ * Executes a database command.
+ * @see List of Commands
+ * @param cmd dbobject representing the command to execute
+ * @param options query options to use
+ * @return result of command from the database
+ * @dochub commands
+ * @throws MongoException
+ */
+ public CommandResult command( DBObject cmd , int options )
+ throws MongoException {
+ return command(cmd, options, getReadPreference());
+ }
+ /**
+ * Executes a database command.
+ * This method constructs a simple dbobject and calls {@link DB#command(com.massivecraft.mcore3.lib.mongodb.DBObject) }
+ * @see List of Commands
+ * @param cmd command to execute
+ * @return result of command from the database
+ * @throws MongoException
+ */
+ public CommandResult command( String cmd )
+ throws MongoException {
+ return command( new BasicDBObject( cmd , Boolean.TRUE ) );
+ }
+
+ /**
+ * Executes a database command.
+ * This method constructs a simple dbobject and calls {@link DB#command(com.massivecraft.mcore3.lib.mongodb.DBObject, int) }
+ * @see List of Commands
+ * @param cmd command to execute
+ * @param options query options to use
+ * @return result of command from the database
+ * @throws MongoException
+ */
+ public CommandResult command( String cmd, int options )
+ throws MongoException {
+ return command( new BasicDBObject( cmd , Boolean.TRUE ), options );
+ }
+
+ /**
+ * evaluates a function on the database.
+ * This is useful if you need to touch a lot of data lightly, in which case network transfer could be a bottleneck.
+ * @param code the function in javascript code
+ * @param args arguments to be passed to the function
+ * @return The command result
+ * @throws MongoException
+ */
+ public CommandResult doEval( String code , Object ... args )
+ throws MongoException {
+
+ return command( BasicDBObjectBuilder.start()
+ .add( "$eval" , code )
+ .add( "args" , args )
+ .get() );
+ }
+
+ /**
+ * calls {@link DB#doEval(java.lang.String, java.lang.Object[]) }.
+ * If the command is successful, the "retval" field is extracted and returned.
+ * Otherwise an exception is thrown.
+ * @param code the function in javascript code
+ * @param args arguments to be passed to the function
+ * @return The object
+ * @throws MongoException
+ */
+ public Object eval( String code , Object ... args )
+ throws MongoException {
+
+ CommandResult res = doEval( code , args );
+ res.throwOnError();
+ return res.get( "retval" );
+ }
+
+ /**
+ * Returns the result of "dbstats" command
+ * @return
+ */
+ public CommandResult getStats() {
+ return command("dbstats");
+ }
+
+ /**
+ * Returns the name of this database.
+ * @return the name
+ */
+ public String getName(){
+ return _name;
+ }
+
+ /**
+ * Makes this database read-only.
+ * Important note: this is a convenience setting that is only known on the client side and not persisted.
+ * @param b if the database should be read-only
+ */
+ public void setReadOnly( Boolean b ){
+ _readOnly = b;
+ }
+
+ /**
+ * Returns a set containing the names of all collections in this database.
+ * @return the names of collections in this database
+ * @throws MongoException
+ */
+ public Set getCollectionNames()
+ throws MongoException {
+
+ DBCollection namespaces = getCollection("system.namespaces");
+ if (namespaces == null)
+ throw new RuntimeException("this is impossible");
+
+ Iterator i = namespaces.__find(new BasicDBObject(), null, 0, 0, 0, getOptions(), getReadPreference(), null);
+ if (i == null)
+ return new HashSet();
+
+ List tables = new ArrayList();
+
+ for (; i.hasNext();) {
+ DBObject o = i.next();
+ if ( o.get( "name" ) == null ){
+ throw new MongoException( "how is name null : " + o );
+ }
+ String n = o.get("name").toString();
+ int idx = n.indexOf(".");
+
+ String root = n.substring(0, idx);
+ if (!root.equals(_name))
+ continue;
+
+ if (n.indexOf("$") >= 0)
+ continue;
+
+ String table = n.substring(idx + 1);
+
+ tables.add(table);
+ }
+
+ Collections.sort(tables);
+
+ return new LinkedHashSet(tables);
+ }
+
+ /**
+ * Checks to see if a collection by name %lt;name> exists.
+ * @param collectionName The collection to test for existence
+ * @return false if no collection by that name exists, true if a match to an existing collection was found
+ */
+ public boolean collectionExists(String collectionName)
+ {
+ if (collectionName == null || "".equals(collectionName))
+ return false;
+
+ Set collections = getCollectionNames();
+ if (collections.isEmpty())
+ return false;
+
+ for (String collection : collections)
+ {
+ if (collectionName.equalsIgnoreCase(collection))
+ return true;
+ }
+
+ return false;
+ }
+
+
+ /**
+ * Returns the name of this database.
+ * @return the name
+ */
+ @Override
+ public String toString(){
+ return _name;
+ }
+
+ /**
+ * Gets the the error (if there is one) from the previous operation on this connection.
+ * The result of this command will look like
+ *
+ *
+ * { "err" : errorMessage , "ok" : 1.0 }
+ *
+ *
+ * The value for errorMessage will be null if no error occurred, or a description otherwise.
+ *
+ * Important note: when calling this method directly, it is undefined which connection "getLastError" is called on.
+ * You may need to explicitly use a "consistent Request", see {@link DB#requestStart()}
+ * For most purposes it is better not to call this method directly but instead use {@link WriteConcern}
+ *
+ * @return DBObject with error and status information
+ * @throws MongoException
+ */
+ public CommandResult getLastError()
+ throws MongoException {
+ return command(new BasicDBObject("getlasterror", 1));
+ }
+
+ /**
+ * @see {@link DB#getLastError() }
+ * @param concern the concern associated with "getLastError" call
+ * @return
+ * @throws MongoException
+ */
+ public CommandResult getLastError( com.massivecraft.mcore3.lib.mongodb.WriteConcern concern )
+ throws MongoException {
+ return command( concern.getCommand() );
+ }
+
+ /**
+ * @see {@link DB#getLastError(com.massivecraft.mcore3.lib.mongodb.WriteConcern) }
+ * @param w
+ * @param wtimeout
+ * @param fsync
+ * @return The command result
+ * @throws MongoException
+ */
+ public CommandResult getLastError( int w , int wtimeout , boolean fsync )
+ throws MongoException {
+ return command( (new com.massivecraft.mcore3.lib.mongodb.WriteConcern( w, wtimeout , fsync )).getCommand() );
+ }
+
+
+ /**
+ * Sets the write concern for this database. It Will be used for
+ * writes to any collection in this database. See the
+ * documentation for {@link WriteConcern} for more information.
+ * @param concern write concern to use
+ */
+ public void setWriteConcern( com.massivecraft.mcore3.lib.mongodb.WriteConcern concern ){
+ if (concern == null) throw new IllegalArgumentException();
+ _concern = concern;
+ }
+
+ /**
+ * Gets the write concern for this database.
+ * @return
+ */
+ public com.massivecraft.mcore3.lib.mongodb.WriteConcern getWriteConcern(){
+ if ( _concern != null )
+ return _concern;
+ return _mongo.getWriteConcern();
+ }
+
+ /**
+ * Sets the read preference for this database. Will be used as default for
+ * reads from any collection in this database. See the
+ * documentation for {@link ReadPreference} for more information.
+ *
+ * @param preference Read Preference to use
+ */
+ public void setReadPreference( ReadPreference preference ){
+ _readPref = preference;
+ }
+
+ /**
+ * Gets the default read preference
+ * @return
+ */
+ public ReadPreference getReadPreference(){
+ if ( _readPref != null )
+ return _readPref;
+ return _mongo.getReadPreference();
+ }
+
+ /**
+ * Drops this database. Removes all data on disk. Use with caution.
+ * @throws MongoException
+ */
+ public void dropDatabase()
+ throws MongoException {
+
+ CommandResult res = command(new BasicDBObject("dropDatabase", 1));
+ res.throwOnError();
+ _mongo._dbs.remove(this.getName());
+ }
+
+ /**
+ * Returns true if a user has been authenticated
+ *
+ * @return true if authenticated, false otherwise
+ * @dochub authenticate
+ */
+ public boolean isAuthenticated() {
+ return ( _username != null );
+ }
+
+ /**
+ * Authenticates to db with the given name and password
+ *
+ * @param username name of user for this database
+ * @param passwd password of user for this database
+ * @return true if authenticated, false otherwise
+ * @throws MongoException
+ * @dochub authenticate
+ */
+ public boolean authenticate(String username, char[] passwd )
+ throws MongoException {
+
+ if ( username == null || passwd == null )
+ throw new NullPointerException( "username can't be null" );
+
+ if ( _username != null )
+ throw new IllegalStateException( "can't call authenticate twice on the same DBObject" );
+
+ String hash = _hash( username , passwd );
+ CommandResult res = _doauth( username , hash.getBytes() );
+ if ( !res.ok())
+ return false;
+ _username = username;
+ _authhash = hash.getBytes();
+ return true;
+ }
+
+ /**
+ * Authenticates to db with the given name and password
+ *
+ * @param username name of user for this database
+ * @param passwd password of user for this database
+ * @return the CommandResult from authenticate command
+ * @throws MongoException if authentication failed due to invalid user/pass, or other exceptions like I/O
+ * @dochub authenticate
+ */
+ public CommandResult authenticateCommand(String username, char[] passwd )
+ throws MongoException {
+
+ if ( username == null || passwd == null )
+ throw new NullPointerException( "username can't be null" );
+
+ if ( _username != null )
+ throw new IllegalStateException( "can't call authenticate twice on the same DBObject" );
+
+ String hash = _hash( username , passwd );
+ CommandResult res = _doauth( username , hash.getBytes() );
+ res.throwOnError();
+ _username = username;
+ _authhash = hash.getBytes();
+ return res;
+ }
+
+ /*
+ boolean reauth(){
+ if ( _username == null || _authhash == null )
+ throw new IllegalStateException( "no auth info!" );
+ return _doauth( _username , _authhash );
+ }
+ */
+
+ DBObject _authCommand( String nonce ){
+ if ( _username == null || _authhash == null )
+ throw new IllegalStateException( "no auth info!" );
+
+ return _authCommand( nonce , _username , _authhash );
+ }
+
+ static DBObject _authCommand( String nonce , String username , byte[] hash ){
+ String key = nonce + username + new String( hash );
+
+ BasicDBObject cmd = new BasicDBObject();
+
+ cmd.put("authenticate", 1);
+ cmd.put("user", username);
+ cmd.put("nonce", nonce);
+ cmd.put("key", Util.hexMD5(key.getBytes()));
+
+ return cmd;
+ }
+
+ private CommandResult _doauth( String username , byte[] hash ){
+ CommandResult res = command(new BasicDBObject("getnonce", 1));
+ res.throwOnError();
+
+ DBObject cmd = _authCommand( res.getString( "nonce" ) , username , hash );
+ return command(cmd);
+ }
+
+ /**
+ * Adds a new user for this db
+ * @param username
+ * @param passwd
+ */
+ public WriteResult addUser( String username , char[] passwd ){
+ return addUser(username, passwd, false);
+ }
+
+ /**
+ * Adds a new user for this db
+ * @param username
+ * @param passwd
+ * @param readOnly if true, user will only be able to read
+ */
+ public WriteResult addUser( String username , char[] passwd, boolean readOnly ){
+ DBCollection c = getCollection( "system.users" );
+ DBObject o = c.findOne( new BasicDBObject( "user" , username ) );
+ if ( o == null )
+ o = new BasicDBObject( "user" , username );
+ o.put( "pwd" , _hash( username , passwd ) );
+ o.put( "readOnly" , readOnly );
+ return c.save( o );
+ }
+
+ /**
+ * Removes a user for this db
+ * @param username
+ */
+ public WriteResult removeUser( String username ){
+ DBCollection c = getCollection( "system.users" );
+ return c.remove(new BasicDBObject( "user" , username ));
+ }
+
+ String _hash( String username , char[] passwd ){
+ ByteArrayOutputStream bout = new ByteArrayOutputStream( username.length() + 20 + passwd.length );
+ try {
+ bout.write( username.getBytes() );
+ bout.write( ":mongo:".getBytes() );
+ for ( int i=0; i= 128 )
+ throw new IllegalArgumentException( "can't handle non-ascii passwords yet" );
+ bout.write( (byte)passwd[i] );
+ }
+ }
+ catch ( IOException ioe ){
+ throw new RuntimeException( "impossible" , ioe );
+ }
+ return Util.hexMD5( bout.toByteArray() );
+ }
+
+ /**
+ * Returns the last error that occurred since start of database or a call to resetError()
+ *
+ * The return object will look like
+ *
+ *
+ * { err : errorMessage, nPrev : countOpsBack, ok : 1 }
+ *
+ *
+ * The value for errorMessage will be null of no error has occurred, otherwise the error message.
+ * The value of countOpsBack will be the number of operations since the error occurred.
+ *
+ * Care must be taken to ensure that calls to getPreviousError go to the same connection as that
+ * of the previous operation.
+ * See {@link DB#requestStart()} for more information.
+ *
+ * @return DBObject with error and status information
+ * @throws MongoException
+ */
+ public CommandResult getPreviousError()
+ throws MongoException {
+ return command(new BasicDBObject("getpreverror", 1));
+ }
+
+ /**
+ * Resets the error memory for this database.
+ * Used to clear all errors such that {@link DB#getPreviousError()} will return no error.
+ * @throws MongoException
+ */
+ public void resetError()
+ throws MongoException {
+ command(new BasicDBObject("reseterror", 1));
+ }
+
+ /**
+ * For testing purposes only - this method forces an error to help test error handling
+ * @throws MongoException
+ */
+ public void forceError()
+ throws MongoException {
+ command(new BasicDBObject("forceerror", 1));
+ }
+
+ /**
+ * Gets the Mongo instance
+ * @return
+ */
+ public Mongo getMongo(){
+ return _mongo;
+ }
+
+ /**
+ * Gets another database on same server
+ * @param name name of the database
+ * @return
+ */
+ public DB getSisterDB( String name ){
+ return _mongo.getDB( name );
+ }
+
+ /**
+ * Makes it possible to execute "read" queries on a slave node
+ *
+ * @deprecated Replaced with ReadPreference.SECONDARY
+ * @see com.massivecraft.mcore3.lib.mongodb.ReadPreference.SECONDARY
+ */
+ @Deprecated
+ public void slaveOk(){
+ addOption( Bytes.QUERYOPTION_SLAVEOK );
+ }
+
+ /**
+ * Adds the give option
+ * @param option
+ */
+ public void addOption( int option ){
+ _options.add( option );
+ }
+
+ /**
+ * Sets the query options
+ * @param options
+ */
+ public void setOptions( int options ){
+ _options.set( options );
+ }
+
+ /**
+ * Resets the query options
+ */
+ public void resetOptions(){
+ _options.reset();
+ }
+
+ /**
+ * Gets the query options
+ * @return
+ */
+ public int getOptions(){
+ return _options.get();
+ }
+
+ public abstract void cleanCursors( boolean force ) throws MongoException;
+
+
+ final Mongo _mongo;
+ final String _name;
+
+ protected boolean _readOnly = false;
+ private com.massivecraft.mcore3.lib.mongodb.WriteConcern _concern;
+ private com.massivecraft.mcore3.lib.mongodb.ReadPreference _readPref;
+ final Bytes.OptionHolder _options;
+
+ String _username;
+ byte[] _authhash = null;
+
+}
diff --git a/src/com/massivecraft/mcore3/lib/mongodb/DBAddress.java b/src/com/massivecraft/mcore3/lib/mongodb/DBAddress.java
new file mode 100644
index 00000000..276b0744
--- /dev/null
+++ b/src/com/massivecraft/mcore3/lib/mongodb/DBAddress.java
@@ -0,0 +1,186 @@
+// DBAddress.java
+
+/**
+ * Copyright (C) 2008 10gen 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 com.massivecraft.mcore3.lib.mongodb;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+
+/**
+ * Represents a database address
+ */
+public class DBAddress extends ServerAddress {
+
+ /** Creates a new address
+ * Accepts as the parameter format:
+ *
+ *
+ * name
+ * "mydb"
+ *
+ *
+ * <host>/name
+ * "127.0.0.1/mydb"
+ *
+ *
+ * <host>:<port>/name
+ * "127.0.0.1:8080/mydb"
+ *
+ *
+ * @param urlFormat
+ * @throws UnknownHostException
+ */
+ public DBAddress( String urlFormat )
+ throws UnknownHostException {
+ super( _getHostSection( urlFormat ) );
+
+ _check( urlFormat , "urlFormat" );
+ _db = _fixName( _getDBSection( urlFormat ) );
+
+ _check( _host , "host" );
+ _check( _db , "db" );
+ }
+
+ static String _getHostSection( String urlFormat ){
+ if ( urlFormat == null )
+ throw new NullPointerException( "urlFormat can't be null" );
+ int idx = urlFormat.indexOf( "/" );
+ if ( idx >= 0 )
+ return urlFormat.substring( 0 , idx );
+ return null;
+ }
+
+ static String _getDBSection( String urlFormat ){
+ if ( urlFormat == null )
+ throw new NullPointerException( "urlFormat can't be null" );
+ int idx = urlFormat.indexOf( "/" );
+ if ( idx >= 0 )
+ return urlFormat.substring( idx + 1 );
+ return urlFormat;
+ }
+
+ static String _fixName( String name ){
+ name = name.replace( '.' , '-' );
+ return name;
+ }
+
+ /**
+ * @param other an existing DBAddress
that gives the host and port
+ * @param dbname the database to which to connect
+ * @throws UnknownHostException
+ */
+ public DBAddress( DBAddress other , String dbname )
+ throws UnknownHostException {
+ this( other._host , other._port , dbname );
+ }
+
+ /**
+ * @param host host name
+ * @param dbname database name
+ * @throws UnknownHostException
+ */
+ public DBAddress( String host , String dbname )
+ throws UnknownHostException {
+ this( host , DBPort.PORT , dbname );
+ }
+
+ /**
+ * @param host host name
+ * @param port database port
+ * @param dbname database name
+ * @throws UnknownHostException
+ */
+ public DBAddress( String host , int port , String dbname )
+ throws UnknownHostException {
+ super( host , port );
+ _db = dbname.trim();
+ }
+
+ /**
+ * @param addr host address
+ * @param port database port
+ * @param dbname database name
+ */
+ public DBAddress( InetAddress addr , int port , String dbname ){
+ super( addr , port );
+ _check( dbname , "name" );
+ _db = dbname.trim();
+ }
+
+ static void _check( String thing , String name ){
+ if ( thing == null )
+ throw new NullPointerException( name + " can't be null " );
+
+ thing = thing.trim();
+ if ( thing.length() == 0 )
+ throw new IllegalArgumentException( name + " can't be empty" );
+ }
+
+ @Override
+ public int hashCode(){
+ return super.hashCode() + _db.hashCode();
+ }
+
+ @Override
+ public boolean equals( Object other ){
+ if ( other instanceof DBAddress ){
+ DBAddress a = (DBAddress)other;
+ return
+ a._port == _port &&
+ a._db.equals( _db ) &&
+ a._host.equals( _host );
+ } else if ( other instanceof ServerAddress ){
+ return other.equals(this);
+ }
+ return false;
+ }
+
+
+ /**
+ * creates a DBAddress pointing to a different database on the same server
+ * @param name database name
+ * @return
+ */
+ public DBAddress getSister( String name ){
+ try {
+ return new DBAddress( _host , _port , name );
+ }
+ catch ( UnknownHostException uh ){
+ throw new MongoInternalException( "shouldn't be possible" , uh );
+ }
+ }
+
+ /**
+ * gets the database name
+ * @return
+ */
+ public String getDBName(){
+ return _db;
+ }
+
+ /**
+ * gets a String representation of address as host:port/dbname.
+ * @return this address
+ */
+ @Override
+ public String toString(){
+ return super.toString() + "/" + _db;
+ }
+
+ final String _db;
+}
diff --git a/src/com/massivecraft/mcore3/lib/mongodb/DBApiLayer.java b/src/com/massivecraft/mcore3/lib/mongodb/DBApiLayer.java
new file mode 100644
index 00000000..7da1bed5
--- /dev/null
+++ b/src/com/massivecraft/mcore3/lib/mongodb/DBApiLayer.java
@@ -0,0 +1,575 @@
+// DBApiLayer.java
+
+/**
+ * Copyright (C) 2008 10gen 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 com.massivecraft.mcore3.lib.mongodb;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+
+import com.massivecraft.mcore3.lib.bson.BSONObject;
+import com.massivecraft.mcore3.lib.bson.types.ObjectId;
+import com.massivecraft.mcore3.lib.mongodb.util.JSON;
+
+/** Database API
+ * This cannot be directly instantiated, but the functions are available
+ * through instances of Mongo.
+ */
+public class DBApiLayer extends DB {
+
+ static final boolean D = Boolean.getBoolean( "DEBUG.DB" );
+ /** The maximum number of cursors allowed */
+ static final int NUM_CURSORS_BEFORE_KILL = 100;
+ static final int NUM_CURSORS_PER_BATCH = 20000;
+
+ // --- show
+
+ static final Logger TRACE_LOGGER = Logger.getLogger( "com.mongodb.TRACE" );
+ static final Level TRACE_LEVEL = Boolean.getBoolean( "DB.TRACE" ) ? Level.INFO : Level.FINEST;
+
+ static final boolean willTrace(){
+ return TRACE_LOGGER.isLoggable( TRACE_LEVEL );
+ }
+
+ static final void trace( String s ){
+ TRACE_LOGGER.log( TRACE_LEVEL , s );
+ }
+
+ static int chooseBatchSize(int batchSize, int limit, int fetched) {
+ int bs = Math.abs(batchSize);
+ int remaining = limit > 0 ? limit - fetched : 0;
+ int res = 0;
+ if (bs == 0 && remaining > 0)
+ res = remaining;
+ else if (bs > 0 && remaining == 0)
+ res = bs;
+ else
+ res = Math.min(bs, remaining);
+
+ if (batchSize < 0) {
+ // force close
+ res = -res;
+ }
+
+ if (res == 1) {
+ // optimization: use negative batchsize to close cursor
+ res = -1;
+ }
+ return res;
+ }
+
+ /**
+ * @param mongo the Mongo instance
+ * @param name the database name
+ * @param connector the connector
+ */
+ protected DBApiLayer( Mongo mongo, String name , DBConnector connector ){
+ super( mongo, name );
+
+ if ( connector == null )
+ throw new IllegalArgumentException( "need a connector: " + name );
+
+ _root = name;
+ _rootPlusDot = _root + ".";
+
+ _connector = connector;
+ }
+
+ public void requestStart(){
+ _connector.requestStart();
+ }
+
+ public void requestDone(){
+ _connector.requestDone();
+ }
+
+ public void requestEnsureConnection(){
+ _connector.requestEnsureConnection();
+ }
+
+ protected MyCollection doGetCollection( String name ){
+ MyCollection c = _collections.get( name );
+ if ( c != null )
+ return c;
+
+ c = new MyCollection( name );
+ MyCollection old = _collections.putIfAbsent(name, c);
+ return old != null ? old : c;
+ }
+
+ String _removeRoot( String ns ){
+ if ( ! ns.startsWith( _rootPlusDot ) )
+ return ns;
+ return ns.substring( _root.length() + 1 );
+ }
+
+ public void cleanCursors( boolean force )
+ throws MongoException {
+
+ int sz = _deadCursorIds.size();
+
+ if ( sz == 0 || ( ! force && sz < NUM_CURSORS_BEFORE_KILL))
+ return;
+
+ Bytes.LOGGER.info( "going to kill cursors : " + sz );
+
+ Map> m = new HashMap>();
+ DeadCursor c;
+ while (( c = _deadCursorIds.poll()) != null ){
+ List x = m.get( c.host );
+ if ( x == null ){
+ x = new LinkedList();
+ m.put( c.host , x );
+ }
+ x.add( c.id );
+ }
+
+ for ( Map.Entry> e : m.entrySet() ){
+ try {
+ killCursors( e.getKey() , e.getValue() );
+ }
+ catch ( Throwable t ){
+ Bytes.LOGGER.log( Level.WARNING , "can't clean cursors" , t );
+ for ( Long x : e.getValue() )
+ _deadCursorIds.add( new DeadCursor( x , e.getKey() ) );
+ }
+ }
+ }
+
+ void killCursors( ServerAddress addr , List all )
+ throws MongoException {
+ if ( all == null || all.size() == 0 )
+ return;
+
+ OutMessage om = new OutMessage( _mongo , 2007 );
+ om.writeInt( 0 ); // reserved
+
+ om.writeInt( Math.min( NUM_CURSORS_PER_BATCH , all.size() ) );
+
+ int soFar = 0;
+ int totalSoFar = 0;
+ for (Long l : all) {
+ om.writeLong(l);
+
+ totalSoFar++;
+ soFar++;
+
+ if ( soFar >= NUM_CURSORS_PER_BATCH ){
+ _connector.say( this , om ,com.massivecraft.mcore3.lib.mongodb.WriteConcern.NONE );
+ om = new OutMessage( _mongo , 2007 );
+ om.writeInt( 0 ); // reserved
+ om.writeInt( Math.min( NUM_CURSORS_PER_BATCH , all.size() - totalSoFar ) );
+ soFar = 0;
+ }
+ }
+
+ _connector.say( this , om ,com.massivecraft.mcore3.lib.mongodb.WriteConcern.NONE , addr );
+ }
+
+ class MyCollection extends DBCollection {
+ MyCollection( String name ){
+ super( DBApiLayer.this , name );
+ _fullNameSpace = _root + "." + name;
+ }
+
+ public void doapply( DBObject o ){
+ }
+
+ @Override
+ public void drop() throws MongoException {
+ _collections.remove(getName());
+ super.drop();
+ }
+
+ public WriteResult insert(DBObject[] arr, com.massivecraft.mcore3.lib.mongodb.WriteConcern concern, DBEncoder encoder )
+ throws MongoException {
+ return insert( arr, true, concern, encoder );
+ }
+
+ protected WriteResult insert(DBObject[] arr, boolean shouldApply , com.massivecraft.mcore3.lib.mongodb.WriteConcern concern, DBEncoder encoder )
+ throws MongoException {
+
+ if (encoder == null)
+ encoder = DefaultDBEncoder.FACTORY.create();
+
+ if ( willTrace() ) {
+ for (DBObject o : arr) {
+ trace( "save: " + _fullNameSpace + " " + JSON.serialize( o ) );
+ }
+ }
+
+ if ( shouldApply ){
+ for ( int i=0; i 2 * maxsize ){
+ cur++;
+ break;
+ }
+ }
+
+ last = _connector.say( _db , om , concern );
+ }
+
+ return last;
+ }
+
+ public WriteResult remove( DBObject o , com.massivecraft.mcore3.lib.mongodb.WriteConcern concern, DBEncoder encoder )
+ throws MongoException {
+
+ if (encoder == null)
+ encoder = DefaultDBEncoder.FACTORY.create();
+
+ if ( willTrace() ) trace( "remove: " + _fullNameSpace + " " + JSON.serialize( o ) );
+
+ OutMessage om = new OutMessage( _mongo , 2006, encoder );
+
+ om.writeInt( 0 ); // reserved
+ om.writeCString( _fullNameSpace );
+
+ Collection keys = o.keySet();
+
+ if ( keys.size() == 1 &&
+ keys.iterator().next().equals( "_id" ) &&
+ o.get( keys.iterator().next() ) instanceof ObjectId )
+ om.writeInt( 1 );
+ else
+ om.writeInt( 0 );
+
+ om.putObject( o );
+
+ return _connector.say( _db , om , concern );
+ }
+
+ @Override
+ Iterator __find( DBObject ref , DBObject fields , int numToSkip , int batchSize, int limit , int options, ReadPreference readPref, DBDecoder decoder )
+ throws MongoException {
+
+ return __find(ref, fields, numToSkip, batchSize, limit, options, readPref, decoder, DefaultDBEncoder.FACTORY.create());
+ }
+
+ @Override
+ Iterator __find( DBObject ref , DBObject fields , int numToSkip , int batchSize , int limit, int options,
+ ReadPreference readPref, DBDecoder decoder, DBEncoder encoder ) throws MongoException {
+
+ if ( ref == null )
+ ref = new BasicDBObject();
+
+ if ( willTrace() ) trace( "find: " + _fullNameSpace + " " + JSON.serialize( ref ) );
+
+ OutMessage query = OutMessage.query( _mongo , options , _fullNameSpace , numToSkip , chooseBatchSize(batchSize, limit, 0) , ref , fields, readPref,
+ encoder);
+
+ Response res = _connector.call( _db , this , query , null , 2, readPref, decoder );
+
+ if ( res.size() == 1 ){
+ BSONObject foo = res.get(0);
+ MongoException e = MongoException.parse( foo );
+ if ( e != null && ! _name.equals( "$cmd" ) )
+ throw e;
+ }
+
+ return new Result( this , res , batchSize, limit , options, decoder );
+ }
+
+ @Override
+ public WriteResult update( DBObject query , DBObject o , boolean upsert , boolean multi , com.massivecraft.mcore3.lib.mongodb.WriteConcern concern, DBEncoder encoder )
+ throws MongoException {
+
+ if (encoder == null)
+ encoder = DefaultDBEncoder.FACTORY.create();
+
+ if (o != null && !o.keySet().isEmpty()) {
+ // if 1st key doesn't start with $, then object will be inserted as is, need to check it
+ String key = o.keySet().iterator().next();
+ if (!key.startsWith("$"))
+ _checkObject(o, false, false);
+ }
+
+ if ( willTrace() ) trace( "update: " + _fullNameSpace + " " + JSON.serialize( query ) + " " + JSON.serialize( o ) );
+
+ OutMessage om = new OutMessage( _mongo , 2001, encoder );
+ om.writeInt( 0 ); // reserved
+ om.writeCString( _fullNameSpace );
+
+ int flags = 0;
+ if ( upsert ) flags |= 1;
+ if ( multi ) flags |= 2;
+ om.writeInt( flags );
+
+ om.putObject( query );
+ om.putObject( o );
+
+ return _connector.say( _db , om , concern );
+ }
+
+ public void createIndex( final DBObject keys, final DBObject options, DBEncoder encoder )
+ throws MongoException {
+
+ if (encoder == null)
+ encoder = DefaultDBEncoder.FACTORY.create();
+
+ DBObject full = new BasicDBObject();
+ for ( String k : options.keySet() )
+ full.put( k , options.get( k ) );
+ full.put( "key" , keys );
+
+ MyCollection idxs = DBApiLayer.this.doGetCollection( "system.indexes" );
+ //query first, maybe we should do an update w/upsert? -- need to test performance and lock behavior
+ if ( idxs.findOne( full ) == null )
+ idxs.insert( new DBObject[] { full }, false, WriteConcern.SAFE, encoder );
+ }
+
+ final String _fullNameSpace;
+ }
+
+ class Result implements Iterator {
+
+ Result( MyCollection coll , Response res , int batchSize, int limit , int options, DBDecoder decoder ){
+ _collection = coll;
+ _batchSize = batchSize;
+ _limit = limit;
+ _options = options;
+ _host = res._host;
+ _decoder = decoder;
+ init( res );
+ }
+
+ private void init( Response res ){
+ _totalBytes += res._len;
+ _curResult = res;
+ _cur = res.iterator();
+ _sizes.add( res.size() );
+ _numFetched += res.size();
+
+ if ( ( res._flags & Bytes.RESULTFLAG_CURSORNOTFOUND ) > 0 ){
+ throw new MongoException.CursorNotFound(res._cursor, res.serverUsed());
+ }
+
+ if (res._cursor != 0 && _limit > 0 && _limit - _numFetched <= 0) {
+ // fetched all docs within limit, close cursor server-side
+ killCursor();
+ }
+ }
+
+ public DBObject next(){
+ if ( _cur.hasNext() ) {
+ return _cur.next();
+ }
+
+ if ( ! _curResult.hasGetMore( _options ) )
+ throw new RuntimeException( "no more" );
+
+ _advance();
+ return next();
+ }
+
+ public boolean hasNext(){
+ boolean hasNext = _cur.hasNext();
+ while ( !hasNext ) {
+ if ( ! _curResult.hasGetMore( _options ) )
+ return false;
+
+ _advance();
+ hasNext = _cur.hasNext();
+
+ if (!hasNext) {
+ if ( ( _options & Bytes.QUERYOPTION_AWAITDATA ) == 0 ) {
+ // dont block waiting for data if no await
+ return false;
+ } else {
+ // if await, driver should block until data is available
+ // if server does not support await, driver must sleep to avoid busy loop
+ if ((_curResult._flags & Bytes.RESULTFLAG_AWAITCAPABLE) == 0) {
+ try {
+ Thread.sleep(500);
+ } catch (Exception e) {
+ }
+ }
+ }
+ }
+ }
+ return hasNext;
+ }
+
+ private void _advance(){
+
+ if ( _curResult.cursor() <= 0 )
+ throw new RuntimeException( "can't advance a cursor <= 0" );
+
+ OutMessage m = new OutMessage( _mongo , 2005 );
+
+ m.writeInt( 0 );
+ m.writeCString( _collection._fullNameSpace );
+ m.writeInt( chooseBatchSize(_batchSize, _limit, _numFetched) );
+ m.writeLong( _curResult.cursor() );
+
+ Response res = _connector.call( DBApiLayer.this , _collection , m , _host, _decoder );
+ _numGetMores++;
+ init( res );
+ }
+
+ public void remove(){
+ throw new RuntimeException( "can't remove this way" );
+ }
+
+ public int getBatchSize(){
+ return _batchSize;
+ }
+
+ public void setBatchSize(int size){
+ _batchSize = size;
+ }
+
+ public String toString(){
+ return "DBCursor";
+ }
+
+ protected void finalize() throws Throwable {
+ if (_curResult != null) {
+ long curId = _curResult.cursor();
+ _curResult = null;
+ _cur = null;
+ if (curId != 0) {
+ _deadCursorIds.add(new DeadCursor(curId, _host));
+ }
+ }
+ super.finalize();
+ }
+
+ public long totalBytes(){
+ return _totalBytes;
+ }
+
+ public long getCursorId(){
+ if ( _curResult == null )
+ return 0;
+ return _curResult._cursor;
+ }
+
+ int numGetMores(){
+ return _numGetMores;
+ }
+
+ List getSizes(){
+ return Collections.unmodifiableList( _sizes );
+ }
+
+ void close(){
+ // not perfectly thread safe here, may need to use an atomicBoolean
+ if (_curResult != null) {
+ killCursor();
+ _curResult = null;
+ _cur = null;
+ }
+ }
+
+ void killCursor() {
+ if (_curResult == null)
+ return;
+ long curId = _curResult.cursor();
+ if (curId == 0)
+ return;
+
+ List l = new ArrayList();
+ l.add(curId);
+
+ try {
+ killCursors(_host, l);
+ } catch (Throwable t) {
+ Bytes.LOGGER.log(Level.WARNING, "can't clean 1 cursor", t);
+ _deadCursorIds.add(new DeadCursor(curId, _host));
+ }
+ _curResult._cursor = 0;
+ }
+
+ public ServerAddress getServerAddress() {
+ return _host;
+ }
+
+ Response _curResult;
+ Iterator _cur;
+ int _batchSize;
+ int _limit;
+ final DBDecoder _decoder;
+ final MyCollection _collection;
+ final int _options;
+ final ServerAddress _host; // host where first went. all subsequent have to go there
+
+ private long _totalBytes = 0;
+ private int _numGetMores = 0;
+ private List _sizes = new ArrayList();
+ private int _numFetched = 0;
+
+ } // class Result
+
+ static class DeadCursor {
+
+ DeadCursor( long a , ServerAddress b ){
+ id = a;
+ host = b;
+ }
+
+ final long id;
+ final ServerAddress host;
+ }
+
+ final String _root;
+ final String _rootPlusDot;
+ final DBConnector _connector;
+ final ConcurrentHashMap _collections = new ConcurrentHashMap();
+
+ ConcurrentLinkedQueue _deadCursorIds = new ConcurrentLinkedQueue();
+
+ static final List EMPTY = Collections.unmodifiableList( new LinkedList() );
+}
diff --git a/src/com/massivecraft/mcore3/lib/mongodb/DBCallback.java b/src/com/massivecraft/mcore3/lib/mongodb/DBCallback.java
new file mode 100644
index 00000000..e49bbd28
--- /dev/null
+++ b/src/com/massivecraft/mcore3/lib/mongodb/DBCallback.java
@@ -0,0 +1,30 @@
+// DBCallback.java
+
+/**
+ * Copyright (C) 2008 10gen 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 com.massivecraft.mcore3.lib.mongodb;
+
+
+import com.massivecraft.mcore3.lib.bson.BSONCallback;
+
+/**
+ * The DB callback interface.
+ */
+public interface DBCallback extends BSONCallback {
+
+}
+
diff --git a/src/com/massivecraft/mcore3/lib/mongodb/DBCallbackFactory.java b/src/com/massivecraft/mcore3/lib/mongodb/DBCallbackFactory.java
new file mode 100644
index 00000000..2f6d9650
--- /dev/null
+++ b/src/com/massivecraft/mcore3/lib/mongodb/DBCallbackFactory.java
@@ -0,0 +1,27 @@
+/**
+ * Copyright (C) 2011 10gen 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 com.massivecraft.mcore3.lib.mongodb;
+
+/**
+ * The DBCallback factory interface.
+ */
+public interface DBCallbackFactory {
+
+ public DBCallback create( DBCollection collection );
+
+}
+
diff --git a/src/com/massivecraft/mcore3/lib/mongodb/DBCollection.java b/src/com/massivecraft/mcore3/lib/mongodb/DBCollection.java
new file mode 100644
index 00000000..a0d63f96
--- /dev/null
+++ b/src/com/massivecraft/mcore3/lib/mongodb/DBCollection.java
@@ -0,0 +1,1522 @@
+// DBCollection.java
+
+/**
+ * Copyright (C) 2008 10gen 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 com.massivecraft.mcore3.lib.mongodb;
+
+// Mongo
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import com.massivecraft.mcore3.lib.bson.types.ObjectId;
+
+/** This class provides a skeleton implementation of a database collection.
+ * A typical invocation sequence is thus
+ *
+ * Mongo mongo = new Mongo( new DBAddress( "localhost", 127017 ) );
+ * DB db = mongo.getDB( "mydb" );
+ * DBCollection collection = db.getCollection( "test" );
+ *
+ * @dochub collections
+ */
+@SuppressWarnings("unchecked")
+public abstract class DBCollection {
+
+ /**
+ * Saves document(s) to the database.
+ * if doc doesn't have an _id, one will be added
+ * you can get the _id that was added from doc after the insert
+ *
+ * @param arr array of documents to save
+ * @param concern the write concern
+ * @return
+ * @throws MongoException
+ * @dochub insert
+ */
+ public WriteResult insert(DBObject[] arr , WriteConcern concern ) throws MongoException {
+ return insert( arr, concern, getDBEncoder());
+ }
+
+ /**
+ * Saves document(s) to the database.
+ * if doc doesn't have an _id, one will be added
+ * you can get the _id that was added from doc after the insert
+ *
+ * @param arr array of documents to save
+ * @param concern the write concern
+ * @param encoder the DBEncoder to use
+ * @return
+ * @throws MongoException
+ * @dochub insert
+ */
+ public abstract WriteResult insert(DBObject[] arr , WriteConcern concern, DBEncoder encoder) throws MongoException;
+
+ /**
+ * Inserts a document into the database.
+ * if doc doesn't have an _id, one will be added
+ * you can get the _id that was added from doc after the insert
+ *
+ * @param o
+ * @param concern the write concern
+ * @return
+ * @throws MongoException
+ * @dochub insert
+ */
+ public WriteResult insert(DBObject o , WriteConcern concern )
+ throws MongoException {
+ return insert( new DBObject[]{ o } , concern );
+ }
+
+ /**
+ * Saves document(s) to the database.
+ * if doc doesn't have an _id, one will be added
+ * you can get the _id that was added from doc after the insert
+ *
+ * @param arr array of documents to save
+ * @return
+ * @throws MongoException
+ * @dochub insert
+ */
+ public WriteResult insert(DBObject ... arr)
+ throws MongoException {
+ return insert( arr , getWriteConcern() );
+ }
+
+ /**
+ * Saves document(s) to the database.
+ * if doc doesn't have an _id, one will be added
+ * you can get the _id that was added from doc after the insert
+ *
+ * @param arr array of documents to save
+ * @return
+ * @throws MongoException
+ * @dochub insert
+ */
+ public WriteResult insert(WriteConcern concern, DBObject ... arr)
+ throws MongoException {
+ return insert( arr, concern );
+ }
+
+ /**
+ * Saves document(s) to the database.
+ * if doc doesn't have an _id, one will be added
+ * you can get the _id that was added from doc after the insert
+ *
+ * @param list list of documents to save
+ * @return
+ * @throws MongoException
+ * @dochub insert
+ */
+ public WriteResult insert(List list )
+ throws MongoException {
+ return insert( list, getWriteConcern() );
+ }
+
+ /**
+ * Saves document(s) to the database.
+ * if doc doesn't have an _id, one will be added
+ * you can get the _id that was added from doc after the insert
+ *
+ * @param list list of documents to save
+ * @param concern the write concern
+ * @return
+ * @throws MongoException
+ * @dochub insert
+ */
+ public WriteResult insert(List list, WriteConcern concern )
+ throws MongoException {
+ return insert( list.toArray( new DBObject[list.size()] ) , concern );
+ }
+
+ /**
+ * Performs an update operation.
+ * @param q search query for old object to update
+ * @param o object with which to update q
+ * @param upsert if the database should create the element if it does not exist
+ * @param multi if the update should be applied to all objects matching (db version 1.1.3 and above). An object will
+ * not be inserted if it does not exist in the collection and upsert=true and multi=true.
+ * See http://www.mongodb.org/display/DOCS/Atomic+Operations
+ * @param concern the write concern
+ * @return
+ * @throws MongoException
+ * @dochub update
+ */
+ public WriteResult update( DBObject q , DBObject o , boolean upsert , boolean multi , WriteConcern concern ) throws MongoException {
+ return update( q, o, upsert, multi, concern, getDBEncoder());
+ }
+
+ /**
+ * Performs an update operation.
+ * @param q search query for old object to update
+ * @param o object with which to update q
+ * @param upsert if the database should create the element if it does not exist
+ * @param multi if the update should be applied to all objects matching (db version 1.1.3 and above). An object will
+ * not be inserted if it does not exist in the collection and upsert=true and multi=true.
+ * See http://www.mongodb.org/display/DOCS/Atomic+Operations
+ * @param concern the write concern
+ * @param encoder the DBEncoder to use
+ * @return
+ * @throws MongoException
+ * @dochub update
+ */
+ public abstract WriteResult update( DBObject q , DBObject o , boolean upsert , boolean multi , WriteConcern concern, DBEncoder encoder ) throws MongoException ;
+
+ /**
+ * calls {@link DBCollection#update(com.massivecraft.mcore3.lib.mongodb.DBObject, com.massivecraft.mcore3.lib.mongodb.DBObject, boolean, boolean, com.massivecraft.mcore3.lib.mongodb.WriteConcern)} with default WriteConcern.
+ * @param q search query for old object to update
+ * @param o object with which to update q
+ * @param upsert if the database should create the element if it does not exist
+ * @param multi if the update should be applied to all objects matching (db version 1.1.3 and above)
+ * See http://www.mongodb.org/display/DOCS/Atomic+Operations
+ * @return
+ * @throws MongoException
+ * @dochub update
+ */
+ public WriteResult update( DBObject q , DBObject o , boolean upsert , boolean multi )
+ throws MongoException {
+ return update( q , o , upsert , multi , getWriteConcern() );
+ }
+
+ /**
+ * calls {@link DBCollection#update(com.massivecraft.mcore3.lib.mongodb.DBObject, com.massivecraft.mcore3.lib.mongodb.DBObject, boolean, boolean)} with upsert=false and multi=false
+ * @param q search query for old object to update
+ * @param o object with which to update q
+ * @return
+ * @throws MongoException
+ * @dochub update
+ */
+ public WriteResult update( DBObject q , DBObject o ) throws MongoException {
+ return update( q , o , false , false );
+ }
+
+ /**
+ * calls {@link DBCollection#update(com.massivecraft.mcore3.lib.mongodb.DBObject, com.massivecraft.mcore3.lib.mongodb.DBObject, boolean, boolean)} with upsert=false and multi=true
+ * @param q search query for old object to update
+ * @param o object with which to update q
+ * @return
+ * @throws MongoException
+ * @dochub update
+ */
+ public WriteResult updateMulti( DBObject q , DBObject o ) throws MongoException {
+ return update( q , o , false , true );
+ }
+
+ /**
+ * Adds any necessary fields to a given object before saving it to the collection.
+ * @param o object to which to add the fields
+ */
+ protected abstract void doapply( DBObject o );
+
+ /**
+ * Removes objects from the database collection.
+ * @param o the object that documents to be removed must match
+ * @param concern WriteConcern for this operation
+ * @return
+ * @throws MongoException
+ * @dochub remove
+ */
+ public WriteResult remove( DBObject o , WriteConcern concern ) throws MongoException {
+ return remove( o, concern, getDBEncoder());
+ }
+
+ /**
+ * Removes objects from the database collection.
+ * @param o the object that documents to be removed must match
+ * @param concern WriteConcern for this operation
+ * @param encoder the DBEncoder to use
+ * @return
+ * @throws MongoException
+ * @dochub remove
+ */
+ public abstract WriteResult remove( DBObject o , WriteConcern concern, DBEncoder encoder ) throws MongoException ;
+
+ /**
+ * calls {@link DBCollection#remove(com.massivecraft.mcore3.lib.mongodb.DBObject, com.massivecraft.mcore3.lib.mongodb.WriteConcern)} with the default WriteConcern
+ * @param o the object that documents to be removed must match
+ * @return
+ * @throws MongoException
+ * @dochub remove
+ */
+ public WriteResult remove( DBObject o )
+ throws MongoException {
+ return remove( o , getWriteConcern() );
+ }
+
+
+ /**
+ * Finds objects
+ */
+ abstract Iterator __find( DBObject ref , DBObject fields , int numToSkip , int batchSize , int limit, int options, ReadPreference readPref, DBDecoder decoder ) throws MongoException ;
+
+ abstract Iterator __find( DBObject ref , DBObject fields , int numToSkip , int batchSize , int limit, int options,
+ ReadPreference readPref, DBDecoder decoder, DBEncoder encoder ) throws MongoException ;
+
+
+ /**
+ * Calls {@link DBCollection#find(com.massivecraft.mcore3.lib.mongodb.DBObject, com.massivecraft.mcore3.lib.mongodb.DBObject, int, int)} and applies the query options
+ * @param query query used to search
+ * @param fields the fields of matching objects to return
+ * @param numToSkip number of objects to skip
+ * @param batchSize the batch size. This option has a complex behavior, see {@link DBCursor#batchSize(int) }
+ * @param options - see Bytes QUERYOPTION_*
+ * @return the cursor
+ * @throws MongoException
+ * @dochub find
+ */
+ @Deprecated
+ public DBCursor find( DBObject query , DBObject fields , int numToSkip , int batchSize , int options ) throws MongoException{
+ return find(query, fields, numToSkip, batchSize).addOption(options);
+ }
+
+
+ /**
+ * Finds objects from the database that match a query.
+ * A DBCursor object is returned, that can be iterated to go through the results.
+ *
+ * @param query query used to search
+ * @param fields the fields of matching objects to return
+ * @param numToSkip number of objects to skip
+ * @param batchSize the batch size. This option has a complex behavior, see {@link DBCursor#batchSize(int) }
+ * @return the cursor
+ * @throws MongoException
+ * @dochub find
+ */
+ @Deprecated
+ public DBCursor find( DBObject query , DBObject fields , int numToSkip , int batchSize ) {
+ DBCursor cursor = find(query, fields).skip(numToSkip).batchSize(batchSize);
+ return cursor;
+ }
+
+ // ------
+
+ /**
+ * Finds an object by its id.
+ * This compares the passed in value to the _id field of the document
+ *
+ * @param obj any valid object
+ * @return the object, if found, otherwise null
+ * @throws MongoException
+ */
+ public DBObject findOne( Object obj )
+ throws MongoException {
+ return findOne(obj, null);
+ }
+
+
+ /**
+ * Finds an object by its id.
+ * This compares the passed in value to the _id field of the document
+ *
+ * @param obj any valid object
+ * @param fields fields to return
+ * @return the object, if found, otherwise null
+ * @dochub find
+ */
+ public DBObject findOne( Object obj, DBObject fields ) {
+ Iterator iterator = __find( new BasicDBObject("_id", obj), fields, 0, -1, 0, getOptions(), getReadPreference(), getDecoder() );
+ return (iterator.hasNext() ? iterator.next() : null);
+ }
+
+ /**
+ * Finds the first document in the query and updates it.
+ * @param query query to match
+ * @param fields fields to be returned
+ * @param sort sort to apply before picking first document
+ * @param remove if true, document found will be removed
+ * @param update update to apply
+ * @param returnNew if true, the updated document is returned, otherwise the old document is returned (or it would be lost forever)
+ * @param upsert do upsert (insert if document not present)
+ * @return the document
+ */
+ public DBObject findAndModify(DBObject query, DBObject fields, DBObject sort, boolean remove, DBObject update, boolean returnNew, boolean upsert) {
+
+ BasicDBObject cmd = new BasicDBObject( "findandmodify", _name);
+ if (query != null && !query.keySet().isEmpty())
+ cmd.append( "query", query );
+ if (fields != null && !fields.keySet().isEmpty())
+ cmd.append( "fields", fields );
+ if (sort != null && !sort.keySet().isEmpty())
+ cmd.append( "sort", sort );
+
+ if (remove)
+ cmd.append( "remove", remove );
+ else {
+ if (update != null && !update.keySet().isEmpty()) {
+ // if 1st key doesnt start with $, then object will be inserted as is, need to check it
+ String key = update.keySet().iterator().next();
+ if (key.charAt(0) != '$')
+ _checkObject(update, false, false);
+ cmd.append( "update", update );
+ }
+ if (returnNew)
+ cmd.append( "new", returnNew );
+ if (upsert)
+ cmd.append( "upsert", upsert );
+ }
+
+ if (remove && !(update == null || update.keySet().isEmpty() || returnNew))
+ throw new MongoException("FindAndModify: Remove cannot be mixed with the Update, or returnNew params!");
+
+ CommandResult res = this._db.command( cmd );
+ if (res.ok() || res.getErrorMessage().equals( "No matching object found" )) {
+ return replaceWithObjectClass((DBObject) res.get( "value" ));
+ }
+ res.throwOnError();
+ return null;
+ }
+
+ /**
+ * Doesn't yet handle internal classes properly, so this method only does something if object class is set but
+ * no internal classes are set.
+ *
+ * @param oldObj the original value from the command result
+ * @return replaced object if necessary, or oldObj
+ */
+ private DBObject replaceWithObjectClass(DBObject oldObj) {
+ if (oldObj == null || getObjectClass() == null & _internalClass.isEmpty()) {
+ return oldObj;
+ }
+
+ DBObject newObj = instantiateObjectClassInstance();
+
+ for (String key : oldObj.keySet()) {
+ newObj.put(key, oldObj.get(key));
+ }
+ return newObj;
+ }
+
+ private DBObject instantiateObjectClassInstance() {
+ try {
+ return (DBObject) getObjectClass().newInstance();
+ } catch (InstantiationException e) {
+ throw new MongoInternalException("can't create instance of type " + getObjectClass(), e);
+ } catch (IllegalAccessException e) {
+ throw new MongoInternalException("can't create instance of type " + getObjectClass(), e);
+ }
+ }
+
+
+ /**
+ * calls {@link DBCollection#findAndModify(com.massivecraft.mcore3.lib.mongodb.DBObject, com.massivecraft.mcore3.lib.mongodb.DBObject, com.massivecraft.mcore3.lib.mongodb.DBObject, boolean, com.massivecraft.mcore3.lib.mongodb.DBObject, boolean, boolean)}
+ * with fields=null, remove=false, returnNew=false, upsert=false
+ * @param query
+ * @param sort
+ * @param update
+ * @return the old document
+ */
+ public DBObject findAndModify( DBObject query , DBObject sort , DBObject update){
+ return findAndModify( query, null, sort, false, update, false, false);
+ }
+
+ /**
+ * calls {@link DBCollection#findAndModify(com.massivecraft.mcore3.lib.mongodb.DBObject, com.massivecraft.mcore3.lib.mongodb.DBObject, com.massivecraft.mcore3.lib.mongodb.DBObject, boolean, com.massivecraft.mcore3.lib.mongodb.DBObject, boolean, boolean)}
+ * with fields=null, sort=null, remove=false, returnNew=false, upsert=false
+ * @param query
+ * @param update
+ * @return the old document
+ */
+ public DBObject findAndModify( DBObject query , DBObject update ) {
+ return findAndModify( query, null, null, false, update, false, false );
+ }
+
+ /**
+ * calls {@link DBCollection#findAndModify(com.massivecraft.mcore3.lib.mongodb.DBObject, com.massivecraft.mcore3.lib.mongodb.DBObject, com.massivecraft.mcore3.lib.mongodb.DBObject, boolean, com.massivecraft.mcore3.lib.mongodb.DBObject, boolean, boolean)}
+ * with fields=null, sort=null, remove=true, returnNew=false, upsert=false
+ * @param query
+ * @return the removed document
+ */
+ public DBObject findAndRemove( DBObject query ) {
+ return findAndModify( query, null, null, true, null, false, false );
+ }
+
+ // --- START INDEX CODE ---
+
+ /**
+ * calls {@link DBCollection#createIndex(com.massivecraft.mcore3.lib.mongodb.DBObject, com.massivecraft.mcore3.lib.mongodb.DBObject)} with default index options
+ * @param keys an object with a key set of the fields desired for the index
+ * @throws MongoException
+ */
+ public void createIndex( final DBObject keys )
+ throws MongoException {
+ createIndex( keys , defaultOptions( keys ) );
+ }
+
+ /**
+ * Forces creation of an index on a set of fields, if one does not already exist.
+ * @param keys
+ * @param options
+ * @throws MongoException
+ */
+ public void createIndex( DBObject keys , DBObject options ) throws MongoException {
+ createIndex( keys, options, getDBEncoder());
+ }
+
+ /**
+ * Forces creation of an index on a set of fields, if one does not already exist.
+ * @param keys
+ * @param options
+ * @param encoder the DBEncoder to use
+ * @throws MongoException
+ */
+ public abstract void createIndex( DBObject keys , DBObject options, DBEncoder encoder ) throws MongoException;
+
+ /**
+ * Creates an ascending index on a field with default options, if one does not already exist.
+ * @param name name of field to index on
+ */
+ public void ensureIndex( final String name ){
+ ensureIndex( new BasicDBObject( name , 1 ) );
+ }
+
+ /**
+ * calls {@link DBCollection#ensureIndex(com.massivecraft.mcore3.lib.mongodb.DBObject, com.massivecraft.mcore3.lib.mongodb.DBObject)} with default options
+ * @param keys an object with a key set of the fields desired for the index
+ * @throws MongoException
+ */
+ public void ensureIndex( final DBObject keys )
+ throws MongoException {
+ ensureIndex( keys , defaultOptions( keys ) );
+ }
+
+ /**
+ * calls {@link DBCollection#ensureIndex(com.massivecraft.mcore3.lib.mongodb.DBObject, java.lang.String, boolean)} with unique=false
+ * @param keys fields to use for index
+ * @param name an identifier for the index
+ * @throws MongoException
+ * @dochub indexes
+ */
+ public void ensureIndex( DBObject keys , String name )
+ throws MongoException {
+ ensureIndex( keys , name , false );
+ }
+
+ /**
+ * Ensures an index on this collection (that is, the index will be created if it does not exist).
+ * @param keys fields to use for index
+ * @param name an identifier for the index. If null or empty, the default name will be used.
+ * @param unique if the index should be unique
+ * @throws MongoException
+ */
+ public void ensureIndex( DBObject keys , String name , boolean unique )
+ throws MongoException {
+ DBObject options = defaultOptions( keys );
+ if (name != null && name.length()>0)
+ options.put( "name" , name );
+ if ( unique )
+ options.put( "unique" , Boolean.TRUE );
+ ensureIndex( keys , options );
+ }
+
+ /**
+ * Creates an index on a set of fields, if one does not already exist.
+ * @param keys an object with a key set of the fields desired for the index
+ * @param optionsIN options for the index (name, unique, etc)
+ * @throws MongoException
+ */
+ public void ensureIndex( final DBObject keys , final DBObject optionsIN )
+ throws MongoException {
+
+ if ( checkReadOnly( false ) ) return;
+
+ final DBObject options = defaultOptions( keys );
+ for ( String k : optionsIN.keySet() )
+ options.put( k , optionsIN.get( k ) );
+
+ final String name = options.get( "name" ).toString();
+
+ if ( _createdIndexes.contains( name ) )
+ return;
+
+ createIndex( keys , options );
+ _createdIndexes.add( name );
+ }
+
+ /**
+ * Clears all indices that have not yet been applied to this collection.
+ */
+ public void resetIndexCache(){
+ _createdIndexes.clear();
+ }
+
+ DBObject defaultOptions( DBObject keys ){
+ DBObject o = new BasicDBObject();
+ o.put( "name" , genIndexName( keys ) );
+ o.put( "ns" , _fullName );
+ return o;
+ }
+
+ /**
+ * Convenience method to generate an index name from the set of fields it is over.
+ * @param keys the names of the fields used in this index
+ * @return a string representation of this index's fields
+ */
+ public static String genIndexName( DBObject keys ){
+ StringBuilder name = new StringBuilder();
+ for ( String s : keys.keySet() ){
+ if ( name.length() > 0 )
+ name.append( '_' );
+ name.append( s ).append( '_' );
+ Object val = keys.get( s );
+ if ( val instanceof Number || val instanceof String )
+ name.append( val.toString().replace( ' ', '_' ) );
+ }
+ return name.toString();
+ }
+
+ // --- END INDEX CODE ---
+
+ /**
+ * Set hint fields for this collection (to optimize queries).
+ * @param lst a list of DBObject
s to be used as hints
+ */
+ public void setHintFields( List lst ){
+ _hintFields = lst;
+ }
+
+ /**
+ * Queries for an object in this collection.
+ * @param ref object for which to search
+ * @return an iterator over the results
+ * @dochub find
+ */
+ public DBCursor find( DBObject ref ){
+ return new DBCursor( this, ref, null, getReadPreference());
+ }
+
+ /**
+ * Queries for an object in this collection.
+ *
+ *
+ * An empty DBObject will match every document in the collection.
+ * Regardless of fields specified, the _id fields are always returned.
+ *
+ *
+ * An example that returns the "x" and "_id" fields for every document
+ * in the collection that has an "x" field:
+ *
+ *
+ * BasicDBObject keys = new BasicDBObject();
+ * keys.put("x", 1);
+ *
+ * DBCursor cursor = collection.find(new BasicDBObject(), keys);
+ *
+ *
+ * @param ref object for which to search
+ * @param keys fields to return
+ * @return a cursor to iterate over results
+ * @dochub find
+ */
+ public DBCursor find( DBObject ref , DBObject keys ){
+ return new DBCursor( this, ref, keys, getReadPreference());
+ }
+
+
+ /**
+ * Queries for all objects in this collection.
+ * @return a cursor which will iterate over every object
+ * @dochub find
+ */
+ public DBCursor find(){
+ return new DBCursor( this, null, null, getReadPreference());
+ }
+
+ /**
+ * Returns a single object from this collection.
+ * @return the object found, or null
if the collection is empty
+ * @throws MongoException
+ */
+ public DBObject findOne()
+ throws MongoException {
+ return findOne( new BasicDBObject() );
+ }
+
+ /**
+ * Returns a single object from this collection matching the query.
+ * @param o the query object
+ * @return the object found, or null
if no such object exists
+ * @throws MongoException
+ */
+ public DBObject findOne( DBObject o )
+ throws MongoException {
+ return findOne( o, null, getReadPreference());
+ }
+
+ /**
+ * Returns a single object from this collection matching the query.
+ * @param o the query object
+ * @param fields fields to return
+ * @return the object found, or null
if no such object exists
+ * @dochub find
+ */
+ public DBObject findOne( DBObject o, DBObject fields ) {
+ return findOne( o, fields, getReadPreference());
+ }
+ /**
+ * Returns a single object from this collection matching the query.
+ * @param o the query object
+ * @param fields fields to return
+ * @return the object found, or null
if no such object exists
+ * @dochub find
+ */
+ public DBObject findOne( DBObject o, DBObject fields, ReadPreference readPref ) {
+ Iterator i = __find( o , fields , 0 , -1 , 0, getOptions(), readPref, getDecoder() );
+ DBObject obj = (i.hasNext() ? i.next() : null);
+ if ( obj != null && ( fields != null && fields.keySet().size() > 0 ) ){
+ obj.markAsPartialObject();
+ }
+ return obj;
+ }
+
+ // Only create a new decoder if there is a decoder factory explicitly set on the collection. Otherwise return null
+ // so that DBPort will use a cached decoder from the default factory.
+ private DBDecoder getDecoder() {
+ return getDBDecoderFactory() != null ? getDBDecoderFactory().create() : null;
+ }
+
+ // Only create a new encoder if there is an encoder factory explicitly set on the collection. Otherwise return null
+ // to allow DB to create its own or use a cached one.
+ private DBEncoder getDBEncoder() {
+ return getDBEncoderFactory() != null ? getDBEncoderFactory().create() : null;
+ }
+
+
+ /**
+ * calls {@link DBCollection#apply(com.massivecraft.mcore3.lib.mongodb.DBObject, boolean)} with ensureID=true
+ * @param o DBObject
to which to add fields
+ * @return the modified parameter object
+ */
+ public Object apply( DBObject o ){
+ return apply( o , true );
+ }
+
+ /**
+ * calls {@link DBCollection#doapply(com.massivecraft.mcore3.lib.mongodb.DBObject)}, optionally adding an automatic _id field
+ * @param jo object to add fields to
+ * @param ensureID whether to add an _id
field
+ * @return the modified object o
+ */
+ public Object apply( DBObject jo , boolean ensureID ){
+
+ Object id = jo.get( "_id" );
+ if ( ensureID && id == null ){
+ id = ObjectId.get();
+ jo.put( "_id" , id );
+ }
+
+ doapply( jo );
+
+ return id;
+ }
+
+ /**
+ * calls {@link DBCollection#save(com.massivecraft.mcore3.lib.mongodb.DBObject, com.massivecraft.mcore3.lib.mongodb.WriteConcern)} with default WriteConcern
+ * @param jo the DBObject
to save
+ * will add _id
field to jo if needed
+ * @return
+ */
+ public WriteResult save( DBObject jo ) {
+ return save(jo, getWriteConcern());
+ }
+
+ /**
+ * Saves an object to this collection (does insert or update based on the object _id).
+ * @param jo the DBObject
to save
+ * @param concern the write concern
+ * @return
+ * @throws MongoException
+ */
+ public WriteResult save( DBObject jo, WriteConcern concern )
+ throws MongoException {
+ if ( checkReadOnly( true ) )
+ return null;
+
+ _checkObject( jo , false , false );
+
+ Object id = jo.get( "_id" );
+
+ if ( id == null || ( id instanceof ObjectId && ((ObjectId)id).isNew() ) ){
+ if ( id != null && id instanceof ObjectId )
+ ((ObjectId)id).notNew();
+ if ( concern == null )
+ return insert( jo );
+ else
+ return insert( jo, concern );
+ }
+
+ DBObject q = new BasicDBObject();
+ q.put( "_id" , id );
+ if ( concern == null )
+ return update( q , jo , true , false );
+ else
+ return update( q , jo , true , false , concern );
+
+ }
+
+ // ---- DB COMMANDS ----
+ /**
+ * Drops all indices from this collection
+ * @throws MongoException
+ */
+ public void dropIndexes()
+ throws MongoException {
+ dropIndexes( "*" );
+ }
+
+
+ /**
+ * Drops an index from this collection
+ * @param name the index name
+ * @throws MongoException
+ */
+ public void dropIndexes( String name )
+ throws MongoException {
+ DBObject cmd = BasicDBObjectBuilder.start()
+ .add( "deleteIndexes" , getName() )
+ .add( "index" , name )
+ .get();
+
+ resetIndexCache();
+ CommandResult res = _db.command( cmd );
+ if (res.ok() || res.getErrorMessage().equals( "ns not found" ))
+ return;
+ res.throwOnError();
+ }
+
+ /**
+ * Drops (deletes) this collection. Use with care.
+ * @throws MongoException
+ */
+ public void drop()
+ throws MongoException {
+ resetIndexCache();
+ CommandResult res =_db.command( BasicDBObjectBuilder.start().add( "drop" , getName() ).get() );
+ if (res.ok() || res.getErrorMessage().equals( "ns not found" ))
+ return;
+ res.throwOnError();
+ }
+
+ /**
+ * returns the number of documents in this collection.
+ * @return
+ * @throws MongoException
+ */
+ public long count()
+ throws MongoException {
+ return getCount(new BasicDBObject(), null);
+ }
+
+ /**
+ * returns the number of documents that match a query.
+ * @param query query to match
+ * @return
+ * @throws MongoException
+ */
+ public long count(DBObject query)
+ throws MongoException {
+ return getCount(query, null);
+ }
+
+
+ /**
+ * calls {@link DBCollection#getCount(com.massivecraft.mcore3.lib.mongodb.DBObject, com.massivecraft.mcore3.lib.mongodb.DBObject)} with an empty query and null fields.
+ * @return number of documents that match query
+ * @throws MongoException
+ */
+ public long getCount()
+ throws MongoException {
+ return getCount(new BasicDBObject(), null);
+ }
+
+ /**
+ * calls {@link DBCollection#getCount(com.massivecraft.mcore3.lib.mongodb.DBObject, com.massivecraft.mcore3.lib.mongodb.DBObject)} with null fields.
+ * @param query query to match
+ * @return
+ * @throws MongoException
+ */
+ public long getCount(DBObject query)
+ throws MongoException {
+ return getCount(query, null);
+ }
+
+ /**
+ * calls {@link DBCollection#getCount(com.massivecraft.mcore3.lib.mongodb.DBObject, com.massivecraft.mcore3.lib.mongodb.DBObject, long, long)} with limit=0 and skip=0
+ * @param query query to match
+ * @param fields fields to return
+ * @return
+ * @throws MongoException
+ */
+ public long getCount(DBObject query, DBObject fields)
+ throws MongoException {
+ return getCount( query , fields , 0 , 0 );
+ }
+
+ /**
+ * Returns the number of documents in the collection
+ * that match the specified query
+ *
+ * @param query query to select documents to count
+ * @param fields fields to return
+ * @param limit limit the count to this value
+ * @param skip number of entries to skip
+ * @return number of documents that match query and fields
+ * @throws MongoException
+ */
+ public long getCount(DBObject query, DBObject fields, long limit, long skip )
+ throws MongoException {
+
+ BasicDBObject cmd = new BasicDBObject();
+ cmd.put("count", getName());
+ cmd.put("query", query);
+ if (fields != null) {
+ cmd.put("fields", fields);
+ }
+
+ if ( limit > 0 )
+ cmd.put( "limit" , limit );
+ if ( skip > 0 )
+ cmd.put( "skip" , skip );
+
+ CommandResult res = _db.command(cmd,getOptions());
+
+ if ( ! res.ok() ){
+ String errmsg = res.getErrorMessage();
+
+ if ( errmsg.equals("ns does not exist") ||
+ errmsg.equals("ns missing" ) ){
+ // for now, return 0 - lets pretend it does exist
+ return 0;
+ }
+
+ res.throwOnError();
+ }
+
+ return res.getLong("n");
+ }
+
+ /**
+ * Calls {@link DBCollection#rename(java.lang.String, boolean)} with dropTarget=false
+ * @param newName new collection name (not a full namespace)
+ * @return the new collection
+ * @throws MongoException
+ */
+ public DBCollection rename( String newName )
+ throws MongoException {
+ return rename(newName, false);
+ }
+
+ /**
+ * renames of this collection to newName
+ * @param newName new collection name (not a full namespace)
+ * @param dropTarget if a collection with the new name exists, whether or not to drop it
+ * @return the new collection
+ * @throws MongoException
+ */
+ public DBCollection rename( String newName, boolean dropTarget )
+ throws MongoException {
+ CommandResult ret =
+ _db.getSisterDB( "admin" )
+ .command( BasicDBObjectBuilder.start()
+ .add( "renameCollection" , _fullName )
+ .add( "to" , _db._name + "." + newName )
+ .add( "dropTarget" , dropTarget )
+ .get() );
+ ret.throwOnError();
+ resetIndexCache();
+ return _db.getCollection( newName );
+ }
+
+ /**
+ * calls {@link DBCollection#group(com.massivecraft.mcore3.lib.mongodb.DBObject, com.massivecraft.mcore3.lib.mongodb.DBObject, com.massivecraft.mcore3.lib.mongodb.DBObject, java.lang.String, java.lang.String)} with finalize=null
+ * @param key - { a : true }
+ * @param cond - optional condition on query
+ * @param reduce javascript reduce function
+ * @param initial initial value for first match on a key
+ * @return
+ * @throws MongoException
+ * @see http://www.mongodb.org/display/DOCS/Aggregation
+ */
+ public DBObject group( DBObject key , DBObject cond , DBObject initial , String reduce )
+ throws MongoException {
+ return group( key , cond , initial , reduce , null );
+ }
+
+ /**
+ * Applies a group operation
+ * @param key - { a : true }
+ * @param cond - optional condition on query
+ * @param reduce javascript reduce function
+ * @param initial initial value for first match on a key
+ * @param finalize An optional function that can operate on the result(s) of the reduce function.
+ * @return
+ * @throws MongoException
+ * @see http://www.mongodb.org/display/DOCS/Aggregation
+ */
+ public DBObject group( DBObject key , DBObject cond , DBObject initial , String reduce , String finalize )
+ throws MongoException {
+ GroupCommand cmd = new GroupCommand(this, key, cond, initial, reduce, finalize);
+ return group( cmd );
+ }
+
+ /**
+ * Applies a group operation
+ * @param cmd the group command
+ * @return
+ * @throws MongoException
+ * @see http://www.mongodb.org/display/DOCS/Aggregation
+ */
+ public DBObject group( GroupCommand cmd ) {
+ CommandResult res = _db.command( cmd.toDBObject(), getOptions() );
+ res.throwOnError();
+ return (DBObject)res.get( "retval" );
+ }
+
+
+ /**
+ * @deprecated prefer the {@link DBCollection#group(com.massivecraft.mcore3.lib.mongodb.GroupCommand)} which is more standard
+ * Applies a group operation
+ * @param args object representing the arguments to the group function
+ * @return
+ * @throws MongoException
+ * @see http://www.mongodb.org/display/DOCS/Aggregation
+ */
+ @Deprecated
+ public DBObject group( DBObject args )
+ throws MongoException {
+ args.put( "ns" , getName() );
+ CommandResult res = _db.command( new BasicDBObject( "group" , args ), getOptions() );
+ res.throwOnError();
+ return (DBObject)res.get( "retval" );
+ }
+
+ /**
+ * find distinct values for a key
+ * @param key
+ * @return
+ */
+ @SuppressWarnings("rawtypes")
+ public List distinct( String key ){
+ return distinct( key , new BasicDBObject() );
+ }
+
+ /**
+ * find distinct values for a key
+ * @param key
+ * @param query query to match
+ * @return
+ */
+ @SuppressWarnings("rawtypes")
+ public List distinct( String key , DBObject query ){
+ DBObject c = BasicDBObjectBuilder.start()
+ .add( "distinct" , getName() )
+ .add( "key" , key )
+ .add( "query" , query )
+ .get();
+
+ CommandResult res = _db.command( c, getOptions() );
+ res.throwOnError();
+ return (List)(res.get( "values" ));
+ }
+
+ /**
+ * performs a map reduce operation
+ * Runs the command in REPLACE output mode (saves to named collection)
+ *
+ * @param map
+ * map function in javascript code
+ * @param outputTarget
+ * optional - leave null if want to use temp collection
+ * @param reduce
+ * reduce function in javascript code
+ * @param query
+ * to match
+ * @return
+ * @throws MongoException
+ * @dochub mapreduce
+ */
+ public MapReduceOutput mapReduce( String map , String reduce , String outputTarget , DBObject query ) throws MongoException{
+ return mapReduce( new MapReduceCommand( this , map , reduce , outputTarget , MapReduceCommand.OutputType.REPLACE, query ) );
+ }
+
+ /**
+ * performs a map reduce operation
+ * Specify an outputType to control job execution
+ * * INLINE - Return results inline
+ * * REPLACE - Replace the output collection with the job output
+ * * MERGE - Merge the job output with the existing contents of outputTarget
+ * * REDUCE - Reduce the job output with the existing contents of
+ * outputTarget
+ *
+ * @param map
+ * map function in javascript code
+ * @param outputTarget
+ * optional - leave null if want to use temp collection
+ * @param outputType
+ * set the type of job output
+ * @param reduce
+ * reduce function in javascript code
+ * @param query
+ * to match
+ * @return
+ * @throws MongoException
+ * @dochub mapreduce
+ */
+ public MapReduceOutput mapReduce( String map , String reduce , String outputTarget , MapReduceCommand.OutputType outputType , DBObject query )
+ throws MongoException{
+ return mapReduce( new MapReduceCommand( this , map , reduce , outputTarget , outputType , query ) );
+ }
+
+ /**
+ * performs a map reduce operation
+ *
+ * @param command
+ * object representing the parameters
+ * @return
+ * @throws MongoException
+ */
+ public MapReduceOutput mapReduce( MapReduceCommand command ) throws MongoException{
+ DBObject cmd = command.toDBObject();
+ // if type in inline, then query options like slaveOk is fine
+ CommandResult res = null;
+ if (command.getOutputType() == MapReduceCommand.OutputType.INLINE)
+ res = _db.command( cmd, getOptions(), command.getReadPreference() != null ? command.getReadPreference() : getReadPreference() );
+ else
+ res = _db.command( cmd );
+ res.throwOnError();
+ return new MapReduceOutput( this , cmd, res );
+ }
+
+ /**
+ * performs a map reduce operation
+ *
+ * @param command
+ * object representing the parameters
+ * @return
+ * @throws MongoException
+ */
+ public MapReduceOutput mapReduce( DBObject command ) throws MongoException{
+ if ( command.get( "mapreduce" ) == null && command.get( "mapReduce" ) == null )
+ throw new IllegalArgumentException( "need mapreduce arg" );
+ CommandResult res = _db.command( command );
+ res.throwOnError();
+ return new MapReduceOutput( this , command, res );
+ }
+
+ /**
+ * Return a list of the indexes for this collection. Each object
+ * in the list is the "info document" from MongoDB
+ *
+ * @return list of index documents
+ */
+ public List getIndexInfo() {
+ BasicDBObject cmd = new BasicDBObject();
+ cmd.put("ns", getFullName());
+
+ DBCursor cur = _db.getCollection("system.indexes").find(cmd);
+
+ List list = new ArrayList();
+
+ while(cur.hasNext()) {
+ list.add(cur.next());
+ }
+
+ return list;
+ }
+
+ /**
+ * Drops an index from this collection
+ * @param keys keys of the index
+ * @throws MongoException
+ */
+ public void dropIndex( DBObject keys )
+ throws MongoException {
+ dropIndexes( genIndexName( keys ) );
+ }
+
+ /**
+ * Drops an index from this collection
+ * @param name name of index to drop
+ * @throws MongoException
+ */
+ public void dropIndex( String name )
+ throws MongoException {
+ dropIndexes( name );
+ }
+
+ /**
+ * gets the collections statistics ("collstats" command)
+ * @return
+ */
+ public CommandResult getStats() {
+ return getDB().command(new BasicDBObject("collstats", getName()), getOptions());
+ }
+
+ /**
+ * returns whether or not this is a capped collection
+ * @return
+ */
+ public boolean isCapped() {
+ CommandResult stats = getStats();
+ Object capped = stats.get("capped");
+ return(capped != null && (Integer)capped == 1);
+ }
+
+ // ------
+
+ /**
+ * Initializes a new collection. No operation is actually performed on the database.
+ * @param base database in which to create the collection
+ * @param name the name of the collection
+ */
+ protected DBCollection( DB base , String name ){
+ _db = base;
+ _name = name;
+ _fullName = _db.getName() + "." + name;
+ _options = new Bytes.OptionHolder( _db._options );
+ }
+
+ protected DBObject _checkObject( DBObject o , boolean canBeNull , boolean query ){
+ if ( o == null ){
+ if ( canBeNull )
+ return null;
+ throw new IllegalArgumentException( "can't be null" );
+ }
+
+ if ( o.isPartialObject() && ! query )
+ throw new IllegalArgumentException( "can't save partial objects" );
+
+ if ( ! query ){
+ _checkKeys(o);
+ }
+ return o;
+ }
+
+ /**
+ * Checks key strings for invalid characters.
+ */
+ private void _checkKeys( DBObject o ) {
+ for ( String s : o.keySet() ){
+ validateKey ( s );
+ Object inner = o.get( s );
+ if ( inner instanceof DBObject ) {
+ _checkKeys( (DBObject)inner );
+ } else if ( inner instanceof Map ) {
+ _checkKeys( (Map)inner );
+ }
+ }
+ }
+
+ /**
+ * Checks key strings for invalid characters.
+ */
+ private void _checkKeys( Map o ) {
+ for ( String s : o.keySet() ){
+ validateKey ( s );
+ Object inner = o.get( s );
+ if ( inner instanceof DBObject ) {
+ _checkKeys( (DBObject)inner );
+ } else if ( inner instanceof Map ) {
+ _checkKeys( (Map)inner );
+ }
+ }
+ }
+
+ /**
+ * Check for invalid key names
+ * @param s the string field/key to check
+ * @exception IllegalArgumentException if the key is not valid.
+ */
+ private void validateKey(String s ) {
+ if ( s.contains( "." ) )
+ throw new IllegalArgumentException( "fields stored in the db can't have . in them. (Bad Key: '" + s + "')" );
+ if ( s.startsWith( "$" ) )
+ throw new IllegalArgumentException( "fields stored in the db can't start with '$' (Bad Key: '" + s + "')" );
+ }
+
+ /**
+ * Finds a collection that is prefixed with this collection's name.
+ * A typical use of this might be
+ *
+ * DBCollection users = mongo.getCollection( "wiki" ).getCollection( "users" );
+ *
+ * Which is equivalent to
+ *
+ * DBCollection users = mongo.getCollection( "wiki.users" );
+ *
+ * @param n the name of the collection to find
+ * @return the matching collection
+ */
+ public DBCollection getCollection( String n ){
+ return _db.getCollection( _name + "." + n );
+ }
+
+ /**
+ * Returns the name of this collection.
+ * @return the name of this collection
+ */
+ public String getName(){
+ return _name;
+ }
+
+ /**
+ * Returns the full name of this collection, with the database name as a prefix.
+ * @return the name of this collection
+ */
+ public String getFullName(){
+ return _fullName;
+ }
+
+ /**
+ * Returns the database this collection is a member of.
+ * @return this collection's database
+ */
+ public DB getDB(){
+ return _db;
+ }
+
+ /**
+ * Returns if this collection's database is read-only
+ * @param strict if an exception should be thrown if the database is read-only
+ * @return if this collection's database is read-only
+ * @throws RuntimeException if the database is read-only and strict
is set
+ */
+ protected boolean checkReadOnly( boolean strict ){
+ if ( ! _db._readOnly )
+ return false;
+
+ if ( ! strict )
+ return true;
+
+ throw new IllegalStateException( "db is read only" );
+ }
+
+ @Override
+ public int hashCode(){
+ return _fullName.hashCode();
+ }
+
+ @Override
+ public boolean equals( Object o ){
+ return o == this;
+ }
+
+ @Override
+ public String toString(){
+ return _name;
+ }
+
+ /**
+ * Sets a default class for objects in this collection; null resets the class to nothing.
+ * @param c the class
+ * @throws IllegalArgumentException if c
is not a DBObject
+ */
+ @SuppressWarnings("rawtypes")
+ public void setObjectClass( Class c ){
+ if ( c == null ){
+ // reset
+ _wrapper = null;
+ _objectClass = null;
+ return;
+ }
+
+ if ( ! DBObject.class.isAssignableFrom( c ) )
+ throw new IllegalArgumentException( c.getName() + " is not a DBObject" );
+ _objectClass = c;
+ if ( ReflectionDBObject.class.isAssignableFrom( c ) )
+ _wrapper = ReflectionDBObject.getWrapper( c );
+ else
+ _wrapper = null;
+ }
+
+ /**
+ * Gets the default class for objects in the collection
+ * @return the class
+ */
+ @SuppressWarnings("rawtypes")
+ public Class getObjectClass(){
+ return _objectClass;
+ }
+
+ /**
+ * sets the internal class
+ * @param path
+ * @param c
+ */
+ @SuppressWarnings("rawtypes")
+ public void setInternalClass( String path , Class c ){
+ _internalClass.put( path , c );
+ }
+
+ /**
+ * gets the internal class
+ * @param path
+ * @return
+ */
+ @SuppressWarnings("rawtypes")
+ protected Class getInternalClass( String path ){
+ Class c = _internalClass.get( path );
+ if ( c != null )
+ return c;
+
+ if ( _wrapper == null )
+ return null;
+ return _wrapper.getInternalClass( path );
+ }
+
+ /**
+ * Set the write concern for this collection. Will be used for
+ * writes to this collection. Overrides any setting of write
+ * concern at the DB level. See the documentation for
+ * {@link WriteConcern} for more information.
+ *
+ * @param concern write concern to use
+ */
+ public void setWriteConcern( WriteConcern concern ){
+ _concern = concern;
+ }
+
+ /**
+ * Get the write concern for this collection.
+ * @return
+ */
+ public WriteConcern getWriteConcern(){
+ if ( _concern != null )
+ return _concern;
+ return _db.getWriteConcern();
+ }
+
+ /**
+ * Sets the read preference for this collection. Will be used as default
+ * for reads from this collection; overrides DB & Connection level settings.
+ * See the * documentation for {@link ReadPreference} for more information.
+ *
+ * @param preference Read Preference to use
+ */
+ public void setReadPreference( ReadPreference preference ){
+ _readPref = preference;
+ }
+
+ /**
+ * Gets the read preference
+ * @return
+ */
+ public ReadPreference getReadPreference(){
+ if ( _readPref != null )
+ return _readPref;
+ return _db.getReadPreference();
+ }
+ /**
+ * makes this query ok to run on a slave node
+ *
+ * @deprecated Replaced with ReadPreference.SECONDARY
+ * @see com.massivecraft.mcore3.lib.mongodb.ReadPreference.SECONDARY
+ */
+ @Deprecated
+ public void slaveOk(){
+ addOption( Bytes.QUERYOPTION_SLAVEOK );
+ }
+
+ /**
+ * adds a default query option
+ * @param option
+ */
+ public void addOption( int option ){
+ _options.add(option);
+ }
+
+ /**
+ * sets the default query options
+ * @param options
+ */
+ public void setOptions( int options ){
+ _options.set(options);
+ }
+
+ /**
+ * resets the default query options
+ */
+ public void resetOptions(){
+ _options.reset();
+ }
+
+ /**
+ * gets the default query options
+ * @return
+ */
+ public int getOptions(){
+ return _options.get();
+ }
+
+ /**
+ * Set a customer decoder factory for this collection. Set to null to use the default from MongoOptions.
+ * @param fact the factory to set.
+ */
+ public synchronized void setDBDecoderFactory(DBDecoderFactory fact) {
+ _decoderFactory = fact;
+ }
+
+ /**
+ * Get the decoder factory for this collection. A null return value means that the default from MongoOptions
+ * is being used.
+ * @return the factory
+ */
+ public synchronized DBDecoderFactory getDBDecoderFactory() {
+ return _decoderFactory;
+ }
+
+ /**
+ * Set a customer encoder factory for this collection. Set to null to use the default from MongoOptions.
+ * @param fact the factory to set.
+ */
+ public synchronized void setDBEncoderFactory(DBEncoderFactory fact) {
+ _encoderFactory = fact;
+ }
+
+ /**
+ * Get the encoder factory for this collection. A null return value means that the default from MongoOptions
+ * is being used.
+ * @return the factory
+ */
+ public synchronized DBEncoderFactory getDBEncoderFactory() {
+ return _encoderFactory;
+ }
+
+ final DB _db;
+
+ final protected String _name;
+ final protected String _fullName;
+
+ protected List _hintFields;
+ private WriteConcern _concern = null;
+ private ReadPreference _readPref = null;
+ private DBDecoderFactory _decoderFactory;
+ private DBEncoderFactory _encoderFactory;
+ final Bytes.OptionHolder _options;
+
+ @SuppressWarnings("rawtypes")
+ protected Class _objectClass = null;
+ @SuppressWarnings("rawtypes")
+ private Map _internalClass = Collections.synchronizedMap( new HashMap() );
+ private ReflectionDBObject.JavaWrapper _wrapper = null;
+
+ final private Set _createdIndexes = new HashSet();
+}
diff --git a/src/com/massivecraft/mcore3/lib/mongodb/DBConnector.java b/src/com/massivecraft/mcore3/lib/mongodb/DBConnector.java
new file mode 100644
index 00000000..88f08df4
--- /dev/null
+++ b/src/com/massivecraft/mcore3/lib/mongodb/DBConnector.java
@@ -0,0 +1,106 @@
+// DBConnector.java
+
+/**
+ * Copyright (C) 2008 10gen 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 com.massivecraft.mcore3.lib.mongodb;
+
+
+/**
+ * Interface that provides the ability to exchange request/response with the database
+ */
+public interface DBConnector {
+
+ /**
+ * initiates a "consistent request" on the thread.
+ * Once this has been called, the connector will ensure that the same underlying connection is always used for a given thread.
+ * This happens until requestStop() is called.
+ */
+ public void requestStart();
+ /**
+ * terminates the "consistent request".
+ */
+ public void requestDone();
+ /**
+ * Ensures that a connection exists for the "consistent request"
+ */
+ public void requestEnsureConnection();
+
+ /**
+ * does a write operation
+ * @param db the database
+ * @param m the request message
+ * @param concern the write concern
+ * @return the write result
+ * @throws MongoException
+ */
+ public WriteResult say( DB db , OutMessage m , WriteConcern concern ) throws MongoException;
+ /**
+ * does a write operation
+ * @param db the database
+ * @param m the request message
+ * @param concern the write concern
+ * @param hostNeeded specific server to connect to
+ * @return the write result
+ * @throws MongoException
+ */
+ public WriteResult say( DB db , OutMessage m , WriteConcern concern , ServerAddress hostNeeded ) throws MongoException;
+
+ /**
+ * does a read operation on the database
+ * @param db the database
+ * @param coll the collection
+ * @param m the request message
+ * @param hostNeeded specific server to connect to
+ * @param decoder the decoder to use
+ * @return the read result
+ * @throws MongoException
+ */
+ public Response call( DB db , DBCollection coll , OutMessage m ,
+ ServerAddress hostNeeded , DBDecoder decoder ) throws MongoException;
+ /**
+ *
+ * does a read operation on the database
+ * @param db the database
+ * @param coll the collection
+ * @param m the request message
+ * @param hostNeeded specific server to connect to
+ * @param retries the number of retries in case of an error
+ * @return the read result
+ * @throws MongoException
+ */
+ public Response call( DB db , DBCollection coll , OutMessage m , ServerAddress hostNeeded , int retries ) throws MongoException;
+
+ /**
+ * does a read operation on the database
+ * @param db the database
+ * @param coll the collection
+ * @param m the request message
+ * @param hostNeeded specific server to connect to
+ * @param retries number of retries in case of error
+ * @param readPref the read preferences
+ * @param decoder the decoder to use
+ * @return the read result
+ * @throws MongoException
+ */
+ public Response call( DB db , DBCollection coll , OutMessage m , ServerAddress hostNeeded , int retries , ReadPreference readPref , DBDecoder decoder ) throws MongoException;
+
+ /**
+ * returns true if the connector is in a usable state
+ * @return
+ */
+ public boolean isOpen();
+}
diff --git a/src/com/massivecraft/mcore3/lib/mongodb/DBCursor.java b/src/com/massivecraft/mcore3/lib/mongodb/DBCursor.java
new file mode 100644
index 00000000..71f0c7e9
--- /dev/null
+++ b/src/com/massivecraft/mcore3/lib/mongodb/DBCursor.java
@@ -0,0 +1,761 @@
+// DBCursor.java
+
+/**
+ * Copyright (C) 2008 10gen 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 com.massivecraft.mcore3.lib.mongodb;
+
+import java.io.Closeable;
+import java.util.*;
+
+import com.massivecraft.mcore3.lib.mongodb.DBApiLayer.Result;
+
+
+/** An iterator over database results.
+ * Doing a find()
query on a collection returns a
+ * DBCursor
thus
+ *
+ *
+ * DBCursor cursor = collection.find( query );
+ * if( cursor.hasNext() )
+ * DBObject obj = cursor.next();
+ *
+ *
+ * Warning: Calling toArray
or length
on
+ * a DBCursor will irrevocably turn it into an array. This
+ * means that, if the cursor was iterating over ten million results
+ * (which it was lazily fetching from the database), suddenly there will
+ * be a ten-million element array in memory. Before converting to an array,
+ * make sure that there are a reasonable number of results using
+ * skip()
and limit()
.
+ *
For example, to get an array of the 1000-1100th elements of a cursor, use
+ *
+ *
+ * List obj = collection.find( query ).skip( 1000 ).limit( 100 ).toArray();
+ *
+ *
+ * @dochub cursors
+ */
+public class DBCursor implements Iterator , Iterable, Closeable {
+
+ /**
+ * Initializes a new database cursor
+ * @param collection collection to use
+ * @param q query to perform
+ * @param k keys to return from the query
+ * @param preference the Read Preference for this query
+ */
+ public DBCursor( DBCollection collection , DBObject q , DBObject k, ReadPreference preference ){
+ if (collection == null) {
+ throw new IllegalArgumentException("collection is null");
+ }
+ _collection = collection;
+ _query = q == null ? new BasicDBObject() : q;
+ _keysWanted = k;
+ _options = _collection.getOptions();
+ _readPref = preference;
+ _decoderFact = collection.getDBDecoderFactory();
+ }
+
+ /**
+ * Types of cursors: iterator or array.
+ */
+ static enum CursorType { ITERATOR , ARRAY };
+
+ /**
+ * Creates a copy of an existing database cursor.
+ * The new cursor is an iterator, even if the original
+ * was an array.
+ *
+ * @return the new cursor
+ */
+ public DBCursor copy() {
+ DBCursor c = new DBCursor(_collection, _query, _keysWanted, _readPref);
+ c._orderBy = _orderBy;
+ c._hint = _hint;
+ c._hintDBObj = _hintDBObj;
+ c._limit = _limit;
+ c._skip = _skip;
+ c._options = _options;
+ c._batchSize = _batchSize;
+ c._snapshot = _snapshot;
+ c._explain = _explain;
+ if ( _specialFields != null )
+ c._specialFields = new BasicDBObject( _specialFields.toMap() );
+ return c;
+ }
+
+ /**
+ * creates a copy of this cursor object that can be iterated.
+ * Note:
+ * - you can iterate the DBCursor itself without calling this method
+ * - no actual data is getting copied.
+ *
+ * @return
+ */
+ public Iterator iterator(){
+ return this.copy();
+ }
+
+ // ---- querty modifiers --------
+
+ /**
+ * Sorts this cursor's elements.
+ * This method must be called before getting any object from the cursor.
+ * @param orderBy the fields by which to sort
+ * @return a cursor pointing to the first element of the sorted results
+ */
+ public DBCursor sort( DBObject orderBy ){
+ if ( _it != null )
+ throw new IllegalStateException( "can't sort after executing query" );
+
+ _orderBy = orderBy;
+ return this;
+ }
+
+ /**
+ * adds a special operator like $maxScan or $returnKey
+ * e.g. addSpecial( "$returnKey" , 1 )
+ * e.g. addSpecial( "$maxScan" , 100 )
+ * @param name
+ * @param o
+ * @return
+ * @dochub specialOperators
+ */
+ public DBCursor addSpecial( String name , Object o ){
+ if ( _specialFields == null )
+ _specialFields = new BasicDBObject();
+ _specialFields.put( name , o );
+ return this;
+ }
+
+ /**
+ * Informs the database of indexed fields of the collection in order to improve performance.
+ * @param indexKeys a DBObject
with fields and direction
+ * @return same DBCursor for chaining operations
+ */
+ public DBCursor hint( DBObject indexKeys ){
+ if ( _it != null )
+ throw new IllegalStateException( "can't hint after executing query" );
+
+ _hintDBObj = indexKeys;
+ return this;
+ }
+
+ /**
+ * Informs the database of an indexed field of the collection in order to improve performance.
+ * @param indexName the name of an index
+ * @return same DBCursort for chaining operations
+ */
+ public DBCursor hint( String indexName ){
+ if ( _it != null )
+ throw new IllegalStateException( "can't hint after executing query" );
+
+ _hint = indexName;
+ return this;
+ }
+
+ /**
+ * Use snapshot mode for the query. Snapshot mode assures no duplicates are
+ * returned, or objects missed, which were present at both the start and end
+ * of the query's execution (if an object is new during the query, or deleted
+ * during the query, it may or may not be returned, even with snapshot mode).
+ * Note that short query responses (less than 1MB) are always effectively snapshotted.
+ * Currently, snapshot mode may not be used with sorting or explicit hints.
+ * @return same DBCursor for chaining operations
+ */
+ public DBCursor snapshot() {
+ if (_it != null)
+ throw new IllegalStateException("can't snapshot after executing the query");
+
+ _snapshot = true;
+
+ return this;
+ }
+
+ /**
+ * Returns an object containing basic information about the
+ * execution of the query that created this cursor
+ * This creates a DBObject
with the key/value pairs:
+ * "cursor" : cursor type
+ * "nScanned" : number of records examined by the database for this query
+ * "n" : the number of records that the database returned
+ * "millis" : how long it took the database to execute the query
+ * @return a DBObject
+ * @dochub explain
+ */
+ public DBObject explain(){
+ DBCursor c = copy();
+ c._explain = true;
+ if (c._limit > 0) {
+ // need to pass a negative batchSize as limit for explain
+ c._batchSize = c._limit * -1;
+ c._limit = 0;
+ }
+ return c.next();
+ }
+
+ /**
+ * Limits the number of elements returned.
+ * Note: parameter n should be positive, although a negative value is supported for legacy reason.
+ * Passing a negative value will call {@link DBCursor#batchSize(int)} which is the preferred method.
+ * @param n the number of elements to return
+ * @return a cursor to iterate the results
+ * @dochub limit
+ */
+ public DBCursor limit( int n ){
+ if ( _it != null )
+ throw new IllegalStateException( "can't set limit after executing query" );
+
+ if (n > 0)
+ _limit = n;
+ else if (n < 0)
+ batchSize(n);
+ return this;
+ }
+
+ /**
+ * Limits the number of elements returned in one batch.
+ * A cursor typically fetches a batch of result objects and store them locally.
+ *
+ * If batchSize is positive, it represents the size of each batch of objects retrieved.
+ * It can be adjusted to optimize performance and limit data transfer.
+ *
+ * If batchSize is negative, it will limit of number objects returned, that fit within the max batch size limit (usually 4MB), and cursor will be closed.
+ * For example if batchSize is -10, then the server will return a maximum of 10 documents and as many as can fit in 4MB, then close the cursor.
+ * Note that this feature is different from limit() in that documents must fit within a maximum size, and it removes the need to send a request to close the cursor server-side.
+ *
+ * The batch size can be changed even after a cursor is iterated, in which case the setting will apply on the next batch retrieval.
+ *
+ * @param n the number of elements to return in a batch
+ * @return
+ */
+ public DBCursor batchSize( int n ){
+ // check for special case, used to have server bug with 1
+ if ( n == 1 )
+ n = 2;
+
+ if ( _it != null ) {
+ if (_it instanceof DBApiLayer.Result)
+ ((DBApiLayer.Result)_it).setBatchSize(n);
+ }
+
+ _batchSize = n;
+ return this;
+ }
+
+ /**
+ * Discards a given number of elements at the beginning of the cursor.
+ * @param n the number of elements to skip
+ * @return a cursor pointing to the new first element of the results
+ * @throws RuntimeException if the cursor has started to be iterated through
+ */
+ public DBCursor skip( int n ){
+ if ( _it != null )
+ throw new IllegalStateException( "can't set skip after executing query" );
+ _skip = n;
+ return this;
+ }
+
+ /**
+ * gets the cursor id.
+ * @return the cursor id, or 0 if there is no active cursor.
+ */
+ public long getCursorId() {
+ if ( _it instanceof Result )
+ return ((Result)_it).getCursorId();
+
+ return 0;
+ }
+
+ /**
+ * kills the current cursor on the server.
+ */
+ public void close() {
+ if ( _it instanceof Result )
+ ((Result)_it).close();
+ }
+
+ /**
+ * makes this query ok to run on a slave node
+ *
+ * @return a copy of the same cursor (for chaining)
+ *
+ * @deprecated Replaced with ReadPreference.SECONDARY
+ * @see com.massivecraft.mcore3.lib.mongodb.ReadPreference.SECONDARY
+ */
+ @Deprecated
+ public DBCursor slaveOk(){
+ return addOption( Bytes.QUERYOPTION_SLAVEOK );
+ }
+
+ /**
+ * adds a query option - see Bytes.QUERYOPTION_* for list
+ * @param option
+ * @return
+ */
+ public DBCursor addOption( int option ){
+ if ( option == Bytes.QUERYOPTION_EXHAUST )
+ throw new IllegalArgumentException("The exhaust option is not user settable.");
+
+ _options |= option;
+ return this;
+ }
+
+ /**
+ * sets the query option - see Bytes.QUERYOPTION_* for list
+ * @param options
+ */
+ public DBCursor setOptions( int options ){
+ _options = options;
+ return this;
+ }
+
+ /**
+ * resets the query options
+ */
+ public DBCursor resetOptions(){
+ _options = 0;
+ return this;
+ }
+
+ /**
+ * gets the query options
+ * @return
+ */
+ public int getOptions(){
+ return _options;
+ }
+
+ // ---- internal stuff ------
+
+ private void _check()
+ throws MongoException {
+ if ( _it != null )
+ return;
+
+ _lookForHints();
+
+ DBObject foo = _query;
+ if (hasSpecialQueryFields()) {
+ foo = _specialFields == null ? new BasicDBObject() : _specialFields;
+
+ _addToQueryObject(foo, "query", _query, true);
+ _addToQueryObject(foo, "orderby", _orderBy, false);
+ if (_hint != null)
+ _addToQueryObject(foo, "$hint", _hint);
+ if (_hintDBObj != null)
+ _addToQueryObject(foo, "$hint", _hintDBObj);
+
+ if (_explain)
+ foo.put("$explain", true);
+ if (_snapshot)
+ foo.put("$snapshot", true);
+ }
+
+ _it = _collection.__find(foo, _keysWanted, _skip, _batchSize, _limit, _options, _readPref, getDecoder());
+ }
+
+ // Only create a new decoder if there is a decoder factory explicitly set on the collection. Otherwise return null
+ // so that the collection can use a cached decoder
+ private DBDecoder getDecoder() {
+ return _decoderFact != null ? _decoderFact.create() : null;
+ }
+
+ /**
+ * if there is a hint to use, use it
+ */
+ private void _lookForHints(){
+
+ if ( _hint != null ) // if someone set a hint, then don't do this
+ return;
+
+ if ( _collection._hintFields == null )
+ return;
+
+ Set mykeys = _query.keySet();
+
+ for ( DBObject o : _collection._hintFields ){
+
+ Set hintKeys = o.keySet();
+
+ if ( ! mykeys.containsAll( hintKeys ) )
+ continue;
+
+ hint( o );
+ return;
+ }
+ }
+
+ boolean hasSpecialQueryFields(){
+ if ( _specialFields != null )
+ return true;
+
+ if ( _orderBy != null && _orderBy.keySet().size() > 0 )
+ return true;
+
+ if ( _hint != null || _hintDBObj != null || _snapshot )
+ return true;
+
+ return _explain;
+ }
+
+ void _addToQueryObject( DBObject query , String field , DBObject thing , boolean sendEmpty ){
+ if ( thing == null )
+ return;
+
+ if ( ! sendEmpty && thing.keySet().size() == 0 )
+ return;
+
+ _addToQueryObject( query , field , thing );
+ }
+
+ void _addToQueryObject( DBObject query , String field , Object thing ){
+
+ if ( thing == null )
+ return;
+
+ query.put( field , thing );
+ }
+
+ void _checkType( CursorType type ){
+ if ( _cursorType == null ){
+ _cursorType = type;
+ return;
+ }
+
+ if ( type == _cursorType )
+ return;
+
+ throw new IllegalArgumentException( "can't switch cursor access methods" );
+ }
+
+ private DBObject _next()
+ throws MongoException {
+ if ( _cursorType == null )
+ _checkType( CursorType.ITERATOR );
+
+ _check();
+
+ _cur = _it.next();
+ _num++;
+
+ if ( _keysWanted != null && _keysWanted.keySet().size() > 0 ){
+ _cur.markAsPartialObject();
+ //throw new UnsupportedOperationException( "need to figure out partial" );
+ }
+
+ if ( _cursorType == CursorType.ARRAY ){
+ _all.add( _cur );
+ }
+
+ return _cur;
+ }
+
+ /**
+ * gets the number of times, so far, that the cursor retrieved a batch from the database
+ * @return
+ */
+ public int numGetMores(){
+ if ( _it instanceof DBApiLayer.Result )
+ return ((DBApiLayer.Result)_it).numGetMores();
+
+ throw new IllegalArgumentException("_it not a real result" );
+ }
+
+ /**
+ * gets a list containing the number of items received in each batch
+ * @return
+ */
+ public List getSizes(){
+ if ( _it instanceof DBApiLayer.Result )
+ return ((DBApiLayer.Result)_it).getSizes();
+
+ throw new IllegalArgumentException("_it not a real result" );
+ }
+
+ private boolean _hasNext()
+ throws MongoException {
+ _check();
+
+ if ( _limit > 0 && _num >= _limit )
+ return false;
+
+ return _it.hasNext();
+ }
+
+ /**
+ * Returns the number of objects through which the cursor has iterated.
+ * @return the number of objects seen
+ */
+ public int numSeen(){
+ return _num;
+ }
+
+ // ----- iterator api -----
+
+ /**
+ * Checks if there is another object available
+ * @return
+ * @throws MongoException
+ */
+ public boolean hasNext() throws MongoException {
+ _checkType( CursorType.ITERATOR );
+ return _hasNext();
+ }
+
+ /**
+ * Returns the object the cursor is at and moves the cursor ahead by one.
+ * @return the next element
+ * @throws MongoException
+ */
+ public DBObject next() throws MongoException {
+ _checkType( CursorType.ITERATOR );
+ return _next();
+ }
+
+ /**
+ * Returns the element the cursor is at.
+ * @return the next element
+ */
+ public DBObject curr(){
+ _checkType( CursorType.ITERATOR );
+ return _cur;
+ }
+
+ /**
+ * Not implemented.
+ */
+ public void remove(){
+ throw new UnsupportedOperationException( "can't remove from a cursor" );
+ }
+
+
+ // ---- array api -----
+
+ void _fill( int n )
+ throws MongoException {
+ _checkType( CursorType.ARRAY );
+ while ( n >= _all.size() && _hasNext() )
+ _next();
+ }
+
+ /**
+ * pulls back all items into an array and returns the number of objects.
+ * Note: this can be resource intensive
+ * @see #count()
+ * @see #size()
+ * @return the number of elements in the array
+ * @throws MongoException
+ */
+ public int length()
+ throws MongoException {
+ _checkType( CursorType.ARRAY );
+ _fill( Integer.MAX_VALUE );
+ return _all.size();
+ }
+
+ /**
+ * Converts this cursor to an array.
+ * @return an array of elements
+ * @throws MongoException
+ */
+ public List toArray()
+ throws MongoException {
+ return toArray( Integer.MAX_VALUE );
+ }
+
+ /**
+ * Converts this cursor to an array.
+ * @param max the maximum number of objects to return
+ * @return an array of objects
+ * @throws MongoException
+ */
+ public List toArray( int max )
+ throws MongoException {
+ _checkType( CursorType.ARRAY );
+ _fill( max - 1 );
+ return _all;
+ }
+
+ /**
+ * for testing only!
+ * Iterates cursor and counts objects
+ * @see #count()
+ * @return num objects
+ */
+ public int itcount(){
+ int n = 0;
+ while ( this.hasNext() ){
+ this.next();
+ n++;
+ }
+ return n;
+ }
+
+ /**
+ * Counts the number of objects matching the query
+ * This does not take limit/skip into consideration
+ * @see #size()
+ * @return the number of objects
+ * @throws MongoException
+ */
+ public int count()
+ throws MongoException {
+ if ( _collection == null )
+ throw new IllegalArgumentException( "why is _collection null" );
+ if ( _collection._db == null )
+ throw new IllegalArgumentException( "why is _collection._db null" );
+
+ return (int)_collection.getCount(this._query, this._keysWanted);
+ }
+
+ /**
+ * Counts the number of objects matching the query
+ * this does take limit/skip into consideration
+ * @see #count()
+ * @return the number of objects
+ * @throws MongoException
+ */
+ public int size()
+ throws MongoException {
+ if ( _collection == null )
+ throw new IllegalArgumentException( "why is _collection null" );
+ if ( _collection._db == null )
+ throw new IllegalArgumentException( "why is _collection._db null" );
+
+ return (int)_collection.getCount(this._query, this._keysWanted, this._limit, this._skip );
+ }
+
+
+ /**
+ * gets the fields to be returned
+ * @return
+ */
+ public DBObject getKeysWanted(){
+ return _keysWanted;
+ }
+
+ /**
+ * gets the query
+ * @return
+ */
+ public DBObject getQuery(){
+ return _query;
+ }
+
+ /**
+ * gets the collection
+ * @return
+ */
+ public DBCollection getCollection(){
+ return _collection;
+ }
+
+ /**
+ * Gets the Server Address of the server that data is pulled from.
+ * Note that this information may not be available until hasNext() or next() is called.
+ * @return
+ */
+ public ServerAddress getServerAddress() {
+ if (_it != null && _it instanceof DBApiLayer.Result)
+ return ((DBApiLayer.Result)_it).getServerAddress();
+
+ return null;
+ }
+
+ /**
+ * Sets the read preference for this cursor.
+ * See the * documentation for {@link ReadPreference}
+ * for more information.
+ *
+ * @param preference Read Preference to use
+ */
+ public DBCursor setReadPreference( ReadPreference preference ){
+ _readPref = preference;
+ return this;
+ }
+
+ /**
+ * Gets the default read preference
+ * @return
+ */
+ public ReadPreference getReadPreference(){
+ return _readPref;
+ }
+
+ public DBCursor setDecoderFactory(DBDecoderFactory fact){
+ _decoderFact = fact;
+ return this;
+ }
+
+ public DBDecoderFactory getDecoderFactory(){
+ return _decoderFact;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("Cursor id=").append(getCursorId());
+ sb.append(", ns=").append(getCollection().getFullName());
+ sb.append(", query=").append(getQuery());
+ if (getKeysWanted() != null)
+ sb.append(", fields=").append(getKeysWanted());
+ sb.append(", numIterated=").append(_num);
+ if (_skip != 0)
+ sb.append(", skip=").append(_skip);
+ if (_limit != 0)
+ sb.append(", limit=").append(_limit);
+ if (_batchSize != 0)
+ sb.append(", batchSize=").append(_batchSize);
+
+ ServerAddress addr = getServerAddress();
+ if (addr != null)
+ sb.append(", addr=").append(addr);
+
+ if (_readPref != null)
+ sb.append(", readPreference=").append( _readPref.toString() );
+ return sb.toString();
+ }
+
+ // ---- query setup ----
+ private final DBCollection _collection;
+ private final DBObject _query;
+ private final DBObject _keysWanted;
+
+ private DBObject _orderBy = null;
+ private String _hint = null;
+ private DBObject _hintDBObj = null;
+ private boolean _explain = false;
+ private int _limit = 0;
+ private int _batchSize = 0;
+ private int _skip = 0;
+ private boolean _snapshot = false;
+ private int _options = 0;
+ private ReadPreference _readPref;
+ private DBDecoderFactory _decoderFact;
+
+ private DBObject _specialFields;
+
+ // ---- result info ----
+ private Iterator _it = null;
+
+ private CursorType _cursorType = null;
+ private DBObject _cur = null;
+ private int _num = 0;
+
+ private final ArrayList _all = new ArrayList();
+}
diff --git a/src/com/massivecraft/mcore3/lib/mongodb/DBDecoder.java b/src/com/massivecraft/mcore3/lib/mongodb/DBDecoder.java
new file mode 100644
index 00000000..95c519d5
--- /dev/null
+++ b/src/com/massivecraft/mcore3/lib/mongodb/DBDecoder.java
@@ -0,0 +1,32 @@
+/**
+ * Copyright (C) 2008 10gen 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 com.massivecraft.mcore3.lib.mongodb;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import com.massivecraft.mcore3.lib.bson.BSONDecoder;
+
+/**
+ *
+ */
+public interface DBDecoder extends BSONDecoder {
+ public DBCallback getDBCallback(DBCollection collection);
+
+ public DBObject decode( byte[] b, DBCollection collection );
+
+ public DBObject decode( InputStream in, DBCollection collection ) throws IOException;
+}
diff --git a/src/com/massivecraft/mcore3/lib/mongodb/DBDecoderFactory.java b/src/com/massivecraft/mcore3/lib/mongodb/DBDecoderFactory.java
new file mode 100644
index 00000000..93e33c60
--- /dev/null
+++ b/src/com/massivecraft/mcore3/lib/mongodb/DBDecoderFactory.java
@@ -0,0 +1,25 @@
+/**
+ * Copyright (C) 2008 10gen 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 com.massivecraft.mcore3.lib.mongodb;
+
+/**
+ *
+ */
+public interface DBDecoderFactory {
+
+ public DBDecoder create( );
+
+}
diff --git a/src/com/massivecraft/mcore3/lib/mongodb/DBEncoder.java b/src/com/massivecraft/mcore3/lib/mongodb/DBEncoder.java
new file mode 100644
index 00000000..e5ab3f02
--- /dev/null
+++ b/src/com/massivecraft/mcore3/lib/mongodb/DBEncoder.java
@@ -0,0 +1,21 @@
+/**
+ * Copyright (c) 2008 - 2011 10gen, 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 com.massivecraft.mcore3.lib.mongodb;
+
+import com.massivecraft.mcore3.lib.bson.*;
+import com.massivecraft.mcore3.lib.bson.io.*;
+
+public interface DBEncoder {
+ public int writeObject( OutputBuffer buf, BSONObject o );
+}
diff --git a/src/com/massivecraft/mcore3/lib/mongodb/DBEncoderFactory.java b/src/com/massivecraft/mcore3/lib/mongodb/DBEncoderFactory.java
new file mode 100644
index 00000000..b9c17eb3
--- /dev/null
+++ b/src/com/massivecraft/mcore3/lib/mongodb/DBEncoderFactory.java
@@ -0,0 +1,25 @@
+/**
+ * Copyright (C) 2008 10gen 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 com.massivecraft.mcore3.lib.mongodb;
+
+/**
+ *
+ */
+public interface DBEncoderFactory {
+
+ public DBEncoder create();
+
+}
diff --git a/src/com/massivecraft/mcore3/lib/mongodb/DBObject.java b/src/com/massivecraft/mcore3/lib/mongodb/DBObject.java
new file mode 100644
index 00000000..5f253a90
--- /dev/null
+++ b/src/com/massivecraft/mcore3/lib/mongodb/DBObject.java
@@ -0,0 +1,40 @@
+// DBObject.java
+
+/**
+ * Copyright (C) 2008 10gen 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 com.massivecraft.mcore3.lib.mongodb;
+
+import com.massivecraft.mcore3.lib.bson.BSONObject;
+
+/**
+ * A key-value map that can be saved to the database.
+ */
+public interface DBObject extends BSONObject {
+
+ /**
+ * if this object was retrieved with only some fields (using a field filter)
+ * this method will be called to mark it as such.
+ */
+ public void markAsPartialObject();
+
+ /**
+ * whether markAsPartialObject was ever called
+ * only matters if you are going to upsert and do not want to risk losing fields
+ */
+ public boolean isPartialObject();
+
+}
diff --git a/src/com/massivecraft/mcore3/lib/mongodb/DBPointer.java b/src/com/massivecraft/mcore3/lib/mongodb/DBPointer.java
new file mode 100644
index 00000000..af13129e
--- /dev/null
+++ b/src/com/massivecraft/mcore3/lib/mongodb/DBPointer.java
@@ -0,0 +1,60 @@
+// DBPointer.java
+
+/**
+ * Copyright (C) 2008 10gen 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 com.massivecraft.mcore3.lib.mongodb;
+
+import com.massivecraft.mcore3.lib.bson.types.ObjectId;
+
+/**
+ * @deprecated
+ */
+@Deprecated
+public class DBPointer extends DBRefBase {
+
+ static final boolean D = Boolean.getBoolean( "DEBUG.DBPOINTER" );
+
+ /**
+ * CTOR used for testing BSON encoding. Otherwise
+ * non-functional due to a DBRef needing a parent db object,
+ * a fieldName and a db
+ *
+ * @param ns namespace to point to
+ * @param id value of _id
+ */
+ public DBPointer(String ns, ObjectId id) {
+ this (null, null, null, ns, id);
+ }
+
+ DBPointer( DBObject parent , String fieldName , DB db , String ns , ObjectId id ){
+ super(db, ns, (Object)id);
+
+ _parent = parent;
+ _fieldName = fieldName;
+ }
+
+ public String toString(){
+ return "{ \"$ref\" : \"" + _ns + "\", \"$id\" : ObjectId(\"" + _id + "\") }";
+ }
+
+ public ObjectId getId() {
+ return (ObjectId)_id;
+ }
+
+ final DBObject _parent;
+ final String _fieldName;
+}
diff --git a/src/com/massivecraft/mcore3/lib/mongodb/DBPort.java b/src/com/massivecraft/mcore3/lib/mongodb/DBPort.java
new file mode 100644
index 00000000..8820cadd
--- /dev/null
+++ b/src/com/massivecraft/mcore3/lib/mongodb/DBPort.java
@@ -0,0 +1,345 @@
+// DBPort.java
+
+/**
+ * Copyright (C) 2008 10gen 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 com.massivecraft.mcore3.lib.mongodb;
+
+import com.massivecraft.mcore3.lib.mongodb.util.ThreadUtil;
+
+import java.io.BufferedInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * represents a Port to the database, which is effectively a single connection to a server
+ * Methods implemented at the port level should throw the raw exceptions like IOException,
+ * so that the connector above can make appropriate decisions on how to handle.
+ */
+public class DBPort {
+
+ /**
+ * the default port
+ */
+ public static final int PORT = 27017;
+ static final boolean USE_NAGLE = false;
+
+ static final long CONN_RETRY_TIME_MS = 15000;
+
+ /**
+ * creates a new DBPort
+ * @param addr the server address
+ */
+ public DBPort( ServerAddress addr ){
+ this( addr , null , new MongoOptions() );
+ }
+
+ DBPort( ServerAddress addr, DBPortPool pool, MongoOptions options ){
+ _options = options;
+ _sa = addr;
+ _addr = addr.getSocketAddress();
+ _pool = pool;
+
+ _hashCode = _addr.hashCode();
+
+ _logger = Logger.getLogger( _rootLogger.getName() + "." + addr.toString() );
+ _decoder = _options.dbDecoderFactory.create();
+ }
+
+ Response call( OutMessage msg , DBCollection coll ) throws IOException{
+ return go( msg, coll );
+ }
+
+ Response call( OutMessage msg , DBCollection coll , DBDecoder decoder) throws IOException{
+ return go( msg, coll, false, null, decoder);
+ }
+
+ Response call( OutMessage msg , DBCollection coll , ReadPreference readPref , DBDecoder decoder) throws IOException{
+ return go( msg, coll, false, readPref, decoder);
+ }
+
+ void say( OutMessage msg )
+ throws IOException {
+ go( msg , null );
+ }
+
+ private synchronized Response go( OutMessage msg , DBCollection coll )
+ throws IOException {
+ return go( msg , coll , false, null, null );
+ }
+
+ private synchronized Response go( OutMessage msg , DBCollection coll , DBDecoder decoder ) throws IOException{
+ return go( msg, coll, false, null, decoder );
+ }
+
+ private synchronized Response go( OutMessage msg , DBCollection coll , boolean forceReponse , ReadPreference readPref, DBDecoder decoder)
+ throws IOException {
+
+ if ( _processingResponse ){
+ if ( coll == null ){
+ // this could be a pipeline and should be safe
+ }
+ else {
+ // this could cause issues since we're reading data off the wire
+ throw new IllegalStateException( "DBPort.go called and expecting a response while processing another response" );
+ }
+ }
+
+ _calls++;
+
+ if ( _socket == null )
+ _open();
+
+ if ( _out == null )
+ throw new IllegalStateException( "_out shouldn't be null" );
+
+ try {
+ msg.prepare();
+ msg.pipe( _out );
+
+ if ( _pool != null )
+ _pool._everWorked = true;
+
+ if ( coll == null && ! forceReponse )
+ return null;
+
+ _processingResponse = true;
+ return new Response( _sa , coll , _in , (decoder == null ? _decoder : decoder) );
+ }
+ catch ( IOException ioe ){
+ close();
+ throw ioe;
+ }
+ finally {
+ _processingResponse = false;
+ }
+ }
+
+ synchronized CommandResult getLastError( DB db , WriteConcern concern ) throws IOException{
+ DBApiLayer dbAL = (DBApiLayer) db;
+ return runCommand( dbAL, concern.getCommand() );
+ }
+
+ synchronized private Response findOne( DB db , String coll , DBObject q ) throws IOException {
+ OutMessage msg = OutMessage.query( db._mongo , 0 , db.getName() + "." + coll , 0 , -1 , q , null );
+ Response res = go( msg , db.getCollection( coll ) , null );
+ return res;
+ }
+
+ @SuppressWarnings("unused")
+ synchronized private Response findOne( String ns , DBObject q ) throws IOException{
+ OutMessage msg = OutMessage.query( null , 0 , ns , 0 , -1 , q , null );
+ Response res = go( msg , null , true, null, null );
+ return res;
+ }
+
+ synchronized CommandResult runCommand( DB db , DBObject cmd ) throws IOException {
+ Response res = findOne( db , "$cmd" , cmd );
+ return convertToCommandResult(cmd, res);
+ }
+
+ private CommandResult convertToCommandResult(DBObject cmd, Response res) {
+ if ( res.size() == 0 )
+ return null;
+ if ( res.size() > 1 )
+ throw new MongoInternalException( "something is wrong. size:" + res.size() );
+
+ DBObject data = res.get(0);
+ if ( data == null )
+ throw new MongoInternalException( "something is wrong, no command result" );
+
+ CommandResult cr = new CommandResult(cmd, res.serverUsed());
+ cr.putAll( data );
+ return cr;
+ }
+
+ synchronized CommandResult tryGetLastError( DB db , long last, WriteConcern concern) throws IOException {
+ if ( last != _calls )
+ return null;
+
+ return getLastError( db , concern );
+ }
+
+ /**
+ * makes sure that a connection to the server has been opened
+ * @throws IOException
+ */
+ public synchronized void ensureOpen()
+ throws IOException {
+
+ if ( _socket != null )
+ return;
+
+ _open();
+ }
+
+ boolean _open()
+ throws IOException {
+
+ long sleepTime = 100;
+
+ long maxAutoConnectRetryTime = CONN_RETRY_TIME_MS;
+ if (_options.maxAutoConnectRetryTime > 0) {
+ maxAutoConnectRetryTime = _options.maxAutoConnectRetryTime;
+ }
+
+ final long start = System.currentTimeMillis();
+ while ( true ){
+
+ IOException lastError = null;
+
+ try {
+ _socket = _options.socketFactory.createSocket();
+ _socket.connect( _addr , _options.connectTimeout );
+
+ _socket.setTcpNoDelay( ! USE_NAGLE );
+ _socket.setKeepAlive( _options.socketKeepAlive );
+ _socket.setSoTimeout( _options.socketTimeout );
+ _in = new BufferedInputStream( _socket.getInputStream() );
+ _out = _socket.getOutputStream();
+ return true;
+ }
+ catch ( IOException ioe ){
+ lastError = new IOException( "couldn't connect to [" + _addr + "] bc:" + ioe );
+ _logger.log( Level.INFO , "connect fail to : " + _addr , ioe );
+ close();
+ }
+
+ if ( ! _options.autoConnectRetry || ( _pool != null && ! _pool._everWorked ) )
+ throw lastError;
+
+ long sleptSoFar = System.currentTimeMillis() - start;
+
+ if ( sleptSoFar >= maxAutoConnectRetryTime )
+ throw lastError;
+
+ if ( sleepTime + sleptSoFar > maxAutoConnectRetryTime )
+ sleepTime = maxAutoConnectRetryTime - sleptSoFar;
+
+ _logger.severe( "going to sleep and retry. total sleep time after = " + ( sleptSoFar + sleptSoFar ) + "ms this time:" + sleepTime + "ms" );
+ ThreadUtil.sleep( sleepTime );
+ sleepTime *= 2;
+
+ }
+ }
+
+ @Override
+ public int hashCode(){
+ return _hashCode;
+ }
+
+ /**
+ * returns a String representation of the target host
+ * @return
+ */
+ public String host(){
+ return _addr.toString();
+ }
+
+ /**
+ * @return the server address for this port
+ */
+ public ServerAddress serverAddress() {
+ return _sa;
+ }
+
+ @Override
+ public String toString(){
+ return "{DBPort " + host() + "}";
+ }
+
+ @Override
+ protected void finalize() throws Throwable{
+ super.finalize();
+ close();
+ }
+
+ /**
+ * closes the underlying connection and streams
+ */
+ protected void close(){
+ _authed.clear();
+
+ if ( _socket != null ){
+ try {
+ _socket.close();
+ }
+ catch ( Exception e ){
+ // don't care
+ }
+ }
+
+ _in = null;
+ _out = null;
+ _socket = null;
+ }
+
+ void checkAuth( DB db ) throws IOException {
+ if ( db._username == null ){
+ if ( db._name.equals( "admin" ) )
+ return;
+ checkAuth( db._mongo.getDB( "admin" ) );
+ return;
+ }
+ if ( _authed.containsKey( db ) )
+ return;
+
+ CommandResult res = runCommand( db , new BasicDBObject( "getnonce" , 1 ) );
+ res.throwOnError();
+
+ DBObject temp = db._authCommand( res.getString( "nonce" ) );
+
+ res = runCommand( db , temp );
+
+ res.throwOnError();
+ _authed.put( db , true );
+ }
+
+ /**
+ * Gets the pool that this port belongs to
+ * @return
+ */
+ public DBPortPool getPool() {
+ return _pool;
+ }
+
+ final int _hashCode;
+ final ServerAddress _sa;
+ final InetSocketAddress _addr;
+ final DBPortPool _pool;
+ final MongoOptions _options;
+ final Logger _logger;
+ final DBDecoder _decoder;
+
+ private Socket _socket;
+ private InputStream _in;
+ private OutputStream _out;
+
+ private boolean _processingResponse;
+
+ private Map _authed = new ConcurrentHashMap( );
+ int _lastThread;
+ long _calls = 0;
+
+ private static Logger _rootLogger = Logger.getLogger( "com.mongodb.port" );
+}
diff --git a/src/com/massivecraft/mcore3/lib/mongodb/DBPortPool.java b/src/com/massivecraft/mcore3/lib/mongodb/DBPortPool.java
new file mode 100644
index 00000000..ca940d24
--- /dev/null
+++ b/src/com/massivecraft/mcore3/lib/mongodb/DBPortPool.java
@@ -0,0 +1,252 @@
+// DBPortPool.java
+
+/**
+ * Copyright (C) 2008 10gen 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 com.massivecraft.mcore3.lib.mongodb;
+
+import java.lang.management.ManagementFactory;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.logging.Level;
+
+import javax.management.JMException;
+import javax.management.MBeanServer;
+import javax.management.MalformedObjectNameException;
+import javax.management.ObjectName;
+
+import com.massivecraft.mcore3.lib.mongodb.util.SimplePool;
+
+public class DBPortPool extends SimplePool {
+
+ static class Holder {
+
+ Holder( MongoOptions options ){
+ _options = options;
+ {
+ MBeanServer temp = null;
+ try {
+ temp = ManagementFactory.getPlatformMBeanServer();
+ }
+ catch ( Throwable t ){
+ }
+
+ _server = temp;
+ }
+ }
+
+ DBPortPool get( ServerAddress addr ){
+
+ DBPortPool p = _pools.get( addr );
+
+ if (p != null)
+ return p;
+
+ synchronized (_pools) {
+ p = _pools.get( addr );
+ if (p != null) {
+ return p;
+ }
+
+ p = new DBPortPool( addr , _options );
+ _pools.put( addr , p);
+
+ if ( _server != null ){
+ try {
+ ObjectName on = createObjectName( addr );
+ if ( _server.isRegistered( on ) ){
+ _server.unregisterMBean( on );
+ Bytes.LOGGER.log( Level.INFO , "multiple Mongo instances for same host, jmx numbers might be off" );
+ }
+ _server.registerMBean( p , on );
+ }
+ catch ( JMException e ){
+ Bytes.LOGGER.log( Level.WARNING , "jmx registration error: " + e + " continuing..." );
+ }
+ catch ( java.security.AccessControlException e ){
+ Bytes.LOGGER.log( Level.WARNING , "jmx registration error: " + e + " continuing..." );
+ }
+ }
+
+ }
+
+ return p;
+ }
+
+ void close(){
+ synchronized ( _pools ){
+ for ( DBPortPool p : _pools.values() ){
+ p.close();
+
+ try {
+ ObjectName on = createObjectName( p._addr );
+ if ( _server.isRegistered( on ) ){
+ _server.unregisterMBean( on );
+ }
+ } catch ( JMException e ){
+ Bytes.LOGGER.log( Level.WARNING , "jmx de-registration error, continuing" , e );
+ }
+ }
+ }
+ }
+
+ private ObjectName createObjectName( ServerAddress addr ) throws MalformedObjectNameException {
+ String name = "com.mongodb:type=ConnectionPool,host=" + addr.toString().replace( ":" , ",port=" ) + ",instance=" + _serial;
+ if ( _options.description != null )
+ name += ",description=" + _options.description;
+ return new ObjectName( name );
+ }
+
+ final MongoOptions _options;
+ final Map _pools = Collections.synchronizedMap( new HashMap() );
+ final MBeanServer _server;
+ final int _serial = nextSerial.incrementAndGet();
+
+ // we use this to give each Holder a different mbean name
+ static AtomicInteger nextSerial = new AtomicInteger(0);
+ }
+
+ // ----
+
+ public static class NoMoreConnection extends MongoInternalException {
+ private static final long serialVersionUID = -4415279469780082174L;
+
+ NoMoreConnection( String msg ){
+ super( msg );
+ }
+ }
+
+ public static class SemaphoresOut extends NoMoreConnection {
+ private static final long serialVersionUID = -4415279469780082174L;
+ SemaphoresOut(){
+ super( "Out of semaphores to get db connection" );
+ }
+ }
+
+ public static class ConnectionWaitTimeOut extends NoMoreConnection {
+ private static final long serialVersionUID = -4415279469780082174L;
+ ConnectionWaitTimeOut(int timeout) {
+ super("Connection wait timeout after " + timeout + " ms");
+ }
+ }
+
+ // ----
+
+ DBPortPool( ServerAddress addr , MongoOptions options ){
+ super( "DBPortPool-" + addr.toString() + ", options = " + options.toString() , options.connectionsPerHost , options.connectionsPerHost );
+ _options = options;
+ _addr = addr;
+ _waitingSem = new Semaphore( _options.connectionsPerHost * _options.threadsAllowedToBlockForConnectionMultiplier );
+ }
+
+ protected long memSize( DBPort p ){
+ return 0;
+ }
+
+ protected int pick( int iThink , boolean couldCreate ){
+ final int id = System.identityHashCode(Thread.currentThread());
+ final int s = _availSafe.size();
+ for ( int i=0; i all = new ArrayList();
+ while ( true ){
+ DBPort temp = get(0);
+ if ( temp == null )
+ break;
+ all.add( temp );
+ }
+
+ for ( DBPort p : all ){
+ p.close();
+ done(p);
+ }
+
+ }
+
+ void close(){
+ clear();
+ }
+
+ public void cleanup( DBPort p ){
+ p.close();
+ }
+
+ public boolean ok( DBPort t ){
+ return _addr.getSocketAddress().equals( t._addr );
+ }
+
+ protected DBPort createNew(){
+ return new DBPort( _addr , this , _options );
+ }
+
+ public ServerAddress getServerAddress() {
+ return _addr;
+ }
+
+ final MongoOptions _options;
+ final private Semaphore _waitingSem;
+ final ServerAddress _addr;
+ boolean _everWorked = false;
+}
diff --git a/src/com/massivecraft/mcore3/lib/mongodb/DBRef.java b/src/com/massivecraft/mcore3/lib/mongodb/DBRef.java
new file mode 100644
index 00000000..61ab66b3
--- /dev/null
+++ b/src/com/massivecraft/mcore3/lib/mongodb/DBRef.java
@@ -0,0 +1,65 @@
+// DBRef.java
+
+/**
+ * Copyright (C) 2008 10gen 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 com.massivecraft.mcore3.lib.mongodb;
+
+import com.massivecraft.mcore3.lib.bson.BSONObject;
+
+/**
+ * overrides DBRefBase to understand a BSONObject representation of a reference.
+ * @dochub dbrefs
+ */
+public class DBRef extends DBRefBase {
+
+ static final boolean D = Boolean.getBoolean( "DEBUG.DBREF" );
+
+ /**
+ * Creates a DBRef
+ * @param db the database
+ * @param o a BSON object representing the reference
+ */
+ public DBRef(DB db , BSONObject o ){
+ super( db , o.get( "$ref" ).toString() , o.get( "$id" ) );
+ }
+
+ /**
+ * Creates a DBRef
+ * @param db the database
+ * @param ns the namespace where the object is stored
+ * @param id the object id
+ */
+ public DBRef(DB db , String ns , Object id) {
+ super(db, ns, id);
+ }
+
+ /**
+ * fetches a referenced object from the database
+ * @param db the database
+ * @param ref the reference
+ * @return
+ */
+ public static DBObject fetch(DB db, DBObject ref) {
+ String ns;
+ Object id;
+
+ if ((ns = (String)ref.get("$ref")) != null && (id = ref.get("$id")) != null) {
+ return db.getCollection(ns).findOne(new BasicDBObject("_id", id));
+ }
+ return null;
+ }
+}
diff --git a/src/com/massivecraft/mcore3/lib/mongodb/DBRefBase.java b/src/com/massivecraft/mcore3/lib/mongodb/DBRefBase.java
new file mode 100644
index 00000000..8b14a569
--- /dev/null
+++ b/src/com/massivecraft/mcore3/lib/mongodb/DBRefBase.java
@@ -0,0 +1,105 @@
+// DBRefBase.java
+
+/**
+ * Copyright (C) 2008 10gen 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 com.massivecraft.mcore3.lib.mongodb;
+
+/**
+ * represents a database reference, which points to an object stored in the database
+ */
+public class DBRefBase {
+
+
+ /**
+ * Creates a DBRefBase
+ * @param db the database
+ * @param ns the namespace where the object is stored
+ * @param id the object id
+ */
+ public DBRefBase(DB db , String ns , Object id) {
+ _db = db;
+ _ns = ns.intern();
+ _id = id;
+ }
+
+ /**
+ * fetches the object referenced from the database
+ * @return
+ */
+ public DBObject fetch() {
+ if (_loadedPointedTo)
+ return _pointedTo;
+
+ if (_db == null)
+ throw new RuntimeException("no db");
+
+ final DBCollection coll = _db.getCollectionFromString(_ns);
+
+ _pointedTo = coll.findOne(_id);
+ _loadedPointedTo = true;
+ return _pointedTo;
+ }
+
+ @Override
+ public String toString(){
+ return "{ \"$ref\" : \"" + _ns + "\", \"$id\" : \"" + _id + "\" }";
+ }
+
+ /**
+ * Gets the object's id
+ * @return
+ */
+ public Object getId() {
+ return _id;
+ }
+
+ /**
+ * Gets the object's namespace (collection name)
+ * @return
+ */
+ public String getRef() {
+ return _ns;
+ }
+
+ /**
+ * Gets the database
+ * @return
+ */
+ public DB getDB() {
+ return _db;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == this)
+ return true;
+
+ if (obj instanceof DBRefBase) {
+ DBRefBase ref = (DBRefBase) obj;
+ if (_ns.equals(ref.getRef()) && _id.equals(ref.getId()))
+ return true;
+ }
+ return false;
+ }
+
+ final Object _id;
+ final String _ns;
+ final DB _db;
+
+ private boolean _loadedPointedTo = false;
+ private DBObject _pointedTo;
+}
diff --git a/src/com/massivecraft/mcore3/lib/mongodb/DBTCPConnector.java b/src/com/massivecraft/mcore3/lib/mongodb/DBTCPConnector.java
new file mode 100644
index 00000000..167baa69
--- /dev/null
+++ b/src/com/massivecraft/mcore3/lib/mongodb/DBTCPConnector.java
@@ -0,0 +1,581 @@
+// DBTCPConnector.java
+
+/**
+ * Copyright (C) 2008 10gen 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 com.massivecraft.mcore3.lib.mongodb;
+
+import com.massivecraft.mcore3.lib.mongodb.ReadPreference.TaggedReadPreference;
+
+import java.io.IOException;
+import java.net.SocketTimeoutException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+public class DBTCPConnector implements DBConnector {
+
+ static Logger _logger = Logger.getLogger( Bytes.LOGGER.getName() + ".tcp" );
+ static Logger _createLogger = Logger.getLogger( _logger.getName() + ".connect" );
+
+ public DBTCPConnector( Mongo m , ServerAddress addr )
+ throws MongoException {
+ _mongo = m;
+ _portHolder = new DBPortPool.Holder( m._options );
+ _checkAddress( addr );
+
+ _createLogger.info( addr.toString() );
+
+ setMasterAddress(addr);
+ _allHosts = null;
+ _rsStatus = null;
+
+ }
+
+ public DBTCPConnector( Mongo m , ServerAddress ... all )
+ throws MongoException {
+ this( m , Arrays.asList( all ) );
+ }
+
+ public DBTCPConnector( Mongo m , List all )
+ throws MongoException {
+ _mongo = m;
+ _portHolder = new DBPortPool.Holder( m._options );
+ _checkAddress( all );
+
+ _allHosts = new ArrayList( all ); // make a copy so it can't be modified
+ _rsStatus = new ReplicaSetStatus( m, _allHosts );
+
+ _createLogger.info( all + " -> " + getAddress() );
+ }
+
+ public void start() {
+ if (_rsStatus != null)
+ _rsStatus.start();
+ }
+
+ private static ServerAddress _checkAddress( ServerAddress addr ){
+ if ( addr == null )
+ throw new NullPointerException( "address can't be null" );
+ return addr;
+ }
+
+ private static ServerAddress _checkAddress( List addrs ){
+ if ( addrs == null )
+ throw new NullPointerException( "addresses can't be null" );
+ if ( addrs.size() == 0 )
+ throw new IllegalArgumentException( "need to specify at least 1 address" );
+ return addrs.get(0);
+ }
+
+ /**
+ * Start a "request".
+ *
+ * A "request" is a group of operations in which order matters. Examples
+ * include inserting a document and then performing a query which expects
+ * that document to have been inserted, or performing an operation and
+ * then using com.mongodb.Mongo.getLastError to perform error-checking
+ * on that operation. When a thread performs operations in a "request", all
+ * operations will be performed on the same socket, so they will be
+ * correctly ordered.
+ */
+ @Override
+ public void requestStart(){
+ _myPort.get().requestStart();
+ }
+
+ /**
+ * End the current "request", if this thread is in one.
+ *
+ * By ending a request when it is safe to do so the built-in connection-
+ * pool is allowed to reassign requests to different sockets in order to
+ * more effectively balance load. See requestStart for more information.
+ */
+ @Override
+ public void requestDone(){
+ _myPort.get().requestDone();
+ }
+
+ @Override
+ public void requestEnsureConnection(){
+ _myPort.get().requestEnsureConnection();
+ }
+
+ void _checkClosed(){
+ if ( _closed.get() )
+ throw new IllegalStateException( "this Mongo has been closed" );
+ }
+
+ WriteResult _checkWriteError( DB db, DBPort port , WriteConcern concern )
+ throws MongoException, IOException {
+ CommandResult e = port.runCommand( db , concern.getCommand() );
+
+ e.throwOnError();
+ return new WriteResult( e , concern );
+ }
+
+ @Override
+ public WriteResult say( DB db , OutMessage m , WriteConcern concern )
+ throws MongoException {
+ return say( db , m , concern , null );
+ }
+
+ @Override
+ public WriteResult say( DB db , OutMessage m , WriteConcern concern , ServerAddress hostNeeded )
+ throws MongoException {
+
+ _checkClosed();
+ checkMaster( false , true );
+
+ MyPort mp = _myPort.get();
+ DBPort port = mp.get( true , ReadPreference.PRIMARY, hostNeeded );
+
+ try {
+ port.checkAuth( db );
+ port.say( m );
+ if ( concern.callGetLastError() ){
+ return _checkWriteError( db , port , concern );
+ }
+ else {
+ return new WriteResult( db , port , concern );
+ }
+ }
+ catch ( IOException ioe ){
+ mp.error( port , ioe );
+ _error( ioe, false );
+
+ if ( concern.raiseNetworkErrors() )
+ throw new MongoException.Network( "can't say something" , ioe );
+
+ CommandResult res = new CommandResult(port.serverAddress());
+ res.put( "ok" , false );
+ res.put( "$err" , "NETWORK ERROR" );
+ return new WriteResult( res , concern );
+ }
+ catch ( MongoException me ){
+ throw me;
+ }
+ catch ( RuntimeException re ){
+ mp.error( port , re );
+ throw re;
+ }
+ finally {
+ mp.done( port );
+ m.doneWithMessage();
+ }
+ }
+
+ @Override
+ public Response call( DB db , DBCollection coll , OutMessage m, ServerAddress hostNeeded, DBDecoder decoder )
+ throws MongoException {
+ return call( db , coll , m , hostNeeded , 2, null, decoder );
+ }
+
+
+ public Response call( DB db , DBCollection coll , OutMessage m , ServerAddress hostNeeded , int retries ) throws MongoException {
+ return call( db, coll, m, hostNeeded, retries, null, null);
+ }
+
+ @Override
+ public Response call( DB db, DBCollection coll, OutMessage m, ServerAddress hostNeeded, int retries, ReadPreference readPref, DBDecoder decoder ) throws MongoException{
+
+ if (readPref == null)
+ readPref = ReadPreference.PRIMARY;
+
+ if (readPref == ReadPreference.PRIMARY && m.hasOption( Bytes.QUERYOPTION_SLAVEOK ))
+ readPref = ReadPreference.SECONDARY;
+
+ boolean secondaryOk = !(readPref == ReadPreference.PRIMARY);
+
+ _checkClosed();
+ checkMaster( false, !secondaryOk );
+
+ final MyPort mp = _myPort.get();
+ final DBPort port = mp.get( false , readPref, hostNeeded );
+
+ Response res = null;
+ boolean retry = false;
+ try {
+ port.checkAuth( db );
+ res = port.call( m , coll, readPref, decoder );
+ if ( res._responseTo != m.getId() )
+ throw new MongoException( "ids don't match" );
+ }
+ catch ( IOException ioe ){
+ mp.error( port , ioe );
+ retry = retries > 0 && !coll._name.equals( "$cmd" )
+ && !(ioe instanceof SocketTimeoutException) && _error( ioe, secondaryOk );
+ if ( !retry ){
+ throw new MongoException.Network( "can't call something : " + port.host() + "/" + db,
+ ioe );
+ }
+ }
+ catch ( RuntimeException re ){
+ mp.error( port , re );
+ throw re;
+ } finally {
+ mp.done( port );
+ }
+
+ if (retry)
+ return call( db , coll , m , hostNeeded , retries - 1 , readPref, decoder );
+
+ ServerError err = res.getError();
+
+ if ( err != null && err.isNotMasterError() ){
+ checkMaster( true , true );
+ if ( retries <= 0 ){
+ throw new MongoException( "not talking to master and retries used up" );
+ }
+ return call( db , coll , m , hostNeeded , retries -1, readPref, decoder );
+ }
+
+ m.doneWithMessage();
+ return res;
+ }
+
+ public ServerAddress getAddress(){
+ DBPortPool pool = _masterPortPool;
+ return pool != null ? pool.getServerAddress() : null;
+ }
+
+ /**
+ * Gets the list of seed server addresses
+ * @return
+ */
+ public List getAllAddress() {
+ return _allHosts;
+ }
+
+ /**
+ * Gets the list of server addresses currently seen by the connector.
+ * This includes addresses auto-discovered from a replica set.
+ * @return
+ */
+ public List getServerAddressList() {
+ if (_rsStatus != null) {
+ return _rsStatus.getServerAddressList();
+ }
+
+ ServerAddress master = getAddress();
+ if (master != null) {
+ // single server
+ List list = new ArrayList();
+ list.add(master);
+ return list;
+ }
+ return null;
+ }
+
+ public ReplicaSetStatus getReplicaSetStatus() {
+ return _rsStatus;
+ }
+
+ public String getConnectPoint(){
+ ServerAddress master = getAddress();
+ return master != null ? master.toString() : null;
+ }
+
+ /**
+ * This method is called in case of an IOException.
+ * It will potentially trigger a checkMaster() to check the status of all servers.
+ * @param t the exception thrown
+ * @param secondaryOk secondaryOk flag
+ * @return true if the request should be retried, false otherwise
+ * @throws MongoException
+ */
+ boolean _error( Throwable t, boolean secondaryOk )
+ throws MongoException {
+ if (_rsStatus == null) {
+ // single server, no need to retry
+ return false;
+ }
+
+ // the replset has at least 1 server up, try to see if should switch master
+ // if no server is up, we wont retry until the updater thread finds one
+ // this is to cut down the volume of requests/errors when all servers are down
+ if ( _rsStatus.hasServerUp() ){
+ checkMaster( true , !secondaryOk );
+ }
+ return _rsStatus.hasServerUp();
+ }
+
+ class MyPort {
+
+ DBPort get( boolean keep , ReadPreference readPref, ServerAddress hostNeeded ){
+
+ if ( hostNeeded != null ){
+ if (_requestPort != null && _requestPort.serverAddress().equals(hostNeeded)) {
+ return _requestPort;
+ }
+
+ // asked for a specific host
+ return _portHolder.get( hostNeeded ).get();
+ }
+
+ if ( _requestPort != null ){
+ // we are within a request, and have a port, should stick to it
+ if ( _requestPort.getPool() == _masterPortPool || !keep ) {
+ // if keep is false, it's a read, so we use port even if master changed
+ return _requestPort;
+ }
+
+ // it's write and master has changed
+ // we fall back on new master and try to go on with request
+ // this may not be best behavior if spec of request is to stick with same server
+ _requestPort.getPool().done(_requestPort);
+ _requestPort = null;
+ }
+
+ if ( !(readPref == ReadPreference.PRIMARY) && _rsStatus != null ){
+ // if not a primary read set, try to use a secondary
+ // Do they want a Secondary, or a specific tag set?
+ if (readPref == ReadPreference.SECONDARY) {
+ ServerAddress slave = _rsStatus.getASecondary();
+ if ( slave != null ){
+ return _portHolder.get( slave ).get();
+ }
+ } else if (readPref instanceof ReadPreference.TaggedReadPreference) {
+ // Tag based read
+ ServerAddress secondary = _rsStatus.getASecondary( ( (TaggedReadPreference) readPref ).getTags() );
+ if (secondary != null)
+ return _portHolder.get( secondary ).get();
+ else
+ throw new MongoException( "Could not find any valid secondaries with the supplied tags ('" +
+ ( (TaggedReadPreference) readPref ).getTags() + "'");
+ }
+ }
+
+ if (_masterPortPool == null) {
+ // this should only happen in rare case that no master was ever found
+ // may get here at startup if it's a read, slaveOk=true, and ALL servers are down
+ throw new MongoException("Rare case where master=null, probably all servers are down");
+ }
+
+ // use master
+ DBPort p = _masterPortPool.get();
+ if ( _inRequest ) {
+ // if within request, remember port to stick to same server
+ _requestPort = p;
+ }
+
+ return p;
+ }
+
+ void done( DBPort p ){
+ // keep request port
+ if ( p != _requestPort ){
+ p.getPool().done(p);
+ }
+ }
+
+ /**
+ * call this method when there is an IOException or other low level error on port.
+ * @param p
+ * @param e
+ */
+ void error( DBPort p , Exception e ){
+ p.close();
+ _requestPort = null;
+// _logger.log( Level.SEVERE , "MyPort.error called" , e );
+
+ // depending on type of error, may need to close other connections in pool
+ p.getPool().gotError(e);
+ }
+
+ void requestEnsureConnection(){
+ if ( ! _inRequest )
+ return;
+
+ if ( _requestPort != null )
+ return;
+
+ _requestPort = _masterPortPool.get();
+ }
+
+ void requestStart(){
+ _inRequest = true;
+ }
+
+ void requestDone(){
+ if ( _requestPort != null )
+ _requestPort.getPool().done( _requestPort );
+ _requestPort = null;
+ _inRequest = false;
+ }
+
+ DBPort _requestPort;
+// DBPortPool _requestPool;
+ boolean _inRequest;
+ }
+
+ void checkMaster( boolean force , boolean failIfNoMaster )
+ throws MongoException {
+
+ if ( _rsStatus != null ){
+ if ( _masterPortPool == null || force ){
+ ReplicaSetStatus.Node master = _rsStatus.ensureMaster();
+ if ( master == null ){
+ if ( failIfNoMaster )
+ throw new MongoException( "can't find a master" );
+ }
+ else {
+ setMaster(master);
+ }
+ }
+ } else {
+ // single server, may have to obtain max bson size
+ if (_maxBsonObjectSize.get() == 0)
+ fetchMaxBsonObjectSize();
+ }
+ }
+
+ synchronized void setMaster(ReplicaSetStatus.Node master) {
+ if (_closed.get()) {
+ return;
+ }
+ setMasterAddress(master.getServerAddress());
+ _maxBsonObjectSize.set(master.getMaxBsonObjectSize());
+ }
+
+ /**
+ * Fetches the maximum size for a BSON object from the current master server
+ * @return the size, or 0 if it could not be obtained
+ */
+ int fetchMaxBsonObjectSize() {
+ if (_masterPortPool == null)
+ return 0;
+ DBPort port = _masterPortPool.get();
+ try {
+ CommandResult res = port.runCommand(_mongo.getDB("admin"), new BasicDBObject("isMaster", 1));
+ // max size was added in 1.8
+ if (res.containsField("maxBsonObjectSize")) {
+ _maxBsonObjectSize.set(((Integer) res.get("maxBsonObjectSize")).intValue());
+ } else {
+ _maxBsonObjectSize.set(Bytes.MAX_OBJECT_SIZE);
+ }
+ } catch (Exception e) {
+ _logger.log(Level.WARNING, "Exception determining maxBSONObjectSize ", e);
+ } finally {
+ port.getPool().done(port);
+ }
+
+ return _maxBsonObjectSize.get();
+ }
+
+
+
+ private synchronized boolean setMasterAddress(ServerAddress addr) {
+ DBPortPool newPool = _portHolder.get( addr );
+ if (newPool == _masterPortPool)
+ return false;
+
+ if ( _masterPortPool != null )
+ _logger.log(Level.WARNING, "Master switching from " + _masterPortPool.getServerAddress() + " to " + addr);
+ _masterPortPool = newPool;
+ return true;
+ }
+
+ public String debugString(){
+ StringBuilder buf = new StringBuilder( "DBTCPConnector: " );
+ if ( _rsStatus != null ) {
+ buf.append( "replica set : " ).append( _allHosts );
+ } else {
+ ServerAddress master = getAddress();
+ buf.append( master ).append( " " ).append( master != null ? master.getSocketAddress() : null );
+ }
+
+ return buf.toString();
+ }
+
+ public void close(){
+ _closed.set( true );
+ if ( _portHolder != null ) {
+ try {
+ _portHolder.close();
+ _portHolder = null;
+ } catch (final Throwable t) { /* nada */ }
+ }
+ if ( _rsStatus != null ) {
+ try {
+ _rsStatus.close();
+ _rsStatus = null;
+ } catch (final Throwable t) { /* nada */ }
+ }
+
+ // below this will remove the myport for this thread only
+ // client using thread pool in web framework may need to call close() from all threads
+ _myPort.remove();
+ }
+
+ /**
+ * Assigns a new DBPortPool for a given ServerAddress.
+ * This is used to obtain a new pool when the resolved IP of a host changes, for example.
+ * User application should not have to call this method directly.
+ * @param addr
+ */
+ public void updatePortPool(ServerAddress addr) {
+ // just remove from map, a new pool will be created lazily
+ _portHolder._pools.remove(addr);
+ }
+
+ /**
+ * Gets the DBPortPool associated with a ServerAddress.
+ * @param addr
+ * @return
+ */
+ public DBPortPool getDBPortPool(ServerAddress addr) {
+ return _portHolder.get(addr);
+ }
+
+ public boolean isOpen(){
+ return ! _closed.get();
+ }
+
+ /**
+ * Gets the maximum size for a BSON object supported by the current master server.
+ * Note that this value may change over time depending on which server is master.
+ * @return the maximum size, or 0 if not obtained from servers yet.
+ */
+ public int getMaxBsonObjectSize() {
+ return _maxBsonObjectSize.get();
+ }
+
+ // expose for unit testing
+ MyPort getMyPort() {
+ return _myPort.get();
+ }
+
+ private volatile DBPortPool _masterPortPool;
+ private final Mongo _mongo;
+ private DBPortPool.Holder _portHolder;
+ private final List _allHosts;
+ private ReplicaSetStatus _rsStatus;
+ private final AtomicBoolean _closed = new AtomicBoolean(false);
+
+ private final AtomicInteger _maxBsonObjectSize = new AtomicInteger(0);
+
+ private ThreadLocal _myPort = new ThreadLocal(){
+ protected MyPort initialValue(){
+ return new MyPort();
+ }
+ };
+
+}
diff --git a/src/com/massivecraft/mcore3/lib/mongodb/DefaultDBCallback.java b/src/com/massivecraft/mcore3/lib/mongodb/DefaultDBCallback.java
new file mode 100644
index 00000000..b476a020
--- /dev/null
+++ b/src/com/massivecraft/mcore3/lib/mongodb/DefaultDBCallback.java
@@ -0,0 +1,142 @@
+// DBCallback.java
+
+/**
+ * Copyright (C) 2008 10gen 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 com.massivecraft.mcore3.lib.mongodb;
+
+// Bson
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import com.massivecraft.mcore3.lib.bson.BSONObject;
+import com.massivecraft.mcore3.lib.bson.BasicBSONCallback;
+import com.massivecraft.mcore3.lib.bson.types.ObjectId;
+
+/**
+ * This class overrides BasicBSONCallback to implement some extra features specific to the Database.
+ * For example DBRef type.
+ * @author antoine
+ */
+public class DefaultDBCallback extends BasicBSONCallback implements DBCallback {
+
+ static class DefaultFactory implements DBCallbackFactory {
+ @Override
+ public DBCallback create( DBCollection collection ){
+ return new DefaultDBCallback( collection );
+ }
+ }
+
+ public static DBCallbackFactory FACTORY = new DefaultFactory();
+
+ public DefaultDBCallback( DBCollection coll ){
+ _collection = coll;
+ _db = _collection == null ? null : _collection.getDB();
+ }
+
+ @Override
+ @SuppressWarnings("deprecation")
+ public void gotDBRef( String name , String ns , ObjectId id ){
+ if ( id.equals( Bytes.COLLECTION_REF_ID ) )
+ cur().put( name , _collection );
+ else
+ cur().put( name , new DBPointer( (DBObject)cur() , name , _db , ns , id ) );
+ }
+
+ @Override
+ public void objectStart(boolean array, String name){
+ _lastName = name;
+ super.objectStart( array , name );
+ }
+
+ @Override
+ public Object objectDone(){
+ BSONObject o = (BSONObject)super.objectDone();
+ if ( ! ( o instanceof List ) &&
+ o.containsField( "$ref" ) &&
+ o.containsField( "$id" ) ){
+ return cur().put( _lastName , new DBRef( _db, o ) );
+ }
+
+ return o;
+ }
+
+ @Override
+ public BSONObject create(){
+ return _create( null );
+ }
+
+ @Override
+ public BSONObject create( boolean array , List path ){
+ if ( array )
+ return new BasicDBList();
+ return _create( path );
+ }
+
+ @SuppressWarnings("rawtypes")
+ private DBObject _create( List path ){
+
+ Class c = null;
+
+ if ( _collection != null && _collection._objectClass != null){
+ if ( path == null || path.size() == 0 ){
+ c = _collection._objectClass;
+ }
+ else {
+ StringBuilder buf = new StringBuilder();
+ for ( int i=0; i 0 )
+ buf.append(".");
+ buf.append( path.get(i) );
+ }
+ c = _collection.getInternalClass( buf.toString() );
+ }
+
+ }
+
+ if ( c != null ){
+ try {
+ return (DBObject)c.newInstance();
+ }
+ catch ( InstantiationException ie ){
+ LOGGER.log( Level.FINE , "can't create a: " + c , ie );
+ throw new MongoInternalException( "can't instantiate a : " + c , ie );
+ }
+ catch ( IllegalAccessException iae ){
+ LOGGER.log( Level.FINE , "can't create a: " + c , iae );
+ throw new MongoInternalException( "can't instantiate a : " + c , iae );
+ }
+ }
+
+ return new BasicDBObject();
+ }
+
+ DBObject dbget(){
+ return (DBObject)get();
+ }
+
+ @Override
+ public void reset(){
+ _lastName = null;
+ super.reset();
+ }
+
+ private String _lastName;
+ final DBCollection _collection;
+ final DB _db;
+ static final Logger LOGGER = Logger.getLogger( "com.mongo.DECODING" );
+}
diff --git a/src/com/massivecraft/mcore3/lib/mongodb/DefaultDBDecoder.java b/src/com/massivecraft/mcore3/lib/mongodb/DefaultDBDecoder.java
new file mode 100644
index 00000000..0c8b7afb
--- /dev/null
+++ b/src/com/massivecraft/mcore3/lib/mongodb/DefaultDBDecoder.java
@@ -0,0 +1,60 @@
+/**
+ * Copyright (C) 2008 10gen 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 com.massivecraft.mcore3.lib.mongodb;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import com.massivecraft.mcore3.lib.bson.BasicBSONDecoder;
+
+/**
+ *
+ * @author antoine
+ */
+public class DefaultDBDecoder extends BasicBSONDecoder implements DBDecoder {
+
+ static class DefaultFactory implements DBDecoderFactory {
+ @Override
+ public DBDecoder create( ){
+ return new DefaultDBDecoder( );
+ }
+ }
+
+ public static DBDecoderFactory FACTORY = new DefaultFactory();
+
+ public DefaultDBDecoder( ){
+ }
+
+ public DBCallback getDBCallback(DBCollection collection) {
+ // brand new callback every time
+ return new DefaultDBCallback(collection);
+ }
+
+ public DBObject decode(byte[] b, DBCollection collection) {
+ DBCallback cbk = getDBCallback(collection);
+ cbk.reset();
+ decode(b, cbk);
+ return (DBObject) cbk.get();
+ }
+
+ public DBObject decode(InputStream in, DBCollection collection) throws IOException {
+ DBCallback cbk = getDBCallback(collection);
+ cbk.reset();
+ decode(in, cbk);
+ return (DBObject) cbk.get();
+ }
+
+}
diff --git a/src/com/massivecraft/mcore3/lib/mongodb/DefaultDBEncoder.java b/src/com/massivecraft/mcore3/lib/mongodb/DefaultDBEncoder.java
new file mode 100644
index 00000000..d9a4077d
--- /dev/null
+++ b/src/com/massivecraft/mcore3/lib/mongodb/DefaultDBEncoder.java
@@ -0,0 +1,83 @@
+/**
+ * Copyright (c) 2008 - 2011 10gen, 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 com.massivecraft.mcore3.lib.mongodb;
+
+import static com.massivecraft.mcore3.lib.bson.BSON.EOO;
+import static com.massivecraft.mcore3.lib.bson.BSON.OBJECT;
+import static com.massivecraft.mcore3.lib.bson.BSON.REF;
+
+import com.massivecraft.mcore3.lib.bson.*;
+import com.massivecraft.mcore3.lib.bson.io.*;
+import com.massivecraft.mcore3.lib.bson.types.*;
+
+
+public class DefaultDBEncoder extends BasicBSONEncoder implements DBEncoder {
+
+ public int writeObject( OutputBuffer buf, BSONObject o ){
+ set( buf );
+ int x = super.putObject( o );
+ done();
+ return x;
+ }
+
+ static class DefaultFactory implements DBEncoderFactory {
+ @Override
+ public DBEncoder create( ){
+ return new DefaultDBEncoder( );
+ }
+ }
+
+ @SuppressWarnings("deprecation")
+ protected boolean putSpecial( String name , Object val ){
+ if ( val instanceof DBPointer ){
+ DBPointer r = (DBPointer)val;
+ putDBPointer( name , r._ns , (ObjectId)r._id );
+ return true;
+ }
+
+ if ( val instanceof DBRefBase ){
+ putDBRef( name, (DBRefBase)val );
+ return true;
+ }
+
+ return false;
+ }
+
+ protected void putDBPointer( String name , String ns , ObjectId oid ){
+ _put( REF , name );
+
+ _putValueString( ns );
+ _buf.writeInt( oid._time() );
+ _buf.writeInt( oid._machine() );
+ _buf.writeInt( oid._inc() );
+ }
+
+ protected void putDBRef( String name, DBRefBase ref ){
+ _put( OBJECT , name );
+ final int sizePos = _buf.getPosition();
+ _buf.writeInt( 0 );
+
+ _putObjectField( "$ref" , ref.getRef() );
+ _putObjectField( "$id" , ref.getId() );
+
+ _buf.write( EOO );
+ _buf.writeInt( sizePos , _buf.getPosition() - sizePos );
+ }
+
+
+ public static DBEncoderFactory FACTORY = new DefaultFactory();
+
+ public DefaultDBEncoder( ){
+ }
+
+}
diff --git a/src/com/massivecraft/mcore3/lib/mongodb/GroupCommand.java b/src/com/massivecraft/mcore3/lib/mongodb/GroupCommand.java
new file mode 100644
index 00000000..1c72df00
--- /dev/null
+++ b/src/com/massivecraft/mcore3/lib/mongodb/GroupCommand.java
@@ -0,0 +1,51 @@
+/**
+ * Copyright (C) 2008 10gen 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 com.massivecraft.mcore3.lib.mongodb;
+
+/**
+ * This class groups the argument for a group operation and can build the underlying command object
+ * @dochub mapreduce
+ */
+public class GroupCommand {
+
+ public GroupCommand(DBCollection inputCollection, DBObject keys, DBObject condition, DBObject initial, String reduce, String finalize) {
+ this.input = inputCollection.getName();
+ this.keys = keys;
+ this.condition = condition;
+ this.initial = initial;
+ this.reduce = reduce;
+ this.finalize = finalize;
+ }
+
+ public DBObject toDBObject() {
+ BasicDBObject args = new BasicDBObject();
+ args.put( "ns" , input );
+ args.put( "key" , keys );
+ args.put( "cond" , condition );
+ args.put( "$reduce" , reduce );
+ args.put( "initial" , initial );
+ if ( finalize != null )
+ args.put( "finalize" , finalize );
+ return new BasicDBObject( "group" , args );
+ }
+
+ String input;
+ DBObject keys;
+ DBObject condition;
+ DBObject initial;
+ String reduce;
+ String finalize;
+}
diff --git a/src/com/massivecraft/mcore3/lib/mongodb/LazyDBCallback.java b/src/com/massivecraft/mcore3/lib/mongodb/LazyDBCallback.java
new file mode 100644
index 00000000..7da326de
--- /dev/null
+++ b/src/com/massivecraft/mcore3/lib/mongodb/LazyDBCallback.java
@@ -0,0 +1,57 @@
+/**
+ * Copyright (C) 2008 10gen 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 com.massivecraft.mcore3.lib.mongodb;
+
+import java.util.Iterator;
+import java.util.logging.Logger;
+
+import com.massivecraft.mcore3.lib.bson.LazyBSONCallback;
+import com.massivecraft.mcore3.lib.bson.types.ObjectId;
+
+/**
+ *
+ */
+public class LazyDBCallback extends LazyBSONCallback implements DBCallback {
+
+ public LazyDBCallback( DBCollection coll ){
+ _collection = coll;
+ _db = _collection == null ? null : _collection.getDB();
+ }
+
+ @SuppressWarnings("rawtypes")
+ @Override
+ public Object createObject( byte[] data, int offset ){
+ LazyDBObject o = new LazyDBObject( data, offset, this );
+ //log.info("Created inner BSONObject: " + o);
+ // need to detect DBRef but must make sure we dont search through all fields
+ // $ref must be 1st key
+ Iterator it = o.keySet().iterator();
+ if ( it.hasNext() && it.next().equals( "$ref" ) &&
+ o.containsField( "$id" ) ){
+ return new DBRef( _db, o );
+ }
+ return o;
+ }
+
+ public Object createDBRef( String ns, ObjectId id ){
+ return new DBRef( _db, ns, id );
+ }
+
+ final DBCollection _collection;
+ final DB _db;
+ @SuppressWarnings("unused")
+ private static final Logger log = Logger.getLogger( LazyDBCallback.class.getName() );
+}
diff --git a/src/com/massivecraft/mcore3/lib/mongodb/LazyDBDecoder.java b/src/com/massivecraft/mcore3/lib/mongodb/LazyDBDecoder.java
new file mode 100644
index 00000000..24ea38fd
--- /dev/null
+++ b/src/com/massivecraft/mcore3/lib/mongodb/LazyDBDecoder.java
@@ -0,0 +1,58 @@
+/**
+ * Copyright (C) 2008 10gen 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 com.massivecraft.mcore3.lib.mongodb;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import com.massivecraft.mcore3.lib.bson.LazyBSONDecoder;
+
+/**
+ *
+ */
+public class LazyDBDecoder extends LazyBSONDecoder implements DBDecoder {
+ static class LazyDBDecoderFactory implements DBDecoderFactory {
+ @Override
+ public DBDecoder create( ){
+ return new LazyDBDecoder();
+ }
+ }
+
+ public static DBDecoderFactory FACTORY = new LazyDBDecoderFactory();
+
+ public LazyDBDecoder( ){
+ }
+
+ public DBCallback getDBCallback(DBCollection collection) {
+ // callback doesnt do anything special, could be unique per decoder
+ // but have to create per collection due to DBRef, at least
+ return new LazyDBCallback(collection);
+ }
+
+ public DBObject decode(byte[] b, DBCollection collection) {
+ DBCallback cbk = getDBCallback(collection);
+ cbk.reset();
+ decode(b, cbk);
+ return (DBObject) cbk.get();
+ }
+
+ public DBObject decode(InputStream in, DBCollection collection) throws IOException {
+ DBCallback cbk = getDBCallback(collection);
+ cbk.reset();
+ decode(in, cbk);
+ return (DBObject) cbk.get();
+ }
+}
diff --git a/src/com/massivecraft/mcore3/lib/mongodb/LazyDBEncoder.java b/src/com/massivecraft/mcore3/lib/mongodb/LazyDBEncoder.java
new file mode 100644
index 00000000..688a3cf4
--- /dev/null
+++ b/src/com/massivecraft/mcore3/lib/mongodb/LazyDBEncoder.java
@@ -0,0 +1,44 @@
+/**
+ * Copyright (c) 2008 - 2011 10gen, 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 com.massivecraft.mcore3.lib.mongodb;
+
+import com.massivecraft.mcore3.lib.bson.BSONObject;
+import com.massivecraft.mcore3.lib.bson.io.OutputBuffer;
+
+import java.io.IOException;
+
+/**
+ * Encoder that only knows how to encode BSONObject instances of type LazyDBObject.
+ */
+public class LazyDBEncoder implements DBEncoder {
+ @Override
+ public int writeObject(final OutputBuffer buf, BSONObject o) {
+ if (!(o instanceof LazyDBObject)) {
+ throw new IllegalArgumentException("LazyDBEncoder can only encode BSONObject instances of type LazyDBObject");
+ }
+
+ LazyDBObject lazyDBObject = (LazyDBObject) o;
+
+ try {
+ lazyDBObject.pipe(buf);
+ } catch (IOException e) {
+ throw new MongoException("Exception serializing a LazyDBObject", e);
+ }
+
+ return lazyDBObject.getBSONSize();
+ }
+}
diff --git a/src/com/massivecraft/mcore3/lib/mongodb/LazyDBObject.java b/src/com/massivecraft/mcore3/lib/mongodb/LazyDBObject.java
new file mode 100644
index 00000000..97ad0770
--- /dev/null
+++ b/src/com/massivecraft/mcore3/lib/mongodb/LazyDBObject.java
@@ -0,0 +1,50 @@
+/**
+ * Copyright (C) 2008 10gen 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 com.massivecraft.mcore3.lib.mongodb;
+
+import com.massivecraft.mcore3.lib.bson.LazyBSONCallback;
+import com.massivecraft.mcore3.lib.bson.LazyBSONObject;
+import com.massivecraft.mcore3.lib.bson.io.BSONByteBuffer;
+
+public class LazyDBObject extends LazyBSONObject implements DBObject {
+
+ public void markAsPartialObject() {
+ _partial = true;
+ }
+
+ public boolean isPartialObject() {
+ return _partial;
+ }
+
+ public LazyDBObject(BSONByteBuffer buff, LazyBSONCallback cbk){
+ super(buff, cbk);
+ }
+
+ public LazyDBObject(BSONByteBuffer buff, int offset, LazyBSONCallback cbk){
+ super(buff, offset, cbk);
+ }
+
+
+ public LazyDBObject(byte[] data, LazyBSONCallback cbk){
+ this(data, 0, cbk);
+ }
+
+ public LazyDBObject(byte[] data, int offset, LazyBSONCallback cbk){
+ super(data, offset, cbk);
+ }
+
+ private boolean _partial = false;
+}
diff --git a/src/com/massivecraft/mcore3/lib/mongodb/LazyWriteableDBCallback.java b/src/com/massivecraft/mcore3/lib/mongodb/LazyWriteableDBCallback.java
new file mode 100644
index 00000000..132d7dad
--- /dev/null
+++ b/src/com/massivecraft/mcore3/lib/mongodb/LazyWriteableDBCallback.java
@@ -0,0 +1,47 @@
+/**
+ * Copyright (C) 2008 10gen 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 com.massivecraft.mcore3.lib.mongodb;
+
+import java.util.Iterator;
+import java.util.logging.Logger;
+
+/**
+ *
+ */
+public class LazyWriteableDBCallback extends LazyDBCallback {
+
+ public LazyWriteableDBCallback( DBCollection coll ){
+ super(coll);
+ }
+
+ @SuppressWarnings("rawtypes")
+ @Override
+ public Object createObject( byte[] data, int offset ){
+ LazyWriteableDBObject o = new LazyWriteableDBObject( data, offset, this );
+ //log.info("Created inner BSONObject: " + o);
+ // need to detect DBRef but must make sure we dont search through all fields
+ // $ref must be 1st key
+ Iterator it = o.keySet().iterator();
+ if ( it.hasNext() && it.next().equals( "$ref" ) &&
+ o.containsField( "$id" ) ){
+ return new DBRef( _db, o );
+ }
+ return o;
+ }
+
+ @SuppressWarnings("unused")
+ private static final Logger log = Logger.getLogger( LazyWriteableDBCallback.class.getName() );
+}
diff --git a/src/com/massivecraft/mcore3/lib/mongodb/LazyWriteableDBDecoder.java b/src/com/massivecraft/mcore3/lib/mongodb/LazyWriteableDBDecoder.java
new file mode 100644
index 00000000..702d8555
--- /dev/null
+++ b/src/com/massivecraft/mcore3/lib/mongodb/LazyWriteableDBDecoder.java
@@ -0,0 +1,36 @@
+/**
+ * Copyright (C) 2008 10gen 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 com.massivecraft.mcore3.lib.mongodb;
+
+
+/**
+ *
+ */
+public class LazyWriteableDBDecoder extends LazyDBDecoder {
+ static class LazyDBDecoderFactory implements DBDecoderFactory {
+ @Override
+ public DBDecoder create( ){
+ return new LazyWriteableDBDecoder();
+ }
+ }
+
+ public static DBDecoderFactory FACTORY = new LazyDBDecoderFactory();
+
+ public DBCallback getDBCallback(DBCollection collection) {
+ return new LazyWriteableDBCallback(collection);
+ }
+
+}
diff --git a/src/com/massivecraft/mcore3/lib/mongodb/LazyWriteableDBObject.java b/src/com/massivecraft/mcore3/lib/mongodb/LazyWriteableDBObject.java
new file mode 100644
index 00000000..da23710d
--- /dev/null
+++ b/src/com/massivecraft/mcore3/lib/mongodb/LazyWriteableDBObject.java
@@ -0,0 +1,120 @@
+/**
+ * Copyright (C) 2008 10gen 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 com.massivecraft.mcore3.lib.mongodb;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import com.massivecraft.mcore3.lib.bson.BSONObject;
+import com.massivecraft.mcore3.lib.bson.LazyBSONCallback;
+import com.massivecraft.mcore3.lib.bson.io.BSONByteBuffer;
+
+public class LazyWriteableDBObject extends LazyDBObject {
+
+ public LazyWriteableDBObject(BSONByteBuffer buff, LazyBSONCallback cbk){
+ super(buff, cbk);
+ }
+
+ public LazyWriteableDBObject(BSONByteBuffer buff, int offset, LazyBSONCallback cbk){
+ super(buff, offset, cbk);
+ }
+
+
+ public LazyWriteableDBObject(byte[] data, LazyBSONCallback cbk){
+ this(data, 0, cbk);
+ }
+
+ public LazyWriteableDBObject(byte[] data, int offset, LazyBSONCallback cbk){
+ super(data, offset, cbk);
+ }
+
+ /* (non-Javadoc)
+ * @see org.bson.LazyBSONObject#put(java.lang.String, java.lang.Object)
+ */
+ @Override
+ public Object put(String key, Object v) {
+ return writeable.put(key, v);
+ }
+
+ /* (non-Javadoc)
+ * @see org.bson.LazyBSONObject#putAll(org.bson.BSONObject)
+ */
+ @Override
+ public void putAll(BSONObject o) {
+ for(String key : o.keySet()){
+ put(key, o.get(key));
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see org.bson.LazyBSONObject#putAll(java.util.Map)
+ */
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ @Override
+ public void putAll(Map m) {
+ writeable.putAll(m);
+ }
+
+ /* (non-Javadoc)
+ * @see org.bson.LazyBSONObject#get(java.lang.String)
+ */
+ @Override
+ public Object get(String key) {
+ Object o = writeable.get(key);
+ return (o!=null) ? o : super.get(key);
+ }
+
+ /* (non-Javadoc)
+ * @see org.bson.LazyBSONObject#removeField(java.lang.String)
+ */
+ @Override
+ public Object removeField(String key) {
+ Object o = writeable.remove(key);
+ return (o!=null) ? o : super.removeField(key);
+ }
+
+ /* (non-Javadoc)
+ * @see org.bson.LazyBSONObject#containsField(java.lang.String)
+ */
+ @Override
+ public boolean containsField(String s) {
+ boolean has = writeable.containsKey(s);
+ return (has) ? has : super.containsField(s);
+ }
+
+ /* (non-Javadoc)
+ * @see org.bson.LazyBSONObject#keySet()
+ */
+ @Override
+ public Set keySet() {
+ Set combined = new HashSet();
+ combined.addAll(writeable.keySet());
+ combined.addAll(super.keySet());
+ return combined;
+ }
+
+ /* (non-Javadoc)
+ * @see org.bson.LazyBSONObject#isEmpty()
+ */
+ @Override
+ public boolean isEmpty() {
+ return writeable.isEmpty() || super.isEmpty();
+ }
+
+ final private HashMap writeable = new HashMap