using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using LinqToDB; using Microsoft.Extensions.DependencyInjection; using Pf2e.Scrapper.Processors.Attributes; using Pf2e.Scrapper.Processors.Definitions; using Pf2e.Scrapper.Processors.Factory; namespace Pf2e.Scrapper.Processors.Collection; /// /// Класс с коллекцией обработчиков. /// internal class ProcessorCollection { public IServiceProvider ServiceProvider { get; } /// /// Коллекция всех доступных обработчиков. /// public ICollection AllItems { get; } = []; /// /// Коллекция обработчиков, которые будут обрабатываться. /// Для изменения необходимо использовать . /// public List Items { get; private set; } = []; /// /// Обеспечение доступа к элементам обработчиков. /// /// /// public ProcessorRecord this[string name] => AllItems.First(item => item.Name == name); /// /// Конструктор класса . /// public ProcessorCollection(IServiceProvider serviceProvider) { ServiceProvider = serviceProvider; Assembly assembly = Assembly.GetExecutingAssembly(); foreach (Type type in assembly.GetTypes()) { ProcessType(type, type.GetCustomAttribute()); } CheckDependencies(); FilterProcessors([]); } private void ProcessType(Type type, AbstractProcessorAttribute? attribute) { switch (attribute) { case null: break; case ProcessorAttribute processorAttribute: ProcessType(type, processorAttribute); break; case ProcessorCollectionAttribute processorCollectionAttribute: ProcessType(type, processorCollectionAttribute); break; case ProcessorFactoryAttribute processorFactoryAttribute: ProcessType(type, processorFactoryAttribute); break; default: throw new( $"Неизвестная реализация класса {typeof(AbstractProcessorAttribute)} - {attribute.GetType()}" ); } } private void ProcessType(Type type, ProcessorAttribute? attribute) { if (attribute is null) { return; } if (!type.IsAssignableTo(typeof(IProcessor))) { throw new( $"Процессор {type}, которому установлен аттрибут {attribute.GetType()}, не представляет интерфейс {typeof(IProcessor)}" ); } ProcessorDependenciesAttribute? dependenciesAttribute = type.GetCustomAttribute(); ProcessorRecord item = new() { Name = attribute.Name, Type = type, Description = attribute.Description, Group = attribute.Group, Dependencies = dependenciesAttribute?.Dependencies ?? [], Active = attribute.Active, Parallel = attribute.Parallel, }; item.Factory = ActivatorUtilities.CreateInstance( ServiceProvider, item ); AllItems.Add(item); } private void ProcessType(Type type, ProcessorCollectionAttribute? attribute) { if (attribute is null) { return; } if (!type.IsAssignableTo(typeof(AbstractInputProcessor))) { throw new( $"Процессор {type}, которому установлен аттрибут {attribute.GetType()}, не реализует класс {typeof(AbstractInputProcessor)}." ); } ProcessorDependenciesAttribute? dependenciesAttribute = type.GetCustomAttribute(); ProcessorRecord item = new() { Name = attribute.Name, Type = type, Description = attribute.Description, Group = attribute.Group, Dependencies = dependenciesAttribute?.Dependencies ?? [], Active = attribute.Active, Parallel = attribute.Parallel, }; CompendiumProcessorFactory factory = ActivatorUtilities.CreateInstance(ServiceProvider, item); factory.CompendiumType = attribute.CompendiumType; factory.InnerType = attribute.InnerType; item.Factory = factory; AllItems.Add(item); } private void ProcessType(Type type, ProcessorFactoryAttribute? attribute) { if (attribute is null) { return; } if (!type.IsAssignableTo(typeof(AbstractProcessorFactory))) { throw new( $"Процессор {type}, которому установлен аттрибут {attribute.GetType()}, не реализует класс {typeof(AbstractProcessorFactory)}." ); } ProcessorDependenciesAttribute? dependenciesAttribute = type.GetCustomAttribute(); ProcessorRecord item = new() { Name = attribute.Name, Type = attribute.ProcessorType, Description = attribute.Description, Group = attribute.Group, Dependencies = dependenciesAttribute?.Dependencies ?? [], Active = attribute.Active, Parallel = attribute.Parallel, Factory = (AbstractProcessorFactory) ActivatorUtilities.CreateInstance(ServiceProvider, type), }; AllItems.Add(item); } /// /// Проверка, что все зависимости всех обработчиков могут быть удовлетворены. /// private void CheckDependencies() { foreach (ProcessorRecord item in Items) { foreach (string dependency in item.Dependencies) { if (Items.All(i => i.Name != dependency)) { throw new($"Dependency '{dependency}' for processor '{item.Name}' not found."); } } } } /// /// Сортировать обработчики в соответсвии со всеми зависимостями. /// public void SortItems() { Items.ForEach(item => item.Priority = -1); Dictionary priorities = []; while (priorities.Count < Items.Count) { foreach (ProcessorRecord item in Items) { if (item.Priority >= 0) { continue; } IEnumerable dependencyPriorities = item.Dependencies.Select(d => priorities.GetValueOrDefault(d, -1) ); if (dependencyPriorities.Any(p => p == -1)) { continue; } item.Priority = Math.Max( dependencyPriorities.Any() ? dependencyPriorities.Max() + 1 : 1, (int)item.Group * 100 ); priorities.Add(item.Name, item.Priority); } } Items.Sort((a, b) => a.Priority - b.Priority); } /// /// Ограничить список используемых обработчиков по названию. /// Автоматически включает все обработчики, от которых зависят указанные в аргументе обработчики. /// /// Коллекция названиий требуемых обработчиков. public void FilterProcessors(ICollection processorNames) { if (processorNames.Count == 0) { Items = [.. AllItems]; SortItems(); return; } Dictionary nameFlags = processorNames.ToDictionary(name => name, _ => false); while (nameFlags.Values.Any(v => !v)) { foreach ( KeyValuePair pair in nameFlags.Where(pair => !pair.Value).ToList() ) { ProcessorRecord item = AllItems.First(item => item.Name == pair.Key); foreach (string dependency in item.Dependencies) { nameFlags.TryAdd(dependency, false); } nameFlags[pair.Key] = true; } } Items = AllItems.Where(item => nameFlags.ContainsKey(item.Name)).ToList(); } /// /// Получить список типов, которые обрабатываются в компендиуме. /// /// Тип компендиума. /// Набор типов. public ICollection GetTypesOfCompendium(ProcessorCompendiumEnum compendium) => Items .Where(item => item.Factory.GetType().IsAssignableTo(typeof(CompendiumProcessorFactory)) ) .Where(item => (item.Factory as CompendiumProcessorFactory)!.CompendiumType == compendium ) .Select(item => (item.Factory as CompendiumProcessorFactory)!.InnerType) .OfType() .ToList(); }