/* 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.IO;
using System.Linq;
using MongoDB.Bson.Serialization.Serializers;
namespace MongoDB.Bson.IO
{
///
/// Represents a BSON reader for some external format (see subclasses).
///
public abstract class BsonReader : IDisposable
{
// 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 static methods
///
/// Creates a BsonReader for a BsonBuffer.
///
/// The BsonBuffer.
/// A BsonReader.
public static BsonReader Create(BsonBuffer buffer)
{
return Create(buffer, BsonBinaryReaderSettings.Defaults);
}
///
/// Creates a BsonReader for a BsonBuffer.
///
/// The BsonBuffer.
/// Optional reader settings.
/// A BsonReader.
public static BsonReader Create(BsonBuffer buffer, BsonBinaryReaderSettings settings)
{
return new BsonBinaryReader(buffer, false, settings);
}
///
/// Creates a BsonReader for a BsonDocument.
///
/// The BsonDocument.
/// A BsonReader.
public static BsonReader Create(BsonDocument document)
{
return Create(document, BsonDocumentReaderSettings.Defaults);
}
///
/// Creates a BsonReader for a BsonDocument.
///
/// The BsonDocument.
/// The settings.
/// A BsonReader.
public static BsonReader Create(BsonDocument document, BsonDocumentReaderSettings settings)
{
return new BsonDocumentReader(document, settings);
}
///
/// Creates a BsonReader for a JsonBuffer.
///
/// The buffer.
/// A BsonReader.
public static BsonReader Create(JsonBuffer buffer)
{
return Create(buffer, JsonReaderSettings.Defaults);
}
///
/// Creates a BsonReader for a JsonBuffer.
///
/// The buffer.
/// The settings.
/// A BsonReader.
public static BsonReader Create(JsonBuffer buffer, JsonReaderSettings settings)
{
return new JsonReader(buffer, settings);
}
///
/// Creates a BsonReader for a BSON Stream.
///
/// The BSON Stream.
/// A BsonReader.
public static BsonReader Create(Stream stream)
{
return Create(stream, BsonBinaryReaderSettings.Defaults);
}
///
/// Creates a BsonReader for a BSON Stream.
///
/// The BSON Stream.
/// Optional reader settings.
/// A BsonReader.
public static BsonReader Create(Stream stream, BsonBinaryReaderSettings settings)
{
var byteBuffer = ByteBufferFactory.LoadFrom(stream);
byteBuffer.MakeReadOnly();
return new BsonBinaryReader(new BsonBuffer(byteBuffer, true), true, settings);
}
///
/// Creates a BsonReader for a JSON string.
///
/// The JSON string.
/// A BsonReader.
public static BsonReader Create(string json)
{
var buffer = new JsonBuffer(json);
return Create(buffer);
}
///
/// Creates a BsonReader for a JSON TextReader.
///
/// The JSON TextReader.
/// A BsonReader.
public static BsonReader Create(TextReader textReader)
{
var json = textReader.ReadToEnd();
return Create(json);
}
// 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;
}
}
///
/// Positions the reader to an element by name.
///
/// The name of the element.
/// True if the element was found.
public bool FindElement(string name)
{
if (_disposed) { ThrowObjectDisposedException(); }
if (_state != BsonReaderState.Type)
{
ThrowInvalidState("FindElement", BsonReaderState.Type);
}
while ((ReadBsonType()) != BsonType.EndOfDocument)
{
var elementName = ReadName();
if (elementName == name)
{
return true;
}
SkipValue();
}
return false;
}
///
/// Positions the reader to a string element by name.
///
/// The name of the element.
/// True if the element was found.
public string FindStringElement(string name)
{
if (_disposed) { ThrowObjectDisposedException(); }
if (_state != BsonReaderState.Type)
{
ThrowInvalidState("FindStringElement", BsonReaderState.Type);
}
BsonType bsonType;
while ((bsonType = ReadBsonType()) != BsonType.EndOfDocument)
{
var elementName = ReadName();
if (bsonType == BsonType.String && elementName == name)
{
return ReadString();
}
else
{
SkipValue();
}
}
return null;
}
///
/// 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.Done || _state == BsonReaderState.ScopeDocument || _state == BsonReaderState.Type)
{
ReadBsonType();
}
if (_state != BsonReaderState.Value)
{
ThrowInvalidState("GetCurrentBsonType", BsonReaderState.Value);
}
return _currentBsonType;
}
///
/// Reads BSON binary data from the reader.
///
/// A BsonBinaryData.
public abstract BsonBinaryData ReadBinaryData();
///
/// Reads BSON binary data from the reader.
///
/// The binary data.
/// The binary data subtype.
[Obsolete("Use ReadBinaryData() instead.")]
public void ReadBinaryData(out byte[] bytes, out BsonBinarySubType subType)
{
GuidRepresentation guidRepresentation;
ReadBinaryData(out bytes, out subType, out guidRepresentation);
}
///
/// Reads BSON binary data from the reader.
///
/// The binary data.
/// The binary data subtype.
/// The representation for Guids.
[Obsolete("Use ReadBinaryData() instead.")]
public void ReadBinaryData(
out byte[] bytes,
out BsonBinarySubType subType,
out GuidRepresentation guidRepresentation)
{
var binaryData = ReadBinaryData();
bytes = binaryData.Bytes;
subType = binaryData.SubType;
guidRepresentation = binaryData.GuidRepresentation;
}
///
/// Reads a BSON binary data element from the reader.
///
/// The name of the element.
/// A BsonBinaryData.
public BsonBinaryData ReadBinaryData(string name)
{
VerifyName(name);
return ReadBinaryData();
}
///
/// Reads a BSON binary data element from the reader.
///
/// The name of the element.
/// The binary data.
/// The binary data subtype.
[Obsolete("Use ReadBinaryData(string name) instead.")]
public void ReadBinaryData(string name, out byte[] bytes, out BsonBinarySubType subType)
{
GuidRepresentation guidRepresentation;
ReadBinaryData(name, out bytes, out subType, out guidRepresentation);
}
///
/// Reads a BSON binary data element from the reader.
///
/// The name of the element.
/// The binary data.
/// The binary data subtype.
/// The representation for Guids.
[Obsolete("Use ReadBinaryData(string name) instead.")]
public void ReadBinaryData(
string name,
out byte[] bytes,
out BsonBinarySubType subType,
out GuidRepresentation guidRepresentation)
{
VerifyName(name);
ReadBinaryData(out bytes, out subType, out guidRepresentation);
}
///
/// Reads a BSON boolean from the reader.
///
/// A Boolean.
public abstract bool ReadBoolean();
///
/// Reads a BSON boolean element from the reader.
///
/// The name of the element.
/// A Boolean.
public bool ReadBoolean(string name)
{
VerifyName(name);
return ReadBoolean();
}
///
/// Reads a BsonType from the reader.
///
/// A BsonType.
public BsonType ReadBsonType()
{
bool found;
object value;
return ReadBsonType(null, out found, out value);
}
///
/// Reads a BsonType from the reader.
///
/// The type of the BsonTrie values.
/// An optional trie to search for a value that matches the next element name.
/// Set to true if a matching value was found in the trie.
/// Set to the matching value found in the trie or null if no matching value was found.
/// A BsonType.
public abstract BsonType ReadBsonType(BsonTrie bsonTrie, out bool found, out TValue value);
///
/// Reads BSON binary data from the reader.
///
/// A byte array.
public abstract byte[] ReadBytes();
///
/// Reads a BSON binary data element from the reader.
///
/// The name of the element.
/// A byte array.
public byte[] ReadBytes(string name)
{
VerifyName(name);
return ReadBytes();
}
///
/// Reads a BSON DateTime from the reader.
///
/// The number of milliseconds since the Unix epoch.
public abstract long ReadDateTime();
///
/// Reads a BSON DateTime element from the reader.
///
/// The name of the element.
/// The number of milliseconds since the Unix epoch.
public long ReadDateTime(string name)
{
VerifyName(name);
return ReadDateTime();
}
///
/// Reads a BSON Double from the reader.
///
/// A Double.
public abstract double ReadDouble();
///
/// Reads a BSON Double element from the reader.
///
/// The name of the element.
/// A Double.
public double ReadDouble(string name)
{
VerifyName(name);
return 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 Int32 element from the reader.
///
/// The name of the element.
/// An Int32.
public int ReadInt32(string name)
{
VerifyName(name);
return ReadInt32();
}
///
/// Reads a BSON Int64 from the reader.
///
/// An Int64.
public abstract long ReadInt64();
///
/// Reads a BSON Int64 element from the reader.
///
/// The name of the element.
/// An Int64.
public long ReadInt64(string name)
{
VerifyName(name);
return ReadInt64();
}
///
/// Reads a BSON JavaScript from the reader.
///
/// A string.
public abstract string ReadJavaScript();
///
/// Reads a BSON JavaScript element from the reader.
///
/// The name of the element.
/// A string.
public string ReadJavaScript(string name)
{
VerifyName(name);
return 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 JavaScript with scope element from the reader (call ReadStartDocument next to read the scope).
///
/// The name of the element.
/// A string.
public string ReadJavaScriptWithScope(string name)
{
VerifyName(name);
return ReadJavaScriptWithScope();
}
///
/// Reads a BSON MaxKey from the reader.
///
public abstract void ReadMaxKey();
///
/// Reads a BSON MaxKey element from the reader.
///
/// The name of the element.
public void ReadMaxKey(string name)
{
VerifyName(name);
ReadMaxKey();
}
///
/// Reads a BSON MinKey from the reader.
///
public abstract void ReadMinKey();
///
/// Reads a BSON MinKey element from the reader.
///
/// The name of the element.
public void ReadMinKey(string name)
{
VerifyName(name);
ReadMinKey();
}
///
/// Reads the name of an element from the reader.
///
/// The name of the element.
public string ReadName()
{
if (_disposed) { ThrowObjectDisposedException(); }
if (_state == BsonReaderState.Type)
{
ReadBsonType();
}
if (_state != BsonReaderState.Name)
{
ThrowInvalidState("ReadName", BsonReaderState.Name);
}
_state = BsonReaderState.Value;
return _currentName;
}
///
/// Reads the name of an element from the reader.
///
/// The name of the element.
public void ReadName(string name)
{
VerifyName(name);
}
///
/// Reads a BSON null from the reader.
///
public abstract void ReadNull();
///
/// Reads a BSON null element from the reader.
///
/// The name of the element.
public void ReadNull(string name)
{
VerifyName(name);
ReadNull();
}
///
/// Reads a BSON ObjectId from the reader.
///
/// An ObjectId.
public abstract ObjectId ReadObjectId();
///
/// Reads a BSON ObjectId from the reader.
///
/// The timestamp.
/// The machine hash.
/// The PID.
/// The increment.
[Obsolete("Use ReadObjectId() instead.")]
public void ReadObjectId(out int timestamp, out int machine, out short pid, out int increment)
{
var objectId = ReadObjectId();
timestamp = objectId.Timestamp;
machine = objectId.Machine;
pid = objectId.Pid;
increment = objectId.Increment;
}
///
/// Reads a BSON ObjectId element from the reader.
///
/// The name of the element.
/// An ObjectId.
public ObjectId ReadObjectId(string name)
{
VerifyName(name);
return ReadObjectId();
}
///
/// Reads a BSON ObjectId element from the reader.
///
/// The name of the element.
/// The timestamp.
/// The machine hash.
/// The PID.
/// The increment.
[Obsolete("Use ReadObjectId(string name) instead.")]
public void ReadObjectId(string name, out int timestamp, out int machine, out short pid, out int increment)
{
VerifyName(name);
ReadObjectId(out timestamp, out machine, out pid, out increment);
}
///
/// Reads a raw BSON array.
///
/// The raw BSON array.
public virtual IByteBuffer ReadRawBsonArray()
{
// overridden in BsonBinaryReader
var array = BsonArraySerializer.Instance.Deserialize(this, typeof(BsonArray), null);
using (var bsonWriter = new BsonBinaryWriter(new BsonBuffer(), true, BsonBinaryWriterSettings.Defaults))
{
bsonWriter.WriteStartDocument();
var startPosition = bsonWriter.Buffer.Position + 3; // just past BsonType, "x" and null byte
bsonWriter.WriteName("x");
BsonArraySerializer.Instance.Serialize(bsonWriter, typeof(BsonArray), array, null);
var endPosition = bsonWriter.Buffer.Position;
bsonWriter.WriteEndDocument();
var length = (int)(endPosition - startPosition);
bsonWriter.Buffer.Position = startPosition;
var bytes = bsonWriter.Buffer.ReadBytes(length);
return new ByteArrayBuffer(bytes, 0, length, true);
}
}
///
/// Reads a raw BSON array.
///
/// The name.
///
/// The raw BSON array.
///
public IByteBuffer ReadRawBsonArray(string name)
{
VerifyName(name);
return ReadRawBsonArray();
}
///
/// Reads a raw BSON document.
///
/// The raw BSON document.
public virtual IByteBuffer ReadRawBsonDocument()
{
// overridden in BsonBinaryReader
var document = BsonDocumentSerializer.Instance.Deserialize(this, typeof(BsonDocument), null);
var bytes = document.ToBson();
return new ByteArrayBuffer(bytes, 0, bytes.Length, true);
}
///
/// Reads a raw BSON document.
///
/// The name.
/// The raw BSON document.
public IByteBuffer ReadRawBsonDocument(string name)
{
VerifyName(name);
return ReadRawBsonDocument();
}
///
/// Reads a BSON regular expression from the reader.
///
/// A BsonRegularExpression.
public abstract BsonRegularExpression ReadRegularExpression();
///
/// Reads a BSON regular expression from the reader.
///
/// A regular expression pattern.
/// A regular expression options.
[Obsolete("Use ReadRegularExpression() instead.")]
public void ReadRegularExpression(out string pattern, out string options)
{
var regex = ReadRegularExpression();
pattern = regex.Pattern;
options = regex.Options;
}
///
/// Reads a BSON regular expression element from the reader.
///
/// The name of the element.
/// A BsonRegularExpression.
public BsonRegularExpression ReadRegularExpression(string name)
{
VerifyName(name);
return ReadRegularExpression();
}
///
/// Reads a BSON regular expression element from the reader.
///
/// The name of the element.
/// A regular expression pattern.
/// A regular expression options.
[Obsolete("Use ReadRegularExpression(string name) instead.")]
public void ReadRegularExpression(string name, out string pattern, out string options)
{
VerifyName(name);
ReadRegularExpression(out pattern, out options);
}
///
/// 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 string element from the reader.
///
/// A String.
/// The name of the element.
public string ReadString(string name)
{
VerifyName(name);
return ReadString();
}
///
/// Reads a BSON symbol from the reader.
///
/// A string.
public abstract string ReadSymbol();
///
/// Reads a BSON symbol element from the reader.
///
/// The name of the element.
/// A string.
public string ReadSymbol(string name)
{
VerifyName(name);
return ReadSymbol();
}
///
/// Reads a BSON timestamp from the reader.
///
/// The combined timestamp/increment.
public abstract long ReadTimestamp();
///
/// Reads a BSON timestamp element from the reader.
///
/// The combined timestamp/increment.
/// The name of the element.
public long ReadTimestamp(string name)
{
VerifyName(name);
return ReadTimestamp();
}
///
/// Reads a BSON undefined from the reader.
///
public abstract void ReadUndefined();
///
/// Reads a BSON undefined element from the reader.
///
/// The name of the element.
public void ReadUndefined(string name)
{
VerifyName(name);
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);
}
}
///
/// Verifies the name of the current element.
///
/// The expected name.
protected void VerifyName(string expectedName)
{
var actualName = ReadName();
if (actualName != expectedName)
{
var message = string.Format(
"Expected element name to be '{0}', not '{1}'.",
expectedName, actualName);
throw new Exception(message);
}
}
}
}