/*
* 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 System;
using System.Collections.Generic;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json;
namespace Avro
{
///
/// Class for fields defined in a record
///
public class Field
{
///
/// Enum for the sorting order of record fields
///
public enum SortOrder
{
///
/// Ascending order.
///
ascending,
///
/// Descending order.
///
descending,
///
/// Ignore sort order.
///
ignore
}
///
/// Name of the field.
///
public readonly string Name;
///
/// List of aliases for the field name
///
[Obsolete("Use Aliases instead. This will be removed from the public API in a future version.")]
public readonly IList aliases;
#pragma warning disable CS0618 // Type or member is obsolete
///
/// List of aliases for the field name.
///
public IList Aliases => aliases;
#pragma warning restore CS0618 // Type or member is obsolete
///
/// Position of the field within its record.
///
public int Pos { get; private set; }
///
/// Documentation for the field, if any. Null if there is no documentation.
///
public string Documentation { get; private set; }
///
/// The default value for the field stored as JSON object, if defined. Otherwise, null.
///
public JToken DefaultValue { get; private set; }
///
/// Order of the field
///
public SortOrder? Ordering { get; private set; }
///
/// Field type's schema
///
public Schema Schema { get; private set; }
///
/// 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.
///
private readonly PropertyMap Props;
///
/// Static comparer object for JSON objects such as the fields default value
///
internal static JTokenEqualityComparer JtokenEqual = new JTokenEqualityComparer();
///
/// 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
///
///
/// Constructor for the field class
///
/// schema for the field type
/// name of the field
/// list of aliases for the name of the field
/// position of the field
/// documentation for the field
/// field's default value if it exists
/// sort order of the field
/// dictionary that provides access to custom properties
internal Field(Schema schema, string name, IList aliases, int pos, string doc,
JToken defaultValue, SortOrder sortorder, PropertyMap props)
{
if (string.IsNullOrEmpty(name)) throw new ArgumentNullException(nameof(name), "name cannot be null.");
if (null == schema) throw new ArgumentNullException("type", "type cannot be null.");
this.Schema = schema;
this.Name = name;
#pragma warning disable CS0618 // Type or member is obsolete
this.aliases = aliases;
#pragma warning restore CS0618 // Type or member is obsolete
this.Pos = pos;
this.Documentation = doc;
this.DefaultValue = defaultValue;
this.Ordering = sortorder;
this.Props = props;
}
///
/// Writes the Field class in JSON format
///
/// JSON writer
/// list of named schemas already written
/// enclosing namespace for the field
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();
}
///
/// Parses the 'aliases' property from the given JSON token
///
/// JSON object to read
/// List of string that represents the list of alias. If no 'aliases' specified, then it returns null.
internal static IList 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 at '{jtok.Path}'");
var aliases = new List();
foreach (JToken jalias in jaliases)
{
if (jalias.Type != JTokenType.String)
throw new SchemaParseException($"Aliases must be of format JSON array of strings at '{jtok.Path}'");
aliases.Add((string)jalias);
}
return aliases;
}
///
/// Returns the field's custom property value given the property name
///
/// custom property name
/// custom property value
public string GetProperty(string key)
{
if (null == this.Props) return null;
string v;
return this.Props.TryGetValue(key, out v) ? v : null;
}
///
/// Compares two field objects
///
/// field to compare with this field
/// true if two fields are equal, false otherwise
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;
}
///
/// Compares two objects
///
/// first object
/// second object
/// true if two objects are equal, false otherwise
private static bool areEqual(object o1, object o2)
{
return o1 == null ? o2 == null : o1.Equals(o2);
}
///
/// Hash code function
///
///
public override int GetHashCode()
{
#pragma warning disable CA1307 // Specify StringComparison
return 17 * Name.GetHashCode() + Pos + 19 * getHashCode(Documentation) +
#pragma warning restore CA1307 // Specify StringComparison
23 * getHashCode(Ordering) + 29 * getHashCode(DefaultValue) + 31 * Schema.GetHashCode() +
37 * getHashCode(Props);
}
///
/// Hash code helper function
///
///
///
private static int getHashCode(object obj)
{
return obj == null ? 0 : obj.GetHashCode();
}
}
}