1
0
mirror of https://github.com/Artemis-RGB/Artemis synced 2025-12-13 05:48:35 +00:00

Conditions - Move to new paths API (WIP)

This commit is contained in:
SpoinkyNL 2020-10-06 23:39:42 +02:00
parent a2759d007e
commit da132fa4e9
7 changed files with 136 additions and 296 deletions

View File

@ -50,6 +50,6 @@ namespace Artemis.Core
/// </summary> /// </summary>
/// <param name="a">The parameter on the left side of the expression</param> /// <param name="a">The parameter on the left side of the expression</param>
/// <param name="b">The parameter on the right side of the expression</param> /// <param name="b">The parameter on the right side of the expression</param>
public abstract bool Evaluate(object a, object b); public abstract bool Evaluate(object? a, object? b);
} }
} }

View File

@ -2,7 +2,6 @@
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Linq.Expressions;
using Artemis.Core.DataModelExpansions; using Artemis.Core.DataModelExpansions;
using Artemis.Storage.Entities.Profile.Abstract; using Artemis.Storage.Entities.Profile.Abstract;
using Artemis.Storage.Entities.Profile.Conditions; using Artemis.Storage.Entities.Profile.Conditions;
@ -37,37 +36,27 @@ namespace Artemis.Core
Initialize(); Initialize();
} }
internal DataModelConditionListEntity Entity { get; set; }
/// <summary> /// <summary>
/// Gets or sets the list operator /// Gets or sets the list operator
/// </summary> /// </summary>
public ListOperator ListOperator { get; set; } public ListOperator ListOperator { get; set; }
/// <summary>
/// Gets the path of the list property
/// </summary>
public DataModelPath? ListPath { get; set; }
/// <summary> /// <summary>
/// Gets the type of the content of the list this predicate is evaluated on /// Gets the type of the content of the list this predicate is evaluated on
/// </summary> /// </summary>
public Type ListType { get; set; } public Type? ListType { get; set; }
/// <summary> /// <summary>
/// Gets whether the list contains primitives /// Gets whether the list contains primitives
/// </summary> /// </summary>
public bool IsPrimitiveList { get; set; } public bool IsPrimitiveList { get; set; }
/// <summary> internal DataModelConditionListEntity Entity { get; set; }
/// Gets the currently used instance of the list data model
/// </summary>
public DataModel ListDataModel { get; private set; }
/// <summary>
/// Gets the path of the list property in the <see cref="ListDataModel" />
/// </summary>
public string ListPropertyPath { get; private set; }
/// <summary>
/// Gets the compiled function that accesses the list this condition evaluates on
/// </summary>
public Func<object, IList> CompiledListAccessor { get; private set; }
/// <inheritdoc /> /// <inheritdoc />
public override bool Evaluate() public override bool Evaluate()
@ -75,10 +64,10 @@ namespace Artemis.Core
if (_disposed) if (_disposed)
throw new ObjectDisposedException("DataModelConditionList"); throw new ObjectDisposedException("DataModelConditionList");
if (CompiledListAccessor == null) if (ListPath == null || !ListPath.IsValid)
return false; return false;
return EvaluateObject(CompiledListAccessor(ListDataModel)); return EvaluateObject(ListPath.GetValue());
} }
/// <summary> /// <summary>
@ -86,7 +75,7 @@ namespace Artemis.Core
/// </summary> /// </summary>
/// <param name="dataModel">The data model of the list</param> /// <param name="dataModel">The data model of the list</param>
/// <param name="path">The path pointing to the list inside the list</param> /// <param name="path">The path pointing to the list inside the list</param>
public void UpdateList(DataModel dataModel, string path) public void UpdateList(DataModel? dataModel, string? path)
{ {
if (_disposed) if (_disposed)
throw new ObjectDisposedException("DataModelConditionList"); throw new ObjectDisposedException("DataModelConditionList");
@ -96,17 +85,23 @@ namespace Artemis.Core
if (dataModel == null && path != null) if (dataModel == null && path != null)
throw new ArtemisCoreException("If path is provided, a data model is also required"); throw new ArtemisCoreException("If path is provided, a data model is also required");
if (dataModel != null) ListPath?.Dispose();
if (dataModel != null && path != null)
{ {
Type listType = dataModel.GetListTypeAtPath(path); DataModelPath newPath = new DataModelPath(dataModel, path);
if (listType == null) if (!newPath.IsValid)
throw new ArtemisCoreException($"Data model of type {dataModel.GetType().Name} does not contain a list at path '{path}'"); throw new ArtemisCoreException($"New left path '{newPath}' is invalid");
Type listType = newPath.GetPropertyType()!;
if (!typeof(IList).IsAssignableFrom(listType))
throw new ArtemisCoreException($"Data model of type {dataModel.GetType().Name} does not contain a list at path '{newPath}'");
ListType = listType; ListType = listType;
IsPrimitiveList = listType.IsPrimitive || listType.IsEnum || listType == typeof(string); IsPrimitiveList = listType.IsPrimitive || listType.IsEnum || listType == typeof(string);
}
ListDataModel = dataModel; else
ListPropertyPath = path; {
ListPath = null;
ListType = null;
} }
// Remove the old root group that was tied to the old data model // Remove the old root group that was tied to the old data model
@ -118,7 +113,6 @@ namespace Artemis.Core
// Create a new root group // Create a new root group
AddChild(new DataModelConditionGroup(this)); AddChild(new DataModelConditionGroup(this));
CreateExpression();
} }
#region IDisposable #region IDisposable
@ -128,8 +122,7 @@ namespace Artemis.Core
{ {
_disposed = true; _disposed = true;
DataModelStore.DataModelAdded -= DataModelStoreOnDataModelAdded; ListPath?.Dispose();
DataModelStore.DataModelRemoved -= DataModelStoreOnDataModelRemoved;
foreach (DataModelConditionPart child in Children) foreach (DataModelConditionPart child in Children)
child.Dispose(); child.Dispose();
@ -139,7 +132,7 @@ namespace Artemis.Core
#endregion #endregion
internal override bool EvaluateObject(object target) internal override bool EvaluateObject(object? target)
{ {
if (_disposed) if (_disposed)
throw new ObjectDisposedException("DataModelConditionList"); throw new ObjectDisposedException("DataModelConditionList");
@ -163,11 +156,8 @@ namespace Artemis.Core
internal override void Save() internal override void Save()
{ {
// Target list // Target list
if (ListDataModel != null) ListPath?.Save();
{ Entity.ListPath = ListPath?.Entity;
Entity.ListDataModelGuid = ListDataModel.PluginInfo.Guid;
Entity.ListPropertyPath = ListPropertyPath;
}
// Operator // Operator
Entity.ListOperator = (int) ListOperator; Entity.ListOperator = (int) ListOperator;
@ -186,80 +176,27 @@ namespace Artemis.Core
internal void Initialize() internal void Initialize()
{ {
DataModelStore.DataModelAdded += DataModelStoreOnDataModelAdded; if (Entity.ListPath == null)
DataModelStore.DataModelRemoved += DataModelStoreOnDataModelRemoved;
if (Entity.ListDataModelGuid == null)
return; return;
// Get the data model ... // Ensure the list path is valid and points to a list
DataModel dataModel = DataModelStore.Get(Entity.ListDataModelGuid.Value)?.DataModel; DataModelPath listPath = new DataModelPath(null, Entity.ListPath);
if (dataModel == null) if (!listPath.IsValid || !typeof(IList).IsAssignableFrom(listPath.GetPropertyType()))
return;
// ... and ensure the path is valid
Type listType = dataModel.GetListTypeAtPath(Entity.ListPropertyPath);
if (listType == null)
return; return;
ListType = listType; ListPath = listPath;
IsPrimitiveList = listType.IsPrimitive || listType.IsEnum || listType == typeof(string);
ListDataModel = dataModel;
ListPropertyPath = Entity.ListPropertyPath;
CreateExpression();
if (ListDataModel == null)
return;
// There should only be one child and it should be a group // There should only be one child and it should be a group
if (Entity.Children.SingleOrDefault() is DataModelConditionGroupEntity rootGroup) if (Entity.Children.SingleOrDefault() is DataModelConditionGroupEntity rootGroup)
{
AddChild(new DataModelConditionGroup(this, rootGroup)); AddChild(new DataModelConditionGroup(this, rootGroup));
}
else else
{ {
Entity.Children.Clear(); Entity.Children.Clear();
AddChild(new DataModelConditionGroup(this)); AddChild(new DataModelConditionGroup(this));
} }
} }
private void CreateExpression()
{
if (_disposed)
throw new ObjectDisposedException("DataModelConditionList");
ParameterExpression parameter = Expression.Parameter(typeof(object), "listDataModel");
Expression accessor = ListPropertyPath.Split('.').Aggregate<string, Expression>(
Expression.Convert(parameter, ListDataModel.GetType()),
Expression.Property
);
accessor = Expression.Convert(accessor, typeof(IList));
Expression<Func<object, IList>> lambda = Expression.Lambda<Func<object, IList>>(accessor, parameter);
CompiledListAccessor = lambda.Compile();
}
#region Event handlers
private void DataModelStoreOnDataModelAdded(object sender, DataModelStoreEvent e)
{
DataModel dataModel = e.Registration.DataModel;
if (dataModel.PluginInfo.Guid == Entity.ListDataModelGuid && dataModel.ContainsPath(Entity.ListPropertyPath))
{
ListDataModel = dataModel;
ListPropertyPath = Entity.ListPropertyPath;
CreateExpression();
}
}
private void DataModelStoreOnDataModelRemoved(object sender, DataModelStoreEvent e)
{
if (ListDataModel != e.Registration.DataModel)
return;
ListDataModel = null;
CompiledListAccessor = null;
}
#endregion
} }
/// <summary> /// <summary>

View File

@ -1,6 +1,4 @@
using System; using System;
using System.Linq;
using System.Linq.Expressions;
using Artemis.Core.DataModelExpansions; using Artemis.Core.DataModelExpansions;
using Artemis.Storage.Entities.Profile.Abstract; using Artemis.Storage.Entities.Profile.Abstract;
using Artemis.Storage.Entities.Profile.Conditions; using Artemis.Storage.Entities.Profile.Conditions;
@ -45,36 +43,23 @@ namespace Artemis.Core
/// <summary> /// <summary>
/// Gets the operator /// Gets the operator
/// </summary> /// </summary>
public ConditionOperator Operator { get; private set; } public ConditionOperator? Operator { get; private set; }
/// <summary> /// <summary>
/// Gets the currently used instance of the left data model /// Gets the path of the left property
/// </summary> /// </summary>
public DataModel LeftDataModel { get; private set; } public DataModelPath? LeftPath { get; set; }
/// <summary> /// <summary>
/// Gets the path of the left property in the <see cref="LeftDataModel" /> /// Gets the path of the right property
/// </summary> /// </summary>
public string LeftPropertyPath { get; private set; } public DataModelPath? RightPath { get; set; }
/// <summary>
/// Gets the currently used instance of the right data model
/// </summary>
public DataModel RightDataModel { get; private set; }
/// <summary>
/// Gets the path of the right property in the <see cref="RightDataModel" />
/// </summary>
public string RightPropertyPath { get; private set; }
/// <summary> /// <summary>
/// Gets the right static value, only used it <see cref="PredicateType" /> is /// Gets the right static value, only used it <see cref="PredicateType" /> is
/// <see cref="ProfileRightSideType.Static" /> /// <see cref="ProfileRightSideType.Static" />
/// </summary> /// </summary>
public object RightStaticValue { get; private set; } public object? RightStaticValue { get; private set; }
public Func<object, object> LeftSideAccessor { get; set; }
public Func<object, object> RightSideAccessor { get; set; }
internal DataModelConditionPredicateEntity Entity { get; set; } internal DataModelConditionPredicateEntity Entity { get; set; }
@ -83,26 +68,28 @@ namespace Artemis.Core
/// </summary> /// </summary>
/// <param name="dataModel">The data model of the left side value</param> /// <param name="dataModel">The data model of the left side value</param>
/// <param name="path">The path pointing to the left side value inside the data model</param> /// <param name="path">The path pointing to the left side value inside the data model</param>
public void UpdateLeftSide(DataModel dataModel, string path) public void UpdateLeftSide(DataModel? dataModel, string? path)
{ {
if (dataModel != null && path == null) if (dataModel != null && path == null)
throw new ArtemisCoreException("If a data model is provided, a path is also required"); throw new ArtemisCoreException("If a data model is provided, a path is also required");
if (dataModel == null && path != null) if (dataModel == null && path != null)
throw new ArtemisCoreException("If path is provided, a data model is also required"); throw new ArtemisCoreException("If path is provided, a data model is also required");
if (dataModel != null) LeftPath?.Dispose();
if (dataModel != null && path != null)
{ {
if (!dataModel.ContainsPath(path)) DataModelPath newPath = new DataModelPath(dataModel, path);
throw new ArtemisCoreException($"Data model of type {dataModel.GetType().Name} does not contain a property at path '{path}'"); if (!newPath.IsValid)
throw new ArtemisCoreException($"New left path '{newPath}' is invalid");
LeftPath = newPath;
}
else
{
LeftPath = null;
} }
LeftDataModel = dataModel;
LeftPropertyPath = path;
ValidateOperator(); ValidateOperator();
ValidateRightSide(); ValidateRightSide();
CreateAccessors();
} }
/// <summary> /// <summary>
@ -110,44 +97,48 @@ namespace Artemis.Core
/// </summary> /// </summary>
/// <param name="dataModel">The data model of the right side value</param> /// <param name="dataModel">The data model of the right side value</param>
/// <param name="path">The path pointing to the right side value inside the data model</param> /// <param name="path">The path pointing to the right side value inside the data model</param>
public void UpdateRightSide(DataModel dataModel, string path) public void UpdateRightSide(DataModel? dataModel, string? path)
{ {
if (dataModel != null && path == null) if (dataModel != null && path == null)
throw new ArtemisCoreException("If a data model is provided, a path is also required"); throw new ArtemisCoreException("If a data model is provided, a path is also required");
if (dataModel == null && path != null) if (dataModel == null && path != null)
throw new ArtemisCoreException("If path is provided, a data model is also required"); throw new ArtemisCoreException("If path is provided, a data model is also required");
if (dataModel != null) RightPath?.Dispose();
if (dataModel != null && path != null)
{ {
if (!dataModel.ContainsPath(path)) DataModelPath newPath = new DataModelPath(dataModel, path);
throw new ArtemisCoreException($"Data model of type {dataModel.GetType().Name} does not contain a property at path '{path}'"); if (!newPath.IsValid)
throw new ArtemisCoreException($"New right path '{newPath}' is invalid");
RightPath = newPath;
}
else
{
RightPath = null;
} }
PredicateType = ProfileRightSideType.Dynamic; PredicateType = ProfileRightSideType.Dynamic;
RightDataModel = dataModel;
RightPropertyPath = path;
CreateAccessors();
} }
/// <summary> /// <summary>
/// Updates the right side of the predicate, makes the predicate static and re-compiles the expression /// Updates the right side of the predicate, makes the predicate static and re-compiles the expression
/// </summary> /// </summary>
/// <param name="staticValue">The right side value to use</param> /// <param name="staticValue">The right side value to use</param>
public void UpdateRightSide(object staticValue) public void UpdateRightSide(object? staticValue)
{ {
PredicateType = ProfileRightSideType.Static; PredicateType = ProfileRightSideType.Static;
RightDataModel = null; RightPath?.Dispose();
RightPropertyPath = null; RightPath = null;
// If the left side is empty simply apply the value, any validation will wait // If the left side is empty simply apply the value, any validation will wait
if (LeftDataModel == null) if (LeftPath == null || !LeftPath.IsValid)
{ {
RightStaticValue = staticValue; RightStaticValue = staticValue;
return; return;
} }
Type leftSideType = LeftDataModel.GetTypeAtPath(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 not null ensure the types match and if not, convert it
if (staticValue != null && staticValue.GetType() == leftSideType) if (staticValue != null && staticValue.GetType() == leftSideType)
@ -159,63 +150,77 @@ namespace Artemis.Core
RightStaticValue = Activator.CreateInstance(leftSideType); RightStaticValue = Activator.CreateInstance(leftSideType);
else else
RightStaticValue = null; RightStaticValue = null;
CreateAccessors();
} }
/// <summary> /// <summary>
/// Updates the operator of the predicate and re-compiles the expression /// Updates the operator of the predicate and re-compiles the expression
/// </summary> /// </summary>
/// <param name="conditionOperator"></param> /// <param name="conditionOperator"></param>
public void UpdateOperator(ConditionOperator conditionOperator) public void UpdateOperator(ConditionOperator? conditionOperator)
{ {
// Calling CreateExpression will clear compiled expressions // Calling CreateExpression will clear compiled expressions
if (conditionOperator == null) if (conditionOperator == null)
{ {
Operator = null; Operator = null;
CreateAccessors();
return; return;
} }
// No need to clear compiled expressions, without a left data model they are already null // No need to clear compiled expressions, without a left data model they are already null
if (LeftDataModel == null) if (LeftPath == null || !LeftPath.IsValid)
{ {
Operator = conditionOperator; Operator = conditionOperator;
return; return;
} }
Type leftType = LeftDataModel.GetTypeAtPath(LeftPropertyPath); Type leftType = LeftPath.GetPropertyType()!;
if (!conditionOperator.SupportsType(leftType)) if (!conditionOperator.SupportsType(leftType))
{
throw new ArtemisCoreException($"Cannot apply operator {conditionOperator.GetType().Name} to this predicate because " + throw new ArtemisCoreException($"Cannot apply operator {conditionOperator.GetType().Name} to this predicate because " +
$"it does not support left side type {leftType.Name}"); $"it does not support left side type {leftType.Name}");
}
Operator = conditionOperator; Operator = conditionOperator;
CreateAccessors();
} }
/// <inheritdoc /> /// <inheritdoc />
public override bool Evaluate() public override bool Evaluate()
{ {
if (Operator == null || LeftSideAccessor == null || PredicateType != ProfileRightSideType.Static && RightSideAccessor == null) if (Operator == null || LeftPath == null || !LeftPath.IsValid)
return false; return false;
// Compare with a static value // Compare with a static value
if (PredicateType == ProfileRightSideType.Static) if (PredicateType == ProfileRightSideType.Static)
{ {
object leftSideValue = LeftSideAccessor(LeftDataModel); object? leftSideValue = LeftPath.GetValue();
if (leftSideValue.GetType().IsValueType && RightStaticValue == null) if (leftSideValue != null && leftSideValue.GetType().IsValueType && RightStaticValue == null)
return false; return false;
return Operator.Evaluate(leftSideValue, RightStaticValue); return Operator.Evaluate(leftSideValue, RightStaticValue);
} }
// Compare with dynamic values if (RightPath == null || !RightPath.IsValid)
if (PredicateType == ProfileRightSideType.Dynamic) return false;
return Operator.Evaluate(LeftSideAccessor(LeftDataModel), RightSideAccessor(RightDataModel));
return false; // Compare with dynamic values
return Operator.Evaluate(LeftPath.GetValue(), RightPath.GetValue());
}
/// <inheritdoc />
public override string ToString()
{
if (PredicateType == ProfileRightSideType.Dynamic)
return $"[Dynamic] {LeftPath} {Operator.Description} {RightPath}";
return $"[Static] {LeftPath} {Operator.Description} {RightStaticValue}";
}
/// <inheritdoc />
protected override void Dispose(bool disposing)
{
ConditionOperatorStore.ConditionOperatorAdded -= ConditionOperatorStoreOnConditionOperatorAdded;
ConditionOperatorStore.ConditionOperatorRemoved -= ConditionOperatorStoreOnConditionOperatorRemoved;
LeftPath?.Dispose();
RightPath?.Dispose();
base.Dispose(disposing);
} }
/// <inheritdoc /> /// <inheritdoc />
@ -224,22 +229,15 @@ namespace Artemis.Core
return false; return false;
} }
/// <inheritdoc />
public override string ToString()
{
if (PredicateType == ProfileRightSideType.Dynamic)
return $"[Dynamic] {LeftPropertyPath} {Operator.Description} {RightPropertyPath}";
return $"[Static] {LeftPropertyPath} {Operator.Description} {RightStaticValue}";
}
internal override void Save() internal override void Save()
{ {
Entity.PredicateType = (int) PredicateType; Entity.PredicateType = (int) PredicateType;
Entity.LeftDataModelGuid = LeftDataModel?.PluginInfo?.Guid;
Entity.LeftPropertyPath = LeftPropertyPath;
Entity.RightDataModelGuid = RightDataModel?.PluginInfo?.Guid; LeftPath?.Save();
Entity.RightPropertyPath = RightPropertyPath; Entity.LeftPath = LeftPath?.Entity;
RightPath?.Save();
Entity.RightPath = RightPath?.Entity;
Entity.RightStaticValue = JsonConvert.SerializeObject(RightStaticValue); Entity.RightStaticValue = JsonConvert.SerializeObject(RightStaticValue);
Entity.OperatorPluginGuid = Operator?.PluginInfo?.Guid; Entity.OperatorPluginGuid = Operator?.PluginInfo?.Guid;
@ -248,44 +246,32 @@ namespace Artemis.Core
internal void Initialize() internal void Initialize()
{ {
DataModelStore.DataModelAdded += DataModelStoreOnDataModelAdded;
DataModelStore.DataModelRemoved += DataModelStoreOnDataModelRemoved;
ConditionOperatorStore.ConditionOperatorAdded += ConditionOperatorStoreOnConditionOperatorAdded; ConditionOperatorStore.ConditionOperatorAdded += ConditionOperatorStoreOnConditionOperatorAdded;
ConditionOperatorStore.ConditionOperatorRemoved += ConditionOperatorStoreOnConditionOperatorRemoved; ConditionOperatorStore.ConditionOperatorRemoved += ConditionOperatorStoreOnConditionOperatorRemoved;
// Left side // Left side
if (Entity.LeftDataModelGuid != null) if (Entity.LeftPath != null) LeftPath = new DataModelPath(null, Entity.LeftPath);
{
DataModel dataModel = DataModelStore.Get(Entity.LeftDataModelGuid.Value)?.DataModel;
if (dataModel != null && dataModel.ContainsPath(Entity.LeftPropertyPath))
UpdateLeftSide(dataModel, Entity.LeftPropertyPath);
}
// Operator // Operator
if (Entity.OperatorPluginGuid != null) 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) if (conditionOperator != null)
UpdateOperator(conditionOperator); UpdateOperator(conditionOperator);
} }
// Right side dynamic // Right side dynamic
if (PredicateType == ProfileRightSideType.Dynamic && Entity.RightDataModelGuid != null) if (PredicateType == ProfileRightSideType.Dynamic && Entity.RightPath != null)
{ RightPath = new DataModelPath(null, Entity.RightPath);
DataModel dataModel = DataModelStore.Get(Entity.RightDataModelGuid.Value)?.DataModel;
if (dataModel != null && dataModel.ContainsPath(Entity.RightPropertyPath))
UpdateRightSide(dataModel, Entity.RightPropertyPath);
}
// Right side static // Right side static
else if (PredicateType == ProfileRightSideType.Static && Entity.RightStaticValue != null) else if (PredicateType == ProfileRightSideType.Static && Entity.RightStaticValue != null)
{
try try
{ {
if (LeftDataModel != null) if (LeftPath != null && LeftPath.IsValid)
{ {
// Use the left side type so JSON.NET has a better idea what to do // Use the left side type so JSON.NET has a better idea what to do
Type leftSideType = LeftDataModel.GetTypeAtPath(LeftPropertyPath); Type leftSideType = LeftPath.GetPropertyType()!;
object rightSideValue; object? rightSideValue;
try try
{ {
@ -311,7 +297,6 @@ namespace Artemis.Core
// ignored // ignored
// TODO: Some logging would be nice // TODO: Some logging would be nice
} }
}
} }
internal override DataModelConditionPartEntity GetEntity() internal override DataModelConditionPartEntity GetEntity()
@ -319,119 +304,47 @@ namespace Artemis.Core
return Entity; return Entity;
} }
private void CreateAccessors()
{
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 == ProfileRightSideType.Dynamic && Operator.SupportsRightSide)
CreateDynamicAccessors();
else
CreateStaticExpression();
}
private void ValidateOperator() private void ValidateOperator()
{ {
if (LeftDataModel == null || Operator == null) if (LeftPath == null || !LeftPath.IsValid || Operator == null)
return; return;
Type leftType = LeftDataModel.GetTypeAtPath(LeftPropertyPath); Type leftType = LeftPath.GetPropertyType()!;
if (!Operator.SupportsType(leftType)) if (!Operator.SupportsType(leftType))
Operator = null; Operator = null;
} }
private void ValidateRightSide() private void ValidateRightSide()
{ {
Type leftSideType = LeftDataModel.GetTypeAtPath(LeftPropertyPath); Type? leftType = LeftPath?.GetPropertyType();
if (PredicateType == ProfileRightSideType.Dynamic) if (PredicateType == ProfileRightSideType.Dynamic)
{ {
if (RightDataModel == null) if (RightPath == null || !RightPath.IsValid)
return; return;
Type rightSideType = RightDataModel.GetTypeAtPath(RightPropertyPath); Type rightSideType = RightPath.GetPropertyType()!;
if (!leftSideType.IsCastableFrom(rightSideType)) if (leftType != null && !leftType.IsCastableFrom(rightSideType))
UpdateRightSide(null, null); UpdateRightSide(null, null);
} }
else else
{ {
if (RightStaticValue != null && leftSideType.IsCastableFrom(RightStaticValue.GetType())) if (RightStaticValue != null && (leftType == null || leftType.IsCastableFrom(RightStaticValue.GetType())))
UpdateRightSide(RightStaticValue); UpdateRightSide(RightStaticValue);
else else
UpdateRightSide(null); UpdateRightSide(null);
} }
} }
private void CreateDynamicAccessors()
{
if (LeftDataModel == null || RightDataModel == null || Operator == null)
return;
Expression leftSideAccessor = ExpressionUtilities.CreateDataModelAccessor(LeftDataModel, LeftPropertyPath, "left", out ParameterExpression 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<Func<object, object>>(leftSideAccessor, leftSideParameter).Compile();
RightSideAccessor = Expression.Lambda<Func<object, object>>(rightSideAccessor, rightSideParameter).Compile();
}
private void CreateStaticExpression()
{
if (LeftDataModel == null || Operator == null)
return;
UnaryExpression leftSideAccessor = Expression.Convert(
ExpressionUtilities.CreateDataModelAccessor(LeftDataModel, LeftPropertyPath, "left", out ParameterExpression leftSideParameter),
typeof(object)
);
// If the left side is a value type but the input is empty, this isn't a valid expression
if (leftSideAccessor.Type.IsValueType && RightStaticValue == null)
return;
LeftSideAccessor = Expression.Lambda<Func<object, object>>(leftSideAccessor, leftSideParameter).Compile();
RightSideAccessor = null;
}
#region Event handlers #region Event handlers
private void DataModelStoreOnDataModelAdded(object sender, DataModelStoreEvent e) private void ConditionOperatorStoreOnConditionOperatorAdded(object? sender, ConditionOperatorStoreEvent e)
{
DataModel dataModel = e.Registration.DataModel;
if (dataModel.PluginInfo.Guid == Entity.LeftDataModelGuid && dataModel.ContainsPath(Entity.LeftPropertyPath))
UpdateLeftSide(dataModel, Entity.LeftPropertyPath);
if (dataModel.PluginInfo.Guid == Entity.RightDataModelGuid && dataModel.ContainsPath(Entity.RightPropertyPath))
UpdateRightSide(dataModel, Entity.RightPropertyPath);
}
private void DataModelStoreOnDataModelRemoved(object sender, DataModelStoreEvent e)
{
if (LeftDataModel == e.Registration.DataModel)
{
LeftSideAccessor = null;
LeftDataModel = null;
}
if (RightDataModel == e.Registration.DataModel)
{
RightSideAccessor = null;
RightDataModel = null;
}
}
private void ConditionOperatorStoreOnConditionOperatorAdded(object sender, ConditionOperatorStoreEvent e)
{ {
ConditionOperator conditionOperator = e.Registration.ConditionOperator; ConditionOperator conditionOperator = e.Registration.ConditionOperator;
if (Entity.OperatorPluginGuid == conditionOperator.PluginInfo.Guid && Entity.OperatorType == conditionOperator.GetType().Name) if (Entity.OperatorPluginGuid == conditionOperator.PluginInfo.Guid && Entity.OperatorType == conditionOperator.GetType().Name)
UpdateOperator(conditionOperator); UpdateOperator(conditionOperator);
} }
private void ConditionOperatorStoreOnConditionOperatorRemoved(object sender, ConditionOperatorStoreEvent e) private void ConditionOperatorStoreOnConditionOperatorRemoved(object? sender, ConditionOperatorStoreEvent e)
{ {
if (e.Registration.ConditionOperator != Operator) if (e.Registration.ConditionOperator != Operator)
return; return;
@ -440,16 +353,5 @@ namespace Artemis.Core
} }
#endregion #endregion
/// <inheritdoc />
protected override void Dispose(bool disposing)
{
DataModelStore.DataModelAdded -= DataModelStoreOnDataModelAdded;
DataModelStore.DataModelRemoved -= DataModelStoreOnDataModelRemoved;
ConditionOperatorStore.ConditionOperatorAdded -= ConditionOperatorStoreOnConditionOperatorAdded;
ConditionOperatorStore.ConditionOperatorRemoved -= ConditionOperatorStoreOnConditionOperatorRemoved;
base.Dispose(disposing);
}
} }
} }

