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

Refactor Composition Context to prevent composition overwrite #35

Merged
merged 2 commits into from
May 19, 2017
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
35 changes: 15 additions & 20 deletions src/Abioc/Composition/CodeComposition.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ private static (string generatedCode, object[] fieldValues) GenerateCode(
IReadOnlyList<IComposition> compositions =
context.Compositions.Values.DistinctBy(r => r.Type).OrderBy(r => r.Type.ToCompileName()).ToList();

var code = new CodeCompositions(registrations, context.ConstructionContext);
var code = new CodeCompositions(registrations);

// First try with simple method names.
foreach (IComposition composition in compositions)
Expand Down Expand Up @@ -106,7 +106,7 @@ private static string GenerateCode(CompositionContext context, CodeCompositions
if (code == null)
throw new ArgumentNullException(nameof(code));

string genericContainerParam = code.HasConstructionContext ? $"<{context.ExtraDataType}>" : string.Empty;
string genericContainerParam = context.HasConstructionContext ? $"<{context.ExtraDataType}>" : string.Empty;

var builder = new StringBuilder(10240);
builder.AppendFormat(
Expand All @@ -125,7 +125,7 @@ private static string GenerateCode(CompositionContext context, CodeCompositions
builder.Append(fieldInitializationsMethod);

builder.Append(NewLine);
string composeMapMethod = GenerateComposeMapMethod(code);
string composeMapMethod = GenerateComposeMapMethod(context, code);
composeMapMethod = CodeGen.Indent(NewLine + composeMapMethod, 2);
builder.Append(composeMapMethod);

Expand Down Expand Up @@ -186,13 +186,15 @@ private static string GenerateConstructor(CodeCompositions code)
return builder.ToString();
}

private static string GenerateComposeMapMethod(CodeCompositions code)
private static string GenerateComposeMapMethod(CompositionContext context, CodeCompositions code)
{
if (context == null)
throw new ArgumentNullException(nameof(context));
if (code == null)
throw new ArgumentNullException(nameof(code));

string composeMapType = code.HasConstructionContext
? $"System.Collections.Generic.Dictionary<System.Type, System.Func<{code.ConstructionContext}, object>>"
string composeMapType = context.HasConstructionContext
? $"System.Collections.Generic.Dictionary<System.Type, System.Func<{context.ConstructionContext}, object>>"
: "System.Collections.Generic.Dictionary<System.Type, System.Func<object>>";

var builder = new StringBuilder(1024);
Expand All @@ -204,7 +206,7 @@ private static string GenerateComposeMapMethod(CodeCompositions code)
string initializers =
string.Join(
NewLine,
code.ComposeMethods.Select(c => GenerateComposeMapInitializer(code.HasConstructionContext, c)));
code.ComposeMethods.Select(c => GenerateComposeMapInitializer(context.HasConstructionContext, c)));
initializers = CodeGen.Indent(NewLine + initializers, 2);
builder.Append(initializers);

Expand All @@ -219,11 +221,11 @@ private static string GenerateGetServiceMethod(CompositionContext context, CodeC
if (code == null)
throw new ArgumentNullException(nameof(code));

string parameter = code.HasConstructionContext
string parameter = context.HasConstructionContext
? $",{NewLine} {context.ExtraDataType} extraData"
: string.Empty;

string contextVariable = code.HasConstructionContext
string contextVariable = context.HasConstructionContext
? $"{NewLine} var context = {context.ConstructionContext}.Default.Initialize(extraData);"
: string.Empty;

Expand All @@ -250,7 +252,7 @@ string GetCaseSnippet(Type key, IComposition composition)
{
string keyComment = key.ToCompileName();
string instanceExpression = composition.GetInstanceExpression(context, code.UsingSimpleNames);
instanceExpression = CodeGen.Indent(instanceExpression, 1);
instanceExpression = CodeGen.Indent(instanceExpression);

string caseSnippet =
$"case {key.GetHashCode()}: // {keyComment}{NewLine} return {instanceExpression};";
Expand All @@ -269,11 +271,11 @@ private static string GenerateGetServicesMethod(CompositionContext context, Code
if (code == null)
throw new ArgumentNullException(nameof(code));

string parameter = code.HasConstructionContext
string parameter = context.HasConstructionContext
? $",{NewLine} {context.ExtraDataType} extraData"
: string.Empty;

string contextVariable = code.HasConstructionContext
string contextVariable = context.HasConstructionContext
? $"{NewLine} var context = {context.ConstructionContext}.Default.Initialize(extraData);"
: string.Empty;

Expand Down Expand Up @@ -335,23 +337,16 @@ private static string GenerateComposeMapInitializer(

private class CodeCompositions
{
public CodeCompositions(
IReadOnlyDictionary<Type, IRegistration[]> registrations,
string constructionContext = null)
public CodeCompositions(IReadOnlyDictionary<Type, IRegistration[]> registrations)
{
if (registrations == null)
throw new ArgumentNullException(nameof(registrations));

Registrations = registrations;
ConstructionContext = constructionContext;
}

public IReadOnlyDictionary<Type, IRegistration[]> Registrations { get; }

public string ConstructionContext { get; }

public bool HasConstructionContext => !string.IsNullOrWhiteSpace(ConstructionContext);

public List<(string name, Type type, bool requiresContext)> ComposeMethods { get; } =
new List<(string, Type, bool)>(32);

Expand Down
83 changes: 82 additions & 1 deletion src/Abioc/Composition/CompositionContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,15 @@ namespace Abioc.Composition
using System;
using System.Collections.Generic;
using System.Linq;
using Abioc.Composition.Compositions;

/// <summary>
/// The composition context.
/// </summary>
public class CompositionContext
{
private readonly Dictionary<Type, IComposition> _compositions = new Dictionary<Type, IComposition>(32);

/// <summary>
/// Initializes a new instance of the <see cref="CompositionContext"/> class.
/// </summary>
Expand All @@ -26,7 +29,7 @@ public CompositionContext(string extraDataType = null, string constructionContex
/// <summary>
/// Gets the context.
/// </summary>
public Dictionary<Type, IComposition> Compositions { get; } = new Dictionary<Type, IComposition>(32);
public IReadOnlyDictionary<Type, IComposition> Compositions => _compositions;

/// <summary>
/// Gets the type of the <see cref="ConstructionContext{T}.Extra"/> data.
Expand All @@ -37,5 +40,83 @@ public CompositionContext(string extraDataType = null, string constructionContex
/// Gets the type of the <see cref="ConstructionContext{T}"/>.
/// </summary>
public string ConstructionContext { get; }

/// <summary>
/// Gets a value indicating whether
/// </summary>
public bool HasConstructionContext => !string.IsNullOrWhiteSpace(ConstructionContext);

/// <summary>
/// Removes the <see cref="IComposition"/> from the <see cref="Compositions"/> for the specified
/// <paramref name="type"/> returning the value.
/// </summary>
/// <param name="type">The type under which the <see cref="IComposition"/> is keyed.</param>
/// <returns>The <see cref="IComposition"/> that was removed from the <see cref="Compositions"/>.</returns>
/// <exception cref="CompositionException">
/// There is no <see cref="IComposition"/> for the specified <paramref name="type"/>.
/// </exception>
public IComposition RemoveComposition(Type type)
{
if (type == null)
throw new ArgumentNullException(nameof(type));

if (!_compositions.TryGetValue(type, out IComposition composition))
{
throw new CompositionException($"There is no current composition for the type '{type}'.");
}

_compositions.Remove(type);
return composition;
}

/// <summary>
/// Adds the <paramref name="composition"/> to the <see cref="Compositions"/>.
/// </summary>
/// <param name="composition">The <see cref="IComposition"/> to add.</param>
/// <exception cref="CompositionException">
/// There is already a <see cref="IComposition"/> for the specified
/// <paramref name="composition"/>.<see cref="IComposition.Type"/> that is not the
/// default and cannot be superseded by specified <paramref name="composition"/>.
/// </exception>
public void AddComposition(IComposition composition)
{
if (composition == null)
throw new ArgumentNullException(nameof(composition));

AddComposition(composition.Type, composition);
}

/// <summary>
/// Adds the <paramref name="composition"/> to the <see cref="Compositions"/>.
/// </summary>
/// <param name="type">The <see cref="Type"/> that is satisfied by the <paramref name="composition"/></param>
/// <param name="composition">The <see cref="IComposition"/> to add.</param>
/// <exception cref="CompositionException">
/// There is already a <see cref="IComposition"/> for the specified <paramref name="type"/> that is not the
/// default and cannot be superseded by specified <paramref name="composition"/>.
/// </exception>
public void AddComposition(Type type, IComposition composition)
{
if (type == null)
throw new ArgumentNullException(nameof(type));
if (composition == null)
throw new ArgumentNullException(nameof(composition));

if (_compositions.TryGetValue(type, out IComposition existing))
{
#pragma warning disable SA1119 // Statement must not use unnecessary parenthesis
if (!(existing is ConstructorComposition constructorComposition) || !constructorComposition.IsDefault)
#pragma warning restore SA1119 // Statement must not use unnecessary parenthesis
{
string message =
$"There is already a composition for '{type}', are there multiple registrations. " +
$"The Existing composition is '{existing.GetType()}', the new composition is " +
$"'{composition.GetType()}'.";
throw new CompositionException(message);
}
}

_compositions[type] = composition;
}
}
}
14 changes: 13 additions & 1 deletion src/Abioc/Composition/Compositions/ConstructorComposition.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,14 @@ public class ConstructorComposition : CompositionBase
/// </summary>
/// <param name="type">The <see cref="Type"/> created by the constructor.</param>
/// <param name="parameters">The <see cref="Parameters"/> of the constructor.</param>
/// <param name="isDefault">
/// A value indicating whether this is the default composition, and therefore can be superseded by
/// another composition.
/// </param>
public ConstructorComposition(
Type type,
IReadOnlyList<ParameterInfo> parameters)
IReadOnlyList<ParameterInfo> parameters,
bool isDefault = false)
{
if (type == null)
throw new ArgumentNullException(nameof(type));
Expand All @@ -31,6 +36,7 @@ public ConstructorComposition(

Type = type;
Parameters = parameters;
IsDefault = isDefault;

_parameterExpressions = new List<IParameterExpression>(parameters.Count);
}
Expand All @@ -45,6 +51,12 @@ public ConstructorComposition(
/// </summary>
public IReadOnlyList<ParameterInfo> Parameters { get; }

/// <summary>
/// Gets a value indicating whether this is the default composition, and therefore can be superseded by
/// another composition.
/// </summary>
public bool IsDefault { get; }

/// <inheritdoc/>
public override string GetInstanceExpression(CompositionContext context, bool simpleName)
{
Expand Down
4 changes: 2 additions & 2 deletions src/Abioc/Composition/RegistrationComposition.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,9 @@ from kvp in registrations
select (kvp.Key, kvp.Value.Single());

// Re-reference the compositions under the type mappings.
foreach ((Type type, IRegistration registration) in typeMappings)
foreach ((Type serviceType, IRegistration registration) in typeMappings)
{
context.Compositions[type] = context.Compositions[registration.ImplementationType];
context.AddComposition(serviceType, context.Compositions[registration.ImplementationType]);
}

return context;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public void Accept(FactoryRegistration<TExtra> registration)

Type type = registration.ImplementationType;
var composition = new FactoryComposition(type, registration.Factory, typeof(ConstructionContext<TExtra>));
_context.Compositions[type] = composition;
_context.AddComposition(composition);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public void Accept(FactoryRegistration registration)

Type type = registration.ImplementationType;
var composition = new FactoryComposition(type, registration.Factory);
_context.Compositions[type] = composition;
_context.AddComposition(composition);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public void Accept(InjectedSingletonRegistration<TImplementation> registration)
throw new ArgumentNullException(nameof(registration));

IComposition composition = new InjectedSingletonComposition<TImplementation>(registration.Value);
_context.Compositions[composition.Type] = composition;
_context.AddComposition(composition);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,17 +42,18 @@ public void Accept(PropertyDependencyRegistration registration)
if (registration == null)
throw new ArgumentNullException(nameof(registration));

(string property, Type type)[] propertiesToInject =
GetPropertiesToInject(registration).Select(p => (p.Name, p.PropertyType)).ToArray();

// Visit the inner registration which will add a composition.
_manager.Visit(registration.Inner);

// Get the original composition.
IComposition inner = _context.Compositions[registration.ImplementationType];
// Get the original composition, removing it to allow it to be replaced.
IComposition inner = _context.RemoveComposition(registration.ImplementationType);

(string property, Type type)[] propertiesToInject =
GetPropertiesToInject(registration).Select(p => (p.Name, p.PropertyType)).ToArray();

// Replace the inner composition.
IComposition composition = new PropertyDependencyComposition(inner, propertiesToInject);
_context.Compositions[composition.Type] = composition;
_context.AddComposition(composition);
}

/// <inheritdoc />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,8 @@ public void Accept(SingleConstructorRegistration registration)
}

ParameterInfo[] parameters = constructors[0].GetParameters();
IComposition composition = new ConstructorComposition(type, parameters);
_context.Compositions[composition.Type] = composition;
IComposition composition = new ConstructorComposition(type, parameters, isDefault: true);
_context.AddComposition(composition);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,15 @@ public void Accept(SingletonRegistration registration)
if (registration == null)
throw new ArgumentNullException(nameof(registration));

// Visit the inner registration which will add a composition.
_manager.Visit(registration.Inner);

// Get the original composition.
IComposition inner = _context.Compositions[registration.ImplementationType];
// Get the original composition, removing it to allow it to be replaced.
IComposition inner = _context.RemoveComposition(registration.ImplementationType);

// Replace the inner composition.
IComposition composition = new SingletonComposition(inner);
_context.Compositions[composition.Type] = composition;
_context.AddComposition(composition);
}

/// <inheritdoc />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public void Accept(TypedFactoryRegistration<TExtra, TImplementation> registratio
IComposition composition = new TypedFactoryComposition<TImplementation>(
registration.Factory,
typeof(ConstructionContext<TExtra>));
_context.Compositions[composition.Type] = composition;
_context.AddComposition(composition);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public void Accept(TypedFactoryRegistration<TImplementation> registration)
throw new ArgumentNullException(nameof(registration));

IComposition composition = new TypedFactoryComposition<TImplementation>(registration.Factory);
_context.Compositions[composition.Type] = composition;
_context.AddComposition(composition);
}
}
}
Loading