Sicherung

This commit is contained in:
Maier Stephan SI
2023-01-02 04:33:49 +01:00
parent bea46135fd
commit d01747f75a
284 changed files with 6106 additions and 65112 deletions

View File

@@ -0,0 +1,204 @@
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();
}
}
}

View File

@@ -0,0 +1,109 @@
using System;
using System.Collections;
using System.Collections.Generic;
namespace Config.Net.Core.Box
{
class CollectionResultBox : ResultBox
{
private readonly ResultBox _elementResultBox;
private string? _basePath;
private DynamicReader? _reader;
public CollectionResultBox(string name, ResultBox elementBox) : base(name, elementBox.ResultType, null)
{
_elementResultBox = elementBox;
}
public ResultBox ElementResultBox => _elementResultBox;
public bool IsInitialised { get; private set; }
public IEnumerable? CollectionInstance { get; private set; }
public void Initialise(string? basePath, int length, DynamicReader reader)
{
_basePath = basePath;
_reader = reader;
CollectionInstance = CreateGenericEnumerable(length);
IsInitialised = true;
}
private IEnumerable? CreateGenericEnumerable(int count)
{
Type t = typeof(DynamicEnumerable<>);
t = t.MakeGenericType(ResultType);
IEnumerable? instance = (IEnumerable?)Activator.CreateInstance(t, count, this);
return instance;
}
private object? ReadAt(int index)
{
return _reader?.Read(ElementResultBox, index);
}
private class DynamicEnumerable<T> : IEnumerable<T>
{
private readonly int _count;
private readonly CollectionResultBox _parent;
public DynamicEnumerable(int count, CollectionResultBox parent)
{
_count = count;
_parent = parent;
}
public IEnumerator<T> GetEnumerator()
{
return new DynamicEnumerator<T>(_count, _parent);
}
IEnumerator IEnumerable.GetEnumerator()
{
return new DynamicEnumerator<T>(_count, _parent);
}
}
private class DynamicEnumerator<T> : IEnumerator<T>
{
private int _index = -1;
private readonly int _count;
private readonly CollectionResultBox _parent;
private T? _current;
public DynamicEnumerator(int count, CollectionResultBox parent)
{
_count = count;
_parent = parent;
}
#pragma warning disable CS8603 // Possible null reference return.
public T Current => _current ?? default(T);
object IEnumerator.Current => _current;
#pragma warning restore CS8603 // Possible null reference return.
public void Dispose()
{
}
public bool MoveNext()
{
_index += 1;
_current = (T?)_parent.ReadAt(_index);
return _index < _count;
}
public void Reset()
{
_index = -1;
}
}
}
}

View File

@@ -0,0 +1,87 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text;
namespace Config.Net.Core.Box
{
class MethodResultBox : ResultBox
{
public MethodResultBox(MethodInfo methodInfo) : base(GetName(methodInfo), GetReturnType(methodInfo), null)
{
StoreByName = GetStoreName(methodInfo);
IsGettter = IsGet(methodInfo);
}
public bool IsGettter { get; private set; }
/// <summary>
/// Composes a uniqueue method name using method name itself and parameter type names, separated by underscore
/// </summary>
public static string GetName(MethodInfo mi)
{
ParameterInfo[] parameters = mi.GetParameters();
var sb = new StringBuilder();
sb.Append(mi.Name);
foreach (ParameterInfo pi in parameters)
{
sb.Append("-");
sb.Append(pi.ParameterType.ToString());
}
return sb.ToString();
}
public string GetValuePath(object[] arguments)
{
var sb = new StringBuilder();
sb.Append(StoreByName);
bool ignoreLast = !IsGettter;
for (int i = 0; i < arguments.Length - (ignoreLast ? 1 : 0); i++)
{
object value = arguments[i];
if (value == null) continue;
if (sb.Length > 0)
{
sb.Append(OptionPath.Separator);
}
sb.Append(value.ToString());
}
return sb.ToString();
}
private static string GetStoreName(MethodInfo mi)
{
string name = mi.Name;
if (name.StartsWith("get", StringComparison.OrdinalIgnoreCase) ||
name.StartsWith("set", StringComparison.OrdinalIgnoreCase))
{
name = name.Substring(3);
}
return name;
}
private static bool IsGet(MethodInfo mi)
{
return mi.ReturnType != typeof(void);
}
private static Type GetReturnType(MethodInfo mi)
{
ParameterInfo[] parameters = mi.GetParameters();
if (parameters == null || parameters.Length == 0)
{
throw new InvalidOperationException($"method {mi.Name} must have at least one parameter");
}
Type returnType = IsGet(mi) ? mi.ReturnType : parameters[parameters.Length - 1].ParameterType;
return returnType;
}
}
}

