/* 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.Collections.Generic; using System.IO; using System.Text; namespace MongoDB.Bson.IO { /// /// Represents a BSON writer to a BSON Stream. /// public class BsonBinaryWriter : BsonWriter { // private static fields private static readonly UTF8Encoding __strictUtf8Encoding = new UTF8Encoding(false, true); // private fields private Stream _stream; // can be null if we're only writing to the buffer private BsonBuffer _buffer; private bool _disposeBuffer; private BsonBinaryWriterSettings _binaryWriterSettings; // same value as in base class just declared as derived class private Stack _maxDocumentSizeStack = new Stack(); private BsonBinaryWriterContext _context; // constructors /// /// Initializes a new instance of the BsonBinaryWriter class. /// /// A stream. /// A BsonBuffer. /// Optional BsonBinaryWriter settings. public BsonBinaryWriter(Stream stream, BsonBuffer buffer, BsonBinaryWriterSettings settings) : this(buffer ?? new BsonBuffer(), buffer == null, settings) { _stream = stream; } /// /// Initializes a new instance of the BsonBinaryWriter class. /// /// A BsonBuffer. /// if set to true this BsonBinaryReader will own the buffer and when Dispose is called the buffer will be Disposed also. /// Optional BsonBinaryWriter settings. /// /// encoder /// or /// settings /// public BsonBinaryWriter(BsonBuffer buffer, bool disposeBuffer, BsonBinaryWriterSettings settings) : base(settings) { if (buffer == null) { throw new ArgumentNullException("encoder"); } _buffer = buffer; _disposeBuffer = disposeBuffer; _binaryWriterSettings = settings; // already frozen by base class _maxDocumentSizeStack.Push(_binaryWriterSettings.MaxDocumentSize); _context = null; State = BsonWriterState.Initial; } // public properties /// /// Gets the writer's BsonBuffer. /// public BsonBuffer Buffer { get { return _buffer; } } // public methods /// /// Closes the writer. /// public override void Close() { // Close can be called on Disposed objects if (State != BsonWriterState.Closed) { if (State == BsonWriterState.Done) { Flush(); } if (_stream != null && _binaryWriterSettings.CloseOutput) { _stream.Close(); } _context = null; State = BsonWriterState.Closed; } } /// /// Flushes any pending data to the output destination. /// public override void Flush() { if (Disposed) { throw new ObjectDisposedException("BsonBinaryWriter"); } if (State == BsonWriterState.Closed) { throw new InvalidOperationException("Flush called on closed BsonWriter."); } if (State != BsonWriterState.Done) { throw new InvalidOperationException("Flush called before BsonBinaryWriter was finished writing to buffer."); } if (_stream != null) { _buffer.WriteTo(_stream); _stream.Flush(); _buffer.Clear(); // only clear the buffer if we have written it to a stream } } /// /// Pops the max document size stack, restoring the previous max document size. /// public void PopMaxDocumentSize() { _maxDocumentSizeStack.Pop(); } /// /// Pushes a new max document size onto the max document size stack. /// /// The maximum size of the document. public void PushMaxDocumentSize(int maxDocumentSize) { _maxDocumentSizeStack.Push(Math.Min(maxDocumentSize, _maxDocumentSizeStack.Peek())); } #pragma warning disable 618 // about obsolete BsonBinarySubType.OldBinary /// /// Writes BSON binary data to the writer. /// /// The binary data. public override void WriteBinaryData(BsonBinaryData binaryData) { if (Disposed) { throw new ObjectDisposedException("BsonBinaryWriter"); } if (State != BsonWriterState.Value) { ThrowInvalidState("WriteBinaryData", BsonWriterState.Value); } var bytes = binaryData.Bytes; var subType = binaryData.SubType; var guidRepresentation = binaryData.GuidRepresentation; switch (subType) { case BsonBinarySubType.OldBinary: if (_binaryWriterSettings.FixOldBinarySubTypeOnOutput) { subType = BsonBinarySubType.Binary; // replace obsolete OldBinary with new Binary sub type } break; case BsonBinarySubType.UuidLegacy: case BsonBinarySubType.UuidStandard: if (_binaryWriterSettings.GuidRepresentation != GuidRepresentation.Unspecified) { var expectedSubType = (_binaryWriterSettings.GuidRepresentation == GuidRepresentation.Standard) ? BsonBinarySubType.UuidStandard : BsonBinarySubType.UuidLegacy; if (subType != expectedSubType) { var message = string.Format( "The GuidRepresentation for the writer is {0}, which requires the subType argument to be {1}, not {2}.", _binaryWriterSettings.GuidRepresentation, expectedSubType, subType); throw new BsonSerializationException(message); } if (guidRepresentation != _binaryWriterSettings.GuidRepresentation) { var message = string.Format( "The GuidRepresentation for the writer is {0}, which requires the the guidRepresentation argument to also be {0}, not {1}.", _binaryWriterSettings.GuidRepresentation, guidRepresentation); throw new BsonSerializationException(message); } } break; } _buffer.WriteByte((byte)BsonType.Binary); WriteNameHelper(); if (subType == BsonBinarySubType.OldBinary) { // sub type OldBinary has two sizes (for historical reasons) _buffer.WriteInt32(bytes.Length + 4); _buffer.WriteByte((byte)subType); _buffer.WriteInt32(bytes.Length); } else { _buffer.WriteInt32(bytes.Length); _buffer.WriteByte((byte)subType); } _buffer.WriteBytes(bytes); State = GetNextState(); } #pragma warning restore 618 /// /// Writes a BSON Boolean to the writer. /// /// The Boolean value. public override void WriteBoolean(bool value) { if (Disposed) { throw new ObjectDisposedException("BsonBinaryWriter"); } if (State != BsonWriterState.Value) { ThrowInvalidState("WriteBoolean", BsonWriterState.Value); } _buffer.WriteByte((byte)BsonType.Boolean); WriteNameHelper(); _buffer.WriteBoolean(value); State = GetNextState(); } /// /// Writes BSON binary data to the writer. /// /// The bytes. public override void WriteBytes(byte[] bytes) { if (Disposed) { throw new ObjectDisposedException("BsonBinaryWriter"); } if (State != BsonWriterState.Value) { ThrowInvalidState("WriteBytes", BsonWriterState.Value); } _buffer.WriteByte((byte)BsonType.Binary); WriteNameHelper(); _buffer.WriteInt32(bytes.Length); _buffer.WriteByte((byte)BsonBinarySubType.Binary); _buffer.WriteBytes(bytes); State = GetNextState(); } /// /// Writes a BSON DateTime to the writer. /// /// The number of milliseconds since the Unix epoch. public override void WriteDateTime(long value) { if (Disposed) { throw new ObjectDisposedException("BsonBinaryWriter"); } if (State != BsonWriterState.Value) { ThrowInvalidState("WriteDateTime", BsonWriterState.Value); } _buffer.WriteByte((byte)BsonType.DateTime); WriteNameHelper(); _buffer.WriteInt64(value); State = GetNextState(); } /// /// Writes a BSON Double to the writer. /// /// The Double value. public override void WriteDouble(double value) { if (Disposed) { throw new ObjectDisposedException("BsonBinaryWriter"); } if (State != BsonWriterState.Value) { ThrowInvalidState("WriteDouble", BsonWriterState.Value); } _buffer.WriteByte((byte)BsonType.Double); WriteNameHelper(); _buffer.WriteDouble(value); State = GetNextState(); } /// /// Writes the end of a BSON array to the writer. /// public override void WriteEndArray() { if (Disposed) { throw new ObjectDisposedException("BsonBinaryWriter"); } if (State != BsonWriterState.Value) { ThrowInvalidState("WriteEndArray", BsonWriterState.Value); } if (_context.ContextType != ContextType.Array) { ThrowInvalidContextType("WriteEndArray", _context.ContextType, ContextType.Array); } base.WriteEndArray(); _buffer.WriteByte(0); BackpatchSize(); // size of document _context = _context.ParentContext; State = GetNextState(); } /// /// Writes the end of a BSON document to the writer. /// public override void WriteEndDocument() { if (Disposed) { throw new ObjectDisposedException("BsonBinaryWriter"); } if (State != BsonWriterState.Name) { ThrowInvalidState("WriteEndDocument", BsonWriterState.Name); } if (_context.ContextType != ContextType.Document && _context.ContextType != ContextType.ScopeDocument) { ThrowInvalidContextType("WriteEndDocument", _context.ContextType, ContextType.Document, ContextType.ScopeDocument); } base.WriteEndDocument(); _buffer.WriteByte(0); BackpatchSize(); // size of document _context = _context.ParentContext; if (_context == null) { State = BsonWriterState.Done; } else { if (_context.ContextType == ContextType.JavaScriptWithScope) { BackpatchSize(); // size of the JavaScript with scope value _context = _context.ParentContext; } State = GetNextState(); } } /// /// Writes a BSON Int32 to the writer. /// /// The Int32 value. public override void WriteInt32(int value) { if (Disposed) { throw new ObjectDisposedException("BsonBinaryWriter"); } if (State != BsonWriterState.Value) { ThrowInvalidState("WriteInt32", BsonWriterState.Value); } _buffer.WriteByte((byte)BsonType.Int32); WriteNameHelper(); _buffer.WriteInt32(value); State = GetNextState(); } /// /// Writes a BSON Int64 to the writer. /// /// The Int64 value. public override void WriteInt64(long value) { if (Disposed) { throw new ObjectDisposedException("BsonBinaryWriter"); } if (State != BsonWriterState.Value) { ThrowInvalidState("WriteInt64", BsonWriterState.Value); } _buffer.WriteByte((byte)BsonType.Int64); WriteNameHelper(); _buffer.WriteInt64(value); State = GetNextState(); } /// /// Writes a BSON JavaScript to the writer. /// /// The JavaScript code. public override void WriteJavaScript(string code) { if (Disposed) { throw new ObjectDisposedException("BsonBinaryWriter"); } if (State != BsonWriterState.Value) { ThrowInvalidState("WriteJavaScript", BsonWriterState.Value); } _buffer.WriteByte((byte)BsonType.JavaScript); WriteNameHelper(); _buffer.WriteString(_binaryWriterSettings.Encoding, code); State = GetNextState(); } /// /// Writes a BSON JavaScript to the writer (call WriteStartDocument to start writing the scope). /// /// The JavaScript code. public override void WriteJavaScriptWithScope(string code) { if (Disposed) { throw new ObjectDisposedException("BsonBinaryWriter"); } if (State != BsonWriterState.Value) { ThrowInvalidState("WriteJavaScriptWithScope", BsonWriterState.Value); } _buffer.WriteByte((byte)BsonType.JavaScriptWithScope); WriteNameHelper(); _context = new BsonBinaryWriterContext(_context, ContextType.JavaScriptWithScope, _buffer.Position); _buffer.WriteInt32(0); // reserve space for size of JavaScript with scope value _buffer.WriteString(_binaryWriterSettings.Encoding, code); State = BsonWriterState.ScopeDocument; } /// /// Writes a BSON MaxKey to the writer. /// public override void WriteMaxKey() { if (Disposed) { throw new ObjectDisposedException("BsonBinaryWriter"); } if (State != BsonWriterState.Value) { ThrowInvalidState("WriteMaxKey", BsonWriterState.Value); } _buffer.WriteByte((byte)BsonType.MaxKey); WriteNameHelper(); State = GetNextState(); } /// /// Writes a BSON MinKey to the writer. /// public override void WriteMinKey() { if (Disposed) { throw new ObjectDisposedException("BsonBinaryWriter"); } if (State != BsonWriterState.Value) { ThrowInvalidState("WriteMinKey", BsonWriterState.Value); } _buffer.WriteByte((byte)BsonType.MinKey); WriteNameHelper(); State = GetNextState(); } /// /// Writes a BSON null to the writer. /// public override void WriteNull() { if (Disposed) { throw new ObjectDisposedException("BsonBinaryWriter"); } if (State != BsonWriterState.Value) { ThrowInvalidState("WriteNull", BsonWriterState.Value); } _buffer.WriteByte((byte)BsonType.Null); WriteNameHelper(); State = GetNextState(); } /// /// Writes a BSON ObjectId to the writer. /// /// The ObjectId. public override void WriteObjectId(ObjectId objectId) { if (Disposed) { throw new ObjectDisposedException("BsonBinaryWriter"); } if (State != BsonWriterState.Value) { ThrowInvalidState("WriteObjectId", BsonWriterState.Value); } _buffer.WriteByte((byte)BsonType.ObjectId); WriteNameHelper(); _buffer.WriteObjectId(objectId); State = GetNextState(); } /// /// Writes a raw BSON array. /// /// The byte buffer containing the raw BSON array. public override void WriteRawBsonArray(IByteBuffer slice) { if (Disposed) { throw new ObjectDisposedException("BsonBinaryWriter"); } if (State != BsonWriterState.Value) { ThrowInvalidState("WriteRawBsonArray", BsonWriterState.Value); } _buffer.WriteByte((byte)BsonType.Array); WriteNameHelper(); _buffer.ByteBuffer.WriteBytes(slice); // assumes byteBuffer is a valid raw BSON array State = GetNextState(); } /// /// Writes a raw BSON document. /// /// The byte buffer containing the raw BSON document. public override void WriteRawBsonDocument(IByteBuffer slice) { if (Disposed) { throw new ObjectDisposedException("BsonBinaryWriter"); } if (State != BsonWriterState.Initial && State != BsonWriterState.Value && State != BsonWriterState.ScopeDocument && State != BsonWriterState.Done) { ThrowInvalidState("WriteRawBsonDocument", BsonWriterState.Initial, BsonWriterState.Value, BsonWriterState.ScopeDocument, BsonWriterState.Done); } if (State == BsonWriterState.Value) { _buffer.WriteByte((byte)BsonType.Document); WriteNameHelper(); } _buffer.ByteBuffer.WriteBytes(slice); // assumes byteBuffer is a valid raw BSON document if (_context == null) { State = BsonWriterState.Done; } else { if (_context.ContextType == ContextType.JavaScriptWithScope) { BackpatchSize(); // size of the JavaScript with scope value _context = _context.ParentContext; } State = GetNextState(); } } /// /// Writes a BSON regular expression to the writer. /// /// A BsonRegularExpression. public override void WriteRegularExpression(BsonRegularExpression regex) { if (Disposed) { throw new ObjectDisposedException("BsonBinaryWriter"); } if (State != BsonWriterState.Value) { ThrowInvalidState("WriteRegularExpression", BsonWriterState.Value); } _buffer.WriteByte((byte)BsonType.RegularExpression); WriteNameHelper(); _buffer.WriteCString(_binaryWriterSettings.Encoding, regex.Pattern); _buffer.WriteCString(_binaryWriterSettings.Encoding, regex.Options); State = GetNextState(); } /// /// Writes the start of a BSON array to the writer. /// public override void WriteStartArray() { if (Disposed) { throw new ObjectDisposedException("BsonBinaryWriter"); } if (State != BsonWriterState.Value) { ThrowInvalidState("WriteStartArray", BsonWriterState.Value); } base.WriteStartArray(); _buffer.WriteByte((byte)BsonType.Array); WriteNameHelper(); _context = new BsonBinaryWriterContext(_context, ContextType.Array, _buffer.Position); _buffer.WriteInt32(0); // reserve space for size State = BsonWriterState.Value; } /// /// Writes the start of a BSON document to the writer. /// public override void WriteStartDocument() { if (Disposed) { throw new ObjectDisposedException("BsonBinaryWriter"); } if (State != BsonWriterState.Initial && State != BsonWriterState.Value && State != BsonWriterState.ScopeDocument && State != BsonWriterState.Done) { ThrowInvalidState("WriteStartDocument", BsonWriterState.Initial, BsonWriterState.Value, BsonWriterState.ScopeDocument, BsonWriterState.Done); } base.WriteStartDocument(); if (State == BsonWriterState.Value) { _buffer.WriteByte((byte)BsonType.Document); WriteNameHelper(); } _context = new BsonBinaryWriterContext(_context, ContextType.Document, _buffer.Position); _buffer.WriteInt32(0); // reserve space for size State = BsonWriterState.Name; } /// /// Writes a BSON String to the writer. /// /// The String value. public override void WriteString(string value) { if (Disposed) { throw new ObjectDisposedException("BsonBinaryWriter"); } if (State != BsonWriterState.Value) { ThrowInvalidState("WriteString", BsonWriterState.Value); } _buffer.WriteByte((byte)BsonType.String); WriteNameHelper(); _buffer.WriteString(_binaryWriterSettings.Encoding, value); State = GetNextState(); } /// /// Writes a BSON Symbol to the writer. /// /// The symbol. public override void WriteSymbol(string value) { if (Disposed) { throw new ObjectDisposedException("BsonBinaryWriter"); } if (State != BsonWriterState.Value) { ThrowInvalidState("WriteSymbol", BsonWriterState.Value); } _buffer.WriteByte((byte)BsonType.Symbol); WriteNameHelper(); _buffer.WriteString(_binaryWriterSettings.Encoding, value); State = GetNextState(); } /// /// Writes a BSON timestamp to the writer. /// /// The combined timestamp/increment value. public override void WriteTimestamp(long value) { if (Disposed) { throw new ObjectDisposedException("BsonBinaryWriter"); } if (State != BsonWriterState.Value) { ThrowInvalidState("WriteTimestamp", BsonWriterState.Value); } _buffer.WriteByte((byte)BsonType.Timestamp); WriteNameHelper(); _buffer.WriteInt64(value); State = GetNextState(); } /// /// Writes a BSON undefined to the writer. /// public override void WriteUndefined() { if (Disposed) { throw new ObjectDisposedException("BsonBinaryWriter"); } if (State != BsonWriterState.Value) { ThrowInvalidState("WriteUndefined", BsonWriterState.Value); } _buffer.WriteByte((byte)BsonType.Undefined); WriteNameHelper(); State = GetNextState(); } // protected methods /// /// Disposes of any resources used by the writer. /// /// True if called from Dispose. protected override void Dispose(bool disposing) { if (disposing) { Close(); if (_buffer != null) { if (_disposeBuffer) { _buffer.Dispose(); } _buffer = null; } } base.Dispose(disposing); } // private methods private void BackpatchSize() { int size = _buffer.Position - _context.StartPosition; if (size > _maxDocumentSizeStack.Peek()) { var message = string.Format("Size {0} is larger than MaxDocumentSize {1}.", size, _maxDocumentSizeStack.Peek()); throw new Exception(message); } _buffer.Backpatch(_context.StartPosition, size); } private BsonWriterState GetNextState() { if (_context.ContextType == ContextType.Array) { return BsonWriterState.Value; } else { return BsonWriterState.Name; } } private void WriteNameHelper() { string name; if (_context.ContextType == ContextType.Array) { name = (_context.Index++).ToString(); } else { name = Name; } _buffer.WriteCString(__strictUtf8Encoding, name); } } }