blob: c159a013e8c66a685b00967216f61f114f250591 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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
*
* https://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 Avro.IO.Parsing;
using System.Collections;
using System.IO;
using System.Text;
using Newtonsoft.Json;
namespace Avro.IO
{
/// <summary>
/// An <see cref="Encoder"/> for Avro's JSON data encoding.
///
/// JsonEncoder buffers output, and data may not appear on the output until
/// <see cref="Encoder.Flush()"/> is called.
///
/// JsonEncoder is not thread-safe.
/// </summary>
public class JsonEncoder : ParsingEncoder, Parser.IActionHandler
{
private readonly Parser parser;
private JsonWriter writer;
private bool includeNamespace = true;
// Has anything been written into the collections?
private readonly BitArray isEmpty = new BitArray(64);
/// <summary>
/// Initializes a new instance of the <see cref="JsonEncoder"/> class.
/// </summary>
public JsonEncoder(Schema sc, Stream stream) : this(sc, GetJsonWriter(stream, false))
{
}
/// <summary>
/// Initializes a new instance of the <see cref="JsonEncoder"/> class.
/// </summary>
public JsonEncoder(Schema sc, Stream stream, bool pretty) : this(sc, GetJsonWriter(stream, pretty))
{
}
/// <summary>
/// Initializes a new instance of the <see cref="JsonEncoder"/> class.
/// </summary>
public JsonEncoder(Schema sc, JsonWriter writer)
{
Configure(writer);
parser = new Parser((new JsonGrammarGenerator()).Generate(sc), this);
}
/// <inheritdoc />
public override void Flush()
{
parser.ProcessImplicitActions();
if (writer != null)
{
writer.Flush();
}
}
// by default, one object per line.
// with pretty option use default pretty printer with root line separator.
private static JsonWriter GetJsonWriter(Stream stream, bool pretty)
{
JsonWriter writer = new JsonTextWriter(new StreamWriter(stream));
if (pretty)
{
writer.Formatting = Formatting.Indented;
}
return writer;
}
/// <summary>
/// Whether to include a union label when generating JSON.
/// </summary>
public virtual bool IncludeNamespace
{
get { return includeNamespace; }
set { includeNamespace = value; }
}
/// <summary>
/// Reconfigures this JsonEncoder to use the output stream provided.
/// Otherwise, this JsonEncoder will flush its current output and then
/// reconfigure its output to use a default UTF8 JsonWriter that writes to the
/// provided Stream.
/// </summary>
/// <param name="stream"> The Stream to direct output to. Cannot be null. </param>
public void Configure(Stream stream)
{
Configure(GetJsonWriter(stream, false));
}
/// <summary>
/// Reconfigures this JsonEncoder to output to the JsonWriter provided.
/// Otherwise, this JsonEncoder will flush its current output and then
/// reconfigure its output to use the provided JsonWriter.
/// </summary>
/// <param name="jsonWriter"> The JsonWriter to direct output to. Cannot be null. </param>
public void Configure(JsonWriter jsonWriter)
{
if (null != parser)
{
Flush();
}
writer = jsonWriter;
}
/// <inheritdoc />
public override void WriteNull()
{
parser.Advance(Symbol.Null);
writer.WriteNull();
}
/// <inheritdoc />
public override void WriteBoolean(bool b)
{
parser.Advance(Symbol.Boolean);
writer.WriteValue(b);
}
/// <inheritdoc />
public override void WriteInt(int n)
{
parser.Advance(Symbol.Int);
writer.WriteValue(n);
}
/// <inheritdoc />
public override void WriteLong(long n)
{
parser.Advance(Symbol.Long);
writer.WriteValue(n);
}
/// <inheritdoc />
public override void WriteFloat(float f)
{
parser.Advance(Symbol.Float);
writer.WriteValue(f);
}
/// <inheritdoc />
public override void WriteDouble(double d)
{
parser.Advance(Symbol.Double);
writer.WriteValue(d);
}
/// <inheritdoc />
public override void WriteString(string str)
{
parser.Advance(Symbol.String);
if (parser.TopSymbol() == Symbol.MapKeyMarker)
{
parser.Advance(Symbol.MapKeyMarker);
writer.WritePropertyName(str);
}
else
{
writer.WriteValue(str);
}
}
/// <inheritdoc />
public override void WriteBytes(byte[] bytes)
{
WriteBytes(bytes, 0, bytes.Length);
}
/// <inheritdoc />
public override void WriteBytes(byte[] bytes, int start, int len)
{
parser.Advance(Symbol.Bytes);
WriteByteArray(bytes, start, len);
}
private void WriteByteArray(byte[] bytes, int start, int len)
{
Encoding iso = Encoding.GetEncoding("ISO-8859-1");
writer.WriteValue(iso.GetString(bytes, start, len));
}
/// <inheritdoc />
public override void WriteFixed(byte[] bytes)
{
WriteFixed(bytes, 0, bytes.Length);
}
/// <inheritdoc />
public override void WriteFixed(byte[] bytes, int start, int len)
{
parser.Advance(Symbol.Fixed);
Symbol.IntCheckAction top = (Symbol.IntCheckAction)parser.PopSymbol();
if (len != top.Size)
{
throw new AvroTypeException("Incorrect length for fixed binary: expected " + top.Size +
" but received " + len + " bytes.");
}
WriteByteArray(bytes, start, len);
}
/// <inheritdoc />
public override void WriteEnum(int e)
{
parser.Advance(Symbol.Enum);
Symbol.EnumLabelsAction top = (Symbol.EnumLabelsAction)parser.PopSymbol();
if (e < 0 || e >= top.Size)
{
throw new AvroTypeException("Enumeration out of range: max is " + top.Size + " but received " + e);
}
writer.WriteValue(top.GetLabel(e));
}
/// <inheritdoc />
public override void WriteArrayStart()
{
parser.Advance(Symbol.ArrayStart);
writer.WriteStartArray();
Push();
if (Depth() >= isEmpty.Length)
{
isEmpty.Length += isEmpty.Length;
}
isEmpty.Set(Depth(), true);
}
/// <inheritdoc />
public override void WriteArrayEnd()
{
if (!isEmpty.Get(Pos))
{
parser.Advance(Symbol.ItemEnd);
}
Pop();
parser.Advance(Symbol.ArrayEnd);
writer.WriteEndArray();
}
/// <inheritdoc />
public override void WriteMapStart()
{
Push();
if (Depth() >= isEmpty.Length)
{
isEmpty.Length += isEmpty.Length;
}
isEmpty.Set(Depth(), true);
parser.Advance(Symbol.MapStart);
writer.WriteStartObject();
}
/// <inheritdoc />
public override void WriteMapEnd()
{
if (!isEmpty.Get(Pos))
{
parser.Advance(Symbol.ItemEnd);
}
Pop();
parser.Advance(Symbol.MapEnd);
writer.WriteEndObject();
}
/// <inheritdoc />
public override void StartItem()
{
if (!isEmpty.Get(Pos))
{
parser.Advance(Symbol.ItemEnd);
}
base.StartItem();
if (Depth() >= isEmpty.Length)
{
isEmpty.Length += isEmpty.Length;
}
isEmpty.Set(Depth(), false);
}
/// <inheritdoc />
public override void WriteUnionIndex(int unionIndex)
{
parser.Advance(Symbol.Union);
Symbol.Alternative top = (Symbol.Alternative)parser.PopSymbol();
Symbol symbol = top.GetSymbol(unionIndex);
if (symbol != Symbol.Null && includeNamespace)
{
writer.WriteStartObject();
writer.WritePropertyName(top.GetLabel(unionIndex));
parser.PushSymbol(Symbol.UnionEnd);
}
parser.PushSymbol(symbol);
}
/// <summary>
/// Perform an action based on the given input.
/// </summary>
public virtual Symbol DoAction(Symbol input, Symbol top)
{
if (top is Symbol.FieldAdjustAction)
{
Symbol.FieldAdjustAction fa = (Symbol.FieldAdjustAction)top;
writer.WritePropertyName(fa.FName);
}
else if (top == Symbol.RecordStart)
{
writer.WriteStartObject();
}
else if (top == Symbol.RecordEnd || top == Symbol.UnionEnd)
{
writer.WriteEndObject();
}
else if (top != Symbol.FieldEnd)
{
throw new AvroTypeException("Unknown action symbol " + top);
}
return null;
}
}
}