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