Skip to content

Commit

Permalink
Move types into their own files.
Browse files Browse the repository at this point in the history
  • Loading branch information
drewnoakes committed Aug 29, 2016
1 parent d30ec24 commit 6f1575d
Show file tree
Hide file tree
Showing 6 changed files with 233 additions and 115 deletions.
42 changes: 42 additions & 0 deletions Servant/Lifestyle.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
#region License
//
// Servant
//
// Copyright 2016 Drew Noakes
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// More information about this project is available at:
//
// https://github.com/drewnoakes/servant
//
#endregion

namespace Servant
{
/// <summary>
/// Specifies how instances are reused between dependants.
/// </summary>
public enum Lifestyle
{
/// <summary>
/// Only a single instance of the service will be created.
/// </summary>
Singleton,

/// <summary>
/// A new instance of the service will be created for each dependant.
/// </summary>
Transient
}
}
115 changes: 0 additions & 115 deletions Servant/Servant.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,111 +25,12 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;

namespace Servant
{
/// <summary>
/// Specifies how instances are reused between dependants.
/// </summary>
public enum Lifestyle
{
/// <summary>
/// Only a single instance of the service will be created.
/// </summary>
Singleton,

/// <summary>
/// A new instance of the service will be created for each dependant.
/// </summary>
Transient
}

internal sealed class TypeEntry
{
public Type DeclaredType { get; }

[CanBeNull] public TypeProvider Provider { get; set; }

public TypeEntry(Type declaredType)
{
DeclaredType = declaredType;
}
}

internal sealed class TypeProvider
{
public Lifestyle Lifestyle { get; }
public IReadOnlyList<TypeEntry> Dependencies { get; }

private readonly Servant _servant;
private readonly Func<object[], Task<object>> _factory;
private readonly Type _declaredType;

[CanBeNull] private object _singletonInstance;

public TypeProvider(Servant servant, Func<object[], Task<object>> factory, Type declaredType, Lifestyle lifestyle, IReadOnlyList<TypeEntry> dependencies)
{
_servant = servant;
_factory = factory;
_declaredType = declaredType;
Lifestyle = lifestyle;
Dependencies = dependencies;
}

public async Task<object> GetAsync()
{
// TODO make concurrency-safe here to avoid double-allocation of singleton

if (Lifestyle == Lifestyle.Singleton && _singletonInstance != null)
return _singletonInstance;

// find arguments
var argumentTasks = new List<Task<object>>();
foreach (var dep in Dependencies)
{
if (dep.Provider == null)
{
// No provider exists for this dependency.
var message = $"Type \"{_declaredType}\" depends upon unregistered type \"{dep.DeclaredType}\".";

// See whether we have a super-type of the requested type.
var superTypes = _servant.GetRegisteredTypes().Where(type => type.IsAssignableFrom(dep.DeclaredType)).ToList();
if (superTypes.Any())
message += $" Did you mean to reference registered super type {string.Join(" or ", superTypes.Select(st => $"\"{st}\""))}?";

throw new ServantException(message);
}
argumentTasks.Add(dep.Provider.GetAsync());
}

await Task.WhenAll(argumentTasks);

var instance = await _factory.Invoke(argumentTasks.Select(t => t.Result).ToArray());

if (instance == null)
throw new ServantException($"Instance for type \"{_declaredType}\" cannot be null.");

if (!_declaredType.IsInstanceOfType(instance))
throw new ServantException($"Instance produced for type \"{_declaredType}\" is not an instance of that type.");

if (Lifestyle == Lifestyle.Singleton)
{
_singletonInstance = instance;

var disposable = instance as IDisposable;
if (disposable != null)
_servant.PushDisposableSingleton(disposable);
}

return instance;
}
}

/// <summary>
/// Serves instances of specific types, resolving dependencies as required, and running any async initialisation.
/// </summary>
Expand Down Expand Up @@ -303,20 +204,4 @@ public void Dispose()
disposable.Dispose();
}
}