View File

@@ -0,0 +1,51 @@
using System;
using System.Reflection;
namespace Config.Net.Core.Box
{
class PropertyResultBox : ResultBox
{
public PropertyResultBox(string name, Type resultType) : base(name, resultType, null)
{
}
public static bool IsProperty(MethodInfo mi, out bool isGetter, out string? name)
{
if (mi.Name.StartsWith("get_"))
{
isGetter = true;
name = mi.Name.Substring(4);
return true;
}
if (mi.Name.StartsWith("set_"))
{
isGetter = false;
name = mi.Name.Substring(4);
return true;
}
isGetter = false;
name = null;
return false;
}
public static bool IsProperty(MethodInfo mi, out string? name)
{
if (mi.Name.StartsWith("get_") || mi.Name.StartsWith("set_"))
{
name = mi.Name.Substring(4);
return true;
}
name = null;
return false;
}
public static bool IsGetProperty(MethodInfo mi)
{
return mi.Name.StartsWith("get_");
}
}
}

View File

@@ -0,0 +1,34 @@
using System;
using System.Collections.Generic;
using Castle.DynamicProxy;
namespace Config.Net.Core.Box
{
class ProxyResultBox : ResultBox
{
private static readonly ProxyGenerator ProxyGenerator = new ProxyGenerator();
private readonly Dictionary<int, object> _indexToProxyInstance = new Dictionary<int, object>();
public ProxyResultBox(string name, Type interfaceType) : base(name, interfaceType, null)
{
}
public bool IsInitialisedAt(int index)
{
return _indexToProxyInstance.ContainsKey(index);
}
public object GetInstanceAt(int index)
{
return _indexToProxyInstance[index];
}
public void InitialiseAt(int index, IoHandler ioHandler, string prefix)
{
object instance = ProxyGenerator.CreateInterfaceProxyWithoutTarget(ResultBaseType,
new InterfaceInterceptor(ResultBaseType, ioHandler, prefix));
_indexToProxyInstance[index] = instance;
}
}
}

View File

@@ -0,0 +1,73 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text;
namespace Config.Net.Core.Box
{
abstract class ResultBox
{
private string? _storeByName;
protected ResultBox(string name, Type resultType, object? defaultResult)
{
Name = name;
ResultType = resultType;
ResultBaseType = GetBaseType(resultType);
DefaultResult = defaultResult;
}
public string Name { get; }
public string StoreByName
{
get => _storeByName ?? Name;
set => _storeByName = value;
}
public Type ResultType { get; }
public Type ResultBaseType { get; }
public object? DefaultResult { get; set; }
#region [ Utility Methods ]
private static Type GetBaseType(Type t)
{
TypeInfo ti = t.GetTypeInfo();
if (ti.IsClass)
{
return t;
}
else
{
if (ti.IsGenericType && ti.GetGenericTypeDefinition() == typeof(Nullable<>))
{
return ti.GenericTypeArguments[0];
}
else
{
return t;
}
}
}
internal static bool TryGetCollection(Type t, out Type elementType)
{
TypeInfo ti = t.GetTypeInfo();
if(ti.IsGenericType && ti.GetGenericTypeDefinition() == typeof(IEnumerable<>))
{
elementType = ti.GenericTypeArguments[0];
return true;
}
elementType = t;
return false;
}
#endregion
}
}

View File

