/* 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 a BSON document that is not materialized until you start using it. /// [Serializable] public abstract class MaterializedOnDemandBsonDocument : BsonDocument, IDisposable { // private fields private bool _disposed; private bool _isMaterialized; // constructors /// /// Initializes a new instance of the class. /// protected MaterializedOnDemandBsonDocument() { } // public properties /// /// Gets the number of elements. /// public override int ElementCount { get { EnsureIsMaterialized(); return base.ElementCount; } } /// /// Gets the elements. /// public override IEnumerable Elements { get { EnsureIsMaterialized(); return base.Elements; } } /// /// Gets a value indicating whether this instance is disposed. /// /// /// true if this instance is disposed; otherwise, false. /// public bool IsDisposed { get { return _disposed; } } /// /// Gets a value indicating whether this instance is materialized. /// /// /// true if this instance is materialized; otherwise, false. /// public bool IsMaterialized { get { return _isMaterialized; } } /// /// Gets the element names. /// public override IEnumerable Names { get { EnsureIsMaterialized(); return base.Names; } } /// /// Gets the raw values (see BsonValue.RawValue). /// [Obsolete("Use Values instead.")] public override IEnumerable RawValues { get { EnsureIsMaterialized(); return base.RawValues; } } /// /// Gets the values. /// public override IEnumerable Values { get { EnsureIsMaterialized(); return base.Values; } } // public indexers /// /// Gets or sets a value by position. /// /// The position. /// The value. public override BsonValue this[int index] { get { EnsureIsMaterialized(); return base[index]; } set { EnsureIsMaterialized(); base[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 { EnsureIsMaterialized(); return base[name, defaultValue]; } } /// /// Gets or sets a value by name. /// /// The name. /// The value. public override BsonValue this[string name] { get { EnsureIsMaterialized(); return base[name]; } set { EnsureIsMaterialized(); base[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) { EnsureIsMaterialized(); return base.Add(element); } /// /// 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) { EnsureIsMaterialized(); return base.Add(dictionary); } /// /// 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) { EnsureIsMaterialized(); return base.Add(dictionary, keys); } /// /// 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) { EnsureIsMaterialized(); return base.Add(dictionary); } /// /// 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) { EnsureIsMaterialized(); return base.Add(dictionary, keys); } /// /// 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) { EnsureIsMaterialized(); return base.Add(dictionary); } /// /// 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) { EnsureIsMaterialized(); return base.Add(dictionary, keys); } /// /// 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) { EnsureIsMaterialized(); return base.Add(elements); } /// /// 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) { EnsureIsMaterialized(); return base.Add(elements); } /// /// 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) { EnsureIsMaterialized(); return base.Add(name, value); } /// /// 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) { EnsureIsMaterialized(); return base.Add(name, value, condition); } /// /// Creates and adds an element to the document, but only if the condition is true. /// If the condition is false the value factory is not called at all. /// /// The name of the element. /// A delegate called to compute the value of the element if condition is true. /// Whether to add the element to the document. /// The document (so method calls can be chained). public override BsonDocument Add(string name, Func valueFactory, bool condition) { EnsureIsMaterialized(); return base.Add(name, valueFactory, condition); } /// /// 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) { EnsureIsMaterialized(); return base.AddRange(dictionary); } /// /// 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) { EnsureIsMaterialized(); return base.AddRange(dictionary); } /// /// 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) { EnsureIsMaterialized(); return base.AddRange(elements); } /// /// 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) { EnsureIsMaterialized(); return base.AddRange(dictionary); } /// /// Clears the document (removes all elements). /// public override void Clear() { EnsureIsMaterialized(); base.Clear(); } /// /// Creates a shallow clone of the document (see also DeepClone). /// /// /// A shallow clone of the document. /// public override BsonValue Clone() { EnsureIsMaterialized(); return base.Clone(); } /// /// Compares this document to another document. /// /// The other document. /// /// A 32-bit signed integer that indicates whether this document is less than, equal to, or greather than the other. /// public override int CompareTo(BsonDocument other) { EnsureIsMaterialized(); return base.CompareTo(other); } /// /// Compares the BsonDocument to another BsonValue. /// /// The other BsonValue. /// A 32-bit signed integer that indicates whether this BsonDocument is less than, equal to, or greather than the other BsonValue. public override int CompareTo(BsonValue other) { EnsureIsMaterialized(); return base.CompareTo(other); } /// /// 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) { EnsureIsMaterialized(); return base.Contains(name); } /// /// 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) { EnsureIsMaterialized(); return base.ContainsValue(value); } /// /// Creates a deep clone of the document (see also Clone). /// /// /// A deep clone of the document. /// public override BsonValue DeepClone() { EnsureIsMaterialized(); return base.DeepClone(); } /// /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } /// /// Determines whether the specified , is equal to this instance. /// /// The to compare with this instance. /// /// true if the specified is equal to this instance; otherwise, false. /// public override bool Equals(object obj) { EnsureIsMaterialized(); return base.Equals(obj); } /// /// Gets the Id of the document. /// /// The Id of the document (the RawValue if it has one, otherwise the element Value). /// The nominal type of the Id. /// The IdGenerator for the Id (or null). /// True (a BsonDocument either has an Id member or one can be added). [Obsolete("GetDocumentId was intended to be private and will become private in a future release. Use document[\"_id\"] or document.GetValue(\"_id\") instead.")] public override bool GetDocumentId(out object id, out Type idNominalType, out IIdGenerator idGenerator) { EnsureIsMaterialized(); return base.GetDocumentId(out id, out idNominalType, out idGenerator); } /// /// Gets an element of this document. /// /// The zero based index of the element. /// /// The element. /// public override BsonElement GetElement(int index) { EnsureIsMaterialized(); return base.GetElement(index); } /// /// Gets an element of this document. /// /// The name of the element. /// /// A BsonElement. /// public override BsonElement GetElement(string name) { EnsureIsMaterialized(); return base.GetElement(name); } /// /// Gets an enumerator that can be used to enumerate the elements of this document. /// /// /// An enumerator. /// public override IEnumerator GetEnumerator() { EnsureIsMaterialized(); return base.GetEnumerator(); } /// /// Returns a hash code for this instance. /// /// /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. /// public override int GetHashCode() { EnsureIsMaterialized(); return base.GetHashCode(); } /// /// Gets the value of an element. /// /// The zero based index of the element. /// /// The value of the element. /// public override BsonValue GetValue(int index) { EnsureIsMaterialized(); return base.GetValue(index); } /// /// Gets the value of an element. /// /// The name of the element. /// /// The value of the element. /// public override BsonValue GetValue(string name) { EnsureIsMaterialized(); return base.GetValue(name); } /// /// 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) { EnsureIsMaterialized(); return base.GetValue(name, 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) { EnsureIsMaterialized(); base.InsertAt(index, element); } /// /// 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) { EnsureIsMaterialized(); return base.Merge(document); } /// /// 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) { EnsureIsMaterialized(); return base.Merge(document, overwriteExistingElements); } /// /// 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) { EnsureIsMaterialized(); base.Remove(name); } /// /// Removes an element from this document. /// /// The zero based index of the element to remove. public override void RemoveAt(int index) { EnsureIsMaterialized(); base.RemoveAt(index); } /// /// Removes an element from this document. /// /// The element to remove. public override void RemoveElement(BsonElement element) { EnsureIsMaterialized(); base.RemoveElement(element); } /// /// 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) { EnsureIsMaterialized(); return base.Set(index, value); } /// /// 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) { EnsureIsMaterialized(); return base.Set(name, value); } /// /// Sets the document Id. /// /// The value of the Id. [Obsolete("SetDocumentId was intended to be private and will become private in a future release. Use document[\"_id\"] = value or document.Set(\"_id\", value) instead.")] public override void SetDocumentId(object id) { EnsureIsMaterialized(); base.SetDocumentId(id); } /// /// 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) { EnsureIsMaterialized(); return base.SetElement(element); } /// /// 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) { EnsureIsMaterialized(); return base.SetElement(index, element); } /// /// 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 value) { EnsureIsMaterialized(); return base.TryGetElement(name, out value); } /// /// 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) { EnsureIsMaterialized(); return base.TryGetValue(name, out value); } // 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) { _disposed = true; } /// /// Materializes the BsonDocument. /// /// The materialized elements. protected abstract IEnumerable Materialize(); /// /// Informs subclasses that the Materialize process completed so they can free any resources related to the unmaterialized state. /// protected abstract void MaterializeCompleted(); /// /// Throws if disposed. /// /// protected void ThrowIfDisposed() { if (_disposed) { throw new ObjectDisposedException(GetType().Name); } } // private methods private void EnsureIsMaterialized() { ThrowIfDisposed(); if (!_isMaterialized) { var elements = Materialize(); try { _isMaterialized = true; base.AddRange(elements); MaterializeCompleted(); } catch { base.Clear(); _isMaterialized = false; throw; } } } } }