/* 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; using MongoDB.Bson.Serialization.Serializers; namespace MongoDB.Bson.IO { /// /// Represents a BSON writer for some external format (see subclasses). /// public abstract class BsonWriter : IDisposable { // private fields private bool _disposed = false; private BsonWriterSettings _settings; private BsonWriterState _state; private string _name; private bool _checkElementNames; private bool _checkUpdateDocument; 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 or sets whether to check element names (no periods or leading $). /// public bool CheckElementNames { get { return _checkElementNames; } set { _checkElementNames = value; } } /// /// Gets or sets whether to check an update document (turns CheckElementNames on if first element name does *not* start with $). /// public bool CheckUpdateDocument { get { return _checkUpdateDocument; } set { _checkUpdateDocument = value; } } /// /// 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 /// /// Creates a BsonWriter to a BsonBuffer. /// /// Optional BsonBinaryWriterSettings. /// A BsonWriter. public static BsonWriter Create(BsonBinaryWriterSettings settings) { return new BsonBinaryWriter(null, null, settings); } /// /// Creates a BsonWriter to a BsonBuffer. /// /// A BsonBuffer. /// A BsonWriter. public static BsonWriter Create(BsonBuffer buffer) { return new BsonBinaryWriter(null, buffer, BsonBinaryWriterSettings.Defaults); } /// /// Creates a BsonWriter to a BsonBuffer. /// /// A BsonBuffer. /// Optional BsonBinaryWriterSettings. /// A BsonWriter. public static BsonWriter Create(BsonBuffer buffer, BsonBinaryWriterSettings settings) { return new BsonBinaryWriter(null, buffer, settings); } /// /// Creates a BsonWriter to a BsonDocument. /// /// A BsonDocument. /// A BsonWriter. public static BsonWriter Create(BsonDocument document) { return Create(document, BsonDocumentWriterSettings.Defaults); } /// /// Creates a BsonWriter to a BsonDocument. /// /// A BsonDocument. /// The settings. /// A BsonWriter. public static BsonWriter Create(BsonDocument document, BsonDocumentWriterSettings settings) { return new BsonDocumentWriter(document, settings); } /// /// Creates a BsonWriter to a BSON Stream. /// /// A Stream. /// A BsonWriter. public static BsonWriter Create(Stream stream) { return Create(stream, BsonBinaryWriterSettings.Defaults); } /// /// Creates a BsonWriter to a BSON Stream. /// /// A Stream. /// Optional BsonBinaryWriterSettings. /// A BsonWriter. public static BsonWriter Create(Stream stream, BsonBinaryWriterSettings settings) { return new BsonBinaryWriter(stream, null, settings); } /// /// Creates a BsonWriter to a JSON TextWriter. /// /// A TextWriter. /// A BsonWriter. public static BsonWriter Create(TextWriter writer) { return new JsonWriter(writer, JsonWriterSettings.Defaults); } /// /// Creates a BsonWriter to a JSON TextWriter. /// /// A TextWriter. /// Optional JsonWriterSettings. /// A BsonWriter. public static BsonWriter Create(TextWriter writer, JsonWriterSettings settings) { return new JsonWriter(writer, settings); } // 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(); /// /// Writes BSON binary data to the writer. /// /// The binary data. public abstract void WriteBinaryData(BsonBinaryData binaryData); /// /// Writes a BSON binary data element to the writer. /// /// The binary data. /// The binary data subtype. [Obsolete("Use WriteBinaryData(BsonBinaryData binaryData) instead.")] public void WriteBinaryData(byte[] bytes, BsonBinarySubType subType) { var guidRepresentation = (subType == BsonBinarySubType.UuidStandard) ? GuidRepresentation.Standard : GuidRepresentation.Unspecified; WriteBinaryData(bytes, subType, guidRepresentation); } /// /// Writes BSON binary data to the writer. /// /// The binary data. /// The binary data subtype. /// The respresentation for Guids. [Obsolete("Use WriteBinaryData(BsonBinaryData binaryData) instead.")] public void WriteBinaryData( byte[] bytes, BsonBinarySubType subType, GuidRepresentation guidRepresentation) { var binaryData = new BsonBinaryData(bytes, subType, guidRepresentation); WriteBinaryData(binaryData); } /// /// Writes a BSON binary data element to the writer. /// /// The name of the element. /// The binary data. public void WriteBinaryData(string name, BsonBinaryData binaryData) { WriteName(name); WriteBinaryData(binaryData); } /// /// Writes a BSON binary data element to the writer. /// /// The name of the element. /// The binary data. /// The binary data subtype. [Obsolete("Use WriteBinaryData(string name, BsonBinaryData binaryData) instead.")] public void WriteBinaryData(string name, byte[] bytes, BsonBinarySubType subType) { WriteName(name); WriteBinaryData(bytes, subType); } /// /// Writes a BSON binary data element to the writer. /// /// The name of the element. /// The binary data. /// The binary data subtype. /// The representation for Guids. [Obsolete("Use WriteBinaryData(string name, BsonBinaryData binaryData) instead.")] public void WriteBinaryData( string name, byte[] bytes, BsonBinarySubType subType, GuidRepresentation guidRepresentation) { WriteName(name); WriteBinaryData(bytes, subType, guidRepresentation); } /// /// Writes a BSON Boolean to the writer. /// /// The Boolean value. public abstract void WriteBoolean(bool value); /// /// Writes a BSON Boolean element to the writer. /// /// The name of the element. /// The Boolean value. public void WriteBoolean(string name, bool value) { WriteName(name); WriteBoolean(value); } /// /// Writes BSON binary data to the writer. /// /// The bytes. public abstract void WriteBytes(byte[] bytes); /// /// Writes a BSON binary data element to the writer. /// /// The name of the element. /// The bytes. public void WriteBytes(string name, byte[] bytes) { WriteName(name); WriteBytes(bytes); } /// /// Writes a BSON DateTime to the writer. /// /// The number of milliseconds since the Unix epoch. public abstract void WriteDateTime(long value); /// /// Writes a BSON DateTime element to the writer. /// /// The name of the element. /// The number of milliseconds since the Unix epoch. public void WriteDateTime(string name, long value) { WriteName(name); WriteDateTime(value); } /// /// Writes a BSON Double to the writer. /// /// The Double value. public abstract void WriteDouble(double value); /// /// Writes a BSON Double element to the writer. /// /// The name of the element. /// The Double value. public void WriteDouble(string name, double value) { WriteName(name); WriteDouble(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--; } /// /// Writes a BSON Int32 to the writer. /// /// The Int32 value. public abstract void WriteInt32(int value); /// /// Writes a BSON Int32 element to the writer. /// /// The name of the element. /// The Int32 value. public void WriteInt32(string name, int value) { WriteName(name); WriteInt32(value); } /// /// Writes a BSON Int64 to the writer. /// /// The Int64 value. public abstract void WriteInt64(long value); /// /// Writes a BSON Int64 element to the writer. /// /// The name of the element. /// The Int64 value. public void WriteInt64(string name, long value) { WriteName(name); WriteInt64(value); } /// /// Writes a BSON JavaScript to the writer. /// /// The JavaScript code. public abstract void WriteJavaScript(string code); /// /// Writes a BSON JavaScript element to the writer. /// /// The name of the element. /// The JavaScript code. public void WriteJavaScript(string name, string code) { WriteName(name); WriteJavaScript(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 JavaScript element to the writer (call WriteStartDocument to start writing the scope). /// /// The name of the element. /// The JavaScript code. public void WriteJavaScriptWithScope(string name, string code) { WriteName(name); WriteJavaScriptWithScope(code); } /// /// Writes a BSON MaxKey to the writer. /// public abstract void WriteMaxKey(); /// /// Writes a BSON MaxKey element to the writer. /// /// The name of the element. public void WriteMaxKey(string name) { WriteName(name); WriteMaxKey(); } /// /// Writes a BSON MinKey to the writer. /// public abstract void WriteMinKey(); /// /// Writes a BSON MinKey element to the writer. /// /// The name of the element. public void WriteMinKey(string name) { WriteName(name); 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 ArgumentException("Element names cannot contain nulls.", "name"); } if (_disposed) { throw new ObjectDisposedException(this.GetType().Name); } if (_state != BsonWriterState.Name) { ThrowInvalidState("WriteName", BsonWriterState.Name); } CheckElementName(name); _name = name; _state = BsonWriterState.Value; } /// /// Writes a BSON null to the writer. /// public abstract void WriteNull(); /// /// Writes a BSON null element to the writer. /// /// The name of the element. public void WriteNull(string name) { WriteName(name); WriteNull(); } /// /// Writes a BSON ObjectId to the writer. /// /// The ObjectId. public abstract void WriteObjectId(ObjectId objectId); /// /// Writes a BSON ObjectId to the writer. /// /// The timestamp. /// The machine hash. /// The PID. /// The increment. [Obsolete("Use WriteObjectId(ObjectId objectId) instead.")] public void WriteObjectId(int timestamp, int machine, short pid, int increment) { var objectId = new ObjectId(timestamp, machine, pid, increment); WriteObjectId(objectId); } /// /// Writes a BSON ObjectId element to the writer. /// /// The name of the element. /// The ObjectId. public void WriteObjectId(string name, ObjectId objectId) { WriteName(name); WriteObjectId(objectId); } /// /// Writes a BSON ObjectId element to the writer. /// /// The name of the element. /// The timestamp. /// The machine hash. /// The PID. /// The increment. [Obsolete("Use WriteObjectId(string name, ObjectId objectId) instead.")] public void WriteObjectId(string name, int timestamp, int machine, short pid, int increment) { WriteName(name); WriteObjectId(timestamp, machine, pid, increment); } /// /// Writes a raw BSON array. /// /// The byte buffer containing the raw BSON array. public virtual void WriteRawBsonArray(IByteBuffer slice) { // overridden in BsonBinaryWriter using (var bsonBuffer = new BsonBuffer()) { BsonArray array; // wrap the array in a fake document so we can deserialize it var arrayLength = slice.Length; var documentLength = arrayLength + 8; bsonBuffer.WriteInt32(documentLength); bsonBuffer.WriteByte((byte)BsonType.Array); bsonBuffer.WriteByte((byte)'x'); bsonBuffer.WriteByte((byte)0); bsonBuffer.ByteBuffer.WriteBytes(slice); bsonBuffer.WriteByte((byte)0); bsonBuffer.Position = 0; using (var bsonReader = new BsonBinaryReader(bsonBuffer, true, BsonBinaryReaderSettings.Defaults)) { bsonReader.ReadStartDocument(); bsonReader.ReadName("x"); array = (BsonArray)BsonArraySerializer.Instance.Deserialize(bsonReader, typeof(BsonArray), null); bsonReader.ReadEndDocument(); } BsonArraySerializer.Instance.Serialize(this, typeof(BsonArray), array, null); } } /// /// Writes a raw BSON array. /// /// The name. /// The byte buffer containing the raw BSON array. public void WriteRawBsonArray(string name, IByteBuffer slice) { WriteName(name); WriteRawBsonArray(slice); } /// /// Writes a raw BSON document. /// /// The byte buffer containing the raw BSON document. public virtual void WriteRawBsonDocument(IByteBuffer slice) { // overridden in BsonBinaryWriter using (var bsonReader = new BsonBinaryReader(new BsonBuffer(slice, false), true, BsonBinaryReaderSettings.Defaults)) { var document = BsonSerializer.Deserialize(bsonReader); BsonDocumentSerializer.Instance.Serialize(this, typeof(BsonDocument), document, null); } } /// /// Writes a raw BSON document. /// /// The name. /// The byte buffer containing the raw BSON document. public void WriteRawBsonDocument(string name, IByteBuffer slice) { WriteName(name); WriteRawBsonDocument(slice); } /// /// Writes a BSON regular expression to the writer. /// /// A BsonRegularExpression. public abstract void WriteRegularExpression(BsonRegularExpression regex); /// /// Writes a BSON regular expression to the writer. /// /// A regular expression pattern. /// A regular expression options. [Obsolete("Use WriteRegularExpression(BsonRegularExpression regex) instead.")] public void WriteRegularExpression(string pattern, string options) { var regex = new BsonRegularExpression(pattern, options); WriteRegularExpression(regex); } /// /// Writes a BSON regular expression element to the writer. /// /// The name of the element. /// A BsonRegularExpression. public void WriteRegularExpression(string name, BsonRegularExpression regex) { WriteName(name); WriteRegularExpression(regex); } /// /// Writes a BSON regular expression element to the writer. /// /// The name of the element. /// A regular expression pattern. /// A regular expression options. [Obsolete("Use WriteRegularExpression(string name, BsonRegularExpression regex) instead.")] public void WriteRegularExpression(string name, string pattern, string options) { WriteName(name); WriteRegularExpression(pattern, options); } /// /// 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 array element to the writer. /// /// The name of the element. public void WriteStartArray(string name) { WriteName(name); WriteStartArray(); } /// /// 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?)."); } } /// /// Writes the start of a BSON document element to the writer. /// /// The name of the element. public void WriteStartDocument(string name) { WriteName(name); WriteStartDocument(); } /// /// Writes a BSON String to the writer. /// /// The String value. public abstract void WriteString(string value); /// /// Writes a BSON String element to the writer. /// /// The name of the element. /// The String value. public void WriteString(string name, string value) { WriteName(name); WriteString(value); } /// /// Writes a BSON Symbol to the writer. /// /// The symbol. public abstract void WriteSymbol(string value); /// /// Writes a BSON Symbol element to the writer. /// /// The name of the element. /// The symbol. public void WriteSymbol(string name, string value) { WriteName(name); WriteSymbol(value); } /// /// Writes a BSON timestamp to the writer. /// /// The combined timestamp/increment value. public abstract void WriteTimestamp(long value); /// /// Writes a BSON timestamp element to the writer. /// /// The name of the element. /// The combined timestamp/increment value. public void WriteTimestamp(string name, long value) { WriteName(name); WriteTimestamp(value); } /// /// Writes a BSON undefined to the writer. /// public abstract void WriteUndefined(); /// /// Writes a BSON undefined element to the writer. /// /// The name of the element. public void WriteUndefined(string name) { WriteName(name); WriteUndefined(); } // protected methods /// /// Checks that the element name is valid. /// /// The element name to be checked. protected void CheckElementName(string name) { if (_checkUpdateDocument) { _checkElementNames = name == "" || name[0] != '$'; _checkUpdateDocument = false; return; } if (_checkElementNames) { if (name == "") { var message = "Element name '' is not valid because it is an empty string."; throw new BsonSerializationException(message); } if (name[0] == '$') { // a few element names starting with $ have to be allowed for historical reasons switch (name) { case "$code": case "$db": case "$id": case "$ref": case "$scope": break; default: var message = string.Format("Element name '{0}' is not valid because it starts with a '$'.", name); throw new BsonSerializationException(message); } } if (name.IndexOf('.') != -1) { var message = string.Format("Element name '{0}' is not valid because it contains a '.'.", name); throw new BsonSerializationException(message); } } } /// /// 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); } } }