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