mirror of https://github.com/AMT-Cheif/Stylet.git
Split out the type which were all previously in StyletIoCContainer.cs
This commit is contained in:
parent
95f0f8b3e4
commit
6533b34757
|
@ -58,6 +58,10 @@
|
||||||
<Compile Include="LambdaEqualityComparer.cs" />
|
<Compile Include="LambdaEqualityComparer.cs" />
|
||||||
<Compile Include="MessageBox.cs" />
|
<Compile Include="MessageBox.cs" />
|
||||||
<Compile Include="StyletConductorExtensions.cs" />
|
<Compile Include="StyletConductorExtensions.cs" />
|
||||||
|
<Compile Include="StyletIoC\IInjectionAware.cs" />
|
||||||
|
<Compile Include="StyletIoC\InjectAttribute.cs" />
|
||||||
|
<Compile Include="StyletIoC\StyletIoCException.cs" />
|
||||||
|
<Compile Include="StyletIoC\TypeExtensions.cs" />
|
||||||
<Compile Include="ValidatingModelBase.cs" />
|
<Compile Include="ValidatingModelBase.cs" />
|
||||||
<Compile Include="Xaml\ActionExtension.cs" />
|
<Compile Include="Xaml\ActionExtension.cs" />
|
||||||
<Compile Include="AssemblySource.cs" />
|
<Compile Include="AssemblySource.cs" />
|
||||||
|
|
|
@ -0,0 +1,74 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace StyletIoC
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Describes an IoC container, specifically StyletIoC
|
||||||
|
/// </summary>
|
||||||
|
public interface IContainer
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Compile all known bindings (which would otherwise be compiled when needed), checking the dependency graph for consistency
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="throwOnError">If true, throw if we fail to compile a type</param>
|
||||||
|
void Compile(bool throwOnError = true);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Fetch a single instance of the specified type
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="type">Type of service to fetch an implementation for</param>
|
||||||
|
/// <param name="key">Key that implementations of the service to fetch were registered with, defaults to null</param>
|
||||||
|
/// <returns>An instance of the requested service</returns>
|
||||||
|
object Get(Type type, string key = null);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Fetch a single instance of the specified type
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">Type of service to fetch an implementation for</typeparam>
|
||||||
|
/// <param name="key">Key that implementations of the service to fetch were registered with, defaults to null</param>
|
||||||
|
/// <returns>An instance of the requested service</returns>
|
||||||
|
T Get<T>(string key = null);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Fetch instances of all types which implement the specified service
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="type">Type of the service to fetch implementations for</param>
|
||||||
|
/// <param name="key">Key that implementations of the service to fetch were registered with, defaults to null</param>
|
||||||
|
/// <returns>All implementations of the requested service, with the requested key</returns>
|
||||||
|
IEnumerable<object> GetAll(Type type, string key = null);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Fetch instances of all types which implement the specified service
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">Type of the service to fetch implementations for</typeparam>
|
||||||
|
/// <param name="key">Key that implementations of the service to fetch were registered with, defaults to null</param>
|
||||||
|
/// <returns>All implementations of the requested service, with the requested key</returns>
|
||||||
|
IEnumerable<T> GetAll<T>(string key = null);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// If type is an IEnumerable{T} or similar, is equivalent to calling GetAll{T}. Else, is equivalent to calling Get{T}.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="type">If IEnumerable{T}, will fetch all implementations of T, otherwise wil fetch a single T</param>
|
||||||
|
/// <param name="key">Key that implementations of the service to fetch were registered with, defaults to null</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
object GetTypeOrAll(Type type, string key = null);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// If type is an IEnumerable{T} or similar, is equivalent to calling GetAll{T}. Else, is equivalent to calling Get{T}.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">If IEnumerable{T}, will fetch all implementations of T, otherwise wil fetch a single T</typeparam>
|
||||||
|
/// <param name="key">Key that implementations of the service to fetch were registered with, defaults to null</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
T GetTypeOrAll<T>(string key = null);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// For each property/field with the [Inject] attribute, sets it to an instance of that type
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="item">Item to build up</param>
|
||||||
|
void BuildUp(object item);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace StyletIoC
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Interface to be implemented by objects if they want to be notified when property injection has occurred
|
||||||
|
/// </summary>
|
||||||
|
public interface IInjectionAware
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Called by StyletIoC when property injection has occurred
|
||||||
|
/// </summary>
|
||||||
|
void ParametersInjected();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace StyletIoC
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Attribute which can be used to mark the constructor to use, properties to inject, which key to use to resolve an injected property, and others. See the docs
|
||||||
|
/// </summary>
|
||||||
|
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Constructor | AttributeTargets.Parameter | AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Method, Inherited = false, AllowMultiple = false)]
|
||||||
|
public sealed class InjectAttribute : Attribute
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Create a new InjectAttribute
|
||||||
|
/// </summary>
|
||||||
|
public InjectAttribute()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a new InjectAttribute, which has the specified key
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="key"></param>
|
||||||
|
public InjectAttribute(string key)
|
||||||
|
{
|
||||||
|
this.Key = key;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Key to use to resolve the relevant dependency
|
||||||
|
/// </summary>
|
||||||
|
public string Key { get; set; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -12,72 +12,6 @@ using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace StyletIoC
|
namespace StyletIoC
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Describes an IoC container, specifically StyletIoC
|
|
||||||
/// </summary>
|
|
||||||
public interface IContainer
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Compile all known bindings (which would otherwise be compiled when needed), checking the dependency graph for consistency
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="throwOnError">If true, throw if we fail to compile a type</param>
|
|
||||||
void Compile(bool throwOnError = true);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Fetch a single instance of the specified type
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="type">Type of service to fetch an implementation for</param>
|
|
||||||
/// <param name="key">Key that implementations of the service to fetch were registered with, defaults to null</param>
|
|
||||||
/// <returns>An instance of the requested service</returns>
|
|
||||||
object Get(Type type, string key = null);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Fetch a single instance of the specified type
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="T">Type of service to fetch an implementation for</typeparam>
|
|
||||||
/// <param name="key">Key that implementations of the service to fetch were registered with, defaults to null</param>
|
|
||||||
/// <returns>An instance of the requested service</returns>
|
|
||||||
T Get<T>(string key = null);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Fetch instances of all types which implement the specified service
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="type">Type of the service to fetch implementations for</param>
|
|
||||||
/// <param name="key">Key that implementations of the service to fetch were registered with, defaults to null</param>
|
|
||||||
/// <returns>All implementations of the requested service, with the requested key</returns>
|
|
||||||
IEnumerable<object> GetAll(Type type, string key = null);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Fetch instances of all types which implement the specified service
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="T">Type of the service to fetch implementations for</typeparam>
|
|
||||||
/// <param name="key">Key that implementations of the service to fetch were registered with, defaults to null</param>
|
|
||||||
/// <returns>All implementations of the requested service, with the requested key</returns>
|
|
||||||
IEnumerable<T> GetAll<T>(string key = null);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// If type is an IEnumerable{T} or similar, is equivalent to calling GetAll{T}. Else, is equivalent to calling Get{T}.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="type">If IEnumerable{T}, will fetch all implementations of T, otherwise wil fetch a single T</param>
|
|
||||||
/// <param name="key">Key that implementations of the service to fetch were registered with, defaults to null</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
object GetTypeOrAll(Type type, string key = null);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// If type is an IEnumerable{T} or similar, is equivalent to calling GetAll{T}. Else, is equivalent to calling Get{T}.
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="T">If IEnumerable{T}, will fetch all implementations of T, otherwise wil fetch a single T</typeparam>
|
|
||||||
/// <param name="key">Key that implementations of the service to fetch were registered with, defaults to null</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
T GetTypeOrAll<T>(string key = null);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// For each property/field with the [Inject] attribute, sets it to an instance of that type
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="item">Item to build up</param>
|
|
||||||
void BuildUp(object item);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Lightweight, very fast IoC container
|
/// Lightweight, very fast IoC container
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -559,133 +493,4 @@ namespace StyletIoC
|
||||||
return other != null && this.Type == other.Type && this.Key == other.Key;
|
return other != null && this.Type == other.Type && this.Key == other.Key;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static class TypeExtensions
|
|
||||||
{
|
|
||||||
public static IEnumerable<Type> GetBaseTypesAndInterfaces(this Type type)
|
|
||||||
{
|
|
||||||
return type.GetInterfaces().Concat(type.GetBaseTypes());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static IEnumerable<Type> GetBaseTypes(this Type type)
|
|
||||||
{
|
|
||||||
for (var baseType = type.BaseType; baseType != null; baseType = baseType.BaseType)
|
|
||||||
{
|
|
||||||
yield return baseType;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool Implements(this Type implementationType, Type serviceType)
|
|
||||||
{
|
|
||||||
return serviceType.IsAssignableFrom(implementationType) ||
|
|
||||||
implementationType.GetBaseTypesAndInterfaces().Any(x => x == serviceType || (x.IsGenericType && x.GetGenericTypeDefinition() == serviceType));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static readonly Dictionary<Type, string> primitiveNameMapping = new Dictionary<Type, string>()
|
|
||||||
{
|
|
||||||
{ typeof(byte), "byte" },
|
|
||||||
{ typeof(sbyte), "sbyte" },
|
|
||||||
{ typeof(char), "char" },
|
|
||||||
{ typeof(short), "short" },
|
|
||||||
{ typeof(ushort), "ushort" },
|
|
||||||
{ typeof(int), "int" },
|
|
||||||
{ typeof(uint), "uint" },
|
|
||||||
{ typeof(long), "long" },
|
|
||||||
{ typeof(ulong), "ulong" },
|
|
||||||
{ typeof(float), "float" },
|
|
||||||
{ typeof(double), "double" },
|
|
||||||
{ typeof(decimal), "decimal" },
|
|
||||||
{ typeof(bool), "bool" },
|
|
||||||
};
|
|
||||||
|
|
||||||
public static string Description(this Type type)
|
|
||||||
{
|
|
||||||
if (type.IsGenericTypeDefinition)
|
|
||||||
return String.Format("{0}<{1}>", type.Name.Split('`')[0], String.Join(", ", type.GetTypeInfo().GenericTypeParameters.Select(x => x.Name)));
|
|
||||||
var genericArguments = type.GetGenericArguments();
|
|
||||||
if (genericArguments.Length > 0)
|
|
||||||
{
|
|
||||||
return String.Format("{0}<{1}>", type.Name.Split('`')[0], String.Join(", ", genericArguments.Select(x =>
|
|
||||||
{
|
|
||||||
string name;
|
|
||||||
return primitiveNameMapping.TryGetValue(x, out name) ? name : x.Name;
|
|
||||||
})));
|
|
||||||
}
|
|
||||||
return type.Name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Interface to be implemented by objects if they want to be notified when property injection has occurred
|
|
||||||
/// </summary>
|
|
||||||
public interface IInjectionAware
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Called by StyletIoC when property injection has occurred
|
|
||||||
/// </summary>
|
|
||||||
void ParametersInjected();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Base class for all exceptions describing StyletIoC-specific problems?
|
|
||||||
/// </summary>
|
|
||||||
public abstract class StyletIoCException : Exception
|
|
||||||
{
|
|
||||||
internal StyletIoCException(string message) : base(message) { }
|
|
||||||
internal StyletIoCException(string message, Exception innerException) : base(message, innerException) { }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A problem occured with a registration process (failed to register, failed to find a registration, etc)
|
|
||||||
/// </summary>
|
|
||||||
public class StyletIoCRegistrationException : StyletIoCException
|
|
||||||
{
|
|
||||||
internal StyletIoCRegistrationException(string message) : base(message) { }
|
|
||||||
internal StyletIoCRegistrationException(string message, Exception innerException) : base(message, innerException) { }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// StyletIoC was unable to find a callable constructor for a type
|
|
||||||
/// </summary>
|
|
||||||
public class StyletIoCFindConstructorException : StyletIoCException
|
|
||||||
{
|
|
||||||
internal StyletIoCFindConstructorException(string message) : base(message) { }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// StyletIoC was unable to create an abstract factory
|
|
||||||
/// </summary>
|
|
||||||
public class StyletIoCCreateFactoryException : StyletIoCException
|
|
||||||
{
|
|
||||||
internal StyletIoCCreateFactoryException(string message) : base(message) { }
|
|
||||||
internal StyletIoCCreateFactoryException(string message, Exception innerException) : base(message, innerException) { }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Attribute which can be used to mark the constructor to use, properties to inject, which key to use to resolve an injected property, and others. See the docs
|
|
||||||
/// </summary>
|
|
||||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Constructor | AttributeTargets.Parameter | AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Method, Inherited = false, AllowMultiple = false)]
|
|
||||||
public sealed class InjectAttribute : Attribute
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Create a new InjectAttribute
|
|
||||||
/// </summary>
|
|
||||||
public InjectAttribute()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Create a new InjectAttribute, which has the specified key
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="key"></param>
|
|
||||||
public InjectAttribute(string key)
|
|
||||||
{
|
|
||||||
this.Key = key;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Key to use to resolve the relevant dependency
|
|
||||||
/// </summary>
|
|
||||||
public string Key { get; set; }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,43 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace StyletIoC
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Base class for all exceptions describing StyletIoC-specific problems?
|
||||||
|
/// </summary>
|
||||||
|
public abstract class StyletIoCException : Exception
|
||||||
|
{
|
||||||
|
internal StyletIoCException(string message) : base(message) { }
|
||||||
|
internal StyletIoCException(string message, Exception innerException) : base(message, innerException) { }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A problem occured with a registration process (failed to register, failed to find a registration, etc)
|
||||||
|
/// </summary>
|
||||||
|
public class StyletIoCRegistrationException : StyletIoCException
|
||||||
|
{
|
||||||
|
internal StyletIoCRegistrationException(string message) : base(message) { }
|
||||||
|
internal StyletIoCRegistrationException(string message, Exception innerException) : base(message, innerException) { }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// StyletIoC was unable to find a callable constructor for a type
|
||||||
|
/// </summary>
|
||||||
|
public class StyletIoCFindConstructorException : StyletIoCException
|
||||||
|
{
|
||||||
|
internal StyletIoCFindConstructorException(string message) : base(message) { }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// StyletIoC was unable to create an abstract factory
|
||||||
|
/// </summary>
|
||||||
|
public class StyletIoCCreateFactoryException : StyletIoCException
|
||||||
|
{
|
||||||
|
internal StyletIoCCreateFactoryException(string message) : base(message) { }
|
||||||
|
internal StyletIoCCreateFactoryException(string message, Exception innerException) : base(message, innerException) { }
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,64 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
namespace StyletIoC
|
||||||
|
{
|
||||||
|
internal static class TypeExtensions
|
||||||
|
{
|
||||||
|
public static IEnumerable<Type> GetBaseTypesAndInterfaces(this Type type)
|
||||||
|
{
|
||||||
|
return type.GetInterfaces().Concat(type.GetBaseTypes());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IEnumerable<Type> GetBaseTypes(this Type type)
|
||||||
|
{
|
||||||
|
for (var baseType = type.BaseType; baseType != null; baseType = baseType.BaseType)
|
||||||
|
{
|
||||||
|
yield return baseType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool Implements(this Type implementationType, Type serviceType)
|
||||||
|
{
|
||||||
|
return serviceType.IsAssignableFrom(implementationType) ||
|
||||||
|
implementationType.GetBaseTypesAndInterfaces().Any(x => x == serviceType || (x.IsGenericType && x.GetGenericTypeDefinition() == serviceType));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static readonly Dictionary<Type, string> primitiveNameMapping = new Dictionary<Type, string>()
|
||||||
|
{
|
||||||
|
{ typeof(byte), "byte" },
|
||||||
|
{ typeof(sbyte), "sbyte" },
|
||||||
|
{ typeof(char), "char" },
|
||||||
|
{ typeof(short), "short" },
|
||||||
|
{ typeof(ushort), "ushort" },
|
||||||
|
{ typeof(int), "int" },
|
||||||
|
{ typeof(uint), "uint" },
|
||||||
|
{ typeof(long), "long" },
|
||||||
|
{ typeof(ulong), "ulong" },
|
||||||
|
{ typeof(float), "float" },
|
||||||
|
{ typeof(double), "double" },
|
||||||
|
{ typeof(decimal), "decimal" },
|
||||||
|
{ typeof(bool), "bool" },
|
||||||
|
};
|
||||||
|
|
||||||
|
public static string Description(this Type type)
|
||||||
|
{
|
||||||
|
if (type.IsGenericTypeDefinition)
|
||||||
|
return String.Format("{0}<{1}>", type.Name.Split('`')[0], String.Join(", ", type.GetTypeInfo().GenericTypeParameters.Select(x => x.Name)));
|
||||||
|
var genericArguments = type.GetGenericArguments();
|
||||||
|
if (genericArguments.Length > 0)
|
||||||
|
{
|
||||||
|
return String.Format("{0}<{1}>", type.Name.Split('`')[0], String.Join(", ", genericArguments.Select(x =>
|
||||||
|
{
|
||||||
|
string name;
|
||||||
|
return primitiveNameMapping.TryGetValue(x, out name) ? name : x.Name;
|
||||||
|
})));
|
||||||
|
}
|
||||||
|
return type.Name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue