blob: 15e0424ed2771c85cf9de6d311668720d0bdb773 [file] [log] [blame]
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);
}
}
}