Skip to content

Commit

Permalink
Add Core.Models.ShotTypeAttribute<T>
Browse files Browse the repository at this point in the history
  • Loading branch information
y-iihoshi committed Jan 5, 2025
1 parent a6750f2 commit 50dedce
Show file tree
Hide file tree
Showing 12 changed files with 717 additions and 3 deletions.
33 changes: 33 additions & 0 deletions ThScoreFileConverter.Core.Tests/Extensions/EnumExtensionsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Globalization;
using ThScoreFileConverter.Core.Extensions;
using ThScoreFileConverter.Core.Models;
using ThScoreFileConverter.Core.Models.Th06;

namespace ThScoreFileConverter.Core.Tests.Extensions;

Expand Down Expand Up @@ -160,4 +161,36 @@ public void ToCharaFullNameTestMultiple()
Assert.AreEqual("Merlin Prismriver", Sisters.Prismriver.ToCharaFullName(1));
Assert.AreEqual("Lyrica Prismriver", Sisters.Prismriver.ToCharaFullName(2));
}

[TestMethod]
public void ToShotTypeNameTest()
{
using var backup = TestHelper.BackupCultureInfo();

CultureInfo.CurrentCulture = CultureInfo.GetCultureInfo("ja-JP");
Assert.AreEqual(string.Empty, DayOfWeek.Sunday.ToShotTypeName());
Assert.AreEqual("霊", Chara.ReimuA.ToShotTypeName());
Assert.AreEqual("魔", Chara.MarisaA.ToShotTypeName());

CultureInfo.CurrentCulture = CultureInfo.GetCultureInfo("en-US");
Assert.AreEqual(string.Empty, DayOfWeek.Sunday.ToShotTypeName());
Assert.AreEqual("Spirit", Chara.ReimuA.ToShotTypeName());
Assert.AreEqual("Magic", Chara.MarisaA.ToShotTypeName());
}

[TestMethod]
public void ToShotTypeFullNameTest()
{
using var backup = TestHelper.BackupCultureInfo();

CultureInfo.CurrentCulture = CultureInfo.GetCultureInfo("ja-JP");
Assert.AreEqual(string.Empty, DayOfWeek.Sunday.ToShotTypeFullName());
Assert.AreEqual("霊符", Chara.ReimuA.ToShotTypeFullName());
Assert.AreEqual("魔符", Chara.MarisaA.ToShotTypeFullName());

CultureInfo.CurrentCulture = CultureInfo.GetCultureInfo("en-US");
Assert.AreEqual(string.Empty, DayOfWeek.Sunday.ToShotTypeFullName());
Assert.AreEqual("Spirit Sign", Chara.ReimuA.ToShotTypeFullName());
Assert.AreEqual("Magic Sign", Chara.MarisaA.ToShotTypeFullName());
}
}
40 changes: 40 additions & 0 deletions ThScoreFileConverter.Core.Tests/Extensions/TypeExtensionsTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using System;
using System.Linq;
using ThScoreFileConverter.Core.Extensions;

internal static class Global;

namespace ThScoreFileConverter.Core.Tests.Extensions
{
[TestClass]
public class TypeExtensionsTests
{
[TestMethod]
public void GetLeafNamespaceTest()
{
Assert.AreEqual("System", typeof(Math).GetLeafNamespace());
Assert.AreEqual("Linq", typeof(Enumerable).GetLeafNamespace());
Assert.AreEqual("Extensions", typeof(TypeExtensionsTests).GetLeafNamespace());
}

[TestMethod]
public void GetLeafNamespaceTestNull()
{
Type type = null!;
_ = Assert.ThrowsException<ArgumentNullException>(type.GetLeafNamespace);
}

[TestMethod]
public void GetLeafNamespaceTestTwice()
{
Assert.AreEqual("System", typeof(Math).GetLeafNamespace());
Assert.AreEqual("System", typeof(Math).GetLeafNamespace()); // cached
}

[TestMethod]
public void GetLeafNamespaceTestGlobal()
{
Assert.AreEqual(string.Empty, typeof(Global).GetLeafNamespace());
}
}
}
89 changes: 89 additions & 0 deletions ThScoreFileConverter.Core.Tests/Models/ShotTypeAttributeTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using ThScoreFileConverter.Core.Models;
using ThScoreFileConverter.Core.Models.Th06;
using ThScoreFileConverter.Core.Resources;

namespace ThScoreFileConverter.Core.Tests.Models;

