using System; using System.Collections; using System.Collections.Generic; using System.IO; using System.Text; using Google.ProtocolBuffers.Descriptors; namespace Google.ProtocolBuffers.Serialization { /// /// JsonFormatWriter is a .NET 2.0 friendly json formatter for proto buffer messages. For .NET 3.5 /// you may also use the XmlFormatWriter with an XmlWriter created by the /// JsonReaderWriterFactory. /// public abstract class JsonFormatWriter : AbstractTextWriter { #region buffering implementations private class JsonTextWriter : JsonFormatWriter { private readonly char[] _buffer; private TextWriter _output; private int _bufferPos; public JsonTextWriter(TextWriter output) { _buffer = new char[4096]; _bufferPos = 0; _output = output; _counter.Add(0); } /// /// Returns the output of TextWriter.ToString() where TextWriter is the ctor argument. /// public override string ToString() { Flush(); if (_output != null) { return _output.ToString(); } return new String(_buffer, 0, _bufferPos); } protected override void WriteToOutput(char[] chars, int offset, int len) { if (_bufferPos + len >= _buffer.Length) { if (_output == null) { _output = new StringWriter(new StringBuilder(_buffer.Length*2 + len)); } Flush(); } if (len < _buffer.Length) { if (len <= 12) { int stop = offset + len; for (int i = offset; i < stop; i++) { _buffer[_bufferPos++] = chars[i]; } } else { Buffer.BlockCopy(chars, offset << 1, _buffer, _bufferPos << 1, len << 1); _bufferPos += len; } } else { _output.Write(chars, offset, len); } } protected override void WriteToOutput(char ch) { if (_bufferPos >= _buffer.Length) { if (_output == null) { _output = new StringWriter(new StringBuilder(_buffer.Length * 2)); } Flush(); } _buffer[_bufferPos++] = ch; } public override void Flush() { if (_bufferPos > 0 && _output != null) { _output.Write(_buffer, 0, _bufferPos); _bufferPos = 0; } base.Flush(); } } private class JsonStreamWriter : JsonFormatWriter { static readonly Encoding Encoding = new UTF8Encoding(false); private readonly byte[] _buffer; private Stream _output; private int _bufferPos; public JsonStreamWriter(Stream output) { _buffer = new byte[8192]; _bufferPos = 0; _output = output; _counter.Add(0); } protected override void WriteToOutput(char[] chars, int offset, int len) { if (_bufferPos + len >= _buffer.Length) { Flush(); } if (len < _buffer.Length) { if (len <= 12) { int stop = offset + len; for (int i = offset; i < stop; i++) { _buffer[_bufferPos++] = (byte) chars[i]; } } else { _bufferPos += Encoding.GetBytes(chars, offset, len, _buffer, _bufferPos); } } else { byte[] temp = Encoding.GetBytes(chars, offset, len); _output.Write(temp, 0, temp.Length); } } protected override void WriteToOutput(char ch) { if (_bufferPos >= _buffer.Length) { Flush(); } _buffer[_bufferPos++] = (byte) ch; } public override void Flush() { if (_bufferPos > 0 && _output != null) { _output.Write(_buffer, 0, _bufferPos); _bufferPos = 0; } base.Flush(); } } #endregion //Tracks the writer depth and the array element count at that depth. private readonly List _counter; //True if the top-level of the writer is an array as opposed to a single message. private bool _isArray; /// /// Constructs a JsonFormatWriter, use the ToString() member to extract the final Json on completion. /// protected JsonFormatWriter() { _counter = new List(); } /// /// Constructs a JsonFormatWriter, use ToString() to extract the final output /// public static JsonFormatWriter CreateInstance() { return new JsonTextWriter(null); } /// /// Constructs a JsonFormatWriter to output to the given text writer /// public static JsonFormatWriter CreateInstance(TextWriter output) { return new JsonTextWriter(output); } /// /// Constructs a JsonFormatWriter to output to the given stream /// public static JsonFormatWriter CreateInstance(Stream output) { return new JsonStreamWriter(output); } /// Write to the output stream protected void WriteToOutput(string format, params object[] args) { WriteToOutput(String.Format(format, args)); } /// Write to the output stream protected void WriteToOutput(string text) { WriteToOutput(text.ToCharArray(), 0, text.Length); } /// Write to the output stream protected abstract void WriteToOutput(char ch); /// Write to the output stream protected abstract void WriteToOutput(char[] chars, int offset, int len); /// Sets the output formatting to use Environment.NewLine with 4-character indentions public JsonFormatWriter Formatted() { NewLine = FrameworkPortability.NewLine; Indent = " "; Whitespace = " "; return this; } /// Gets or sets the characters to use for the new-line, default = empty public string NewLine { get; set; } /// Gets or sets the text to use for indenting, default = empty public string Indent { get; set; } /// Gets or sets the whitespace to use to separate the text, default = empty public string Whitespace { get; set; } private void Seperator() { if (_counter.Count == 0) { throw new InvalidOperationException("Mismatched open/close in Json writer."); } int index = _counter.Count - 1; if (_counter[index] > 0) { WriteToOutput(','); } WriteLine(String.Empty); _counter[index] = _counter[index] + 1; } private void WriteLine(string content) { if (!String.IsNullOrEmpty(NewLine)) { WriteToOutput(NewLine); for (int i = 1; i < _counter.Count; i++) { WriteToOutput(Indent); } } else if (!String.IsNullOrEmpty(Whitespace)) { WriteToOutput(Whitespace); } WriteToOutput(content); } private void WriteName(string field) { Seperator(); if (!String.IsNullOrEmpty(field)) { WriteToOutput('"'); WriteToOutput(field); WriteToOutput('"'); WriteToOutput(':'); if (!String.IsNullOrEmpty(Whitespace)) { WriteToOutput(Whitespace); } } } private void EncodeText(string value) { char[] text = value.ToCharArray(); int len = text.Length; int pos = 0; while (pos < len) { int next = pos; while (next < len && text[next] >= 32 && text[next] < 127 && text[next] != '\\' && text[next] != '/' && text[next] != '"') { next++; } WriteToOutput(text, pos, next - pos); if (next < len) { switch (text[next]) { case '"': WriteToOutput(@"\"""); break; case '\\': WriteToOutput(@"\\"); break; //odd at best to escape '/', most Json implementations don't, but it is defined in the rfc-4627 case '/': WriteToOutput(@"\/"); break; case '\b': WriteToOutput(@"\b"); break; case '\f': WriteToOutput(@"\f"); break; case '\n': WriteToOutput(@"\n"); break; case '\r': WriteToOutput(@"\r"); break; case '\t': WriteToOutput(@"\t"); break; default: WriteToOutput(@"\u{0:x4}", (int) text[next]); break; } next++; } pos = next; } } /// /// Writes a String value /// protected override void WriteAsText(string field, string textValue, object typedValue) { WriteName(field); if (typedValue is bool || typedValue is int || typedValue is uint || typedValue is long || typedValue is ulong || typedValue is double || typedValue is float) { WriteToOutput(textValue); } else { WriteToOutput('"'); if (typedValue is string) { EncodeText(textValue); } else { WriteToOutput(textValue); } WriteToOutput('"'); } } /// /// Writes a Double value /// protected override void Write(string field, double value) { if (double.IsNaN(value) || double.IsNegativeInfinity(value) || double.IsPositiveInfinity(value)) { throw new InvalidOperationException("This format does not support NaN, Infinity, or -Infinity"); } base.Write(field, value); } /// /// Writes a Single value /// protected override void Write(string field, float value) { if (float.IsNaN(value) || float.IsNegativeInfinity(value) || float.IsPositiveInfinity(value)) { throw new InvalidOperationException("This format does not support NaN, Infinity, or -Infinity"); } base.Write(field, value); } // Treat enum as string protected override void WriteEnum(string field, int number, string name) { Write(field, name); } /// /// Writes an array of field values /// protected override void WriteArray(FieldType type, string field, IEnumerable items) { IEnumerator enumerator = items.GetEnumerator(); try { if (!enumerator.MoveNext()) { return; } } finally { if (enumerator is IDisposable) { ((IDisposable) enumerator).Dispose(); } } WriteName(field); WriteToOutput("["); _counter.Add(0); base.WriteArray(type, String.Empty, items); _counter.RemoveAt(_counter.Count - 1); WriteLine("]"); } /// /// Writes a message /// protected override void WriteMessageOrGroup(string field, IMessageLite message) { WriteName(field); WriteMessage(message); } /// /// Writes the message to the the formatted stream. /// public override void WriteMessage(IMessageLite message) { WriteMessageStart(); message.WriteTo(this); WriteMessageEnd(); } /// /// Used to write the root-message preamble, in json this is the left-curly brace '{'. /// After this call you can call IMessageLite.MergeTo(...) and complete the message with /// a call to WriteMessageEnd(). /// public override void WriteMessageStart() { if (_isArray) { Seperator(); } WriteToOutput("{"); _counter.Add(0); } /// /// Used to complete a root-message previously started with a call to WriteMessageStart() /// public override void WriteMessageEnd() { _counter.RemoveAt(_counter.Count - 1); WriteLine("}"); Flush(); } /// /// Used in streaming arrays of objects to the writer /// /// /// /// using(writer.StartArray()) /// foreach(IMessageLite m in messages) /// writer.WriteMessage(m); /// /// public sealed class JsonArray : IDisposable { private JsonFormatWriter _writer; internal JsonArray(JsonFormatWriter writer) { _writer = writer; _writer.WriteToOutput("["); _writer._counter.Add(0); } /// /// Causes the end of the array character to be written. /// private void EndArray() { if (_writer != null) { _writer._counter.RemoveAt(_writer._counter.Count - 1); _writer.WriteLine("]"); _writer.Flush(); } _writer = null; } void IDisposable.Dispose() { EndArray(); } } /// /// Used to write an array of messages as the output rather than a single message. /// /// /// /// using(writer.StartArray()) /// foreach(IMessageLite m in messages) /// writer.WriteMessage(m); /// /// public JsonArray StartArray() { if (_isArray) { Seperator(); } _isArray = true; return new JsonArray(this); } } }