mirror of
https://github.com/dcronqvist/DotTiled.git
synced 2025-02-05 08:52:50 +02:00
Initial working concept of mapping properties to C# classes using reflection
This commit is contained in:
parent
2b05ab9a72
commit
fda0922dcc
3 changed files with 255 additions and 29 deletions
194
src/DotTiled.Tests/Properties/HasPropertiesBaseTests.cs
Normal file
194
src/DotTiled.Tests/Properties/HasPropertiesBaseTests.cs
Normal file
|
@ -0,0 +1,194 @@
|
|||
using System.Globalization;
|
||||
|
||||
namespace DotTiled.Tests;
|
||||
|
||||
public class HasPropertiesBaseTests
|
||||
{
|
||||
private sealed class TestHasProperties(IList<IProperty> props) : HasPropertiesBase
|
||||
{
|
||||
public override IList<IProperty> GetProperties() => props;
|
||||
}
|
||||
|
||||
private sealed class MapTo
|
||||
{
|
||||
public bool MapToBool { get; set; } = false;
|
||||
public Color MapToColor { get; set; } = Color.Parse("#00000000", CultureInfo.InvariantCulture);
|
||||
public float MapToFloat { get; set; } = 0.0f;
|
||||
public string MapToFile { get; set; } = "";
|
||||
public int MapToInt { get; set; } = 0;
|
||||
public int MapToObject { get; set; } = 0;
|
||||
public string MapToString { get; set; } = "";
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetMappedProperty_PropertyNotFound_ThrowsKeyNotFoundException()
|
||||
{
|
||||
// Arrange
|
||||
List<IProperty> props = [
|
||||
new ClassProperty {
|
||||
Name = "ClassInObject",
|
||||
PropertyType = "MapTo",
|
||||
Value = [
|
||||
new StringProperty { Name = "PropertyThatDoesNotExistInMapTo", Value = "Test" }
|
||||
],
|
||||
}
|
||||
];
|
||||
var hasProperties = new TestHasProperties(props);
|
||||
|
||||
// Act
|
||||
var act = () => hasProperties.GetMappedProperty<MapTo>("ClassInObject");
|
||||
|
||||
// Assert
|
||||
Assert.Throws<KeyNotFoundException>(act);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetMappedProperty_AllBasicValidProperties_ReturnsMappedProperty()
|
||||
{
|
||||
// Arrange
|
||||
List<IProperty> props = [
|
||||
new ClassProperty {
|
||||
Name = "ClassInObject",
|
||||
PropertyType = "MapTo",
|
||||
Value = [
|
||||
new BoolProperty { Name = "MapToBool", Value = true },
|
||||
new ColorProperty { Name = "MapToColor", Value = Color.Parse("#FF0000FF", CultureInfo.InvariantCulture) },
|
||||
new FloatProperty { Name = "MapToFloat", Value = 1.0f },
|
||||
new StringProperty { Name = "MapToFile", Value = "Test" },
|
||||
new IntProperty { Name = "MapToInt", Value = 1 },
|
||||
new IntProperty { Name = "MapToObject", Value = 1 },
|
||||
new StringProperty { Name = "MapToString", Value = "Test" },
|
||||
],
|
||||
}
|
||||
];
|
||||
var hasProperties = new TestHasProperties(props);
|
||||
|
||||
// Act
|
||||
var mappedProperty = hasProperties.GetMappedProperty<MapTo>("ClassInObject");
|
||||
|
||||
// Assert
|
||||
Assert.True(mappedProperty.MapToBool);
|
||||
Assert.Equal(Color.Parse("#FF0000FF", CultureInfo.InvariantCulture), mappedProperty.MapToColor);
|
||||
Assert.Equal(1.0f, mappedProperty.MapToFloat);
|
||||
Assert.Equal("Test", mappedProperty.MapToFile);
|
||||
Assert.Equal(1, mappedProperty.MapToInt);
|
||||
Assert.Equal(1, mappedProperty.MapToObject);
|
||||
Assert.Equal("Test", mappedProperty.MapToString);
|
||||
}
|
||||
|
||||
private sealed class NestedMapTo
|
||||
{
|
||||
public string NestedMapToString { get; set; } = "";
|
||||
public MapTo MapToInNested { get; set; } = new MapTo();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetMappedProperty_NestedMapTo_ReturnsMappedProperty()
|
||||
{
|
||||
// Arrange
|
||||
List<IProperty> props = [
|
||||
new ClassProperty {
|
||||
Name = "ClassInObject",
|
||||
PropertyType = "NestedMapTo",
|
||||
Value = [
|
||||
new StringProperty { Name = "NestedMapToString", Value = "Test" },
|
||||
new ClassProperty {
|
||||
Name = "MapToInNested",
|
||||
PropertyType = "MapTo",
|
||||
Value = [
|
||||
new BoolProperty { Name = "MapToBool", Value = true },
|
||||
new ColorProperty { Name = "MapToColor", Value = Color.Parse("#FF0000FF", CultureInfo.InvariantCulture) },
|
||||
new FloatProperty { Name = "MapToFloat", Value = 1.0f },
|
||||
new StringProperty { Name = "MapToFile", Value = "Test" },
|
||||
new IntProperty { Name = "MapToInt", Value = 1 },
|
||||
new IntProperty { Name = "MapToObject", Value = 1 },
|
||||
new StringProperty { Name = "MapToString", Value = "Test" },
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
];
|
||||
var hasProperties = new TestHasProperties(props);
|
||||
|
||||
// Act
|
||||
var mappedProperty = hasProperties.GetMappedProperty<NestedMapTo>("ClassInObject");
|
||||
|
||||
// Assert
|
||||
Assert.Equal("Test", mappedProperty.NestedMapToString);
|
||||
Assert.True(mappedProperty.MapToInNested.MapToBool);
|
||||
Assert.Equal(Color.Parse("#FF0000FF", CultureInfo.InvariantCulture), mappedProperty.MapToInNested.MapToColor);
|
||||
Assert.Equal(1.0f, mappedProperty.MapToInNested.MapToFloat);
|
||||
Assert.Equal("Test", mappedProperty.MapToInNested.MapToFile);
|
||||
Assert.Equal(1, mappedProperty.MapToInNested.MapToInt);
|
||||
Assert.Equal(1, mappedProperty.MapToInNested.MapToObject);
|
||||
Assert.Equal("Test", mappedProperty.MapToInNested.MapToString);
|
||||
}
|
||||
|
||||
private enum TestEnum
|
||||
{
|
||||
TestValue1,
|
||||
TestValue2,
|
||||
TestValue3
|
||||
}
|
||||
|
||||
private sealed class EnumMapTo
|
||||
{
|
||||
public TestEnum EnumMapToEnum { get; set; } = TestEnum.TestValue1;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetMappedProperty_EnumProperty_ReturnsMappedProperty()
|
||||
{
|
||||
// Arrange
|
||||
List<IProperty> props = [
|
||||
new ClassProperty {
|
||||
Name = "ClassInObject",
|
||||
PropertyType = "EnumMapTo",
|
||||
Value = [
|
||||
new EnumProperty { Name = "EnumMapToEnum", PropertyType = "TestEnum", Value = new HashSet<string> { "TestValue1" } },
|
||||
],
|
||||
}
|
||||
];
|
||||
var hasProperties = new TestHasProperties(props);
|
||||
|
||||
// Act
|
||||
var mappedProperty = hasProperties.GetMappedProperty<EnumMapTo>("ClassInObject");
|
||||
|
||||
// Assert
|
||||
Assert.Equal(TestEnum.TestValue1, mappedProperty.EnumMapToEnum);
|
||||
}
|
||||
|
||||
private enum TestEnumWithFlags
|
||||
{
|
||||
TestValue1 = 1,
|
||||
TestValue2 = 2,
|
||||
TestValue3 = 4
|
||||
}
|
||||
|
||||
private sealed class EnumWithFlagsMapTo
|
||||
{
|
||||
public TestEnumWithFlags EnumWithFlagsMapToEnum { get; set; } = TestEnumWithFlags.TestValue1;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetMappedProperty_EnumWithFlagsProperty_ReturnsMappedProperty()
|
||||
{
|
||||
// Arrange
|
||||
List<IProperty> props = [
|
||||
new ClassProperty {
|
||||
Name = "ClassInObject",
|
||||
PropertyType = "EnumWithFlagsMapTo",
|
||||
Value = [
|
||||
new EnumProperty { Name = "EnumWithFlagsMapToEnum", PropertyType = "TestEnumWithFlags", Value = new HashSet<string> { "TestValue1", "TestValue2" } },
|
||||
],
|
||||
}
|
||||
];
|
||||
var hasProperties = new TestHasProperties(props);
|
||||
|
||||
// Act
|
||||
var mappedProperty = hasProperties.GetMappedProperty<EnumWithFlagsMapTo>("ClassInObject");
|
||||
|
||||
// Assert
|
||||
Assert.Equal(TestEnumWithFlags.TestValue1 | TestEnumWithFlags.TestValue2, mappedProperty.EnumWithFlagsMapToEnum);
|
||||
}
|
||||
}
|
|
@ -1,6 +1,4 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
|
||||
namespace DotTiled;
|
||||
|
@ -8,7 +6,7 @@ namespace DotTiled;
|
|||
/// <summary>
|
||||
/// Represents a class property.
|
||||
/// </summary>
|
||||
public class ClassProperty : IHasProperties, IProperty<IList<IProperty>>
|
||||
public class ClassProperty : HasPropertiesBase, IProperty<IList<IProperty>>
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public required string Name { get; set; }
|
||||
|
@ -36,30 +34,5 @@ public class ClassProperty : IHasProperties, IProperty<IList<IProperty>>
|
|||
};
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IList<IProperty> GetProperties() => Value;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public T GetProperty<T>(string name) where T : IProperty
|
||||
{
|
||||
var property = Value.FirstOrDefault(_properties => _properties.Name == name) ?? throw new InvalidOperationException($"Property '{name}' not found.");
|
||||
if (property is T prop)
|
||||
{
|
||||
return prop;
|
||||
}
|
||||
|
||||
throw new InvalidOperationException($"Property '{name}' is not of type '{typeof(T).Name}'.");
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool TryGetProperty<T>(string name, [NotNullWhen(true)] out T property) where T : IProperty
|
||||
{
|
||||
if (Value.FirstOrDefault(_properties => _properties.Name == name) is T prop)
|
||||
{
|
||||
property = prop;
|
||||
return true;
|
||||
}
|
||||
|
||||
property = default;
|
||||
return false;
|
||||
}
|
||||
public override IList<IProperty> GetProperties() => Value;
|
||||
}
|
||||
|
|
|
@ -31,6 +31,8 @@ public interface IHasProperties
|
|||
/// <param name="name">The name of the property to get.</param>
|
||||
/// <returns>The property with the specified name.</returns>
|
||||
T GetProperty<T>(string name) where T : IProperty;
|
||||
|
||||
T GetMappedProperty<T>(string name) where T : new();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -69,4 +71,61 @@ public abstract class HasPropertiesBase : IHasProperties
|
|||
property = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
public T GetMappedProperty<T>(string name) where T : new()
|
||||
{
|
||||
var property = GetProperty<ClassProperty>(name);
|
||||
return CreateMappedInstance<T>(property);
|
||||
}
|
||||
|
||||
private static object CreatedMappedInstance(Type type, ClassProperty classProperty)
|
||||
{
|
||||
var instance = Activator.CreateInstance(type);
|
||||
|
||||
foreach (var prop in classProperty.Value)
|
||||
{
|
||||
if (type.GetProperty(prop.Name) == null)
|
||||
throw new KeyNotFoundException($"Property '{prop.Name}' not found in '{type.Name}'.");
|
||||
|
||||
switch (prop)
|
||||
{
|
||||
case BoolProperty boolProp:
|
||||
type.GetProperty(prop.Name)?.SetValue(instance, boolProp.Value);
|
||||
break;
|
||||
case ColorProperty colorProp:
|
||||
type.GetProperty(prop.Name)?.SetValue(instance, colorProp.Value);
|
||||
break;
|
||||
case FloatProperty floatProp:
|
||||
type.GetProperty(prop.Name)?.SetValue(instance, floatProp.Value);
|
||||
break;
|
||||
case FileProperty fileProp:
|
||||
type.GetProperty(prop.Name)?.SetValue(instance, fileProp.Value);
|
||||
break;
|
||||
case IntProperty intProp:
|
||||
type.GetProperty(prop.Name)?.SetValue(instance, intProp.Value);
|
||||
break;
|
||||
case ObjectProperty objectProp:
|
||||
type.GetProperty(prop.Name)?.SetValue(instance, objectProp.Value);
|
||||
break;
|
||||
case StringProperty stringProp:
|
||||
type.GetProperty(prop.Name)?.SetValue(instance, stringProp.Value);
|
||||
break;
|
||||
case ClassProperty classProp:
|
||||
var subClassProp = type.GetProperty(prop.Name);
|
||||
subClassProp?.SetValue(instance, CreatedMappedInstance(subClassProp.PropertyType, classProp));
|
||||
break;
|
||||
case EnumProperty enumProp:
|
||||
var enumPropInClass = type.GetProperty(prop.Name);
|
||||
var enumType = enumPropInClass?.PropertyType;
|
||||
enumPropInClass?.SetValue(instance, Enum.Parse(enumType!, string.Join(", ", enumProp.Value)));
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException($"Unknown property type {prop.GetType().Name}");
|
||||
}
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
private static T CreateMappedInstance<T>(ClassProperty classProperty) where T : new() => (T)CreatedMappedInstance(typeof(T), classProperty);
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue