mirror of
https://github.com/Artemis-RGB/Artemis
synced 2026-11-12 20:06:33 +00:00
Display conditions - Implemented lists in the core, UI needs more work
This commit is contained in:
1 parent
ae708fa26a
commit
f2f77da953
13 files changed
+288
-84
No files matched your search
@@ -35,8 +35,19 @@ namespace Artemis.Core.Models.Profile.Conditions.Abstract
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Evaluates the condition part on the data model
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public abstract bool Evaluate();
|
||||
|
||||
/// <summary>
|
||||
/// Evaluates the condition part on the given target (currently only for lists)
|
||||
/// </summary>
|
||||
/// <param name="target"></param>
|
||||
/// <returns></returns>
|
||||
public abstract bool EvaluateObject(object target);
|
||||
|
||||
internal abstract void Initialize(IDataModelService dataModelService);
|
||||
internal abstract void ApplyToEntity();
|
||||
internal abstract DisplayConditionPartEntity GetEntity();
|
||||
|
||||
@@ -37,9 +37,12 @@ namespace Artemis.Core.Models.Profile.Conditions
|
||||
|
||||
public override bool Evaluate()
|
||||
{
|
||||
// If there are less than two children, ignore the boolean operator
|
||||
if (Children.Count <= 2)
|
||||
return Children.All(c => c.Evaluate());
|
||||
// Empty groups are always true
|
||||
if (Children.Count == 0)
|
||||
return true;
|
||||
// Groups with only one child ignore the boolean operator
|
||||
if (Children.Count == 1)
|
||||
return Children[0].Evaluate();
|
||||
|
||||
switch (BooleanOperator)
|
||||
{
|
||||
@@ -56,6 +59,25 @@ namespace Artemis.Core.Models.Profile.Conditions
|
||||
}
|
||||
}
|
||||
|
||||
public override bool EvaluateObject(object target)
|
||||
{
|
||||
// Empty groups are always true
|
||||
if (Children.Count == 0)
|
||||
return true;
|
||||
// Groups with only one child ignore the boolean operator
|
||||
if (Children.Count == 1)
|
||||
return Children[0].EvaluateObject(target);
|
||||
|
||||
return BooleanOperator switch
|
||||
{
|
||||
BooleanOperator.And => Children.All(c => c.EvaluateObject(target)),
|
||||
BooleanOperator.Or => Children.Any(c => c.EvaluateObject(target)),
|
||||
BooleanOperator.AndNot => Children.All(c => !c.EvaluateObject(target)),
|
||||
BooleanOperator.OrNot => Children.Any(c => !c.EvaluateObject(target)),
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
}
|
||||
|
||||
internal override void ApplyToEntity()
|
||||
{
|
||||
DisplayConditionGroupEntity.BooleanOperator = (int) BooleanOperator;
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
using System.Linq;
|
||||
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;
|
||||
@@ -44,7 +47,23 @@ namespace Artemis.Core.Models.Profile.Conditions
|
||||
|
||||
public override bool Evaluate()
|
||||
{
|
||||
return true;
|
||||
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()
|
||||
@@ -94,11 +113,26 @@ namespace Artemis.Core.Models.Profile.Conditions
|
||||
{
|
||||
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
|
||||
|
||||
@@ -39,10 +39,9 @@ namespace Artemis.Core.Models.Profile.Conditions
|
||||
public string RightPropertyPath { get; private set; }
|
||||
public object RightStaticValue { get; private set; }
|
||||
|
||||
public Expression<Func<DataModel, DataModel, bool>> DynamicConditionLambda { get; private set; }
|
||||
public Func<DataModel, DataModel, bool> CompiledDynamicConditionLambda { get; private set; }
|
||||
public Expression<Func<DataModel, bool>> StaticConditionLambda { get; private set; }
|
||||
public Func<DataModel, bool> CompiledStaticConditionLambda { get; private set; }
|
||||
public Func<DataModel, DataModel, bool> CompiledDynamicPredicate { get; private set; }
|
||||
public Func<DataModel, bool> CompiledStaticPredicate { get; private set; }
|
||||
public Func<object, bool> CompiledListPredicate { get; private set; }
|
||||
|
||||
public void UpdateLeftSide(DataModel dataModel, string path)
|
||||
{
|
||||
@@ -120,10 +119,9 @@ namespace Artemis.Core.Models.Profile.Conditions
|
||||
|
||||
private void CreateExpression()
|
||||
{
|
||||
DynamicConditionLambda = null;
|
||||
CompiledDynamicConditionLambda = null;
|
||||
StaticConditionLambda = null;
|
||||
CompiledStaticConditionLambda = null;
|
||||
CompiledDynamicPredicate = null;
|
||||
CompiledStaticPredicate = null;
|
||||
CompiledListPredicate = null;
|
||||
|
||||
if (Operator == null)
|
||||
return;
|
||||
@@ -137,7 +135,7 @@ namespace Artemis.Core.Models.Profile.Conditions
|
||||
|
||||
internal override void ApplyToEntity()
|
||||
{
|
||||
DisplayConditionPredicateEntity.PredicateType = (int)PredicateType;
|
||||
DisplayConditionPredicateEntity.PredicateType = (int) PredicateType;
|
||||
DisplayConditionPredicateEntity.LeftDataModelGuid = LeftDataModel?.PluginInfo?.Guid;
|
||||
DisplayConditionPredicateEntity.LeftPropertyPath = LeftPropertyPath;
|
||||
|
||||
@@ -151,10 +149,18 @@ namespace Artemis.Core.Models.Profile.Conditions
|
||||
|
||||
public override bool Evaluate()
|
||||
{
|
||||
if (CompiledDynamicConditionLambda != null)
|
||||
return CompiledDynamicConditionLambda(LeftDataModel, RightDataModel);
|
||||
if (CompiledStaticConditionLambda != null)
|
||||
return CompiledStaticConditionLambda(LeftDataModel);
|
||||
if (CompiledDynamicPredicate != null)
|
||||
return CompiledDynamicPredicate(LeftDataModel, RightDataModel);
|
||||
if (CompiledStaticPredicate != null)
|
||||
return CompiledStaticPredicate(LeftDataModel);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public override bool EvaluateObject(object target)
|
||||
{
|
||||
if (CompiledListPredicate != null)
|
||||
return CompiledListPredicate(target);
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -194,7 +200,7 @@ namespace Artemis.Core.Models.Profile.Conditions
|
||||
// Use the left side type so JSON.NET has a better idea what to do
|
||||
var leftSideType = LeftDataModel.GetTypeAtPath(LeftPropertyPath);
|
||||
object rightSideValue;
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
rightSideValue = JsonConvert.DeserializeObject(DisplayConditionPredicateEntity.RightStaticValue, leftSideType);
|
||||
@@ -286,26 +292,42 @@ namespace Artemis.Core.Models.Profile.Conditions
|
||||
if (LeftDataModel == null || RightDataModel == null || Operator == null)
|
||||
return;
|
||||
|
||||
var leftSideParameter = Expression.Parameter(typeof(DataModel), "leftDataModel");
|
||||
var leftSideAccessor = LeftPropertyPath.Split('.').Aggregate<string, Expression>(
|
||||
Expression.Convert(leftSideParameter, LeftDataModel.GetType()), // Cast to the appropriate type
|
||||
Expression.Property
|
||||
);
|
||||
var rightSideParameter = Expression.Parameter(typeof(DataModel), "rightDataModel");
|
||||
var rightSideAccessor = RightPropertyPath.Split('.').Aggregate<string, Expression>(
|
||||
Expression.Convert(rightSideParameter, LeftDataModel.GetType()), // Cast to the appropriate type
|
||||
Expression.Property
|
||||
);
|
||||
var isListExpression = LeftDataModel.GetListTypeAtPath(LeftPropertyPath) != null;
|
||||
|
||||
Expression leftSideAccessor;
|
||||
Expression rightSideAccessor;
|
||||
ParameterExpression leftSideParameter;
|
||||
ParameterExpression rightSideParameter = null;
|
||||
if (isListExpression)
|
||||
{
|
||||
// List accessors share the same parameter because a list always contains one item per entry
|
||||
leftSideParameter = Expression.Parameter(typeof(object), "listItem");
|
||||
leftSideAccessor = CreateListAccessor(LeftDataModel, LeftPropertyPath, leftSideParameter);
|
||||
rightSideAccessor = CreateListAccessor(RightDataModel, RightPropertyPath, leftSideParameter);
|
||||
}
|
||||
else
|
||||
{
|
||||
leftSideAccessor = CreateAccessor(LeftDataModel, LeftPropertyPath, "left", out leftSideParameter);
|
||||
rightSideAccessor = CreateAccessor(RightDataModel, RightPropertyPath, "right", out rightSideParameter);
|
||||
}
|
||||
|
||||
// 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 dynamicConditionExpression = Operator.CreateExpression(leftSideAccessor, rightSideAccessor);
|
||||
var conditionExpression = Operator.CreateExpression(leftSideAccessor, rightSideAccessor);
|
||||
|
||||
DynamicConditionLambda = Expression.Lambda<Func<DataModel, DataModel, bool>>(dynamicConditionExpression, leftSideParameter, rightSideParameter);
|
||||
CompiledDynamicConditionLambda = DynamicConditionLambda.Compile();
|
||||
if (isListExpression)
|
||||
{
|
||||
var lambda = Expression.Lambda<Func<object, bool>>(conditionExpression, leftSideParameter);
|
||||
CompiledListPredicate = lambda.Compile();
|
||||
}
|
||||
else
|
||||
{
|
||||
var lambda = Expression.Lambda<Func<DataModel, DataModel, bool>>(conditionExpression, leftSideParameter, rightSideParameter);
|
||||
CompiledDynamicPredicate = lambda.Compile();
|
||||
}
|
||||
}
|
||||
|
||||
private void CreateStaticExpression()
|
||||
@@ -313,11 +335,18 @@ namespace Artemis.Core.Models.Profile.Conditions
|
||||
if (LeftDataModel == null || Operator == null)
|
||||
return;
|
||||
|
||||
var leftSideParameter = Expression.Parameter(typeof(DataModel), "leftDataModel");
|
||||
var leftSideAccessor = LeftPropertyPath.Split('.').Aggregate<string, Expression>(
|
||||
Expression.Convert(leftSideParameter, LeftDataModel.GetType()), // Cast to the appropriate type
|
||||
Expression.Property
|
||||
);
|
||||
var isListExpression = LeftDataModel.GetListTypeAtPath(LeftPropertyPath) != null;
|
||||
|
||||
Expression leftSideAccessor;
|
||||
ParameterExpression leftSideParameter;
|
||||
if (isListExpression)
|
||||
{
|
||||
// List accessors share the same parameter because a list always contains one item per entry
|
||||
leftSideParameter = Expression.Parameter(typeof(object), "listItem");
|
||||
leftSideAccessor = CreateListAccessor(LeftDataModel, LeftPropertyPath, leftSideParameter);
|
||||
}
|
||||
else
|
||||
leftSideAccessor = CreateAccessor(LeftDataModel, LeftPropertyPath, "left", out 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)
|
||||
@@ -330,8 +359,42 @@ namespace Artemis.Core.Models.Profile.Conditions
|
||||
|
||||
var conditionExpression = Operator.CreateExpression(leftSideAccessor, rightSideConstant);
|
||||
|
||||
StaticConditionLambda = Expression.Lambda<Func<DataModel, bool>>(conditionExpression, leftSideParameter);
|
||||
CompiledStaticConditionLambda = StaticConditionLambda.Compile();
|
||||
if (isListExpression)
|
||||
{
|
||||
var lambda = Expression.Lambda<Func<object, bool>>(conditionExpression, leftSideParameter);
|
||||
CompiledListPredicate = lambda.Compile();
|
||||
}
|
||||
else
|
||||
{
|
||||
var lambda = Expression.Lambda<Func<DataModel, bool>>(conditionExpression, leftSideParameter);
|
||||
CompiledStaticPredicate = lambda.Compile();
|
||||
}
|
||||
}
|
||||
|
||||
private Expression CreateAccessor(DataModel dataModel, string path, string parameterName, out ParameterExpression parameter)
|
||||
{
|
||||
var listType = dataModel.GetListTypeAtPath(path);
|
||||
if (listType != null)
|
||||
throw new ArtemisCoreException($"Cannot create a regular accessor at path {path} because the path contains a list");
|
||||
|
||||
parameter = Expression.Parameter(typeof(object), parameterName + "DataModel");
|
||||
return path.Split('.').Aggregate<string, Expression>(
|
||||
Expression.Convert(parameter, dataModel.GetType()), // Cast to the appropriate type
|
||||
Expression.Property
|
||||
);
|
||||
}
|
||||
|
||||
private Expression CreateListAccessor(DataModel dataModel, string path, ParameterExpression listParameter)
|
||||
{
|
||||
var listType = dataModel.GetListTypeAtPath(path);
|
||||
if (listType == null)
|
||||
throw new ArtemisCoreException($"Cannot create a list accessor at path {path} because the path does not contain a list");
|
||||
|
||||
path = dataModel.GetListInnerPath(path);
|
||||
return path.Split('.').Aggregate<string, Expression>(
|
||||
Expression.Convert(listParameter, listType), // Cast to the appropriate type
|
||||
Expression.Property
|
||||
);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
|
||||
Reference in new issue
Block a user