/* 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.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 array that is represented using only the raw bytes. /// [Serializable] [BsonSerializer(typeof(RawBsonArraySerializer))] public class RawBsonArray : BsonArray, 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 /// RawBsonArray cannot be used with an IByteBuffer that needs disposing. public RawBsonArray(IByteBuffer slice) { if (slice == null) { throw new ArgumentNullException("slice"); } _slice = slice; } // public properties /// /// Gets or sets the total number of elements the internal data structure can hold without resizing. /// public override int Capacity { get { ThrowIfDisposed(); return _slice.Capacity; } set { throw new NotSupportedException("RawBsonArray instances are immutable."); } } /// /// Gets the count of array elements. /// public override int Count { get { ThrowIfDisposed(); using (var bsonReader = new BsonBinaryReader(new BsonBuffer(CloneSlice(), false), true, _readerSettings)) { var count = 0; bsonReader.ReadStartDocument(); while (bsonReader.ReadBsonType() != BsonType.EndOfDocument) { bsonReader.SkipName(); bsonReader.SkipValue(); count++; } bsonReader.ReadEndDocument(); return count; } } } /// /// Gets whether the array is read-only. /// public override bool IsReadOnly { get { return true; } } /// /// Gets the array elements as raw values (see BsonValue.RawValue). /// [Obsolete("Use ToArray to ToList instead.")] public override IEnumerable RawValues { get { ThrowIfDisposed(); using (var bsonReader = new BsonBinaryReader(new BsonBuffer(CloneSlice(), false), 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 array elements. /// public override IEnumerable Values { get { ThrowIfDisposed(); using (var bsonReader = new BsonBinaryReader(new BsonBuffer(CloneSlice(), false), 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 { if (index < 0) { throw new ArgumentOutOfRangeException("index"); } ThrowIfDisposed(); using (var bsonReader = new BsonBinaryReader(new BsonBuffer(CloneSlice(), false), 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"); } } set { throw new NotSupportedException("RawBsonArray instances are immutable."); } } // public methods /// /// Adds an element to the array. /// /// The value to add to the array. /// The array (so method calls can be chained). public override BsonArray Add(BsonValue value) { throw new NotSupportedException("RawBsonArray instances are immutable."); } /// /// Adds multiple elements to the array. /// /// A list of values to add to the array. /// The array (so method calls can be chained). public override BsonArray AddRange(IEnumerable values) { throw new NotSupportedException("RawBsonArray instances are immutable."); } /// /// Adds multiple elements to the array. /// /// A list of values to add to the array. /// The array (so method calls can be chained). public override BsonArray AddRange(IEnumerable values) { throw new NotSupportedException("RawBsonArray instances are immutable."); } /// /// Adds multiple elements to the array. /// /// A list of values to add to the array. /// The array (so method calls can be chained). public override BsonArray AddRange(IEnumerable values) { throw new NotSupportedException("RawBsonArray instances are immutable."); } /// /// Adds multiple elements to the array. /// /// A list of values to add to the array. /// The array (so method calls can be chained). public override BsonArray AddRange(IEnumerable values) { throw new NotSupportedException("RawBsonArray instances are immutable."); } /// /// Adds multiple elements to the array. /// /// A list of values to add to the array. /// The array (so method calls can be chained). public override BsonArray AddRange(IEnumerable values) { throw new NotSupportedException("RawBsonArray instances are immutable."); } /// /// Adds multiple elements to the array. /// /// A list of values to add to the array. /// The array (so method calls can be chained). public override BsonArray AddRange(IEnumerable values) { throw new NotSupportedException("RawBsonArray instances are immutable."); } /// /// Adds multiple elements to the array. /// /// A list of values to add to the array. /// The array (so method calls can be chained). public override BsonArray AddRange(IEnumerable values) { throw new NotSupportedException("RawBsonArray instances are immutable."); } /// /// Adds multiple elements to the array. /// /// A list of values to add to the array. /// The array (so method calls can be chained). public override BsonArray AddRange(IEnumerable values) { throw new NotSupportedException("RawBsonArray instances are immutable."); } /// /// Adds multiple elements to the array. /// /// A list of values to add to the array. /// The array (so method calls can be chained). public override BsonArray AddRange(IEnumerable values) { throw new NotSupportedException("RawBsonArray instances are immutable."); } /// /// Creates a shallow clone of the array (see also DeepClone). /// /// A shallow clone of the array. public override BsonValue Clone() { return new RawBsonArray(CloneSlice()); } /// /// Clears the array. /// public override void Clear() { throw new NotSupportedException("RawBsonArray instances are immutable."); } /// /// Tests whether the array contains a value. /// /// The value to test for. /// True if the array contains the value. public override bool Contains(BsonValue value) { ThrowIfDisposed(); using (var bsonReader = new BsonBinaryReader(new BsonBuffer(CloneSlice(), false), true, _readerSettings)) { bsonReader.ReadStartDocument(); while (bsonReader.ReadBsonType() != BsonType.EndOfDocument) { bsonReader.SkipName(); if (DeserializeBsonValue(bsonReader).Equals(value)) { return true; } } bsonReader.ReadEndDocument(); return false; } } /// /// Copies elements from this array to another array. /// /// The other array. /// The zero based index of the other array at which to start copying. public override void CopyTo(BsonValue[] array, int arrayIndex) { ThrowIfDisposed(); using (var bsonReader = new BsonBinaryReader(new BsonBuffer(CloneSlice(), false), true, _readerSettings)) { bsonReader.ReadStartDocument(); while (bsonReader.ReadBsonType() != BsonType.EndOfDocument) { bsonReader.SkipName(); array[arrayIndex++] = DeserializeBsonValue(bsonReader); } bsonReader.ReadEndDocument(); } } /// /// Copies elements from this array to another array as raw values (see BsonValue.RawValue). /// /// The other array. /// The zero based index of the other array at which to start copying. [Obsolete("Use ToArray or ToList instead.")] public override void CopyTo(object[] array, int arrayIndex) { ThrowIfDisposed(); using (var bsonReader = new BsonBinaryReader(new BsonBuffer(CloneSlice(), false), true, _readerSettings)) { bsonReader.ReadStartDocument(); while (bsonReader.ReadBsonType() != BsonType.EndOfDocument) { bsonReader.SkipName(); array[arrayIndex++] = DeserializeBsonValue(bsonReader).RawValue; } bsonReader.ReadEndDocument(); } } /// /// Creates a deep clone of the array (see also Clone). /// /// A deep clone of the array. public override BsonValue DeepClone() { ThrowIfDisposed(); return new RawBsonArray(CloneSlice()); } /// /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } /// /// Gets an enumerator that can enumerate the elements of the array. /// /// An enumerator. public override IEnumerator GetEnumerator() { ThrowIfDisposed(); using (var bsonReader = new BsonBinaryReader(new BsonBuffer(CloneSlice(), false), true, _readerSettings)) { bsonReader.ReadStartDocument(); while (bsonReader.ReadBsonType() != BsonType.EndOfDocument) { bsonReader.SkipName(); yield return DeserializeBsonValue(bsonReader); } bsonReader.ReadEndDocument(); } } /// /// Gets the index of a value in the array. /// /// The value to search for. /// The zero based index of the value (or -1 if not found). public override int IndexOf(BsonValue value) { return IndexOf(value, 0, int.MaxValue); } /// /// Gets the index of a value in the array. /// /// The value to search for. /// The zero based index at which to start the search. /// The zero based index of the value (or -1 if not found). public override int IndexOf(BsonValue value, int index) { return IndexOf(value, index, int.MaxValue); } /// /// Gets the index of a value in the array. /// /// The value to search for. /// The zero based index at which to start the search. /// The number of elements to search. /// The zero based index of the value (or -1 if not found). public override int IndexOf(BsonValue value, int index, int count) { ThrowIfDisposed(); using (var bsonReader = new BsonBinaryReader(new BsonBuffer(CloneSlice(), false), true, _readerSettings)) { bsonReader.ReadStartDocument(); var i = 0; while (bsonReader.ReadBsonType() != BsonType.EndOfDocument) { bsonReader.SkipName(); if (i >= index) { if (count == 0) { return -1; } if (DeserializeBsonValue(bsonReader).Equals(value)) { return i; } count--; } else { bsonReader.SkipValue(); } i++; } bsonReader.ReadEndDocument(); return -1; } } /// /// Inserts a new value into the array. /// /// The zero based index at which to insert the new value. /// The new value. public override void Insert(int index, BsonValue value) { throw new NotSupportedException("RawBsonArray instances are immutable."); } /// /// Removes the first occurrence of a value from the array. /// /// The value to remove. /// True if the value was removed. public override bool Remove(BsonValue value) { throw new NotSupportedException("RawBsonArray instances are immutable."); } /// /// Removes an element from the array. /// /// The zero based index of the element to remove. public override void RemoveAt(int index) { throw new NotSupportedException("RawBsonArray instances are immutable."); } /// /// Converts the BsonArray to an array of BsonValues. /// /// An array of BsonValues. public override BsonValue[] ToArray() { ThrowIfDisposed(); return Values.ToArray(); } /// /// Converts the BsonArray to a list of BsonValues. /// /// A list of BsonValues. public override List ToList() { ThrowIfDisposed(); return Values.ToList(); } /// /// Returns a string representation of the array. /// /// A string representation of the array. public override string ToString() { ThrowIfDisposed(); var parts = Values.Select(v => v.ToString()).ToArray(); return string.Format("[{0}]", string.Join(", ", parts)); } // 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. /// /// protected void ThrowIfDisposed() { if (_disposed) { throw new ObjectDisposedException(GetType().Name); } } // 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); } } } }