From 26db794467392d4af530505650da0c7888f2a814 Mon Sep 17 00:00:00 2001 From: SpoinkyNL Date: Sat, 3 Oct 2020 00:19:14 +0200 Subject: [PATCH] Dynamic data models - Finished abstraction of paths --- .../Models/Profile/DataModel/DataModelPath.cs | 95 ++++++++++--------- .../Profile/DataModel/DataModelPathPart.cs | 85 ++++++++++------- .../Plugins/DataModelExpansions/DataModel.cs | 26 ++++- .../DataModels/PluginDataModel.cs | 2 +- .../PluginDataModelExpansion.cs | 6 -- 5 files changed, 126 insertions(+), 88 deletions(-) diff --git a/src/Artemis.Core/Models/Profile/DataModel/DataModelPath.cs b/src/Artemis.Core/Models/Profile/DataModel/DataModelPath.cs index a5a7bfa71..7b301001b 100644 --- a/src/Artemis.Core/Models/Profile/DataModel/DataModelPath.cs +++ b/src/Artemis.Core/Models/Profile/DataModel/DataModelPath.cs @@ -11,75 +11,80 @@ namespace Artemis.Core /// public class DataModelPath { - private readonly List _parts; + private readonly LinkedList _parts; - // TODO: Make internal - public DataModelPath(DataModel dataModel, string path) + internal DataModelPath(DataModel dataModel, string path) { - if (dataModel == null) - throw new ArgumentNullException(nameof(dataModel)); - if (path == null) - throw new ArgumentNullException(nameof(path)); - - _parts = new List(); - - DataModel = dataModel; + DataModel = dataModel ?? throw new ArgumentNullException(nameof(dataModel)); + Path = path ?? throw new ArgumentNullException(nameof(path)); DataModelGuid = dataModel.PluginInfo.Guid; + _parts = new LinkedList(); Initialize(path); } - private void Initialize(string path) - { - var parts = path.Split("."); - foreach (var identifier in parts) - _parts.Add(new DataModelPathPart(this, identifier)); - - var parameter = Expression.Parameter(typeof(DataModel), "dm"); - Expression expression = Expression.Convert(parameter, DataModel.GetType()); - Expression nullCondition = null; - DataModelPathPart previous = null; - - foreach (var part in _parts) - { - var notNull = Expression.NotEqual(expression, Expression.Constant(null)); - nullCondition = nullCondition != null ? Expression.AndAlso(nullCondition, notNull) : notNull; - expression = part.Initialize(previous, parameter, expression, nullCondition); - if (expression == null) - return; - - previous = part; - } - } - /// /// Gets the data model at which this path starts /// public DataModel DataModel { get; } + /// + /// Gets a string representation of the + /// + public string Path { get; } + + /// + /// Gets a boolean indicating whether all are valid + /// + public bool IsValid => Parts.All(p => p.Type != DataModelPathPartType.Invalid); + /// /// Gets a read-only list of all parts of this path /// public IReadOnlyCollection Parts => _parts.ToList().AsReadOnly(); + internal Func Accessor { get; private set; } + internal Guid DataModelGuid { get; set; } - public string GetPathToPart(DataModelPathPart part) + /// + public override string ToString() { - var endIndex = _parts.IndexOf(part); - return endIndex < 0 ? null : string.Join('.', _parts.Take(endIndex + 1)); + return Path; } - internal DataModelPathPart GetPartBefore(DataModelPathPart dataModelPathPart) + private void Initialize(string path) { - var index = _parts.IndexOf(dataModelPathPart); - return index > 0 ? _parts[index - 1] : null; - } + var parts = path.Split("."); + for (var index = 0; index < parts.Length; index++) + { + var identifier = parts[index]; + var node = _parts.AddLast(new DataModelPathPart(this, identifier, string.Join('.', parts.Take(index + 1)))); + node.Value.Node = node; + } - internal DataModelPathPart GetPartAfter(DataModelPathPart dataModelPathPart) - { - var index = _parts.IndexOf(dataModelPathPart); - return index < _parts.Count - 1 ? _parts[index + 1] : null; + var parameter = Expression.Parameter(typeof(DataModel), "dm"); + Expression expression = Expression.Convert(parameter, DataModel.GetType()); + Expression nullCondition = null; + + foreach (var part in _parts) + { + var notNull = Expression.NotEqual(expression, Expression.Constant(null)); + nullCondition = nullCondition != null ? Expression.AndAlso(nullCondition, notNull) : notNull; + expression = part.Initialize(parameter, expression, nullCondition); + if (expression == null) + return; + } + + Accessor = Expression.Lambda>( + // Wrap with a null check + Expression.Condition( + nullCondition, + Expression.Convert(expression, typeof(object)), + Expression.Convert(Expression.Default(expression.Type), typeof(object)) + ), + parameter + ).Compile(); } } } \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/DataModel/DataModelPathPart.cs b/src/Artemis.Core/Models/Profile/DataModel/DataModelPathPart.cs index fc72190dc..131c0b3cc 100644 --- a/src/Artemis.Core/Models/Profile/DataModel/DataModelPathPart.cs +++ b/src/Artemis.Core/Models/Profile/DataModel/DataModelPathPart.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq.Expressions; using System.Reflection; using Artemis.Core.DataModelExpansions; @@ -10,10 +11,11 @@ namespace Artemis.Core /// public class DataModelPathPart { - internal DataModelPathPart(DataModelPath dataModelPath, string identifier) + internal DataModelPathPart(DataModelPath dataModelPath, string identifier, string path) { DataModelPath = dataModelPath; Identifier = identifier; + Path = path; } /// @@ -26,6 +28,11 @@ namespace Artemis.Core /// public string Identifier { get; } + /// + /// Gets the path that leads to this part + /// + public string Path { get; } + /// /// Gets the type of data model this part of the path points to /// @@ -40,14 +47,15 @@ namespace Artemis.Core /// /// Gets the previous part in the path /// - public DataModelPathPart Previous => DataModelPath.GetPartBefore(this); + public DataModelPathPart Previous => Node.Previous?.Value; /// /// Gets the next part in the path /// - public DataModelPathPart Next => DataModelPath.GetPartAfter(this); + public DataModelPathPart Next => Node.Next?.Value; internal Func Accessor { get; set; } + internal LinkedListNode Node { get; set; } /// /// Returns the current value of the path up to this part @@ -58,6 +66,39 @@ namespace Artemis.Core return Type == DataModelPathPartType.Invalid ? null : Accessor(DataModelPath.DataModel); } + /// + public override string ToString() + { + return $"[{Type}] {Path}"; + } + + internal Expression Initialize(ParameterExpression parameter, Expression expression, Expression nullCondition) + { + var previousValue = Previous != null ? Previous.GetValue() : DataModelPath.DataModel; + if (previousValue == null) + { + Type = DataModelPathPartType.Invalid; + return null; + } + + // Determine this part's type by looking for a dynamic data model with the identifier + if (previousValue is DataModel dataModel) + { + var hasDynamicDataModel = dataModel.DynamicDataModels.TryGetValue(Identifier, out var dynamicDataModel); + // If a dynamic data model is found the use that + if (hasDynamicDataModel) + DetermineDynamicType(dynamicDataModel); + // Otherwise look for a static type + else + DetermineStaticType(previousValue); + } + // Only data models can have dynamic types so if it is something else, its always going to be static + else + DetermineStaticType(previousValue); + + return CreateExpression(parameter, expression, nullCondition); + } + private Expression CreateExpression(ParameterExpression parameter, Expression expression, Expression nullCondition) { if (Type == DataModelPathPartType.Invalid) @@ -81,45 +122,19 @@ namespace Artemis.Core ); } - var test = Expression.Lambda>( - Expression.Condition(nullCondition, expression, Expression.Default(expression.Type)), // Wrap with a null check - parameter - ); Accessor = Expression.Lambda>( - Expression.Condition(nullCondition, expression, Expression.Default(expression.Type)), // Wrap with a null check + // Wrap with a null check + Expression.Condition( + nullCondition, + Expression.Convert(accessorExpression, typeof(object)), + Expression.Convert(Expression.Default(accessorExpression.Type), typeof(object)) + ), parameter ).Compile(); return accessorExpression; } - internal Expression Initialize(DataModelPathPart previous, ParameterExpression parameter, Expression expression, Expression nullCondition) - { - var previousValue = previous != null ? previous.GetValue() : DataModelPath.DataModel; - if (previousValue == null) - { - Type = DataModelPathPartType.Invalid; - return null; - } - - // Determine this part's type by looking for a dynamic data model with the identifier - if (previousValue is DataModel dataModel) - { - var hasDynamicDataModel = dataModel.DynamicDataModels.TryGetValue(Identifier, out var dynamicDataModel); - // If a dynamic data model is found the use that - if (hasDynamicDataModel) - DetermineDynamicType(dynamicDataModel); - // Otherwise look for a static type - else - DetermineStaticType(previousValue); - } - // Only data models can have dynamic types so if it is something else, its always going to be static - else - DetermineStaticType(previousValue); - - return CreateExpression(parameter, expression, nullCondition); - } - private void DetermineDynamicType(DataModel dynamicDataModel) { Type = DataModelPathPartType.Dynamic; diff --git a/src/Artemis.Core/Plugins/DataModelExpansions/DataModel.cs b/src/Artemis.Core/Plugins/DataModelExpansions/DataModel.cs index f558f6e5d..04d7dffc6 100644 --- a/src/Artemis.Core/Plugins/DataModelExpansions/DataModel.cs +++ b/src/Artemis.Core/Plugins/DataModelExpansions/DataModel.cs @@ -79,7 +79,7 @@ namespace Artemis.Core.DataModelExpansions } dynamicDataModel.PluginInfo = PluginInfo; - dynamicDataModel.DataModelDescription = new DataModelPropertyAttribute() + dynamicDataModel.DataModelDescription = new DataModelPropertyAttribute { Name = name ?? key.Humanize(), Description = description @@ -189,5 +189,29 @@ namespace Artemis.Core.DataModelExpansions var child = GetTypeAtPath(path); return child.GenericTypeArguments.Length > 0 ? child.GenericTypeArguments[0] : null; } + + #region Events + + /// + /// Occurs when a dynamic data model has been added to this data model + /// + public event EventHandler DynamicDataBindingAdded; + + /// + /// Occurs when a dynamic data model has been removed from this data model + /// + public event EventHandler DynamicDataBindingRemoved; + + protected virtual void OnDynamicDataBindingAdded() + { + DynamicDataBindingAdded?.Invoke(this, EventArgs.Empty); + } + + protected virtual void OnDynamicDataBindingRemoved() + { + DynamicDataBindingRemoved?.Invoke(this, EventArgs.Empty); + } + + #endregion } } \ No newline at end of file diff --git a/src/Plugins/Artemis.Plugins.DataModelExpansions.TestData/DataModels/PluginDataModel.cs b/src/Plugins/Artemis.Plugins.DataModelExpansions.TestData/DataModels/PluginDataModel.cs index 48d021160..0e24eac8b 100644 --- a/src/Plugins/Artemis.Plugins.DataModelExpansions.TestData/DataModels/PluginDataModel.cs +++ b/src/Plugins/Artemis.Plugins.DataModelExpansions.TestData/DataModels/PluginDataModel.cs @@ -8,7 +8,7 @@ namespace Artemis.Plugins.DataModelExpansions.TestData.DataModels { public PluginDataModel() { - // PluginSubDataModel = new PluginSubDataModel(); + PluginSubDataModel = new PluginSubDataModel(); ListItems = new List(); for (var i = 0; i < 20; i++) ListItems.Add(new SomeListItem {ItemName = $"Item {i + 1}", Number = i}); diff --git a/src/Plugins/Artemis.Plugins.DataModelExpansions.TestData/PluginDataModelExpansion.cs b/src/Plugins/Artemis.Plugins.DataModelExpansions.TestData/PluginDataModelExpansion.cs index d8a2c34a5..2f9f944fd 100644 --- a/src/Plugins/Artemis.Plugins.DataModelExpansions.TestData/PluginDataModelExpansion.cs +++ b/src/Plugins/Artemis.Plugins.DataModelExpansions.TestData/PluginDataModelExpansion.cs @@ -1,5 +1,4 @@ using System; -using Artemis.Core; using Artemis.Core.DataModelExpansions; using Artemis.Plugins.DataModelExpansions.TestData.DataModels; using SkiaSharp; @@ -16,11 +15,6 @@ namespace Artemis.Plugins.DataModelExpansions.TestData AddTimedUpdate(TimeSpan.FromSeconds(1), TimedUpdate); DataModel.AddDynamicChild(new DynamicDataModel(), "Dynamic1", "Dynamic data model 1"); - - // var testPath1 = new DataModelPath(DataModel, "TemplateDataModelString"); - var testPath2 = new DataModelPath(DataModel, "PluginSubDataModel.Number"); - // var testPath3 = new DataModelPath(DataModel, "Dynamic1.DynamicString"); - // var testPath4 = new DataModelPath(DataModel, "TemplateDataModelString"); } public override void DisablePlugin()