View File

@ -56,7 +56,7 @@ namespace Artemis.Core
SubscribeToDataModelStore(); SubscribeToDataModelStore();
} }
internal DataModelPath(object target, DataModelPathEntity entity) internal DataModelPath(object? target, DataModelPathEntity entity)
{ {
Target = target!; Target = target!;
Path = entity.Path; Path = entity.Path;

View File

@ -11,9 +11,7 @@ namespace Artemis.Storage.Entities.Profile.Conditions
Children = new List<DataModelConditionPartEntity>(); Children = new List<DataModelConditionPartEntity>();
} }
public Guid? ListDataModelGuid { get; set; } public DataModelPathEntity ListPath { get; set; }
public string ListPropertyPath { get; set; }
public int ListOperator { get; set; } public int ListOperator { get; set; }
} }
} }

View File

@ -6,16 +6,14 @@ namespace Artemis.Storage.Entities.Profile.Conditions
public class DataModelConditionPredicateEntity : DataModelConditionPartEntity public class DataModelConditionPredicateEntity : DataModelConditionPartEntity
{ {
public int PredicateType { get; set; } public int PredicateType { get; set; }
public Guid? LeftDataModelGuid { get; set; } public DataModelPathEntity LeftPath { get; set; }
public string LeftPropertyPath { get; set; } public DataModelPathEntity RightPath { get; set; }
public Guid? RightDataModelGuid { get; set; }
public string RightPropertyPath { get; set; }
public string OperatorType { get; set; } public string OperatorType { get; set; }
public Guid? OperatorPluginGuid { get; set; } public Guid? OperatorPluginGuid { get; set; }
// Stored as a string to be able to control serialization and deserialization ourselves // Stored as a string to be able to control serialization and deserialization ourselves
public string RightStaticValue { get; set; } public string RightStaticValue { get; set; }
} }
} }

