sources/Google.Solutions.IapDesktop.Core/EntityModel/EntityContext.Builder.cs (258 lines of code) (raw):
//
// Copyright 2024 Google LLC
//
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
//
using Google.Solutions.Apis.Locator;
using Google.Solutions.IapDesktop.Core.ObjectModel;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
namespace Google.Solutions.IapDesktop.Core.EntityModel
{
public partial class EntityContext
{
/// <summary>
/// Builder for EntityContext objects.
/// </summary>
public class Builder
{
private readonly List<RegisteredEntityCache> entityCaches =
new List<RegisteredEntityCache>();
private readonly List<RegisteredEntityNavigator> entityNavigators =
new List<RegisteredEntityNavigator>();
private readonly List<RegisteredEntitySearcher> entitySearchers =
new List<RegisteredEntitySearcher>();
private readonly List<RegisteredAspectProvider> aspectProviders =
new List<RegisteredAspectProvider>();
private readonly List<RegisteredAsyncAspectProvider> asyncAspectProviders =
new List<RegisteredAsyncAspectProvider>();
private readonly MethodInfo addCacheCoreMethod;
private readonly MethodInfo addNavigatorCoreMethod;
private readonly MethodInfo addSearcherCoreMethod;
private readonly MethodInfo addAspectProviderCoreMethod;
private readonly MethodInfo addAsyncAspectProviderCoreMethod;
public IEventQueue EventQueue { get; }
public Builder(IEventQueue eventQueue)
{
this.EventQueue = eventQueue;
this.addCacheCoreMethod = GetType().GetMethod(
nameof(AddCacheCore),
BindingFlags.Instance | BindingFlags.NonPublic);
this.addNavigatorCoreMethod = GetType().GetMethod(
nameof(AddNavigatorCore),
BindingFlags.Instance | BindingFlags.NonPublic);
this.addSearcherCoreMethod = GetType().GetMethod(
nameof(AddSearcherCore),
BindingFlags.Instance | BindingFlags.NonPublic);
this.addAspectProviderCoreMethod = GetType().GetMethod(
nameof(AddAspectProviderCore),
BindingFlags.Instance | BindingFlags.NonPublic);
this.addAsyncAspectProviderCoreMethod = GetType().GetMethod(
nameof(AddAsyncAspectProviderCore),
BindingFlags.Instance | BindingFlags.NonPublic);
//
// Enable introspection of registered searchers.
//
var introspector = new Introspector(this.entitySearchers);
AddSearcher(introspector);
AddNavigator(introspector);
}
/// <summary>
/// Lookup a type's implemented interface based on its unbound type.
/// </summary>
private static IEnumerable<Type> GetGenericInterfaces(
Type type,
Type unboundType)
{
return type
.GetInterfaces()
.Where(
i => i.IsGenericType &&
i.GetGenericTypeDefinition() == unboundType);
}
private void AddCacheCore<TLocator>(IEntityCache<TLocator> cache)
where TLocator : ILocator
{
this.entityCaches.Add(new RegisteredEntityCache(
typeof(TLocator),
locator => cache.Invalidate((TLocator)locator)));
}
private void AddNavigatorCore<TLocator, TEntity>(
IEntityNavigator<TLocator, TEntity> navigator)
where TLocator : ILocator
where TEntity : IEntity<ILocator>
{
this.entityNavigators.Add(new RegisteredEntityNavigator(
typeof(TLocator),
typeof(TEntity),
async (locator, cancellationToken) =>
{
Debug.Assert(locator is TLocator);
var result = await navigator
.ListDescendantsAsync((TLocator)locator, cancellationToken)
.ConfigureAwait(false);
return result.Cast<IEntity<ILocator>>().ToList();
}));
}
private void AddSearcherCore<TQuery, TEntity>(
IEntitySearcher<TQuery, TEntity> searcher)
where TEntity : IEntity<ILocator>
{
this.entitySearchers.Add(new RegisteredEntitySearcher(
typeof(TEntity),
typeof(TQuery),
async (query, cancellationToken) =>
{
Debug.Assert(query is TQuery);
var result = await searcher
.SearchAsync((TQuery)query, cancellationToken)
.ConfigureAwait(false);
return result.Cast<IEntity<ILocator>>().ToList();
}));
}
private void AddAspectProviderCore<TLocator, TAspect>(
IEntityAspectProvider<TLocator, TAspect> provider)
where TLocator : ILocator
where TAspect : class
{
this.aspectProviders.Add(new RegisteredAspectProvider(
typeof(TLocator),
typeof(TAspect),
locator =>
{
Debug.Assert(locator is TLocator);
return provider.QueryAspect((TLocator)locator);
}));
}
private void AddAsyncAspectProviderCore<TLocator, TAspect>(
IAsyncEntityAspectProvider<TLocator, TAspect> provider)
where TLocator : ILocator
where TAspect : class
{
this.asyncAspectProviders.Add(new RegisteredAsyncAspectProvider(
typeof(TLocator),
typeof(TAspect),
async (locator, cancellationToken) =>
{
Debug.Assert(locator is TLocator);
var result = await provider
.QueryAspectAsync((TLocator)locator, cancellationToken)
.ConfigureAwait(false);
Debug.Assert(result != null);
return result!;
}));
}
internal Builder AddCache(IEntityCache cache)
{
foreach (var genericInterface in GetGenericInterfaces(
cache.GetType(),
typeof(IEntityCache<>)))
{
this.addCacheCoreMethod
.MakeGenericMethod(genericInterface.GenericTypeArguments)
.Invoke(this, new object[] { cache });
}
return this;
}
public Builder AddNavigator(IEntityNavigator navigator)
{
foreach (var genericInterface in GetGenericInterfaces(
navigator.GetType(),
typeof(IEntityNavigator<,>)))
{
this.addNavigatorCoreMethod
.MakeGenericMethod(genericInterface.GenericTypeArguments)
.Invoke(this, new object[] { navigator });
}
if (navigator is IEntityCache cache)
{
AddCache(cache);
}
return this;
}
public Builder AddSearcher(IEntitySearcher searcher)
{
foreach (var genericInterface in GetGenericInterfaces(
searcher.GetType(),
typeof(IEntitySearcher<,>)))
{
this.addSearcherCoreMethod
.MakeGenericMethod(genericInterface.GenericTypeArguments)
.Invoke(this, new object[] { searcher });
}
if (searcher is IEntityCache cache)
{
AddCache(cache);
}
return this;
}
public Builder AddAspectProvider(IEntityAspectProvider provider)
{
//
// Synchrounous.
//
foreach (var genericInterface in GetGenericInterfaces(
provider.GetType(),
typeof(IEntityAspectProvider<,>)))
{
this.addAspectProviderCoreMethod
.MakeGenericMethod(genericInterface.GenericTypeArguments)
.Invoke(this, new object[] { provider });
}
//
// Asynchrounous.
//
foreach (var genericInterface in GetGenericInterfaces(
provider.GetType(),
typeof(IAsyncEntityAspectProvider<,>)))
{
this.addAsyncAspectProviderCoreMethod
.MakeGenericMethod(genericInterface.GenericTypeArguments)
.Invoke(this, new object[] { provider });
}
if (provider is IEntityCache cache)
{
AddCache(cache);
}
return this;
}
public Builder AddNavigators(IEnumerable<IEntityNavigator> navigator)
{
foreach (var container in navigator)
{
AddNavigator(container);
}
return this;
}
public Builder AddSearchers(IEnumerable<IEntitySearcher> searchers)
{
foreach (var searcher in searchers)
{
AddSearcher(searcher);
}
return this;
}
public Builder AddAspectProviders(IEnumerable<IEntityAspectProvider> providers)
{
foreach (var provider in providers)
{
AddAspectProvider(provider);
}
return this;
}
internal IDictionary<Type, LocatorConfiguration> BuildLocatorConfiguration()
{
return Enumerable.Empty<Type>()
.Concat(this.entityNavigators.Select(c => c.LocatorType))
.Concat(this.aspectProviders.Select(c => c.LocatorType))
.Concat(this.asyncAspectProviders.Select(c => c.LocatorType))
.Distinct()
.ToDictionary(
type => type,
type => new LocatorConfiguration(
type,
this.entityNavigators.Where(c => c.LocatorType == type).ToList(),
this.aspectProviders.Where(c => c.LocatorType == type).ToList(),
this.asyncAspectProviders.Where(c => c.LocatorType == type).ToList()));
}
internal List<RegisteredEntityCache> BuildCaches()
{
return this.entityCaches;
}
internal IDictionary<Type, List<RegisteredEntitySearcher>> BuildSearchers()
{
return this.entitySearchers
.GroupBy(s => s.QueryType)
.ToDictionary(g => g.Key, g => g.ToList());
}
public EntityContext Build()
{
return new EntityContext(this);
}
}
}
}