@@ -0,0 +1,74 @@
using System;
using System.Collections.Generic;
using System.Text;
using Config.Net.Core.Box;
namespace Config.Net.Core
{
class DynamicReader
{
private readonly string? _basePath;
private readonly IoHandler _ioHandler;
public DynamicReader(string? basePath, IoHandler ioHandler)
{
_basePath = basePath;
_ioHandler = ioHandler;
}
public object? Read(ResultBox rbox, int index = -1, params object[] arguments)
{
if (rbox is PropertyResultBox pbox) return ReadProperty(pbox, index);
if (rbox is ProxyResultBox xbox) return ReadProxy(xbox, index);
if (rbox is CollectionResultBox cbox) return ReadCollection(cbox, index);
if (rbox is MethodResultBox mbox) return ReadMethod(mbox, arguments);
throw new NotImplementedException($"don't know how to read {rbox.GetType()}");
}
private object? ReadProperty(PropertyResultBox pbox, int index)
{
string path = OptionPath.Combine(index, _basePath, pbox.StoreByName);
return _ioHandler.Read(pbox.ResultBaseType, path, pbox.DefaultResult);
}
private object ReadProxy(ProxyResultBox xbox, int index)
{
if (!xbox.IsInitialisedAt(index))
{
string prefix = OptionPath.Combine(index, _basePath, xbox.StoreByName);
xbox.InitialiseAt(index, _ioHandler, prefix);
}
return xbox.GetInstanceAt(index);
}
private object? ReadCollection(CollectionResultBox cbox, int index)
{
string lengthPath = OptionPath.Combine(index, _basePath, cbox.StoreByName);
lengthPath = OptionPath.AddLength(lengthPath);
if (!cbox.IsInitialised)
{
int length = (int?)_ioHandler.Read(typeof(int), lengthPath, 0) ?? 0;
cbox.Initialise(_basePath, length, this);
}
return cbox.CollectionInstance;
}
private object? ReadMethod(MethodResultBox mbox, object[] arguments)
{
string path = mbox.GetValuePath(arguments);
path = OptionPath.Combine(_basePath, path);
return _ioHandler.Read(mbox.ResultBaseType, path, mbox.DefaultResult);
}
}
}

View File

@@ -0,0 +1,48 @@
using System;
using Config.Net.Core.Box;
namespace Config.Net.Core
{
class DynamicWriter
{
private readonly string? _basePath;
private readonly IoHandler _ioHandler;
public DynamicWriter(string? basePath, IoHandler ioHandler)
{
_basePath = basePath;
_ioHandler = ioHandler;
}
public void Write(ResultBox rbox, object[] arguments)
{
if (rbox is PropertyResultBox pbox) WriteProperty(pbox, arguments);
else if (rbox is MethodResultBox mbox) WriteMethod(mbox, arguments);
else if (rbox is ProxyResultBox xbox) WriteProxy(xbox, arguments);
else throw new NotImplementedException($"don't know how to write {rbox.GetType()}");
}
private void WriteProperty(PropertyResultBox pbox, object[] arguments)
{
string path = OptionPath.Combine(_basePath, pbox.StoreByName);
_ioHandler.Write(pbox.ResultBaseType, path, arguments[0]);
}
private void WriteMethod(MethodResultBox mbox, object[] arguments)
{
object value = arguments[arguments.Length - 1];
string path = mbox.GetValuePath(arguments);
_ioHandler.Write(mbox.ResultBaseType, path, value);
}
private void WriteProxy(ProxyResultBox xbox, object[] arguments)
{
throw new NotSupportedException("cannot assign values to interface properties");
}
}
}

View File

@@ -0,0 +1,16 @@
using System.Collections.Generic;
namespace Config.Net.Core
{
static class Extensions
{
public static TValue? GetValueOrDefaultInternal<TKey, TValue>(this IDictionary<TKey, TValue> dictionary, TKey key)
where TKey: notnull
where TValue: class
{
if (!dictionary.TryGetValue(key, out TValue? value)) return default(TValue);
return value;
}
}
}

View File

@@ -0,0 +1,58 @@
using System;
using System.Collections.Generic;
using System.Text;
using Config.Net.Core;
using Config.Net.TypeParsers;
namespace Config.Net.Core
{
/// <summary>
/// Helper class to implement flat arrays
/// </summary>
public static class FlatArrays
{
public static bool IsArrayLength(string? key, Func<string, string?> getValue, out int length)
{
if (!OptionPath.TryStripLength(key, out key))
{
length = 0;
return false;
}
string? value = key == null ? null : getValue(key);
if (value == null)
{
length = 0;
return false;
}
if (!StringArrayParser.TryParse(value, out string[]? ar))
{
length = 0;
return false;
}
length = ar?.Length ?? 0;
return true;
}
public static bool IsArrayElement(string? key, Func<string, string?> getValue, out string? value)
{
if(!OptionPath.TryStripIndex(key, out key, out int index))
{
value = null;
return false;
}
string? arrayString = key == null ? null : getValue(key);
if (!StringArrayParser.TryParse(arrayString, out string[]? array) || index >= array?.Length)
{
value = null;
return false;
}
value = array?[index] ?? null;
return true;
}
}
}

