blob: 4e03ce0009243bc145dda2b2c5f941a75f07b12d [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 System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
namespace Avro.Generic
{
/// <summary>
/// The default type used by GenericReader and GenericWriter for RecordSchema.
/// </summary>
public class GenericRecord : IEquatable<GenericRecord>
{
/// <summary>
/// Schema for this record.
/// </summary>
public RecordSchema Schema { get; private set; }
private readonly object[] contents;
/// <summary>
/// Initializes a new instance of the <see cref="GenericRecord"/> class.
/// </summary>
/// <param name="schema">Schema for this record.</param>
public GenericRecord(RecordSchema schema)
{
this.Schema = schema;
contents = new object[schema.Fields.Count];
}
/// <summary>
/// Returns the value of the field with the given name.
/// </summary>
/// <param name="fieldName">Name of the field.</param>
/// <returns>Value of the field with the given name.</returns>
/// <exception cref="ArgumentNullException">
/// <paramref name="fieldName"/> is null.
/// </exception>
/// <exception cref="KeyNotFoundException">
/// <paramref name="fieldName"/> does not exist in this record.
/// </exception>
public object this[string fieldName]
{
get
{
if (Schema.TryGetField(fieldName, out Field field))
{
return contents[field.Pos];
}
throw new KeyNotFoundException("Key name: " + fieldName);
}
}
/// <summary>
/// Sets the value for a field. You may call this method multiple times with the same
/// field name to change its value. The given field name must exist in the schema.
/// </summary>
/// <param name="fieldName">Name of the field.</param>
/// <param name="fieldValue">Value of the field.</param>
/// <exception cref="AvroException">
/// <paramref name="fieldName"/> does not exist in this record.
/// </exception>
public void Add(string fieldName, object fieldValue)
{
if (Schema.TryGetField(fieldName, out Field field))
{
// TODO: Use a matcher to verify that object has the right type for the field.
//contents.Add(fieldName, fieldValue);
contents[field.Pos] = fieldValue;
return;
}
throw new AvroException("No such field: " + fieldName);
}
/// <summary>
/// Gets the value the specified field name.
/// </summary>
/// <param name="fieldName">Name of the field.</param>
/// <param name="result">
/// When this method returns true, contains the value of the specified field;
/// otherwise, null.
/// </param>
/// <returns>
/// True if the field was found in the record. This method will only return true if
/// <see cref="Add(string, object)"/> has been called for the given field name.
/// </returns>
public bool TryGetValue(string fieldName, out object result)
{
if (!Schema.TryGetField(fieldName, out Field field))
{
result = null;
return false;
}
result = contents[field.Pos];
return true;
}
/// <summary>
/// Returns the value of the field with the given position.
/// </summary>
/// <param name="fieldPos">The position of the field.</param>
/// <returns>Value of the field with the given position.</returns>
/// <exception cref="IndexOutOfRangeException">
/// <paramref name="fieldPos"/>
/// </exception>
public object GetValue(int fieldPos)
{
return contents[fieldPos];
}
/// <summary>
/// Adds the value in the specified field position.
/// </summary>
/// <param name="fieldPos">Position of the field.</param>
/// <param name="fieldValue">The value to add.</param>
/// <exception cref="IndexOutOfRangeException">
/// <paramref name="fieldPos"/>.
/// </exception>
public void Add(int fieldPos, object fieldValue) => contents[fieldPos] = fieldValue;
/// <summary>
/// Gets the value in the specified field position.
/// </summary>
/// <param name="fieldPos">Position of the field.</param>
/// <param name="result">
/// When this method returns true, contains the value of the specified field;
/// otherwise, null.
/// </param>
/// <returns>
/// True if the field position is valid.
/// </returns>
public bool TryGetValue(int fieldPos, out object result)
{
if (fieldPos < contents.Length)
{
result = contents[fieldPos];
return true;
}
result = null;
return false;
}
/// <inheritdoc/>
public override bool Equals(object obj)
{
if (this == obj) return true;
return obj is GenericRecord
&& Equals((GenericRecord)obj);
}
/// <inheritdoc/>
public bool Equals(GenericRecord other)
{
return Schema.Equals(other.Schema)
&& arraysEqual(contents, other.contents);
}
private static bool mapsEqual(IDictionary d1, IDictionary d2)
{
if (d1.Count != d2.Count) return false;
foreach (DictionaryEntry kv in d1)
{
if (!d2.Contains(kv.Key))
return false;
if (!objectsEqual(d2[kv.Key], kv.Value))
return false;
}
return true;
}
private static bool objectsEqual(object o1, object o2)
{
if (o1 == null) return o2 == null;
if (o2 == null) return false;
if (o1 is Array)
{
if (!(o2 is Array)) return false;
return arraysEqual((Array)o1 , (Array)o2);
}
if (o1 is IDictionary)
{
if (!(o2 is IDictionary)) return false;
return mapsEqual((IDictionary)o1, (IDictionary)o2);
}
return o1.Equals(o2);
}
private static bool arraysEqual(Array a1, Array a2)
{
if (a1.Length != a2.Length) return false;
for (int i = 0; i < a1.Length; i++)
{
if (!objectsEqual(a1.GetValue(i), a2.GetValue(i))) return false;
}
return true;
}
/// <inheritdoc/>
public override int GetHashCode()
{
return 31 * contents.GetHashCode()/* + 29 * Schema.GetHashCode()*/;
}
/// <inheritdoc/>
public override string ToString()
{
StringBuilder sb = new StringBuilder();
sb.Append("Schema: ");
sb.Append(Schema);
sb.Append(", contents: ");
sb.Append("{ ");
foreach (var field in Schema.Fields)
{
sb.Append(field.Name);
sb.Append(": ");
sb.Append(contents[field.Pos]);
sb.Append(", ");
}
sb.Append('}');
return sb.ToString();
}
}
}