using System.ComponentModel; using System.Reflection; using Pf2eDataScrapper.Processors; namespace Pf2eDataScrapper.App; /// /// Класс с коллекцией обработчиков. /// internal class ProcessorCollection { /// /// Коллекция всех доступных обработчиков. /// public ICollection AllItems { get; } = []; /// /// Коллекция обработчиков, которые будут обрабатываться. /// Для изменения необходимо использовать . /// public List Items { get; private set; } = []; /// /// Конструктор класса . /// public ProcessorCollection() { Assembly assembly = Assembly.GetExecutingAssembly(); foreach (Type type in assembly.GetTypes()) { if (type.GetCustomAttributes(typeof(ProcessAttribute), true).Length <= 0) { continue; } AllItems.Add(CreateItem(type)); } CheckDependencies(); FilterProcessors([]); } /// /// Создание элемента коллекции обработчиков. /// /// Тип обработчика. /// private ProcessorCollectionItem CreateItem(Type type) { if (!type.IsSubclassOf(typeof(IProcessor))) { throw new( $"'ProcessAttribute' attribute should only be applied to children of 'IProcessor' class. '{type.FullName}' is not." ); } ProcessAttribute processAttribute = type.GetCustomAttribute()!; DescriptionAttribute? descriptionAttribute = type.GetCustomAttribute(); ProcessorGroupAttribute? groupAttribute = type.GetCustomAttribute(); ProcessorDependenciesAttribute? dependenciesAttribute = type.GetCustomAttribute(); ProcessorInnerTypeAttribute? innerTypeAttribute = type.GetCustomAttribute(); bool isCollection = innerTypeAttribute is not null; ConstructorInfo? constructor = isCollection ? type.GetConstructor([]) : type.GetConstructor([typeof(ProcessorInput)])!; if (constructor is null) { throw new($"Не удаётся определить конструктор класса {type}"); } return new() { Name = processAttribute.Name, Type = type, Description = descriptionAttribute?.Description ?? processAttribute.Name, Group = groupAttribute?.Group ?? ProcessorGroupEnum.General, Dependencies = dependenciesAttribute?.Dependencies ?? [], IsCollectionProcessor = isCollection, InnerType = innerTypeAttribute?.InnerType, Constructor = constructor, }; } /// /// Проверка, что все зависимости всех обработчиков могут быть удовлетворены. /// private void CheckDependencies() { foreach (ProcessorCollectionItem 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() { Dictionary priorities = []; while (priorities.Count < Items.Count) { foreach (ProcessorCollectionItem 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 = dependencyPriorities.Any() ? dependencyPriorities.Max() + 1 : 1; 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() ) { ProcessorCollectionItem 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(); } } /// /// Элемент коллекции обработчиков. /// internal class ProcessorCollectionItem { /// /// Название обработчика. /// public required string Name { get; init; } /// /// Тип класса обработчика. /// public required Type Type { get; init; } /// /// Описание обработчика. /// public string Description { get; init; } = ""; /// /// Группа обработчика. /// public required ProcessorGroupEnum Group { get; init; } /// /// Коллекция названий обработчиков, после которых должен выполняться обработчик. /// public ICollection Dependencies { get; init; } = []; /// /// Приоритет выполнения обработчика. /// public int Priority { get; set; } = -1; /// /// Флаг, указывающий, обрабатывает ли обработчик коллекцию элементов. /// Внутренний тип элементов указывается в . /// public required bool IsCollectionProcessor { get; init; } /// /// Флаг, указывающий внутренний тип коллекции обрабатываемых элементов. /// public string? InnerType { get; init; } /// /// Конструктор обработчика. /// public required ConstructorInfo Constructor { get; init; } }