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