Implemented IEquatable interface for tags

Error checking names/types with list types is down performed within the abstract base list itself when added, not during serialization
This commit is contained in:
ForeverZer0 2021-08-23 00:17:06 -04:00
parent 68dd7aa011
commit e728f47790
4 changed files with 126 additions and 9 deletions

View File

@ -21,6 +21,8 @@ namespace SharpNBT
/// <param name="name">The name of the tag, or <see langword="null"/> if tag has no name.</param>
public CompoundTag([CanBeNull] string name) : base(TagType.Compound, name)
{
NamedChildren = true;
RequiredType = null;
}
/// <summary>
@ -28,8 +30,9 @@ namespace SharpNBT
/// </summary>
/// <param name="name">The name of the tag, or <see langword="null"/> if tag has no name.</param>
/// <param name="values">A collection <see cref="Tag"/> objects that are children of this object.</param>
public CompoundTag([CanBeNull] string name, [NotNull] IEnumerable<Tag> values) : base(TagType.Compound, name, values)
public CompoundTag([CanBeNull] string name, [NotNull] IEnumerable<Tag> values) : this(name)
{
AddRange(values);
}
/// <summary>Returns a string that represents the current object.</summary>

View File

@ -27,6 +27,8 @@ namespace SharpNBT
/// <param name="childType">A constant describing the NBT type for children in this tag.</param>
public ListTag([CanBeNull] string name, TagType childType) : base(TagType.List, name)
{
RequiredType = childType;
NamedChildren = false;
ChildType = childType;
}
@ -36,9 +38,8 @@ namespace SharpNBT
/// <param name="name">The name of the tag, or <see langword="null"/> if tag has no name.</param>
/// <param name="childType">A constant describing the NBT type for children in this tag.</param>
/// <param name="children">A collection of values to include in this tag.</param>
public ListTag([CanBeNull] string name, TagType childType, [NotNull][ItemNotNull] IEnumerable<Tag> children) : base(TagType.List, name)
public ListTag([CanBeNull] string name, TagType childType, [NotNull][ItemNotNull] IEnumerable<Tag> children) : this(name, childType)
{
ChildType = childType;
AddRange(children);
}

View File

@ -14,7 +14,7 @@ namespace SharpNBT
/// Abstract base class that all NBT tags inherit from.
/// </summary>
[PublicAPI][DataContract][KnownType("GetKnownTypes")]
public abstract class Tag
public abstract class Tag : IEquatable<Tag>
{
private static IEnumerable<Type> GetKnownTypes()
{
@ -130,6 +130,48 @@ namespace SharpNBT
stream.Flush();
return Encoding.UTF8.GetString(stream.ToArray());
}
/// <summary>Indicates whether the current object is equal to another object of the same type.</summary>
/// <param name="other">An object to compare with this object.</param>
/// <returns>
/// <see langword="true" /> if the current object is equal to the <paramref name="other" /> parameter; otherwise, <see langword="false" />.</returns>
/// <footer><a href="https://docs.microsoft.com/en-us/dotnet/api/System.IEquatable-1.Equals?view=netstandard-2.1">`IEquatable.Equals` on docs.microsoft.com</a></footer>
public bool Equals(Tag other)
{
if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true;
return Type == other.Type && Name == other.Name;
}
/// <summary>Determines whether the specified object is equal to the current object.</summary>
/// <param name="obj">The object to compare with the current object.</param>
/// <returns>
/// <see langword="true" /> if the specified object is equal to the current object; otherwise, <see langword="false" />.</returns>
/// <footer><a href="https://docs.microsoft.com/en-us/dotnet/api/System.Object.Equals?view=netstandard-2.1">`Object.Equals` on docs.microsoft.com</a></footer>
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
if (obj.GetType() != this.GetType()) return false;
return Equals((Tag)obj);
}
/// <summary>Serves as the default hash function.</summary>
/// <returns>A hash code for the current object.</returns>
/// <footer><a href="https://docs.microsoft.com/en-us/dotnet/api/System.Object.GetHashCode?view=netstandard-2.1">`Object.GetHashCode` on docs.microsoft.com</a></footer>
public override int GetHashCode()
{
unchecked
{
// ReSharper disable NonReadonlyMemberInGetHashCode
return ((int)Type * 373) ^ (Name != null ? Name.GetHashCode() : 0);
// ReSharper restore NonReadonlyMemberInGetHashCode
}
}
public static bool operator ==(Tag left, Tag right) => Equals(left, right);
public static bool operator !=(Tag left, Tag right) => !Equals(left, right);
}
/// <summary>
@ -137,7 +179,7 @@ namespace SharpNBT
/// </summary>
/// <typeparam name="T">The type of the value the tag represents.</typeparam>
[PublicAPI][DataContract]
public abstract class Tag<T> : Tag
public abstract class Tag<T> : Tag, IEquatable<Tag<T>>
{
/// <summary>
/// Gets or sets the value of the tag.
@ -163,5 +205,47 @@ namespace SharpNBT
buffer.Append(indent);
buffer.AppendLine(ToString());
}
/// <summary>Indicates whether the current object is equal to another object of the same type.</summary>
/// <param name="other">An object to compare with this object.</param>
/// <returns>
/// <see langword="true" /> if the current object is equal to the <paramref name="other" /> parameter; otherwise, <see langword="false" />.</returns>
/// <footer><a href="https://docs.microsoft.com/en-us/dotnet/api/System.IEquatable-1.Equals?view=netstandard-2.1">`IEquatable.Equals` on docs.microsoft.com</a></footer>
public bool Equals(Tag<T> other)
{
if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true;
return base.Equals(other) && EqualityComparer<T>.Default.Equals(Value, other.Value);
}
/// <summary>Determines whether the specified object is equal to the current object.</summary>
/// <param name="obj">The object to compare with the current object.</param>
/// <returns>
/// <see langword="true" /> if the specified object is equal to the current object; otherwise, <see langword="false" />.</returns>
/// <footer><a href="https://docs.microsoft.com/en-us/dotnet/api/System.Object.Equals?view=netstandard-2.1">`Object.Equals` on docs.microsoft.com</a></footer>
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
if (obj.GetType() != GetType()) return false;
return Equals((Tag<T>)obj);
}
/// <summary>Serves as the default hash function.</summary>
/// <returns>A hash code for the current object.</returns>
/// <footer><a href="https://docs.microsoft.com/en-us/dotnet/api/System.Object.GetHashCode?view=netstandard-2.1">`Object.GetHashCode` on docs.microsoft.com</a></footer>
public override int GetHashCode()
{
unchecked
{
// ReSharper disable NonReadonlyMemberInGetHashCode
return (base.GetHashCode() * 421) ^ EqualityComparer<T>.Default.GetHashCode(Value);
// ReSharper restore NonReadonlyMemberInGetHashCode
}
}
public static bool operator ==(Tag<T> left, Tag<T> right) => Equals(left, right);
public static bool operator !=(Tag<T> left, Tag<T> right) => !Equals(left, right);
}
}

