/* Copyright 2010-2014 MongoDB Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using MongoDB.Bson.IO;
using MongoDB.Bson.Serialization;
using MongoDB.Bson.Serialization.Attributes;
using MongoDB.Bson.Serialization.Serializers;
namespace MongoDB.Bson
{
///
/// Represents an immutable BSON document that is represented using only the raw bytes.
///
[Serializable]
[BsonSerializer(typeof(RawBsonDocumentSerializer))]
public class RawBsonDocument : BsonDocument, IDisposable
{
// private fields
private bool _disposed;
private IByteBuffer _slice;
private List _disposableItems = new List();
private BsonBinaryReaderSettings _readerSettings = BsonBinaryReaderSettings.Defaults;
// constructors
///
/// Initializes a new instance of the class.
///
/// The slice.
/// slice
/// RawBsonDocument cannot be used with an IByteBuffer that needs disposing.
public RawBsonDocument(IByteBuffer slice)
{
if (slice == null)
{
throw new ArgumentNullException("slice");
}
_slice = slice;
}
///
/// Initializes a new instance of the class.
///
/// The bytes.
public RawBsonDocument(byte[] bytes)
: this(new ByteArrayBuffer(bytes, 0, bytes.Length, true))
{
}
// public properties
///
/// Gets the number of elements.
///
public override int ElementCount
{
get
{
ThrowIfDisposed();
using (var bsonReader = new BsonBinaryReader(new BsonBuffer(CloneSlice(), true), true, _readerSettings))
{
var elementCount = 0;
bsonReader.ReadStartDocument();
while (bsonReader.ReadBsonType() != BsonType.EndOfDocument)
{
bsonReader.SkipName();
bsonReader.SkipValue();
elementCount++;
}
bsonReader.ReadEndDocument();
return elementCount;
}
}
}
///
/// Gets the elements.
///
public override IEnumerable Elements
{
get
{
ThrowIfDisposed();
using (var bsonReader = new BsonBinaryReader(new BsonBuffer(CloneSlice(), true), true, _readerSettings))
{
bsonReader.ReadStartDocument();
while (bsonReader.ReadBsonType() != BsonType.EndOfDocument)
{
var name = bsonReader.ReadName();
var value = DeserializeBsonValue(bsonReader);
yield return new BsonElement(name, value);
}
bsonReader.ReadEndDocument();
}
}
}
///
/// Gets the element names.
///
public override IEnumerable Names
{
get
{
ThrowIfDisposed();
using (var bsonReader = new BsonBinaryReader(new BsonBuffer(CloneSlice(), true), true, _readerSettings))
{
bsonReader.ReadStartDocument();
while (bsonReader.ReadBsonType() != BsonType.EndOfDocument)
{
yield return bsonReader.ReadName();
bsonReader.SkipValue();
}
bsonReader.ReadEndDocument();
}
}
}
///
/// Gets the raw values (see BsonValue.RawValue).
///
[Obsolete("Use Values instead.")]
public override IEnumerable RawValues
{
get
{
ThrowIfDisposed();
using (var bsonReader = new BsonBinaryReader(new BsonBuffer(CloneSlice(), true), true, _readerSettings))
{
bsonReader.ReadStartDocument();
while (bsonReader.ReadBsonType() != BsonType.EndOfDocument)
{
bsonReader.SkipName();
yield return DeserializeBsonValue(bsonReader).RawValue;
}
bsonReader.ReadEndDocument();
}
}
}
///
/// Gets the slice.
///
///
/// The slice.
///
public IByteBuffer Slice
{
get { return _slice; }
}
///
/// Gets the values.
///
public override IEnumerable Values
{
get
{
ThrowIfDisposed();
using (var bsonReader = new BsonBinaryReader(new BsonBuffer(CloneSlice(), true), true, _readerSettings))
{
bsonReader.ReadStartDocument();
while (bsonReader.ReadBsonType() != BsonType.EndOfDocument)
{
bsonReader.SkipName();
yield return DeserializeBsonValue(bsonReader);
}
bsonReader.ReadEndDocument();
}
}
}
// public indexers
///
/// Gets or sets a value by position.
///
/// The position.
/// The value.
public override BsonValue this[int index]
{
get { return GetValue(index); }
set { Set(index, value); }
}
///
/// Gets the value of an element or a default value if the element is not found.
///
/// The name of the element.
/// The default value to return if the element is not found.
/// Teh value of the element or a default value if the element is not found.
[Obsolete("Use GetValue(string name, BsonValue defaultValue) instead.")]
public override BsonValue this[string name, BsonValue defaultValue]
{
get { return GetValue(name, defaultValue); }
}
///
/// Gets or sets a value by name.
///
/// The name.
/// The value.
public override BsonValue this[string name]
{
get { return GetValue(name); }
set { Set(name, value); }
}
// public methods
///
/// Adds an element to the document.
///
/// The element to add.
///
/// The document (so method calls can be chained).
///
public override BsonDocument Add(BsonElement element)
{
throw new NotSupportedException("RawBsonDocument instances are immutable.");
}
///
/// Adds elements to the document from a dictionary of key/value pairs.
///
/// The dictionary.
/// The document (so method calls can be chained).
[Obsolete("Use AddRange instead.")]
public override BsonDocument Add(Dictionary dictionary)
{
throw new NotSupportedException("RawBsonDocument instances are immutable.");
}
///
/// Adds elements to the document from a dictionary of key/value pairs.
///
/// The dictionary.
/// Which keys of the hash table to add.
/// The document (so method calls can be chained).
[Obsolete("Use AddRange(IEnumerable elements) instead.")]
public override BsonDocument Add(Dictionary dictionary, IEnumerable keys)
{
throw new NotSupportedException("RawBsonDocument instances are immutable.");
}
///
/// Adds elements to the document from a dictionary of key/value pairs.
///
/// The dictionary.
/// The document (so method calls can be chained).
[Obsolete("Use AddRange instead.")]
public override BsonDocument Add(IDictionary dictionary)
{
throw new NotSupportedException("RawBsonDocument instances are immutable.");
}
///
/// Adds elements to the document from a dictionary of key/value pairs.
///
/// The dictionary.
/// Which keys of the hash table to add.
/// The document (so method calls can be chained).
[Obsolete("Use AddRange(IEnumerable elements) instead.")]
public override BsonDocument Add(IDictionary dictionary, IEnumerable keys)
{
throw new NotSupportedException("RawBsonDocument instances are immutable.");
}
///
/// Adds elements to the document from a dictionary of key/value pairs.
///
/// The dictionary.
/// The document (so method calls can be chained).
[Obsolete("Use AddRange instead.")]
public override BsonDocument Add(IDictionary dictionary)
{
throw new NotSupportedException("RawBsonDocument instances are immutable.");
}
///
/// Adds elements to the document from a dictionary of key/value pairs.
///
/// The dictionary.
/// Which keys of the hash table to add.
/// The document (so method calls can be chained).
[Obsolete("Use AddRange(IEnumerable elements) instead.")]
public override BsonDocument Add(IDictionary dictionary, IEnumerable keys)
{
throw new NotSupportedException("RawBsonDocument instances are immutable.");
}
///
/// Adds a list of elements to the document.
///
/// The list of elements.
/// The document (so method calls can be chained).
[Obsolete("Use AddRange instead.")]
public override BsonDocument Add(IEnumerable elements)
{
throw new NotSupportedException("RawBsonDocument instances are immutable.");
}
///
/// Adds a list of elements to the document.
///
/// The list of elements.
/// The document (so method calls can be chained).
[Obsolete("Use AddRange(IEnumerable elements) instead.")]
public override BsonDocument Add(params BsonElement[] elements)
{
throw new NotSupportedException("RawBsonDocument instances are immutable.");
}
///
/// Creates and adds an element to the document.
///
/// The name of the element.
/// The value of the element.
///
/// The document (so method calls can be chained).
///
public override BsonDocument Add(string name, BsonValue value)
{
throw new NotSupportedException("RawBsonDocument instances are immutable.");
}
///
/// Creates and adds an element to the document, but only if the condition is true.
///
/// The name of the element.
/// The value of the element.
/// Whether to add the element to the document.
/// The document (so method calls can be chained).
public override BsonDocument Add(string name, BsonValue value, bool condition)
{
throw new NotSupportedException("RawBsonDocument instances are immutable.");
}
///
/// Adds elements to the document from a dictionary of key/value pairs.
///
/// The dictionary.
///
/// The document (so method calls can be chained).
///
public override BsonDocument AddRange(Dictionary dictionary)
{
throw new NotSupportedException("RawBsonDocument instances are immutable.");
}
///
/// Adds elements to the document from a dictionary of key/value pairs.
///
/// The dictionary.
///
/// The document (so method calls can be chained).
///
public override BsonDocument AddRange(IDictionary dictionary)
{
throw new NotSupportedException("RawBsonDocument instances are immutable.");
}
///
/// Adds a list of elements to the document.
///
/// The list of elements.
///
/// The document (so method calls can be chained).
///
public override BsonDocument AddRange(IEnumerable elements)
{
throw new NotSupportedException("RawBsonDocument instances are immutable.");
}
///
/// Adds elements to the document from a dictionary of key/value pairs.
///
/// The dictionary.
///
/// The document (so method calls can be chained).
///
public override BsonDocument AddRange(IEnumerable> dictionary)
{
throw new NotSupportedException("RawBsonDocument instances are immutable.");
}
///
/// Clears the document (removes all elements).
///
public override void Clear()
{
throw new NotSupportedException("RawBsonDocument instances are immutable.");
}
///
/// Creates a shallow clone of the document (see also DeepClone).
///
///
/// A shallow clone of the document.
///
public override BsonValue Clone()
{
ThrowIfDisposed();
return new RawBsonDocument(CloneSlice());
}
///
/// Tests whether the document contains an element with the specified name.
///
/// The name of the element to look for.
///
/// True if the document contains an element with the specified name.
///
public override bool Contains(string name)
{
if (name == null)
{
throw new ArgumentNullException("name");
}
ThrowIfDisposed();
using (var bsonReader = new BsonBinaryReader(new BsonBuffer(CloneSlice(), true), true, _readerSettings))
{
bsonReader.ReadStartDocument();
while (bsonReader.ReadBsonType() != BsonType.EndOfDocument)
{
if (bsonReader.ReadName() == name)
{
return true;
}
bsonReader.SkipValue();
}
bsonReader.ReadEndDocument();
return false;
}
}
///
/// Tests whether the document contains an element with the specified value.
///
/// The value of the element to look for.
///
/// True if the document contains an element with the specified value.
///
public override bool ContainsValue(BsonValue value)
{
ThrowIfDisposed();
using (var bsonReader = new BsonBinaryReader(new BsonBuffer(CloneSlice(), true), true, _readerSettings))
{
bsonReader.ReadStartDocument();
while (bsonReader.ReadBsonType() != BsonType.EndOfDocument)
{
bsonReader.SkipName();
if (DeserializeBsonValue(bsonReader).Equals(value))
{
return true;
}
}
bsonReader.ReadEndDocument();
return false;
}
}
///
/// Creates a deep clone of the document (see also Clone).
///
///
/// A deep clone of the document.
///
public override BsonValue DeepClone()
{
ThrowIfDisposed();
return new RawBsonDocument(CloneSlice());
}
///
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
///
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
///
/// Gets an element of this document.
///
/// The zero based index of the element.
///
/// The element.
///
public override BsonElement GetElement(int index)
{
if (index < 0)
{
throw new ArgumentOutOfRangeException("index");
}
ThrowIfDisposed();
using (var bsonReader = new BsonBinaryReader(new BsonBuffer(CloneSlice(), true), true, _readerSettings))
{
bsonReader.ReadStartDocument();
var i = 0;
while (bsonReader.ReadBsonType() != BsonType.EndOfDocument)
{
if (i == index)
{
var name = bsonReader.ReadName();
var value = DeserializeBsonValue(bsonReader);
return new BsonElement(name, value);
}
bsonReader.SkipName();
bsonReader.SkipValue();
i++;
}
bsonReader.ReadEndDocument();
throw new ArgumentOutOfRangeException("index");
}
}
///
/// Gets an element of this document.
///
/// The name of the element.
///
/// A BsonElement.
///
public override BsonElement GetElement(string name)
{
ThrowIfDisposed();
BsonElement element;
if (TryGetElement(name, out element))
{
return element;
}
string message = string.Format("Element '{0}' not found.", name);
throw new KeyNotFoundException(message);
}
///
/// Gets an enumerator that can be used to enumerate the elements of this document.
///
///
/// An enumerator.
///
public override IEnumerator GetEnumerator()
{
ThrowIfDisposed();
using (var bsonReader = new BsonBinaryReader(new BsonBuffer(CloneSlice(), true), true, _readerSettings))
{
bsonReader.ReadStartDocument();
while (bsonReader.ReadBsonType() != BsonType.EndOfDocument)
{
var name = bsonReader.ReadName();
var value = DeserializeBsonValue(bsonReader);
yield return new BsonElement(name, value);
}
bsonReader.ReadEndDocument();
}
}
///
/// Gets the value of an element.
///
/// The zero based index of the element.
///
/// The value of the element.
///
public override BsonValue GetValue(int index)
{
if (index < 0)
{
throw new ArgumentOutOfRangeException("index");
}
ThrowIfDisposed();
using (var bsonReader = new BsonBinaryReader(new BsonBuffer(CloneSlice(), true), true, _readerSettings))
{
bsonReader.ReadStartDocument();
var i = 0;
while (bsonReader.ReadBsonType() != BsonType.EndOfDocument)
{
bsonReader.SkipName();
if (i == index)
{
return DeserializeBsonValue(bsonReader);
}
bsonReader.SkipValue();
i++;
}
bsonReader.ReadEndDocument();
throw new ArgumentOutOfRangeException("index");
}
}
///
/// Gets the value of an element.
///
/// The name of the element.
///
/// The value of the element.
///
public override BsonValue GetValue(string name)
{
ThrowIfDisposed();
BsonValue value;
if (TryGetValue(name, out value))
{
return value;
}
string message = string.Format("Element '{0}' not found.", name);
throw new KeyNotFoundException(message);
}
///
/// Gets the value of an element or a default value if the element is not found.
///
/// The name of the element.
/// The default value returned if the element is not found.
///
/// The value of the element or the default value if the element is not found.
///
public override BsonValue GetValue(string name, BsonValue defaultValue)
{
ThrowIfDisposed();
BsonValue value;
if (TryGetValue(name, out value))
{
return value;
}
return defaultValue;
}
///
/// Inserts a new element at a specified position.
///
/// The position of the new element.
/// The element.
public override void InsertAt(int index, BsonElement element)
{
throw new NotSupportedException("RawBsonDocument instances are immutable.");
}
///
/// Merges another document into this one. Existing elements are not overwritten.
///
/// The other document.
///
/// The document (so method calls can be chained).
///
public override BsonDocument Merge(BsonDocument document)
{
throw new NotSupportedException("RawBsonDocument instances are immutable.");
}
///
/// Merges another document into this one, specifying whether existing elements are overwritten.
///
/// The other document.
/// Whether to overwrite existing elements.
///
/// The document (so method calls can be chained).
///
public override BsonDocument Merge(BsonDocument document, bool overwriteExistingElements)
{
throw new NotSupportedException("RawBsonDocument instances are immutable.");
}
///
/// Removes an element from this document (if duplicate element names are allowed
/// then all elements with this name will be removed).
///
/// The name of the element to remove.
public override void Remove(string name)
{
throw new NotSupportedException("RawBsonDocument instances are immutable.");
}
///
/// Removes an element from this document.
///
/// The zero based index of the element to remove.
public override void RemoveAt(int index)
{
throw new NotSupportedException("RawBsonDocument instances are immutable.");
}
///
/// Removes an element from this document.
///
/// The element to remove.
public override void RemoveElement(BsonElement element)
{
throw new NotSupportedException("RawBsonDocument instances are immutable.");
}
///
/// Sets the value of an element.
///
/// The zero based index of the element whose value is to be set.
/// The new value.
///
/// The document (so method calls can be chained).
///
public override BsonDocument Set(int index, BsonValue value)
{
throw new NotSupportedException("RawBsonDocument instances are immutable.");
}
///
/// Sets the value of an element (an element will be added if no element with this name is found).
///
/// The name of the element whose value is to be set.
/// The new value.
///
/// The document (so method calls can be chained).
///
public override BsonDocument Set(string name, BsonValue value)
{
throw new NotSupportedException("RawBsonDocument instances are immutable.");
}
///
/// Sets an element of the document (replaces any existing element with the same name or adds a new element if an element with the same name is not found).
///
/// The new element.
///
/// The document.
///
public override BsonDocument SetElement(BsonElement element)
{
throw new NotSupportedException("RawBsonDocument instances are immutable.");
}
///
/// Sets an element of the document (replacing the existing element at that position).
///
/// The zero based index of the element to replace.
/// The new element.
///
/// The document.
///
public override BsonDocument SetElement(int index, BsonElement element)
{
throw new NotSupportedException("RawBsonDocument instances are immutable.");
}
///
/// Tries to get an element of this document.
///
/// The name of the element.
/// The element.
///
/// True if an element with that name was found.
///
public override bool TryGetElement(string name, out BsonElement element)
{
ThrowIfDisposed();
using (var bsonReader = new BsonBinaryReader(new BsonBuffer(CloneSlice(), true), true, _readerSettings))
{
bsonReader.ReadStartDocument();
while (bsonReader.ReadBsonType() != BsonType.EndOfDocument)
{
if (bsonReader.ReadName() == name)
{
var value = DeserializeBsonValue(bsonReader);
element = new BsonElement(name, value);
return true;
}
bsonReader.SkipValue();
}
bsonReader.ReadEndDocument();
element = null;
return false;
}
}
///
/// Tries to get the value of an element of this document.
///
/// The name of the element.
/// The value of the element.
///
/// True if an element with that name was found.
///
public override bool TryGetValue(string name, out BsonValue value)
{
ThrowIfDisposed();
using (var bsonReader = new BsonBinaryReader(new BsonBuffer(CloneSlice(), true), true, _readerSettings))
{
bsonReader.ReadStartDocument();
while (bsonReader.ReadBsonType() != BsonType.EndOfDocument)
{
if (bsonReader.ReadName() == name)
{
value = DeserializeBsonValue(bsonReader);
return true;
}
bsonReader.SkipValue();
}
bsonReader.ReadEndDocument();
value = null;
return false;
}
}
// protected methods
///
/// Releases unmanaged and - optionally - managed resources.
///
/// true to release both managed and unmanaged resources; false to release only unmanaged resources.
protected virtual void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing)
{
if (_slice != null)
{
_slice.Dispose();
_slice = null;
}
if (_disposableItems != null)
{
_disposableItems.ForEach(x => x.Dispose());
_disposableItems = null;
}
}
_disposed = true;
}
}
///
/// Throws if disposed.
///
/// RawBsonDocument
protected void ThrowIfDisposed()
{
if (_disposed)
{
throw new ObjectDisposedException("RawBsonDocument");
}
}
// private methods
private IByteBuffer CloneSlice()
{
return _slice.GetSlice(0, _slice.Length);
}
private RawBsonArray DeserializeRawBsonArray(BsonBinaryReader bsonReader)
{
var slice = bsonReader.ReadRawBsonArray();
var nestedArray = new RawBsonArray(slice);
_disposableItems.Add(nestedArray);
return nestedArray;
}
private RawBsonDocument DeserializeRawBsonDocument(BsonBinaryReader bsonReader)
{
var slice = bsonReader.ReadRawBsonDocument();
var nestedDocument = new RawBsonDocument(slice);
_disposableItems.Add(nestedDocument);
return nestedDocument;
}
private BsonValue DeserializeBsonValue(BsonBinaryReader bsonReader)
{
switch (bsonReader.GetCurrentBsonType())
{
case BsonType.Array: return DeserializeRawBsonArray(bsonReader);
case BsonType.Document: return DeserializeRawBsonDocument(bsonReader);
default: return (BsonValue)BsonValueSerializer.Instance.Deserialize(bsonReader, typeof(BsonValue), null);
}
}
}
}