262 lines
9.1 KiB
C#
262 lines
9.1 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Reflection;
|
|
using Pf2eDataScrapper.Processors;
|
|
|
|
namespace Pf2eDataScrapper.App;
|
|
|
|
/// <summary>
|
|
/// Класс с коллекцией обработчиков.
|
|
/// </summary>
|
|
internal class ProcessorCollection
|
|
{
|
|
/// <summary>
|
|
/// Коллекция всех доступных обработчиков.
|
|
/// </summary>
|
|
public ICollection<ProcessorCollectionItem> AllItems { get; } = [];
|
|
|
|
/// <summary>
|
|
/// Коллекция обработчиков, которые будут обрабатываться.
|
|
/// Для изменения необходимо использовать <see cref="FilterProcessors"/>.
|
|
/// </summary>
|
|
public List<ProcessorCollectionItem> Items { get; private set; } = [];
|
|
|
|
/// <summary>
|
|
/// Обеспечение доступа к элементам обработчиков.
|
|
/// </summary>
|
|
/// <param name="name"></param>
|
|
/// <returns></returns>
|
|
public ProcessorCollectionItem this[string name] => AllItems.First(item => item.Name == name);
|
|
|
|
/// <summary>
|
|
/// Конструктор класса <see cref="ProcessorCollection"/>.
|
|
/// </summary>
|
|
public ProcessorCollection()
|
|
{
|
|
Assembly assembly = Assembly.GetExecutingAssembly();
|
|
|
|
foreach (Type type in assembly.GetTypes())
|
|
{
|
|
if (type.GetCustomAttributes(typeof(ProcessorAttribute), true).Length <= 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
AllItems.Add(CreateItem(type));
|
|
}
|
|
|
|
CheckDependencies();
|
|
FilterProcessors([]);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Создание элемента коллекции обработчиков.
|
|
/// </summary>
|
|
/// <param name="type">Тип обработчика.</param>
|
|
/// <returns></returns>
|
|
private ProcessorCollectionItem CreateItem(Type type)
|
|
{
|
|
if (!type.IsAssignableTo(typeof(IProcessor)))
|
|
{
|
|
throw new(
|
|
$"'ProcessorAttribute' attribute should only be applied to children of 'IProcessor' class. '{type.FullName}' is not."
|
|
);
|
|
}
|
|
|
|
ProcessorAttribute processorAttribute = type.GetCustomAttribute<ProcessorAttribute>()!;
|
|
ProcessorDependenciesAttribute? dependenciesAttribute =
|
|
type.GetCustomAttribute<ProcessorDependenciesAttribute>();
|
|
|
|
bool isCollection = processorAttribute.Collection;
|
|
|
|
string? innerType = null;
|
|
ProcessorCompendiumEnum? compendium = null;
|
|
|
|
if (processorAttribute.GetType() == typeof(ProcessorCollectionAttribute))
|
|
{
|
|
ProcessorCollectionAttribute processorCollectionAttribute =
|
|
(ProcessorCollectionAttribute)processorAttribute;
|
|
|
|
innerType = processorCollectionAttribute.InnerType;
|
|
compendium = processorCollectionAttribute.CompendiumType;
|
|
}
|
|
|
|
return new()
|
|
{
|
|
Name = processorAttribute.Name,
|
|
Type = type,
|
|
Description = processorAttribute.Description,
|
|
Group = processorAttribute.Group,
|
|
Dependencies = dependenciesAttribute?.Dependencies ?? [],
|
|
IsCollection = isCollection,
|
|
InnerType = innerType,
|
|
Compendium = compendium,
|
|
Active = processorAttribute.Active,
|
|
Parallel = processorAttribute.Parallel,
|
|
};
|
|
}
|
|
|
|
/// <summary>
|
|
/// Проверка, что все зависимости всех обработчиков могут быть удовлетворены.
|
|
/// </summary>
|
|
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.");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Сортировать обработчики в соответсвии со всеми зависимостями.
|
|
/// </summary>
|
|
public void SortItems()
|
|
{
|
|
Items.ForEach(item => item.Priority = -1);
|
|
|
|
Dictionary<string, int> priorities = [];
|
|
|
|
while (priorities.Count < Items.Count)
|
|
{
|
|
foreach (ProcessorCollectionItem item in Items)
|
|
{
|
|
if (item.Priority >= 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
IEnumerable<int> 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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Ограничить список используемых обработчиков по названию.
|
|
/// Автоматически включает все обработчики, от которых зависят указанные в аргументе обработчики.
|
|
/// </summary>
|
|
/// <param name="processorNames">Коллекция названиий требуемых обработчиков.</param>
|
|
public void FilterProcessors(ICollection<string> processorNames)
|
|
{
|
|
if (processorNames.Count == 0)
|
|
{
|
|
Items = [.. AllItems];
|
|
SortItems();
|
|
return;
|
|
}
|
|
|
|
Dictionary<string, bool> nameFlags = processorNames.ToDictionary(name => name, _ => false);
|
|
|
|
while (nameFlags.Values.Any(v => !v))
|
|
{
|
|
foreach (
|
|
KeyValuePair<string, bool> 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();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Получить список типов, которые обрабатываются в компендиуме.
|
|
/// </summary>
|
|
/// <param name="compendium">Тип компендиума.</param>
|
|
/// <returns>Набор типов.</returns>
|
|
public ICollection<string> GetTypesOfCompendium(ProcessorCompendiumEnum compendium) =>
|
|
Items.Select(item => item.InnerType).OfType<string>().ToList();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Элемент коллекции обработчиков.
|
|
/// </summary>
|
|
internal class ProcessorCollectionItem
|
|
{
|
|
/// <summary>
|
|
/// Название обработчика.
|
|
/// </summary>
|
|
public required string Name { get; init; }
|
|
|
|
/// <summary>
|
|
/// Тип класса обработчика.
|
|
/// </summary>
|
|
public required Type Type { get; init; }
|
|
|
|
/// <summary>
|
|
/// Описание обработчика.
|
|
/// </summary>
|
|
public string Description { get; init; } = "";
|
|
|
|
/// <summary>
|
|
/// Группа обработчика.
|
|
/// </summary>
|
|
public required ProcessorGroupEnum Group { get; init; }
|
|
|
|
/// <summary>
|
|
/// Коллекция названий обработчиков, после которых должен выполняться обработчик.
|
|
/// </summary>
|
|
public ICollection<string> Dependencies { get; init; } = [];
|
|
|
|
/// <summary>
|
|
/// Процессор включён по умолчанию.
|
|
/// </summary>
|
|
public bool Active { get; init; } = true;
|
|
|
|
/// <summary>
|
|
/// Приоритет выполнения обработчика.
|
|
/// </summary>
|
|
public int Priority { get; set; } = -1;
|
|
|
|
/// <summary>
|
|
/// Флаг, указывающий, обрабатывает ли обработчик коллекцию элементов.
|
|
/// Внутренний тип элементов указывается в <see cref="InnerType"/>.
|
|
/// </summary>
|
|
public required bool IsCollection { get; init; }
|
|
|
|
/// <summary>
|
|
/// Флаг, указывающий внутренний тип коллекции обрабатываемых элементов.
|
|
/// </summary>
|
|
public string? InnerType { get; init; }
|
|
|
|
/// <summary>
|
|
/// Название типов, который обрабатывает этот оброботчик.
|
|
/// <c>null</c> - обработать все типы.
|
|
/// </summary>
|
|
public ProcessorCompendiumEnum? Compendium { get; init; }
|
|
|
|
/// <summary>
|
|
/// Разрешить выполнять процессов в параллельном режиме
|
|
/// </summary>
|
|
public bool Parallel { get; init; } = true;
|
|
}
|