mirror of
https://github.com/Artemis-RGB/Artemis
synced 2026-11-11 19:36:28 +00:00
Meta - Enabled nullable ref types in Core project
Meta - Simplified R# settings to be more in line with defaults, sorry Darth 😝
Dynamic data models - Implemented add/remove events
Data model paths - Implemented storage
This commit is contained in:
1 parent
642a6b95b9
commit
a2759d007e
13 files changed
+302
-492
No files matched your search
@@ -1,21 +1,21 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
using Artemis.Core.DataModelExpansions;
|
||||
using Artemis.Storage.Entities.Profile;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a path that points to a property in data model
|
||||
/// </summary>
|
||||
public class DataModelPath
|
||||
public class DataModelPath : IStorageModel, IDisposable
|
||||
{
|
||||
private Expression<Func<object, object>> _accessorLambda;
|
||||
private readonly LinkedList<DataModelPathSegment> _segments;
|
||||
private Expression<Func<object, object>>? _accessorLambda;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="DataModelPath" /> class pointing directly to the target
|
||||
@@ -25,9 +25,15 @@ namespace Artemis.Core
|
||||
{
|
||||
Target = target ?? throw new ArgumentNullException(nameof(target));
|
||||
Path = "";
|
||||
Entity = new DataModelPathEntity();
|
||||
if (Target is DataModel dataModel)
|
||||
DataModelGuid = dataModel.PluginInfo.Guid;
|
||||
|
||||
_segments = new LinkedList<DataModelPathSegment>();
|
||||
Initialize(Path);
|
||||
|
||||
Save();
|
||||
Initialize();
|
||||
SubscribeToDataModelStore();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -39,25 +45,51 @@ namespace Artemis.Core
|
||||
{
|
||||
Target = target ?? throw new ArgumentNullException(nameof(target));
|
||||
Path = path ?? throw new ArgumentNullException(nameof(path));
|
||||
Entity = new DataModelPathEntity();
|
||||
if (Target is DataModel dataModel)
|
||||
DataModelGuid = dataModel.PluginInfo.Guid;
|
||||
|
||||
_segments = new LinkedList<DataModelPathSegment>();
|
||||
Initialize(Path);
|
||||
|
||||
Save();
|
||||
Initialize();
|
||||
SubscribeToDataModelStore();
|
||||
}
|
||||
|
||||
internal DataModelPath(object target, DataModelPathEntity entity)
|
||||
{
|
||||
Target = target!;
|
||||
Path = entity.Path;
|
||||
Entity = entity;
|
||||
|
||||
_segments = new LinkedList<DataModelPathSegment>();
|
||||
|
||||
Load();
|
||||
Initialize();
|
||||
SubscribeToDataModelStore();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the data model at which this path starts
|
||||
/// </summary>
|
||||
public object Target { get; }
|
||||
public object? Target { get; private set; }
|
||||
|
||||
internal DataModelPathEntity Entity { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the data model GUID of the <see cref="Target" /> if it is a <see cref="DataModel" />
|
||||
/// </summary>
|
||||
public Guid? DataModelGuid { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the point-separated path associated with this <see cref="DataModelPath" />
|
||||
/// </summary>
|
||||
public string Path { get; }
|
||||
public string Path { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a boolean indicating whether all <see cref="Segments" /> are valid
|
||||
/// </summary>
|
||||
public bool IsValid => Segments.All(p => p.Type != DataModelPathSegmentType.Invalid);
|
||||
public bool IsValid => Segments.Any() && Segments.All(p => p.Type != DataModelPathSegmentType.Invalid);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a read-only list of all segments of this path
|
||||
@@ -67,17 +99,16 @@ namespace Artemis.Core
|
||||
/// <summary>
|
||||
/// Gets a boolean indicating whether this data model path points to a list
|
||||
/// </summary>
|
||||
public bool PointsToList => Segments.LastOrDefault()?.GetPropertyType() != null &&
|
||||
typeof(IList).IsAssignableFrom(Segments.LastOrDefault()?.GetPropertyType());
|
||||
public bool PointsToList => Segments.LastOrDefault()?.GetPropertyType() != null && typeof(IList).IsAssignableFrom(Segments.LastOrDefault()?.GetPropertyType());
|
||||
|
||||
internal Func<object, object> Accessor { get; private set; }
|
||||
internal Func<object, object>? Accessor { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current value of the path
|
||||
/// </summary>
|
||||
public object GetValue()
|
||||
public object? GetValue()
|
||||
{
|
||||
if (_accessorLambda == null)
|
||||
if (_accessorLambda == null || Target == null)
|
||||
return null;
|
||||
|
||||
// If the accessor has not yet been compiled do it now that it's first required
|
||||
@@ -90,7 +121,7 @@ namespace Artemis.Core
|
||||
/// Gets the property info of the property this path points to
|
||||
/// </summary>
|
||||
/// <returns>If static, the property info. If dynamic, <c>null</c></returns>
|
||||
public PropertyInfo GetPropertyInfo()
|
||||
public PropertyInfo? GetPropertyInfo()
|
||||
{
|
||||
return Segments.LastOrDefault()?.GetPropertyInfo();
|
||||
}
|
||||
@@ -99,7 +130,7 @@ namespace Artemis.Core
|
||||
/// Gets the type of the property this path points to
|
||||
/// </summary>
|
||||
/// <returns>If possible, the property type</returns>
|
||||
public Type GetPropertyType()
|
||||
public Type? GetPropertyType()
|
||||
{
|
||||
return Segments.LastOrDefault()?.GetPropertyType();
|
||||
}
|
||||
@@ -108,7 +139,7 @@ namespace Artemis.Core
|
||||
/// Gets the property description of the property this path points to
|
||||
/// </summary>
|
||||
/// <returns>If found, the data model property description</returns>
|
||||
public DataModelPropertyAttribute GetPropertyDescription()
|
||||
public DataModelPropertyAttribute? GetPropertyDescription()
|
||||
{
|
||||
return Segments.LastOrDefault()?.GetPropertyDescription();
|
||||
}
|
||||
@@ -119,15 +150,30 @@ namespace Artemis.Core
|
||||
return string.IsNullOrWhiteSpace(Path) ? "this" : Path;
|
||||
}
|
||||
|
||||
private void Initialize(string path)
|
||||
internal void Invalidate()
|
||||
{
|
||||
foreach (DataModelPathSegment dataModelPathSegment in _segments)
|
||||
dataModelPathSegment.Dispose();
|
||||
_segments.Clear();
|
||||
|
||||
_accessorLambda = null;
|
||||
Accessor = null;
|
||||
}
|
||||
|
||||
internal void Initialize()
|
||||
{
|
||||
Invalidate();
|
||||
|
||||
if (Target == null)
|
||||
return;
|
||||
|
||||
DataModelPathSegment startSegment = new DataModelPathSegment(this, "target", "target");
|
||||
startSegment.Node = _segments.AddFirst(startSegment);
|
||||
|
||||
// On an empty path don't bother processing segments
|
||||
if (!string.IsNullOrWhiteSpace(path))
|
||||
if (!string.IsNullOrWhiteSpace(Path))
|
||||
{
|
||||
string[] segments = path.Split(".");
|
||||
string[] segments = Path.Split(".");
|
||||
for (int index = 0; index < segments.Length; index++)
|
||||
{
|
||||
string identifier = segments[index];
|
||||
@@ -137,8 +183,8 @@ namespace Artemis.Core
|
||||
}
|
||||
|
||||
ParameterExpression parameter = Expression.Parameter(typeof(object), "t");
|
||||
Expression expression = Expression.Convert(parameter, Target.GetType());
|
||||
Expression nullCondition = null;
|
||||
Expression? expression = Expression.Convert(parameter, Target.GetType());
|
||||
Expression? nullCondition = null;
|
||||
|
||||
foreach (DataModelPathSegment segment in _segments)
|
||||
{
|
||||
@@ -149,6 +195,9 @@ namespace Artemis.Core
|
||||
return;
|
||||
}
|
||||
|
||||
if (nullCondition == null)
|
||||
return;
|
||||
|
||||
_accessorLambda = Expression.Lambda<Func<object, object>>(
|
||||
// Wrap with a null check
|
||||
Expression.Condition(
|
||||
@@ -158,7 +207,68 @@ namespace Artemis.Core
|
||||
),
|
||||
parameter
|
||||
);
|
||||
Accessor = null;
|
||||
}
|
||||
|
||||
private void SubscribeToDataModelStore()
|
||||
{
|
||||
DataModelStore.DataModelAdded += DataModelStoreOnDataModelAdded;
|
||||
DataModelStore.DataModelRemoved += DataModelStoreOnDataModelRemoved;
|
||||
}
|
||||
|
||||
#region Storage
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Load()
|
||||
{
|
||||
Path = Entity.Path;
|
||||
DataModelGuid = Entity.DataModelGuid;
|
||||
|
||||
if (Target == null && Entity.DataModelGuid != null)
|
||||
Target = DataModelStore.Get(Entity.DataModelGuid.Value);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Save()
|
||||
{
|
||||
Entity.Path = Path;
|
||||
Entity.DataModelGuid = DataModelGuid;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IDisposable
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
DataModelStore.DataModelAdded -= DataModelStoreOnDataModelAdded;
|
||||
DataModelStore.DataModelRemoved -= DataModelStoreOnDataModelRemoved;
|
||||
|
||||
Invalidate();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Event handlers
|
||||
|
||||
private void DataModelStoreOnDataModelAdded(object? sender, DataModelStoreEvent e)
|
||||
{
|
||||
if (e.Registration.DataModel.PluginInfo.Guid != DataModelGuid)
|
||||
return;
|
||||
|
||||
Target = e.Registration.DataModel;
|
||||
Initialize();
|
||||
}
|
||||
|
||||
private void DataModelStoreOnDataModelRemoved(object? sender, DataModelStoreEvent e)
|
||||
{
|
||||
if (e.Registration.DataModel.PluginInfo.Guid != DataModelGuid)
|
||||
return;
|
||||
|
||||
Target = null;
|
||||
Invalidate();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -11,9 +11,10 @@ namespace Artemis.Core
|
||||
/// <summary>
|
||||
/// Represents a segment of a data model path
|
||||
/// </summary>
|
||||
public class DataModelPathSegment
|
||||
public class DataModelPathSegment : IDisposable
|
||||
{
|
||||
private Expression<Func<object, object>> _accessorLambda;
|
||||
private Expression<Func<object, object>>? _accessorLambda;
|
||||
private DataModel? _dynamicDataModel;
|
||||
|
||||
internal DataModelPathSegment(DataModelPath dataModelPath, string identifier, string path)
|
||||
{
|
||||
@@ -52,28 +53,28 @@ namespace Artemis.Core
|
||||
/// Gets the type of dynamic data model this path points to
|
||||
/// <para>Not used if the <see cref="Type" /> is <see cref="DataModelPathSegmentType.Static" /></para>
|
||||
/// </summary>
|
||||
public Type DynamicDataModelType { get; private set; }
|
||||
public Type? DynamicDataModelType { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the previous segment in the path
|
||||
/// </summary>
|
||||
public DataModelPathSegment Previous => Node.Previous?.Value;
|
||||
public DataModelPathSegment? Previous => Node?.Previous?.Value;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the next segment in the path
|
||||
/// </summary>
|
||||
public DataModelPathSegment Next => Node.Next?.Value;
|
||||
public DataModelPathSegment? Next => Node?.Next?.Value;
|
||||
|
||||
internal Func<object, object> Accessor { get; set; }
|
||||
internal LinkedListNode<DataModelPathSegment> Node { get; set; }
|
||||
internal Func<object, object>? Accessor { get; set; }
|
||||
internal LinkedListNode<DataModelPathSegment>? Node { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns the current value of the path up to this segment
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public object GetValue()
|
||||
public object? GetValue()
|
||||
{
|
||||
if (Type == DataModelPathSegmentType.Invalid || _accessorLambda == null)
|
||||
if (Type == DataModelPathSegmentType.Invalid || DataModelPath.Target == null || _accessorLambda == null)
|
||||
return null;
|
||||
|
||||
// If the accessor has not yet been compiled do it now that it's first required
|
||||
@@ -92,7 +93,7 @@ namespace Artemis.Core
|
||||
/// Gets the property info of the property this segment points to
|
||||
/// </summary>
|
||||
/// <returns>If static, the property info. If dynamic, <c>null</c></returns>
|
||||
public PropertyInfo GetPropertyInfo()
|
||||
public PropertyInfo? GetPropertyInfo()
|
||||
{
|
||||
// Dynamic types have no property and therefore no property info
|
||||
if (Type == DataModelPathSegmentType.Dynamic)
|
||||
@@ -102,35 +103,36 @@ namespace Artemis.Core
|
||||
return null;
|
||||
|
||||
// If this is not the first segment in a path, the property is located on the previous segment
|
||||
return Previous.GetPropertyType()?.GetProperty(Identifier);
|
||||
return Previous?.GetPropertyType()?.GetProperty(Identifier);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the property description of the property this segment points to
|
||||
/// </summary>
|
||||
/// <returns>If found, the data model property description</returns>
|
||||
public DataModelPropertyAttribute GetPropertyDescription()
|
||||
public DataModelPropertyAttribute? GetPropertyDescription()
|
||||
{
|
||||
// Dynamic types have a data model description
|
||||
if (Type == DataModelPathSegmentType.Dynamic)
|
||||
return ((DataModel) GetValue())?.DataModelDescription;
|
||||
return (GetValue() as DataModel)?.DataModelDescription;
|
||||
if (IsStartSegment && DataModelPath.Target is DataModel targetDataModel)
|
||||
return targetDataModel.DataModelDescription;
|
||||
if (IsStartSegment)
|
||||
return null;
|
||||
|
||||
PropertyInfo propertyInfo = GetPropertyInfo();
|
||||
PropertyInfo? propertyInfo = GetPropertyInfo();
|
||||
if (propertyInfo == null)
|
||||
return null;
|
||||
|
||||
// Static types may have one as an attribute
|
||||
DataModelPropertyAttribute attribute = (DataModelPropertyAttribute) Attribute.GetCustomAttribute(propertyInfo, typeof(DataModelPropertyAttribute));
|
||||
DataModelPropertyAttribute? attribute = (DataModelPropertyAttribute) Attribute.GetCustomAttribute(propertyInfo, typeof(DataModelPropertyAttribute));
|
||||
if (attribute != null)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(attribute.Name))
|
||||
attribute.Name = propertyInfo.Name.Humanize();
|
||||
return attribute;
|
||||
}
|
||||
|
||||
return new DataModelPropertyAttribute {Name = propertyInfo.Name.Humanize(), ResetsDepth = false};
|
||||
}
|
||||
|
||||
@@ -138,19 +140,19 @@ namespace Artemis.Core
|
||||
/// Gets the type of the property this path points to
|
||||
/// </summary>
|
||||
/// <returns>If possible, the property type</returns>
|
||||
public Type GetPropertyType()
|
||||
public Type? GetPropertyType()
|
||||
{
|
||||
// The start segment type is always the target type
|
||||
if (IsStartSegment)
|
||||
return DataModelPath.Target.GetType();
|
||||
return DataModelPath.Target?.GetType();
|
||||
|
||||
// Prefer basing the type on the property info
|
||||
PropertyInfo propertyInfo = GetPropertyInfo();
|
||||
Type type = propertyInfo?.PropertyType;
|
||||
PropertyInfo? propertyInfo = GetPropertyInfo();
|
||||
Type? type = propertyInfo?.PropertyType;
|
||||
// Property info is not available on dynamic paths though, so fall back on the current value
|
||||
if (propertyInfo == null)
|
||||
{
|
||||
object currentValue = GetValue();
|
||||
object? currentValue = GetValue();
|
||||
if (currentValue != null)
|
||||
type = currentValue.GetType();
|
||||
}
|
||||
@@ -158,7 +160,7 @@ namespace Artemis.Core
|
||||
return type;
|
||||
}
|
||||
|
||||
internal Expression Initialize(ParameterExpression parameter, Expression expression, Expression nullCondition)
|
||||
internal Expression? Initialize(ParameterExpression parameter, Expression expression, Expression nullCondition)
|
||||
{
|
||||
if (IsStartSegment)
|
||||
{
|
||||
@@ -166,7 +168,7 @@ namespace Artemis.Core
|
||||
return CreateExpression(parameter, expression, nullCondition);
|
||||
}
|
||||
|
||||
Type previousType = Previous.GetPropertyType();
|
||||
Type? previousType = Previous?.GetPropertyType();
|
||||
if (previousType == null)
|
||||
{
|
||||
Type = DataModelPathSegmentType.Invalid;
|
||||
@@ -179,21 +181,24 @@ namespace Artemis.Core
|
||||
// If no static type could be found, check if this is a data model and if so, look for a dynamic type
|
||||
if (Type == DataModelPathSegmentType.Invalid && typeof(DataModel).IsAssignableFrom(previousType))
|
||||
{
|
||||
DataModel dataModel = (DataModel) Previous.GetValue();
|
||||
_dynamicDataModel = Previous?.GetValue() as DataModel;
|
||||
// Cannot determine a dynamic type on a null data model, leave the segment invalid
|
||||
if (dataModel == null)
|
||||
if (_dynamicDataModel == null)
|
||||
return CreateExpression(parameter, expression, nullCondition);
|
||||
|
||||
// If a dynamic data model is found the use that
|
||||
bool hasDynamicDataModel = dataModel.DynamicDataModels.TryGetValue(Identifier, out DataModel dynamicDataModel);
|
||||
bool hasDynamicDataModel = _dynamicDataModel.DynamicDataModels.TryGetValue(Identifier, out DataModel dynamicDataModel);
|
||||
if (hasDynamicDataModel)
|
||||
DetermineDynamicType(dynamicDataModel);
|
||||
|
||||
_dynamicDataModel.DynamicDataModelAdded += DynamicDataModelOnDynamicDataModelAdded;
|
||||
_dynamicDataModel.DynamicDataModelRemoved += DynamicDataModelOnDynamicDataModelRemoved;
|
||||
}
|
||||
|
||||
return CreateExpression(parameter, expression, nullCondition);
|
||||
}
|
||||
|
||||
private Expression CreateExpression(ParameterExpression parameter, Expression expression, Expression nullCondition)
|
||||
private Expression? CreateExpression(ParameterExpression parameter, Expression expression, Expression nullCondition)
|
||||
{
|
||||
if (Type == DataModelPathSegmentType.Invalid)
|
||||
{
|
||||
@@ -211,14 +216,12 @@ namespace Artemis.Core
|
||||
accessorExpression = Expression.PropertyOrField(expression, Identifier);
|
||||
// A dynamic segment calls the generic method DataModel.DynamicChild<T> and provides the identifier as an argument
|
||||
else
|
||||
{
|
||||
accessorExpression = Expression.Call(
|
||||
expression,
|
||||
nameof(DataModel.DynamicChild),
|
||||
new[] {DynamicDataModelType},
|
||||
Expression.Constant(Identifier)
|
||||
);
|
||||
}
|
||||
|
||||
_accessorLambda = Expression.Lambda<Func<object, object>>(
|
||||
// Wrap with a null check
|
||||
@@ -244,5 +247,40 @@ namespace Artemis.Core
|
||||
PropertyInfo? property = previousType.GetProperty(Identifier, BindingFlags.Public | BindingFlags.Instance);
|
||||
Type = property == null ? DataModelPathSegmentType.Invalid : DataModelPathSegmentType.Static;
|
||||
}
|
||||
|
||||
#region IDisposable
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
if (_dynamicDataModel != null)
|
||||
{
|
||||
_dynamicDataModel.DynamicDataModelAdded -= DynamicDataModelOnDynamicDataModelAdded;
|
||||
_dynamicDataModel.DynamicDataModelRemoved -= DynamicDataModelOnDynamicDataModelRemoved;
|
||||
}
|
||||
|
||||
Type = DataModelPathSegmentType.Invalid;
|
||||
|
||||
_accessorLambda = null;
|
||||
Accessor = null;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Event handlers
|
||||
|
||||
private void DynamicDataModelOnDynamicDataModelAdded(object? sender, DynamicDataModelEventArgs e)
|
||||
{
|
||||
if (e.Key == Identifier)
|
||||
DataModelPath.Initialize();
|
||||
}
|
||||
|
||||
private void DynamicDataModelOnDynamicDataModelRemoved(object? sender, DynamicDataModelEventArgs e)
|
||||
{
|
||||
if (e.DynamicDataModel == _dynamicDataModel)
|
||||
DataModelPath.Initialize();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user