View File

@@ -0,0 +1,103 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Reflection;
using System.Text;
using Castle.DynamicProxy;
using Config.Net.Core.Box;
namespace Config.Net.Core
{
class InterfaceInterceptor : IInterceptor
{
private readonly Dictionary<string, ResultBox> _boxes;
private IoHandler _ioHandler;
private readonly string? _prefix;
private readonly DynamicReader _reader;
private readonly DynamicWriter _writer;
private readonly bool _isInpc;
private PropertyChangedEventHandler? _inpcHandler;
public InterfaceInterceptor(Type interfaceType, IoHandler ioHandler, string? prefix = null)
{
_boxes = BoxFactory.Discover(interfaceType, ioHandler.ValueHandler, prefix);
_ioHandler = ioHandler;
_prefix = prefix;
_reader = new DynamicReader(prefix, ioHandler);
_writer = new DynamicWriter(prefix, ioHandler);
_isInpc = interfaceType.GetInterface(nameof(INotifyPropertyChanged)) != null;
}
private ResultBox FindBox(IInvocation invocation)
{
if (PropertyResultBox.IsProperty(invocation.Method, out string? propertyName) && propertyName != null)
{
return _boxes[propertyName];
}
else //method
{
string name = MethodResultBox.GetName(invocation.Method);
return _boxes[name];
}
}
public void Intercept(IInvocation invocation)
{
if (TryInterceptInpc(invocation)) return;
ResultBox rbox = FindBox(invocation);
bool isRead =
(rbox is PropertyResultBox && PropertyResultBox.IsGetProperty(invocation.Method)) ||
(rbox is ProxyResultBox && PropertyResultBox.IsGetProperty(invocation.Method)) ||
(rbox is MethodResultBox mbox && mbox.IsGettter) ||
(rbox is CollectionResultBox);
if(isRead)
{
invocation.ReturnValue = _reader.Read(rbox, -1, invocation.Arguments);
return;
}
else
{
_writer.Write(rbox, invocation.Arguments);
TryNotifyInpc(invocation, rbox);
}
}
private bool TryInterceptInpc(IInvocation invocation)
{
if (!_isInpc) return false;
if (invocation.Method.Name == "add_PropertyChanged")
{
invocation.ReturnValue =
_inpcHandler =
(PropertyChangedEventHandler)Delegate.Combine(_inpcHandler, (Delegate)invocation.Arguments[0]);
return true;
}
else if(invocation.Method.Name == "remove_PropertyChanged")
{
invocation.ReturnValue =
_inpcHandler =
(PropertyChangedEventHandler?)Delegate.Remove(_inpcHandler, (Delegate)invocation.Arguments[0]);
return true;
}
return false;
}
private void TryNotifyInpc(IInvocation invocation, ResultBox rbox)
{
if (_inpcHandler == null || rbox is MethodResultBox) return;
_inpcHandler.Invoke(invocation.InvocationTarget, new PropertyChangedEventArgs(rbox.Name));
if(rbox.Name != rbox.StoreByName)
{
//notify on StoreByName as well
_inpcHandler.Invoke(invocation.InvocationTarget, new PropertyChangedEventArgs(rbox.StoreByName));
}
}
}
}

View File

@@ -0,0 +1,70 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
namespace Config.Net.Core
{
class IoHandler
{
private readonly IEnumerable<IConfigStore> _stores;
private readonly ValueHandler _valueHandler;
private readonly TimeSpan _cacheInterval;
private readonly ConcurrentDictionary<string, LazyVar<object>> _keyToValue = new ConcurrentDictionary<string, LazyVar<object>>();
public IoHandler(IEnumerable<IConfigStore> stores, ValueHandler valueHandler, TimeSpan cacheInterval)
{
_stores = stores ?? throw new ArgumentNullException(nameof(stores));
_valueHandler = valueHandler ?? throw new ArgumentNullException(nameof(valueHandler));
_cacheInterval = cacheInterval;
}
public ValueHandler ValueHandler => _valueHandler;
public object? Read(Type baseType, string path, object? defaultValue)
{
if(!_keyToValue.TryGetValue(path, out _))
{
var v = new LazyVar<object>(_cacheInterval, () => ReadNonCached(baseType, path, defaultValue));
_keyToValue[path] = v;
return v.GetValue();
}
return _keyToValue[path].GetValue();
}
public void Write(Type baseType, string path, object? value)
{
string? valueToWrite = _valueHandler.ConvertValue(baseType, value);
foreach (IConfigStore store in _stores.Where(s => s.CanWrite))
{
store.Write(path, valueToWrite);
}
}
private object? ReadNonCached(Type baseType, string path, object? defaultValue)
{
string? rawValue = ReadFirstValue(path);
return _valueHandler.ParseValue(baseType, rawValue, defaultValue);
}
private string? ReadFirstValue(string key)
{
foreach (IConfigStore store in _stores)
{
if (store.CanRead)
{
string? value = store.Read(key);
if (value != null) return value;
}
}
return null;
}
}
}

