1
0
mirror of https://github.com/Artemis-RGB/Artemis synced 2026-01-01 10:13:30 +00:00

Conditions - Added null-checks to accessors

Conditions - Simplified operators, removing unnecessary expression trees
This commit is contained in:
SpoinkyNL 2020-09-24 00:02:53 +02:00
parent ad3581a93a
commit 33373bda57
24 changed files with 253 additions and 311 deletions

View File

@ -1,6 +1,5 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq.Expressions;
namespace Artemis.Core.DefaultTypes namespace Artemis.Core.DefaultTypes
{ {
@ -11,9 +10,9 @@ namespace Artemis.Core.DefaultTypes
public override string Description => "Equals"; public override string Description => "Equals";
public override string Icon => "Equal"; public override string Icon => "Equal";
public override BinaryExpression CreateExpression(Expression leftSide, Expression rightSide) public override bool Evaluate(object a, object b)
{ {
return Expression.Equal(leftSide, rightSide); return Equals(a, b);
} }
} }
} }

View File

@ -1,6 +1,5 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq.Expressions;
namespace Artemis.Core.DefaultTypes namespace Artemis.Core.DefaultTypes
{ {
@ -11,9 +10,9 @@ namespace Artemis.Core.DefaultTypes
public override string Description => "Is greater than"; public override string Description => "Is greater than";
public override string Icon => "GreaterThan"; public override string Icon => "GreaterThan";
public override BinaryExpression CreateExpression(Expression leftSide, Expression rightSide) public override bool Evaluate(object a, object b)
{ {
return Expression.GreaterThan(leftSide, rightSide); return Convert.ToSingle(a) > Convert.ToSingle(b);
} }
} }
} }

View File

@ -1,6 +1,5 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq.Expressions;
namespace Artemis.Core.DefaultTypes namespace Artemis.Core.DefaultTypes
{ {
@ -11,9 +10,9 @@ namespace Artemis.Core.DefaultTypes
public override string Description => "Is greater than or equal to"; public override string Description => "Is greater than or equal to";
public override string Icon => "GreaterThanOrEqual"; public override string Icon => "GreaterThanOrEqual";
public override BinaryExpression CreateExpression(Expression leftSide, Expression rightSide) public override bool Evaluate(object a, object b)
{ {
return Expression.GreaterThanOrEqual(leftSide, rightSide); return Convert.ToSingle(a) >= Convert.ToSingle(b);
} }
} }
} }

View File

@ -1,6 +1,5 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq.Expressions;
namespace Artemis.Core.DefaultTypes namespace Artemis.Core.DefaultTypes
{ {
@ -11,9 +10,9 @@ namespace Artemis.Core.DefaultTypes
public override string Description => "Is less than"; public override string Description => "Is less than";
public override string Icon => "LessThan"; public override string Icon => "LessThan";
public override BinaryExpression CreateExpression(Expression leftSide, Expression rightSide) public override bool Evaluate(object a, object b)
{ {
return Expression.LessThan(leftSide, rightSide); return Convert.ToSingle(a) < Convert.ToSingle(b);
} }
} }
} }

View File

@ -1,6 +1,5 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq.Expressions;
namespace Artemis.Core.DefaultTypes namespace Artemis.Core.DefaultTypes
{ {
@ -11,9 +10,9 @@ namespace Artemis.Core.DefaultTypes
public override string Description => "Is less than or equal to"; public override string Description => "Is less than or equal to";
public override string Icon => "LessThanOrEqual"; public override string Icon => "LessThanOrEqual";
public override BinaryExpression CreateExpression(Expression leftSide, Expression rightSide) public override bool Evaluate(object a, object b)
{ {
return Expression.LessThanOrEqual(leftSide, rightSide); return Convert.ToSingle(a) <= Convert.ToSingle(b);
} }
} }
} }

View File

@ -1,6 +1,5 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq.Expressions;
namespace Artemis.Core.DefaultTypes namespace Artemis.Core.DefaultTypes
{ {
@ -11,9 +10,9 @@ namespace Artemis.Core.DefaultTypes
public override string Description => "Does not equal"; public override string Description => "Does not equal";
public override string Icon => "NotEqualVariant"; public override string Icon => "NotEqualVariant";
public override BinaryExpression CreateExpression(Expression leftSide, Expression rightSide) public override bool Evaluate(object a, object b)
{ {
return Expression.NotEqual(leftSide, rightSide); return !Equals(a, b);
} }
} }
} }

View File

@ -0,0 +1,23 @@
using System;
using System.Collections.Generic;
namespace Artemis.Core.DefaultTypes
{
internal class NotNullConditionOperator : ConditionOperator
{
public NotNullConditionOperator()
{
SupportsRightSide = false;
}
public override IReadOnlyCollection<Type> CompatibleTypes => new List<Type> {typeof(object)};
public override string Description => "Is not null";
public override string Icon => "CheckboxMarkedCircleOutline";
public override bool Evaluate(object a, object b)
{
return a != null;
}
}
}

View File

