Removed unneeded classes, reimplemented ListTag

This commit is contained in:
Eric Freed 2023-08-27 04:37:49 -04:00
parent a4ecca89a8
commit 95af348ddc
8 changed files with 149 additions and 385 deletions

View File

@ -1,3 +1,6 @@
using System;
using System.Linq;
using System.Text;
using Xunit; using Xunit;
using Xunit.Abstractions; using Xunit.Abstractions;
@ -19,5 +22,24 @@ namespace SharpNBT.Tests
{ {
output.WriteLine(tag.ToJsonString(true)); output.WriteLine(tag.ToJsonString(true));
} }
[Fact]
public unsafe void PinTest()
{
const int length = 69;
var ary = Enumerable.Range(420, length);
var intTag = new IntArrayTag("Foobar", ary);
var sb = new StringBuilder();
fixed (int* ptr = &intTag.GetPinnableReference())
{
for (var i = 0; i < length; i++)
{
sb.Append(ptr[i]);
sb.Append(',');
}
}
output.WriteLine(sb.ToString());
}
} }
} }

View File

@ -8,6 +8,8 @@
<LangVersion>latestmajor</LangVersion> <LangVersion>latestmajor</LangVersion>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>

View File

@ -32,6 +32,14 @@ namespace SharpNBT.Tests
output.WriteLine(tag.Stringify(true)); output.WriteLine(tag.Stringify(true));
} }
[Fact]
public void ParseBugged()
{
var testString = "{Count:1b,id:\"minecraft:netherite_sword\",tag:{Damage:0,Enchantments:[{id:\"minecraft:looting\",lvl:3s},{id:\"minecraft:smite\",lvl:5s},{id:\"minecraft:sweeping\",lvl:3s}],RepairCost:7,display:{Name:'{\"extra\":[{\"text\":\"我是修改的名字\"}],\"text\":\"\"}'}}}";
var tag = StringNbt.Parse(testString);
output.WriteLine(tag.PrettyPrinted());
}
[Fact] [Fact]
public void ParseSmall() public void ParseSmall()
{ {

View File

@ -1,4 +1,5 @@
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.IO.Compression; using System.IO.Compression;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -21,7 +22,7 @@ public static class NbtFile
/// <param name="options">Bitwise flags to configure how data should be handled for compatibility between different specifications.</param> /// <param name="options">Bitwise flags to configure how data should be handled for compatibility between different specifications.</param>
/// <typeparam name="T">The type of tag to deserialize.</typeparam> /// <typeparam name="T">The type of tag to deserialize.</typeparam>
/// <returns>The deserialized <see cref="Tag"/> instance.</returns> /// <returns>The deserialized <see cref="Tag"/> instance.</returns>
public static T Read<T>(string path, FormatOptions options, CompressionType compression = CompressionType.AutoDetect) where T : TagContainer public static T Read<T>(string path, FormatOptions options, CompressionType compression = CompressionType.AutoDetect) where T : Tag, ICollection<Tag>
{ {
using var reader = new TagReader(GetReadStream(path, compression), options); using var reader = new TagReader(GetReadStream(path, compression), options);
return reader.ReadTag<T>(); return reader.ReadTag<T>();
@ -47,7 +48,7 @@ public static class NbtFile
/// <param name="compression">Indicates the compression algorithm used to compress the file.</param> /// <param name="compression">Indicates the compression algorithm used to compress the file.</param>
/// <param name="options">Bitwise flags to configure how data should be handled for compatibility between different specifications.</param> /// <param name="options">Bitwise flags to configure how data should be handled for compatibility between different specifications.</param>
/// <returns>The deserialized <see cref="Tag"/> instance.</returns> /// <returns>The deserialized <see cref="Tag"/> instance.</returns>
public static async Task<T> ReadAsync<T>(string path, FormatOptions options, CompressionType compression = CompressionType.AutoDetect) where T : TagContainer public static async Task<T> ReadAsync<T>(string path, FormatOptions options, CompressionType compression = CompressionType.AutoDetect) where T : Tag, ICollection<Tag>
{ {
await using var reader = new TagReader(GetReadStream(path, compression), options); await using var reader = new TagReader(GetReadStream(path, compression), options);
return await reader.ReadTagAsync<T>(); return await reader.ReadTagAsync<T>();

View File

@ -1,203 +0,0 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.Serialization;
using System.Text;
using JetBrains.Annotations;
using SuppressMessageAttribute = System.Diagnostics.CodeAnalysis.SuppressMessageAttribute;
namespace SharpNBT;
/// <summary>
/// Base class for tags that contain a collection of values and can be enumerated.
/// </summary>
/// <typeparam name="T">The type of the item the tag contains.</typeparam>
[PublicAPI][Serializable]
public abstract class EnumerableTag<T> : Tag, IList<T>
{
/// <summary>
/// Internal list implementation.
/// </summary>
[ItemNotNull]
private readonly List<T> internalList = new List<T>();
/// <summary>
/// Initializes a new instance of the <see cref="EnumerableTag{T}"/>.
/// </summary>
/// <param name="type">A constant describing the NBT type for this tag.</param>
/// <param name="name">The name of the tag, or <see langword="null"/> if tag has no name.</param>
protected EnumerableTag(TagType type, string? name) : base(type, name)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="EnumerableTag{T}"/> with the specified <paramref name="values"/>.
/// </summary>
/// <param name="type">A constant describing the NBT type for this tag.</param>
/// <param name="name">The name of the tag, or <see langword="null"/> if tag has no name.</param>
/// <param name="values">A collection of values to include in this tag.</param>
protected EnumerableTag(TagType type, string? name, T[] values) : base(type, name)
{
internalList.AddRange(values);
}
/// <summary>
/// Initializes a new instance of the <see cref="EnumerableTag{T}"/> with the specified <paramref name="values"/>.
/// </summary>
/// <param name="type">A constant describing the NBT type for this tag.</param>
/// <param name="name">The name of the tag, or <see langword="null"/> if tag has no name.</param>
/// <param name="values">A collection of values to include in this tag.</param>
protected EnumerableTag(TagType type, string? name, IEnumerable<T> values) : base(type, name)
{
internalList.AddRange(values);
}
/// <summary>
/// Initializes a new instance of the <see cref="EnumerableTag{T}"/> with the specified <paramref name="values"/>.
/// </summary>
/// <param name="type">A constant describing the NBT type for this tag.</param>
/// <param name="name">The name of the tag, or <see langword="null"/> if tag has no name.</param>
/// <param name="values">A collection of values to include in this tag.</param>
protected EnumerableTag(TagType type, string? name, ReadOnlySpan<T> values) : base(type, name)
{
internalList.AddRange(values.ToArray());
}
/// <summary>
/// Required constructor for ISerializable implementation.
/// </summary>
/// <param name="info">The <see cref="T:System.Runtime.Serialization.SerializationInfo" /> to describing this instance.</param>
/// <param name="context">The destination (see <see cref="T:System.Runtime.Serialization.StreamingContext" />) for this serialization.</param>
protected EnumerableTag(SerializationInfo info, StreamingContext context) : base(info, context)
{
var dummy = info.GetInt32("count");
var obj = info.GetValue("values", typeof(T[])) as T[];
if (obj is IEnumerable<T> e)
internalList.AddRange(e);
}
/// <summary>Populates a <see cref="T:System.Runtime.Serialization.SerializationInfo" /> with the data needed to serialize the target object.</summary>
/// <param name="info">The <see cref="T:System.Runtime.Serialization.SerializationInfo" /> to populate with data.</param>
/// <param name="context">The destination (see <see cref="T:System.Runtime.Serialization.StreamingContext" />) for this serialization.</param>
/// <exception cref="T:System.Security.SecurityException">The caller does not have the required permission.</exception>
public override void GetObjectData(SerializationInfo info, StreamingContext context)
{
base.GetObjectData(info, context);
info.AddValue("count", Count);
info.AddValue("values", internalList.ToArray(), typeof(T[]));
}
/// <summary>Returns an enumerator that iterates through the collection.</summary>
/// <returns>An enumerator that can be used to iterate through the collection.</returns>
/// <footer><a href="https://docs.microsoft.com/en-us/dotnet/api/System.Collections.Generic.IEnumerable-1.GetEnumerator?view=netcore-5.0">`IEnumerable.GetEnumerator` on docs.microsoft.com</a></footer>
public IEnumerator<T> GetEnumerator() => internalList.GetEnumerator();
/// <summary>Returns an enumerator that iterates through a collection.</summary>
/// <returns>An <see cref="T:System.Collections.IEnumerator" /> object that can be used to iterate through the collection.</returns>
/// <footer><a href="https://docs.microsoft.com/en-us/dotnet/api/System.Collections.IEnumerable.GetEnumerator?view=netcore-5.0">`IEnumerable.GetEnumerator` on docs.microsoft.com</a></footer>
IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)internalList).GetEnumerator();
/// <summary>Adds an item to the <see cref="T:System.Collections.Generic.ICollection`1" />.</summary>
/// <param name="item">The object to add to the <see cref="T:System.Collections.Generic.ICollection`1" />.</param>
/// <exception cref="T:System.NotSupportedException">The <see cref="T:System.Collections.Generic.ICollection`1" /> is read-only.</exception>
/// <footer><a href="https://docs.microsoft.com/en-us/dotnet/api/System.Collections.Generic.ICollection-1.Add?view=netcore-5.0">`ICollection.Add` on docs.microsoft.com</a></footer>
[SuppressMessage("ReSharper", "AnnotationConflictInHierarchy")]
public virtual void Add(T item) => internalList.Add(item);
/// <summary>
/// Adds the elements of the specified collection to the <see cref="EnumerableTag{T}"/>.
/// </summary>
/// <param name="items">A collection containing the items to add.</param>
public void AddRange([ItemNotNull] IEnumerable<T> items)
{
foreach (var item in items)
Add(item!);
}
/// <summary>Inserts an item to the <see cref="T:System.Collections.Generic.IList`1" /> at the specified index.</summary>
/// <param name="index">The zero-based index at which <paramref name="item" /> should be inserted.</param>
/// <param name="item">The object to insert into the <see cref="T:System.Collections.Generic.IList`1" />.</param>
/// <exception cref="T:System.ArgumentOutOfRangeException">
/// <paramref name="index" /> is not a valid index in the <see cref="T:System.Collections.Generic.IList`1" />.</exception>
/// <exception cref="T:System.NotSupportedException">The <see cref="T:System.Collections.Generic.IList`1" /> is read-only.</exception>
/// <footer><a href="https://docs.microsoft.com/en-us/dotnet/api/System.Collections.Generic.IList-1.Insert?view=netcore-5.0">`IList.Insert` on docs.microsoft.com</a></footer>
[SuppressMessage("ReSharper", "AnnotationConflictInHierarchy")]
public virtual void Insert(int index, T item) => internalList.Insert(index, item);
/// <summary>Gets or sets the element at the specified index.</summary>
/// <param name="index">The zero-based index of the element to get or set.</param>
/// <exception cref="T:System.ArgumentOutOfRangeException">
/// <paramref name="index" /> is not a valid index in the <see cref="T:System.Collections.Generic.IList`1" />.</exception>
/// <exception cref="T:System.NotSupportedException">The property is set and the <see cref="T:System.Collections.Generic.IList`1" /> is read-only.</exception>
/// <returns>The element at the specified index.</returns>
/// <footer><a href="https://docs.microsoft.com/en-us/dotnet/api/System.Collections.Generic.IList-1.Item?view=netcore-5.0">`IList.Item` on docs.microsoft.com</a></footer>
public virtual T this[int index]
{
get => internalList[index];
set => internalList[index] = value;
}
/// <summary>Removes all items from the <see cref="T:System.Collections.Generic.ICollection`1" />.</summary>
/// <exception cref="T:System.NotSupportedException">The <see cref="T:System.Collections.Generic.ICollection`1" /> is read-only.</exception>
/// <footer><a href="https://docs.microsoft.com/en-us/dotnet/api/System.Collections.Generic.ICollection-1.Clear?view=netcore-5.0">`ICollection.Clear` on docs.microsoft.com</a></footer>
public virtual void Clear() => internalList.Clear();
/// <summary>Determines whether the <see cref="T:System.Collections.Generic.ICollection`1" /> contains a specific value.</summary>
/// <param name="item">The object to locate in the <see cref="T:System.Collections.Generic.ICollection`1" />.</param>
/// <returns>
/// <see langword="true" /> if <paramref name="item" /> is found in the <see cref="T:System.Collections.Generic.ICollection`1" />; otherwise, <see langword="false" />.</returns>
/// <footer><a href="https://docs.microsoft.com/en-us/dotnet/api/System.Collections.Generic.ICollection-1.Contains?view=netcore-5.0">`ICollection.Contains` on docs.microsoft.com</a></footer>
public bool Contains(T item) => internalList.Contains(item);
/// <summary>Copies the elements of the <see cref="T:System.Collections.Generic.ICollection`1" /> to an <see cref="T:System.Array" />, starting at a particular <see cref="T:System.Array" /> index.</summary>
/// <param name="array">The one-dimensional <see cref="T:System.Array" /> that is the destination of the elements copied from <see cref="T:System.Collections.Generic.ICollection`1" />. The <see cref="T:System.Array" /> must have zero-based indexing.</param>
/// <param name="arrayIndex">The zero-based index in <paramref name="array" /> at which copying begins.</param>
/// <exception cref="T:System.ArgumentNullException">
/// <paramref name="array" /> is <see langword="null" />.</exception>
/// <exception cref="T:System.ArgumentOutOfRangeException">
/// <paramref name="arrayIndex" /> is less than 0.</exception>
/// <exception cref="T:System.ArgumentException">The number of elements in the source <see cref="T:System.Collections.Generic.ICollection`1" /> is greater than the available space from <paramref name="arrayIndex" /> to the end of the destination <paramref name="array" />.</exception>
/// <footer><a href="https://docs.microsoft.com/en-us/dotnet/api/System.Collections.Generic.ICollection-1.CopyTo?view=netcore-5.0">`ICollection.CopyTo` on docs.microsoft.com</a></footer>
public void CopyTo(T[] array, int arrayIndex) => internalList.CopyTo(array, arrayIndex);
/// <summary>Removes the first occurrence of a specific object from the <see cref="T:System.Collections.Generic.ICollection`1" />.</summary>
/// <param name="item">The object to remove from the <see cref="T:System.Collections.Generic.ICollection`1" />.</param>
/// <exception cref="T:System.NotSupportedException">The <see cref="T:System.Collections.Generic.ICollection`1" /> is read-only.</exception>
/// <returns>
/// <see langword="true" /> if <paramref name="item" /> was successfully removed from the <see cref="T:System.Collections.Generic.ICollection`1" />; otherwise, <see langword="false" />. This method also returns <see langword="false" /> if <paramref name="item" /> is not found in the original <see cref="T:System.Collections.Generic.ICollection`1" />.</returns>
/// <footer><a href="https://docs.microsoft.com/en-us/dotnet/api/System.Collections.Generic.ICollection-1.Remove?view=netcore-5.0">`ICollection.Remove` on docs.microsoft.com</a></footer>
public virtual bool Remove(T item) => internalList.Remove(item);
/// <summary>Gets the number of elements contained in the <see cref="T:System.Collections.Generic.ICollection`1" />.</summary>
/// <returns>The number of elements contained in the <see cref="T:System.Collections.Generic.ICollection`1" />.</returns>
/// <footer><a href="https://docs.microsoft.com/en-us/dotnet/api/System.Collections.Generic.ICollection-1.Count?view=netcore-5.0">`ICollection.Count` on docs.microsoft.com</a></footer>
public int Count => internalList.Count;
/// <summary>Gets a value indicating whether the <see cref="T:System.Collections.Generic.ICollection`1" /> is read-only.</summary>
/// <returns>
/// <see langword="true" /> if the <see cref="T:System.Collections.Generic.ICollection`1" /> is read-only; otherwise, <see langword="false" />.</returns>
/// <footer><a href="https://docs.microsoft.com/en-us/dotnet/api/System.Collections.Generic.ICollection-1.IsReadOnly?view=netcore-5.0">`ICollection.IsReadOnly` on docs.microsoft.com</a></footer>
public bool IsReadOnly => false;
/// <summary>Determines the index of a specific item in the <see cref="T:System.Collections.Generic.IList`1" />.</summary>
/// <param name="item">The object to locate in the <see cref="T:System.Collections.Generic.IList`1" />.</param>
/// <returns>The index of <paramref name="item" /> if found in the list; otherwise, -1.</returns>
/// <footer><a href="https://docs.microsoft.com/en-us/dotnet/api/System.Collections.Generic.IList-1.IndexOf?view=netcore-5.0">`IList.IndexOf` on docs.microsoft.com</a></footer>
public int IndexOf(T item) => internalList.IndexOf(item);
/// <summary>Removes the <see cref="T:System.Collections.Generic.IList`1" /> item at the specified index.</summary>
/// <param name="index">The zero-based index of the item to remove.</param>
/// <exception cref="T:System.ArgumentOutOfRangeException">
/// <paramref name="index" /> is not a valid index in the <see cref="T:System.Collections.Generic.IList`1" />.</exception>
/// <exception cref="T:System.NotSupportedException">The <see cref="T:System.Collections.Generic.IList`1" /> is read-only.</exception>
/// <footer><a href="https://docs.microsoft.com/en-us/dotnet/api/System.Collections.Generic.IList-1.RemoveAt?view=netcore-5.0">`IList.RemoveAt` on docs.microsoft.com</a></footer>
public virtual void RemoveAt(int index) => internalList.RemoveAt(index);
/// <inheritdoc cref="Tag.PrettyPrinted(StringBuilder,int,string)"/>
protected internal override void PrettyPrinted(StringBuilder buffer, int level, string indent)
{
for (var i = 0; i < level; i++)
buffer.Append(indent);
buffer.AppendLine(ToString());
}
}

View File

@ -1,5 +1,7 @@
using System; using System;
using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Runtime.Serialization; using System.Runtime.Serialization;
using System.Text; using System.Text;
using JetBrains.Annotations; using JetBrains.Annotations;
@ -13,7 +15,7 @@ namespace SharpNBT;
/// All child tags <b>must</b> be have the same <see cref="Tag.Type"/> value, and their <see cref="Tag.Name"/> value will be omitted during serialization. /// All child tags <b>must</b> be have the same <see cref="Tag.Type"/> value, and their <see cref="Tag.Name"/> value will be omitted during serialization.
/// </remarks> /// </remarks>
[PublicAPI][Serializable] [PublicAPI][Serializable]
public class ListTag : TagContainer public class ListTag : Tag, IList<Tag>
{ {
/// <summary> /// <summary>
/// Gets the NBT type of this tag's children. /// Gets the NBT type of this tag's children.
@ -27,9 +29,20 @@ public class ListTag : TagContainer
/// <param name="childType">A constant describing the NBT type for children in this tag.</param> /// <param name="childType">A constant describing the NBT type for children in this tag.</param>
public ListTag(string? name, TagType childType) : base(TagType.List, name) public ListTag(string? name, TagType childType) : base(TagType.List, name)
{ {
RequiredType = childType;
NamedChildren = false;
ChildType = childType; ChildType = childType;
list = new List<Tag>();
}
/// <summary>
/// Creates a new instance of the <see cref="ListTag"/> class.
/// </summary>
/// <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="capacity">The initial capacity of the list.</param>
public ListTag(string? name, TagType childType, int capacity) : base(TagType.List, name)
{
ChildType = childType;
list = new List<Tag>(capacity);
} }
/// <summary> /// <summary>
@ -40,7 +53,8 @@ public class ListTag : TagContainer
/// <param name="children">A collection of values to include in this tag.</param> /// <param name="children">A collection of values to include in this tag.</param>
public ListTag(string? name, TagType childType, IEnumerable<Tag> children) : this(name, childType) public ListTag(string? name, TagType childType, IEnumerable<Tag> children) : this(name, childType)
{ {
AddRange(children); foreach (var item in children)
list.Add(AssertType(item));
} }
/// <summary> /// <summary>
@ -50,15 +64,98 @@ public class ListTag : TagContainer
/// <param name="context">The destination (see <see cref="T:System.Runtime.Serialization.StreamingContext" />) for this serialization.</param> /// <param name="context">The destination (see <see cref="T:System.Runtime.Serialization.StreamingContext" />) for this serialization.</param>
protected ListTag(SerializationInfo info, StreamingContext context) : base(info, context) protected ListTag(SerializationInfo info, StreamingContext context) : base(info, context)
{ {
ChildType = (TagType)info.GetByte("child_type");
var count = info.GetInt32("count");
list = new List<Tag>(count);
if (info.GetValue("values", typeof(Tag[])) is Tag[] ary)
AddRange(ary);
} }
public override void GetObjectData(SerializationInfo info, StreamingContext context)
{
base.GetObjectData(info, context);
info.AddValue("child_type", (byte) ChildType);
info.AddValue("count", list.Count);
info.AddValue("values", list.ToArray());
}
/// <inheritdoc />
public IEnumerator<Tag> GetEnumerator() => list.GetEnumerator();
/// <inheritdoc />
IEnumerator IEnumerable.GetEnumerator() => list.GetEnumerator();
/// <inheritdoc />
public void Add(Tag item) => list.Add(AssertType(item));
public void AddRange(IEnumerable<Tag> items)
{
foreach (var item in items)
list.Add(AssertType(item));
}
/// <inheritdoc />
public void Clear() => list.Clear();
/// <inheritdoc />
public bool Contains(Tag item) => list.Contains(item);
/// <inheritdoc />
public void CopyTo(Tag[] array, int arrayIndex) => list.CopyTo(array, arrayIndex);
/// <inheritdoc />
public bool Remove(Tag item) => list.Remove(item);
/// <inheritdoc />
public int Count => list.Count;
/// <inheritdoc />
bool ICollection<Tag>.IsReadOnly => false;
/// <inheritdoc />
public int IndexOf(Tag item) => list.IndexOf(item);
/// <inheritdoc />
public void Insert(int index, Tag item) => list.Insert(index, AssertType(item));
/// <inheritdoc />
public void RemoveAt(int index) => list.RemoveAt(index);
/// <inheritdoc />
public Tag this[int index]
{
get => list[index];
set => list[index] = AssertType(value);
}
/// <inheritdoc cref="object.ToString"/> /// <inheritdoc cref="object.ToString"/>
public override string ToString() public override string ToString()
{ {
var word = Count == 1 ? Strings.WordEntry : Strings.WordEntries; var word = Count == 1 ? Strings.WordEntry : Strings.WordEntries;
return $"TAG_List({PrettyName}): [{Count} {word}]"; return $"TAG_List({PrettyName}): [{Count} {word}]";
} }
/// <inheritdoc cref="Tag.PrettyPrinted(StringBuilder,int,string)"/> /// <inheritdoc cref="Tag.PrettyPrinted(StringBuilder,int,string)"/>
protected internal override void PrettyPrinted(StringBuilder buffer, int level, string indent) protected internal override void PrettyPrinted(StringBuilder buffer, int level, string indent)
{ {
@ -100,4 +197,16 @@ public class ListTag : TagContainer
return $"{StringifyName}:[{string.Join(',', strings)}]"; return $"{StringifyName}:[{string.Join(',', strings)}]";
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private Tag AssertType(Tag tag)
{
if (tag.Type != ChildType)
throw new ArrayTypeMismatchException(Strings.ChildWrongType);
return tag;
}
private readonly List<Tag> list;
} }

View File

@ -35,8 +35,6 @@ public abstract class Tag : IEquatable<Tag>, ISerializable, ICloneable
typeof(NumericTag<>), typeof(NumericTag<>),
typeof(ArrayTag<>), typeof(ArrayTag<>),
typeof(Tag[]), typeof(Tag[]),
typeof(EnumerableTag<>),
typeof(TagContainer),
typeof(ByteTag), typeof(ByteTag),
typeof(ShortTag), typeof(ShortTag),
typeof(IntTag), typeof(IntTag),

View File

@ -1,173 +0,0 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Runtime.Serialization;
using JetBrains.Annotations;
using SuppressMessageAttribute = System.Diagnostics.CodeAnalysis.SuppressMessageAttribute;
namespace SharpNBT;
/// <summary>
/// Base class for tags that contain a collection of other <see cref="Tag"/> objects and can be enumerated.
/// </summary>
[PublicAPI][Serializable]
public abstract class TagContainer : EnumerableTag<Tag>
{
/// <summary>
/// A value indicating if children of this container are required to have be named.
/// </summary>
protected bool NamedChildren;
/// <summary>
/// When not <see langword="null"/>, indicates that a child must be of a specific type to be added.
/// </summary>
protected TagType? RequiredType;
/// <summary>
/// Initializes a new instance of the <see cref="TagContainer"/>.
/// </summary>
/// <param name="type">A constant describing the NBT type for this tag.</param>
/// <param name="name">The name of the tag, or <see langword="null"/> if tag has no name.</param>
protected TagContainer(TagType type, string? name) : base(type, name)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="TagContainer"/> with the specified <paramref name="values"/>.
/// </summary>
/// <param name="type">A constant describing the NBT type for this tag.</param>
/// <param name="name">The name of the tag, or <see langword="null"/> if tag has no name.</param>
/// <param name="values">A collection of values to include in this tag.</param>
protected TagContainer(TagType type, string? name, IEnumerable<Tag> values) : base(type, name, values)
{
}
/// <summary>
/// Required constructor for ISerializable implementation.
/// </summary>
/// <param name="info">The <see cref="T:System.Runtime.Serialization.SerializationInfo" /> to describing this instance.</param>
/// <param name="context">The destination (see <see cref="T:System.Runtime.Serialization.StreamingContext" />) for this serialization.</param>
protected TagContainer(SerializationInfo info, StreamingContext context) : base(info, context)
{
RequiredType = (TagType?) info.GetValue("child_type", typeof(TagType?));
NamedChildren = !RequiredType.HasValue;
}
/// <summary>Populates a <see cref="T:System.Runtime.Serialization.SerializationInfo" /> with the data needed to serialize the target object.</summary>
/// <param name="info">The <see cref="T:System.Runtime.Serialization.SerializationInfo" /> to populate with data.</param>
/// <param name="context">The destination (see <see cref="T:System.Runtime.Serialization.StreamingContext" />) for this serialization.</param>
/// <exception cref="T:System.Security.SecurityException">The caller does not have the required permission.</exception>
public override void GetObjectData(SerializationInfo info, StreamingContext context)
{
base.GetObjectData(info, context);
if (RequiredType.HasValue)
info.AddValue("child_type", RequiredType.Value);
}
/// <summary>Adds an item to the <see cref="T:System.Collections.Generic.ICollection`1" />.</summary>
/// <param name="item">The object to add to the <see cref="T:System.Collections.Generic.ICollection`1" />.</param>
/// <exception cref="T:System.NotSupportedException">The <see cref="T:System.Collections.Generic.ICollection`1" /> is read-only.</exception>
/// <footer><a href="https://docs.microsoft.com/en-us/dotnet/api/System.Collections.Generic.ICollection-1.Add?view=netcore-5.0">`ICollection.Add` on docs.microsoft.com</a></footer>
[SuppressMessage("ReSharper", "AnnotationConflictInHierarchy")]
public sealed override void Add(Tag item)
{
base.Add(AssertConventions(item));
item.Parent = this;
}
/// <summary>Inserts an item to the <see cref="T:System.Collections.Generic.IList`1" /> at the specified index.</summary>
/// <param name="index">The zero-based index at which <paramref name="item" /> should be inserted.</param>
/// <param name="item">The object to insert into the <see cref="T:System.Collections.Generic.IList`1" />.</param>
/// <exception cref="T:System.ArgumentOutOfRangeException">
/// <paramref name="index" /> is not a valid index in the <see cref="T:System.Collections.Generic.IList`1" />.</exception>
/// <exception cref="T:System.NotSupportedException">The <see cref="T:System.Collections.Generic.IList`1" /> is read-only.</exception>
/// <footer><a href="https://docs.microsoft.com/en-us/dotnet/api/System.Collections.Generic.IList-1.Insert?view=netcore-5.0">`IList.Insert` on docs.microsoft.com</a></footer>
[SuppressMessage("ReSharper", "AnnotationConflictInHierarchy")]
public sealed override void Insert(int index, Tag item)
{
base.Insert(index, AssertConventions(item));
item.Parent = this;
}
/// <summary>Gets or sets the element at the specified index.</summary>
/// <param name="index">The zero-based index of the element to get or set.</param>
/// <exception cref="T:System.ArgumentOutOfRangeException">
/// <paramref name="index" /> is not a valid index in the <see cref="T:System.Collections.Generic.IList`1" />.</exception>
/// <exception cref="T:System.NotSupportedException">The property is set and the <see cref="T:System.Collections.Generic.IList`1" /> is read-only.</exception>
/// <returns>The element at the specified index.</returns>
/// <footer><a href="https://docs.microsoft.com/en-us/dotnet/api/System.Collections.Generic.IList-1.Item?view=netcore-5.0">`IList.Item` on docs.microsoft.com</a></footer>
public sealed override Tag this[int index]
{
get => base[index];
set
{
base[index] = AssertConventions(value);
value.Parent = this;
}
}
/// <summary>Removes all items from the <see cref="T:System.Collections.Generic.ICollection`1" />.</summary>
/// <exception cref="T:System.NotSupportedException">The <see cref="T:System.Collections.Generic.ICollection`1" /> is read-only.</exception>
/// <footer><a href="https://docs.microsoft.com/en-us/dotnet/api/System.Collections.Generic.ICollection-1.Clear?view=netcore-5.0">`ICollection.Clear` on docs.microsoft.com</a></footer>
public sealed override void Clear()
{
foreach (var item in this)
item.Parent = null;
base.Clear();
}
/// <summary>Removes the first occurrence of a specific object from the <see cref="T:System.Collections.Generic.ICollection`1" />.</summary>
/// <param name="item">The object to remove from the <see cref="T:System.Collections.Generic.ICollection`1" />.</param>
/// <exception cref="T:System.NotSupportedException">The <see cref="T:System.Collections.Generic.ICollection`1" /> is read-only.</exception>
/// <returns>
/// <see langword="true" /> if <paramref name="item" /> was successfully removed from the <see cref="T:System.Collections.Generic.ICollection`1" />; otherwise, <see langword="false" />. This method also returns <see langword="false" /> if <paramref name="item" /> is not found in the original <see cref="T:System.Collections.Generic.ICollection`1" />.</returns>
/// <footer><a href="https://docs.microsoft.com/en-us/dotnet/api/System.Collections.Generic.ICollection-1.Remove?view=netcore-5.0">`ICollection.Remove` on docs.microsoft.com</a></footer>
public sealed override bool Remove(Tag item)
{
if (item is null || !base.Remove(item))
return false;
item.Parent = null;
return true;
}
/// <summary>Removes the <see cref="T:System.Collections.Generic.IList`1" /> item at the specified index.</summary>
/// <param name="index">The zero-based index of the item to remove.</param>
/// <exception cref="T:System.ArgumentOutOfRangeException">
/// <paramref name="index" /> is not a valid index in the <see cref="T:System.Collections.Generic.IList`1" />.</exception>
/// <exception cref="T:System.NotSupportedException">The <see cref="T:System.Collections.Generic.IList`1" /> is read-only.</exception>
/// <footer><a href="https://docs.microsoft.com/en-us/dotnet/api/System.Collections.Generic.IList-1.RemoveAt?view=netcore-5.0">`IList.RemoveAt` on docs.microsoft.com</a></footer>
public sealed override void RemoveAt(int index)
{
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(Tag? tag)
{
if (tag is null)
throw new ArgumentNullException(nameof(tag), Strings.ChildCannotBeNull);
switch (NamedChildren)
{
case true when tag.Name is null:
throw new FormatException(Strings.ChildrenMustBeNamed);
case false when tag.Name != null:
throw new FormatException(Strings.ChildrenMustNotBeNamed);
}
if (RequiredType.HasValue && RequiredType.Value != tag.Type)
throw new ArrayTypeMismatchException(Strings.ChildWrongType);
return tag;
}
}