Checkpoint reflection replacement

This commit is contained in:
Matt Nadareski
2026-04-07 10:37:03 -04:00
parent 6ba4a5b3ab
commit 76c5dc40e3
2 changed files with 126 additions and 89 deletions

View File

@@ -1898,5 +1898,89 @@ namespace SabreTools.Metadata.Filter.Test
}
#endregion
#region SharedFeat
[Theory]
[InlineData("sharedfeat.name", "name")]
[InlineData("sharedfeat.value", "value")]
public void Matches_SharedFeat(string itemField, string value)
{
var filter = new FilterObject(itemField, value, Operation.Equals);
SharedFeat obj = new SharedFeat
{
Name = "name",
Value = "value",
};
bool actual = filter.Matches(obj);
Assert.True(actual);
}
#endregion
#region Slot
[Theory]
[InlineData("slot.name", "name")]
public void Matches_Slot(string itemField, string value)
{
var filter = new FilterObject(itemField, value, Operation.Equals);
Slot obj = new Slot
{
Name = "name",
};
bool actual = filter.Matches(obj);
Assert.True(actual);
}
#endregion
#region SlotOption
[Theory]
[InlineData("slotoption.default", "yes")]
[InlineData("slotoption.devname", "devname")]
[InlineData("slotoption.name", "name")]
public void Matches_SlotOption(string itemField, string value)
{
var filter = new FilterObject(itemField, value, Operation.Equals);
SlotOption obj = new SlotOption
{
Default = true,
DevName = "devname",
Name = "name",
};
bool actual = filter.Matches(obj);
Assert.True(actual);
}
#endregion
#region SoftwareList
[Theory]
[InlineData("softwarelist.filter", "filter")]
[InlineData("softwarelist.name", "name")]
[InlineData("softwarelist.status", "original")]
[InlineData("softwarelist.tag", "tag")]
public void Matches_SoftwareList(string itemField, string value)
{
var filter = new FilterObject(itemField, value, Operation.Equals);
SoftwareList obj = new SoftwareList
{
Filter = "filter",
Name = "name",
Status = SoftwareListStatus.Original,
Tag = "tag",
};
bool actual = filter.Matches(obj);
Assert.True(actual);
}
#endregion
}
}

View File

@@ -1,6 +1,4 @@
using System;
using System.Reflection;
using System.Xml.Serialization;
using SabreTools.Data.Models.Metadata;
namespace SabreTools.Metadata.Filter
@@ -735,6 +733,44 @@ namespace SabreTools.Metadata.Filter
"savechipserial",
];
/// <summary>
/// Known keys for SharedFeat
/// </summary>
private static readonly string[] _sharedFeatKeys =
[
"name",
"value",
];
/// <summary>
/// Known keys for Slot
/// </summary>
private static readonly string[] _slotKeys =
[
"name",
];
/// <summary>
/// Known keys for SlotOption
/// </summary>
private static readonly string[] _slotOptionKeys =
[
"default",
"devname",
"name",
];
/// <summary>
/// Known keys for SoftwareList
/// </summary>
private static readonly string[] _softwareListKeys =
[
"filter",
"name",
"status",
"tag",
];
#endregion
/// <summary>
@@ -948,24 +984,12 @@ namespace SabreTools.Metadata.Filter
"rom" => _romKeys,
"sample" => _sampleKeys,
"serials" => _serialsKeys,
"sharedfeat" => _sharedFeatKeys,
"slot" => _slotKeys,
"slotoption" => _slotOptionKeys,
"softwarelist" => _softwareListKeys,
_ => null,
};
// TODO: Remove this fallback path
if (properties is null)
{
// Get the correct item type
var itemType = GetDatItemType(itemName.ToLowerInvariant());
if (itemType is null)
return null;
properties = GetProperties(itemType);
// Special cases for mismatched names
if (properties is not null && itemType == typeof(Rom))
properties = [.. properties, "crc"];
}
if (properties is null)
return null;
@@ -973,76 +997,5 @@ namespace SabreTools.Metadata.Filter
string? propertyMatch = Array.Find(properties, c => string.Equals(c, fieldName, StringComparison.OrdinalIgnoreCase));
return propertyMatch?.ToLowerInvariant();
}
#region Reflection-based Helpers
/// <summary>
/// Attempt to get the DatItem type from the name
/// </summary>
private static Type? GetDatItemType(string? itemType)
{
if (string.IsNullOrEmpty(itemType))
return null;
// Loop through all loaded assemblies
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
{
// If not all types can be loaded, use the ones that could be
Type?[] assemblyTypes = [];
try
{
assemblyTypes = assembly.GetTypes();
}
catch (ReflectionTypeLoadException rtle)
{
assemblyTypes = Array.FindAll(rtle.Types ?? [], t => t is not null);
}
// Loop through all types
foreach (Type? type in assemblyTypes)
{
// If the type is invalid
if (type is null)
continue;
// If the type isn't a class or doesn't implement the interface
if (!type.IsClass || !typeof(DatItem).IsAssignableFrom(type))
continue;
// Get the XML type name
#if NET20 || NET35 || NET40
string? elementName = (Attribute.GetCustomAttribute(type, typeof(XmlRootAttribute)) as XmlRootAttribute)!.ElementName;
#else
string? elementName = type.GetCustomAttribute<XmlRootAttribute>()?.ElementName;
#endif
if (elementName is null)
continue;
// If the name matches
if (string.Equals(elementName, itemType, StringComparison.OrdinalIgnoreCase))
return type;
}
}
return null;
}
/// <summary>
/// Get property names for the given type, if possible
/// </summary>
private static string[]? GetProperties(Type? type)
{
if (type is null)
return null;
var properties = type.GetProperties(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public);
if (properties is null)
return null;
string[] propertyNames = Array.ConvertAll(properties, f => f.Name);
return Array.FindAll(propertyNames, s => s.Length > 0);
}
#endregion
}
}