using System; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; using System.Text; using Newtonsoft.Json.Utilities; namespace Newtonsoft.Json.Bson { internal class BsonBinaryWriter { private static readonly Encoding Encoding = Encoding.UTF8; private readonly BinaryWriter _writer; private byte[] _largeByteBuffer; private int _maxChars; public BsonBinaryWriter(Stream stream) { _writer = new BinaryWriter(stream); } public void Flush() { _writer.Flush(); } public void WriteToken(BsonToken t) { CalculateSize(t); WriteTokenInternal(t); } private void WriteTokenInternal(BsonToken t) { switch (t.Type) { case BsonType.Object: { BsonObject value = (BsonObject) t; _writer.Write(value.CalculatedSize); foreach (BsonProperty property in value) { _writer.Write((sbyte)property.Value.Type); WriteString((string)property.Name.Value, property.Name.ByteCount, null); WriteTokenInternal(property.Value); } _writer.Write((byte)0); } break; case BsonType.Array: { BsonArray value = (BsonArray) t; _writer.Write(value.CalculatedSize); int index = 0; foreach (BsonToken c in value) { _writer.Write((sbyte)c.Type); WriteString(index.ToString(CultureInfo.InvariantCulture), MathUtils.IntLength(index), null); WriteTokenInternal(c); index++; } _writer.Write((byte)0); } break; case BsonType.Integer: { BsonValue value = (BsonValue) t; _writer.Write(Convert.ToInt32(value.Value, CultureInfo.InvariantCulture)); } break; case BsonType.Long: { BsonValue value = (BsonValue)t; _writer.Write(Convert.ToInt64(value.Value, CultureInfo.InvariantCulture)); } break; case BsonType.Number: { BsonValue value = (BsonValue)t; _writer.Write(Convert.ToDouble(value.Value, CultureInfo.InvariantCulture)); } break; case BsonType.String: { BsonString value = (BsonString)t; WriteString((string)value.Value, value.ByteCount, value.CalculatedSize - 4); } break; case BsonType.Boolean: { BsonValue value = (BsonValue)t; _writer.Write((bool)value.Value); } break; case BsonType.Null: case BsonType.Undefined: break; case BsonType.Date: { BsonValue value = (BsonValue)t; long ticks = 0; if (value.Value is DateTime) { DateTime dateTime = (DateTime) value.Value; dateTime = dateTime.ToUniversalTime(); ticks = JsonConvert.ConvertDateTimeToJavaScriptTicks(dateTime); } #if !PocketPC && !NET20 else { DateTimeOffset dateTimeOffset = (DateTimeOffset) value.Value; ticks = JsonConvert.ConvertDateTimeToJavaScriptTicks(dateTimeOffset.UtcDateTime, dateTimeOffset.Offset); } #endif _writer.Write(ticks); } break; case BsonType.Binary: { BsonValue value = (BsonValue)t; byte[] data = (byte[])value.Value; _writer.Write(data.Length); _writer.Write((byte)BsonBinaryType.Data); _writer.Write(data); } break; case BsonType.Oid: { BsonValue value = (BsonValue)t; byte[] data = (byte[])value.Value; _writer.Write(data); } break; case BsonType.Regex: { BsonRegex value = (BsonRegex) t; WriteString((string)value.Pattern.Value, value.Pattern.ByteCount, null); WriteString((string)value.Options.Value, value.Options.ByteCount, null); } break; default: throw new ArgumentOutOfRangeException("t", "Unexpected token when writing BSON: {0}".FormatWith(CultureInfo.InvariantCulture, t.Type)); } } private void WriteString(string s, int byteCount, int? calculatedlengthPrefix) { if (calculatedlengthPrefix != null) _writer.Write(calculatedlengthPrefix.Value); if (s != null) { if (_largeByteBuffer == null) { _largeByteBuffer = new byte[256]; _maxChars = 256/Encoding.GetMaxByteCount(1); } if (byteCount <= 256) { Encoding.GetBytes(s, 0, s.Length, _largeByteBuffer, 0); _writer.Write(_largeByteBuffer, 0, byteCount); } else { int charCount; int totalCharsWritten = 0; for (int i = s.Length; i > 0; i -= charCount) { charCount = (i > _maxChars) ? _maxChars : i; int count = Encoding.GetBytes(s, totalCharsWritten, charCount, _largeByteBuffer, 0); _writer.Write(_largeByteBuffer, 0, count); totalCharsWritten += charCount; } } } _writer.Write((byte)0); } private int CalculateSize(int stringByteCount) { return stringByteCount + 1; } private int CalculateSizeWithLength(int stringByteCount, bool includeSize) { int baseSize = (includeSize) ? 5 // size bytes + terminator : 1; // terminator return baseSize + stringByteCount; } private int CalculateSize(BsonToken t) { switch (t.Type) { case BsonType.Object: { BsonObject value = (BsonObject) t; int bases = 4; foreach (BsonProperty p in value) { int size = 1; size += CalculateSize(p.Name); size += CalculateSize(p.Value); bases += size; } bases += 1; value.CalculatedSize = bases; return bases; } case BsonType.Array: { BsonArray value = (BsonArray)t; int size = 4; int index = 0; foreach (BsonToken c in value) { size += 1; size += CalculateSize(MathUtils.IntLength(index)); size += CalculateSize(c); index++; } size += 1; value.CalculatedSize = size; return value.CalculatedSize; } case BsonType.Integer: return 4; case BsonType.Long: return 8; case BsonType.Number: return 8; case BsonType.String: { BsonString value = (BsonString)t; string s = (string) value.Value; value.ByteCount = (s != null) ? Encoding.GetByteCount(s) : 0; value.CalculatedSize = CalculateSizeWithLength(value.ByteCount, value.IncludeLength); return value.CalculatedSize; } case BsonType.Boolean: return 1; case BsonType.Null: case BsonType.Undefined: return 0; case BsonType.Date: return 8; case BsonType.Binary: { BsonValue value = (BsonValue) t; byte[] data = (byte[])value.Value; value.CalculatedSize = 4 + 1 + data.Length; return value.CalculatedSize; } case BsonType.Oid: return 12; case BsonType.Regex: { BsonRegex value = (BsonRegex) t; int size = 0; size += CalculateSize(value.Pattern); size += CalculateSize(value.Options); value.CalculatedSize = size; return value.CalculatedSize; } default: throw new ArgumentOutOfRangeException("t", "Unexpected token when writing BSON: {0}".FormatWith(CultureInfo.InvariantCulture, t.Type)); } } } }