@ -0,0 +1,23 @@
using System;
using System.Collections.Generic;
namespace Artemis.Core.DefaultTypes
{
internal class NullConditionOperator : ConditionOperator
{
public NullConditionOperator()
{
SupportsRightSide = false;
}
public override IReadOnlyCollection<Type> CompatibleTypes => new List<Type> {typeof(object)};
public override string Description => "Is null";
public override string Icon => "Null";
public override bool Evaluate(object a, object b)
{
return a == null;
}
}
}

View File

@ -1,33 +1,21 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;
namespace Artemis.Core.DefaultTypes namespace Artemis.Core.DefaultTypes
{ {
internal class StringContainsConditionOperator : ConditionOperator internal class StringContainsConditionOperator : ConditionOperator
{ {
private readonly MethodInfo _contains;
private readonly MethodInfo _toLower;
public StringContainsConditionOperator()
{
_toLower = typeof(string).GetMethod("ToLower", new Type[] { });
_contains = typeof(string).GetMethod("Contains", new[] {typeof(string)});
}
public override IReadOnlyCollection<Type> CompatibleTypes => new List<Type> {typeof(string)}; public override IReadOnlyCollection<Type> CompatibleTypes => new List<Type> {typeof(string)};
public override string Description => "Contains"; public override string Description => "Contains";
public override string Icon => "Contain"; public override string Icon => "Contain";
public override BinaryExpression CreateExpression(Expression leftSide, Expression rightSide) public override bool Evaluate(object a, object b)
{ {
var contains = Expression.Equal( var aString = (string) a;
Expression.Call(Expression.Call(leftSide, _toLower), _contains, Expression.Call(rightSide, _toLower)), var bString = (string) b;
Expression.Constant(true)
); return bString != null && aString != null && aString.Contains(bString, StringComparison.InvariantCultureIgnoreCase);
return AddNullChecks(leftSide, rightSide, contains);
} }
} }
} }

View File

@ -1,33 +1,21 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;
namespace Artemis.Core.DefaultTypes namespace Artemis.Core.DefaultTypes
{ {
internal class StringEndsWithConditionOperator : ConditionOperator internal class StringEndsWithConditionOperator : ConditionOperator
{ {
private readonly MethodInfo _endsWith;
private readonly MethodInfo _toLower;
public StringEndsWithConditionOperator()
{
_toLower = typeof(string).GetMethod("ToLower", new Type[] { });
_endsWith = typeof(string).GetMethod("EndsWith", new[] {typeof(string)});
}
public override IReadOnlyCollection<Type> CompatibleTypes => new List<Type> {typeof(string)}; public override IReadOnlyCollection<Type> CompatibleTypes => new List<Type> {typeof(string)};
public override string Description => "Ends with"; public override string Description => "Ends with";
public override string Icon => "ContainEnd"; public override string Icon => "ContainEnd";
public override BinaryExpression CreateExpression(Expression leftSide, Expression rightSide) public override bool Evaluate(object a, object b)
{ {
var endsWith = Expression.Equal( var aString = (string) a;
Expression.Call(Expression.Call(leftSide, _toLower), _endsWith, Expression.Call(rightSide, _toLower)), var bString = (string) b;
Expression.Constant(true)
); return bString != null && aString != null && aString.EndsWith(bString, StringComparison.InvariantCultureIgnoreCase);
return AddNullChecks(leftSide, rightSide, endsWith);
} }
} }
} }

View File

@ -1,27 +1,21 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;
namespace Artemis.Core.DefaultTypes namespace Artemis.Core.DefaultTypes
{ {
internal class StringEqualsConditionOperator : ConditionOperator internal class StringEqualsConditionOperator : ConditionOperator
{ {
private readonly MethodInfo _toLower;
public StringEqualsConditionOperator()
{
_toLower = typeof(string).GetMethod("ToLower", new Type[] { });
}
public override IReadOnlyCollection<Type> CompatibleTypes => new List<Type> {typeof(string)}; public override IReadOnlyCollection<Type> CompatibleTypes => new List<Type> {typeof(string)};
public override string Description => "Equals"; public override string Description => "Equals";
public override string Icon => "Equal"; public override string Icon => "Equal";
public override BinaryExpression CreateExpression(Expression leftSide, Expression rightSide) public override bool Evaluate(object a, object b)
{ {
return Expression.Equal(Expression.Call(leftSide, _toLower), Expression.Call(rightSide, _toLower)); var aString = (string) a;
var bString = (string) b;
return string.Equals(aString, bString, StringComparison.InvariantCultureIgnoreCase);
} }
} }
} }

View File

