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