mirror of
https://github.com/Artemis-RGB/Artemis
synced 2026-11-11 19:36:28 +00:00
Display conditions - Seperate list conditions into their own type
This commit is contained in:
1 parent
f2f77da953
commit
f359256ede
22 files changed
+692
-218
No files matched your search
@@ -12,19 +12,21 @@ namespace Artemis.Core.Models.Profile.Conditions
|
||||
public DisplayConditionGroup(DisplayConditionPart parent)
|
||||
{
|
||||
Parent = parent;
|
||||
DisplayConditionGroupEntity = new DisplayConditionGroupEntity();
|
||||
Entity = new DisplayConditionGroupEntity();
|
||||
}
|
||||
|
||||
public DisplayConditionGroup(DisplayConditionPart parent, DisplayConditionGroupEntity entity)
|
||||
{
|
||||
Parent = parent;
|
||||
DisplayConditionGroupEntity = entity;
|
||||
BooleanOperator = (BooleanOperator) DisplayConditionGroupEntity.BooleanOperator;
|
||||
Entity = entity;
|
||||
BooleanOperator = (BooleanOperator) Entity.BooleanOperator;
|
||||
|
||||
foreach (var childEntity in DisplayConditionGroupEntity.Children)
|
||||
foreach (var childEntity in Entity.Children)
|
||||
{
|
||||
if (childEntity is DisplayConditionGroupEntity groupEntity)
|
||||
AddChild(new DisplayConditionGroup(this, groupEntity));
|
||||
else if (childEntity is DisplayConditionListEntity listEntity)
|
||||
AddChild(new DisplayConditionList(this, listEntity));
|
||||
else if (childEntity is DisplayConditionPredicateEntity predicateEntity)
|
||||
AddChild(new DisplayConditionPredicate(this, predicateEntity));
|
||||
else if (childEntity is DisplayConditionListPredicateEntity listPredicateEntity)
|
||||
@@ -33,7 +35,7 @@ namespace Artemis.Core.Models.Profile.Conditions
|
||||
}
|
||||
|
||||
public BooleanOperator BooleanOperator { get; set; }
|
||||
public DisplayConditionGroupEntity DisplayConditionGroupEntity { get; set; }
|
||||
public DisplayConditionGroupEntity Entity { get; set; }
|
||||
|
||||
public override bool Evaluate()
|
||||
{
|
||||
@@ -80,10 +82,10 @@ namespace Artemis.Core.Models.Profile.Conditions
|
||||
|
||||
internal override void ApplyToEntity()
|
||||
{
|
||||
DisplayConditionGroupEntity.BooleanOperator = (int) BooleanOperator;
|
||||
Entity.BooleanOperator = (int) BooleanOperator;
|
||||
|
||||
DisplayConditionGroupEntity.Children.Clear();
|
||||
DisplayConditionGroupEntity.Children.AddRange(Children.Select(c => c.GetEntity()));
|
||||
Entity.Children.Clear();
|
||||
Entity.Children.AddRange(Children.Select(c => c.GetEntity()));
|
||||
foreach (var child in Children)
|
||||
child.ApplyToEntity();
|
||||
}
|
||||
@@ -96,7 +98,7 @@ namespace Artemis.Core.Models.Profile.Conditions
|
||||
|
||||
internal override DisplayConditionPartEntity GetEntity()
|
||||
{
|
||||
return DisplayConditionGroupEntity;
|
||||
return Entity;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using Artemis.Core.Exceptions;
|
||||
using Artemis.Core.Models.Profile.Conditions.Abstract;
|
||||
using Artemis.Core.Plugins.Abstract.DataModels;
|
||||
using Artemis.Core.Services.Interfaces;
|
||||
using Artemis.Storage.Entities.Profile;
|
||||
using Artemis.Storage.Entities.Profile.Abstract;
|
||||
|
||||
namespace Artemis.Core.Models.Profile.Conditions
|
||||
{
|
||||
public class DisplayConditionList : DisplayConditionPart
|
||||
{
|
||||
public DisplayConditionList(DisplayConditionPart parent)
|
||||
{
|
||||
Parent = parent;
|
||||
Entity = new DisplayConditionListEntity();
|
||||
|
||||
// There is always a child root group, add it
|
||||
AddChild(new DisplayConditionGroup(this));
|
||||
}
|
||||
|
||||
public DisplayConditionList(DisplayConditionPart parent, DisplayConditionListEntity entity)
|
||||
{
|
||||
Parent = parent;
|
||||
Entity = entity;
|
||||
ListOperator = (ListOperator) entity.ListOperator;
|
||||
|
||||
// There should only be one child and it should be a group
|
||||
var rootGroup = Entity.Children.SingleOrDefault() as DisplayConditionGroupEntity;
|
||||
if (rootGroup == null)
|
||||
{
|
||||
Entity.Children.Clear();
|
||||
AddChild(new DisplayConditionGroup(this));
|
||||
}
|
||||
else
|
||||
AddChild(new DisplayConditionGroup(this, rootGroup));
|
||||
}
|
||||
|
||||
public DisplayConditionListEntity Entity { get; set; }
|
||||
|
||||
public ListOperator ListOperator { get; set; }
|
||||
public DataModel ListDataModel { get; private set; }
|
||||
public string ListPropertyPath { get; private set; }
|
||||
|
||||
public override bool Evaluate()
|
||||
{
|
||||
if (CompiledListAccessor == null)
|
||||
return false;
|
||||
|
||||
return EvaluateObject(CompiledListAccessor(ListDataModel));
|
||||
}
|
||||
|
||||
public override bool EvaluateObject(object target)
|
||||
{
|
||||
if (!(target is IList list))
|
||||
return false;
|
||||
|
||||
var objectList = list.Cast<object>();
|
||||
return ListOperator switch
|
||||
{
|
||||
ListOperator.Any => objectList.Any(o => Children[0].EvaluateObject(o)),
|
||||
ListOperator.All => objectList.All(o => Children[0].EvaluateObject(o)),
|
||||
ListOperator.None => objectList.Any(o => !Children[0].EvaluateObject(o)),
|
||||
ListOperator.Count => false,
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
}
|
||||
|
||||
internal override void ApplyToEntity()
|
||||
{
|
||||
// Target list
|
||||
Entity.ListDataModelGuid = ListDataModel?.PluginInfo?.Guid;
|
||||
Entity.ListPropertyPath = ListPropertyPath;
|
||||
|
||||
// Operator
|
||||
Entity.ListOperator = (int) ListOperator;
|
||||
|
||||
// Children
|
||||
Entity.Children.Clear();
|
||||
Entity.Children.AddRange(Children.Select(c => c.GetEntity()));
|
||||
foreach (var child in Children)
|
||||
child.ApplyToEntity();
|
||||
}
|
||||
|
||||
internal override DisplayConditionPartEntity GetEntity()
|
||||
{
|
||||
return Entity;
|
||||
}
|
||||
|
||||
internal override void Initialize(IDataModelService dataModelService)
|
||||
{
|
||||
// Target list
|
||||
if (Entity.ListDataModelGuid != null)
|
||||
{
|
||||
var dataModel = dataModelService.GetPluginDataModelByGuid(Entity.ListDataModelGuid.Value);
|
||||
if (dataModel != null && dataModel.ContainsPath(Entity.ListPropertyPath))
|
||||
UpdateList(dataModel, Entity.ListPropertyPath);
|
||||
}
|
||||
|
||||
// Children
|
||||
var rootGroup = (DisplayConditionGroup) Children.Single();
|
||||
rootGroup.Initialize(dataModelService);
|
||||
}
|
||||
|
||||
public void UpdateList(DataModel dataModel, string path)
|
||||
{
|
||||
if (dataModel != null && path == null)
|
||||
throw new ArtemisCoreException("If a data model is provided, a path is also required");
|
||||
if (dataModel == null && path != null)
|
||||
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}'");
|
||||
if (dataModel.GetListTypeAtPath(path) == null)
|
||||
throw new ArtemisCoreException($"The path '{path}' does not contain a list");
|
||||
}
|
||||
|
||||
ListDataModel = dataModel;
|
||||
ListPropertyPath = path;
|
||||
|
||||
if (dataModel != null)
|
||||
{
|
||||
var parameter = Expression.Parameter(typeof(object), "listDataModel");
|
||||
var accessor = path.Split('.').Aggregate<string, Expression>(
|
||||
Expression.Convert(parameter, dataModel.GetType()),
|
||||
(expression, s) => Expression.Convert(Expression.Property(expression, s), typeof(IList)));
|
||||
|
||||
var lambda = Expression.Lambda<Func<object, IList>>(accessor, parameter);
|
||||
CompiledListAccessor = lambda.Compile();
|
||||
}
|
||||
}
|
||||
|
||||
public Func<object, IList> CompiledListAccessor { get; set; }
|
||||
}
|
||||
|
||||
public enum ListOperator
|
||||
{
|
||||
Any,
|
||||
All,
|
||||
None,
|
||||
Count
|
||||
}
|
||||
}
|
||||
@@ -1,106 +1,47 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using Artemis.Core.Exceptions;
|
||||
using Artemis.Core.Extensions;
|
||||
using Artemis.Core.Models.Profile.Conditions.Abstract;
|
||||
using Artemis.Core.Plugins.Abstract.DataModels;
|
||||
using Artemis.Core.Services.Interfaces;
|
||||
using Artemis.Storage.Entities.Profile;
|
||||
using Artemis.Storage.Entities.Profile.Abstract;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Artemis.Core.Models.Profile.Conditions
|
||||
{
|
||||
public class DisplayConditionListPredicate : DisplayConditionPart
|
||||
{
|
||||
public DisplayConditionListPredicate(DisplayConditionPart parent)
|
||||
public DisplayConditionListPredicate(DisplayConditionPart parent, PredicateType predicateType)
|
||||
{
|
||||
Parent = parent;
|
||||
DisplayConditionListPredicateEntity = new DisplayConditionListPredicateEntity();
|
||||
|
||||
// There is always a child root group, add it
|
||||
AddChild(new DisplayConditionGroup(this));
|
||||
PredicateType = predicateType;
|
||||
Entity = new DisplayConditionListPredicateEntity();
|
||||
}
|
||||
|
||||
public DisplayConditionListPredicate(DisplayConditionPart parent, DisplayConditionListPredicateEntity entity)
|
||||
{
|
||||
Parent = parent;
|
||||
DisplayConditionListPredicateEntity = entity;
|
||||
ListOperator = (ListOperator) entity.ListOperator;
|
||||
|
||||
// There should only be one child and it should be a group
|
||||
var rootGroup = DisplayConditionListPredicateEntity.Children.SingleOrDefault() as DisplayConditionGroupEntity;
|
||||
if (rootGroup == null)
|
||||
{
|
||||
DisplayConditionListPredicateEntity.Children.Clear();
|
||||
AddChild(new DisplayConditionGroup(this));
|
||||
}
|
||||
else
|
||||
AddChild(new DisplayConditionGroup(this, rootGroup));
|
||||
Entity = entity;
|
||||
PredicateType = (PredicateType) entity.PredicateType;
|
||||
}
|
||||
|
||||
public DisplayConditionListPredicateEntity DisplayConditionListPredicateEntity { get; set; }
|
||||
public DisplayConditionListPredicateEntity Entity { get; set; }
|
||||
|
||||
public ListOperator ListOperator { get; set; }
|
||||
public PredicateType PredicateType { get; set; }
|
||||
public DisplayConditionOperator Operator { get; private set; }
|
||||
|
||||
public Type ListType { get; private set; }
|
||||
public DataModel ListDataModel { get; private set; }
|
||||
public string ListPropertyPath { get; private set; }
|
||||
|
||||
public override bool Evaluate()
|
||||
{
|
||||
return EvaluateObject(CompiledListAccessor(ListDataModel));
|
||||
}
|
||||
public string LeftPropertyPath { get; private set; }
|
||||
public string RightPropertyPath { get; private set; }
|
||||
public object RightStaticValue { get; private set; }
|
||||
|
||||
public override bool EvaluateObject(object target)
|
||||
{
|
||||
if (!(target is IList list))
|
||||
return false;
|
||||
|
||||
var objectList = list.Cast<object>();
|
||||
return ListOperator switch
|
||||
{
|
||||
ListOperator.Any => objectList.Any(o => Children[0].EvaluateObject(o)),
|
||||
ListOperator.All => objectList.All(o => Children[0].EvaluateObject(o)),
|
||||
ListOperator.None => objectList.Any(o => !Children[0].EvaluateObject(o)),
|
||||
ListOperator.Count => false,
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
}
|
||||
|
||||
internal override void ApplyToEntity()
|
||||
{
|
||||
// Target list
|
||||
DisplayConditionListPredicateEntity.ListDataModelGuid = ListDataModel?.PluginInfo?.Guid;
|
||||
DisplayConditionListPredicateEntity.ListPropertyPath = ListPropertyPath;
|
||||
|
||||
// Operator
|
||||
DisplayConditionListPredicateEntity.ListOperator = (int) ListOperator;
|
||||
|
||||
// Children
|
||||
DisplayConditionListPredicateEntity.Children.Clear();
|
||||
DisplayConditionListPredicateEntity.Children.AddRange(Children.Select(c => c.GetEntity()));
|
||||
foreach (var child in Children)
|
||||
child.ApplyToEntity();
|
||||
}
|
||||
|
||||
internal override DisplayConditionPartEntity GetEntity()
|
||||
{
|
||||
return DisplayConditionListPredicateEntity;
|
||||
}
|
||||
|
||||
internal override void Initialize(IDataModelService dataModelService)
|
||||
{
|
||||
// Target list
|
||||
if (DisplayConditionListPredicateEntity.ListDataModelGuid != null)
|
||||
{
|
||||
var dataModel = dataModelService.GetPluginDataModelByGuid(DisplayConditionListPredicateEntity.ListDataModelGuid.Value);
|
||||
if (dataModel != null && dataModel.ContainsPath(DisplayConditionListPredicateEntity.ListPropertyPath))
|
||||
UpdateList(dataModel, DisplayConditionListPredicateEntity.ListPropertyPath);
|
||||
}
|
||||
|
||||
// Children
|
||||
var rootGroup = (DisplayConditionGroup) Children.Single();
|
||||
rootGroup.Initialize(dataModelService);
|
||||
}
|
||||
public Func<object, bool> CompiledListPredicate { get; private set; }
|
||||
|
||||
public void UpdateList(DataModel dataModel, string path)
|
||||
{
|
||||
@@ -111,35 +52,322 @@ namespace Artemis.Core.Models.Profile.Conditions
|
||||
|
||||
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}'");
|
||||
if (dataModel.GetListTypeAtPath(path) == null)
|
||||
throw new ArtemisCoreException($"The path '{path}' does not contain a list");
|
||||
var listType = dataModel.GetListTypeAtPath(path);
|
||||
if (listType == null)
|
||||
throw new ArtemisCoreException($"Data model of type {dataModel.GetType().Name} does not contain a list at path '{path}'");
|
||||
|
||||
ListType = listType;
|
||||
}
|
||||
else
|
||||
{
|
||||
ListType = null;
|
||||
}
|
||||
|
||||
ListDataModel = dataModel;
|
||||
ListPropertyPath = path;
|
||||
|
||||
if (dataModel != null)
|
||||
{
|
||||
var parameter = Expression.Parameter(typeof(object), "listDataModel");
|
||||
var accessor = path.Split('.').Aggregate<string, Expression>(
|
||||
Expression.Convert(parameter, dataModel.GetType()),
|
||||
(expression, s) => Expression.Convert(Expression.Property(expression, s), typeof(IList)));
|
||||
if (!ListContainsInnerPath(LeftPropertyPath))
|
||||
LeftPropertyPath = null;
|
||||
if (!ListContainsInnerPath(RightPropertyPath))
|
||||
RightPropertyPath = null;
|
||||
|
||||
var lambda = Expression.Lambda<Func<object, IList>>(accessor, parameter);
|
||||
CompiledListAccessor = lambda.Compile();
|
||||
CreateExpression();
|
||||
}
|
||||
|
||||
public void UpdateLeftSide(string path)
|
||||
{
|
||||
if (!ListContainsInnerPath(path))
|
||||
throw new ArtemisCoreException($"List type {ListType.Name} does not contain path {path}");
|
||||
|
||||
LeftPropertyPath = path;
|
||||
|
||||
ValidateOperator();
|
||||
ValidateRightSide();
|
||||
CreateExpression();
|
||||
}
|
||||
|
||||
public void UpdateRightSideDynamic(string path)
|
||||
{
|
||||
if (!ListContainsInnerPath(path))
|
||||
throw new ArtemisCoreException($"List type {ListType.Name} does not contain path {path}");
|
||||
|
||||
PredicateType = PredicateType.Dynamic;
|
||||
RightPropertyPath = path;
|
||||
|
||||
CreateExpression();
|
||||
}
|
||||
|
||||
public void UpdateRightSideStatic(object staticValue)
|
||||
{
|
||||
PredicateType = PredicateType.Static;
|
||||
RightPropertyPath = null;
|
||||
|
||||
SetStaticValue(staticValue);
|
||||
CreateExpression();
|
||||
}
|
||||
|
||||
public void UpdateOperator(DisplayConditionOperator displayConditionOperator)
|
||||
{
|
||||
if (displayConditionOperator == null)
|
||||
{
|
||||
Operator = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (LeftPropertyPath == null)
|
||||
{
|
||||
Operator = displayConditionOperator;
|
||||
return;
|
||||
}
|
||||
|
||||
var leftType = GetTypeAtInnerPath(LeftPropertyPath);
|
||||
if (displayConditionOperator.SupportsType(leftType))
|
||||
Operator = displayConditionOperator;
|
||||
|
||||
CreateExpression();
|
||||
}
|
||||
|
||||
private void CreateExpression()
|
||||
{
|
||||
CompiledListPredicate = null;
|
||||
|
||||
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 == PredicateType.Dynamic && Operator.SupportsRightSide)
|
||||
CreateDynamicExpression();
|
||||
|
||||
CreateStaticExpression();
|
||||
}
|
||||
|
||||
internal override void ApplyToEntity()
|
||||
{
|
||||
Entity.PredicateType = (int) PredicateType;
|
||||
Entity.ListDataModelGuid = ListDataModel?.PluginInfo?.Guid;
|
||||
Entity.ListPropertyPath = ListPropertyPath;
|
||||
|
||||
Entity.LeftPropertyPath = LeftPropertyPath;
|
||||
Entity.RightPropertyPath = RightPropertyPath;
|
||||
Entity.RightStaticValue = JsonConvert.SerializeObject(RightStaticValue);
|
||||
|
||||
Entity.OperatorPluginGuid = Operator?.PluginInfo?.Guid;
|
||||
Entity.OperatorType = Operator?.GetType().Name;
|
||||
}
|
||||
|
||||
public override bool Evaluate()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public override bool EvaluateObject(object target)
|
||||
{
|
||||
return CompiledListPredicate != null && CompiledListPredicate(target);
|
||||
}
|
||||
|
||||
internal override void Initialize(IDataModelService dataModelService)
|
||||
{
|
||||
// Left side
|
||||
if (Entity.LeftPropertyPath != null && ListContainsInnerPath(Entity.LeftPropertyPath))
|
||||
UpdateLeftSide(Entity.LeftPropertyPath);
|
||||
|
||||
// Operator
|
||||
if (Entity.OperatorPluginGuid != null)
|
||||
{
|
||||
var conditionOperator = dataModelService.GetConditionOperator(Entity.OperatorPluginGuid.Value, Entity.OperatorType);
|
||||
if (conditionOperator != null)
|
||||
UpdateOperator(conditionOperator);
|
||||
}
|
||||
|
||||
// Right side dynamic
|
||||
if (PredicateType == PredicateType.Dynamic && Entity.RightPropertyPath != null)
|
||||
{
|
||||
if (ListContainsInnerPath(Entity.RightPropertyPath))
|
||||
UpdateLeftSide(Entity.LeftPropertyPath);
|
||||
}
|
||||
// Right side static
|
||||
else if (PredicateType == PredicateType.Static && Entity.RightStaticValue != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (LeftPropertyPath != null)
|
||||
{
|
||||
// Use the left side type so JSON.NET has a better idea what to do
|
||||
var leftSideType = GetTypeAtInnerPath(LeftPropertyPath);
|
||||
object rightSideValue;
|
||||
|
||||
try
|
||||
{
|
||||
rightSideValue = JsonConvert.DeserializeObject(Entity.RightStaticValue, leftSideType);
|
||||
}
|
||||
// If deserialization fails, use the type's default
|
||||
catch (JsonSerializationException e)
|
||||
{
|
||||
dataModelService.LogListPredicateDeserializationFailure(this, e);
|
||||
rightSideValue = Activator.CreateInstance(leftSideType);
|
||||
}
|
||||
|
||||
UpdateRightSideStatic(rightSideValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Hope for the best... we must infer the type from JSON now
|
||||
UpdateRightSideStatic(JsonConvert.DeserializeObject(Entity.RightStaticValue));
|
||||
}
|
||||
}
|
||||
catch (JsonException e)
|
||||
{
|
||||
dataModelService.LogListPredicateDeserializationFailure(this, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Func<object, IList> CompiledListAccessor { get; set; }
|
||||
}
|
||||
internal override DisplayConditionPartEntity GetEntity()
|
||||
{
|
||||
return Entity;
|
||||
}
|
||||
|
||||
public enum ListOperator
|
||||
{
|
||||
Any,
|
||||
All,
|
||||
None,
|
||||
Count
|
||||
private void ValidateOperator()
|
||||
{
|
||||
if (LeftPropertyPath == null || Operator == null)
|
||||
return;
|
||||
|
||||
var leftSideType = GetTypeAtInnerPath(LeftPropertyPath);
|
||||
if (!Operator.SupportsType(leftSideType))
|
||||
Operator = null;
|
||||
}
|
||||
|
||||
private void ValidateRightSide()
|
||||
{
|
||||
var leftSideType = GetTypeAtInnerPath(LeftPropertyPath);
|
||||
if (PredicateType == PredicateType.Dynamic)
|
||||
{
|
||||
if (RightPropertyPath == null)
|
||||
return;
|
||||
|
||||
var rightSideType = GetTypeAtInnerPath(RightPropertyPath);
|
||||
if (!leftSideType.IsCastableFrom(rightSideType))
|
||||
UpdateRightSideDynamic(null);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (RightStaticValue != null && leftSideType.IsCastableFrom(RightStaticValue.GetType()))
|
||||
UpdateRightSideStatic(RightStaticValue);
|
||||
else
|
||||
UpdateRightSideStatic(null);
|
||||
}
|
||||
}
|
||||
|
||||
private void SetStaticValue(object staticValue)
|
||||
{
|
||||
// If the left side is empty simply apply the value, any validation will wait
|
||||
if (LeftPropertyPath == null)
|
||||
{
|
||||
RightStaticValue = staticValue;
|
||||
return;
|
||||
}
|
||||
|
||||
var leftSideType = GetTypeAtInnerPath(LeftPropertyPath);
|
||||
|
||||
// If not null ensure the types match and if not, convert it
|
||||
if (staticValue != null && staticValue.GetType() == leftSideType)
|
||||
RightStaticValue = staticValue;
|
||||
else if (staticValue != null)
|
||||
RightStaticValue = Convert.ChangeType(staticValue, leftSideType);
|
||||
// If null create a default instance for value types or simply make it null for reference types
|
||||
else if (leftSideType.IsValueType)
|
||||
RightStaticValue = Activator.CreateInstance(leftSideType);
|
||||
else
|
||||
RightStaticValue = null;
|
||||
}
|
||||
|
||||
private void CreateDynamicExpression()
|
||||
{
|
||||
if (LeftPropertyPath == null || RightPropertyPath == null || Operator == null)
|
||||
return;
|
||||
|
||||
// List accessors share the same parameter because a list always contains one item per entry
|
||||
var leftSideParameter = Expression.Parameter(typeof(object), "listItem");
|
||||
var leftSideAccessor = CreateListAccessor(LeftPropertyPath, leftSideParameter);
|
||||
var rightSideAccessor = CreateListAccessor(RightPropertyPath, leftSideParameter);
|
||||
|
||||
// A conversion may be required if the types differ
|
||||
// This can cause issues if the DisplayConditionOperator 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);
|
||||
|
||||
var conditionExpression = Operator.CreateExpression(leftSideAccessor, rightSideAccessor);
|
||||
var lambda = Expression.Lambda<Func<object, bool>>(conditionExpression, leftSideParameter);
|
||||
CompiledListPredicate = lambda.Compile();
|
||||
}
|
||||
|
||||
private void CreateStaticExpression()
|
||||
{
|
||||
if (LeftPropertyPath == null || Operator == null)
|
||||
return;
|
||||
|
||||
// List accessors share the same parameter because a list always contains one item per entry
|
||||
var leftSideParameter = Expression.Parameter(typeof(object), "listItem");
|
||||
var leftSideAccessor = CreateListAccessor(LeftPropertyPath, leftSideParameter);
|
||||
|
||||
// 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;
|
||||
|
||||
// If the right side value is null, the constant type cannot be inferred and must be provided manually
|
||||
var rightSideConstant = RightStaticValue != null
|
||||
? Expression.Constant(RightStaticValue)
|
||||
: Expression.Constant(null, leftSideAccessor.Type);
|
||||
|
||||
var conditionExpression = Operator.CreateExpression(leftSideAccessor, rightSideConstant);
|
||||
var lambda = Expression.Lambda<Func<object, bool>>(conditionExpression, leftSideParameter);
|
||||
CompiledListPredicate = lambda.Compile();
|
||||
}
|
||||
|
||||
private Expression CreateListAccessor(string path, ParameterExpression listParameter)
|
||||
{
|
||||
return path.Split('.').Aggregate<string, Expression>(
|
||||
Expression.Convert(listParameter, ListType), // Cast to the appropriate type
|
||||
Expression.Property
|
||||
);
|
||||
}
|
||||
|
||||
public bool ListContainsInnerPath(string path)
|
||||
{
|
||||
if (ListType == null)
|
||||
return false;
|
||||
|
||||
var parts = path.Split('.');
|
||||
var current = ListType;
|
||||
foreach (var part in parts)
|
||||
{
|
||||
var property = current.GetProperty(part);
|
||||
current = property?.PropertyType;
|
||||
|
||||
if (property == null)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public Type GetTypeAtInnerPath(string path)
|
||||
{
|
||||
if (!ListContainsInnerPath(path))
|
||||
return null;
|
||||
|
||||
var parts = path.Split('.');
|
||||
var current = ListType;
|
||||
|
||||
Type result = null;
|
||||
foreach (var part in parts)
|
||||
{
|
||||
var property = current.GetProperty(part);
|
||||
current = property.PropertyType;
|
||||
result = property.PropertyType;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,17 +18,17 @@ namespace Artemis.Core.Models.Profile.Conditions
|
||||
{
|
||||
Parent = parent;
|
||||
PredicateType = predicateType;
|
||||
DisplayConditionPredicateEntity = new DisplayConditionPredicateEntity();
|
||||
Entity = new DisplayConditionPredicateEntity();
|
||||
}
|
||||
|
||||
public DisplayConditionPredicate(DisplayConditionPart parent, DisplayConditionPredicateEntity entity)
|
||||
{
|
||||
Parent = parent;
|
||||
DisplayConditionPredicateEntity = entity;
|
||||
Entity = entity;
|
||||
PredicateType = (PredicateType) entity.PredicateType;
|
||||
}
|
||||
|
||||
public DisplayConditionPredicateEntity DisplayConditionPredicateEntity { get; set; }
|
||||
public DisplayConditionPredicateEntity Entity { get; set; }
|
||||
|
||||
public PredicateType PredicateType { get; set; }
|
||||
public DisplayConditionOperator Operator { get; private set; }
|
||||
@@ -38,6 +38,8 @@ namespace Artemis.Core.Models.Profile.Conditions
|
||||
public DataModel RightDataModel { get; private set; }
|
||||
public string RightPropertyPath { get; private set; }
|
||||
public object RightStaticValue { get; private set; }
|
||||
public DataModel ListDataModel { get; private set; }
|
||||
public string ListPropertyPath { get; private set; }
|
||||
|
||||
public Func<DataModel, DataModel, bool> CompiledDynamicPredicate { get; private set; }
|
||||
public Func<DataModel, bool> CompiledStaticPredicate { get; private set; }
|
||||
@@ -135,16 +137,16 @@ namespace Artemis.Core.Models.Profile.Conditions
|
||||
|
||||
internal override void ApplyToEntity()
|
||||
{
|
||||
DisplayConditionPredicateEntity.PredicateType = (int) PredicateType;
|
||||
DisplayConditionPredicateEntity.LeftDataModelGuid = LeftDataModel?.PluginInfo?.Guid;
|
||||
DisplayConditionPredicateEntity.LeftPropertyPath = LeftPropertyPath;
|
||||
Entity.PredicateType = (int) PredicateType;
|
||||
Entity.LeftDataModelGuid = LeftDataModel?.PluginInfo?.Guid;
|
||||
Entity.LeftPropertyPath = LeftPropertyPath;
|
||||
|
||||
DisplayConditionPredicateEntity.RightDataModelGuid = RightDataModel?.PluginInfo?.Guid;
|
||||
DisplayConditionPredicateEntity.RightPropertyPath = RightPropertyPath;
|
||||
DisplayConditionPredicateEntity.RightStaticValue = JsonConvert.SerializeObject(RightStaticValue);
|
||||
Entity.RightDataModelGuid = RightDataModel?.PluginInfo?.Guid;
|
||||
Entity.RightPropertyPath = RightPropertyPath;
|
||||
Entity.RightStaticValue = JsonConvert.SerializeObject(RightStaticValue);
|
||||
|
||||
DisplayConditionPredicateEntity.OperatorPluginGuid = Operator?.PluginInfo?.Guid;
|
||||
DisplayConditionPredicateEntity.OperatorType = Operator?.GetType().Name;
|
||||
Entity.OperatorPluginGuid = Operator?.PluginInfo?.Guid;
|
||||
Entity.OperatorType = Operator?.GetType().Name;
|
||||
}
|
||||
|
||||
public override bool Evaluate()
|
||||
@@ -168,30 +170,30 @@ namespace Artemis.Core.Models.Profile.Conditions
|
||||
internal override void Initialize(IDataModelService dataModelService)
|
||||
{
|
||||
// Left side
|
||||
if (DisplayConditionPredicateEntity.LeftDataModelGuid != null)
|
||||
if (Entity.LeftDataModelGuid != null)
|
||||
{
|
||||
var dataModel = dataModelService.GetPluginDataModelByGuid(DisplayConditionPredicateEntity.LeftDataModelGuid.Value);
|
||||
if (dataModel != null && dataModel.ContainsPath(DisplayConditionPredicateEntity.LeftPropertyPath))
|
||||
UpdateLeftSide(dataModel, DisplayConditionPredicateEntity.LeftPropertyPath);
|
||||
var dataModel = dataModelService.GetPluginDataModelByGuid(Entity.LeftDataModelGuid.Value);
|
||||
if (dataModel != null && dataModel.ContainsPath(Entity.LeftPropertyPath))
|
||||
UpdateLeftSide(dataModel, Entity.LeftPropertyPath);
|
||||
}
|
||||
|
||||
// Operator
|
||||
if (DisplayConditionPredicateEntity.OperatorPluginGuid != null)
|
||||
if (Entity.OperatorPluginGuid != null)
|
||||
{
|
||||
var conditionOperator = dataModelService.GetConditionOperator(DisplayConditionPredicateEntity.OperatorPluginGuid.Value, DisplayConditionPredicateEntity.OperatorType);
|
||||
var conditionOperator = dataModelService.GetConditionOperator(Entity.OperatorPluginGuid.Value, Entity.OperatorType);
|
||||
if (conditionOperator != null)
|
||||
UpdateOperator(conditionOperator);
|
||||
}
|
||||
|
||||
// Right side dynamic
|
||||
if (PredicateType == PredicateType.Dynamic && DisplayConditionPredicateEntity.RightDataModelGuid != null)
|
||||
if (PredicateType == PredicateType.Dynamic && Entity.RightDataModelGuid != null)
|
||||
{
|
||||
var dataModel = dataModelService.GetPluginDataModelByGuid(DisplayConditionPredicateEntity.RightDataModelGuid.Value);
|
||||
if (dataModel != null && dataModel.ContainsPath(DisplayConditionPredicateEntity.RightPropertyPath))
|
||||
UpdateRightSide(dataModel, DisplayConditionPredicateEntity.RightPropertyPath);
|
||||
var dataModel = dataModelService.GetPluginDataModelByGuid(Entity.RightDataModelGuid.Value);
|
||||
if (dataModel != null && dataModel.ContainsPath(Entity.RightPropertyPath))
|
||||
UpdateRightSide(dataModel, Entity.RightPropertyPath);
|
||||
}
|
||||
// Right side static
|
||||
else if (PredicateType == PredicateType.Static && DisplayConditionPredicateEntity.RightStaticValue != null)
|
||||
else if (PredicateType == PredicateType.Static && Entity.RightStaticValue != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -203,12 +205,12 @@ namespace Artemis.Core.Models.Profile.Conditions
|
||||
|
||||
try
|
||||
{
|
||||
rightSideValue = JsonConvert.DeserializeObject(DisplayConditionPredicateEntity.RightStaticValue, leftSideType);
|
||||
rightSideValue = JsonConvert.DeserializeObject(Entity.RightStaticValue, leftSideType);
|
||||
}
|
||||
// If deserialization fails, use the type's default
|
||||
catch (JsonSerializationException e)
|
||||
{
|
||||
dataModelService.LogDeserializationFailure(this, e);
|
||||
dataModelService.LogPredicateDeserializationFailure(this, e);
|
||||
rightSideValue = Activator.CreateInstance(leftSideType);
|
||||
}
|
||||
|
||||
@@ -217,7 +219,7 @@ namespace Artemis.Core.Models.Profile.Conditions
|
||||
else
|
||||
{
|
||||
// Hope for the best...
|
||||
UpdateRightSide(JsonConvert.DeserializeObject(DisplayConditionPredicateEntity.RightStaticValue));
|
||||
UpdateRightSide(JsonConvert.DeserializeObject(Entity.RightStaticValue));
|
||||
}
|
||||
}
|
||||
catch (JsonReaderException)
|
||||
@@ -230,7 +232,7 @@ namespace Artemis.Core.Models.Profile.Conditions
|
||||
|
||||
internal override DisplayConditionPartEntity GetEntity()
|
||||
{
|
||||
return DisplayConditionPredicateEntity;
|
||||
return Entity;
|
||||
}
|
||||
|
||||
private void ValidateOperator()
|
||||
@@ -292,7 +294,7 @@ namespace Artemis.Core.Models.Profile.Conditions
|
||||
if (LeftDataModel == null || RightDataModel == null || Operator == null)
|
||||
return;
|
||||
|
||||
var isListExpression = LeftDataModel.GetListTypeAtPath(LeftPropertyPath) != null;
|
||||
var isListExpression = LeftDataModel.GetListTypeInPath(LeftPropertyPath) != null;
|
||||
|
||||
Expression leftSideAccessor;
|
||||
Expression rightSideAccessor;
|
||||
@@ -335,7 +337,7 @@ namespace Artemis.Core.Models.Profile.Conditions
|
||||
if (LeftDataModel == null || Operator == null)
|
||||
return;
|
||||
|
||||
var isListExpression = LeftDataModel.GetListTypeAtPath(LeftPropertyPath) != null;
|
||||
var isListExpression = LeftDataModel.GetListTypeInPath(LeftPropertyPath) != null;
|
||||
|
||||
Expression leftSideAccessor;
|
||||
ParameterExpression leftSideParameter;
|
||||
@@ -373,7 +375,7 @@ namespace Artemis.Core.Models.Profile.Conditions
|
||||
|
||||
private Expression CreateAccessor(DataModel dataModel, string path, string parameterName, out ParameterExpression parameter)
|
||||
{
|
||||
var listType = dataModel.GetListTypeAtPath(path);
|
||||
var listType = dataModel.GetListTypeInPath(path);
|
||||
if (listType != null)
|
||||
throw new ArtemisCoreException($"Cannot create a regular accessor at path {path} because the path contains a list");
|
||||
|
||||
@@ -386,7 +388,7 @@ namespace Artemis.Core.Models.Profile.Conditions
|
||||
|
||||
private Expression CreateListAccessor(DataModel dataModel, string path, ParameterExpression listParameter)
|
||||
{
|
||||
var listType = dataModel.GetListTypeAtPath(path);
|
||||
var listType = dataModel.GetListTypeInPath(path);
|
||||
if (listType == null)
|
||||
throw new ArtemisCoreException($"Cannot create a list accessor at path {path} because the path does not contain a list");
|
||||
|
||||
|
||||
@@ -299,7 +299,7 @@ namespace Artemis.Core.Models.Profile
|
||||
ApplyRenderElementToEntity();
|
||||
|
||||
// Conditions
|
||||
RenderElementEntity.RootDisplayCondition = DisplayConditionGroup?.DisplayConditionGroupEntity;
|
||||
RenderElementEntity.RootDisplayCondition = DisplayConditionGroup?.Entity;
|
||||
DisplayConditionGroup?.ApplyToEntity();
|
||||
}
|
||||
|
||||
|
||||
@@ -206,7 +206,7 @@ namespace Artemis.Core.Models.Profile
|
||||
}
|
||||
|
||||
// Conditions
|
||||
RenderElementEntity.RootDisplayCondition = DisplayConditionGroup?.DisplayConditionGroupEntity;
|
||||
RenderElementEntity.RootDisplayCondition = DisplayConditionGroup?.Entity;
|
||||
DisplayConditionGroup?.ApplyToEntity();
|
||||
}
|
||||
|
||||
|
||||
@@ -31,14 +31,8 @@ namespace Artemis.Core.Plugins.Abstract.DataModels
|
||||
var current = GetType();
|
||||
foreach (var part in parts)
|
||||
{
|
||||
var property = current.GetProperty(part);
|
||||
|
||||
// For lists, look into the list type instead of the list itself
|
||||
if (property != null && typeof(IList).IsAssignableFrom(property.PropertyType))
|
||||
current = property.PropertyType.GetGenericArguments()[0];
|
||||
else
|
||||
current = property?.PropertyType;
|
||||
|
||||
var property = current?.GetProperty(part);
|
||||
current = property?.PropertyType;
|
||||
if (property == null)
|
||||
return false;
|
||||
}
|
||||
@@ -58,20 +52,14 @@ namespace Artemis.Core.Plugins.Abstract.DataModels
|
||||
foreach (var part in parts)
|
||||
{
|
||||
var property = current.GetProperty(part);
|
||||
|
||||
// For lists, look into the list type instead of the list itself
|
||||
if (typeof(IList).IsAssignableFrom(property.PropertyType))
|
||||
current = property.PropertyType.GetGenericArguments()[0];
|
||||
else
|
||||
current = property.PropertyType;
|
||||
|
||||
current = property.PropertyType;
|
||||
result = property.PropertyType;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public Type GetListTypeAtPath(string path)
|
||||
public Type GetListTypeInPath(string path)
|
||||
{
|
||||
if (!ContainsPath(path))
|
||||
return null;
|
||||
@@ -79,8 +67,13 @@ namespace Artemis.Core.Plugins.Abstract.DataModels
|
||||
var parts = path.Split('.');
|
||||
var current = GetType();
|
||||
|
||||
var index = 0;
|
||||
foreach (var part in parts)
|
||||
{
|
||||
// Only return a type if the path CONTAINS a list, not if it points TO a list
|
||||
if (index == parts.Length - 1)
|
||||
return null;
|
||||
|
||||
var property = current.GetProperty(part);
|
||||
|
||||
// For lists, look into the list type instead of the list itself
|
||||
@@ -88,14 +81,24 @@ namespace Artemis.Core.Plugins.Abstract.DataModels
|
||||
return property.PropertyType.GetGenericArguments()[0];
|
||||
|
||||
current = property.PropertyType;
|
||||
index++;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public Type GetListTypeAtPath(string path)
|
||||
{
|
||||
if (!ContainsPath(path))
|
||||
return null;
|
||||
|
||||
var child = GetTypeAtPath(path);
|
||||
return child.GenericTypeArguments.Length > 0 ? child.GenericTypeArguments[0] : null;
|
||||
}
|
||||
|
||||
public string GetListInnerPath(string path)
|
||||
{
|
||||
if (GetListTypeAtPath(path) == null)
|
||||
if (GetListTypeInPath(path) == null)
|
||||
throw new ArtemisCoreException($"Cannot determine inner list path at {path} because it does not contain a list");
|
||||
|
||||
var parts = path.Split('.');
|
||||
|
||||
@@ -178,9 +178,27 @@ namespace Artemis.Core.Services
|
||||
return RegisteredConditionOperators.FirstOrDefault(o => o.PluginInfo.Guid == operatorPluginGuid && o.GetType().Name == operatorType);
|
||||
}
|
||||
|
||||
public void LogDeserializationFailure(DisplayConditionPredicate displayConditionPredicate, JsonSerializationException exception)
|
||||
public void LogPredicateDeserializationFailure(DisplayConditionPredicate displayConditionPredicate, JsonException exception)
|
||||
{
|
||||
_logger.Warning(exception, "Failed to deserialize display condition predicate {predicate}", displayConditionPredicate);
|
||||
_logger.Warning(
|
||||
exception,
|
||||
"Failed to deserialize display condition predicate {left} {operator} {right}",
|
||||
displayConditionPredicate.Entity.LeftPropertyPath,
|
||||
displayConditionPredicate.Entity.OperatorType,
|
||||
displayConditionPredicate.Entity.RightPropertyPath
|
||||
);
|
||||
}
|
||||
|
||||
public void LogListPredicateDeserializationFailure(DisplayConditionListPredicate displayConditionPredicate, JsonException exception)
|
||||
{
|
||||
_logger.Warning(
|
||||
exception,
|
||||
"Failed to deserialize display condition list predicate {list} => {left} {operator} {right}",
|
||||
displayConditionPredicate.Entity.ListPropertyPath,
|
||||
displayConditionPredicate.Entity.LeftPropertyPath,
|
||||
displayConditionPredicate.Entity.OperatorType,
|
||||
displayConditionPredicate.Entity.RightPropertyPath
|
||||
);
|
||||
}
|
||||
|
||||
private void RegisterBuiltInConditionOperators()
|
||||
|
||||
@@ -60,6 +60,7 @@ namespace Artemis.Core.Services.Interfaces
|
||||
|
||||
List<DisplayConditionOperator> GetCompatibleConditionOperators(Type type);
|
||||
DisplayConditionOperator GetConditionOperator(Guid operatorPluginGuid, string operatorType);
|
||||
void LogDeserializationFailure(DisplayConditionPredicate displayConditionPredicate, JsonSerializationException exception);
|
||||
void LogPredicateDeserializationFailure(DisplayConditionPredicate displayConditionPredicate, JsonException exception);
|
||||
void LogListPredicateDeserializationFailure(DisplayConditionListPredicate displayConditionListPredicate, JsonException exception);
|
||||
}
|
||||
}
|
||||
@@ -154,8 +154,15 @@ namespace Artemis.Core.Services
|
||||
? new DisplayConditionGroup(null, renderElement.RenderElementEntity.RootDisplayCondition)
|
||||
: new DisplayConditionGroup(null);
|
||||
|
||||
displayCondition.Initialize(_dataModelService);
|
||||
renderElement.DisplayConditionGroup = displayCondition;
|
||||
try
|
||||
{
|
||||
displayCondition.Initialize(_dataModelService);
|
||||
renderElement.DisplayConditionGroup = displayCondition;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.Warning(e, $"Failed to init display conditions for {renderElement}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user