@ -1,33 +1,21 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;
namespace Artemis.Core.DefaultTypes namespace Artemis.Core.DefaultTypes
{ {
internal class StringNotContainsConditionOperator : ConditionOperator internal class StringNotContainsConditionOperator : ConditionOperator
{ {
private readonly MethodInfo _contains;
private readonly MethodInfo _toLower;
public StringNotContainsConditionOperator()
{
_toLower = typeof(string).GetMethod("ToLower", new Type[] { });
_contains = typeof(string).GetMethod("Contains", new[] {typeof(string)});
}
public override IReadOnlyCollection<Type> CompatibleTypes => new List<Type> {typeof(string)}; public override IReadOnlyCollection<Type> CompatibleTypes => new List<Type> {typeof(string)};
public override string Description => "Does not contain"; public override string Description => "Does not contain";
public override string Icon => "FormatStrikethrough"; public override string Icon => "FormatStrikethrough";
public override BinaryExpression CreateExpression(Expression leftSide, Expression rightSide) public override bool Evaluate(object a, object b)
{ {
var notContains = Expression.Equal( var aString = (string) a;
Expression.Call(Expression.Call(leftSide, _toLower), _contains, Expression.Call(rightSide, _toLower)), var bString = (string) b;
Expression.Constant(false)
); return bString != null && aString != null && !aString.Contains(bString, StringComparison.InvariantCultureIgnoreCase);
return AddNullChecks(leftSide, rightSide, notContains);
} }
} }
} }

View File

@ -1,27 +1,21 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;
namespace Artemis.Core.DefaultTypes namespace Artemis.Core.DefaultTypes
{ {
internal class StringNotEqualConditionOperator : ConditionOperator internal class StringNotEqualConditionOperator : ConditionOperator
{ {
private readonly MethodInfo _toLower;
public StringNotEqualConditionOperator()
{
_toLower = typeof(string).GetMethod("ToLower", new Type[] { });
}
public override IReadOnlyCollection<Type> CompatibleTypes => new List<Type> {typeof(string)}; public override IReadOnlyCollection<Type> CompatibleTypes => new List<Type> {typeof(string)};
public override string Description => "Does not equal"; public override string Description => "Does not equal";
public override string Icon => "NotEqualVariant"; public override string Icon => "NotEqualVariant";
public override BinaryExpression CreateExpression(Expression leftSide, Expression rightSide) public override bool Evaluate(object a, object b)
{ {
return Expression.NotEqual(Expression.Call(leftSide, _toLower), Expression.Call(rightSide, _toLower)); var aString = (string) a;
var bString = (string) b;
return !string.Equals(aString, bString, StringComparison.InvariantCultureIgnoreCase);
} }
} }
} }

View File

@ -1,24 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
namespace Artemis.Core.DefaultTypes
{
internal class StringNullConditionOperator : ConditionOperator
{
public StringNullConditionOperator()
{
SupportsRightSide = false;
}
public override IReadOnlyCollection<Type> CompatibleTypes => new List<Type> {typeof(string)};
public override string Description => "Is null";
public override string Icon => "Null";
public override BinaryExpression CreateExpression(Expression leftSide, Expression rightSide)
{
return Expression.Equal(leftSide, Expression.Constant(null, leftSide.Type));
}
}
}

View File

@ -1,33 +1,21 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;
namespace Artemis.Core.DefaultTypes namespace Artemis.Core.DefaultTypes
{ {
internal class StringStartsWithConditionOperator : ConditionOperator internal class StringStartsWithConditionOperator : ConditionOperator
{ {
private readonly MethodInfo _startsWith;
private readonly MethodInfo _toLower;
public StringStartsWithConditionOperator()
{
_toLower = typeof(string).GetMethod("ToLower", new Type[] { });
_startsWith = typeof(string).GetMethod("StartsWith", new[] {typeof(string)});
}
public override IReadOnlyCollection<Type> CompatibleTypes => new List<Type> {typeof(string)}; public override IReadOnlyCollection<Type> CompatibleTypes => new List<Type> {typeof(string)};
public override string Description => "Starts with"; public override string Description => "Starts with";
public override string Icon => "ContainStart"; public override string Icon => "ContainStart";
public override BinaryExpression CreateExpression(Expression leftSide, Expression rightSide) public override bool Evaluate(object a, object b)
{ {
var startsWith = Expression.Equal( var aString = (string) a;
Expression.Call(Expression.Call(leftSide, _toLower), _startsWith, Expression.Call(rightSide, _toLower)), var bString = (string) b;
Expression.Constant(true)
); return bString != null && aString != null && aString.StartsWith(bString, StringComparison.InvariantCultureIgnoreCase);
return AddNullChecks(leftSide, rightSide, startsWith);
} }
} }
} }

View File

@ -1,7 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Linq.Expressions;
namespace Artemis.Core namespace Artemis.Core
{ {
@ -47,40 +46,10 @@ namespace Artemis.Core
} }
/// <summary> /// <summary>
/// Creates a binary expression comparing two types /// Evaluates the operator on a and b
/// </summary> /// </summary>
/// <param name="leftSide">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="rightSide">The parameter on the right side of the expression</param> /// <param name="b">The parameter on the right side of the expression</param>
/// <returns></returns> public abstract bool Evaluate(object a, object b);
public abstract BinaryExpression CreateExpression(Expression leftSide, Expression rightSide);
/// <summary>
/// Wraps the provided expression in null-checks for the left and right side
/// <para>
/// The resulting expression body looks like:
/// <code>(a == null &amp;&amp; b == null) || ((a != null &amp;&amp; b != null) &amp;&amp; &lt;expression&gt;)</code>
/// </para>
/// </summary>
/// <param name="leftSide">The left side to check for nulls</param>
/// <param name="rightSide">The right side to check for nulls</param>
/// <param name="expression">The expression to wrap</param>
/// <returns>The wrapped expression</returns>
protected BinaryExpression AddNullChecks(Expression leftSide, Expression rightSide, BinaryExpression expression)
{
var nullConst = Expression.Constant(null);
return Expression.OrElse(
Expression.AndAlso(
Expression.Equal(leftSide, nullConst),
Expression.Equal(rightSide, nullConst)
),
Expression.AndAlso(
Expression.AndAlso(
Expression.NotEqual(leftSide, nullConst),
Expression.NotEqual(rightSide, nullConst)
),
expression
)
);
}
} }
} }

