/* Copyright 2010-2016 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.IO;
using System.Linq;
using MongoDB.Bson.Serialization;
using MongoDB.Bson.Serialization.Serializers;
namespace MongoDB.Bson.IO
{
///
/// Represents a BSON reader for some external format (see subclasses).
///
public abstract class BsonReader : IBsonReader
{
// private fields
private bool _disposed = false;
private BsonReaderSettings _settings;
private BsonReaderState _state;
private BsonType _currentBsonType;
private string _currentName;
// constructors
///
/// Initializes a new instance of the BsonReader class.
///
/// The reader settings.
protected BsonReader(BsonReaderSettings settings)
{
if (settings == null)
{
throw new ArgumentNullException("settings");
}
_settings = settings.FrozenCopy();
_state = BsonReaderState.Initial;
}
// public properties
///
/// Gets the current BsonType.
///
public BsonType CurrentBsonType
{
get { return _currentBsonType; }
protected set { _currentBsonType = value; }
}
///
/// Gets the settings of the reader.
///
public BsonReaderSettings Settings
{
get { return _settings; }
}
///
/// Gets the current state of the reader.
///
public BsonReaderState State
{
get { return _state; }
protected set { _state = value; }
}
// protected properties
///
/// Gets the current name.
///
protected string CurrentName
{
get { return _currentName; }
set { _currentName = value; }
}
///
/// Gets whether the BsonReader has been disposed.
///
protected bool Disposed
{
get { return _disposed; }
}
// public methods
///
/// Closes the reader.
///
public abstract void Close();
///
/// Disposes of any resources used by the reader.
///
public void Dispose()
{
if (!_disposed)
{
Dispose(true);
_disposed = true;
}
}
///
/// Gets a bookmark to the reader's current position and state.
///
/// A bookmark.
public abstract BsonReaderBookmark GetBookmark();
///
/// Gets the current BsonType (calls ReadBsonType if necessary).
///
/// The current BsonType.
public BsonType GetCurrentBsonType()
{
if (_state == BsonReaderState.Initial || _state == BsonReaderState.ScopeDocument || _state == BsonReaderState.Type)
{
ReadBsonType();
}
if (_state != BsonReaderState.Value)
{
ThrowInvalidState("GetCurrentBsonType", BsonReaderState.Value);
}
return _currentBsonType;
}
///
/// Determines whether this reader is at end of file.
///
///
/// Whether this reader is at end of file.
///
public abstract bool IsAtEndOfFile();
///
/// Reads BSON binary data from the reader.
///
/// A BsonBinaryData.
public abstract BsonBinaryData ReadBinaryData();
///
/// Reads a BSON boolean from the reader.
///
/// A Boolean.
public abstract bool ReadBoolean();
///
/// Reads a BsonType from the reader.
///
/// A BsonType.
public abstract BsonType ReadBsonType();
///
/// Reads BSON binary data from the reader.
///
/// A byte array.
public abstract byte[] ReadBytes();
///
/// Reads a BSON DateTime from the reader.
///
/// The number of milliseconds since the Unix epoch.
public abstract long ReadDateTime();
///
public abstract Decimal128 ReadDecimal128();
///
/// Reads a BSON Double from the reader.
///
/// A Double.
public abstract double ReadDouble();
///
/// Reads the end of a BSON array from the reader.
///
public abstract void ReadEndArray();
///
/// Reads the end of a BSON document from the reader.
///
public abstract void ReadEndDocument();
///
/// Reads a BSON Int32 from the reader.
///
/// An Int32.
public abstract int ReadInt32();
///
/// Reads a BSON Int64 from the reader.
///
/// An Int64.
public abstract long ReadInt64();
///
/// Reads a BSON JavaScript from the reader.
///
/// A string.
public abstract string ReadJavaScript();
///
/// Reads a BSON JavaScript with scope from the reader (call ReadStartDocument next to read the scope).
///
/// A string.
public abstract string ReadJavaScriptWithScope();
///
/// Reads a BSON MaxKey from the reader.
///
public abstract void ReadMaxKey();
///
/// Reads a BSON MinKey from the reader.
///
public abstract void ReadMinKey();
///
/// Reads the name of an element from the reader.
///
/// The name of the element.
public virtual string ReadName()
{
return ReadName(Utf8NameDecoder.Instance);
}
///
/// Reads the name of an element from the reader (using the provided name decoder).
///
/// The name decoder.
///
/// The name of the element.
///
public abstract string ReadName(INameDecoder nameDecoder);
///
/// Reads a BSON null from the reader.
///
public abstract void ReadNull();
///
/// Reads a BSON ObjectId from the reader.
///
/// An ObjectId.
public abstract ObjectId ReadObjectId();
///
/// Reads a raw BSON array.
///
/// The raw BSON array.
public virtual IByteBuffer ReadRawBsonArray()
{
// overridden in BsonBinaryReader to read the raw bytes from the stream
// for all other streams, deserialize the array and reserialize it using a BsonBinaryWriter to get the raw bytes
var deserializationContext = BsonDeserializationContext.CreateRoot(this);
var array = BsonArraySerializer.Instance.Deserialize(deserializationContext);
using (var memoryStream = new MemoryStream())
using (var bsonWriter = new BsonBinaryWriter(memoryStream, BsonBinaryWriterSettings.Defaults))
{
var serializationContext = BsonSerializationContext.CreateRoot(bsonWriter);
bsonWriter.WriteStartDocument();
var startPosition = memoryStream.Position + 3; // just past BsonType, "x" and null byte
bsonWriter.WriteName("x");
BsonArraySerializer.Instance.Serialize(serializationContext, array);
var endPosition = memoryStream.Position;
bsonWriter.WriteEndDocument();
byte[] memoryStreamBuffer;
#if NETSTANDARD1_5 || NETSTANDARD1_6
memoryStreamBuffer = memoryStream.ToArray();
#else
memoryStreamBuffer = memoryStream.GetBuffer();
#endif
var buffer = new ByteArrayBuffer(memoryStreamBuffer, (int)memoryStream.Length, isReadOnly: true);
return new ByteBufferSlice(buffer, (int)startPosition, (int)(endPosition - startPosition));
}
}
///
/// Reads a raw BSON document.
///
/// The raw BSON document.
public virtual IByteBuffer ReadRawBsonDocument()
{
// overridden in BsonBinaryReader to read the raw bytes from the stream
// for all other streams, deserialize the document and use ToBson to get the raw bytes
var deserializationContext = BsonDeserializationContext.CreateRoot(this);
var document = BsonDocumentSerializer.Instance.Deserialize(deserializationContext);
var bytes = document.ToBson();
return new ByteArrayBuffer(bytes, isReadOnly: true);
}
///
/// Reads a BSON regular expression from the reader.
///
/// A BsonRegularExpression.
public abstract BsonRegularExpression ReadRegularExpression();
///
/// Reads the start of a BSON array.
///
public abstract void ReadStartArray();
///
/// Reads the start of a BSON document.
///
public abstract void ReadStartDocument();
///
/// Reads a BSON string from the reader.
///
/// A String.
public abstract string ReadString();
///
/// Reads a BSON symbol from the reader.
///
/// A string.
public abstract string ReadSymbol();
///
/// Reads a BSON timestamp from the reader.
///
/// The combined timestamp/increment.
public abstract long ReadTimestamp();
///
/// Reads a BSON undefined from the reader.
///
public abstract void ReadUndefined();
///
/// Returns the reader to previously bookmarked position and state.
///
/// The bookmark.
public abstract void ReturnToBookmark(BsonReaderBookmark bookmark);
///
/// Skips the name (reader must be positioned on a name).
///
public abstract void SkipName();
///
/// Skips the value (reader must be positioned on a value).
///
public abstract void SkipValue();
// protected methods
///
/// Disposes of any resources used by the reader.
///
/// True if called from Dispose.
protected virtual void Dispose(bool disposing)
{
}
///
/// Throws an InvalidOperationException when the method called is not valid for the current ContextType.
///
/// The name of the method.
/// The actual ContextType.
/// The valid ContextTypes.
protected void ThrowInvalidContextType(
string methodName,
ContextType actualContextType,
params ContextType[] validContextTypes)
{
var validContextTypesString = string.Join(" or ", validContextTypes.Select(c => c.ToString()).ToArray());
var message = string.Format(
"{0} can only be called when ContextType is {1}, not when ContextType is {2}.",
methodName, validContextTypesString, actualContextType);
throw new InvalidOperationException(message);
}
///
/// Throws an InvalidOperationException when the method called is not valid for the current state.
///
/// The name of the method.
/// The valid states.
protected void ThrowInvalidState(string methodName, params BsonReaderState[] validStates)
{
var validStatesString = string.Join(" or ", validStates.Select(s => s.ToString()).ToArray());
var message = string.Format(
"{0} can only be called when State is {1}, not when State is {2}.",
methodName, validStatesString, _state);
throw new InvalidOperationException(message);
}
///
/// Throws an ObjectDisposedException.
///
protected void ThrowObjectDisposedException()
{
throw new ObjectDisposedException(this.GetType().Name);
}
///
/// Verifies the current state and BsonType of the reader.
///
/// The name of the method calling this one.
/// The required BSON type.
protected void VerifyBsonType(string methodName, BsonType requiredBsonType)
{
if (_state == BsonReaderState.Initial || _state == BsonReaderState.ScopeDocument || _state == BsonReaderState.Type)
{
ReadBsonType();
}
if (_state == BsonReaderState.Name)
{
// ignore name
SkipName();
}
if (_state != BsonReaderState.Value)
{
ThrowInvalidState(methodName, BsonReaderState.Value);
}
if (_currentBsonType != requiredBsonType)
{
var message = string.Format(
"{0} can only be called when CurrentBsonType is {1}, not when CurrentBsonType is {2}.",
methodName, requiredBsonType, _currentBsonType);
throw new InvalidOperationException(message);
}
}
}
}