/* 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.Collections.Generic; using System.Linq; using MongoDB.Bson.Serialization; using MongoDB.Bson.Serialization.Serializers; namespace MongoDB.Bson.IO { /// /// Represents a BSON writer for some external format (see subclasses). /// public abstract class BsonWriter : IBsonWriter { // private fields private Func _childElementNameValidatorFactory = () => NoOpElementNameValidator.Instance; private bool _disposed = false; private IElementNameValidator _elementNameValidator = NoOpElementNameValidator.Instance; private Stack _elementNameValidatorStack = new Stack(); private BsonWriterSettings _settings; private BsonWriterState _state; private string _name; private int _serializationDepth; // constructors /// /// Initializes a new instance of the BsonWriter class. /// /// The writer settings. protected BsonWriter(BsonWriterSettings settings) { if (settings == null) { throw new ArgumentNullException("settings"); } _settings = settings.FrozenCopy(); _state = BsonWriterState.Initial; } // public properties /// /// Gets the current serialization depth. /// public int SerializationDepth { get { return _serializationDepth; } } /// /// Gets the settings of the writer. /// public BsonWriterSettings Settings { get { return _settings; } } /// /// Gets the current state of the writer. /// public BsonWriterState State { get { return _state; } protected set { _state = value; } } // protected properties /// /// Gets whether the BsonWriter has been disposed. /// public bool Disposed { get { return _disposed; } } // protected properties /// /// Gets the name of the element being written. /// protected string Name { get { return _name; } } // public static methods // public methods /// /// Closes the writer. /// public abstract void Close(); /// /// Disposes of any resources used by the writer. /// public void Dispose() { if (!_disposed) { Dispose(true); _disposed = true; } } /// /// Flushes any pending data to the output destination. /// public abstract void Flush(); /// /// Pops the element name validator. /// /// The popped element validator. public void PopElementNameValidator() { _elementNameValidator = _elementNameValidatorStack.Pop(); _childElementNameValidatorFactory = () => _elementNameValidator; } /// /// Pushes the element name validator. /// /// The validator. public void PushElementNameValidator(IElementNameValidator validator) { if (validator == null) { throw new ArgumentNullException("validator"); } _elementNameValidatorStack.Push(_elementNameValidator); _elementNameValidator = validator; _childElementNameValidatorFactory = () => _elementNameValidator; } /// /// Writes BSON binary data to the writer. /// /// The binary data. public abstract void WriteBinaryData(BsonBinaryData binaryData); /// /// Writes a BSON Boolean to the writer. /// /// The Boolean value. public abstract void WriteBoolean(bool value); /// /// Writes BSON binary data to the writer. /// /// The bytes. public abstract void WriteBytes(byte[] bytes); /// /// Writes a BSON DateTime to the writer. /// /// The number of milliseconds since the Unix epoch. public abstract void WriteDateTime(long value); /// public abstract void WriteDecimal128(Decimal128 value); /// /// Writes a BSON Double to the writer. /// /// The Double value. public abstract void WriteDouble(double value); /// /// Writes the end of a BSON array to the writer. /// public virtual void WriteEndArray() { _serializationDepth--; } /// /// Writes the end of a BSON document to the writer. /// public virtual void WriteEndDocument() { _serializationDepth--; PopElementNameValidator(); } /// /// Writes a BSON Int32 to the writer. /// /// The Int32 value. public abstract void WriteInt32(int value); /// /// Writes a BSON Int64 to the writer. /// /// The Int64 value. public abstract void WriteInt64(long value); /// /// Writes a BSON JavaScript to the writer. /// /// The JavaScript code. public abstract void WriteJavaScript(string code); /// /// Writes a BSON JavaScript to the writer (call WriteStartDocument to start writing the scope). /// /// The JavaScript code. public abstract void WriteJavaScriptWithScope(string code); /// /// Writes a BSON MaxKey to the writer. /// public abstract void WriteMaxKey(); /// /// Writes a BSON MinKey to the writer. /// public abstract void WriteMinKey(); /// /// Writes the name of an element to the writer. /// /// The name of the element. public virtual void WriteName(string name) { if (name == null) { throw new ArgumentNullException("name"); } if (name.IndexOf('\0') != -1) { throw new BsonSerializationException("Element names cannot contain nulls."); } if (_disposed) { throw new ObjectDisposedException(this.GetType().Name); } if (_state != BsonWriterState.Name) { ThrowInvalidState("WriteName", BsonWriterState.Name); } if (!_elementNameValidator.IsValidElementName(name)) { var message = string.Format("Element name '{0}' is not valid'.", name); throw new BsonSerializationException(message); } _childElementNameValidatorFactory = () => _elementNameValidator.GetValidatorForChildContent(name); _name = name; _state = BsonWriterState.Value; } /// /// Writes a BSON null to the writer. /// public abstract void WriteNull(); /// /// Writes a BSON ObjectId to the writer. /// /// The ObjectId. public abstract void WriteObjectId(ObjectId objectId); /// /// Writes a raw BSON array. /// /// The byte buffer containing the raw BSON array. public virtual void WriteRawBsonArray(IByteBuffer slice) { // overridden in BsonBinaryWriter to write the raw bytes to the stream // for all other streams, deserialize the raw bytes and serialize the resulting array instead using (var chunkSource = new InputBufferChunkSource(BsonChunkPool.Default)) using (var buffer = new MultiChunkBuffer(chunkSource)) using (var stream = new ByteBufferStream(buffer)) { // wrap the array in a fake document so we can deserialize it var documentLength = slice.Length + 8; buffer.EnsureCapacity(documentLength); stream.WriteInt32(documentLength); stream.WriteBsonType(BsonType.Array); stream.WriteByte((byte)'x'); stream.WriteByte(0); stream.WriteSlice(slice); stream.WriteByte(0); buffer.MakeReadOnly(); stream.Position = 0; using (var reader = new BsonBinaryReader(stream, BsonBinaryReaderSettings.Defaults)) { var deserializationContext = BsonDeserializationContext.CreateRoot(reader); reader.ReadStartDocument(); reader.ReadName("x"); var array = BsonArraySerializer.Instance.Deserialize(deserializationContext); reader.ReadEndDocument(); var serializationContext = BsonSerializationContext.CreateRoot(this); BsonArraySerializer.Instance.Serialize(serializationContext, array); } } } /// /// Writes a raw BSON document. /// /// The byte buffer containing the raw BSON document. public virtual void WriteRawBsonDocument(IByteBuffer slice) { // overridden in BsonBinaryWriter to write the raw bytes to the stream // for all other streams, deserialize the raw bytes and serialize the resulting document instead using (var stream = new ByteBufferStream(slice, ownsBuffer: false)) using (var bsonReader = new BsonBinaryReader(stream, BsonBinaryReaderSettings.Defaults)) { var deserializationContext = BsonDeserializationContext.CreateRoot(bsonReader); var document = BsonDocumentSerializer.Instance.Deserialize(deserializationContext); var serializationContext = BsonSerializationContext.CreateRoot(this); BsonDocumentSerializer.Instance.Serialize(serializationContext, document); } } /// /// Writes a BSON regular expression to the writer. /// /// A BsonRegularExpression. public abstract void WriteRegularExpression(BsonRegularExpression regex); /// /// Writes the start of a BSON array to the writer. /// public virtual void WriteStartArray() { _serializationDepth++; if (_serializationDepth > _settings.MaxSerializationDepth) { throw new BsonSerializationException("Maximum serialization depth exceeded (does the object being serialized have a circular reference?)."); } } /// /// Writes the start of a BSON document to the writer. /// public virtual void WriteStartDocument() { _serializationDepth++; if (_serializationDepth > _settings.MaxSerializationDepth) { throw new BsonSerializationException("Maximum serialization depth exceeded (does the object being serialized have a circular reference?)."); } PushElementNameValidator(_childElementNameValidatorFactory()); } /// /// Writes a BSON String to the writer. /// /// The String value. public abstract void WriteString(string value); /// /// Writes a BSON Symbol to the writer. /// /// The symbol. public abstract void WriteSymbol(string value); /// /// Writes a BSON timestamp to the writer. /// /// The combined timestamp/increment value. public abstract void WriteTimestamp(long value); /// /// Writes a BSON undefined to the writer. /// public abstract void WriteUndefined(); // protected methods /// /// Disposes of any resources used by the writer. /// /// 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 BsonWriterState[] validStates) { string message; if (_state == BsonWriterState.Initial || _state == BsonWriterState.ScopeDocument || _state == BsonWriterState.Done) { if (!methodName.StartsWith("End", StringComparison.Ordinal) && methodName != "WriteName") { var typeName = methodName.Substring(5); if (typeName.StartsWith("Start", StringComparison.Ordinal)) { typeName = typeName.Substring(5); } var article = "A"; if (new char[] { 'A', 'E', 'I', 'O', 'U' }.Contains(typeName[0])) { article = "An"; } message = string.Format( "{0} {1} value cannot be written to the root level of a BSON document.", article, typeName); throw new InvalidOperationException(message); } } var validStatesString = string.Join(" or ", validStates.Select(s => s.ToString()).ToArray()); message = string.Format( "{0} can only be called when State is {1}, not when State is {2}", methodName, validStatesString, _state); throw new InvalidOperationException(message); } } }