[TestClass]
public class ShotTypeAttributeTests
{
[TestMethod]
public void ShotTypeAttributeTest()
{
var attribute = new ShotTypeAttribute<Chara>(Chara.ReimuA);

Assert.IsNotNull(attribute);
Assert.AreEqual("Th06.ReimuA", attribute.Name);
Assert.AreEqual("Th06.ReimuAFullName", attribute.FullName);
Assert.AreEqual(typeof(ShotTypeNames), attribute.ResourceType);
Assert.AreEqual(Chara.ReimuA, attribute.Value);
}

public static IEnumerable<object[]> InvalidCharacters => TestHelper.GetInvalidEnumerators<Chara>();

[DataTestMethod]
[DynamicData(nameof(InvalidCharacters))]
public void ShotTypeAttributeTestInvalid(int chara)
{
_ = Assert.ThrowsException<ArgumentException>(() => new ShotTypeAttribute<Chara>((Chara)chara));
}

[TestMethod]
public void GetLocalizedNameTest()
{
using var backup = TestHelper.BackupCultureInfo();

var attribute = new ShotTypeAttribute<Chara>(Chara.MarisaA);

CultureInfo.CurrentCulture = CultureInfo.GetCultureInfo("ja-JP");
Assert.AreEqual("魔", attribute.GetLocalizedName());

CultureInfo.CurrentCulture = CultureInfo.GetCultureInfo("en-US");
Assert.AreEqual("Magic", attribute.GetLocalizedName());
}

[TestMethod]
public void GetLocalizedNameTestUnregistered()
{
using var backup = TestHelper.BackupCultureInfo();

var attribute = new ShotTypeAttribute<DayOfWeek>(DayOfWeek.Sunday);

CultureInfo.CurrentCulture = CultureInfo.GetCultureInfo("ja-JP");
Assert.AreEqual("System.Sunday", attribute.GetLocalizedName());

CultureInfo.CurrentCulture = CultureInfo.GetCultureInfo("en-US");
Assert.AreEqual("System.Sunday", attribute.GetLocalizedName());
}

[TestMethod]
public void GetLocalizedFullNameTest()
{
using var backup = TestHelper.BackupCultureInfo();

var attribute = new ShotTypeAttribute<Chara>(Chara.MarisaA);

CultureInfo.CurrentCulture = CultureInfo.GetCultureInfo("ja-JP");
Assert.AreEqual("魔符", attribute.GetLocalizedFullName());

CultureInfo.CurrentCulture = CultureInfo.GetCultureInfo("en-US");
Assert.AreEqual("Magic Sign", attribute.GetLocalizedFullName());
}

[TestMethod]
public void GetLocalizedFullNameTestUnregistered()
{
using var backup = TestHelper.BackupCultureInfo();

var attribute = new ShotTypeAttribute<DayOfWeek>(DayOfWeek.Sunday);

CultureInfo.CurrentCulture = CultureInfo.GetCultureInfo("ja-JP");
Assert.AreEqual("System.SundayFullName", attribute.GetLocalizedFullName());

CultureInfo.CurrentCulture = CultureInfo.GetCultureInfo("en-US");
Assert.AreEqual("System.SundayFullName", attribute.GetLocalizedFullName());
}
}
24 changes: 24 additions & 0 deletions ThScoreFileConverter.Core/Extensions/EnumExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,30 @@ public static string ToCharaFullName<T>(this T enumValue, int index = 0)
? attribute.GetLocalizedFullName() : string.Empty;
}

/// <summary>
/// Gets the name of the shot type represented as a given enumeration value.
/// </summary>
/// <typeparam name="T">The enumeration type.</typeparam>
/// <param name="enumValue">An enumeration value.</param>
/// <returns>The name of the shot type represented as <paramref name="enumValue"/>.</returns>
public static string ToShotTypeName<T>(this T enumValue)
where T : struct, Enum
{
return enumValue.ToMember().ShotTypeAttribute?.GetLocalizedName() ?? string.Empty;
}

/// <summary>
/// Gets the full name of the shot type represented as a given enumeration value.
/// </summary>
/// <typeparam name="T">The enumeration type.</typeparam>
/// <param name="enumValue">An enumeration value.</param>
/// <returns>The full name of the shot type represented as <paramref name="enumValue"/>.</returns>
public static string ToShotTypeFullName<T>(this T enumValue)
where T : struct, Enum
{
return enumValue.ToMember().ShotTypeAttribute?.GetLocalizedFullName() ?? string.Empty;
}

/// <summary>
/// Gets the <see cref="EnumHelper{T}.Member"/> instance corresponding to the specified enumeration value.
/// </summary>
Expand Down
48 changes: 48 additions & 0 deletions ThScoreFileConverter.Core/Extensions/TypeExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
//-----------------------------------------------------------------------
// <copyright file="TypeExtensions.cs" company="None">
// Copyright (c) IIHOSHI Yoshinori.
// Licensed under the BSD-2-Clause license. See LICENSE.txt file in the project root for full license information.
// </copyright>
//-----------------------------------------------------------------------

using System;
using System.Collections.Generic;
using CommunityToolkit.Diagnostics;

namespace ThScoreFileConverter.Core.Extensions;