View File

@@ -0,0 +1,96 @@
using System;
using System.Threading.Tasks;
namespace Config.Net.Core
{
/// <summary>
/// Implements a lazy value i.e. that can expire in future
/// </summary>
/// <typeparam name="T"></typeparam>
class LazyVar<T> where T : class
{
private readonly Func<Task<T?>>? _renewFuncAsync;
private readonly Func<T?>? _renewFunc;
private DateTime _lastRenewed = DateTime.MinValue;
private readonly TimeSpan _timeToLive;
private T? _value;
/// <summary>
/// Creates an instance of a lazy variable with time-to-live value
/// </summary>
/// <param name="timeToLive">Time to live. Setting to <see cref="TimeSpan.Zero"/> disables caching completely</param>
/// <param name="renewFunc"></param>
public LazyVar(TimeSpan timeToLive, Func<Task<T?>> renewFunc)
{
_timeToLive = timeToLive;
_renewFuncAsync = renewFunc ?? throw new ArgumentNullException(nameof(renewFunc));
_renewFunc = null;
}
/// <summary>
/// Creates an instance of a lazy variable with time-to-live value
/// </summary>
/// <param name="timeToLive">Time to live. Setting to <see cref="TimeSpan.Zero"/> disables caching completely</param>
/// <param name="renewFunc"></param>
public LazyVar(TimeSpan timeToLive, Func<T?> renewFunc)
{
_timeToLive = timeToLive;
_renewFuncAsync = null;
_renewFunc = renewFunc ?? throw new ArgumentNullException(nameof(renewFunc));
}
/// <summary>
/// Gets the values, renewing it if necessary
/// </summary>
/// <returns>Value</returns>
public async Task<T?> GetValueAsync()
{
if (_renewFuncAsync == null)
{
throw new InvalidOperationException("cannot renew value, async delegate is not specified");
}
if (_timeToLive == TimeSpan.Zero)
{
return await _renewFuncAsync();
}
bool expired = (DateTime.UtcNow - _lastRenewed) > _timeToLive;
if (expired)
{
_value = await _renewFuncAsync();
_lastRenewed = DateTime.UtcNow;
}
return _value;
}
/// <summary>
/// Gets the values, renewing it if necessary
/// </summary>
/// <returns>Value</returns>
public T? GetValue()
{
if (_renewFunc == null)
{
throw new InvalidOperationException("cannot renew value, synchronous delegate is not specified");
}
if (_timeToLive == TimeSpan.Zero)
{
return _renewFunc();
}
bool expired = (DateTime.UtcNow - _lastRenewed) > _timeToLive;
if (expired)
{
_value = _renewFunc();
_lastRenewed = DateTime.UtcNow;
}
return _value;
}
}
}

View File

