blob: e9645a5457da58638a4021a033636524a075a7cc [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
*
* http://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 System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace Avro
{
public class Protocol
{
/// <summary>
/// Name of the protocol
/// </summary>
public string Name { get; set; }
/// <summary>
/// Namespace of the protocol
/// </summary>
public string Namespace { get; set; }
/// <summary>
/// Documentation for the protocol
/// </summary>
public string Doc { get; set; }
/// <summary>
/// List of schemas objects representing the different schemas defined under the 'types' attribute
/// </summary>
public IList<Schema> Types { get; set; }
/// <summary>
/// List of message objects representing the different schemas defined under the 'messages' attribute
/// </summary>
public IDictionary<string,Message> Messages { get; set; }
private byte[] md5;
public byte[] MD5
{
get
{
try
{
if (md5 == null)
md5 = System.Security.Cryptography.MD5.Create().ComputeHash(Encoding.UTF8.GetBytes(ToString()));
}
catch (Exception ex)
{
throw new AvroRuntimeException("MD5 get exception", ex);
}
return md5;
}
}
/// <summary>
/// Constructor for Protocol class
/// </summary>
/// <param name="name">required name of protocol</param>
/// <param name="space">optional namespace</param>
/// <param name="doc">optional documentation</param>
/// <param name="types">required list of types</param>
/// <param name="messages">required list of messages</param>
public Protocol(string name, string space,
string doc, IEnumerable<Schema> types,
IDictionary<string,Message> messages)
{
if (string.IsNullOrEmpty(name)) throw new ArgumentNullException("name", "name cannot be null.");
if (null == types) throw new ArgumentNullException("types", "types cannot be null.");
if (null == messages) throw new ArgumentNullException("messages", "messages cannot be null.");
this.Name = name;
this.Namespace = space;
this.Doc = doc;
this.Types = new List<Schema>(types);
this.Messages = new Dictionary<string, Message>(messages);
}
/// <summary>
/// Parses the given JSON string to create a Protocol object
/// </summary>
/// <param name="jstring">JSON string</param>
/// <returns>Protocol object</returns>
public static Protocol Parse(string jstring)
{
if (string.IsNullOrEmpty(jstring)) throw new ArgumentNullException("json", "json cannot be null.");
JToken jtok = null;
try
{
jtok = JObject.Parse(jstring);
}
catch (Exception ex)
{
throw new ProtocolParseException("Invalid JSON format: " + jstring, ex);
}
return Parse(jtok);
}
/// <summary>
/// Parses the given JSON object to create a Protocol object
/// </summary>
/// <param name="jtok">JSON object</param>
/// <returns>Protocol object</returns>
private static Protocol Parse(JToken jtok)
{
string name = JsonHelper.GetRequiredString(jtok, "protocol");
string space = JsonHelper.GetOptionalString(jtok, "namespace");
string doc = JsonHelper.GetOptionalString(jtok, "doc");
var names = new SchemaNames();
JToken jtypes = jtok["types"];
var types = new List<Schema>();
if (jtypes is JArray)
{
foreach (JToken jtype in jtypes)
{
var schema = Schema.ParseJson(jtype, names, space);
types.Add(schema);
}
}
var messages = new Dictionary<string,Message>();
JToken jmessages = jtok["messages"];
if (null != jmessages)
{
foreach (JProperty jmessage in jmessages)
{
var message = Message.Parse(jmessage, names, space);
messages.Add(message.Name, message);
}
}
return new Protocol(name, space, doc, types, messages);
}
/// <summary>
/// Writes Protocol in JSON format
/// </summary>
/// <returns>JSON string</returns>
public override string ToString()
{
using (System.IO.StringWriter sw = new System.IO.StringWriter())
{
using (Newtonsoft.Json.JsonTextWriter writer = new Newtonsoft.Json.JsonTextWriter(sw))
{
#if(DEBUG)
writer.Formatting = Newtonsoft.Json.Formatting.Indented;
#endif
WriteJson(writer, new SchemaNames());
writer.Flush();
return sw.ToString();
}
}
}
/// <summary>
/// Writes Protocol in JSON format
/// </summary>
/// <param name="writer">JSON writer</param>
/// <param name="names">list of named schemas already written</param>
internal void WriteJson(Newtonsoft.Json.JsonTextWriter writer, SchemaNames names)
{
writer.WriteStartObject();
JsonHelper.writeIfNotNullOrEmpty(writer, "protocol", this.Name);
JsonHelper.writeIfNotNullOrEmpty(writer, "namespace", this.Namespace);
JsonHelper.writeIfNotNullOrEmpty(writer, "doc", this.Doc);
writer.WritePropertyName("types");
writer.WriteStartArray();
foreach (Schema type in this.Types)
type.WriteJson(writer, names, this.Namespace);
writer.WriteEndArray();
writer.WritePropertyName("messages");
writer.WriteStartObject();
foreach (KeyValuePair<string,Message> message in this.Messages)
{
writer.WritePropertyName(message.Key);
message.Value.writeJson(writer, names, this.Namespace);
}
writer.WriteEndObject();
writer.WriteEndObject();
}
/// <summary>
/// Tests equality of this protocol object with the passed object
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
public override bool Equals(object obj)
{
if (obj == this) return true;
if (!(obj is Protocol)) return false;
Protocol that = obj as Protocol;
return this.Name.Equals(that.Name) && this.Namespace.Equals(that.Namespace) &&
TypesEquals(that.Types) && MessagesEquals(that.Messages);
}
/// <summary>
/// Test equality of this protocols Types list with the passed Types list.
/// Order of schemas does not matter, as long as all types in this protocol
/// are also defined in the passed protocol
/// </summary>
/// <param name="that"></param>
/// <returns></returns>
private bool TypesEquals(IList<Schema> that)
{
if (Types.Count != that.Count) return false;
foreach (Schema schema in Types)
if (!that.Contains(schema)) return false;
return true;
}
/// <summary>
/// Test equality of this protocols Message map with the passed Message map
/// Order of messages does not matter, as long as all messages in this protocol
/// are also defined in the passed protocol
/// </summary>
/// <param name="that"></param>
/// <returns></returns>
private bool MessagesEquals(IDictionary<string, Message> that)
{
if (Messages.Count != that.Count) return false;
foreach (KeyValuePair<string, Message> pair in Messages)
{
if (!that.ContainsKey(pair.Key))
return false;
if (!pair.Value.Equals(that[pair.Key]))
return false;
}
return true;
}
/// <summary>
/// Returns the hash code of this protocol object
/// </summary>
/// <returns></returns>
public override int GetHashCode()
{
return Name.GetHashCode() + Namespace.GetHashCode() +
GetTypesHashCode() + GetMessagesHashCode();
}
/// <summary>
/// Returns the hash code of the Types list
/// </summary>
/// <returns></returns>
private int GetTypesHashCode()
{
int hash = Types.Count;
foreach (Schema schema in Types)
hash += schema.GetHashCode();
return hash;
}
/// <summary>
/// Returns the hash code of the Messages map
/// </summary>
/// <returns></returns>
private int GetMessagesHashCode()
{
int hash = Messages.Count;
foreach (KeyValuePair<string, Message> pair in Messages)
hash += (pair.Key.GetHashCode() + pair.Value.GetHashCode());
return hash;
}
}
}