View File

@ -77,15 +77,8 @@ namespace Artemis.Core
/// </summary> /// </summary>
public object RightStaticValue { get; private set; } public object RightStaticValue { get; private set; }
/// <summary> public Func<object, object> LeftSideAccessor { get; set; }
/// Gets the compiled expression that evaluates this predicate public Func<object, object> RightSideAccessor { get; set; }
/// </summary>
public Func<object, bool> CompiledListPredicate { get; private set; }
/// <summary>
/// Gets the compiled expression that evaluates this predicate on an external right-side data model
/// </summary>
public Func<object, DataModel, bool> CompiledExternalListPredicate { get; set; }
/// <summary> /// <summary>
/// Updates the left side of the predicate /// Updates the left side of the predicate
@ -215,28 +208,26 @@ namespace Artemis.Core
return true; return true;
} }
#region IDisposable
/// <inheritdoc />
protected override void Dispose(bool disposing)
{
ConditionOperatorStore.ConditionOperatorAdded -= ConditionOperatorStoreOnConditionOperatorAdded;
ConditionOperatorStore.ConditionOperatorRemoved -= ConditionOperatorStoreOnConditionOperatorRemoved;
base.Dispose(disposing);
}
#endregion
internal override bool EvaluateObject(object target) internal override bool EvaluateObject(object target)
{ {
if (PredicateType == ListRightSideType.Static && CompiledListPredicate != null) if (Operator == null || LeftSideAccessor == null || PredicateType != ListRightSideType.Static && RightSideAccessor == null)
return CompiledListPredicate(target); return false;
if (PredicateType == ListRightSideType.DynamicList && CompiledListPredicate != null)
return CompiledListPredicate(target); // Compare with a static value
if (PredicateType == ListRightSideType.Dynamic && CompiledExternalListPredicate != null) if (PredicateType == ListRightSideType.Static)
return CompiledExternalListPredicate(target, RightDataModel); {
if (!DataModelConditionList.ListType.IsValueType && RightStaticValue == null)
return false;
return Operator.Evaluate(LeftSideAccessor(target), RightStaticValue);
}
// Compare with dynamic values
if (PredicateType == ListRightSideType.Dynamic)
return Operator.Evaluate(LeftSideAccessor(target), RightSideAccessor(RightDataModel));
if (PredicateType == ListRightSideType.DynamicList)
return Operator.Evaluate(LeftSideAccessor(target), RightSideAccessor(target));
return false; return false;
} }
@ -308,6 +299,8 @@ namespace Artemis.Core
private void Initialize() private void Initialize()
{ {
DataModelStore.DataModelAdded += DataModelStoreOnDataModelAdded;
DataModelStore.DataModelRemoved += DataModelStoreOnDataModelRemoved;
ConditionOperatorStore.ConditionOperatorAdded += ConditionOperatorStoreOnConditionOperatorAdded; ConditionOperatorStore.ConditionOperatorAdded += ConditionOperatorStoreOnConditionOperatorAdded;
ConditionOperatorStore.ConditionOperatorRemoved += ConditionOperatorStoreOnConditionOperatorRemoved; ConditionOperatorStore.ConditionOperatorRemoved += ConditionOperatorStoreOnConditionOperatorRemoved;
@ -375,18 +368,16 @@ namespace Artemis.Core
private void CreateExpression() private void CreateExpression()
{ {
CompiledListPredicate = null;
if (Operator == null) if (Operator == null)
return; return;
// If the operator does not support a right side, create a static expression because the right side will simply be null // 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) if (PredicateType == ListRightSideType.DynamicList && Operator.SupportsRightSide)
CreateDynamicListExpression(); CreateDynamicListAccessors();
else if (PredicateType == ListRightSideType.Dynamic && Operator.SupportsRightSide) else if (PredicateType == ListRightSideType.Dynamic && Operator.SupportsRightSide)
CreateDynamicExpression(); CreateDynamicAccessors();
else else
CreateStaticExpression(); CreateStaticAccessors();
} }
private void ValidateOperator() private void ValidateOperator()
@ -452,7 +443,7 @@ namespace Artemis.Core
RightStaticValue = null; RightStaticValue = null;
} }
private void CreateDynamicListExpression() private void CreateDynamicListAccessors()
{ {
if (LeftPropertyPath == null || RightPropertyPath == null || Operator == null) if (LeftPropertyPath == null || RightPropertyPath == null || Operator == null)
return; return;
@ -467,12 +458,11 @@ namespace Artemis.Core
if (rightSideAccessor.Type != leftSideAccessor.Type) if (rightSideAccessor.Type != leftSideAccessor.Type)
rightSideAccessor = Expression.Convert(rightSideAccessor, leftSideAccessor.Type); rightSideAccessor = Expression.Convert(rightSideAccessor, leftSideAccessor.Type);
var conditionExpression = Operator.CreateExpression(leftSideAccessor, rightSideAccessor); LeftSideAccessor = Expression.Lambda<Func<object, object>>(leftSideAccessor, leftSideParameter).Compile();
var lambda = Expression.Lambda<Func<object, bool>>(conditionExpression, leftSideParameter); RightSideAccessor = Expression.Lambda<Func<object, object>>(rightSideAccessor, leftSideParameter).Compile();
CompiledListPredicate = lambda.Compile();
} }
private void CreateDynamicExpression() private void CreateDynamicAccessors()
{ {
if (LeftPropertyPath == null || RightPropertyPath == null || RightDataModel == null || Operator == null) if (LeftPropertyPath == null || RightPropertyPath == null || RightDataModel == null || Operator == null)
return; return;
@ -480,19 +470,18 @@ namespace Artemis.Core
// List accessors share the same parameter because a list always contains one item per entry // List accessors share the same parameter because a list always contains one item per entry
var leftSideParameter = Expression.Parameter(typeof(object), "listItem"); var leftSideParameter = Expression.Parameter(typeof(object), "listItem");
var leftSideAccessor = CreateListAccessor(LeftPropertyPath, leftSideParameter); var leftSideAccessor = CreateListAccessor(LeftPropertyPath, leftSideParameter);
var rightSideAccessor = CreateAccessor(RightDataModel, RightPropertyPath, "right", out var rightSideParameter); var rightSideAccessor = ExpressionUtilities.CreateDataModelAccessor(RightDataModel, RightPropertyPath, "right", out var rightSideParameter);
// A conversion may be required if the types differ // 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 // 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) if (rightSideAccessor.Type != leftSideAccessor.Type)
rightSideAccessor = Expression.Convert(rightSideAccessor, leftSideAccessor.Type); rightSideAccessor = Expression.Convert(rightSideAccessor, leftSideAccessor.Type);
var conditionExpression = Operator.CreateExpression(leftSideAccessor, rightSideAccessor); LeftSideAccessor = Expression.Lambda<Func<object, object>>(leftSideAccessor, leftSideParameter).Compile();
var lambda = Expression.Lambda<Func<object, DataModel, bool>>(conditionExpression, leftSideParameter, rightSideParameter); RightSideAccessor = Expression.Lambda<Func<object, object>>(rightSideAccessor, rightSideParameter).Compile();
CompiledExternalListPredicate = lambda.Compile();
} }
private void CreateStaticExpression() private void CreateStaticAccessors()
{ {
if (!DataModelConditionList.IsPrimitiveList && LeftPropertyPath == null || Operator == null) if (!DataModelConditionList.IsPrimitiveList && LeftPropertyPath == null || Operator == null)
return; return;
@ -503,43 +492,51 @@ namespace Artemis.Core
? Expression.Convert(leftSideParameter, DataModelConditionList.ListType) ? Expression.Convert(leftSideParameter, DataModelConditionList.ListType)
: CreateListAccessor(LeftPropertyPath, leftSideParameter); : CreateListAccessor(LeftPropertyPath, leftSideParameter);
// If the left side is a value type but the input is empty, this isn't a valid expression LeftSideAccessor = Expression.Lambda<Func<object, object>>(leftSideAccessor, leftSideParameter).Compile();
if (leftSideAccessor.Type.IsValueType && RightStaticValue == null) RightSideAccessor = 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(Convert.ChangeType(RightStaticValue, leftSideAccessor.Type))
: 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) private Expression CreateListAccessor(string path, ParameterExpression listParameter)
{ {
return path.Split('.').Aggregate<string, Expression>( // Create an expression that checks every part of the path for null
Expression.Convert(listParameter, DataModelConditionList.ListType), // Cast to the appropriate type // In the same iteration, create the accessor
Expression.Property Expression source = Expression.Convert(listParameter, DataModelConditionList.ListType);
); return ExpressionUtilities.CreateNullCheckedAccessor(source, path);
} }
private Expression CreateAccessor(DataModel dataModel, string path, string parameterName, out ParameterExpression parameter) #region IDisposable
/// <inheritdoc />
protected override void Dispose(bool disposing)
{ {
var listType = dataModel.GetListTypeInPath(path); DataModelStore.DataModelAdded -= DataModelStoreOnDataModelAdded;
if (listType != null) DataModelStore.DataModelRemoved -= DataModelStoreOnDataModelRemoved;
throw new ArtemisCoreException($"Cannot create a regular accessor at path {path} because the path contains a list"); ConditionOperatorStore.ConditionOperatorAdded -= ConditionOperatorStoreOnConditionOperatorAdded;
ConditionOperatorStore.ConditionOperatorRemoved -= ConditionOperatorStoreOnConditionOperatorRemoved;
parameter = Expression.Parameter(typeof(object), parameterName + "DataModel"); base.Dispose(disposing);
return path.Split('.').Aggregate<string, Expression>(
Expression.Convert(parameter, dataModel.GetType()), // Cast to the appropriate type
Expression.Property
);
} }
#endregion
#region Event handlers #region Event handlers
private void DataModelStoreOnDataModelAdded(object sender, DataModelStoreEvent e)
{
var 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) private void ConditionOperatorStoreOnConditionOperatorAdded(object sender, ConditionOperatorStoreEvent e)
{ {
var conditionOperator = e.Registration.ConditionOperator; var conditionOperator = e.Registration.ConditionOperator;
@ -552,7 +549,6 @@ namespace Artemis.Core
if (e.Registration.ConditionOperator != Operator) if (e.Registration.ConditionOperator != Operator)
return; return;
Operator = null; Operator = null;
CompiledListPredicate = null;
} }
#endregion #endregion

View File

@ -73,15 +73,8 @@ namespace Artemis.Core
/// </summary> /// </summary>
public object RightStaticValue { get; private set; } public object RightStaticValue { get; private set; }
/// <summary> public Func<object, object> LeftSideAccessor { get; set; }
/// Gets the compiled function that evaluates this predicate if it of a dynamic <see cref="PredicateType" /> public Func<object, object> RightSideAccessor { get; set; }
/// </summary>
public Func<DataModel, DataModel, bool> CompiledDynamicPredicate { get; private set; }
/// <summary>
/// Gets the compiled function that evaluates this predicate if it is of a static <see cref="PredicateType" />
/// </summary>
public Func<DataModel, bool> CompiledStaticPredicate { get; private set; }
internal DataModelConditionPredicateEntity Entity { get; set; } internal DataModelConditionPredicateEntity Entity { get; set; }
@ -109,7 +102,7 @@ namespace Artemis.Core
ValidateOperator(); ValidateOperator();
ValidateRightSide(); ValidateRightSide();
CreateExpression(); CreateAccessors();
} }
/// <summary> /// <summary>
@ -134,7 +127,7 @@ namespace Artemis.Core
RightDataModel = dataModel; RightDataModel = dataModel;
RightPropertyPath = path; RightPropertyPath = path;
CreateExpression(); CreateAccessors();
} }
/// <summary> /// <summary>
@ -167,7 +160,7 @@ namespace Artemis.Core
else else
RightStaticValue = null; RightStaticValue = null;
CreateExpression(); CreateAccessors();
} }
/// <summary> /// <summary>
@ -180,7 +173,7 @@ namespace Artemis.Core
if (conditionOperator == null) if (conditionOperator == null)
{ {
Operator = null; Operator = null;
CreateExpression(); CreateAccessors();
return; return;
} }
@ -199,16 +192,28 @@ namespace Artemis.Core
} }
Operator = conditionOperator; Operator = conditionOperator;
CreateExpression(); CreateAccessors();
} }
/// <inheritdoc /> /// <inheritdoc />
public override bool Evaluate() public override bool Evaluate()
{ {
if (CompiledDynamicPredicate != null) if (Operator == null || LeftSideAccessor == null || PredicateType != ProfileRightSideType.Static && RightSideAccessor == null)
return CompiledDynamicPredicate(LeftDataModel, RightDataModel); return false;
if (CompiledStaticPredicate != null)
return CompiledStaticPredicate(LeftDataModel); // Compare with a static value
if (PredicateType == ProfileRightSideType.Static)
{
var leftSideValue = LeftSideAccessor(LeftDataModel);
if (leftSideValue.GetType().IsValueType && RightStaticValue == null)
return false;
return Operator.Evaluate(leftSideValue, RightStaticValue);
}
// Compare with dynamic values
if (PredicateType == ProfileRightSideType.Dynamic)
return Operator.Evaluate(LeftSideAccessor(LeftDataModel), RightSideAccessor(RightDataModel));
return false; return false;
} }
@ -314,17 +319,14 @@ namespace Artemis.Core
return Entity; return Entity;
} }
private void CreateExpression() private void CreateAccessors()
{ {
CompiledDynamicPredicate = null;
CompiledStaticPredicate = null;
if (Operator == null) if (Operator == null)
return; return;
// If the operator does not support a right side, create a static expression because the right side will simply be null // 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) if (PredicateType == ProfileRightSideType.Dynamic && Operator.SupportsRightSide)
CreateDynamicExpression(); CreateDynamicAccessors();
else else
CreateStaticExpression(); CreateStaticExpression();
} }
@ -360,22 +362,21 @@ namespace Artemis.Core
} }
} }
private void CreateDynamicExpression() private void CreateDynamicAccessors()
{ {
if (LeftDataModel == null || RightDataModel == null || Operator == null) if (LeftDataModel == null || RightDataModel == null || Operator == null)
return; return;
var leftSideAccessor = CreateAccessor(LeftDataModel, LeftPropertyPath, "left", out var leftSideParameter); var leftSideAccessor = ExpressionUtilities.CreateDataModelAccessor(LeftDataModel, LeftPropertyPath, "left", out var leftSideParameter);
var rightSideAccessor = CreateAccessor(RightDataModel, RightPropertyPath, "right", out var rightSideParameter); var rightSideAccessor = ExpressionUtilities.CreateDataModelAccessor(RightDataModel, RightPropertyPath, "right", out var rightSideParameter);
// A conversion may be required if the types differ // 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 // 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) if (rightSideAccessor.Type != leftSideAccessor.Type)
rightSideAccessor = Expression.Convert(rightSideAccessor, leftSideAccessor.Type); rightSideAccessor = Expression.Convert(rightSideAccessor, leftSideAccessor.Type);
var conditionExpression = Operator.CreateExpression(leftSideAccessor, rightSideAccessor); LeftSideAccessor = Expression.Lambda<Func<object, object>>(leftSideAccessor, leftSideParameter).Compile();
var lambda = Expression.Lambda<Func<DataModel, DataModel, bool>>(conditionExpression, leftSideParameter, rightSideParameter); RightSideAccessor = Expression.Lambda<Func<object, object>>(rightSideAccessor, rightSideParameter).Compile();
CompiledDynamicPredicate = lambda.Compile();
} }
private void CreateStaticExpression() private void CreateStaticExpression()
@ -383,34 +384,19 @@ namespace Artemis.Core
if (LeftDataModel == null || Operator == null) if (LeftDataModel == null || Operator == null)
return; return;
var leftSideAccessor = CreateAccessor(LeftDataModel, LeftPropertyPath, "left", out var leftSideParameter); var leftSideAccessor = Expression.Convert(
ExpressionUtilities.CreateDataModelAccessor(LeftDataModel, LeftPropertyPath, "left", out var leftSideParameter),
typeof(object)
);
// If the left side is a value type but the input is empty, this isn't a valid expression // 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) if (leftSideAccessor.Type.IsValueType && RightStaticValue == null)
return; return;
// If the right side value is null, the constant type cannot be inferred and must be provided manually LeftSideAccessor = Expression.Lambda<Func<object, object>>(leftSideAccessor, leftSideParameter).Compile();
var rightSideConstant = RightStaticValue != null RightSideAccessor = null;
? Expression.Constant(Convert.ChangeType(RightStaticValue, leftSideAccessor.Type))
: Expression.Constant(null, leftSideAccessor.Type);
var conditionExpression = Operator.CreateExpression(leftSideAccessor, rightSideConstant);
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.GetListTypeInPath(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
);
}
#region Event handlers #region Event handlers
@ -427,13 +413,13 @@ namespace Artemis.Core
{ {
if (LeftDataModel == e.Registration.DataModel) if (LeftDataModel == e.Registration.DataModel)
{ {
CompiledDynamicPredicate = null; LeftSideAccessor = null;
LeftDataModel = null; LeftDataModel = null;
} }
if (RightDataModel == e.Registration.DataModel) if (RightDataModel == e.Registration.DataModel)
{ {
CompiledDynamicPredicate = null; RightSideAccessor = null;
RightDataModel = null; RightDataModel = null;
} }
} }
@ -451,8 +437,6 @@ namespace Artemis.Core
return; return;
Operator = null; Operator = null;
CompiledStaticPredicate = null;
CompiledDynamicPredicate = null;
} }
#endregion #endregion

