Начало переработки программы (не рабочий промежуточный коммит).
This commit is contained in:
@@ -0,0 +1,229 @@
|
||||
using System.ComponentModel;
|
||||
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>
|
||||
/// Конструктор класса <see cref="ProcessorCollection"/>.
|
||||
/// </summary>
|
||||
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([]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Создание элемента коллекции обработчиков.
|
||||
/// </summary>
|
||||
/// <param name="type">Тип обработчика.</param>
|
||||
/// <returns></returns>
|
||||
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<ProcessAttribute>()!;
|
||||
DescriptionAttribute? descriptionAttribute =
|
||||
type.GetCustomAttribute<DescriptionAttribute>();
|
||||
ProcessorGroupAttribute? groupAttribute =
|
||||
type.GetCustomAttribute<ProcessorGroupAttribute>();
|
||||
ProcessorDependenciesAttribute? dependenciesAttribute =
|
||||
type.GetCustomAttribute<ProcessorDependenciesAttribute>();
|
||||
ProcessorInnerTypeAttribute? innerTypeAttribute =
|
||||
type.GetCustomAttribute<ProcessorInnerTypeAttribute>();
|
||||
|
||||
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,
|
||||
};
|
||||
}
|
||||
|
||||
/// <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()
|
||||
{
|
||||
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 = dependencyPriorities.Any() ? dependencyPriorities.Max() + 1 : 1;
|
||||
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>
|
||||
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 int Priority { get; set; } = -1;
|
||||
|
||||
/// <summary>
|
||||
/// Флаг, указывающий, обрабатывает ли обработчик коллекцию элементов.
|
||||
/// Внутренний тип элементов указывается в <see cref="InnerType"/>.
|
||||
/// </summary>
|
||||
public required bool IsCollectionProcessor { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Флаг, указывающий внутренний тип коллекции обрабатываемых элементов.
|
||||
/// </summary>
|
||||
public string? InnerType { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Конструктор обработчика.
|
||||
/// </summary>
|
||||
public required ConstructorInfo Constructor { get; init; }
|
||||
}
|
||||
Reference in New Issue
Block a user