This repository has been archived on 2023-02-02. You can view files and clone it, but cannot push or open issues or pull requests.
mfgames-nitride-cil/src/Nitride/Entities/CreateIndexEntities.cs
2022-06-06 21:46:31 -05:00

180 lines
5.7 KiB
C#

using System;
using System.Collections.Generic;
using FluentValidation;
using Gallium;
using Serilog;
namespace Nitride.Entities;
/// <summary>
/// A Nitride operation that creates and merges entities that are intended
/// to be indexes of another entity. For example, this could be year and
/// month archive pages, tag or category pages. Support is given for
/// merging existing pages so a description could be written from a file
/// and then the index logic is automatically added.
/// </summary>
public class CreateIndexEntities<TIndexKey> : OperationBase
where TIndexKey : notnull
{
// TODO: This does not use [WithProperties] because the source generator hasn't been taught how to do generics.
private readonly ILogger logger;
private readonly IValidator<CreateIndexEntities<TIndexKey>> validator;
public CreateIndexEntities(ILogger logger)
{
// TODO: Figure out why Autofac won't let us register IValidator of generic classes.
this.validator = new CreateIndexEntitiesValidator<TIndexKey>();
this.logger = logger.ForContext(typeof(CreateIndexEntities<>));
}
/// <summary>
/// Creates an index for a given key. This will not be called for any
/// index that has been already created.
/// </summary>
public Func<TIndexKey, IList<Entity>, Entity>? CreateIndexEntity
{
get;
set;
}
/// <summary>
/// Gets or sets the function to retrieve the key from an existing
/// index page. If this returns null, then the entity is considered not
/// to be an index page.
/// </summary>
public Func<Entity, TIndexKey?>? GetIndexEntityKey { get; set; }
/// <summary>
/// A method that gets the keys for a given entity. If this returns an
/// empty list, then the entity will not added to an index.
/// </summary>
public Func<Entity, IEnumerable<TIndexKey>>? GetIndexKeys { get; set; }
/// <summary>
/// Updates an existing index entity to include new information.
/// </summary>
public Func<Entity, TIndexKey, IList<Entity>, Entity>? UpdateIndexEntity
{
get;
set;
}
/// <inheritdoc />
public override IEnumerable<Entity> Run(IEnumerable<Entity> input)
{
// Make sure we have sane data.
this.validator.ValidateAndThrow(this);
// We need to process two lists out of the output, so we need to put
// it into a list so we can enumerate through it twice. This will
// also cause the output to be reordered.
Dictionary<TIndexKey, Entity> indexes = new();
Dictionary<TIndexKey, List<Entity>> indexed = new();
List<Entity> results = new();
foreach (Entity? entity in input)
{
// See if we are an index page first.
if (this.GetIndexEntityKey != null)
{
TIndexKey? indexKey = this.GetIndexEntityKey(entity);
if (indexKey != null)
{
indexes[indexKey] = entity;
continue;
}
}
// We aren't an index, so check to see if this entity is
// something to be indexed.
foreach (TIndexKey indexedKey in this.GetIndexKeys!(entity))
{
if (!indexed.TryGetValue(indexedKey, out List<Entity>? list))
{
indexed[indexedKey] = list = new List<Entity>();
}
list.Add(entity);
}
// Add to the non-index page list.
results.Add(entity);
}
// Go through all the index pages and update them. We get a list of
// all the pages in the index and pass them into the function to
// update the existing index. Then we update the entity and add it
// to the bottom of the results list.
foreach ((TIndexKey key, Entity? oldIndex) in indexes)
{
if (!indexed.TryGetValue(key, out List<Entity>? list))
{
list = new List<Entity>();
}
Entity newEntity = this.UpdateIndexEntity!(oldIndex, key, list);
results.Add(newEntity);
}
// Go through all the known index keys and create the missing pages.
int created = 0;
foreach ((TIndexKey key, List<Entity>? list) in indexed)
{
// See if we already have a page, if we do, then we've already
// processed that page and don't have to do anything.
if (indexes.ContainsKey(key))
{
continue;
}
// We don't have that page and need to add it to the list.
Entity entity = this.CreateIndexEntity!(key, list);
created++;
results.Add(entity);
}
// Return the combined together version.
this.logger.Debug(
"Found {Old:N0} and created {New:N0} index pages for {Keys:N0} keys",
indexes.Count,
created,
indexed.Count);
return results;
}
public CreateIndexEntities<TIndexKey> WithCreateIndexEntity(Func<TIndexKey, IList<Entity>, Entity>? callback)
{
this.CreateIndexEntity = callback;
return this;
}
public CreateIndexEntities<TIndexKey> WithGetIndexEntityKey(Func<Entity, TIndexKey?>? callback)
{
this.GetIndexEntityKey = callback;
return this;
}
public CreateIndexEntities<TIndexKey> WithGetIndexKeys(Func<Entity, IEnumerable<TIndexKey>>? callback)
{
this.GetIndexKeys = callback;
return this;
}
public CreateIndexEntities<TIndexKey> WithUpdateIndexEntity(
Func<Entity, TIndexKey, IList<Entity>, Entity>? callback)
{
this.UpdateIndexEntity = callback;
return this;
}
}