Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduced type-based serialization interception. #452

Merged
merged 5 commits into from
Sep 22, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 2 additions & 7 deletions src/ExtendedXmlSerializer/ContentModel/RuntimeSerializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
using ExtendedXmlSerializer.ContentModel.Format;
using ExtendedXmlSerializer.ContentModel.Reflection;
using JetBrains.Annotations;
using System.Reflection;

namespace ExtendedXmlSerializer.ContentModel
{
Expand All @@ -20,13 +19,9 @@ public RuntimeSerializer(IRuntimeSerialization serialization, IClassification cl

public void Write(IFormatWriter writer, object instance)
{
var typeInfo = instance.GetType()
.GetTypeInfo();
_serialization.Get(typeInfo)
.Write(writer, instance);
_serialization.Get(instance.GetType()).Write(writer, instance);
}

public object Get(IFormatReader reader) => _serialization.Get(_classification.Get(reader))
.Get(reader);
public object Get(IFormatReader reader) => _serialization.Get(_classification.Get(reader)).Get(reader);
}
}
38 changes: 33 additions & 5 deletions src/ExtendedXmlSerializer/ExtensionMethodsForContent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,34 @@ 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.WithInterceptor(new SerializationInterceptor<T>(interceptor));

/// <summary>
/// Applies a generalized 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 interceptor)
=> @this.Root.With<SerializationInterceptionExtension>()
.Apply(Support<T>.Metadata, 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 Expand Up @@ -310,23 +338,23 @@ public static IMemberConfiguration Include(this IMemberConfiguration @this)

/// <exclude />
[Obsolete(
"This method is being deprecated and will be removed in a future release. Use Decorate.Element.When instead.")]
"This method is being deprecated and will be removed in a future release. Use Decorate.Element.When instead.")]
public static IServiceRepository Decorate<T>(this IServiceRepository @this,
ISpecification<TypeInfo> specification)
where T : IElement
=> new ConditionalElementDecoration<T>(specification).Get(@this);

/// <exclude />
[Obsolete(
"This method is being deprecated and will be removed in a future release. Use Decorate.Contents.When instead.")]
"This method is being deprecated and will be removed in a future release. Use Decorate.Contents.When instead.")]
public static IServiceRepository DecorateContent<TSpecification, T>(this IServiceRepository @this)
where TSpecification : ISpecification<TypeInfo>
where T : IContents
=> ConditionalContentDecoration<TSpecification, T>.Default.Get(@this);

/// <exclude />
[Obsolete(
"This method is being deprecated and will be removed in a future release. Use Decorate.Contents.When instead.")]
"This method is being deprecated and will be removed in a future release. Use Decorate.Contents.When instead.")]
public static IServiceRepository DecorateContent<T>(this IServiceRepository @this,
ISpecification<TypeInfo> specification) where T : IContents
=> new ConditionalContentDecoration<T>(specification).Get(@this);
Expand All @@ -344,13 +372,13 @@ public static IConfigurationContainer OptimizeConverters(this IConfigurationCont

/// <exclude />
[Obsolete(
"This method will be removed in a future release. Use IConfigurationContainer.IncludeConfiguredMembers instead.")]
"This method will be removed in a future release. Use IConfigurationContainer.IncludeConfiguredMembers instead.")]
public static IConfigurationContainer OnlyConfiguredProperties(this IConfigurationContainer @this)
=> @this.IncludeConfiguredMembers();

/// <exclude />
[Obsolete(
"This method will be removed in a future release. Use ITypeConfiguration<T>.IncludeConfiguredMembers instead.")]
"This method will be removed in a future release. Use ITypeConfiguration<T>.IncludeConfiguredMembers instead.")]
public static ITypeConfiguration<T> OnlyConfiguredProperties<T>(this ITypeConfiguration<T> @this)
=> @this.IncludeConfiguredTypeMembers()
.Return(@this);
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,20 @@
using ExtendedXmlSerializer.ContentModel.Format;
using System;

namespace ExtendedXmlSerializer.ExtensionModel.Instances
{
/// <summary>
/// A generalized serialization interceptor base class for convenience.
/// </summary>
public abstract class SerializationActivator : ISerializationInterceptor
{
/// <inheritdoc />
public virtual object Serializing(IFormatWriter writer, object instance) => instance;

/// <inheritdoc />
public abstract object Activating(Type instanceType);

/// <inheritdoc />
public virtual object Deserialized(IFormatReader reader, object instance) => instance;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
using ExtendedXmlSerializer.ContentModel;
using ExtendedXmlSerializer.ContentModel.Content;
using ExtendedXmlSerializer.ContentModel.Format;
using ExtendedXmlSerializer.Core;
using ExtendedXmlSerializer.Core.Sources;
using ExtendedXmlSerializer.Core.Specifications;
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<IActivatingTypeSpecification>(Register)
.Decorate<IActivators>(Register)
.Decorate<IContents>(Register);

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

IActivatingTypeSpecification Register(IServiceProvider _, IActivatingTypeSpecification previous)
=> new IsActivatingType(_registrations, previous);

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

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

sealed class IsActivatingType : AnySpecification<TypeInfo>, IActivatingTypeSpecification
{
public IsActivatingType(ISpecification<TypeInfo> specification, IActivatingTypeSpecification previous)
: base(specification, previous) {}
}

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 _previous;

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

public IActivator Get(Type parameter)
=> _table.IsSatisfiedBy(parameter)
? new Activator(parameter, _previous.Build(parameter), _table.Get(parameter))
: _previous.Get(parameter);

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

public Activator(Type instanceType, Func<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);
}
}
Loading