diff --git a/StackInjector/Core/AsyncStackWrapperCore.cs b/StackInjector/Core/AsyncStackWrapperCore.cs index a3921f0..22b3234 100644 --- a/StackInjector/Core/AsyncStackWrapperCore.cs +++ b/StackInjector/Core/AsyncStackWrapperCore.cs @@ -34,7 +34,7 @@ public CancellationToken PendingTasksCancellationToken // register an event that in case the list is empty, release the empty event listener. - internal AsyncStackWrapperCore ( InjectionCore core, Type toRegister ) : base(core, toRegister) + internal AsyncStackWrapperCore ( InjectionCore core ) : base(core) { this.cancelPendingTasksSource.Token.Register(this.ReleaseListAwaiter); } diff --git a/StackInjector/Core/AsyncStackWrapperCore.logic.cs b/StackInjector/Core/AsyncStackWrapperCore.logic.cs index d4f2a7b..4b28e4a 100644 --- a/StackInjector/Core/AsyncStackWrapperCore.logic.cs +++ b/StackInjector/Core/AsyncStackWrapperCore.logic.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; -using StackInjector.Settings; namespace StackInjector.Core { @@ -40,7 +39,13 @@ public bool AnyTaskCompleted () public async IAsyncEnumerable Elaborated () { - this.EnsureExclusiveExecution(true); + // begin elaboration + lock ( this._listAccessLock ) + { + if ( this._exclusiveExecution ) + throw new InvalidOperationException(); + this._exclusiveExecution = true; + } while ( !this.cancelPendingTasksSource.IsCancellationRequested ) { @@ -62,8 +67,11 @@ public async IAsyncEnumerable Elaborated () } } + // no more elaborating lock ( this._listAccessLock ) + { this._exclusiveExecution = false; + } } @@ -77,50 +85,15 @@ public async Task Elaborate () // true if outher loop is to break private async Task OnNoTasksLeft () { - // to not repeat code - Task listAwaiter () - { - return this._emptyListAwaiter.WaitAsync(); - } - - switch ( this.Settings.Runtime._asyncWaitingMethod ) - { - - case AsyncWaitingMethod.Exit: - default: - - return true; - - - case AsyncWaitingMethod.Wait: - // wait for a signal of the list not being empty anymore - await listAwaiter().ConfigureAwait(true); - return false; + if ( this.Settings.Runtime._asyncWaitTime == 0 ) + return true; - - case AsyncWaitingMethod.Timeout: - var list = listAwaiter(); - var timeout = Task.Delay( this.Settings.Runtime._asyncWaitTime ); - - // if the timeout elapses first, then stop waiting - return (await Task.WhenAny(list, timeout).ConfigureAwait(true)) == timeout; - } - } - - private void EnsureExclusiveExecution ( bool set = false ) - { - lock ( this._listAccessLock ) // reused lock - { - if ( this._exclusiveExecution ) - throw new InvalidOperationException(); - - if ( set ) - this._exclusiveExecution = set; - } + return !(await this._emptyListAwaiter.WaitAsync( + this.Settings.Runtime._asyncWaitTime, + this.PendingTasksCancellationToken + ) + .ConfigureAwait(true)); } - - - } -} \ No newline at end of file +} diff --git a/StackInjector/Core/IStackWrapperCore.cs b/StackInjector/Core/IStackWrapperCore.cs index 4eb9a3f..3ee2daf 100644 --- a/StackInjector/Core/IStackWrapperCore.cs +++ b/StackInjector/Core/IStackWrapperCore.cs @@ -24,10 +24,9 @@ public interface IStackWrapperCore : IDisposable, ICloneableCore /// IEnumerable GetServices (); - //! description is wrong /// /// The current number of all tracked services.
- /// Does also include the Wrapper, so if you want all the instances + /// Does also include the Wrapper, so if you want just the instances /// wrapper.CountServices()-1 ///
int CountServices (); diff --git a/StackInjector/Core/InjectionCore/InjectionCore.injection.cs b/StackInjector/Core/InjectionCore/InjectionCore.injection.cs index 307fda8..1f8c801 100644 --- a/StackInjector/Core/InjectionCore/InjectionCore.injection.cs +++ b/StackInjector/Core/InjectionCore/InjectionCore.injection.cs @@ -54,10 +54,10 @@ private void InjectFields ( Type type, object instance, ref List used, b { var fields = type.GetFields( BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance ) - .Where( f => f.GetCustomAttribute() is null ); - - if ( strict ) - fields = fields.Where(field => field.GetCustomAttribute() != null); + .Where( f => + f.GetCustomAttribute() is null + && (!strict||f.GetCustomAttribute() != null) //if not strict, skip this check + ); foreach ( var serviceField in fields ) { @@ -78,10 +78,10 @@ private void InjectProperties ( Type type, object instance, ref List use { var properties = type.GetProperties( BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance ) - .Where( p => p.GetCustomAttribute() is null ); - - if ( strict ) - properties = properties.Where(property => property.GetCustomAttribute() != null); + .Where( p => + p.GetCustomAttribute() is null + && (!strict || p.GetCustomAttribute() != null) //if not strict, skip this check + ); foreach ( var serviceProperty in properties ) { diff --git a/StackInjector/Core/InjectionCore/InjectionCore.instantiation.cs b/StackInjector/Core/InjectionCore/InjectionCore.instantiation.cs index 415b1d8..5c1b751 100644 --- a/StackInjector/Core/InjectionCore/InjectionCore.instantiation.cs +++ b/StackInjector/Core/InjectionCore/InjectionCore.instantiation.cs @@ -17,9 +17,6 @@ private object OfTypeOrInstantiate ( Type type ) if ( serviceAtt == null ) throw new NotAServiceException(type, $"Type {type.FullName} is not a [Service]"); - ////if( !this.instances.ContainsKey(type) ) - //// throw new ServiceNotFoundException(type, $"The type {type.FullName} is not in a registred assembly!"); - return serviceAtt.Pattern switch { @@ -40,7 +37,7 @@ private object InstantiateService ( Type type ) //todo add more constructor options if ( type.GetConstructor(Array.Empty()) == null ) - throw new MissingParameterlessConstructorException(type, $"Missing parameteless constructor for {type.FullName}"); + throw new InvalidConstructorException(type, $"Missing parameteless constructor for {type.FullName}"); var instance = Activator.CreateInstance(type); diff --git a/StackInjector/Core/InjectionCore/InjectionCore.reflection.cs b/StackInjector/Core/InjectionCore/InjectionCore.reflection.cs index 60a6eb5..4397185 100644 --- a/StackInjector/Core/InjectionCore/InjectionCore.reflection.cs +++ b/StackInjector/Core/InjectionCore/InjectionCore.reflection.cs @@ -14,41 +14,34 @@ private Type ClassOrVersionFromInterface ( Type type, ServedAttribute servedAttr { if ( type.IsInterface ) { - IEnumerable versions = this.instances.TypesAssignableFrom(type); + IEnumerable versions = this.Version(type, servedAttribute); - // is there already an implementation for the interface? if ( versions.Any() ) { - return versions.First(); + //todo check for multiple valid versions + var t = versions.First(); + _MaskPass(t); + return t; } + + if ( servedAttribute is null ) + throw new ImplementationNotFoundException(type, $"can't find [Service] for interface {type.Name}"); else - { - versions = this.Version(type, servedAttribute); - if ( versions.Any() ) - { - var t = versions.First(); - MaskPass(t); - return t; - } - - if ( servedAttribute is null ) - throw new ImplementationNotFoundException(type, $"can't find [Service] for interface {type.Name}"); - else - throw new ImplementationNotFoundException(type, $"can't find [Service] for {type.Name} v{servedAttribute.TargetVersion}"); - } + throw new ImplementationNotFoundException(type, $"can't find [Service] for {type.Name} v{servedAttribute.TargetVersion}"); + } else { - MaskPass(type); + _MaskPass(type); return type; } - - void MaskPass ( Type type ) + // exception check and thrower + void _MaskPass ( Type type ) { - if ( this.settings.Mask.IsMasked(type) ) + if ( this.settings.Mask.IsMasked(type) ) //todo create custom exception throw new InvalidOperationException($"Type {type.Name} is { (this.settings.Mask._isWhiteList ? "not whitelisted" : "blacklisted")}"); - //todo create custom exception + } diff --git a/StackInjector/Core/InjectionCore/InjectionCore.utilities.cs b/StackInjector/Core/InjectionCore/InjectionCore.utilities.cs index 3e2af51..fc0ef48 100644 --- a/StackInjector/Core/InjectionCore/InjectionCore.utilities.cs +++ b/StackInjector/Core/InjectionCore/InjectionCore.utilities.cs @@ -16,8 +16,7 @@ internal T GetEntryPoint () : throw new InvalidEntryTypeException ( this.EntryType, - $"No instance found for entry type {this.EntryType.FullName}", - innerException: new ServiceNotFoundException(typeof(T), string.Empty) + $"No instance found for entry type {this.EntryType.FullName}" ); } diff --git a/StackInjector/Core/InjectionCore/InjectionCore.versioning.cs b/StackInjector/Core/InjectionCore/InjectionCore.versioning.cs index 283cfc3..e85f1a6 100644 --- a/StackInjector/Core/InjectionCore/InjectionCore.versioning.cs +++ b/StackInjector/Core/InjectionCore/InjectionCore.versioning.cs @@ -9,22 +9,40 @@ namespace StackInjector.Core { internal partial class InjectionCore { + private static readonly Type _istackwrappercore = typeof(IStackWrapperCore); + // performs versioning on the specified type private IEnumerable Version ( Type targetType, ServedAttribute servedAttribute ) { + if ( targetType.IsSubclassOf(_istackwrappercore) || targetType == _istackwrappercore ) + return this.instances.TypesAssignableFrom(targetType); + + if ( this.settings.Versioning._customBindings != null && + this.settings.Versioning._customBindings.TryGetValue(targetType, out var t) ) + { + return Enumerable.Repeat(t, 1); + } + var targetVersion = servedAttribute?.TargetVersion ?? 0.0; var method = - (this.settings.Injection._overrideTargetingMethod || servedAttribute is null || !(servedAttribute._targetingDefined)) - ? this.settings.Injection._targetingMethod + (this.settings.Versioning._overrideTargetingMethod || servedAttribute is null || !(servedAttribute._targetingDefined)) + ? this.settings.Versioning._targetingMethod : servedAttribute.TargetingMethod; - - ////var candidateTypes = this.instances.TypesAssignableFrom(targetType); - var candidateTypes = targetType - .Assembly - .GetTypes() - .Where( t => t.IsClass && !t.IsAbstract && targetType.IsAssignableFrom(t)); + IEnumerable candidateTypes = + (this.settings.Versioning.AssemblyLookUpOrder.Any()) + ? + this.settings.Versioning + .AssemblyLookUpOrder + .SelectMany( a => a.DefinedTypes ) + .Where( t => t.IsClass && !t.IsAbstract + && targetType.IsAssignableFrom(t) && !this.settings.Mask.IsMasked(t) ) + : + targetType + .Assembly + .DefinedTypes + .Where( t => t.IsClass && !t.IsAbstract && targetType.IsAssignableFrom(t) ); return method switch diff --git a/StackInjector/Core/InstancesHolder.cs b/StackInjector/Core/InstancesHolder.cs index f3114df..2077df5 100644 --- a/StackInjector/Core/InstancesHolder.cs +++ b/StackInjector/Core/InstancesHolder.cs @@ -27,11 +27,12 @@ internal bool AddType ( Type type ) return this.TryAdd(type, new LinkedList()); } - internal void CountAllInstances () + internal int CountAllInstances () { this.total_count = 0; foreach ( var pair in this ) this.total_count += pair.Value.Count; + return this.total_count; } diff --git a/StackInjector/Core/StackWrapperCore.cs b/StackInjector/Core/StackWrapperCore.cs index 2071ccd..b2b396c 100644 --- a/StackInjector/Core/StackWrapperCore.cs +++ b/StackInjector/Core/StackWrapperCore.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using StackInjector.Core.Cloning; using StackInjector.Settings; @@ -18,15 +17,16 @@ public ref readonly StackWrapperSettings Settings private protected readonly InjectionCore Core; - public StackWrapperCore ( InjectionCore core, Type toRegister ) + public StackWrapperCore ( InjectionCore core ) { this.Core = core; // add this wrapper to possible instances - if ( !this.Core.instances.AddType(toRegister) ) - this.Core.instances[toRegister].Clear(); - this.Core.instances[toRegister].AddFirst(this); - + var registerAs = this.GetType(); + if ( !this.Core.instances.AddType(registerAs) ) + this.Core.instances[registerAs].Clear(); + this.Core.instances[registerAs].AddFirst(this); + } @@ -38,8 +38,10 @@ public IEnumerable GetServices () .Select(o => (T)o); } - public int CountServices () => this.Core.instances.total_count; - + public int CountServices () + { + return this.Core.instances.CountAllInstances(); + } public IClonedCore CloneCore ( StackWrapperSettings settings = null ) { diff --git a/StackInjector/Exceptions/InvalidConstructorException.cs b/StackInjector/Exceptions/InvalidConstructorException.cs new file mode 100644 index 0000000..755b690 --- /dev/null +++ b/StackInjector/Exceptions/InvalidConstructorException.cs @@ -0,0 +1,22 @@ +using System; + +namespace StackInjector.Exceptions +{ + /// + /// Thrown when a class has no parameterless constructor to call. + /// + public sealed class InvalidConstructorException : StackInjectorException + { + internal InvalidConstructorException () { } + + internal InvalidConstructorException ( Type type, string message ) : base(message) + { + this.SourceType = type; + } + + internal InvalidConstructorException ( string message ) : base(message) { } + + internal InvalidConstructorException ( string message, Exception innerException ) : base(message, innerException) { } + + } +} diff --git a/StackInjector/Exceptions/MissingParameterlessConstructorException.cs b/StackInjector/Exceptions/MissingParameterlessConstructorException.cs deleted file mode 100644 index 9f0febd..0000000 --- a/StackInjector/Exceptions/MissingParameterlessConstructorException.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; - -namespace StackInjector.Exceptions -{ - /// - /// Thrown when a class has no parameterless constructor to call. - /// - public sealed class MissingParameterlessConstructorException : StackInjectorException - { - internal MissingParameterlessConstructorException () { } - - internal MissingParameterlessConstructorException ( Type type, string message ) : base(message) - { - this.SourceType = type; - } - - internal MissingParameterlessConstructorException ( string message ) : base(message) { } - - internal MissingParameterlessConstructorException ( string message, Exception innerException ) : base(message, innerException) { } - - } -} diff --git a/StackInjector/Exceptions/ServiceNotFoundException.cs b/StackInjector/Exceptions/ServiceNotFoundException.cs deleted file mode 100644 index 619f93c..0000000 --- a/StackInjector/Exceptions/ServiceNotFoundException.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System; - -namespace StackInjector.Exceptions -{ - /// - /// Thrown when a type for a service has not been found. - /// - public sealed class ServiceNotFoundException : StackInjectorException - { - - internal ServiceNotFoundException ( Type type, string message ) : base(type, message) { } - - internal ServiceNotFoundException () { } - - internal ServiceNotFoundException ( string message ) : base(message) { } - - internal ServiceNotFoundException ( string message, Exception innerException ) : base(message, innerException) { } - } -} diff --git a/StackInjector/Injector.cs b/StackInjector/Injector.cs index 93e166d..f7bc8bb 100644 --- a/StackInjector/Injector.cs +++ b/StackInjector/Injector.cs @@ -24,7 +24,6 @@ public static partial class Injector /// The Initialized StackWrapper /// /// - /// /// /// public static IStackWrapper From ( StackWrapperSettings settings = null ) @@ -66,7 +65,6 @@ public static IStackWrapper From ( StackWrapperSettings settings = null ) /// The created asyncronous wrapper /// /// - /// /// /// public static IAsyncStackWrapper AsyncFrom diff --git a/StackInjector/Settings/AsyncWaitingMethod.cs b/StackInjector/Settings/AsyncWaitingMethod.cs deleted file mode 100644 index 1c7c6a8..0000000 --- a/StackInjector/Settings/AsyncWaitingMethod.cs +++ /dev/null @@ -1,28 +0,0 @@ -namespace StackInjector.Settings -{ - - /// - /// How an should behave when there are no pending tasks. - /// - public enum AsyncWaitingMethod - { - - /// - /// Simply exit the await foreach loop withouth doing anything - /// - Exit, - - /// - /// don't exit the loop, but wait for new Tasks to be submitted. - /// Exiting the loop requires Dispose() to be called on the wrapper or a break - /// - Wait, - - /// - /// don't immediately exit the loop, but rather wait a certain amount of time for - /// new tasks to be submitted tobefore exiting the loop - /// - Timeout - - } -} \ No newline at end of file diff --git a/StackInjector/Settings/InjectionOptions.cs b/StackInjector/Settings/InjectionOptions.cs index 7279921..b9cc797 100644 --- a/StackInjector/Settings/InjectionOptions.cs +++ b/StackInjector/Settings/InjectionOptions.cs @@ -5,12 +5,10 @@ namespace StackInjector.Settings { /// - /// Options for the injection process. + /// Options for the injection process, regarding HOW the inejction should be performed /// public sealed class InjectionOptions : IOptions { - internal ServedVersionTargetingMethod _targetingMethod = ServedVersionTargetingMethod.None; - internal bool _overrideTargetingMethod; internal ServingMethods _servingMethod = StackWrapperSettings.ServeAllStrict; internal bool _overrideServingMethod; @@ -42,10 +40,7 @@ public object Clone () /// /// The default injection. Settings are valorized as following: /// - /// - /// - /// , false - /// + /// /// /// , false /// @@ -82,20 +77,6 @@ public InjectionOptions TrackInstantiationDiff ( bool track = true, bool callDis return this; } - - /// - /// Overrides default targetting method - /// - /// the new default targetting method - /// if true, versioning methods for [Served] fields and properties are overriden - /// the modified settings - public InjectionOptions VersioningMethod ( ServedVersionTargetingMethod targetMethod, bool @override = false ) - { - this._targetingMethod = targetMethod; - this._overrideTargetingMethod = @override; - return this; - } - /// /// Overrides default serving method /// diff --git a/StackInjector/Settings/MaskOptions.cs b/StackInjector/Settings/MaskOptions.cs index d13f9e5..b395fe4 100644 --- a/StackInjector/Settings/MaskOptions.cs +++ b/StackInjector/Settings/MaskOptions.cs @@ -50,8 +50,8 @@ public bool IsMasked ( Type type ) { if ( _isDisabled ) return false; - else - return this._isWhiteList ^ this.Contains(type); //.XOR + + return this._isWhiteList ^ this.Contains(type); //.XOR } @@ -68,7 +68,7 @@ public object Clone () public static MaskOptions WhiteList => new MaskOptions() { _isWhiteList = true }; /// - /// allow every type except the regisred ones. + /// allow every type except the registred ones. /// public static MaskOptions BlackList => new MaskOptions(); diff --git a/StackInjector/Settings/RuntimeOptions.cs b/StackInjector/Settings/RuntimeOptions.cs index 3b53477..3f65e4f 100644 --- a/StackInjector/Settings/RuntimeOptions.cs +++ b/StackInjector/Settings/RuntimeOptions.cs @@ -1,4 +1,6 @@ -namespace StackInjector.Settings +using System; + +namespace StackInjector.Settings { /// /// Options used at runtime @@ -7,8 +9,7 @@ public sealed class RuntimeOptions : IOptions { // async management - internal AsyncWaitingMethod _asyncWaitingMethod = AsyncWaitingMethod.Exit; - internal int _asyncWaitTime = 500; + internal int _asyncWaitTime; // 0 internal RuntimeOptions () { } @@ -30,8 +31,8 @@ public object Clone () /// Default runtime options. Settings are valorized as following: /// /// - /// - /// , 500 + /// + /// -1 /// /// /// @@ -44,12 +45,16 @@ public object Clone () /// What to do when an /// has no more pending tasks to execute /// - /// the new waiting method - /// if is set, this will be max time to wait + /// this will be max time to wait.
+ /// -1: wait forever
+ /// 0: exit immediatly
+ /// +n: wait specified timeout (in ms)
/// the modified settings - public RuntimeOptions WhenNoMoreTasks ( AsyncWaitingMethod waitingMethod, int waitTime ) + public RuntimeOptions WaitTimeout ( int waitTime = 0 ) { - this._asyncWaitingMethod = waitingMethod; + if ( waitTime < -1 ) + throw new ArgumentOutOfRangeException(nameof(waitTime), $"{waitTime} cannot be below -1!"); + this._asyncWaitTime = waitTime; return this; } diff --git a/StackInjector/Settings/StackWrapperSettings.cs b/StackInjector/Settings/StackWrapperSettings.cs index 25c7b79..6db9efa 100644 --- a/StackInjector/Settings/StackWrapperSettings.cs +++ b/StackInjector/Settings/StackWrapperSettings.cs @@ -18,6 +18,11 @@ public sealed partial class StackWrapperSettings /// public InjectionOptions Injection { get; private set; } + /// + /// manages versioning options. + /// + public VersioningOptions Versioning { get; private set; } + /// /// manages runtime options /// @@ -25,7 +30,18 @@ public sealed partial class StackWrapperSettings - private StackWrapperSettings () { } + + private StackWrapperSettings ( + MaskOptions mo, + InjectionOptions io, + VersioningOptions vo, + RuntimeOptions ro ) + { + this.Mask = mo; + this.Injection = io; + this.Versioning = vo; + this.Runtime = ro; + } @@ -38,7 +54,8 @@ public StackWrapperSettings Clone () return With( (InjectionOptions)this.Injection.Clone(), (RuntimeOptions)this.Runtime.Clone(), - (MaskOptions)this.Mask.Clone() + (MaskOptions)this.Mask.Clone(), + (VersioningOptions)this.Versioning.Clone() ); } @@ -48,8 +65,14 @@ public StackWrapperSettings Clone () /// , /// , /// + /// /// - public static StackWrapperSettings Default => With(); + public static StackWrapperSettings Default => new StackWrapperSettings( + MaskOptions.Disabled, + InjectionOptions.Default, + VersioningOptions.Default, + RuntimeOptions.Default + ); /// @@ -58,15 +81,20 @@ public StackWrapperSettings Clone () /// the injection options /// the runtime options /// mask options + /// versioning options /// - public static StackWrapperSettings With ( InjectionOptions injection = null, RuntimeOptions runtime = null, MaskOptions mask = null ) + public static StackWrapperSettings With ( + InjectionOptions injection = null, + RuntimeOptions runtime = null, + MaskOptions mask = null, + VersioningOptions versioning = null ) { - return new StackWrapperSettings() - { - Mask = mask ?? MaskOptions.Disabled, - Injection = injection ?? InjectionOptions.Default, - Runtime = runtime ?? RuntimeOptions.Default - }; + return new StackWrapperSettings( + mask ?? MaskOptions.Disabled, + injection ?? InjectionOptions.Default, + versioning ?? VersioningOptions.Default, + runtime ?? RuntimeOptions.Default + ); } @@ -75,16 +103,16 @@ public static StackWrapperSettings With ( InjectionOptions injection = null, Run /// everything is served by default, and you must instead use [Ignored] on properties and fields you don't want injected /// /// - public static StackWrapperSettings DefaultBySubtraction => - With( - injection: - InjectionOptions.Default - .ServingMethod(ServeAll, true), - mask: - null, - runtime: - null - ); + public static StackWrapperSettings DefaultBySubtraction + { + get + { + var settings = Default; + settings.Injection + .ServingMethod(ServeAll, true); + return settings; + } + } } diff --git a/StackInjector/Settings/VersioningOptions.cs b/StackInjector/Settings/VersioningOptions.cs new file mode 100644 index 0000000..7de19a8 --- /dev/null +++ b/StackInjector/Settings/VersioningOptions.cs @@ -0,0 +1,142 @@ +using System; +using System.Collections.Generic; +using System.Reflection; + +namespace StackInjector.Settings +{ + + /// + /// Used to determine how to find the correct type from an interface. + /// + public sealed class VersioningOptions : IOptions + { + + internal ServedVersionTargetingMethod _targetingMethod = ServedVersionTargetingMethod.None; + internal bool _overrideTargetingMethod; + + internal Dictionary _customBindings; + + /// + /// A set of assemblies overriding where to look up types in, when not empty. + /// + public HashSet AssemblyLookUpOrder { get; private set; } = new HashSet(); + + + + + + internal VersioningOptions () { } + + + + /// + /// The default versioning options, valorized as following: + /// + /// + /// + /// , false + /// + /// + /// empty + /// + /// + /// + public static VersioningOptions Default => new VersioningOptions(); + + + /// + public object Clone () + { + return this.MemberwiseClone(); + } + + IOptions IOptions.CreateDefault () + { + return Default; + } + + + + #region configuration methods + + /// + /// Overrides default versioning method + /// + /// the new default targeting method + /// if true, versioning methods for [Served] fields and properties are overriden + /// the modified settings + public VersioningOptions VersioningMethod ( ServedVersionTargetingMethod targetMethod, bool @override = false ) + { + this._targetingMethod = targetMethod; + this._overrideTargetingMethod = @override; + return this; + } + + + /// + /// add a range of assemblies from which, in order, look for types when injecting interfaces, overriding any default option. + /// + /// + /// the modified options + public VersioningOptions AddAssembliesToLookup ( params Assembly[] assemblies ) + { + foreach ( var a in assemblies ) + { + this.AssemblyLookUpOrder.Add(a); + } + return this; + } + + #region bindings + + /// + /// binds the specified implementation with the interface, ensuring that versioning + /// the specified interface will always return the wanted implementation. + /// + /// Must be an interface type. + /// The implementation type for the type + /// the modified options + public VersioningOptions AddInterfaceBinding () + where TImpl : class, new() + { + var ti = typeof(TInterface); + if ( !ti.IsInterface ) + { + throw new ArgumentException($"{ti.FullName} is not an interface!"); + } + + if ( this._customBindings == null ) + this._customBindings = new Dictionary(); + + this._customBindings.Add(typeof(TInterface), typeof(TImpl)); + return this; + } + + /// + /// removes the specified interface type. + /// + /// the modified options + public VersioningOptions RemoveInterfaceBinding () + { + this._customBindings.Remove(typeof(TInterface)); + return this; + } + + /// + /// removes all interface bindings + /// + /// the modified options + public VersioningOptions RemoveInterfaceBinding () + { + this._customBindings.Clear(); + this._customBindings = null; + return this; + } + + #endregion + + + #endregion + + } +} diff --git a/StackInjector/Wrappers/AsyncStackWrapper.cs b/StackInjector/Wrappers/AsyncStackWrapper.cs index e4f7111..b92215f 100644 --- a/StackInjector/Wrappers/AsyncStackWrapper.cs +++ b/StackInjector/Wrappers/AsyncStackWrapper.cs @@ -16,8 +16,8 @@ public TEntry Entry this.Core.GetEntryPoint(); - public AsyncStackWrapper ( InjectionCore core ) : base(core, typeof(AsyncStackWrapper)) - { } + public AsyncStackWrapper ( InjectionCore core ) : base(core) { } + public void Submit ( TIn item ) { diff --git a/StackInjector/Wrappers/StackWrapper.cs b/StackInjector/Wrappers/StackWrapper.cs index 0817566..3978d18 100644 --- a/StackInjector/Wrappers/StackWrapper.cs +++ b/StackInjector/Wrappers/StackWrapper.cs @@ -9,7 +9,7 @@ namespace StackInjector.Wrappers internal class StackWrapper : StackWrapperCore, IStackWrapper { - internal StackWrapper ( InjectionCore core ) : base(core, typeof(StackWrapper)) { } + internal StackWrapper ( InjectionCore core ) : base(core) { } diff --git a/tests/StackInjector.TEST.BlackBox/Behaviour.cs b/tests/StackInjector.TEST.BlackBox/Behaviour.cs index 070afab..ce99061 100644 --- a/tests/StackInjector.TEST.BlackBox/Behaviour.cs +++ b/tests/StackInjector.TEST.BlackBox/Behaviour.cs @@ -26,7 +26,7 @@ public void Avoid_CircularReference () Assert.Multiple(() => { Assert.AreSame(wrapper.Entry, wrapper.Entry.loopB.loopA); - Assert.AreEqual(2, wrapper.CountServices()); + Assert.AreEqual(3, wrapper.CountServices()); }); } diff --git a/tests/StackInjector.TEST.BlackBox/Cloning.cs b/tests/StackInjector.TEST.BlackBox/Cloning.cs index 35c38bf..77b198c 100644 --- a/tests/StackInjector.TEST.BlackBox/Cloning.cs +++ b/tests/StackInjector.TEST.BlackBox/Cloning.cs @@ -1,11 +1,5 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using System.Linq; using NUnit.Framework; -using StackInjector.Attributes; -using StackInjector.Core; using StackInjector.Exceptions; using StackInjector.Settings; using StackInjector.TEST.Structures.Simple; @@ -17,11 +11,6 @@ namespace StackInjector.TEST.BlackBox public class Cloning { - //? might be used for exception asserting - //var iete = Assert.Throws(() => wrapperA.Entry.Logic() ); - //Assert.IsInstanceOf(iete.InnerException); - //StringAssert.Contains("No instance found", iete.Message); - #pragma warning disable 0649 #region Simple cloning @@ -97,7 +86,7 @@ public void Deep_IsDifferentCore () }); } - [Test] //? perhaps move to Settings + [Test] public void Deep_RemoveUnused () { var settings = StackWrapperSettings.Default; @@ -107,7 +96,7 @@ public void Deep_RemoveUnused () Assert.Multiple(() => { var wrap = Injector.From( ); - Assert.AreEqual(4, wrap.CountServices()); + Assert.AreEqual(5, wrap.CountServices()); // base is removed after injecting from a class that doesn't need it var clone = wrap.DeepCloneCore( settings ).ToWrapper( ); diff --git a/tests/StackInjector.TEST.BlackBox/Exceptions.cs b/tests/StackInjector.TEST.BlackBox/Exceptions.cs index 4e86e5c..4a147e7 100644 --- a/tests/StackInjector.TEST.BlackBox/Exceptions.cs +++ b/tests/StackInjector.TEST.BlackBox/Exceptions.cs @@ -45,7 +45,7 @@ private abstract class AbstractThrower { } [Test] public void ThrowsOnAbstractClass () { - Assert.Throws(() => Injector.From()); + Assert.Throws(() => Injector.From()); } @@ -72,7 +72,7 @@ private class BaseNoParameterlessConstructorThrower [Test] public void ThrowsMissingParameterlessConstructor () - => Assert.Throws(() => Injector.From()); + => Assert.Throws(() => Injector.From()); // ---------- diff --git a/tests/StackInjector.TEST.BlackBox/Injector.cs b/tests/StackInjector.TEST.BlackBox/Injector.cs index 8794a50..1ca42ae 100644 --- a/tests/StackInjector.TEST.BlackBox/Injector.cs +++ b/tests/StackInjector.TEST.BlackBox/Injector.cs @@ -1,8 +1,7 @@ using System; using System.Collections.Generic; -using System.IO.Pipes; using System.Linq; -using NUnit; +using System.Reflection; using NUnit.Framework; using StackInjector.Attributes; using StackInjector.Core; @@ -33,7 +32,7 @@ public void From_Class () Assert.That(entry.level1A, Is.Not.Null.And.InstanceOf()); Assert.That(entry.level1B, Is.Not.Null.And.InstanceOf()); Assert.AreSame(entry.level1A.level2, entry.level1B.level2); - Assert.AreEqual(4, wrapper.CountServices()); + Assert.AreEqual(5, wrapper.CountServices()); // 4 classes + 1 wrapper }); } @@ -51,7 +50,7 @@ public void From_Interface () Assert.That(_entry.level1A, Is.Not.Null.And.InstanceOf()); Assert.That(_entry.level1B, Is.Not.Null.And.InstanceOf()); Assert.AreSame(_entry.level1A.level2, _entry.level1B.level2); - Assert.AreEqual(4, wrapper.CountServices()); + Assert.AreEqual(5, wrapper.CountServices()); // 4 classes + 1 wrapper }); } @@ -130,23 +129,20 @@ public void AlwaysCreate () private class _ExternalAssemblyReference_Class {[Served] public readonly ExternalAssembly.Externalclass externalclass; } [Test] - public void ExternalAssembly_NoRegistration_FromClass () + public void ExternalAssembly_FromClass () { Assert.DoesNotThrow(() => Injector.From<_ExternalAssemblyReference_Class>()); } [Service] - private class _ExternalAssemblyReference_Interface {[Served] public readonly ExternalAssembly.IExternalClass externalclass; } + internal class _ExternalAssemblyReference_Interface {[Served] public readonly ExternalAssembly.IExternalClass externalclass; } [Test] - public void ExternalAssembly_NoRegistration_FromInterface () + public void ExternalAssembly_FromInterface () { Assert.DoesNotThrow(() => Injector.From<_ExternalAssemblyReference_Interface>()); } - - //todo asas - } } \ No newline at end of file diff --git a/tests/StackInjector.TEST.BlackBox/Injector_Async.cs b/tests/StackInjector.TEST.BlackBox/Injector_Async.cs new file mode 100644 index 0000000..313ac67 --- /dev/null +++ b/tests/StackInjector.TEST.BlackBox/Injector_Async.cs @@ -0,0 +1,214 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using NUnit.Framework; +using StackInjector.Attributes; +using StackInjector.Core; +using StackInjector.Settings; +using CTkn = System.Threading.CancellationToken; + +namespace StackInjector.TEST.BlackBox +{ + [TestFixture] + public class Injector_Async + { + /*NOTE: AsyncStackWrapper is an extension od the normal wrapper, + * there is a common core logic; this means that the tests done in UseCases.Sync + * are also valid for the AsyncStackWrapper, since the tested code is the same + * (injection logic) + */ + + // base async test class + [Service] + [System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1822", Justification = "methods don't need to be static.")] + private class AsyncBase + { + internal async Task WaitForever ( object obj, CTkn tkn ) + { + // waits forever unless cancelled + await Task.Delay(-1, tkn); + return obj; + } + + internal async Task ReturnArg ( object obj, CTkn tkn ) + { + if ( tkn.IsCancellationRequested ) + return obj; + + await Task.CompletedTask; + return obj; + } + } + + + [Test] + public void SubmitNoElaboration () + { + using var wrapper = Injector.AsyncFrom( (b,i,t) => b.WaitForever(i,t) ); + wrapper.Submit(new object()); + + Assert.Multiple(() => + { + Assert.IsTrue(wrapper.AnyTaskLeft()); + Assert.IsFalse(wrapper.AnyTaskCompleted()); + }); + } + + + [Test] + public void SubmitNoCatch () + { + var wrapper = Injector.AsyncFrom( (b,i,t) => b.ReturnArg(i,t) ); + + var task = wrapper.SubmitAndGet(new object()); + task.Wait(); + + Assert.Multiple(() => + { + Assert.IsTrue(wrapper.AnyTaskLeft()); + Assert.IsTrue(wrapper.AnyTaskCompleted()); + }); + } + + + [Timeout(1000)] + [Test] + public async Task SubmitAndCatchAsyncEnumerable () + { + var wrapper = Injector.AsyncFrom( (b,i,t) => b.ReturnArg(i,t) ); + object + obj1 = new(), + obj2 = new(); + + wrapper.Submit(obj1); + wrapper.Submit(obj2); + + var objs = new List(); + var count = 0; + + await foreach ( var obj in wrapper.Elaborated() ) + { + objs.Add(obj); + if ( ++count > 2 ) + break; + } + + Assert.Multiple(() => + { + Assert.IsFalse(wrapper.AnyTaskLeft()); + CollectionAssert.AreEquivalent(new object[] { obj1, obj2 }, objs); + }); + + } + + + [Test] + [Timeout(1000)] + [Retry(3)] + public void TaskCancellation () + { + var wrapper = Injector.AsyncFrom( (b,i,t) => b.WaitForever(i,t) ); + var task = wrapper.SubmitAndGet(new object()); + var tkn = wrapper.PendingTasksCancellationToken; + + Assume.That(!tkn.IsCancellationRequested); + + var elaborationTask = wrapper.Elaborate(); + + wrapper.Dispose(); + + Assert.Multiple(() => + { + Assert.That(tkn.IsCancellationRequested); + var aggregate = Assert.Throws(()=>task.Wait()); + Assert.IsInstanceOf(aggregate.InnerException); + }); + } + + + + [Test] + public void ThrowsInvalidOnMultipleElaboration () + { + using var wrapper = Injector.AsyncFrom( (b,i,t) => b.WaitForever(i,t) ); + wrapper.Submit(new object()); + + wrapper.Elaborate(); + + Assert.Multiple(() => + { + Assert.IsTrue(wrapper.IsElaborating); + var aggregate = Assert.Throws(() => wrapper.Elaborate().Wait()); + Assert.IsInstanceOf(aggregate.InnerException); + }); + + + } + + + [Test] + [Timeout(1000)] + public async Task SubmitWithEvent () + { + using var wrapper = Injector.AsyncFrom( + (b,i,t) => b.ReturnArg(i,t), + StackWrapperSettings.With( + runtime: RuntimeOptions.Default + .WaitTimeout(-1)) + ); + + // test holders + var semaphore = new SemaphoreSlim(0); + + object + token = new(), + tokentest = null; + IStackWrapperCore wrappertest = null; + + wrapper.OnElaborated += ( sender, args ) => + { + tokentest = args.Result; + wrappertest = (IStackWrapperCore)sender; + semaphore.Release(); + }; + + var eltask = wrapper.Elaborate(); + + await wrapper.SubmitAndGet(token); + await semaphore.WaitAsync(); + + + Assert.AreSame(token, tokentest); + Assert.AreSame(wrapper, wrappertest); + } + + + [Test] + [Timeout(1000)] + public async Task StopOnTimeout () + { + using var wrapper = Injector.AsyncFrom( + (b,i,t) => b.ReturnArg(i,t), + StackWrapperSettings.With( + runtime: + RuntimeOptions.Default + .WaitTimeout(10) + ) + ); + + Assume.That(!wrapper.IsElaborating); + + var elTask = wrapper.Elaborate(); + + await elTask; + + Assert.That(!wrapper.IsElaborating); + Assert.That(!wrapper.AnyTaskLeft()); + + } + + } +} diff --git a/tests/StackInjector.TEST.BlackBox/settings/Versioning.cs b/tests/StackInjector.TEST.BlackBox/settings/Versioning.cs new file mode 100644 index 0000000..15d2e2a --- /dev/null +++ b/tests/StackInjector.TEST.BlackBox/settings/Versioning.cs @@ -0,0 +1,125 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; +using NUnit.Framework; +using StackInjector.Attributes; +using StackInjector.Settings; +using StackInjector.TEST.ExternalAssembly; +using StackInjector.TEST.Structures.Simple; +using static StackInjector.TEST.BlackBox.Injection; + +namespace StackInjector.TEST.BlackBox +{ + + #pragma warning disable 0649 + + [TestFixture] + class Versioning + { + #region maj min exact + + [Service] private class VersionClass {[Served] internal Level1_11 Level; } + + [Test] + public void ServedVersioningClass () + { + var settings = StackWrapperSettings.Default; + settings.Versioning + .VersioningMethod(ServedVersionTargetingMethod.LatestMajor, true); + + /* CLASSES ARE NOT VERSIONED, ONLY INTERFACES */ + Assert.IsInstanceOf( + Injector.From(settings).Entry.Level + ); + + } + + [Service] private class VersionInterface {[Served(TargetVersion = 1.0)] public ILevel1 level1; } + + [Test] + public void ServedVersioningInterface () + { + var versionedService = Injector.From().Entry.level1; + + Assert.IsInstanceOf(versionedService); + } + + + [Test] + public void SettingVersioningLatestMaj () + { + var settings = StackWrapperSettings.Default; + settings.Versioning + .VersioningMethod(ServedVersionTargetingMethod.LatestMajor, true); + + var versionedService = Injector.From( settings ).Entry.level1; + + Assert.IsInstanceOf(versionedService); + } + + + [Test] + public void SettingVersioningLatestMin () + { + var settings = StackWrapperSettings.Default; + settings.Versioning + .VersioningMethod(ServedVersionTargetingMethod.LatestMinor, true); + + var versionedService = Injector.From( settings ).Entry.level1; + + Assert.IsInstanceOf(versionedService); + } + + #endregion + + + [Service(Version = 2.0)] + private class _ExternalAssemblyReference_Local : ExternalAssembly.IExternalClass + { + public void SomeMethod () => throw new NotImplementedException(); + } + + [Test] + public void ExternalAssembly_LocalImplementation_Throws () + { + var settings = StackWrapperSettings.With( + mask: MaskOptions.BlackList + ); + settings.Mask.Add(typeof(ExternalAssembly.Externalclass)); + Assert.Throws(() => Injector.From<_ExternalAssemblyReference_Interface>(settings)); + } + + [Test] + public void ExternalAssembly_LocalImplementation_Vers () + { + var settings = StackWrapperSettings.With( + mask: MaskOptions.BlackList + ); + settings.Mask.Add(typeof(Externalclass)); + settings.Versioning.AddAssembliesToLookup(Assembly.GetExecutingAssembly()); + + var wrapper = Injector.From<_ExternalAssemblyReference_Interface>(settings); + Assert.IsInstanceOf<_ExternalAssemblyReference_Local>(wrapper.Entry.externalclass); + } + + [Test] + public void ExternalAssembly_LocalImplementation_Bind () + { + var settings = StackWrapperSettings.With( + mask: MaskOptions.BlackList + ); + settings.Mask + .Add(typeof(Externalclass)); + settings.Versioning + .AddAssembliesToLookup(typeof(IExternalClass).Assembly) + .AddInterfaceBinding(); + + var wrapper = Injector.From<_ExternalAssemblyReference_Interface>(settings); + Assert.IsInstanceOf<_ExternalAssemblyReference_Local>(wrapper.Entry.externalclass); + } + + } +}