Skip to content

Commit

Permalink
Enable support for generated lists
Browse files Browse the repository at this point in the history
  • Loading branch information
Mike-E-angelo committed Oct 24, 2024
1 parent c1f19d6 commit 1adeafd
Show file tree
Hide file tree
Showing 11 changed files with 150 additions and 23 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ namespace ExtendedXmlSerializer.ContentModel.Collections
{
class CollectionSpecification : DecoratedSpecification<TypeInfo>
{
public CollectionSpecification(ISpecification<TypeInfo> specification)
protected CollectionSpecification(ISpecification<TypeInfo> specification)
: base(IsCollectionTypeSpecification.Default.And(specification)) {}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,14 @@ namespace ExtendedXmlSerializer.ContentModel.Identification
{
sealed class Identities : CacheBase<TypeInfo, IIdentity>, IIdentities
{
readonly static TypeNameFormatter TypeNameFormatter = TypeNameFormatter.Default;

readonly IIdentityStore _source;
readonly INames _alias;
readonly ITypeFormatter _formatter;
readonly IIdentifiers _identifiers;

[UsedImplicitly]
public Identities(IIdentifiers identifiers, IIdentityStore source, INames names)
: this(source, names, TypeNameFormatter, identifiers) {}
: this(source, names, TypeNameFormatter.Default, identifiers) {}

// ReSharper disable once TooManyDependencies
public Identities(IIdentityStore source, INames alias, ITypeFormatter formatter, IIdentifiers identifiers)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,7 @@ sealed class GenericNameParser : Parsing<string>

GenericNameParser() : this(CodeIdentifier.Default, Parse.Char(DefaultClrDelimiters.Default.Generic)) {}

public GenericNameParser(Parser<string> identifier, Parser<char> delimiter) : base(
identifier
.SelectMany(delimiter
.Optional()
.Accept,
(name, _)
=> name)
) {}
public GenericNameParser(Parser<string> identifier, Parser<char> delimiter)
: base(identifier.SelectMany(delimiter.Optional().Accept, (name, _) => name)) {}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ public override IEnumerator<ISerializerExtension> GetEnumerator()
yield return new AllowedMembersExtension(_metadata);
yield return new AllowedMemberValuesExtension();
yield return new MemberFormatExtension();
yield return GeneratedListAwareExtension.Default;
yield return ImmutableArrayExtension.Default;
yield return SerializationExtension.Default;
yield return RecursionAwareExtension.Default;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
using ExtendedXmlSerializer.ContentModel.Conversion;
using ExtendedXmlSerializer.ContentModel.Reflection;
using ExtendedXmlSerializer.Core.Sources;
using ExtendedXmlSerializer.Core.Specifications;
using System;
using System.Reflection;

namespace ExtendedXmlSerializer.ExtensionModel;

sealed class GeneratedListAwareExtension : ISerializerExtension
{
public static GeneratedListAwareExtension Default { get; } = new();

GeneratedListAwareExtension() {}

public IServiceRepository Get(IServiceRepository parameter)
=> parameter.Decorate<ITypePartResolver, TypePartResolver>();

public void Execute(IServices parameter) {}

sealed class TypePartResolver : ITypePartResolver
{
readonly ITypePartResolver _previous;
readonly ISpecification<TypeInfo> _specification;
readonly IAlteration<Type> _alter;

public TypePartResolver(ITypePartResolver previous)
: this(previous, IsGeneratedList.Default, GeneratedSubstitute.Default) {}

public TypePartResolver(ITypePartResolver previous, ISpecification<TypeInfo> specification,
IAlteration<Type> alter)
{
_previous = previous;
_specification = specification;
_alter = alter;
}

public TypeParts Get(TypeInfo parameter)
{
var type = _specification.IsSatisfiedBy(parameter) ? _alter.Get(parameter) : parameter;
var result = _previous.Get(type);
return result;
}
}
}
29 changes: 29 additions & 0 deletions src/ExtendedXmlSerializer/ExtensionModel/GeneratedSubstitute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using ExtendedXmlSerializer.Core.Sources;
using ExtendedXmlSerializer.ReflectionModel;
using System;
using System.Collections.Generic;

namespace ExtendedXmlSerializer.ExtensionModel;

sealed class GeneratedSubstitute : IAlteration<Type>
{
public static GeneratedSubstitute Default { get; } = new();

GeneratedSubstitute() : this(typeof(List<>), CollectionItemTypeLocator.Default) {}

readonly Type _definition;
readonly ICollectionItemTypeLocator _locator;

public GeneratedSubstitute(Type definition, ICollectionItemTypeLocator locator)
{
_definition = definition;
_locator = locator;
}

public Type Get(Type parameter)
{
var arguments = _locator.Get(parameter);
var result = _definition.MakeGenericType(arguments);
return result;
}
}
14 changes: 14 additions & 0 deletions src/ExtendedXmlSerializer/ExtensionModel/IsGeneratedList.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using ExtendedXmlSerializer.Core.Specifications;
using System.Reflection;

namespace ExtendedXmlSerializer.ExtensionModel;

sealed class IsGeneratedList : DelegatedSpecification<TypeInfo>
{
public static IsGeneratedList Default { get; } = new();

IsGeneratedList()
: base(x => x.FullName != null &&
(x.FullName.StartsWith("<>z__ReadOnlySingleElementList") ||
x.FullName.StartsWith("<>z__ReadOnlyArray"))) {}
}
Original file line number Diff line number Diff line change
@@ -1,21 +1,14 @@
using ExtendedXmlSerializer.Core.Specifications;
using System.Collections.Generic;
using ExtendedXmlSerializer.ExtensionModel;
using System.Reflection;

namespace ExtendedXmlSerializer.ReflectionModel
{
sealed class CollectionAwareConstructorLocator : ListConstructorLocator, IConstructorLocator
{
readonly static ISpecification<TypeInfo> Specification = IsInterface.Default.And(IsCollectionType.Instance);
readonly static ISpecification<TypeInfo> Specification
= IsInterface.Default.Or(IsGeneratedList.Default).And(IsCollectionTypeExpandedSpecification.Default);

public CollectionAwareConstructorLocator(IConstructorLocator previous) : base(Specification, previous) {}

sealed class IsCollectionType : AnySpecification<TypeInfo>
{
public static IsCollectionType Instance { get; } = new IsCollectionType();

IsCollectionType() : base(IsCollectionTypeSpecification.Default,
new IsAssignableGenericSpecification(typeof(IReadOnlyCollection<>))) {}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using ExtendedXmlSerializer.Core.Specifications;
using System.Collections.Generic;
using System.Reflection;

namespace ExtendedXmlSerializer.ReflectionModel;

sealed class IsCollectionTypeExpandedSpecification : AnySpecification<TypeInfo>
{
public static IsCollectionTypeExpandedSpecification Default { get; } = new();

IsCollectionTypeExpandedSpecification()
: base(IsCollectionTypeSpecification.Default,
new IsAssignableGenericSpecification(typeof(IReadOnlyList<>)),
new IsAssignableGenericSpecification(typeof(IReadOnlyCollection<>))) {}
}
2 changes: 1 addition & 1 deletion src/ExtendedXmlSerializer/ReflectionModel/IsUnspeakable.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,6 @@ sealed class IsUnspeakable : DelegatedSpecification<TypeInfo>
{
public static IsUnspeakable Default { get; } = new IsUnspeakable();

IsUnspeakable() : base(x => x.Name.StartsWith("<")) {}
IsUnspeakable() : base(x => x.Name.StartsWith("<") || (x.FullName is not null && x.FullName.StartsWith("<"))) {}
}
}
38 changes: 38 additions & 0 deletions test/ExtendedXmlSerializer.Tests.ReportedIssues/Issue637Tests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#if CORE
using ExtendedXmlSerializer.Configuration;
using ExtendedXmlSerializer.Tests.ReportedIssues.Support;
using FluentAssertions;
using System.Collections.Generic;
using Xunit;

namespace ExtendedXmlSerializer.Tests.ReportedIssues
{
public sealed class Issue637Tests
{
[Fact]
public void Verify()
{
var sut = new ConfigurationContainer().Create().ForTesting();
var instance = new Class2();
sut.Cycle(instance).Should().BeEquivalentTo(instance);
}

[Fact]
public void VerifyModified()
{
var sut = new ConfigurationContainer().Create().ForTesting();
var instance = new Class2 { List2 = [23]};
sut.Cycle(instance).Should().BeEquivalentTo(instance);
}

sealed class Class2
{
public IReadOnlyList<int> List1 { get; set; } = []; //this works
public IReadOnlyList<int> List2 { get; set; } = [1]; //this breaks Serialize&Deserialize
public IList<int> List3 { get; set; } = [1,2]; //this works
public IReadOnlyList<int> List4 { get; set; } = new List<int>(){1}; //this works (workaround)
}

}
}
#endif

0 comments on commit 1adeafd

Please sign in to comment.