blob: 740c44d8fc2f0c439dc926e5ddb980310b0e4b64 [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.Text;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json;
namespace Avro
{
/// <summary>
/// Class for fields defined in a record
/// </summary>
public class Field
{
/// <summary>
/// Enum for the sorting order of record fields
/// </summary>
public enum SortOrder
{
ascending,
descending,
ignore
}
/// <summary>
/// Name of the field.
/// </summary>
public readonly string Name;
/// <summary>
/// List of aliases for the field name
/// </summary>
public readonly IList<string> aliases;
/// <summary>
/// Position of the field within its record.
/// </summary>
public int Pos { get; private set; }
/// <summary>
/// Documentation for the field, if any. Null if there is no documentation.
/// </summary>
public string Documentation { get; private set; }
/// <summary>
/// The default value for the field stored as JSON object, if defined. Otherwise, null.
/// </summary>
public JToken DefaultValue { get; private set; }
/// <summary>
/// Order of the field
/// </summary>
public SortOrder? Ordering { get; private set; }
/// <summary>
/// Field type's schema
/// </summary>
public Schema Schema { get; private set; }
/// <summary>
/// Custom properties for the field. We don't store the fields custom properties in
/// the field type's schema because if the field type is only a reference to the schema
/// instead of an actual schema definition, then the schema could already have it's own set
/// of custom properties when it was previously defined.
/// </summary>
private readonly PropertyMap Props;
/// <summary>
/// Static comparer object for JSON objects such as the fields default value
/// </summary>
internal static JTokenEqualityComparer JtokenEqual = new JTokenEqualityComparer();
/// <summary>
/// A flag to indicate if reader schema has a field that is missing from writer schema and has a default value
/// This is set in CanRead() which is always be called before deserializing data
/// </summary>
/// <summary>
/// Constructor for the field class
/// </summary>
/// <param name="schema">schema for the field type</param>
/// <param name="name">name of the field</param>
/// <param name="aliases">list of aliases for the name of the field</param>
/// <param name="pos">position of the field</param>
/// <param name="doc">documentation for the field</param>
/// <param name="defaultValue">field's default value if it exists</param>
/// <param name="sortorder">sort order of the field</param>
internal Field(Schema schema, string name, IList<string> aliases, int pos, string doc,
JToken defaultValue, SortOrder sortorder, PropertyMap props)
{
if (string.IsNullOrEmpty(name)) throw new ArgumentNullException("name", "name cannot be null.");
if (null == schema) throw new ArgumentNullException("type", "type cannot be null.");
this.Schema = schema;
this.Name = name;
this.aliases = aliases;
this.Pos = pos;
this.Documentation = doc;
this.DefaultValue = defaultValue;
this.Ordering = sortorder;
this.Props = props;
}
/// <summary>
/// Writes the Field class in JSON format
/// </summary>
/// <param name="writer">JSON writer</param>
/// <param name="names">list of named schemas already written</param>
/// <param name="encspace">enclosing namespace for the field</param>
protected internal void writeJson(JsonTextWriter writer, SchemaNames names, string encspace)
{
writer.WriteStartObject();
JsonHelper.writeIfNotNullOrEmpty(writer, "name", this.Name);
JsonHelper.writeIfNotNullOrEmpty(writer, "doc", this.Documentation);
if (null != this.DefaultValue)
{
writer.WritePropertyName("default");
this.DefaultValue.WriteTo(writer, null);
}
if (null != this.Schema)
{
writer.WritePropertyName("type");
Schema.WriteJson(writer, names, encspace);
}
if (null != this.Props)
this.Props.WriteJson(writer);
if (null != aliases)
{
writer.WritePropertyName("aliases");
writer.WriteStartArray();
foreach (string name in aliases)
writer.WriteValue(name);
writer.WriteEndArray();
}
writer.WriteEndObject();
}
/// <summary>
/// Parses the 'aliases' property from the given JSON token
/// </summary>
/// <param name="jtok">JSON object to read</param>
/// <returns>List of string that represents the list of alias. If no 'aliases' specified, then it returns null.</returns>
internal static IList<string> GetAliases(JToken jtok)
{
JToken jaliases = jtok["aliases"];
if (null == jaliases)
return null;
if (jaliases.Type != JTokenType.Array)
throw new SchemaParseException("Aliases must be of format JSON array of strings");
var aliases = new List<string>();
foreach (JToken jalias in jaliases)
{
if (jalias.Type != JTokenType.String)
throw new SchemaParseException("Aliases must be of format JSON array of strings");
aliases.Add((string)jalias);
}
return aliases;
}
/// <summary>
/// Returns the field's custom property value given the property name
/// </summary>
/// <param name="key">custom property name</param>
/// <returns>custom property value</returns>
public string GetProperty(string key)
{
if (null == this.Props) return null;
string v;
return (this.Props.TryGetValue(key, out v)) ? v : null;
}
/// <summary>
/// Compares two field objects
/// </summary>
/// <param name="obj">field to compare with this field</param>
/// <returns>true if two fields are equal, false otherwise</returns>
public override bool Equals(object obj)
{
if (obj == this) return true;
if (obj != null && obj is Field)
{
Field that = obj as Field;
return areEqual(that.Name, Name) && that.Pos == Pos && areEqual(that.Documentation, Documentation)
&& areEqual(that.Ordering, Ordering) && JtokenEqual.Equals(that.DefaultValue, DefaultValue)
&& that.Schema.Equals(Schema) && areEqual(that.Props, this.Props);
}
return false;
}
/// <summary>
/// Compares two objects
/// </summary>
/// <param name="o1">first object</param>
/// <param name="o2">second object</param>
/// <returns>true if two objects are equal, false otherwise</returns>
private static bool areEqual(object o1, object o2)
{
return o1 == null ? o2 == null : o1.Equals(o2);
}
/// <summary>
/// Hash code function
/// </summary>
/// <returns></returns>
public override int GetHashCode()
{
return 17 * Name.GetHashCode() + Pos + 19 * getHashCode(Documentation) +
23 * getHashCode(Ordering) + 29 * getHashCode(DefaultValue) + 31 * Schema.GetHashCode() +
37 * getHashCode(Props);
}
/// <summary>
/// Hash code helper function
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
private static int getHashCode(object obj)
{
return obj == null ? 0 : obj.GetHashCode();
}
}
}