/* 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); } } } }