/// <summary>
/// Provides some extension methods for <see cref="Type"/>.
/// </summary>
public static class TypeExtensions
{
private static readonly Dictionary<Type, string> LeafNamespaceCache = [];

/// <summary>
/// Gets the name of the "leaf" namespace of <paramref name="type"/>.
/// </summary>
/// <param name="type">The type.</param>
/// <returns>The name of the "leaf" namespace of <paramref name="type"/>.</returns>
public static string GetLeafNamespace(this Type type)
{
Guard.IsNotNull(type);

if (LeafNamespaceCache.TryGetValue(type, out var value))
{
return value;
}

if (string.IsNullOrEmpty(type.Namespace))
{
value = string.Empty;
}
else
{
var index = type.Namespace.LastIndexOf(Type.Delimiter);
value = (index >= 0) ? type.Namespace[(index + 1)..] : type.Namespace;
}

LeafNamespaceCache.Add(type, value);
return value;
}
}
12 changes: 9 additions & 3 deletions ThScoreFileConverter.Core/Helpers/EnumHelper{T}.cs
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,9 @@ public Member(string name)
this.LongName = enumAltNameAttribute?.LongName ?? string.Empty;
this.Pattern = fieldInfo.GetCustomAttribute<PatternAttribute>()?.Pattern ?? string.Empty;
this.DisplayAttribute = fieldInfo.GetCustomAttribute<DisplayAttribute>();
this.CharacterAttributes = fieldInfo.GetCustomAttributes<CharacterAttribute>().ToFrozenDictionary(
static attr => attr.Index,
static attr => attr);
this.CharacterAttributes = fieldInfo.GetCustomAttributes<CharacterAttribute>()
.ToFrozenDictionary(static attr => attr.Index, static attr => attr);
this.ShotTypeAttribute = fieldInfo.GetCustomAttribute<ShotTypeAttribute<T>>();
}

/// <summary>
Expand Down Expand Up @@ -111,5 +111,11 @@ public Member(string name)
/// as a dictionary keyed by <see cref="CharacterAttribute.Index"/>.
/// </summary>
public FrozenDictionary<int, CharacterAttribute> CharacterAttributes { get; }

/// <summary>
/// Gets the <see cref="Models.ShotTypeAttribute{T}"/> instance of the field in
/// <typeparamref name="T"/>, if defined; otherwise, <see langword="null"/>.
/// </summary>
public ShotTypeAttribute<T>? ShotTypeAttribute { get; }
}
}
38 changes: 38 additions & 0 deletions ThScoreFileConverter.Core/Models/ShotTypeAttribute{T}.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
//-----------------------------------------------------------------------
// <copyright file="ShotTypeAttribute{T}.cs" company="None">
// Copyright (c) IIHOSHI Yoshinori.
// Licensed under the BSD-2-Clause license. See LICENSE.txt file in the project root for full license information.
// </copyright>
//-----------------------------------------------------------------------

using System;
using CommunityToolkit.Diagnostics;
using ThScoreFileConverter.Core.Extensions;
using ThScoreFileConverter.Core.Resources;

namespace ThScoreFileConverter.Core.Models;

/// <summary>
/// Provides names of the playable character's shot type represented as an enumeration field.
/// </summary>
/// <typeparam name="T">The enumeration type representing playable characters' shot types.</typeparam>
[CLSCompliant(false)]
public sealed class ShotTypeAttribute<T> : EnumDisplayAttribute
where T : struct, Enum
{
/// <summary>
/// Initializes a new instance of the <see cref="ShotTypeAttribute{T}"/> class.
/// </summary>
/// <param name="value">An enumeration field representing a playable character's shot type.</param>
public ShotTypeAttribute(T value)
: base($"{typeof(T).GetLeafNamespace()}.{value}", typeof(ShotTypeNames))
{
Guard.IsTrue(Enum.IsDefined(value));
this.Value = value;
}

/// <summary>
/// Gets the value of the playable character's shot type.
/// </summary>
public T Value { get; }
}
4 changes: 4 additions & 0 deletions ThScoreFileConverter.Core/Models/Th06/Chara.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,23 +16,27 @@ public enum Chara
/// Hakurei Reimu (Spirit Sign).
/// </summary>
[EnumAltName("RA")]
[ShotType<Chara>(ReimuA)]
ReimuA,

/// <summary>
/// Hakurei Reimu (Dream Sign).
/// </summary>
[EnumAltName("RB")]
[ShotType<Chara>(ReimuB)]
ReimuB,

/// <summary>
/// Kirisame Marisa (Magic Sign).
/// </summary>
[EnumAltName("MA")]
[ShotType<Chara>(MarisaA)]
MarisaA,

/// <summary>
/// Kirisame Marisa (Love Sign).
/// </summary>
[EnumAltName("MB")]
[ShotType<Chara>(MarisaB)]
MarisaB,
}
Loading

0 comments on commit 50dedce

Please sign in to comment.