/// <summary>
/// An exception raised by Servant.
/// </summary>
[ExcludeFromCodeCoverage]
public sealed class ServantException : Exception
{
/// <inheritdoc />
public ServantException() { }

/// <inheritdoc />
public ServantException(string message) : base(message) { }

/// <inheritdoc />
public ServantException(string message, Exception innerException) : base(message, innerException) { }
}
}
4 changes: 4 additions & 0 deletions Servant/Servant.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -39,15 +39,19 @@
<Reference Include="System.Core" />
</ItemGroup>
<ItemGroup>
<Compile Include="Lifestyle.cs" />
<Compile Include="Servant.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="ServantException.cs" />
<Compile Include="ServantExtensions.cs" />
<Compile Include="ServantExtensions.Generated.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
<DependentUpon>ServantExtensions.Generated.tt</DependentUpon>
</Compile>
<Compile Include="TaskUtil.cs" />
<Compile Include="TypeEntry.cs" />
<Compile Include="TypeProvider.cs" />
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
Expand Down
45 changes: 45 additions & 0 deletions Servant/ServantException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
#region License
//
// Servant
//
// Copyright 2016 Drew Noakes
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// More information about this project is available at:
//
// https://github.com/drewnoakes/servant
//
#endregion

using System;
using System.Diagnostics.CodeAnalysis;

namespace Servant
{
/// <summary>
/// An exception raised by Servant.
/// </summary>
[ExcludeFromCodeCoverage]
public sealed class ServantException : Exception
{
/// <inheritdoc />
public ServantException() { }

/// <inheritdoc />
public ServantException(string message) : base(message) { }

/// <inheritdoc />
public ServantException(string message, Exception innerException) : base(message, innerException) { }
}
}
41 changes: 41 additions & 0 deletions Servant/TypeEntry.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#region License
//
// Servant
//
// Copyright 2016 Drew Noakes
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// More information about this project is available at:
//
// https://github.com/drewnoakes/servant
//
#endregion

using System;
using JetBrains.Annotations;

namespace Servant
{
internal sealed class TypeEntry
{
public Type DeclaredType { get; }

[CanBeNull] public TypeProvider Provider { get; set; }

public TypeEntry(Type declaredType)
{
DeclaredType = declaredType;
}
}
}
101 changes: 101 additions & 0 deletions Servant/TypeProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
#region License
//
// Servant
//
// Copyright 2016 Drew Noakes
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// More information about this project is available at:
//
// https://github.com/drewnoakes/servant
//
#endregion

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using JetBrains.Annotations;

namespace Servant
{
internal sealed class TypeProvider
{
public Lifestyle Lifestyle { get; }
public IReadOnlyList<TypeEntry> Dependencies { get; }

private readonly Servant _servant;
private readonly Func<object[], Task<object>> _factory;
private readonly Type _declaredType;

[CanBeNull] private object _singletonInstance;

public TypeProvider(Servant servant, Func<object[], Task<object>> factory, Type declaredType, Lifestyle lifestyle, IReadOnlyList<TypeEntry> dependencies)
{
_servant = servant;
_factory = factory;
_declaredType = declaredType;
Lifestyle = lifestyle;
Dependencies = dependencies;
}

public async Task<object> GetAsync()
{
// TODO make concurrency-safe here to avoid double-allocation of singleton

if (Lifestyle == Lifestyle.Singleton && _singletonInstance != null)
return _singletonInstance;

// find arguments
var argumentTasks = new List<Task<object>>();
foreach (var dep in Dependencies)
{
if (dep.Provider == null)
{
// No provider exists for this dependency.
var message = $"Type \"{_declaredType}\" depends upon unregistered type \"{dep.DeclaredType}\".";

// See whether we have a super-type of the requested type.
var superTypes = _servant.GetRegisteredTypes().Where(type => type.IsAssignableFrom(dep.DeclaredType)).ToList();
if (superTypes.Any())
message += $" Did you mean to reference registered super type {string.Join(" or ", superTypes.Select(st => $"\"{st}\""))}?";

throw new ServantException(message);
}
argumentTasks.Add(dep.Provider.GetAsync());
}

await Task.WhenAll(argumentTasks);

var instance = await _factory.Invoke(argumentTasks.Select(t => t.Result).ToArray());

if (instance == null)
throw new ServantException($"Instance for type \"{_declaredType}\" cannot be null.");

if (!_declaredType.IsInstanceOfType(instance))
throw new ServantException($"Instance produced for type \"{_declaredType}\" is not an instance of that type.");

if (Lifestyle == Lifestyle.Singleton)
{
_singletonInstance = instance;

var disposable = instance as IDisposable;
if (disposable != null)
_servant.PushDisposableSingleton(disposable);
}

return instance;
}
}
}

0 comments on commit 6f1575d

Please sign in to comment.