Skip to content

Commit

Permalink
Introduced basic serialization pipeline interception.
Browse files Browse the repository at this point in the history
  • Loading branch information
Mike-E-angelo committed Sep 19, 2020
1 parent 7b3917e commit e8cc4cf
Show file tree
Hide file tree
Showing 5 changed files with 235 additions and 0 deletions.
6 changes: 6 additions & 0 deletions src/ExtendedXmlSerializer/ExtensionMethodsForContent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,12 @@ public static ITypeConfiguration<T> WithMonitor<T>(this ITypeConfiguration<T> @t
.Apply(Support<T>.Metadata, new SerializationMonitor<T>(monitor))
.Return(@this);

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,16 @@
using ExtendedXmlSerializer.ContentModel.Format;
using System;

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

public interface ISerializationInterceptor<T>
{
T Serializing(IFormatWriter writer, T instance);

T Activating(Type instanceType);

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);
}
}
62 changes: 62 additions & 0 deletions test/ExtendedXmlSerializer.Tests.ReportedIssues/Issue451Tests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
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 serializer = new ConfigurationContainer().Type<Subject>()
.WithInterceptor(Interceptor.Default)
.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 Interceptor : ISerializationInterceptor<Subject>
{
public static Interceptor Default { get; } = new Interceptor();

Interceptor() {}

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

public Subject Activating(Type instanceType) => new ActivatedSubject();

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 e8cc4cf

Please sign in to comment.