diff --git a/src/Artemis.Core/Models/Profile/Conditions/Abstract/DataModelConditionPart.cs b/src/Artemis.Core/Models/Profile/Conditions/Abstract/DataModelConditionPart.cs index dd6d8425b..f16df9e7d 100644 --- a/src/Artemis.Core/Models/Profile/Conditions/Abstract/DataModelConditionPart.cs +++ b/src/Artemis.Core/Models/Profile/Conditions/Abstract/DataModelConditionPart.cs @@ -15,7 +15,7 @@ namespace Artemis.Core /// /// Gets the parent of this part /// - public DataModelConditionPart Parent { get; internal set; } + public DataModelConditionPart? Parent { get; internal set; } /// /// Gets the children of this part diff --git a/src/Artemis.Core/Models/Profile/Conditions/DataModelConditionList.cs b/src/Artemis.Core/Models/Profile/Conditions/DataModelConditionList.cs index 58fac9e3d..60739c2b5 100644 --- a/src/Artemis.Core/Models/Profile/Conditions/DataModelConditionList.cs +++ b/src/Artemis.Core/Models/Profile/Conditions/DataModelConditionList.cs @@ -95,6 +95,7 @@ namespace Artemis.Core if (!typeof(IList).IsAssignableFrom(listType)) throw new ArtemisCoreException($"Data model of type {dataModel.GetType().Name} does not contain a list at path '{newPath}'"); + ListPath = newPath; ListType = listType; IsPrimitiveList = listType.IsPrimitive || listType.IsEnum || listType == typeof(string); } diff --git a/src/Artemis.Core/Models/Profile/Conditions/DataModelConditionListPredicate.cs b/src/Artemis.Core/Models/Profile/Conditions/DataModelConditionListPredicate.cs index ff023d34b..f7e484254 100644 --- a/src/Artemis.Core/Models/Profile/Conditions/DataModelConditionListPredicate.cs +++ b/src/Artemis.Core/Models/Profile/Conditions/DataModelConditionListPredicate.cs @@ -1,6 +1,4 @@ using System; -using System.Linq; -using System.Linq.Expressions; using System.Reflection; using Artemis.Core.DataModelExpansions; using Artemis.Storage.Entities.Profile.Abstract; @@ -41,8 +39,6 @@ namespace Artemis.Core Initialize(); } - internal DataModelConditionListPredicateEntity Entity { get; set; } - /// /// Gets or sets the predicate type /// @@ -68,6 +64,8 @@ namespace Artemis.Core /// public object? RightStaticValue { get; private set; } + internal DataModelConditionListPredicateEntity Entity { get; set; } + /// /// Updates the left side of the predicate /// @@ -80,11 +78,12 @@ namespace Artemis.Core throw new ArtemisCoreException($"List type {DataModelConditionList.ListType.Name} does not contain path {path}"); LeftPath?.Dispose(); - LeftPath = new DataModelPath(new ListPredicateWrapperDataModel(), path); + LeftPath = DataModelConditionList.ListType != null + ? new DataModelPath(ListPredicateWrapperDataModel.Create(DataModelConditionList.ListType), path) + : null; ValidateOperator(); ValidateRightSide(); - CreateExpression(); } /// @@ -99,9 +98,9 @@ namespace Artemis.Core PredicateType = ListRightSideType.DynamicList; RightPath?.Dispose(); - RightPath = new DataModelPath(new ListPredicateWrapperDataModel(), path); - - CreateExpression(); + RightPath = DataModelConditionList.ListType != null + ? new DataModelPath(ListPredicateWrapperDataModel.Create(DataModelConditionList.ListType), path) + : null; } /// @@ -118,37 +117,32 @@ namespace Artemis.Core throw new ArtemisCoreException("If path is provided, a data model is also required"); if (dataModel != null) - { if (!dataModel.ContainsPath(path)) throw new ArtemisCoreException($"Data model of type {dataModel.GetType().Name} does not contain a property at path '{path}'"); - } PredicateType = ListRightSideType.Dynamic; RightPath?.Dispose(); RightPath = new DataModelPath(dataModel, path); - - CreateExpression(); } /// /// Updates the right side of the predicate, makes the predicate static and re-compiles the expression /// /// The right side value to use - public void UpdateRightSideStatic(object staticValue) + public void UpdateRightSideStatic(object? staticValue) { PredicateType = ListRightSideType.Static; RightPath?.Dispose(); RightPath = null; SetStaticValue(staticValue); - CreateExpression(); } /// /// Updates the operator of the predicate and re-compiles the expression /// /// - public void UpdateOperator(ConditionOperator conditionOperator) + public void UpdateOperator(ConditionOperator? conditionOperator) { if (conditionOperator == null) { @@ -170,8 +164,6 @@ namespace Artemis.Core if (conditionOperator.SupportsType(leftType)) Operator = conditionOperator; - - CreateExpression(); } /// @@ -205,6 +197,22 @@ namespace Artemis.Core return true; } + #region IDisposable + + /// + protected override void Dispose(bool disposing) + { + ConditionOperatorStore.ConditionOperatorAdded -= ConditionOperatorStoreOnConditionOperatorAdded; + ConditionOperatorStore.ConditionOperatorRemoved -= ConditionOperatorStoreOnConditionOperatorRemoved; + + LeftPath?.Dispose(); + RightPath?.Dispose(); + + base.Dispose(disposing); + } + + #endregion + internal override bool EvaluateObject(object target) { if (Operator == null || LeftPath == null || !LeftPath.IsValid) @@ -254,10 +262,12 @@ namespace Artemis.Core internal override void Save() { Entity.PredicateType = (int) PredicateType; - Entity.LeftPropertyPath = LeftPropertyPath; - Entity.RightDataModelGuid = RightDataModel?.PluginInfo?.Guid; - Entity.RightPropertyPath = RightPropertyPath; + LeftPath?.Save(); + Entity.LeftPath = LeftPath?.Entity; + RightPath?.Save(); + Entity.RightPath = RightPath?.Entity; + Entity.RightStaticValue = JsonConvert.SerializeObject(RightStaticValue); if (Operator != null) @@ -274,19 +284,12 @@ namespace Artemis.Core private void ApplyParentList() { - DataModelConditionPart current = Parent; - + DataModelConditionPart? current = Parent; while (current != null) { if (current is DataModelConditionList parentList) { DataModelConditionList = parentList; - - if (LeftPropertyPath != null && !ListContainsInnerPath(LeftPropertyPath)) - LeftPropertyPath = null; - if (RightPropertyPath != null && !ListContainsInnerPath(RightPropertyPath)) - RightPropertyPath = null; - return; } @@ -299,46 +302,44 @@ namespace Artemis.Core private void Initialize() { - DataModelStore.DataModelAdded += DataModelStoreOnDataModelAdded; - DataModelStore.DataModelRemoved += DataModelStoreOnDataModelRemoved; ConditionOperatorStore.ConditionOperatorAdded += ConditionOperatorStoreOnConditionOperatorAdded; ConditionOperatorStore.ConditionOperatorRemoved += ConditionOperatorStoreOnConditionOperatorRemoved; // Left side - if (Entity.LeftPropertyPath != null && ListContainsInnerPath(Entity.LeftPropertyPath)) - UpdateLeftSide(Entity.LeftPropertyPath); + if (Entity.LeftPath != null) + { + LeftPath = DataModelConditionList.ListType != null + ? new DataModelPath(ListPredicateWrapperDataModel.Create(DataModelConditionList.ListType), Entity.LeftPath) + : null; + } // Operator if (Entity.OperatorPluginGuid != null) { - ConditionOperator conditionOperator = ConditionOperatorStore.Get(Entity.OperatorPluginGuid.Value, Entity.OperatorType)?.ConditionOperator; + ConditionOperator? conditionOperator = ConditionOperatorStore.Get(Entity.OperatorPluginGuid.Value, Entity.OperatorType)?.ConditionOperator; if (conditionOperator != null) UpdateOperator(conditionOperator); } // Right side dynamic - if (PredicateType == ListRightSideType.Dynamic && Entity.RightDataModelGuid != null && Entity.RightPropertyPath != null) - { - DataModel dataModel = DataModelStore.Get(Entity.RightDataModelGuid.Value)?.DataModel; - if (dataModel != null && dataModel.ContainsPath(Entity.RightPropertyPath)) - UpdateRightSideDynamic(dataModel, Entity.RightPropertyPath); - } + if (PredicateType == ListRightSideType.Dynamic && Entity.RightPath != null) + RightPath = new DataModelPath(null, Entity.RightPath); // Right side dynamic inside the list - else if (PredicateType == ListRightSideType.DynamicList && Entity.RightPropertyPath != null) + else if (PredicateType == ListRightSideType.DynamicList && Entity.RightPath != null) { - if (ListContainsInnerPath(Entity.RightPropertyPath)) - UpdateRightSideDynamic(Entity.RightPropertyPath); + RightPath = DataModelConditionList.ListType != null + ? new DataModelPath(ListPredicateWrapperDataModel.Create(DataModelConditionList.ListType), Entity.RightPath) + : null; } // Right side static else if (PredicateType == ListRightSideType.Static && Entity.RightStaticValue != null) - { try { - if (LeftPropertyPath != null) + if (LeftPath != null && LeftPath.IsValid) { // Use the left side type so JSON.NET has a better idea what to do - Type leftSideType = GetTypeAtInnerPath(LeftPropertyPath); - object rightSideValue; + Type leftSideType = LeftPath.GetPropertyType()!; + object? rightSideValue; try { @@ -363,73 +364,62 @@ namespace Artemis.Core { DeserializationLogger.LogListPredicateDeserializationFailure(this, e); } - } - } - - private void CreateExpression() - { - if (Operator == null) - return; - - // If the operator does not support a right side, create a static expression because the right side will simply be null - if (PredicateType == ListRightSideType.DynamicList && Operator.SupportsRightSide) - CreateDynamicListAccessors(); - else if (PredicateType == ListRightSideType.Dynamic && Operator.SupportsRightSide) - CreateDynamicAccessors(); - else - CreateStaticAccessors(); } private void ValidateOperator() { - if (LeftPropertyPath == null || Operator == null) + if (LeftPath == null || !LeftPath.IsValid || Operator == null) return; - Type leftSideType = GetTypeAtInnerPath(LeftPropertyPath); - if (!Operator.SupportsType(leftSideType)) + Type leftType = LeftPath.GetPropertyType()!; + if (!Operator.SupportsType(leftType)) Operator = null; } private void ValidateRightSide() { - Type leftSideType = GetTypeAtInnerPath(LeftPropertyPath); + Type? leftType = LeftPath?.GetPropertyType(); if (PredicateType == ListRightSideType.Dynamic) { - if (RightDataModel == null) + if (RightPath == null || !RightPath.IsValid) return; - Type rightSideType = RightDataModel.GetTypeAtPath(RightPropertyPath); - if (!leftSideType.IsCastableFrom(rightSideType)) + Type rightSideType = RightPath.GetPropertyType()!; + if (leftType != null && !leftType.IsCastableFrom(rightSideType)) UpdateRightSideDynamic(null, null); } else if (PredicateType == ListRightSideType.DynamicList) { - if (RightPropertyPath == null) + if (RightPath == null || !RightPath.IsValid) return; - Type rightSideType = GetTypeAtInnerPath(RightPropertyPath); - if (!leftSideType.IsCastableFrom(rightSideType)) + Type rightSideType = RightPath.GetPropertyType()!; + if (leftType != null && !leftType.IsCastableFrom(rightSideType)) UpdateRightSideDynamic(null); } else { - if (RightStaticValue != null && leftSideType.IsCastableFrom(RightStaticValue.GetType())) + if (RightStaticValue != null && (leftType == null || leftType.IsCastableFrom(RightStaticValue.GetType()))) UpdateRightSideStatic(RightStaticValue); else UpdateRightSideStatic(null); } } - private void SetStaticValue(object staticValue) + private void SetStaticValue(object? staticValue) { + RightPath?.Dispose(); + RightPath = null; + // If the left side is empty simply apply the value, any validation will wait - if (LeftPropertyPath == null) + if (LeftPath == null || !LeftPath.IsValid) { RightStaticValue = staticValue; return; } - Type leftSideType = GetTypeAtInnerPath(LeftPropertyPath); + // If the left path is valid we can expect a type + Type leftSideType = LeftPath.GetPropertyType()!; // If not null ensure the types match and if not, convert it if (staticValue != null && staticValue.GetType() == leftSideType) @@ -448,107 +438,12 @@ namespace Artemis.Core if (!(path.Target is ListPredicateWrapperDataModel wrapper)) throw new ArtemisCoreException("Data model condition list predicate has a path with an invalid target"); - wrapper.Value = target; + wrapper.UntypedValue = target; return path.GetValue(); } - private void CreateDynamicListAccessors() - { - if (LeftPropertyPath == null || RightPropertyPath == null || Operator == null) - return; - - // List accessors share the same parameter because a list always contains one item per entry - ParameterExpression leftSideParameter = Expression.Parameter(typeof(object), "listItem"); - Expression leftSideAccessor = CreateListAccessor(LeftPropertyPath, leftSideParameter); - Expression rightSideAccessor = CreateListAccessor(RightPropertyPath, leftSideParameter); - - // A conversion may be required if the types differ - // This can cause issues if the DataModelConditionOperator wasn't accurate in it's supported types but that is not a concern here - if (rightSideAccessor.Type != leftSideAccessor.Type) - rightSideAccessor = Expression.Convert(rightSideAccessor, leftSideAccessor.Type); - - LeftSideAccessor = Expression.Lambda>(leftSideAccessor, leftSideParameter).Compile(); - RightSideAccessor = Expression.Lambda>(rightSideAccessor, leftSideParameter).Compile(); - } - - private void CreateDynamicAccessors() - { - if (LeftPropertyPath == null || RightPropertyPath == null || RightDataModel == null || Operator == null) - return; - - // List accessors share the same parameter because a list always contains one item per entry - ParameterExpression leftSideParameter = Expression.Parameter(typeof(object), "listItem"); - Expression leftSideAccessor = CreateListAccessor(LeftPropertyPath, leftSideParameter); - Expression rightSideAccessor = ExpressionUtilities.CreateDataModelAccessor(RightDataModel, RightPropertyPath, "right", out ParameterExpression rightSideParameter); - - // A conversion may be required if the types differ - // This can cause issues if the DataModelConditionOperator wasn't accurate in it's supported types but that is not a concern here - if (rightSideAccessor.Type != leftSideAccessor.Type) - rightSideAccessor = Expression.Convert(rightSideAccessor, leftSideAccessor.Type); - - LeftSideAccessor = Expression.Lambda>(leftSideAccessor, leftSideParameter).Compile(); - RightSideAccessor = Expression.Lambda>(rightSideAccessor, rightSideParameter).Compile(); - } - - private void CreateStaticAccessors() - { - if (!DataModelConditionList.IsPrimitiveList && LeftPropertyPath == null || Operator == null) - return; - - // List accessors share the same parameter because a list always contains one item per entry - ParameterExpression leftSideParameter = Expression.Parameter(typeof(object), "listItem"); - Expression leftSideAccessor = DataModelConditionList.IsPrimitiveList - ? Expression.Convert(leftSideParameter, DataModelConditionList.ListType) - : CreateListAccessor(LeftPropertyPath, leftSideParameter); - - LeftSideAccessor = Expression.Lambda>(leftSideAccessor, leftSideParameter).Compile(); - RightSideAccessor = null; - } - - private Expression CreateListAccessor(string path, ParameterExpression listParameter) - { - // Create an expression that checks every part of the path for null - // In the same iteration, create the accessor - Expression source = Expression.Convert(listParameter, DataModelConditionList.ListType); - return ExpressionUtilities.CreateNullCheckedAccessor(source, path); - } - - #region IDisposable - - /// - protected override void Dispose(bool disposing) - { - DataModelStore.DataModelAdded -= DataModelStoreOnDataModelAdded; - DataModelStore.DataModelRemoved -= DataModelStoreOnDataModelRemoved; - ConditionOperatorStore.ConditionOperatorAdded -= ConditionOperatorStoreOnConditionOperatorAdded; - ConditionOperatorStore.ConditionOperatorRemoved -= ConditionOperatorStoreOnConditionOperatorRemoved; - - LeftPath?.Dispose(); - RightPath?.Dispose(); - - base.Dispose(disposing); - } - - #endregion - #region Event handlers - private void DataModelStoreOnDataModelAdded(object sender, DataModelStoreEvent e) - { - DataModel dataModel = e.Registration.DataModel; - if (dataModel.PluginInfo.Guid == Entity.RightDataModelGuid && dataModel.ContainsPath(Entity.RightPropertyPath)) - UpdateRightSideDynamic(dataModel, Entity.RightPropertyPath); - } - - private void DataModelStoreOnDataModelRemoved(object sender, DataModelStoreEvent e) - { - if (RightDataModel == e.Registration.DataModel) - { - RightSideAccessor = null; - RightDataModel = null; - } - } - private void ConditionOperatorStoreOnConditionOperatorAdded(object sender, ConditionOperatorStoreEvent e) { ConditionOperator conditionOperator = e.Registration.ConditionOperator; diff --git a/src/Artemis.Core/Models/Profile/Conditions/DataModelConditionPredicate.cs b/src/Artemis.Core/Models/Profile/Conditions/DataModelConditionPredicate.cs index d5869fdf5..601bea3ff 100644 --- a/src/Artemis.Core/Models/Profile/Conditions/DataModelConditionPredicate.cs +++ b/src/Artemis.Core/Models/Profile/Conditions/DataModelConditionPredicate.cs @@ -240,8 +240,11 @@ namespace Artemis.Core Entity.RightStaticValue = JsonConvert.SerializeObject(RightStaticValue); - Entity.OperatorPluginGuid = Operator?.PluginInfo?.Guid; - Entity.OperatorType = Operator?.GetType().Name; + if (Operator != null) + { + Entity.OperatorPluginGuid = Operator.PluginInfo.Guid; + Entity.OperatorType = Operator.GetType().Name; + } } internal void Initialize() @@ -250,7 +253,8 @@ namespace Artemis.Core ConditionOperatorStore.ConditionOperatorRemoved += ConditionOperatorStoreOnConditionOperatorRemoved; // Left side - if (Entity.LeftPath != null) LeftPath = new DataModelPath(null, Entity.LeftPath); + if (Entity.LeftPath != null) + LeftPath = new DataModelPath(null, Entity.LeftPath); // Operator if (Entity.OperatorPluginGuid != null) diff --git a/src/Artemis.Core/Models/Profile/Conditions/ListPredicateWrapperDataModel.cs b/src/Artemis.Core/Models/Profile/Conditions/ListPredicateWrapperDataModel.cs index 404f6d5b0..e134b5dc8 100644 --- a/src/Artemis.Core/Models/Profile/Conditions/ListPredicateWrapperDataModel.cs +++ b/src/Artemis.Core/Models/Profile/Conditions/ListPredicateWrapperDataModel.cs @@ -1,9 +1,24 @@ -using Artemis.Core.DataModelExpansions; +using System; +using Artemis.Core.DataModelExpansions; namespace Artemis.Core { - internal class ListPredicateWrapperDataModel : DataModel + internal class ListPredicateWrapperDataModel : ListPredicateWrapperDataModel { - public object Value { get; set; } + public T Value => (UntypedValue is T typedValue ? typedValue : default)!; + } + + public abstract class ListPredicateWrapperDataModel : DataModel + { + public object? UntypedValue { get; set; } + + public static ListPredicateWrapperDataModel Create(Type type) + { + object? instance = Activator.CreateInstance(typeof(ListPredicateWrapperDataModel<>).MakeGenericType(type)); + if (instance == null) + throw new ArtemisCoreException($"Failed to create an instance of ListPredicateWrapperDataModel for type {type.Name}"); + + return (ListPredicateWrapperDataModel) instance; + } } } \ No newline at end of file diff --git a/src/Artemis.Core/Utilities/DeserializationLogger.cs b/src/Artemis.Core/Utilities/DeserializationLogger.cs index 26caddb4e..e1c176283 100644 --- a/src/Artemis.Core/Utilities/DeserializationLogger.cs +++ b/src/Artemis.Core/Utilities/DeserializationLogger.cs @@ -29,9 +29,9 @@ namespace Artemis.Core _logger.Warning( exception, "Failed to deserialize display condition list predicate {list} => {left} {operator} {right}", - dataModelConditionPredicate.Entity.LeftPropertyPath, + dataModelConditionPredicate.Entity.LeftPath?.Path, dataModelConditionPredicate.Entity.OperatorType, - dataModelConditionPredicate.Entity.RightPropertyPath + dataModelConditionPredicate.Entity.RightPath?.Path ); } diff --git a/src/Artemis.Storage/Entities/Profile/Conditions/DataModelConditionListPredicateEntity.cs b/src/Artemis.Storage/Entities/Profile/Conditions/DataModelConditionListPredicateEntity.cs index 025541f30..513a9af43 100644 --- a/src/Artemis.Storage/Entities/Profile/Conditions/DataModelConditionListPredicateEntity.cs +++ b/src/Artemis.Storage/Entities/Profile/Conditions/DataModelConditionListPredicateEntity.cs @@ -6,16 +6,15 @@ namespace Artemis.Storage.Entities.Profile.Conditions public class DataModelConditionListPredicateEntity : DataModelConditionPartEntity { public int PredicateType { get; set; } - - public string LeftPropertyPath { get; set; } - public Guid? RightDataModelGuid { get; set; } - public string RightPropertyPath { get; set; } + public DataModelPathEntity LeftPath { get; set; } + public DataModelPathEntity RightPath { get; set; } // Stored as a string to be able to control serialization and deserialization ourselves public string RightStaticValue { get; set; } public string OperatorType { get; set; } public Guid? OperatorPluginGuid { get; set; } + } } \ No newline at end of file diff --git a/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelListPropertiesViewModel.cs b/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelListPropertiesViewModel.cs index 314e37d39..6d6afaa64 100644 --- a/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelListPropertiesViewModel.cs +++ b/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelListPropertiesViewModel.cs @@ -1,5 +1,5 @@ using System; -using Artemis.Core.DataModelExpansions; +using Artemis.Core; using Artemis.UI.Shared.Services; namespace Artemis.UI.Shared @@ -10,9 +10,9 @@ namespace Artemis.UI.Shared private int _index; private Type _listType; - public DataModelListPropertiesViewModel(DataModel dataModel, object listItem) : base(null, null, null) + public DataModelListPropertiesViewModel(object listItem) : base(null, null, null) { - DataModel = dataModel; + DataModel = ListPredicateWrapperDataModel.Create(listItem.GetType()); ListType = listItem.GetType(); DisplayValue = listItem; } @@ -44,7 +44,7 @@ namespace Artemis.UI.Shared return; ListType = DisplayValue.GetType(); - PopulateProperties(dataModelUIService, DisplayValue); + PopulateProperties(dataModelUIService); foreach (DataModelVisualizationViewModel dataModelVisualizationViewModel in Children) dataModelVisualizationViewModel.Update(dataModelUIService); } diff --git a/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelListPropertyViewModel.cs b/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelListPropertyViewModel.cs index 2001cc4d8..db47e9f7a 100644 --- a/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelListPropertyViewModel.cs +++ b/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelListPropertyViewModel.cs @@ -1,5 +1,5 @@ using System; -using Artemis.Core.DataModelExpansions; +using Artemis.Core; using Artemis.UI.Shared.Services; namespace Artemis.UI.Shared @@ -9,17 +9,17 @@ namespace Artemis.UI.Shared private int _index; private Type _listType; - public DataModelListPropertyViewModel(DataModel dataModel, object listItem, DataModelDisplayViewModel displayViewModel) : base(null, null, null) + public DataModelListPropertyViewModel(object listItem, DataModelDisplayViewModel displayViewModel) : base(null, null, null) { - DataModel = dataModel; + DataModel = ListPredicateWrapperDataModel.Create(listItem.GetType()); ListType = listItem.GetType(); DisplayValue = listItem; DisplayViewModel = displayViewModel; } - public DataModelListPropertyViewModel(DataModel dataModel, object listItem) : base(null, null, null) + public DataModelListPropertyViewModel(object listItem) : base(null, null, null) { - DataModel = dataModel; + DataModel = ListPredicateWrapperDataModel.Create(listItem.GetType()); ListType = listItem.GetType(); DisplayValue = listItem; } diff --git a/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelListViewModel.cs b/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelListViewModel.cs index 0bd4a2c91..28c483320 100644 --- a/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelListViewModel.cs +++ b/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelListViewModel.cs @@ -11,8 +11,7 @@ namespace Artemis.UI.Shared { private string _count; private IList _list; - private DataModelVisualizationViewModel _listTypePropertyViewModel; - + internal DataModelListViewModel(DataModel dataModel, DataModelVisualizationViewModel parent, DataModelPath dataModelPath) : base(dataModel, parent, dataModelPath) { ListChildren = new BindableCollection(); @@ -44,8 +43,6 @@ namespace Artemis.UI.Shared // Put an empty value into the list type property view model if (viewModel is DataModelListPropertiesViewModel dataModelListClassViewModel) { - dataModelListClassViewModel.DisplayValue = Activator.CreateInstance(dataModelListClassViewModel.ListType); - dataModelListClassViewModel.Update(dataModelUIService); return dataModelListClassViewModel; } @@ -109,13 +106,13 @@ namespace Artemis.UI.Shared // If a display VM was found, prefer to use that in any case DataModelDisplayViewModel typeViewModel = dataModelUIService.GetDataModelDisplayViewModel(listType); if (typeViewModel != null) - return new DataModelListPropertyViewModel(DataModel, listItem, typeViewModel); + return new DataModelListPropertyViewModel(listItem, typeViewModel); // For primitives, create a property view model, it may be null that is fine if (listType.IsPrimitive || listType.IsEnum || listType == typeof(string)) - return new DataModelListPropertyViewModel(DataModel, listItem); + return new DataModelListPropertyViewModel(listItem); // For other value types create a child view model if (listType.IsClass || listType.IsStruct()) - return new DataModelListPropertiesViewModel(DataModel, listItem); + return new DataModelListPropertiesViewModel(listItem); return null; } diff --git a/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelPropertiesViewModel.cs b/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelPropertiesViewModel.cs index adcda365d..6da643ac7 100644 --- a/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelPropertiesViewModel.cs +++ b/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelPropertiesViewModel.cs @@ -13,7 +13,7 @@ namespace Artemis.UI.Shared public override void Update(IDataModelUIService dataModelUIService) { // Always populate properties - PopulateProperties(dataModelUIService, null); + PopulateProperties(dataModelUIService); // Only update children if the parent is expanded if (Parent != null && !Parent.IsVisualizationExpanded && !Parent.IsRootViewModel) diff --git a/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelVisualizationViewModel.cs b/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelVisualizationViewModel.cs index 744982273..67651698f 100644 --- a/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelVisualizationViewModel.cs +++ b/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelVisualizationViewModel.cs @@ -125,7 +125,7 @@ namespace Artemis.UI.Shared } // If the type couldn't be retrieved either way, assume false - Type type = DataModelPath.GetPropertyType(); + Type type = DataModelPath?.GetPropertyType(); if (type == null) { IsMatchingFilteredTypes = false; @@ -178,18 +178,12 @@ namespace Artemis.UI.Shared return 0; } - internal void PopulateProperties(IDataModelUIService dataModelUIService, object overrideValue) + internal void PopulateProperties(IDataModelUIService dataModelUIService) { - if (IsRootViewModel && overrideValue == null) + if (IsRootViewModel) return; - Type modelType; - if (overrideValue != null) - modelType = overrideValue.GetType(); - else if (Parent.IsRootViewModel) - modelType = DataModel.GetType(); - else - modelType = DataModelPath.GetPropertyType(); + Type modelType = Parent.IsRootViewModel ? DataModel.GetType() : DataModelPath.GetPropertyType(); // Add missing static children foreach (PropertyInfo propertyInfo in modelType.GetProperties(BindingFlags.Public | BindingFlags.Instance)) @@ -200,17 +194,13 @@ namespace Artemis.UI.Shared if (propertyInfo.GetCustomAttribute() != null) continue; - DataModelVisualizationViewModel child = CreateChild(dataModelUIService, childPath, GetChildDepth(), overrideValue); + DataModelVisualizationViewModel child = CreateChild(dataModelUIService, childPath, GetChildDepth()); if (child != null) Children.Add(child); } // Remove static children that should be hidden - ReadOnlyCollection hiddenProperties; - if (overrideValue != null && overrideValue is DataModel overrideValueDataModel) - hiddenProperties = overrideValueDataModel.GetHiddenProperties(); - else - hiddenProperties = DataModel.GetHiddenProperties(); + ReadOnlyCollection hiddenProperties = DataModel.GetHiddenProperties(); foreach (PropertyInfo hiddenProperty in hiddenProperties) { string childPath = AppendToPath(hiddenProperty.Name); @@ -220,11 +210,7 @@ namespace Artemis.UI.Shared } // Add missing dynamic children - object value; - if (overrideValue != null) - value = overrideValue; - else - value = Parent.IsRootViewModel ? DataModel : DataModelPath.GetValue(); + object value = Parent.IsRootViewModel ? DataModel : DataModelPath.GetValue(); if (value is DataModel dataModel) { foreach (KeyValuePair kvp in dataModel.DynamicDataModels) @@ -233,7 +219,7 @@ namespace Artemis.UI.Shared if (Children.Any(c => c.Path != null && c.Path.Equals(childPath))) continue; - DataModelVisualizationViewModel child = CreateChild(dataModelUIService, childPath, GetChildDepth(), overrideValue); + DataModelVisualizationViewModel child = CreateChild(dataModelUIService, childPath, GetChildDepth()); if (child != null) Children.Add(child); } @@ -245,12 +231,12 @@ namespace Artemis.UI.Shared Children.RemoveRange(toRemoveDynamic); } - private DataModelVisualizationViewModel CreateChild(IDataModelUIService dataModelUIService, string path, int depth, object overrideValue) + private DataModelVisualizationViewModel CreateChild(IDataModelUIService dataModelUIService, string path, int depth) { if (depth > MaxDepth) return null; - DataModelPath dataModelPath = new DataModelPath(overrideValue ?? DataModel, path); + DataModelPath dataModelPath = new DataModelPath(DataModel, path); if (!dataModelPath.IsValid) return null; diff --git a/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionListPredicateViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionListPredicateViewModel.cs index 10e820f58..fe0c16bb0 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionListPredicateViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionListPredicateViewModel.cs @@ -79,18 +79,6 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions public DelegateCommand SelectOperatorCommand { get; } - public void Dispose() - { - if (!_isPrimitiveList) - { - LeftSideSelectionViewModel.PropertySelected -= LeftSideOnPropertySelected; - LeftSideSelectionViewModel.Dispose(); - } - - DisposeRightSideDynamic(); - DisposeRightSideStatic(); - } - public override void Delete() { base.Delete(); @@ -123,7 +111,7 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions public override void Update() { - Guid? listDataModelGuid = DataModelConditionListPredicate.DataModelConditionList.ListPath.DataModelGuid; + Guid? listDataModelGuid = DataModelConditionListPredicate.DataModelConditionList.ListPath?.DataModelGuid; if (listDataModelGuid == null) return; @@ -132,7 +120,7 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions LeftSideSelectionViewModel.FilterTypes = _supportedInputTypes.ToArray(); LeftSideSelectionViewModel.ButtonBrush = new SolidColorBrush(Color.FromRgb(71, 108, 188)); LeftSideSelectionViewModel.SelectedPropertyViewModel = LeftSideSelectionViewModel.DataModelViewModel.GetChildByPath( - listDataModelGuid.Value, DataModelConditionListPredicate.LeftPropertyPath + listDataModelGuid.Value, DataModelConditionListPredicate.DataModelConditionList.ListPath.Path ); } @@ -142,14 +130,20 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions // Get the supported operators Operators.Clear(); - Operators.AddRange(_conditionOperatorService.GetConditionOperatorsForType(leftSideType)); + Operators.AddRange(_conditionOperatorService.GetConditionOperatorsForType(leftSideType ?? typeof(object))); if (DataModelConditionListPredicate.Operator == null) - DataModelConditionListPredicate.UpdateOperator(Operators.FirstOrDefault(o => o.SupportsType(leftSideType))); + DataModelConditionListPredicate.UpdateOperator(Operators.FirstOrDefault(o => o.SupportsType(leftSideType ?? typeof(object)))); SelectedOperator = DataModelConditionListPredicate.Operator; + if (SelectedOperator == null || !SelectedOperator.SupportsRightSide) + { + DisposeRightSideStatic(); + DisposeRightSideDynamic(); + } // Ensure the right side has the proper VM - if (DataModelConditionListPredicate.PredicateType == ListRightSideType.Dynamic || - DataModelConditionListPredicate.PredicateType == ListRightSideType.DynamicList) + if ((DataModelConditionListPredicate.PredicateType == ListRightSideType.Dynamic || + DataModelConditionListPredicate.PredicateType == ListRightSideType.DynamicList) && + SelectedOperator.SupportsRightSide) { DisposeRightSideStatic(); if (RightSideSelectionViewModel == null) @@ -164,20 +158,16 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions RightSideSelectionViewModel.FilterTypes = new[] {leftSideType}; if (DataModelConditionListPredicate.PredicateType == ListRightSideType.Dynamic) - { RightSideSelectionViewModel.PopulateSelectedPropertyViewModel( - DataModelConditionListPredicate.RightDataModel, - DataModelConditionListPredicate.RightPropertyPath + DataModelConditionListPredicate.RightPath?.Target, + DataModelConditionListPredicate.RightPath?.Path ); - } else - { RightSideSelectionViewModel.SelectedPropertyViewModel = RightSideSelectionViewModel.DataModelViewModel.GetChildByPath( - listDataModelGuid.Value, DataModelConditionListPredicate.RightPropertyPath + listDataModelGuid.Value, DataModelConditionListPredicate.RightPath?.Path ); - } } - else + else if (SelectedOperator.SupportsRightSide) { DisposeRightSideDynamic(); if (RightSideInputViewModel == null) @@ -205,16 +195,12 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions public void ApplyRightSideDynamic() { if (DataModelConditionListPredicate.PredicateType == ListRightSideType.Dynamic) - { DataModelConditionListPredicate.UpdateRightSideDynamic( RightSideSelectionViewModel.SelectedPropertyViewModel.DataModel, RightSideSelectionViewModel.SelectedPropertyViewModel.Path ); - } else if (DataModelConditionListPredicate.PredicateType == ListRightSideType.DynamicList) - { DataModelConditionListPredicate.UpdateRightSideDynamic(RightSideSelectionViewModel.SelectedPropertyViewModel.Path); - } _profileEditorService.UpdateSelectedProfileElement(); Update(); @@ -238,10 +224,10 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions private DataModelVisualizationViewModel GetListDataModel() { - DataModelPropertiesViewModel dataModel = _dataModelUIService.GetPluginDataModelVisualization(_profileEditorService.GetCurrentModule(), true); - if (DataModelConditionListPredicate.DataModelConditionList.ListPath.DataModelGuid == null) - return null; + if (DataModelConditionListPredicate.DataModelConditionList.ListPath?.DataModelGuid == null) + throw new ArtemisUIException("Failed to retrieve the list data model VM for this list predicate because it has no list path"); + DataModelPropertiesViewModel dataModel = _dataModelUIService.GetPluginDataModelVisualization(_profileEditorService.GetCurrentModule(), true); DataModelListViewModel listDataModel = (DataModelListViewModel) dataModel.GetChildByPath( DataModelConditionListPredicate.DataModelConditionList.ListPath.DataModelGuid.Value, DataModelConditionListPredicate.DataModelConditionList.ListPath.Path @@ -293,5 +279,17 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions RightSideSelectionViewModel = null; } } + + public void Dispose() + { + if (!_isPrimitiveList) + { + LeftSideSelectionViewModel.PropertySelected -= LeftSideOnPropertySelected; + LeftSideSelectionViewModel.Dispose(); + } + + DisposeRightSideDynamic(); + DisposeRightSideStatic(); + } } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionListViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionListViewModel.cs index d0288eb10..36144400a 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionListViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionListViewModel.cs @@ -107,7 +107,7 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions public override void Update() { - TargetSelectionViewModel.PopulateSelectedPropertyViewModel(DataModelConditionList.ListPath.Target, DataModelConditionList.ListPath.Path); + TargetSelectionViewModel.PopulateSelectedPropertyViewModel(DataModelConditionList.ListPath?.Target, DataModelConditionList.ListPath?.Path); NotifyOfPropertyChange(nameof(SelectedListOperator)); // Remove VMs of effects no longer applied on the layer diff --git a/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionPredicateViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionPredicateViewModel.cs index 64808dd19..52055076b 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionPredicateViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/Conditions/DataModelConditionPredicateViewModel.cs @@ -118,25 +118,24 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions { LeftSideSelectionViewModel.FilterTypes = _supportedInputTypes.ToArray(); LeftSideSelectionViewModel.PopulateSelectedPropertyViewModel( - DataModelConditionPredicate.LeftPath.Target as DataModel, - DataModelConditionPredicate.LeftPath.Path + DataModelConditionPredicate.LeftPath?.Target, + DataModelConditionPredicate.LeftPath?.Path ); Type leftSideType = LeftSideSelectionViewModel.SelectedPropertyViewModel?.DataModelPath?.GetPropertyType(); // Get the supported operators Operators.Clear(); - Operators.AddRange(_conditionOperatorService.GetConditionOperatorsForType(leftSideType)); + Operators.AddRange(_conditionOperatorService.GetConditionOperatorsForType(leftSideType ?? typeof(object))); if (DataModelConditionPredicate.Operator == null) - DataModelConditionPredicate.UpdateOperator(Operators.FirstOrDefault(o => o.SupportsType(leftSideType))); + DataModelConditionPredicate.UpdateOperator(Operators.FirstOrDefault(o => o.SupportsType(leftSideType ?? typeof(object)))); SelectedOperator = DataModelConditionPredicate.Operator; - if (!SelectedOperator.SupportsRightSide) + if (SelectedOperator == null || !SelectedOperator.SupportsRightSide) { DisposeRightSideStatic(); DisposeRightSideDynamic(); } // Ensure the right side has the proper VM - Type targetType = LeftSideSelectionViewModel?.SelectedPropertyViewModel?.DataModelPath?.GetPropertyType(); if (DataModelConditionPredicate.PredicateType == ProfileRightSideType.Dynamic && SelectedOperator.SupportsRightSide) { DisposeRightSideStatic(); @@ -148,24 +147,24 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions } RightSideSelectionViewModel.PopulateSelectedPropertyViewModel( - DataModelConditionPredicate.RightPath.Target as DataModel, - DataModelConditionPredicate.RightPath.Path + DataModelConditionPredicate.RightPath?.Target, + DataModelConditionPredicate.RightPath?.Path ); - RightSideSelectionViewModel.FilterTypes = new[] {targetType}; + RightSideSelectionViewModel.FilterTypes = new[] {leftSideType}; } else if (SelectedOperator.SupportsRightSide) { DisposeRightSideDynamic(); if (RightSideInputViewModel == null) { - RightSideInputViewModel = _dataModelUIService.GetStaticInputViewModel(targetType); + RightSideInputViewModel = _dataModelUIService.GetStaticInputViewModel(leftSideType); RightSideInputViewModel.ButtonBrush = (Brush) Application.Current.FindResource("PrimaryHueMidBrush"); RightSideInputViewModel.ValueUpdated += RightSideOnValueEntered; } RightSideInputViewModel.Value = DataModelConditionPredicate.RightStaticValue; - if (RightSideInputViewModel.TargetType != targetType) - RightSideInputViewModel.UpdateTargetType(targetType); + if (RightSideInputViewModel.TargetType != leftSideType) + RightSideInputViewModel.UpdateTargetType(leftSideType); } } @@ -213,7 +212,7 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions ApplyLeftSide(); } - private void RightSideOnPropertySelected(object? sender, DataModelInputDynamicEventArgs e) + private void RightSideOnPropertySelected(object sender, DataModelInputDynamicEventArgs e) { ApplyRightSideDynamic(); } diff --git a/src/Artemis.sln.DotSettings b/src/Artemis.sln.DotSettings index 382c2aef7..6f469af9c 100644 --- a/src/Artemis.sln.DotSettings +++ b/src/Artemis.sln.DotSettings @@ -207,6 +207,7 @@ ERROR ERROR ERROR + UI True True True