/* 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; namespace MongoDB.Bson.IO { /// /// Represents a BSON reader for a BsonDocument. /// public class BsonDocumentReader : BsonReader { // private fields private BsonDocumentReaderContext _context; private BsonValue _currentValue; // constructors /// /// Initializes a new instance of the BsonDocumentReader class. /// /// A BsonDocument. public BsonDocumentReader(BsonDocument document) : this(document, BsonDocumentReaderSettings.Defaults) { } /// /// Initializes a new instance of the BsonDocumentReader class. /// /// A BsonDocument. /// The reader settings. public BsonDocumentReader(BsonDocument document, BsonDocumentReaderSettings settings) : base(settings) { if (document == null) { throw new ArgumentNullException("document"); } _context = new BsonDocumentReaderContext(null, ContextType.TopLevel, document); _currentValue = document; } // public methods /// /// Closes the reader. /// public override void Close() { // Close can be called on Disposed objects State = BsonReaderState.Closed; } /// /// Gets a bookmark to the reader's current position and state. /// /// A bookmark. public override BsonReaderBookmark GetBookmark() { return new BsonDocumentReaderBookmark(State, CurrentBsonType, CurrentName, _context, _currentValue); } /// /// Determines whether this reader is at end of file. /// /// /// Whether this reader is at end of file. /// public override bool IsAtEndOfFile() { return State == BsonReaderState.Done; } /// /// Reads BSON binary data from the reader. /// /// A BsonBinaryData. public override BsonBinaryData ReadBinaryData() { if (Disposed) { ThrowObjectDisposedException(); } VerifyBsonType("ReadBinaryData", BsonType.Binary); State = GetNextState(); return _currentValue.AsBsonBinaryData; } /// /// Reads a BSON boolean from the reader. /// /// A Boolean. public override bool ReadBoolean() { if (Disposed) { ThrowObjectDisposedException(); } VerifyBsonType("ReadBoolean", BsonType.Boolean); State = GetNextState(); return _currentValue.AsBoolean; } /// /// Reads a BsonType from the reader. /// /// A BsonType. public override BsonType ReadBsonType() { if (Disposed) { ThrowObjectDisposedException(); } if (State == BsonReaderState.Initial || State == BsonReaderState.ScopeDocument) { // there is an implied type of Document for the top level and for scope documents CurrentBsonType = BsonType.Document; State = BsonReaderState.Value; return CurrentBsonType; } if (State != BsonReaderState.Type) { ThrowInvalidState("ReadBsonType", BsonReaderState.Type); } switch (_context.ContextType) { case ContextType.Array: if (!_context.TryGetNextValue(out _currentValue)) { State = BsonReaderState.EndOfArray; return BsonType.EndOfDocument; } State = BsonReaderState.Value; break; case ContextType.Document: BsonElement currentElement; if (!_context.TryGetNextElement(out currentElement)) { State = BsonReaderState.EndOfDocument; return BsonType.EndOfDocument; } CurrentName = currentElement.Name; _currentValue = currentElement.Value; State = BsonReaderState.Name; break; default: throw new BsonInternalException("Invalid ContextType."); } CurrentBsonType = _currentValue.BsonType; return CurrentBsonType; } /// /// Reads BSON binary data from the reader. /// /// A byte array. #pragma warning disable 618 // about obsolete BsonBinarySubType.OldBinary public override byte[] ReadBytes() { if (Disposed) { ThrowObjectDisposedException(); } VerifyBsonType("ReadBytes", BsonType.Binary); State = GetNextState(); var binaryData = _currentValue.AsBsonBinaryData; var subType = binaryData.SubType; if (subType != BsonBinarySubType.Binary && subType != BsonBinarySubType.OldBinary) { var message = string.Format("ReadBytes requires the binary sub type to be Binary, not {0}.", subType); throw new FormatException(message); } return binaryData.Bytes; } #pragma warning restore 618 /// /// Reads a BSON DateTime from the reader. /// /// The number of milliseconds since the Unix epoch. public override long ReadDateTime() { if (Disposed) { ThrowObjectDisposedException(); } VerifyBsonType("ReadDateTime", BsonType.DateTime); State = GetNextState(); return _currentValue.AsBsonDateTime.MillisecondsSinceEpoch; } /// public override Decimal128 ReadDecimal128() { if (Disposed) { ThrowObjectDisposedException(); } VerifyBsonType(nameof(ReadDecimal128), BsonType.Decimal128); State = GetNextState(); return _currentValue.AsDecimal128; } /// /// Reads a BSON Double from the reader. /// /// A Double. public override double ReadDouble() { if (Disposed) { ThrowObjectDisposedException(); } VerifyBsonType("ReadDouble", BsonType.Double); State = GetNextState(); return _currentValue.AsDouble; } /// /// Reads the end of a BSON array from the reader. /// public override void ReadEndArray() { if (Disposed) { ThrowObjectDisposedException(); } if (_context.ContextType != ContextType.Array) { ThrowInvalidContextType("ReadEndArray", _context.ContextType, ContextType.Array); } if (State == BsonReaderState.Type) { ReadBsonType(); // will set state to EndOfArray if at end of array } if (State != BsonReaderState.EndOfArray) { ThrowInvalidState("ReadEndArray", BsonReaderState.EndOfArray); } _context = _context.PopContext(); switch (_context.ContextType) { case ContextType.Array: State = BsonReaderState.Type; break; case ContextType.Document: State = BsonReaderState.Type; break; case ContextType.TopLevel: State = BsonReaderState.Done; break; default: throw new BsonInternalException("Unexpected ContextType."); } } /// /// Reads the end of a BSON document from the reader. /// public override void ReadEndDocument() { if (Disposed) { ThrowObjectDisposedException(); } if (_context.ContextType != ContextType.Document && _context.ContextType != ContextType.ScopeDocument) { ThrowInvalidContextType("ReadEndDocument", _context.ContextType, ContextType.Document, ContextType.ScopeDocument); } if (State == BsonReaderState.Type) { ReadBsonType(); // will set state to EndOfDocument if at end of document } if (State != BsonReaderState.EndOfDocument) { ThrowInvalidState("ReadEndDocument", BsonReaderState.EndOfDocument); } _context = _context.PopContext(); switch (_context.ContextType) { case ContextType.Array: State = BsonReaderState.Type; break; case ContextType.Document: State = BsonReaderState.Type; break; case ContextType.TopLevel: State = BsonReaderState.Done; break; default: throw new BsonInternalException("Unexpected ContextType."); } } /// /// Reads a BSON Int32 from the reader. /// /// An Int32. public override int ReadInt32() { if (Disposed) { ThrowObjectDisposedException(); } VerifyBsonType("ReadInt32", BsonType.Int32); State = GetNextState(); return _currentValue.AsInt32; } /// /// Reads a BSON Int64 from the reader. /// /// An Int64. public override long ReadInt64() { if (Disposed) { ThrowObjectDisposedException(); } VerifyBsonType("ReadInt64", BsonType.Int64); State = GetNextState(); return _currentValue.AsInt64; } /// /// Reads a BSON JavaScript from the reader. /// /// A string. public override string ReadJavaScript() { if (Disposed) { ThrowObjectDisposedException(); } VerifyBsonType("ReadJavaScript", BsonType.JavaScript); State = GetNextState(); return _currentValue.AsBsonJavaScript.Code; } /// /// Reads a BSON JavaScript with scope from the reader (call ReadStartDocument next to read the scope). /// /// A string. public override string ReadJavaScriptWithScope() { if (Disposed) { ThrowObjectDisposedException(); } VerifyBsonType("ReadJavaScriptWithScope", BsonType.JavaScriptWithScope); State = BsonReaderState.ScopeDocument; return _currentValue.AsBsonJavaScriptWithScope.Code; } /// /// Reads a BSON MaxKey from the reader. /// public override void ReadMaxKey() { if (Disposed) { ThrowObjectDisposedException(); } VerifyBsonType("ReadMaxKey", BsonType.MaxKey); State = GetNextState(); } /// /// Reads a BSON MinKey from the reader. /// public override void ReadMinKey() { if (Disposed) { ThrowObjectDisposedException(); } VerifyBsonType("ReadMinKey", BsonType.MinKey); State = GetNextState(); } /// /// Reads the name of an element from the reader. /// /// The name decoder. /// /// The name of the element. /// public override string ReadName(INameDecoder nameDecoder) { if (nameDecoder == null) { throw new ArgumentNullException("nameDecoder"); } if (Disposed) { ThrowObjectDisposedException(); } if (State == BsonReaderState.Type) { ReadBsonType(); } if (State != BsonReaderState.Name) { ThrowInvalidState("ReadName", BsonReaderState.Name); } nameDecoder.Inform(CurrentName); State = BsonReaderState.Value; return CurrentName; } /// /// Reads a BSON null from the reader. /// public override void ReadNull() { if (Disposed) { ThrowObjectDisposedException(); } VerifyBsonType("ReadNull", BsonType.Null); State = GetNextState(); } /// /// Reads a BSON ObjectId from the reader. /// /// An ObjectId. public override ObjectId ReadObjectId() { if (Disposed) { ThrowObjectDisposedException(); } VerifyBsonType("ReadObjectId", BsonType.ObjectId); State = GetNextState(); return _currentValue.AsObjectId; } /// /// Reads a BSON regular expression from the reader. /// /// A BsonRegularExpression. public override BsonRegularExpression ReadRegularExpression() { if (Disposed) { ThrowObjectDisposedException(); } VerifyBsonType("ReadRegularExpression", BsonType.RegularExpression); State = GetNextState(); return _currentValue.AsBsonRegularExpression; } /// /// Reads the start of a BSON array. /// public override void ReadStartArray() { if (Disposed) { ThrowObjectDisposedException(); } VerifyBsonType("ReadStartArray", BsonType.Array); var array = _currentValue.AsBsonArray; _context = new BsonDocumentReaderContext(_context, ContextType.Array, array); State = BsonReaderState.Type; } /// /// Reads the start of a BSON document. /// public override void ReadStartDocument() { if (Disposed) { ThrowObjectDisposedException(); } VerifyBsonType("ReadStartDocument", BsonType.Document); BsonDocument document; var script = _currentValue as BsonJavaScriptWithScope; if (script != null) { document = script.Scope; } else { document = _currentValue.AsBsonDocument; } _context = new BsonDocumentReaderContext(_context, ContextType.Document, document); State = BsonReaderState.Type; } /// /// Reads a BSON string from the reader. /// /// A String. public override string ReadString() { if (Disposed) { ThrowObjectDisposedException(); } VerifyBsonType("ReadString", BsonType.String); State = GetNextState(); return _currentValue.AsString; } /// /// Reads a BSON symbol from the reader. /// /// A string. public override string ReadSymbol() { if (Disposed) { ThrowObjectDisposedException(); } VerifyBsonType("ReadSymbol", BsonType.Symbol); State = GetNextState(); return _currentValue.AsBsonSymbol.Name; } /// /// Reads a BSON timestamp from the reader. /// /// The combined timestamp/increment. public override long ReadTimestamp() { if (Disposed) { ThrowObjectDisposedException(); } VerifyBsonType("ReadTimestamp", BsonType.Timestamp); State = GetNextState(); return _currentValue.AsBsonTimestamp.Value; } /// /// Reads a BSON undefined from the reader. /// public override void ReadUndefined() { if (Disposed) { ThrowObjectDisposedException(); } VerifyBsonType("ReadUndefined", BsonType.Undefined); State = GetNextState(); } /// /// Returns the reader to previously bookmarked position and state. /// /// The bookmark. public override void ReturnToBookmark(BsonReaderBookmark bookmark) { var documentReaderBookmark = (BsonDocumentReaderBookmark)bookmark; State = documentReaderBookmark.State; CurrentBsonType = documentReaderBookmark.CurrentBsonType; CurrentName = documentReaderBookmark.CurrentName; _context = documentReaderBookmark.CloneContext(); _currentValue = documentReaderBookmark.CurrentValue; } /// /// Skips the name (reader must be positioned on a name). /// public override void SkipName() { if (Disposed) { ThrowObjectDisposedException(); } if (State != BsonReaderState.Name) { ThrowInvalidState("SkipName", BsonReaderState.Name); } State = BsonReaderState.Value; } /// /// Skips the value (reader must be positioned on a value). /// public override void SkipValue() { if (Disposed) { ThrowObjectDisposedException(); } if (State != BsonReaderState.Value) { ThrowInvalidState("SkipValue", BsonReaderState.Value); } State = BsonReaderState.Type; } // protected methods /// /// Disposes of any resources used by the reader. /// /// True if called from Dispose. protected override void Dispose(bool disposing) { if (disposing) { try { Close(); } catch { } // ignore exceptions } base.Dispose(disposing); } // private methods private BsonReaderState GetNextState() { switch (_context.ContextType) { case ContextType.Array: case ContextType.Document: return BsonReaderState.Type; case ContextType.TopLevel: return BsonReaderState.Done; default: throw new BsonInternalException("Unexpected ContextType."); } } } }