@@ -0,0 +1,88 @@
namespace Config.Net.Core
{
public static class OptionPath
{
public const string Separator = ".";
private const string IndexOpen = "[";
private const string IndexClose = "]";
public const string LengthFunction = ".$l";
public static string Combine(params string?[] parts)
{
return Combine(-1, parts);
}
public static string AddLength(string path)
{
return path + LengthFunction;
}
public static bool TryStripLength(string? path, out string? noLengthPath)
{
if (path == null)
{
noLengthPath = path;
return false;
}
if (!path.EndsWith(LengthFunction))
{
noLengthPath = path;
return false;
}
noLengthPath = path.Substring(0, path.Length - LengthFunction.Length);
return true;
}
/// <summary>
/// For indexed paths like "creds[1]" strips index part so it becomes:
/// - noIndexPath: "creds"
/// - index: 1
///
/// If path is not indexed returns false and noIndexPath is equal to path itself
/// </summary>
public static bool TryStripIndex(string? path, out string? noIndexPath, out int index)
{
if (path == null)
{
index = 0;
noIndexPath = path;
return false;
}
int openIdx = path.IndexOf(IndexOpen);
int closeIdx = path.IndexOf(IndexClose);
if (openIdx == -1 || closeIdx == -1 || openIdx > closeIdx || closeIdx != path.Length - 1)
{
noIndexPath = path;
index = 0;
return false;
}
noIndexPath = path.Substring(0, openIdx);
int.TryParse(path.Substring(openIdx + 1, closeIdx - openIdx - 1), out index);
return true;
}
public static string Combine(int index, params string?[] parts)
{
string s = string.Empty;
for (int i = 0; i < parts.Length; i++)
{
if (s.Length > 0) s += Separator;
if (!string.IsNullOrEmpty(parts[i])) s += parts[i];
}
if (index != -1)
{
s = $"{s}{IndexOpen}{index}{IndexClose}";
}
return s;
}
}
}

View File

@@ -0,0 +1,157 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using Config.Net.TypeParsers;
namespace Config.Net.Core
{
class ValueHandler
{
private readonly DefaultParser _defaultParser = new DefaultParser();
private readonly ConcurrentDictionary<Type, ITypeParser> _allParsers = new ConcurrentDictionary<Type, ITypeParser>();
private readonly HashSet<Type> _supportedTypes = new HashSet<Type>();
private static readonly ValueHandler _default = new ValueHandler();
public ValueHandler(IEnumerable<ITypeParser>? customParsers = null)
{
foreach (ITypeParser pc in GetBuiltInParsers())
{
AddParser(pc);
}
if(customParsers != null)
{
foreach(ITypeParser pc in customParsers)
{
AddParser(pc);
}
}
}
public void AddParser(ITypeParser parser)
{
if (parser == null)
{
throw new ArgumentNullException(nameof(parser));
}
foreach (Type t in parser.SupportedTypes)
{
_allParsers[t] = parser;
_supportedTypes.Add(t);
}
}
public bool IsSupported(Type t)
{
return _supportedTypes.Contains(t) || _defaultParser.IsSupported(t);
}
public object? ParseValue(Type baseType, string? rawValue, object? defaultValue)
{
object? result;
if (rawValue == null)
{
result = defaultValue;
}
else
{
if(!TryParse(baseType, rawValue, out result))
{
result = defaultValue;
}
}
return result;
}
public bool TryParse(Type propertyType, string? rawValue, out object? result)
{
if (_defaultParser.IsSupported(propertyType)) //type here must be a non-nullable one
{
if (!_defaultParser.TryParse(rawValue, propertyType, out result))
{
return false;
}
}
else
{
ITypeParser? typeParser = GetParser(propertyType);
if (typeParser == null)
{
result = null;
return false;
}
if (!typeParser.TryParse(rawValue, propertyType, out result))
{
return false;
}
}
return true;
}
public string? ConvertValue(Type baseType, object? value)
{
string? str;
if (value == null)
{
str = null;
}
else
{
if (_defaultParser.IsSupported(baseType))
{
str = _defaultParser.ToRawString(value);
}
else
{
ITypeParser? parser = GetParser(value.GetType());
str = parser?.ToRawString(value);
}
}
return str;
}
private ITypeParser? GetParser(Type t)
{
ITypeParser? result;
_allParsers.TryGetValue(t, out result);
return result;
}
/// <summary>
/// Scans assembly for types implementing <see cref="ITypeParser"/> and builds Type => instance dictionary.
/// Not sure if I should use reflection here, however the assembly is small and this shouldn't cause any
/// performance issues
/// </summary>
/// <returns></returns>
private static IEnumerable<ITypeParser> GetBuiltInParsers()
{
return new ITypeParser[]
{
new FloatParser(),
new DoubleParser(),
new DecimalParser(),
new SByteParser(),
new ShortParser(),
new IntParser(),
new LongParser(),
new ByteParser(),
new UShortParser(),
new UIntParser(),
new ULongParser(),
new StringArrayParser(),
new StringParser(),
new TimeSpanParser(),
new CoreParsers(),
new NetworkCredentialParser()
};
}
}
}