297 lines
9.9 KiB
C#
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();
|
|
}
|