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