View File

@ -37,6 +37,8 @@ namespace Artemis.Core
throw new ObjectDisposedException("ConditionalDataBinding"); throw new ObjectDisposedException("ConditionalDataBinding");
var condition = Conditions.FirstOrDefault(c => c.Evaluate()); var condition = Conditions.FirstOrDefault(c => c.Evaluate());
if (condition != null)
Console.WriteLine();
return condition == null ? baseValue : condition.Value; return condition == null ? baseValue : condition.Value;
} }
@ -108,8 +110,7 @@ namespace Artemis.Core
} }
#endregion #endregion
#region Storage #region Storage
/// <inheritdoc /> /// <inheritdoc />

View File

@ -300,25 +300,14 @@ namespace Artemis.Core
return; return;
// If the right side value is null, the constant type cannot be inferred and must be provided based on the data binding target // If the right side value is null, the constant type cannot be inferred and must be provided based on the data binding target
var parameterAccessor = CreateAccessor(ParameterDataModel, ParameterPropertyPath, "parameter", out var rightSideParameter); var parameterAccessor = ExpressionUtilities.CreateDataModelAccessor(
ParameterDataModel, ParameterPropertyPath, "parameter", out var rightSideParameter
);
var lambda = Expression.Lambda<Func<DataModel, object>>(Expression.Convert(parameterAccessor, typeof(object)), rightSideParameter); var lambda = Expression.Lambda<Func<DataModel, object>>(Expression.Convert(parameterAccessor, typeof(object)), rightSideParameter);
CompiledParameterAccessor = lambda.Compile(); CompiledParameterAccessor = lambda.Compile();
} }
} }
private Expression CreateAccessor(DataModel dataModel, string path, string parameterName, out ParameterExpression parameter)
{
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");
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
);
}
#region Event handlers #region Event handlers
private void DataBindingModifierTypeStoreOnDataBindingModifierAdded(object sender, DataBindingModifierTypeStoreEvent e) private void DataBindingModifierTypeStoreOnDataBindingModifierAdded(object sender, DataBindingModifierTypeStoreEvent e)

