Files
FSI.BT.IR.Tools/Config.Net/Core/Box/BoxFactory.cs
Stephan Maier 647f938eee v1.2
2024-08-27 08:10:27 +02:00

205 lines
6.5 KiB
C#

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
namespace Config.Net.Core.Box
{
static class BoxFactory
{
public static Dictionary<string, ResultBox> Discover(Type t, ValueHandler valueHandler, string? basePath)
{
var result = new Dictionary<string, ResultBox>();
DiscoverProperties(t, valueHandler, result, basePath);
DiscoverMethods(t, valueHandler, result);
return result;
}
private static void DiscoverProperties(Type t, ValueHandler valueHandler, Dictionary<string, ResultBox> result, string? basePath)
{
IEnumerable<PropertyInfo> properties = GetHierarchyPublicProperties(t);
foreach (PropertyInfo pi in properties)
{
Type propertyType = pi.PropertyType;
ResultBox rbox;
bool isCollection = false;
if(ResultBox.TryGetCollection(propertyType, out propertyType))
{
if(pi.SetMethod != null)
{
throw new NotSupportedException($"Collection properties cannot have a setter. Detected at '{OptionPath.Combine(basePath, pi.Name)}'");
}
isCollection = true;
}
if(propertyType.GetTypeInfo().IsInterface)
{
rbox = new ProxyResultBox(pi.Name, propertyType);
}
else
{
rbox = new PropertyResultBox(pi.Name, propertyType);
}
ValidateSupportedType(rbox, valueHandler);
AddAttributes(rbox, pi, valueHandler);
//adjust to collection
if(isCollection)
{
rbox = new CollectionResultBox(pi.Name, rbox);
AddAttributes(rbox, pi, valueHandler);
}
result[pi.Name] = rbox;
}
}
private static void DiscoverMethods(Type t, ValueHandler valueHandler, Dictionary<string, ResultBox> result)
{
TypeInfo ti = t.GetTypeInfo();
IEnumerable<MethodInfo> methods = ti.DeclaredMethods.Where(m => !m.IsSpecialName);
foreach (MethodInfo method in methods)
{
var mbox = new MethodResultBox(method);
AddAttributes(mbox, method, valueHandler);
result[mbox.Name] = mbox;
}
}
private static void ValidateSupportedType(ResultBox rb, ValueHandler valueHandler)
{
Type? t = null;
if (rb is PropertyResultBox pbox)
t = rb.ResultBaseType;
if (t != null && !valueHandler.IsSupported(t))
{
throw new NotSupportedException($"type {t} on object '{rb.Name}' is not supported.");
}
}
private static object? GetDefaultValue(Type t)
{
if (t.GetTypeInfo().IsValueType) return Activator.CreateInstance(t);
return null;
}
private static void AddAttributes(ResultBox box, PropertyInfo pi, ValueHandler valueHandler)
{
AddAttributes(box, valueHandler,
pi.GetCustomAttribute<OptionAttribute>(),
pi.GetCustomAttribute<DefaultValueAttribute>());
}
private static void AddAttributes(ResultBox box, MethodInfo mi, ValueHandler valueHandler)
{
AddAttributes(box, valueHandler, mi.GetCustomAttribute<OptionAttribute>(), mi.GetCustomAttribute<DefaultValueAttribute>());
}
private static void AddAttributes(ResultBox box, ValueHandler valueHandler, params Attribute?[] attributes)
{
OptionAttribute? optionAttribute = attributes.OfType<OptionAttribute>().FirstOrDefault();
DefaultValueAttribute? defaultValueAttribute = attributes.OfType<DefaultValueAttribute>().FirstOrDefault();
if (optionAttribute?.Alias != null)
{
box.StoreByName = optionAttribute.Alias;
}
box.DefaultResult = GetDefaultValue(optionAttribute?.DefaultValue, box, valueHandler) ??
GetDefaultValue(defaultValueAttribute?.Value, box, valueHandler) ??
GetDefaultValue(box.ResultType);
}
private static object? GetDefaultValue(object? defaultValue, ResultBox box, ValueHandler valueHandler)
{
object? result = null;
if (defaultValue != null)
{
//validate that types for default value match
Type dvt = defaultValue.GetType();
if (dvt != box.ResultType && dvt != typeof(string))
{
throw new InvalidCastException($"Default value for option {box.Name} is of type {dvt.FullName} whereas the property has type {box.ResultType.FullName}. To fix this, either set default value to type {box.ResultType.FullName} or a string parseable to the target type.");
}
if (box.ResultType != typeof(string) && dvt == typeof(string))
{
valueHandler.TryParse(box.ResultType, (string?)defaultValue, out result);
}
}
if (result == null)
{
result = defaultValue;
}
return result;
}
private static PropertyInfo[] GetHierarchyPublicProperties(Type type)
{
var propertyInfos = new List<PropertyInfo>();
var considered = new List<TypeInfo>();
var queue = new Queue<TypeInfo>();
considered.Add(type.GetTypeInfo());
queue.Enqueue(type.GetTypeInfo());
while (queue.Count > 0)
{
TypeInfo typeInfo = queue.Dequeue();
//add base interfaces to the queue
foreach (Type subInterface in typeInfo.ImplementedInterfaces)
{
TypeInfo subInterfaceTypeInfo = subInterface.GetTypeInfo();
if (considered.Contains(subInterfaceTypeInfo)) continue;
considered.Add(subInterfaceTypeInfo);
queue.Enqueue(subInterfaceTypeInfo);
}
//add base classes to the queue
if (typeInfo.BaseType != null)
{
TypeInfo baseType = typeInfo.BaseType.GetTypeInfo();
if (!considered.Contains(baseType))
{
considered.Add(baseType);
queue.Enqueue(baseType);
}
}
//get properties from the current type
IEnumerable<PropertyInfo> newProperties = typeInfo.DeclaredProperties.Where(p => !propertyInfos.Contains(p));
propertyInfos.InsertRange(0, newProperties);
}
return propertyInfos.ToArray();
}
}
}