Files
Pf2eDataScrapper/Pf2e/Scrapper/Processors/Collection/ProcessorCollection.cs
T

297 lines
9.9 KiB
C#

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;
/// <summary>
/// Класс с коллекцией обработчиков.
/// </summary>
internal class ProcessorCollection
{
public IServiceProvider ServiceProvider { get; }
/// <summary>
/// Коллекция всех доступных обработчиков.
/// </summary>
public ICollection<ProcessorRecord> AllItems { get; } = [];
/// <summary>
/// Коллекция обработчиков, которые будут обрабатываться.
/// Для изменения необходимо использовать <see cref="FilterProcessors"/>.
/// </summary>
public List<ProcessorRecord> Items { get; private set; } = [];
/// <summary>
/// Обеспечение доступа к элементам обработчиков.
/// </summary>
/// <param name="name"></param>
/// <returns></returns>
public ProcessorRecord this[string name] => AllItems.First(item => item.Name == name);
/// <summary>
/// Конструктор класса <see cref="ProcessorCollection"/>.
/// </summary>
public ProcessorCollection(IServiceProvider serviceProvider)
{
ServiceProvider = serviceProvider;
Assembly assembly = Assembly.GetExecutingAssembly();
foreach (Type type in assembly.GetTypes())
{
ProcessType(type, type.GetCustomAttribute<AbstractProcessorAttribute>());
}
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<ProcessorDependenciesAttribute>();
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<SimpleProcessorFactory>(
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<ProcessorDependenciesAttribute>();
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<CompendiumProcessorFactory>(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<ProcessorDependenciesAttribute>();
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);
}
/// <summary>
/// Проверка, что все зависимости всех обработчиков могут быть удовлетворены.
/// </summary>
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.");
}
}
}
}
/// <summary>
/// Сортировать обработчики в соответсвии со всеми зависимостями.
/// </summary>
public void SortItems()
{
Items.ForEach(item => item.Priority = -1);
Dictionary<string, int> priorities = [];
while (priorities.Count < Items.Count)
{
foreach (ProcessorRecord 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()
)
{
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();
}
/// <summary>
/// Получить список типов, которые обрабатываются в компендиуме.
/// </summary>
/// <param name="compendium">Тип компендиума.</param>
/// <returns>Набор типов.</returns>
public ICollection<string> 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<string>()
.ToList();
}