// Copyright (c) The Geekeey Authors // SPDX-License-Identifier: EUPL-1.2 using System.Collections; using System.Collections.Concurrent; using Microsoft.Extensions.DependencyInjection; namespace Geekeey.Request.Dispatcher; internal sealed class RequestDispatcherOptions { private readonly List _search = []; private readonly Lazy _behaviorsTypeIndex; private readonly Lazy _handlersTypeIndex; public RequestDispatcherOptions() { _behaviorsTypeIndex = new Lazy(() => new BehaviorTypeIndex(_search.Distinct())); _handlersTypeIndex = new Lazy(() => new HandlerTypeIndex(_search.Distinct())); } public void Inspect(IEnumerable assembly) { if (_behaviorsTypeIndex.IsValueCreated || _handlersTypeIndex.IsValueCreated) { throw new InvalidOperationException("The type index has already been created. Cannot inspect new assemblies."); } _search.AddRange(assembly); } public IEnumerable GetRequestBehaviors(IServiceProvider services) { return _behaviorsTypeIndex.Value.Resolve(services); } public IEnumerable GetRequestHandlers(IServiceProvider services) { return _handlersTypeIndex.Value.Resolve(services); } private abstract class TypeIndex { private readonly ConcurrentDictionary> _cache = new(); protected readonly Dictionary> _closedTypeInfo = []; protected readonly List _openTypeInfo = []; protected TypeIndex(IEnumerable collection, Func predicate) { foreach (var type in collection) { if (type.IsGenericTypeDefinition) { if (type.GetInterfaces().Any(predicate)) { _openTypeInfo.Add(type); } } else { foreach (var @interface in type.GetInterfaces().Where(predicate)) { (_closedTypeInfo.TryGetValue(@interface, out var list) ? list : _closedTypeInfo[@interface] = []).Add(type); } } } } public IEnumerable Resolve(IServiceProvider services) { return (IEnumerable)_cache.GetOrAdd(typeof(T), CreateResolverFactory)(services); } protected abstract IReadOnlyList IsAssignableTo(Type type); private Func> CreateResolverFactory(Type @interface) { var list = IsAssignableTo(@interface); return ResolverFactory; IEnumerable ResolverFactory(IServiceProvider services) { foreach (var type in list) { yield return (T)ActivatorUtilities.GetServiceOrCreateInstance(services, type); } } } } internal static bool IsRequestHandlerType(Type type) { return type.IsGenericType && (type.GetGenericTypeDefinition() == typeof(IScalarRequestHandler<,>) || type.GetGenericTypeDefinition() == typeof(IStreamRequestHandler<,>)); } private sealed class HandlerTypeIndex(IEnumerable collection) : TypeIndex(collection, IsRequestHandlerType) { protected override IReadOnlyList IsAssignableTo(Type @interface) { var result = new List(); if (_closedTypeInfo.TryGetValue(@interface, out var list)) { result.AddRange(list); } var requestType = @interface.GetGenericArguments()[0]; _ = @interface.GetGenericArguments()[1]; foreach (var type in _openTypeInfo) { try { // open type case one: Handler : IRequestHandler // We try to close it with the request type. var impl = type.MakeGenericType(requestType); if (impl.IsAssignableTo(@interface)) { result.Add(impl); } } catch (ArgumentException) { } try { // open type case two: Handler : IRequestHandler, TOutput> // If the request is generic, we try to close the handler with the request's generic arguments. if (requestType.IsGenericType) { var impl = type.MakeGenericType(requestType.GetGenericArguments()); if (impl.IsAssignableTo(@interface)) { result.Add(impl); } } } catch (ArgumentException) { } } return result; } } internal static bool IsRequestBehaviorType(Type type) { return type.IsGenericType && (type.GetGenericTypeDefinition() == typeof(IScalarRequestBehavior<,>) || type.GetGenericTypeDefinition() == typeof(IStreamRequestBehavior<,>)); } private sealed class BehaviorTypeIndex(IEnumerable collection) : TypeIndex(collection, IsRequestBehaviorType) { protected override IReadOnlyList IsAssignableTo(Type @interface) { var result = new List(); if (_closedTypeInfo.TryGetValue(@interface, out var list)) { result.AddRange(list); } var requestType = @interface.GetGenericArguments()[0]; var responseType = @interface.GetGenericArguments()[1]; foreach (var behaviour in _openTypeInfo) { try { // open type case one: Behaviour : IRequestBehaviour var impl = behaviour.MakeGenericType(requestType, responseType); if (impl.IsAssignableTo(@interface)) { result.Add(impl); } } catch (ArgumentException) { } try { // open type case two: Behaviour : IRequestBehaviour, TResponse> var impl = behaviour.MakeGenericType(requestType.GetGenericArguments()); if (impl.IsAssignableTo(@interface)) { result.Add(impl); } } catch (ArgumentException) { } } return result; } } }