View File

@ -59,7 +59,11 @@ namespace Artemis.Core.Services
RegisterConditionOperator(Constants.CorePluginInfo, new StringNotContainsConditionOperator()); RegisterConditionOperator(Constants.CorePluginInfo, new StringNotContainsConditionOperator());
RegisterConditionOperator(Constants.CorePluginInfo, new StringStartsWithConditionOperator()); RegisterConditionOperator(Constants.CorePluginInfo, new StringStartsWithConditionOperator());
RegisterConditionOperator(Constants.CorePluginInfo, new StringEndsWithConditionOperator()); RegisterConditionOperator(Constants.CorePluginInfo, new StringEndsWithConditionOperator());
RegisterConditionOperator(Constants.CorePluginInfo, new StringNullConditionOperator());
// Null checks, at the bottom
// TODO: Implement a priority mechanism
RegisterConditionOperator(Constants.CorePluginInfo, new NullConditionOperator());
RegisterConditionOperator(Constants.CorePluginInfo, new NotNullConditionOperator());
} }
} }
} }

View File

@ -0,0 +1,37 @@
using System.Linq.Expressions;
using Artemis.Core.DataModelExpansions;
namespace Artemis.Core
{
internal static class ExpressionUtilities
{
internal static Expression CreateDataModelAccessor(DataModel dataModel, string path, string parameterName, out ParameterExpression parameter)
{
parameter = Expression.Parameter(typeof(object), parameterName + "DataModel");
// Create an expression that checks every part of the path for null
// In the same iteration, create the accessor
Expression source = Expression.Convert(parameter, dataModel.GetType());
return CreateNullCheckedAccessor(source, path);
}
internal static Expression CreateNullCheckedAccessor(Expression source, string path)
{
// Create an expression that checks every part of the path for null
// In the same iteration, create the accessor
Expression condition = null;
foreach (var memberName in path.Split('.'))
{
var notNull = Expression.NotEqual(source, Expression.Constant(null));
condition = condition != null ? Expression.AndAlso(condition, notNull) : notNull;
source = Expression.PropertyOrField(source, memberName);
}
if (condition == null)
throw new ArtemisCoreException($"Failed to create a null-check for path {path}");
// Combine the null check and the accessor in a conditional statement that returns the default for the type if null
return Expression.Condition(condition, source, Expression.Default(source.Type));
}
}
}

