Skip to content

Commit

Permalink
Improved content serializer composition API. (#350)
Browse files Browse the repository at this point in the history
* Added example code.

* Added additional testing around content

* Improved content serializer composition API.
  • Loading branch information
Mike-EEE authored Dec 13, 2019
1 parent 789e82b commit 34500c3
Show file tree
Hide file tree
Showing 12 changed files with 542 additions and 42 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
using ExtendedXmlSerializer.ContentModel;
using ExtendedXmlSerializer.ExtensionModel.Content;
using ExtendedXmlSerializer.ReflectionModel;
using System;
using ISerializer = ExtendedXmlSerializer.ContentModel.ISerializer;

namespace ExtendedXmlSerializer.Configuration
{
/// <summary>
/// Provides a context for registering content composers for a particular type.
/// </summary>
/// <typeparam name="T">The type under configuration.</typeparam>
public sealed class TypeContentCompositionRegistrationContext<T>
{
readonly ITypeConfiguration<T> _configuration;

/// <inheritdoc />
public TypeContentCompositionRegistrationContext(ITypeConfiguration<T> configuration)
=> _configuration = configuration;

/// <summary>
/// Registers a content serializer composer of the specified type.
/// </summary>
/// <typeparam name="TComposer">The type of the content composer to register.</typeparam>
/// <returns>The configured type configuration.</returns>
/// <seealso href="https://github.com/ExtendedXmlSerializer/home/issues/264#issuecomment-531491807"/>
public ITypeConfiguration<T> Of<TComposer>() where TComposer : ISerializerComposer
=> Of(Support<TComposer>.Key);

/// <summary>
/// Registers a content serializer composer of the specified type.
/// </summary>
/// <param name="composerType">The type that implements <see cref="ISerializerComposer"/> of the content composer to
/// register.</param>
/// <returns>The configured type configuration.</returns>
/// <seealso href="https://github.com/ExtendedXmlSerializer/home/issues/264#issuecomment-531491807"/>
public ITypeConfiguration<T> Of(Type composerType)
=> _configuration.Root.With<RegisteredCompositionExtension>()
.Apply(Support<T>.Metadata, composerType)
.Return(_configuration);


/// <summary>
/// Used to alter a serializer whenever one is created for a specific type. This allows the scenario of decorating
/// a serializer to override or monitor serialization and/or deserialization.
/// </summary>
/// <param name="compose">The delegate that defines how to create a content serializer.</param>
/// <returns>The configured type configuration.</returns>
/// <seealso href="https://github.com/ExtendedXmlSerializer/home/issues/264#issuecomment-531491807"/>
public ITypeConfiguration<T> ByCalling(Func<ISerializer<T>, ISerializer<T>> compose)
=> Using(new SerializerComposer<T>(compose));

/// <summary>
/// Used to alter a serializer whenever one is created for a specific type. This allows the scenario of decorating
/// a serializer to override or monitor serialization and/or deserialization. This override accepts a generalized
/// serializer delegate.
/// </summary>
/// <param name="compose">The delegate that defines how to create a content serializer.</param>
/// <returns>The configured type configuration.</returns>
/// <seealso href="https://github.com/ExtendedXmlSerializer/home/issues/264#issuecomment-531491807"/>
public ITypeConfiguration<T> ByCalling(Func<ISerializer, ISerializer> compose)
=> Using(new SerializerComposer(compose));

/// <summary>
/// Used to alter a serializer whenever one is created for a specific type. This allows the scenario of decorating a
/// serializer to override or monitor serialization and/or deserialization. This override accepts an
/// <see cref="ISerializerComposer"/> that performs the alteration on the created serializer.
/// </summary>
/// <param name="composer">The serializer composer to register.</param>
/// <returns>The configured type configuration.</returns>
/// <seealso href="https://github.com/ExtendedXmlSerializer/home/issues/264#issuecomment-531491807"/>
public ITypeConfiguration<T> Using(ISerializerComposer composer)
=> _configuration.Root.With<RegisteredCompositionExtension>()
.Apply(Support<T>.Metadata, composer)
.Return(_configuration);

/// <summary>
/// Clears any registered content serializer composers for the type under configuration.
/// </summary>
/// <returns>The configured type configuration.</returns>
/// <seealso href="https://github.com/ExtendedXmlSerializer/home/issues/264#issuecomment-531491807"/>
public ITypeConfiguration<T> None() => _configuration.Root.With<RegisteredCompositionExtension>()
.Apply(_configuration.Get())
.Return(_configuration);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,13 @@ public TypeSerializationRegistrationContext<T> Serializer()
/// <returns>A context to perform operations on converter registrations for the captured type configuration.</returns>
public TypeConverterRegistrationContext<T> Converter()
=> new TypeConverterRegistrationContext<T>(_configuration);

/// <summary>
/// Establishes a content-composition context.
/// </summary>
/// <returns>A context to perform operations for registering content serializer composition for the type under
/// configuration.</returns>
public TypeContentCompositionRegistrationContext<T> Composition()
=> new TypeContentCompositionRegistrationContext<T>(_configuration);
}
}
19 changes: 17 additions & 2 deletions src/ExtendedXmlSerializer/ExtensionMethods.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using ExtendedXmlSerializer.Core.Sources;
using ExtendedXmlSerializer.Core;
using ExtendedXmlSerializer.Core.Sources;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
Expand Down Expand Up @@ -28,13 +29,27 @@ public static class ExtensionMethods
/// <typeparam name="T">The parameter type.</typeparam>
/// <param name="this">The delegate to call.</param>
/// <param name="parameter">The parameter to pass to the delegate and return.</param>
/// <returns>The delegate.</returns>
/// <returns>The provided parameter.</returns>
public static T Apply<T>(this Action<T> @this, T parameter)
{
@this(parameter);
return parameter;
}

/// <summary>
/// Convenience method to invoke a command and return the parameter. This is useful for fluent-based configuration
/// method calls.
/// </summary>
/// <typeparam name="T">The parameter type.</typeparam>
/// <param name="this">The command to call.</param>
/// <param name="parameter">The parameter to pass to the command and return.</param>
/// <returns>The provided parameter.</returns>
public static T Apply<T>(this ICommand<T> @this, T parameter)
{
@this.Execute(parameter);
return parameter;
}

/// <summary>
/// Convenience method to pass values to an assignable command, and return the command. This is useful for
/// fluent-based configuration method calls.
Expand Down
39 changes: 22 additions & 17 deletions src/ExtendedXmlSerializer/ExtensionMethodsForAlteration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,21 @@ public static IMemberConfiguration<T, TMember> Alter<T, TMember>(this IMemberCon
.Members.Apply(@this.GetMember(), new ContentAlteration(read.Adapt(), write.Adapt()))
.Return(@this);

/// <summary>
/// Provides a way to alter converters when they are accessed by the serializer. This provides a mechanism to
/// decorate converters. Alterations only occur once per converter per serializer.
/// </summary>
/// <param name="this">The container to configure.</param>
/// <param name="alteration">The alteration to perform on each converter when it is accessed by the serializer.</param>
/// <returns>The configured container.</returns>
public static IConfigurationContainer Alter(this IConfigurationContainer @this,
IAlteration<IConverter> alteration)
=> @this.Root.With<ConverterAlterationsExtension>()
.Alterations.Apply(alteration)
.Return(@this);

#region Obsolete

/// <summary>
/// Used to alter a serializer whenever one is created for a specific type. This allows the scenario of decorating
/// a serializer to override or monitor serialization and/or deserialization.
Expand All @@ -124,9 +139,10 @@ public static IMemberConfiguration<T, TMember> Alter<T, TMember>(this IMemberCon
/// <param name="compose">The delegate used to alterate the created serializer.</param>
/// <returns>The configured type configuration.</returns>
/// <seealso href="https://github.com/ExtendedXmlSerializer/home/issues/264#issuecomment-531491807"/>
[Obsolete("This method is considered obsolete and will be removed in a future release. Its new equivalent is ITypeConfiguration<T>.Register().Composition().ByCalling.")]
public static ITypeConfiguration<T> RegisterContentComposition<T>(this ITypeConfiguration<T> @this,
Func<ISerializer<T>, ISerializer<T>> compose)
=> @this.RegisterContentComposition(new SerializerComposer<T>(compose).Get);
=> @this.Register().Composition().ByCalling(compose);

/// <summary>
/// Used to alter a serializer whenever one is created for a specific type. This allows the scenario of decorating
Expand All @@ -138,9 +154,10 @@ public static ITypeConfiguration<T> RegisterContentComposition<T>(this ITypeConf
/// <param name="compose">The delegate used to alterate the created serializer.</param>
/// <returns>The configured type configuration.</returns>
/// <seealso href="https://github.com/ExtendedXmlSerializer/home/issues/264#issuecomment-531491807"/>
[Obsolete("This method is considered obsolete and will be removed in a future release. Its new equivalent is ITypeConfiguration<T>.Register().Composition().ByCalling.")]
public static ITypeConfiguration<T> RegisterContentComposition<T>(this ITypeConfiguration<T> @this,
Func<ISerializer, ISerializer> compose)
=> @this.RegisterContentComposition(new SerializerComposer(compose));
=> @this.Register().Composition().ByCalling(compose);

/// <summary>
/// Used to alter a serializer whenever one is created for a specific type. This allows the scenario of decorating a
Expand All @@ -152,23 +169,11 @@ public static ITypeConfiguration<T> RegisterContentComposition<T>(this ITypeConf
/// <param name="composer">The composer that is used to alter the serializer upon creation.</param>
/// <returns>The configured type configuration.</returns>
/// <seealso href="https://github.com/ExtendedXmlSerializer/home/issues/264#issuecomment-531491807"/>
[Obsolete("This method is considered obsolete and will be removed in a future release. Its new equivalent is ITypeConfiguration<T>.Register().Composition().Using.")]
public static ITypeConfiguration<T> RegisterContentComposition<T>(this ITypeConfiguration<T> @this,
ISerializerComposer composer)
=> @this.Root.With<RegisteredCompositionExtension>()
.Apply(Support<T>.Metadata, composer)
.Return(@this);
=> @this.Register().Composition().Using(composer);

/// <summary>
/// Provides a way to alter converters when they are accessed by the serializer. This provides a mechanism to
/// decorate converters. Alterations only occur once per converter per serializer.
/// </summary>
/// <param name="this">The container to configure.</param>
/// <param name="alteration">The alteration to perform on each converter when it is accessed by the serializer.</param>
/// <returns>The configured container.</returns>
public static IConfigurationContainer Alter(this IConfigurationContainer @this,
IAlteration<IConverter> alteration)
=> @this.Root.With<ConverterAlterationsExtension>()
.Alterations.Apply(alteration)
.Return(@this);
#endregion
}
}
2 changes: 1 addition & 1 deletion src/ExtendedXmlSerializer/ExtensionMethodsForXml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ public static ITypeConfiguration<T> CustomSerializer<T, TSerializer>(this IConfi
/// <param name="this"></param>
/// <param name="serializerType"></param>
/// <returns></returns>
/// /// <seealso cref="TypeSerializationRegistrationContext{T}.Of"/>
/// <seealso cref="TypeSerializationRegistrationContext{T}.Of"/>
public static ITypeConfiguration<T> CustomSerializer<T>(this IConfigurationContainer @this, Type serializerType)
=> @this.Type<T>()
.CustomSerializer(new ActivatedXmlSerializer(serializerType, Support<T>.Metadata));
Expand Down
Original file line number Diff line number Diff line change
@@ -1,25 +1,140 @@
using ExtendedXmlSerializer.ContentModel.Content;
using ExtendedXmlSerializer.Core;
using ExtendedXmlSerializer.Core.Sources;
using ExtendedXmlSerializer.ExtensionModel.Types;
using ExtendedXmlSerializer.ReflectionModel;
using JetBrains.Annotations;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

namespace ExtendedXmlSerializer.ExtensionModel.Content
{
sealed class RegisteredCompositionExtension : ISerializerExtension, IAssignable<TypeInfo, ISerializerComposer>
interface ISerializerComposerResult
: IParameterizedSource<IServices, ISerializerComposer>, IAlteration<IServiceRepository> {}

interface ISerializerComposerResults : ITypedTable<ISerializerComposerResult>, ISerializerExtension {}

sealed class ResultContainer : ITypedTable<ISerializerComposer>, ICommand<ITypedTable<ISerializerComposer>>
{
readonly ITypedTable<ISerializerComposer> _composers;
readonly ITypedTable<ISerializerComposer>[] _store;

public ResultContainer() : this(new ITypedTable<ISerializerComposer>[1]) {}

public ResultContainer(ITypedTable<ISerializerComposer>[] store) => _store = store;

public bool IsSatisfiedBy(TypeInfo parameter) => _store[0].IsSatisfiedBy(parameter);

public ISerializerComposer Get(TypeInfo parameter) => _store[0].Get(parameter);

public void Assign(TypeInfo key, ISerializerComposer value)
{
_store[0].Assign(key, value);
}

public bool Remove(TypeInfo key) => _store[0].Remove(key);

public void Execute(ITypedTable<ISerializerComposer> parameter)
{
_store[0] = parameter;
}
}

sealed class RegisteredCompositionExtension : ISerializerExtension,
IAssignable<TypeInfo, ISerializerComposer>,
IAssignable<TypeInfo, Type>,
ICommand<TypeInfo>

{
readonly ITypedTable<ISerializerComposer> _table;
readonly ISerializerComposerResults _composers;

[UsedImplicitly]
public RegisteredCompositionExtension() : this(new TypedTable<ISerializerComposer>()) {}
public RegisteredCompositionExtension() : this(new ResultContainer()) {}

public RegisteredCompositionExtension(ResultContainer container)
: this(container, new SerializerComposerResults(container)) {}

public RegisteredCompositionExtension(ITypedTable<ISerializerComposer> table,
ISerializerComposerResults composers)
{
_table = table;
_composers = composers;
}

public RegisteredCompositionExtension(ITypedTable<ISerializerComposer> composers) => _composers = composers;
public IServiceRepository Get(IServiceRepository parameter) => _composers.Get(parameter)
.RegisterInstance(_table)
.Decorate<IContents, Contents>();

public IServiceRepository Get(IServiceRepository parameter) => parameter.RegisterInstance(_composers)
.Decorate<IContents, Contents>();
void ICommand<IServices>.Execute(IServices parameter)
{
_composers.Execute(parameter);
}

sealed class SerializerComposerResults : TypedTable<ISerializerComposerResult>, ISerializerComposerResults
{
readonly ICommand<ITypedTable<ISerializerComposer>> _assign;
readonly IDictionary<TypeInfo, ISerializerComposerResult> _store;

void ICommand<IServices>.Execute(IServices parameter) {}
public SerializerComposerResults(ICommand<ITypedTable<ISerializerComposer>> assign)
: this(assign, new Dictionary<TypeInfo, ISerializerComposerResult>()) {}

public SerializerComposerResults(ICommand<ITypedTable<ISerializerComposer>> assign,
IDictionary<TypeInfo, ISerializerComposerResult> store) : base(store)
{
_assign = assign;
_store = store;
}

public IServiceRepository Get(IServiceRepository parameter)
=> _store.Values.Aggregate(parameter, (repository, pair) => pair.Get(repository));

public void Execute(IServices parameter)
{
var table = new TypedTable<ISerializerComposer>();
foreach (var result in _store)
{
table.Assign(result.Key, result.Value.Get(parameter));
}

_assign.Execute(table);
}
}

sealed class FixedSerializerComposerResult
: FixedInstanceSource<IServices, ISerializerComposer>, ISerializerComposerResult
{
public FixedSerializerComposerResult(ISerializerComposer instance) : base(instance) {}

public IServiceRepository Get(IServiceRepository parameter) => parameter;
}

sealed class LocatedSerializerComposerResult : ISerializerComposerResult
{
readonly ISingletonLocator _locator;
readonly Type _composerType;

public LocatedSerializerComposerResult(Type composerType) : this(SingletonLocator.Default, composerType) {}

public LocatedSerializerComposerResult(ISingletonLocator locator, Type composerType)
{
_locator = locator;
_composerType = composerType;
}

public IServiceRepository Get(IServiceRepository parameter)
{
var singleton = _locator.Get(_composerType);
var result = singleton != null
? parameter.RegisterInstance(_composerType, singleton)
: parameter.Register(_composerType);
return result;
}

public ISerializerComposer Get(IServices parameter) => parameter.GetService(_composerType)
.AsValid<ISerializerComposer>();
}

sealed class Contents : IContents
{
Expand All @@ -36,16 +151,25 @@ public ContentModel.ISerializer Get(TypeInfo parameter)
{
var content = _contents.Get(parameter);
var result = _table.IsSatisfiedBy(parameter)
? _table.Get(parameter)
.Get(content)
? _table.Get(parameter).Get(content)
: content;
return result;
}
}

public void Assign(TypeInfo key, ISerializerComposer value)
{
_composers.Assign(key, value);
_composers.Assign(key, new FixedSerializerComposerResult(value));
}

public void Assign(TypeInfo key, Type value)
{
_composers.Assign(key, new LocatedSerializerComposerResult(value));
}

public void Execute(TypeInfo parameter)
{
_composers.Remove(parameter);
}
}
}
Loading

0 comments on commit 34500c3

Please sign in to comment.