Skip to content

Commit

Permalink
Merge e93a24d into 7b3917e
Browse files Browse the repository at this point in the history
  • Loading branch information
Mike-E-angelo authored Sep 19, 2020
2 parents 7b3917e + e93a24d commit f8e2e91
Show file tree
Hide file tree
Showing 5 changed files with 275 additions and 0 deletions.
15 changes: 15 additions & 0 deletions src/ExtendedXmlSerializer/ExtensionMethodsForContent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,21 @@ public static ITypeConfiguration<T> WithMonitor<T>(this ITypeConfiguration<T> @t
.Apply(Support<T>.Metadata, new SerializationMonitor<T>(monitor))
.Return(@this);

/// <summary>
/// Applies an interceptor for type configuration. An interceptor participates in the serialization pipeline by being
/// introduced during key serialization and deserialization events.
/// </summary>
/// <param name="this">The type to intercept.</param>
/// <param name="interceptor">The interceptor to apply to a type.</param>
/// <typeparam name="T">The type argument of the type configuration being configured.</typeparam>
/// <returns>The configured type configuration.</returns>
/// <seealso href="https://github.com/ExtendedXmlSerializer/home/issues/451" />
public static ITypeConfiguration<T> WithInterceptor<T>(this ITypeConfiguration<T> @this,
ISerializationInterceptor<T> interceptor)
=> @this.Root.With<SerializationInterceptionExtension>()
.Apply(Support<T>.Metadata, new SerializationInterceptor<T>(interceptor))
.Return(@this);

/// <summary>
/// Allows content to be read as parameters for a constructor call to activate an object, rather than the more
/// traditional route of activating an object and its content read as property assignments. This is preferred --
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
using ExtendedXmlSerializer.ContentModel.Format;
using System;

namespace ExtendedXmlSerializer.ExtensionModel.Instances
{
/// <inheritdoc />
public interface ISerializationInterceptor : ISerializationInterceptor<object> {}

/// <summary>
/// Provides a mechanism to partake in the serialization pipeline.
/// </summary>
/// <typeparam name="T">The type to serialize.</typeparam>
/// <seealso href="https://github.com/ExtendedXmlSerializer/home/issues/451" />
public interface ISerializationInterceptor<T>
{
/// <summary>
/// Called when an instance is being serialized.
/// </summary>
/// <param name="writer">The writer performing the serialization.</param>
/// <param name="instance">The instance to serialize.</param>
/// <returns>The actual instance to serialize.</returns>
T Serializing(IFormatWriter writer, T instance);

/// <summary>
/// Returns the instance to activate during deserialization. If `null` is returned, the default internal infrastructure is used to activate the object.
/// </summary>
/// <param name="instanceType">The type to activate.</param>
/// <returns>An instance of the provided type to activate. If `null`, the default internal activation is used.</returns>
T Activating(Type instanceType);

/// <summary>
/// Called after an object has been activated and properties have been assigned their values from deserialization.
/// </summary>
/// <param name="reader">The reader used to deserialize the provided instance</param>
/// <param name="instance">The instance that was deserialized.</param>
/// <returns>The actual instance deserialized.</returns>
T Deserialized(IFormatReader reader, T instance);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
using ExtendedXmlSerializer.ContentModel;
using ExtendedXmlSerializer.ContentModel.Content;
using ExtendedXmlSerializer.ContentModel.Format;
using ExtendedXmlSerializer.Core;
using ExtendedXmlSerializer.Core.Sources;
using ExtendedXmlSerializer.ReflectionModel;
using JetBrains.Annotations;
using System;
using System.Reflection;

namespace ExtendedXmlSerializer.ExtensionModel.Instances
{
sealed class SerializationInterceptionExtension
: ISerializerExtension, IAssignable<TypeInfo, ISerializationInterceptor>
{
readonly ITypedTable<ISerializationInterceptor> _registrations;

[UsedImplicitly]
public SerializationInterceptionExtension() : this(new TypedTable<ISerializationInterceptor>()) {}

public SerializationInterceptionExtension(ITypedTable<ISerializationInterceptor> registrations)
=> _registrations = registrations;

public IServiceRepository Get(IServiceRepository parameter) => parameter.Decorate<IActivators>(Register)
.Decorate<IContents>(Register);

IContents Register(IServiceProvider _, IContents previous) => new Contents(_registrations, previous);

IActivators Register(IServiceProvider _, IActivators previous) => new Activators(_registrations, previous);

void ICommand<IServices>.Execute(IServices parameter) {}

sealed class Contents : IContents
{
readonly ITypedTable<ISerializationInterceptor> _interceptors;
readonly IContents _contents;

public Contents(ITypedTable<ISerializationInterceptor> interceptors, IContents contents)
{
_interceptors = interceptors;
_contents = contents;
}

public ISerializer Get(TypeInfo parameter)
{
var previous = _contents.Get(parameter);
var result = _interceptors.IsSatisfiedBy(parameter) ? Create(parameter, previous) : previous;
return result;
}

Serializer Create(TypeInfo parameter, ISerializer previous)
{
var interceptor = _interceptors.Get(parameter);
var result = new Serializer(new Reader(interceptor, previous), new Writer(interceptor, previous));
return result;
}
}

sealed class Activators : IActivators
{
readonly ITypedTable<ISerializationInterceptor> _table;
readonly IActivators _activators;

public Activators(ITypedTable<ISerializationInterceptor> table, IActivators activators)
{
_table = table;
_activators = activators;
}

public IActivator Get(Type parameter)
=> new Activator(parameter, _activators.Get(parameter), _table.Get(parameter));

sealed class Activator : IActivator
{
readonly Type _instanceType;
readonly IActivator _activator;
readonly ISerializationInterceptor _interceptor;

public Activator(Type instanceType, IActivator activator, ISerializationInterceptor interceptor)
{
_instanceType = instanceType;
_activator = activator;
_interceptor = interceptor;
}

public object Get() => _interceptor.Activating(_instanceType) ?? _activator.Get();
}
}

sealed class Writer : IWriter
{
readonly ISerializationInterceptor _interceptor;
readonly IWriter _writer;

public Writer(ISerializationInterceptor interceptor, IWriter writer)
{
_interceptor = interceptor;
_writer = writer;
}

public void Write(IFormatWriter writer, object instance)
{
_writer.Write(writer, _interceptor.Serializing(writer, instance));
}
}

sealed class Reader : IReader
{
readonly ISerializationInterceptor _interceptor;
readonly IReader _reader;

public Reader(ISerializationInterceptor interceptor, IReader reader)
{
_interceptor = interceptor;
_reader = reader;
}

public object Get(IFormatReader parameter)
{
var instance = _reader.Get(parameter);
var result = _interceptor.Deserialized(parameter, instance);
return result;
}
}

public void Assign(TypeInfo key, ISerializationInterceptor value)
{
_registrations.Assign(key, value);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using ExtendedXmlSerializer.ContentModel.Format;
using System;

namespace ExtendedXmlSerializer.ExtensionModel.Instances
{
sealed class SerializationInterceptor<T> : ISerializationInterceptor
{
readonly ISerializationInterceptor<T> _interceptor;

public SerializationInterceptor(ISerializationInterceptor<T> interceptor) => _interceptor = interceptor;

public object Serializing(IFormatWriter writer, object instance)
=> _interceptor.Serializing(writer, (T)instance);

public object Activating(Type instanceType) => _interceptor.Activating(instanceType);

public object Deserialized(IFormatReader reader, object instance)
=> _interceptor.Deserialized(reader, (T)instance);
}
}
70 changes: 70 additions & 0 deletions test/ExtendedXmlSerializer.Tests.ReportedIssues/Issue451Tests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
using ExtendedXmlSerializer.Configuration;
using ExtendedXmlSerializer.ContentModel.Format;
using ExtendedXmlSerializer.ExtensionModel.Instances;
using ExtendedXmlSerializer.Tests.ReportedIssues.Support;
using FluentAssertions;
using System;
using Xunit;

namespace ExtendedXmlSerializer.Tests.ReportedIssues
{
public sealed class Issue451Tests
{
[Fact]
public void Verify()
{
var provider = new ServiceLocator();
var serializer = new ConfigurationContainer().Type<Subject>()
.WithInterceptor(new Interceptor(provider))
.Create()
.ForTesting();

var instance = new Subject();

instance.Count.Should().Be(0);
const string content =
@"<?xml version=""1.0"" encoding=""utf-8""?><Issue451Tests-Subject xmlns=""clr-namespace:ExtendedXmlSerializer.Tests.ReportedIssues;assembly=ExtendedXmlSerializer.Tests.ReportedIssues""><Count>1</Count></Issue451Tests-Subject>";
serializer.Assert(instance, content);
instance.Count.Should().Be(1);

var cycled = serializer.Deserialize<Subject>(content);
cycled.Should().BeOfType<ActivatedSubject>();
cycled.Count.Should().Be(2);
}

sealed class ServiceLocator : IServiceProvider
{
public object GetService(Type serviceType) => typeof(Subject).IsAssignableFrom(serviceType)
? new ActivatedSubject()
: throw new InvalidOperationException();
}

sealed class Interceptor : ISerializationInterceptor<Subject>
{
readonly IServiceProvider _provider;

public Interceptor(IServiceProvider provider) => _provider = provider;

public Subject Serializing(IFormatWriter writer, Subject instance)
{
instance.Count++;
return instance;
}

public Subject Activating(Type instanceType) => (Subject)_provider.GetService(instanceType);

public Subject Deserialized(IFormatReader reader, Subject instance)
{
instance.Count++;
return instance;
}
}

sealed class ActivatedSubject : Subject {}

class Subject
{
public uint Count { get; set; }
}
}
}

0 comments on commit f8e2e91

Please sign in to comment.