View File

@ -1,4 +1,5 @@
using System; using System;
using System.Linq;
using Artemis.Core.DataModelExpansions; using Artemis.Core.DataModelExpansions;
using Artemis.Plugins.DataModelExpansions.TestData.DataModels; using Artemis.Plugins.DataModelExpansions.TestData.DataModels;
using SkiaSharp; using SkiaSharp;

View File

@ -128,10 +128,15 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions
if (DataModelConditionPredicate.Operator == null) if (DataModelConditionPredicate.Operator == null)
DataModelConditionPredicate.UpdateOperator(Operators.FirstOrDefault(o => o.SupportsType(leftSideType))); DataModelConditionPredicate.UpdateOperator(Operators.FirstOrDefault(o => o.SupportsType(leftSideType)));
SelectedOperator = DataModelConditionPredicate.Operator; SelectedOperator = DataModelConditionPredicate.Operator;
if (!SelectedOperator.SupportsRightSide)
{
DisposeRightSideStatic();
DisposeRightSideDynamic();
}
// Ensure the right side has the proper VM // Ensure the right side has the proper VM
var targetType = LeftSideSelectionViewModel?.SelectedPropertyViewModel?.PropertyInfo?.PropertyType; var targetType = LeftSideSelectionViewModel?.SelectedPropertyViewModel?.PropertyInfo?.PropertyType;
if (DataModelConditionPredicate.PredicateType == ProfileRightSideType.Dynamic) if (DataModelConditionPredicate.PredicateType == ProfileRightSideType.Dynamic && SelectedOperator.SupportsRightSide)
{ {
DisposeRightSideStatic(); DisposeRightSideStatic();
if (RightSideSelectionViewModel == null) if (RightSideSelectionViewModel == null)
@ -147,7 +152,7 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions
); );
RightSideSelectionViewModel.FilterTypes = new[] {targetType}; RightSideSelectionViewModel.FilterTypes = new[] {targetType};
} }
else else if (SelectedOperator.SupportsRightSide)
{ {
DisposeRightSideDynamic(); DisposeRightSideDynamic();
if (RightSideInputViewModel == null) if (RightSideInputViewModel == null)