using System; | |
using System.Collections; | |
using System.Collections.Generic; | |
using System.IO; | |
using System.Text; | |
using Google.ProtocolBuffers.Descriptors; | |
namespace Google.ProtocolBuffers.Serialization | |
{ | |
/// <summary> | |
/// 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 | |
/// <see cref="System.Runtime.Serialization.Json.JsonReaderWriterFactory">JsonReaderWriterFactory</see>. | |
/// </summary> | |
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); | |
} | |
/// <summary> | |
/// Returns the output of TextWriter.ToString() where TextWriter is the ctor argument. | |
/// </summary> | |
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<int> _counter; | |
//True if the top-level of the writer is an array as opposed to a single message. | |
private bool _isArray; | |
/// <summary> | |
/// Constructs a JsonFormatWriter, use the ToString() member to extract the final Json on completion. | |
/// </summary> | |
protected JsonFormatWriter() | |
{ | |
_counter = new List<int>(); | |
} | |
/// <summary> | |
/// Constructs a JsonFormatWriter, use ToString() to extract the final output | |
/// </summary> | |
public static JsonFormatWriter CreateInstance() | |
{ | |
return new JsonTextWriter(null); | |
} | |
/// <summary> | |
/// Constructs a JsonFormatWriter to output to the given text writer | |
/// </summary> | |
public static JsonFormatWriter CreateInstance(TextWriter output) | |
{ | |
return new JsonTextWriter(output); | |
} | |
/// <summary> | |
/// Constructs a JsonFormatWriter to output to the given stream | |
/// </summary> | |
public static JsonFormatWriter CreateInstance(Stream output) | |
{ | |
return new JsonStreamWriter(output); | |
} | |
/// <summary> Write to the output stream </summary> | |
protected void WriteToOutput(string format, params object[] args) | |
{ | |
WriteToOutput(String.Format(format, args)); | |
} | |
/// <summary> Write to the output stream </summary> | |
protected void WriteToOutput(string text) | |
{ | |
WriteToOutput(text.ToCharArray(), 0, text.Length); | |
} | |
/// <summary> Write to the output stream </summary> | |
protected abstract void WriteToOutput(char ch); | |
/// <summary> Write to the output stream </summary> | |
protected abstract void WriteToOutput(char[] chars, int offset, int len); | |
/// <summary> Sets the output formatting to use Environment.NewLine with 4-character indentions </summary> | |
public JsonFormatWriter Formatted() | |
{ | |
NewLine = FrameworkPortability.NewLine; | |
Indent = " "; | |
Whitespace = " "; | |
return this; | |
} | |
/// <summary> Gets or sets the characters to use for the new-line, default = empty </summary> | |
public string NewLine { get; set; } | |
/// <summary> Gets or sets the text to use for indenting, default = empty </summary> | |
public string Indent { get; set; } | |
/// <summary> Gets or sets the whitespace to use to separate the text, default = empty </summary> | |
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; | |
} | |
} | |
/// <summary> | |
/// Writes a String value | |
/// </summary> | |
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('"'); | |
} | |
} | |
/// <summary> | |
/// Writes a Double value | |
/// </summary> | |
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); | |
} | |
/// <summary> | |
/// Writes a Single value | |
/// </summary> | |
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); | |
} | |
/// <summary> | |
/// Writes an array of field values | |
/// </summary> | |
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("]"); | |
} | |
/// <summary> | |
/// Writes a message | |
/// </summary> | |
protected override void WriteMessageOrGroup(string field, IMessageLite message) | |
{ | |
WriteName(field); | |
WriteMessage(message); | |
} | |
/// <summary> | |
/// Writes the message to the the formatted stream. | |
/// </summary> | |
public override void WriteMessage(IMessageLite message) | |
{ | |
WriteMessageStart(); | |
message.WriteTo(this); | |
WriteMessageEnd(); | |
} | |
/// <summary> | |
/// 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(). | |
/// </summary> | |
public override void WriteMessageStart() | |
{ | |
if (_isArray) | |
{ | |
Seperator(); | |
} | |
WriteToOutput("{"); | |
_counter.Add(0); | |
} | |
/// <summary> | |
/// Used to complete a root-message previously started with a call to WriteMessageStart() | |
/// </summary> | |
public override void WriteMessageEnd() | |
{ | |
_counter.RemoveAt(_counter.Count - 1); | |
WriteLine("}"); | |
Flush(); | |
} | |
/// <summary> | |
/// Used in streaming arrays of objects to the writer | |
/// </summary> | |
/// <example> | |
/// <code> | |
/// using(writer.StartArray()) | |
/// foreach(IMessageLite m in messages) | |
/// writer.WriteMessage(m); | |
/// </code> | |
/// </example> | |
public sealed class JsonArray : IDisposable | |
{ | |
private JsonFormatWriter _writer; | |
internal JsonArray(JsonFormatWriter writer) | |
{ | |
_writer = writer; | |
_writer.WriteToOutput("["); | |
_writer._counter.Add(0); | |
} | |
/// <summary> | |
/// Causes the end of the array character to be written. | |
/// </summary> | |
private void EndArray() | |
{ | |
if (_writer != null) | |
{ | |
_writer._counter.RemoveAt(_writer._counter.Count - 1); | |
_writer.WriteLine("]"); | |
_writer.Flush(); | |
} | |
_writer = null; | |
} | |
void IDisposable.Dispose() | |
{ | |
EndArray(); | |
} | |
} | |
/// <summary> | |
/// Used to write an array of messages as the output rather than a single message. | |
/// </summary> | |
/// <example> | |
/// <code> | |
/// using(writer.StartArray()) | |
/// foreach(IMessageLite m in messages) | |
/// writer.WriteMessage(m); | |
/// </code> | |
/// </example> | |
public JsonArray StartArray() | |
{ | |
if (_isArray) | |
{ | |
Seperator(); | |
} | |
_isArray = true; | |
return new JsonArray(this); | |
} | |
} | |
} |