View File

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Runtime.Serialization;
using JetBrains.Annotations;
using SuppressMessageAttribute = System.Diagnostics.CodeAnalysis.SuppressMessageAttribute;
@ -12,6 +13,9 @@ namespace SharpNBT
[PublicAPI][DataContract]
public abstract class TagContainer : EnumerableTag<Tag>
{
protected bool NamedChildren;
protected TagType? RequiredType;
/// <summary>
/// Initializes a new instance of the <see cref="TagContainer"/>.
/// </summary>
@ -38,7 +42,7 @@ namespace SharpNBT
[SuppressMessage("ReSharper", "AnnotationConflictInHierarchy")]
public sealed override void Add(Tag item)
{
base.Add(item ?? throw new ArgumentNullException(nameof(item)));
base.Add(AssertConventions(item));
item.Parent = this;
}
@ -52,7 +56,7 @@ namespace SharpNBT
[SuppressMessage("ReSharper", "AnnotationConflictInHierarchy")]
public sealed override void Insert(int index, Tag item)
{
base.Insert(index, item ?? throw new ArgumentNullException(nameof(item)));
base.Insert(index, AssertConventions(item));
item.Parent = this;
}
@ -68,7 +72,7 @@ namespace SharpNBT
get => base[index];
set
{
base[index] = value ?? throw new ArgumentNullException(nameof(value));
base[index] = AssertConventions(value);
value.Parent = this;
}
}
@ -109,5 +113,30 @@ namespace SharpNBT
this[index].Parent = null;
base.RemoveAt(index);
}
/// <summary>
/// Performs routine checks to ensure that the given <paramref name="tag"/> complies with the NBT standard for this collection type.
/// </summary>
/// <param name="tag">A <see cref="Tag"/> instance to validate.</param>
/// <returns>Returns the <paramref name="tag"/> instance.</returns>
/// <exception cref="ArgumentNullException"></exception>
/// <exception cref="FormatException"></exception>
/// <exception cref="ArrayTypeMismatchException"></exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected Tag AssertConventions([CanBeNull] Tag tag)
{
if (tag is null)
throw new ArgumentNullException(nameof(tag), "Child tag in collection cannot be null");
if (NamedChildren && tag.Name is null)
throw new FormatException("Children of this collection type must be named.");
if (!NamedChildren && tag.Name != null)
throw new FormatException("Children of this collection type cannot be named.");
if (RequiredType.HasValue && RequiredType.Value != tag.Type)
throw new ArrayTypeMismatchException("Incorrect tag type added to this collection.");
return tag;
}
}
}