View File

@ -50,7 +50,7 @@
&lt;/Entry.SortBy&gt;&#xD; &lt;/Entry.SortBy&gt;&#xD;
&lt;/Entry&gt;&#xD; &lt;/Entry&gt;&#xD;
&lt;Entry DisplayName="All other members" /&gt;&#xD; &lt;Entry DisplayName="All other members" /&gt;&#xD;
&lt;Entry DisplayName="Test Methods" Priority="100"&gt;&#xD; &lt;Entry Priority="100" DisplayName="Test Methods"&gt;&#xD;
&lt;Entry.Match&gt;&#xD; &lt;Entry.Match&gt;&#xD;
&lt;And&gt;&#xD; &lt;And&gt;&#xD;
&lt;Kind Is="Method" /&gt;&#xD; &lt;Kind Is="Method" /&gt;&#xD;
@ -97,7 +97,7 @@
&lt;/Entry.Match&gt;&#xD; &lt;/Entry.Match&gt;&#xD;
&lt;/Entry&gt;&#xD; &lt;/Entry&gt;&#xD;
&lt;Entry DisplayName="All other members" /&gt;&#xD; &lt;Entry DisplayName="All other members" /&gt;&#xD;
&lt;Entry DisplayName="Test Methods" Priority="100"&gt;&#xD; &lt;Entry Priority="100" DisplayName="Test Methods"&gt;&#xD;
&lt;Entry.Match&gt;&#xD; &lt;Entry.Match&gt;&#xD;
&lt;And&gt;&#xD; &lt;And&gt;&#xD;
&lt;Kind Is="Method" /&gt;&#xD; &lt;Kind Is="Method" /&gt;&#xD;
@ -112,7 +112,7 @@
&lt;/Entry&gt;&#xD; &lt;/Entry&gt;&#xD;
&lt;/TypePattern&gt;&#xD; &lt;/TypePattern&gt;&#xD;
&lt;TypePattern DisplayName="Default Pattern"&gt;&#xD; &lt;TypePattern DisplayName="Default Pattern"&gt;&#xD;
&lt;Entry DisplayName="Public Delegates" Priority="100"&gt;&#xD; &lt;Entry Priority="100" DisplayName="Public Delegates"&gt;&#xD;
&lt;Entry.Match&gt;&#xD; &lt;Entry.Match&gt;&#xD;
&lt;And&gt;&#xD; &lt;And&gt;&#xD;
&lt;Access Is="Public" /&gt;&#xD; &lt;Access Is="Public" /&gt;&#xD;
@ -123,7 +123,7 @@
&lt;Name /&gt;&#xD; &lt;Name /&gt;&#xD;
&lt;/Entry.SortBy&gt;&#xD; &lt;/Entry.SortBy&gt;&#xD;
&lt;/Entry&gt;&#xD; &lt;/Entry&gt;&#xD;
&lt;Entry DisplayName="Public Enums" Priority="100"&gt;&#xD; &lt;Entry Priority="100" DisplayName="Public Enums"&gt;&#xD;
&lt;Entry.Match&gt;&#xD; &lt;Entry.Match&gt;&#xD;
&lt;And&gt;&#xD; &lt;And&gt;&#xD;
&lt;Access Is="Public" /&gt;&#xD; &lt;Access Is="Public" /&gt;&#xD;
@ -191,7 +191,7 @@
&lt;Kind Is="Type" /&gt;&#xD; &lt;Kind Is="Type" /&gt;&#xD;
&lt;/Entry.Match&gt;&#xD; &lt;/Entry.Match&gt;&#xD;
&lt;/Entry&gt;&#xD; &lt;/Entry&gt;&#xD;
&lt;Entry DisplayName="Interface Implementations" Priority="100"&gt;&#xD; &lt;Entry Priority="100" DisplayName="Interface Implementations"&gt;&#xD;
&lt;Entry.Match&gt;&#xD; &lt;Entry.Match&gt;&#xD;
&lt;And&gt;&#xD; &lt;And&gt;&#xD;
&lt;Kind Is="Member" /&gt;&#xD; &lt;Kind Is="Member" /&gt;&#xD;
@ -206,4 +206,9 @@
&lt;/Patterns&gt;</s:String> &lt;/Patterns&gt;</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=SuggestVarOrType_005FElsewhere/@EntryIndexedValue">ERROR</s:String> <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=SuggestVarOrType_005FElsewhere/@EntryIndexedValue">ERROR</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=SuggestVarOrType_005FSimpleTypes/@EntryIndexedValue">ERROR</s:String> <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=SuggestVarOrType_005FSimpleTypes/@EntryIndexedValue">ERROR</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=SuggestVarOrType_005FBuiltInTypes/@EntryIndexedValue">ERROR</s:String></wpf:ResourceDictionary> <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=SuggestVarOrType_005FBuiltInTypes/@EntryIndexedValue">ERROR</s:String>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpKeepExistingMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpPlaceEmbeddedOnSameLineMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpUseContinuousIndentInsideBracesMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EAlwaysTreatStructAsNotReorderableMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>