Skip to content

Commit

Permalink
Introduced type-based serialization interception. (#452)
Browse files Browse the repository at this point in the history
  • Loading branch information
Mike-E-angelo authored Sep 22, 2020
1 parent 7b3917e commit 39ae9e9
Show file tree
Hide file tree
Showing 8 changed files with 409 additions and 12 deletions.
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

0 comments on commit 39ae9e9

Please sign in to comment.