mirror of
https://github.com/Artemis-RGB/Artemis
synced 2025-12-13 05:48:35 +00:00
Condition operators - Redesigned API to leverage generics
Condition operators - Updated default operators to use new API
This commit is contained in:
parent
cf16b9c218
commit
4e69395ce8
@ -1,12 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Artemis.Core.DefaultTypes
|
||||
namespace Artemis.Core.DefaultTypes
|
||||
{
|
||||
internal class EqualsConditionOperator : ConditionOperator
|
||||
internal class EqualsConditionOperator : ConditionOperator<object, object>
|
||||
{
|
||||
public override IReadOnlyCollection<Type> CompatibleTypes => new List<Type> {typeof(object)};
|
||||
|
||||
public override string Description => "Equals";
|
||||
public override string Icon => "Equal";
|
||||
|
||||
|
||||
@ -1,18 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Artemis.Core.DefaultTypes
|
||||
namespace Artemis.Core.DefaultTypes
|
||||
{
|
||||
internal class GreaterThanConditionOperator : ConditionOperator
|
||||
internal class GreaterThanConditionOperator : ConditionOperator<double, double>
|
||||
{
|
||||
public override IReadOnlyCollection<Type> CompatibleTypes => Constants.NumberTypes;
|
||||
|
||||
public override string Description => "Is greater than";
|
||||
public override string Icon => "GreaterThan";
|
||||
|
||||
public override bool Evaluate(object a, object b)
|
||||
public override bool Evaluate(double a, double b)
|
||||
{
|
||||
return Convert.ToSingle(a) > Convert.ToSingle(b);
|
||||
return a > b;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,18 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Artemis.Core.DefaultTypes
|
||||
namespace Artemis.Core.DefaultTypes
|
||||
{
|
||||
internal class GreaterThanOrEqualConditionOperator : ConditionOperator
|
||||
internal class GreaterThanOrEqualConditionOperator : ConditionOperator<double, double>
|
||||
{
|
||||
public override IReadOnlyCollection<Type> CompatibleTypes => Constants.NumberTypes;
|
||||
|
||||
public override string Description => "Is greater than or equal to";
|
||||
public override string Icon => "GreaterThanOrEqual";
|
||||
|
||||
public override bool Evaluate(object a, object b)
|
||||
public override bool Evaluate(double a, double b)
|
||||
{
|
||||
return Convert.ToSingle(a) >= Convert.ToSingle(b);
|
||||
return a >= b;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,18 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Artemis.Core.DefaultTypes
|
||||
namespace Artemis.Core.DefaultTypes
|
||||
{
|
||||
internal class LessThanConditionOperator : ConditionOperator
|
||||
internal class LessThanConditionOperator : ConditionOperator<double, double>
|
||||
{
|
||||
public override IReadOnlyCollection<Type> CompatibleTypes => Constants.NumberTypes;
|
||||
|
||||
public override string Description => "Is less than";
|
||||
public override string Icon => "LessThan";
|
||||
|
||||
public override bool Evaluate(object a, object b)
|
||||
public override bool Evaluate(double a, double b)
|
||||
{
|
||||
return Convert.ToSingle(a) < Convert.ToSingle(b);
|
||||
return a < b;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,18 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Artemis.Core.DefaultTypes
|
||||
namespace Artemis.Core.DefaultTypes
|
||||
{
|
||||
internal class LessThanOrEqualConditionOperator : ConditionOperator
|
||||
internal class LessThanOrEqualConditionOperator : ConditionOperator<double, double>
|
||||
{
|
||||
public override IReadOnlyCollection<Type> CompatibleTypes => Constants.NumberTypes;
|
||||
|
||||
public override string Description => "Is less than or equal to";
|
||||
public override string Icon => "LessThanOrEqual";
|
||||
|
||||
public override bool Evaluate(object a, object b)
|
||||
public override bool Evaluate(double a, double b)
|
||||
{
|
||||
return Convert.ToSingle(a) <= Convert.ToSingle(b);
|
||||
return a <= b;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,12 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Artemis.Core.DefaultTypes
|
||||
namespace Artemis.Core.DefaultTypes
|
||||
{
|
||||
internal class NotEqualConditionOperator : ConditionOperator
|
||||
internal class NotEqualConditionOperator : ConditionOperator<object, object>
|
||||
{
|
||||
public override IReadOnlyCollection<Type> CompatibleTypes => new List<Type> {typeof(object)};
|
||||
|
||||
public override string Description => "Does not equal";
|
||||
public override string Icon => "NotEqualVariant";
|
||||
|
||||
|
||||
@ -1,21 +1,11 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Artemis.Core.DefaultTypes
|
||||
namespace Artemis.Core.DefaultTypes
|
||||
{
|
||||
internal class NotNullConditionOperator : ConditionOperator
|
||||
internal class NotNullConditionOperator : ConditionOperator<object>
|
||||
{
|
||||
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)
|
||||
public override bool Evaluate(object a)
|
||||
{
|
||||
return a != null;
|
||||
}
|
||||
|
||||
@ -1,21 +1,11 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Artemis.Core.DefaultTypes
|
||||
namespace Artemis.Core.DefaultTypes
|
||||
{
|
||||
internal class NullConditionOperator : ConditionOperator
|
||||
internal class NullConditionOperator : ConditionOperator<object>
|
||||
{
|
||||
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)
|
||||
public override bool Evaluate(object a)
|
||||
{
|
||||
return a == null;
|
||||
}
|
||||
|
||||
@ -0,0 +1,16 @@
|
||||
using System;
|
||||
|
||||
namespace Artemis.Core.DefaultTypes
|
||||
{
|
||||
internal class NumberEqualsConditionOperator : ConditionOperator<double, double>
|
||||
{
|
||||
public override string Description => "Equals";
|
||||
public override string Icon => "Equal";
|
||||
|
||||
public override bool Evaluate(double a, double b)
|
||||
{
|
||||
// Numbers can be tricky, an epsilon like this is close enough
|
||||
return Math.Abs(a - b) < 0.000001;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,16 @@
|
||||
using System;
|
||||
|
||||
namespace Artemis.Core.DefaultTypes
|
||||
{
|
||||
internal class NumberNotEqualConditionOperator : ConditionOperator<double, double>
|
||||
{
|
||||
public override string Description => "Does not equal";
|
||||
public override string Icon => "NotEqualVariant";
|
||||
|
||||
public override bool Evaluate(double a, double b)
|
||||
{
|
||||
// Numbers can be tricky, an epsilon like this is close enough
|
||||
return Math.Abs(a - b) > 0.000001;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,21 +1,15 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Artemis.Core.DefaultTypes
|
||||
{
|
||||
internal class StringContainsConditionOperator : ConditionOperator
|
||||
internal class StringContainsConditionOperator : ConditionOperator<string, string>
|
||||
{
|
||||
public override IReadOnlyCollection<Type> CompatibleTypes => new List<Type> {typeof(string)};
|
||||
|
||||
public override string Description => "Contains";
|
||||
public override string Icon => "Contain";
|
||||
|
||||
public override bool Evaluate(object a, object b)
|
||||
public override bool Evaluate(string a, string b)
|
||||
{
|
||||
string aString = (string) a;
|
||||
string bString = (string) b;
|
||||
|
||||
return bString != null && aString != null && aString.Contains(bString, StringComparison.InvariantCultureIgnoreCase);
|
||||
return a != null && b != null && a.Contains(b, StringComparison.InvariantCultureIgnoreCase);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,21 +1,15 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Artemis.Core.DefaultTypes
|
||||
{
|
||||
internal class StringEndsWithConditionOperator : ConditionOperator
|
||||
internal class StringEndsWithConditionOperator : ConditionOperator<string, string>
|
||||
{
|
||||
public override IReadOnlyCollection<Type> CompatibleTypes => new List<Type> {typeof(string)};
|
||||
|
||||
public override string Description => "Ends with";
|
||||
public override string Icon => "ContainEnd";
|
||||
|
||||
public override bool Evaluate(object a, object b)
|
||||
public override bool Evaluate(string a, string b)
|
||||
{
|
||||
string aString = (string) a;
|
||||
string bString = (string) b;
|
||||
|
||||
return bString != null && aString != null && aString.EndsWith(bString, StringComparison.InvariantCultureIgnoreCase);
|
||||
return a != null && b != null && a.EndsWith(b, StringComparison.InvariantCultureIgnoreCase);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,21 +1,15 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Artemis.Core.DefaultTypes
|
||||
{
|
||||
internal class StringEqualsConditionOperator : ConditionOperator
|
||||
internal class StringEqualsConditionOperator : ConditionOperator<string, string>
|
||||
{
|
||||
public override IReadOnlyCollection<Type> CompatibleTypes => new List<Type> {typeof(string)};
|
||||
|
||||
public override string Description => "Equals";
|
||||
public override string Icon => "Equal";
|
||||
|
||||
public override bool Evaluate(object a, object b)
|
||||
public override bool Evaluate(string a, string b)
|
||||
{
|
||||
string aString = (string) a;
|
||||
string bString = (string) b;
|
||||
|
||||
return string.Equals(aString, bString, StringComparison.InvariantCultureIgnoreCase);
|
||||
return string.Equals(a, b, StringComparison.InvariantCultureIgnoreCase);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,21 +1,15 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Artemis.Core.DefaultTypes
|
||||
{
|
||||
internal class StringNotContainsConditionOperator : ConditionOperator
|
||||
internal class StringNotContainsConditionOperator : ConditionOperator<string, string>
|
||||
{
|
||||
public override IReadOnlyCollection<Type> CompatibleTypes => new List<Type> {typeof(string)};
|
||||
|
||||
public override string Description => "Does not contain";
|
||||
public override string Icon => "FormatStrikethrough";
|
||||
|
||||
public override bool Evaluate(object a, object b)
|
||||
public override bool Evaluate(string a, string b)
|
||||
{
|
||||
string aString = (string) a;
|
||||
string bString = (string) b;
|
||||
|
||||
return bString != null && aString != null && !aString.Contains(bString, StringComparison.InvariantCultureIgnoreCase);
|
||||
return a != null && b != null && !a.Contains(b, StringComparison.InvariantCultureIgnoreCase);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,21 +1,15 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Artemis.Core.DefaultTypes
|
||||
{
|
||||
internal class StringNotEqualConditionOperator : ConditionOperator
|
||||
internal class StringNotEqualConditionOperator : ConditionOperator<string, string>
|
||||
{
|
||||
public override IReadOnlyCollection<Type> CompatibleTypes => new List<Type> {typeof(string)};
|
||||
|
||||
public override string Description => "Does not equal";
|
||||
public override string Icon => "NotEqualVariant";
|
||||
|
||||
public override bool Evaluate(object a, object b)
|
||||
public override bool Evaluate(string a, string b)
|
||||
{
|
||||
string aString = (string) a;
|
||||
string bString = (string) b;
|
||||
|
||||
return !string.Equals(aString, bString, StringComparison.InvariantCultureIgnoreCase);
|
||||
return !string.Equals(a, b, StringComparison.InvariantCultureIgnoreCase);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,21 +1,15 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Artemis.Core.DefaultTypes
|
||||
{
|
||||
internal class StringStartsWithConditionOperator : ConditionOperator
|
||||
internal class StringStartsWithConditionOperator : ConditionOperator<string, string>
|
||||
{
|
||||
public override IReadOnlyCollection<Type> CompatibleTypes => new List<Type> {typeof(string)};
|
||||
|
||||
public override string Description => "Starts with";
|
||||
public override string Icon => "ContainStart";
|
||||
|
||||
public override bool Evaluate(object a, object b)
|
||||
public override bool Evaluate(string a, string b)
|
||||
{
|
||||
string aString = (string) a;
|
||||
string bString = (string) b;
|
||||
|
||||
return bString != null && aString != null && aString.StartsWith(bString, StringComparison.InvariantCultureIgnoreCase);
|
||||
return a != null && b != null && a.StartsWith(b, StringComparison.InvariantCultureIgnoreCase);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -21,6 +21,25 @@ namespace Artemis.Core
|
||||
{typeof(short), new List<Type> {typeof(byte)}}
|
||||
};
|
||||
|
||||
private static readonly Dictionary<Type, string> TypeKeywords = new Dictionary<Type, string>
|
||||
{
|
||||
{typeof(bool), "bool"},
|
||||
{typeof(byte), "byte"},
|
||||
{typeof(sbyte), "sbyte"},
|
||||
{typeof(char), "char"},
|
||||
{typeof(decimal), "decimal"},
|
||||
{typeof(double), "double"},
|
||||
{typeof(float), "float"},
|
||||
{typeof(int), "int"},
|
||||
{typeof(uint), "uint"},
|
||||
{typeof(long), "long"},
|
||||
{typeof(ulong), "ulong"},
|
||||
{typeof(short), "short"},
|
||||
{typeof(ushort), "ushort"},
|
||||
{typeof(object), "object"},
|
||||
{typeof(string), "string"}
|
||||
};
|
||||
|
||||
public static bool IsGenericType(this Type type, Type genericType)
|
||||
{
|
||||
if (type == null)
|
||||
@ -64,31 +83,10 @@ namespace Artemis.Core
|
||||
|| value is decimal;
|
||||
}
|
||||
|
||||
private static readonly Dictionary<Type, string> TypeKeywords = new Dictionary<Type, string>()
|
||||
{
|
||||
{typeof(bool), "bool"},
|
||||
{typeof(byte), "byte"},
|
||||
{typeof(sbyte), "sbyte"},
|
||||
{typeof(char), "char"},
|
||||
{typeof(decimal), "decimal"},
|
||||
{typeof(double), "double"},
|
||||
{typeof(float), "float"},
|
||||
{typeof(int), "int"},
|
||||
{typeof(uint), "uint"},
|
||||
{typeof(long), "long"},
|
||||
{typeof(ulong), "ulong"},
|
||||
{typeof(short), "short"},
|
||||
{typeof(ushort), "ushort"},
|
||||
{typeof(object), "object"},
|
||||
{typeof(string), "string"},
|
||||
};
|
||||
|
||||
// From https://stackoverflow.com/a/2224421/5015269 but inverted and renamed to match similar framework methods
|
||||
/// <summary>
|
||||
/// Determines whether an instance of a specified type can be casted to a variable of the current type
|
||||
/// </summary>
|
||||
/// <param name="to"></param>
|
||||
/// <param name="from"></param>
|
||||
/// <returns></returns>
|
||||
public static bool IsCastableFrom(this Type to, Type from)
|
||||
{
|
||||
@ -99,14 +97,32 @@ namespace Artemis.Core
|
||||
if (PrimitiveTypeConversions.ContainsKey(to) && PrimitiveTypeConversions[to].Contains(from))
|
||||
return true;
|
||||
bool castable = from.GetMethods(BindingFlags.Public | BindingFlags.Static)
|
||||
.Any(
|
||||
m => m.ReturnType == to &&
|
||||
(m.Name == "op_Implicit" ||
|
||||
m.Name == "op_Explicit")
|
||||
);
|
||||
.Any(m => m.ReturnType == to && (m.Name == "op_Implicit" || m.Name == "op_Explicit"));
|
||||
return castable;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Scores how well the two types can be casted from one to another, 5 being a perfect match and 0 being not castable
|
||||
/// at all
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static int ScoreCastability(this Type to, Type from)
|
||||
{
|
||||
if (to == from)
|
||||
return 5;
|
||||
if (to.TypeIsNumber() && from.TypeIsNumber())
|
||||
return 4;
|
||||
if (PrimitiveTypeConversions.ContainsKey(to) && PrimitiveTypeConversions[to].Contains(from))
|
||||
return 3;
|
||||
bool castable = from.GetMethods(BindingFlags.Public | BindingFlags.Static)
|
||||
.Any(m => m.ReturnType == to && (m.Name == "op_Implicit" || m.Name == "op_Explicit"));
|
||||
if (castable)
|
||||
return 2;
|
||||
if (to.IsAssignableFrom(from))
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the default value of the given type
|
||||
/// </summary>
|
||||
@ -149,7 +165,7 @@ namespace Artemis.Core
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines a display name for the given type
|
||||
/// Determines a display name for the given type
|
||||
/// </summary>
|
||||
/// <param name="type">The type to determine the name for</param>
|
||||
/// <param name="humanize">Whether or not to humanize the result, defaults to false</param>
|
||||
|
||||
@ -0,0 +1,65 @@
|
||||
using System;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a condition operator that performs a boolean operation
|
||||
/// <para>
|
||||
/// To implement your own condition operator, inherit <see cref="ConditionOperator{TLeftSide, TRightSide}" /> or
|
||||
/// <see cref="ConditionOperator{TLeftSide}" />
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public abstract class BaseConditionOperator
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the description of this logical operator
|
||||
/// </summary>
|
||||
public abstract string Description { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the icon of this logical operator
|
||||
/// </summary>
|
||||
public abstract string Icon { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the plugin info this condition operator belongs to
|
||||
/// <para>Note: Not set until after registering</para>
|
||||
/// </summary>
|
||||
public PluginInfo PluginInfo { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the left side type of this condition operator
|
||||
/// </summary>
|
||||
public abstract Type LeftSideType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the right side type of this condition operator. May be null if the operator does not support a left side type
|
||||
/// </summary>
|
||||
public abstract Type? RightSideType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns whether the given type is supported by the operator
|
||||
/// </summary>
|
||||
/// <param name="type">The type to check for, must be either the same or be castable to the target type</param>
|
||||
/// <param name="side">Which side of the operator to check, left or right</param>
|
||||
public abstract bool SupportsType(Type type, ConditionParameterSide side);
|
||||
|
||||
/// <summary>
|
||||
/// Evaluates the condition with the input types being provided as objects
|
||||
/// <para>
|
||||
/// This leaves the caller responsible for the types matching <see cref="LeftSideType" /> and
|
||||
/// <see cref="RightSideType" />
|
||||
/// </para>
|
||||
/// </summary>
|
||||
/// <param name="leftSideValue">The left side value, type should match <see cref="LeftSideType" /></param>
|
||||
/// <param name="rightSideValue">The right side value, type should match <see cref="RightSideType" /></param>
|
||||
/// <returns>The result of the boolean condition's evaluation</returns>
|
||||
internal abstract bool InternalEvaluate(object? leftSideValue, object? rightSideValue);
|
||||
}
|
||||
|
||||
public enum ConditionParameterSide
|
||||
{
|
||||
Left,
|
||||
Right
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,95 @@
|
||||
using System;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a condition operator that performs a boolean operation using a left- and right-side
|
||||
/// </summary>
|
||||
public abstract class ConditionOperator<TLeftSide, TRightSide> : BaseConditionOperator
|
||||
{
|
||||
/// <summary>
|
||||
/// Evaluates the operator on a and b
|
||||
/// </summary>
|
||||
/// <param name="a">The parameter on the left side of the expression</param>
|
||||
/// <param name="b">The parameter on the right side of the expression</param>
|
||||
public abstract bool Evaluate(TLeftSide a, TRightSide b);
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool SupportsType(Type type, ConditionParameterSide side)
|
||||
{
|
||||
if (type == null)
|
||||
return true;
|
||||
if (side == ConditionParameterSide.Left)
|
||||
return LeftSideType.IsCastableFrom(type);
|
||||
return RightSideType.IsCastableFrom(type);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
internal override bool InternalEvaluate(object? leftSideValue, object? rightSideValue)
|
||||
{
|
||||
// TODO: Can we avoid boxing/unboxing?
|
||||
TLeftSide leftSide;
|
||||
if (leftSideValue != null)
|
||||
leftSide = (TLeftSide) Convert.ChangeType(leftSideValue, typeof(TLeftSide));
|
||||
else
|
||||
leftSide = default;
|
||||
|
||||
TRightSide rightSide;
|
||||
if (rightSideValue != null)
|
||||
rightSide = (TRightSide) Convert.ChangeType(rightSideValue, typeof(TRightSide));
|
||||
else
|
||||
rightSide = default;
|
||||
|
||||
return Evaluate(leftSide, rightSide);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Type LeftSideType => typeof(TLeftSide);
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Type RightSideType => typeof(TRightSide);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a condition operator that performs a boolean operation using only a left side
|
||||
/// </summary>
|
||||
public abstract class ConditionOperator<TLeftSide> : BaseConditionOperator
|
||||
{
|
||||
/// <summary>
|
||||
/// Evaluates the operator on a and b
|
||||
/// </summary>
|
||||
/// <param name="a">The parameter on the left side of the expression</param>
|
||||
public abstract bool Evaluate(TLeftSide a);
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool SupportsType(Type type, ConditionParameterSide side)
|
||||
{
|
||||
if (type == null)
|
||||
return true;
|
||||
if (side == ConditionParameterSide.Left)
|
||||
return LeftSideType.IsCastableFrom(type);
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
internal override bool InternalEvaluate(object? leftSideValue, object? rightSideValue)
|
||||
{
|
||||
// TODO: Can we avoid boxing/unboxing?
|
||||
TLeftSide leftSide;
|
||||
if (leftSideValue != null)
|
||||
leftSide = (TLeftSide) Convert.ChangeType(leftSideValue, typeof(TLeftSide));
|
||||
else
|
||||
leftSide = default;
|
||||
|
||||
return Evaluate(leftSide);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Type LeftSideType => typeof(TLeftSide);
|
||||
|
||||
/// <summary>
|
||||
/// Always <c>null</c>, not applicable to this type of condition operator
|
||||
/// </summary>
|
||||
public override Type? RightSideType => null;
|
||||
}
|
||||
}
|
||||
@ -1,55 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// A condition operator is used by the conditions system to perform a specific boolean check
|
||||
/// </summary>
|
||||
public abstract class ConditionOperator
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the plugin info this condition operator belongs to
|
||||
/// <para>Note: Not set until after registering</para>
|
||||
/// </summary>
|
||||
public PluginInfo PluginInfo { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the types this operator supports
|
||||
/// </summary>
|
||||
public abstract IReadOnlyCollection<Type> CompatibleTypes { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the description of this logical operator
|
||||
/// </summary>
|
||||
public abstract string Description { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the icon of this logical operator
|
||||
/// </summary>
|
||||
public abstract string Icon { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether this condition operator supports a right side, defaults to true
|
||||
/// </summary>
|
||||
public bool SupportsRightSide { get; protected set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Returns whether the given type is supported by the operator
|
||||
/// </summary>
|
||||
public bool SupportsType(Type type)
|
||||
{
|
||||
if (type == null)
|
||||
return true;
|
||||
return CompatibleTypes.Any(t => t.IsCastableFrom(type));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Evaluates the operator on a and b
|
||||
/// </summary>
|
||||
/// <param name="a">The parameter on the left side of the expression</param>
|
||||
/// <param name="b">The parameter on the right side of the expression</param>
|
||||
public abstract bool Evaluate(object? a, object? b);
|
||||
}
|
||||
}
|
||||
@ -47,7 +47,7 @@ namespace Artemis.Core
|
||||
/// <summary>
|
||||
/// Gets the operator
|
||||
/// </summary>
|
||||
public ConditionOperator? Operator { get; private set; }
|
||||
public BaseConditionOperator? Operator { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the data model condition list this predicate belongs to
|
||||
@ -99,6 +99,8 @@ namespace Artemis.Core
|
||||
{
|
||||
if (path != null && !path.IsValid)
|
||||
throw new ArtemisCoreException("Cannot update right side of predicate to an invalid path");
|
||||
if (Operator != null && path != null && !Operator.SupportsType(path.GetPropertyType()!, ConditionParameterSide.Right))
|
||||
throw new ArtemisCoreException($"Selected operator does not support right side of type {path.GetPropertyType()!.Name}");
|
||||
|
||||
RightPath?.Dispose();
|
||||
RightPath = path != null ? new DataModelPath(path) : null;
|
||||
@ -123,7 +125,7 @@ namespace Artemis.Core
|
||||
/// Updates the operator of the predicate and re-compiles the expression
|
||||
/// </summary>
|
||||
/// <param name="conditionOperator"></param>
|
||||
public void UpdateOperator(ConditionOperator? conditionOperator)
|
||||
public void UpdateOperator(BaseConditionOperator? conditionOperator)
|
||||
{
|
||||
if (conditionOperator == null)
|
||||
{
|
||||
@ -139,12 +141,13 @@ namespace Artemis.Core
|
||||
}
|
||||
|
||||
Type leftType = LeftPath.GetPropertyType()!;
|
||||
if (!conditionOperator.SupportsType(leftType))
|
||||
// Left side can't go empty so enforce a match
|
||||
if (!conditionOperator.SupportsType(leftType, ConditionParameterSide.Left))
|
||||
throw new ArtemisCoreException($"Cannot apply operator {conditionOperator.GetType().Name} to this predicate because " +
|
||||
$"it does not support left side type {leftType.Name}");
|
||||
|
||||
if (conditionOperator.SupportsType(leftType))
|
||||
Operator = conditionOperator;
|
||||
Operator = conditionOperator;
|
||||
ValidateRightSide();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -206,7 +209,7 @@ namespace Artemis.Core
|
||||
if (leftSideValue != null && leftSideValue.GetType().IsValueType && RightStaticValue == null)
|
||||
return false;
|
||||
|
||||
return Operator.Evaluate(leftSideValue, RightStaticValue);
|
||||
return Operator.InternalEvaluate(leftSideValue, RightStaticValue);
|
||||
}
|
||||
|
||||
if (RightPath == null || !RightPath.IsValid)
|
||||
@ -217,8 +220,8 @@ namespace Artemis.Core
|
||||
{
|
||||
// If the path targets a property inside the list, evaluate on the list path value instead of the right path value
|
||||
if (RightPath.Target is ListPredicateWrapperDataModel)
|
||||
return Operator.Evaluate(GetListPathValue(LeftPath, target), GetListPathValue(RightPath, target));
|
||||
return Operator.Evaluate(GetListPathValue(LeftPath, target), RightPath.GetValue());
|
||||
return Operator.InternalEvaluate(GetListPathValue(LeftPath, target), GetListPathValue(RightPath, target));
|
||||
return Operator.InternalEvaluate(GetListPathValue(LeftPath, target), RightPath.GetValue());
|
||||
}
|
||||
|
||||
return false;
|
||||
@ -300,7 +303,7 @@ namespace Artemis.Core
|
||||
// Operator
|
||||
if (Entity.OperatorPluginGuid != null)
|
||||
{
|
||||
ConditionOperator? conditionOperator = ConditionOperatorStore.Get(Entity.OperatorPluginGuid.Value, Entity.OperatorType)?.ConditionOperator;
|
||||
BaseConditionOperator? conditionOperator = ConditionOperatorStore.Get(Entity.OperatorPluginGuid.Value, Entity.OperatorType)?.ConditionOperator;
|
||||
if (conditionOperator != null)
|
||||
UpdateOperator(conditionOperator);
|
||||
}
|
||||
@ -361,28 +364,31 @@ namespace Artemis.Core
|
||||
return;
|
||||
|
||||
Type leftType = LeftPath.GetPropertyType()!;
|
||||
if (!Operator.SupportsType(leftType))
|
||||
if (!Operator.SupportsType(leftType, ConditionParameterSide.Left))
|
||||
Operator = null;
|
||||
}
|
||||
|
||||
private void ValidateRightSide()
|
||||
{
|
||||
Type? leftType = LeftPath?.GetPropertyType();
|
||||
if (Operator == null)
|
||||
return;
|
||||
|
||||
if (PredicateType == ProfileRightSideType.Dynamic)
|
||||
{
|
||||
if (RightPath == null || !RightPath.IsValid)
|
||||
return;
|
||||
|
||||
Type rightSideType = RightPath.GetPropertyType()!;
|
||||
if (leftType != null && !leftType.IsCastableFrom(rightSideType))
|
||||
if (!Operator.SupportsType(rightSideType, ConditionParameterSide.Right))
|
||||
UpdateRightSideDynamic(null);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (RightStaticValue != null && (leftType == null || leftType.IsCastableFrom(RightStaticValue.GetType())))
|
||||
UpdateRightSideStatic(RightStaticValue);
|
||||
else
|
||||
UpdateRightSideStatic(null);
|
||||
if (RightStaticValue == null)
|
||||
return;
|
||||
|
||||
if (!Operator.SupportsType(RightStaticValue.GetType(), ConditionParameterSide.Right))
|
||||
UpdateRightSideDynamic(null);
|
||||
}
|
||||
}
|
||||
|
||||
@ -391,24 +397,27 @@ namespace Artemis.Core
|
||||
RightPath?.Dispose();
|
||||
RightPath = null;
|
||||
|
||||
// If the left side is empty simply apply the value, any validation will wait
|
||||
if (LeftPath == null || !LeftPath.IsValid)
|
||||
// If the operator is null simply apply the value, any validation will wait
|
||||
if (Operator == null)
|
||||
{
|
||||
RightStaticValue = staticValue;
|
||||
return;
|
||||
}
|
||||
|
||||
// If the left path is valid we can expect a type
|
||||
Type leftSideType = LeftPath.GetPropertyType()!;
|
||||
// If the operator does not support a right side, always set it to null
|
||||
if (Operator.RightSideType == null)
|
||||
{
|
||||
RightStaticValue = null;
|
||||
return;
|
||||
}
|
||||
|
||||
// If not null ensure the types match and if not, convert it
|
||||
if (staticValue != null && staticValue.GetType() == leftSideType)
|
||||
if (staticValue != null && staticValue.GetType() == Operator.RightSideType)
|
||||
RightStaticValue = staticValue;
|
||||
else if (staticValue != null)
|
||||
RightStaticValue = Convert.ChangeType(staticValue, leftSideType);
|
||||
RightStaticValue = Convert.ChangeType(staticValue, Operator.RightSideType);
|
||||
// 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 if (Operator.RightSideType.IsValueType)
|
||||
RightStaticValue = Activator.CreateInstance(Operator.RightSideType);
|
||||
else
|
||||
RightStaticValue = null;
|
||||
}
|
||||
@ -426,7 +435,7 @@ namespace Artemis.Core
|
||||
|
||||
private void ConditionOperatorStoreOnConditionOperatorAdded(object sender, ConditionOperatorStoreEvent e)
|
||||
{
|
||||
ConditionOperator conditionOperator = e.Registration.ConditionOperator;
|
||||
BaseConditionOperator conditionOperator = e.Registration.ConditionOperator;
|
||||
if (Entity.OperatorPluginGuid == conditionOperator.PluginInfo.Guid && Entity.OperatorType == conditionOperator.GetType().Name)
|
||||
UpdateOperator(conditionOperator);
|
||||
}
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using Artemis.Core.DataModelExpansions;
|
||||
using Artemis.Storage.Entities.Profile.Abstract;
|
||||
using Artemis.Storage.Entities.Profile.Conditions;
|
||||
@ -43,7 +44,7 @@ namespace Artemis.Core
|
||||
/// <summary>
|
||||
/// Gets the operator
|
||||
/// </summary>
|
||||
public ConditionOperator? Operator { get; private set; }
|
||||
public BaseConditionOperator? Operator { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the path of the left property
|
||||
@ -87,6 +88,8 @@ namespace Artemis.Core
|
||||
{
|
||||
if (path != null && !path.IsValid)
|
||||
throw new ArtemisCoreException("Cannot update right side of predicate to an invalid path");
|
||||
if (Operator != null && path != null && !Operator.SupportsType(path.GetPropertyType()!, ConditionParameterSide.Right))
|
||||
throw new ArtemisCoreException($"Selected operator does not support right side of type {path.GetPropertyType()!.Name}");
|
||||
|
||||
RightPath?.Dispose();
|
||||
RightPath = path != null ? new DataModelPath(path) : null;
|
||||
@ -104,24 +107,27 @@ namespace Artemis.Core
|
||||
RightPath?.Dispose();
|
||||
RightPath = null;
|
||||
|
||||
// If the left side is empty simply apply the value, any validation will wait
|
||||
if (LeftPath == null || !LeftPath.IsValid)
|
||||
// If the operator is null simply apply the value, any validation will wait
|
||||
if (Operator == null)
|
||||
{
|
||||
RightStaticValue = staticValue;
|
||||
return;
|
||||
}
|
||||
|
||||
// If the left path is valid we can expect a type
|
||||
Type leftSideType = LeftPath.GetPropertyType()!;
|
||||
// If the operator does not support a right side, always set it to null
|
||||
if (Operator.RightSideType == null)
|
||||
{
|
||||
RightStaticValue = null;
|
||||
return;
|
||||
}
|
||||
|
||||
// If not null ensure the types match and if not, convert it
|
||||
if (staticValue != null && staticValue.GetType() == leftSideType)
|
||||
if (staticValue != null && staticValue.GetType() == Operator.RightSideType)
|
||||
RightStaticValue = staticValue;
|
||||
else if (staticValue != null)
|
||||
RightStaticValue = Convert.ChangeType(staticValue, leftSideType);
|
||||
RightStaticValue = Convert.ChangeType(staticValue, Operator.RightSideType);
|
||||
// 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 if (Operator.RightSideType.IsValueType)
|
||||
RightStaticValue = Activator.CreateInstance(Operator.RightSideType);
|
||||
else
|
||||
RightStaticValue = null;
|
||||
}
|
||||
@ -130,9 +136,8 @@ namespace Artemis.Core
|
||||
/// Updates the operator of the predicate and re-compiles the expression
|
||||
/// </summary>
|
||||
/// <param name="conditionOperator"></param>
|
||||
public void UpdateOperator(ConditionOperator? conditionOperator)
|
||||
public void UpdateOperator(BaseConditionOperator? conditionOperator)
|
||||
{
|
||||
// Calling CreateExpression will clear compiled expressions
|
||||
if (conditionOperator == null)
|
||||
{
|
||||
Operator = null;
|
||||
@ -147,11 +152,13 @@ namespace Artemis.Core
|
||||
}
|
||||
|
||||
Type leftType = LeftPath.GetPropertyType()!;
|
||||
if (!conditionOperator.SupportsType(leftType))
|
||||
// Left side can't go empty so enforce a match
|
||||
if (!conditionOperator.SupportsType(leftType, ConditionParameterSide.Left))
|
||||
throw new ArtemisCoreException($"Cannot apply operator {conditionOperator.GetType().Name} to this predicate because " +
|
||||
$"it does not support left side type {leftType.Name}");
|
||||
|
||||
Operator = conditionOperator;
|
||||
ValidateRightSide();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@ -167,14 +174,14 @@ namespace Artemis.Core
|
||||
if (leftSideValue != null && leftSideValue.GetType().IsValueType && RightStaticValue == null)
|
||||
return false;
|
||||
|
||||
return Operator.Evaluate(leftSideValue, RightStaticValue);
|
||||
return Operator.InternalEvaluate(leftSideValue, RightStaticValue);
|
||||
}
|
||||
|
||||
if (RightPath == null || !RightPath.IsValid)
|
||||
return false;
|
||||
|
||||
// Compare with dynamic values
|
||||
return Operator.Evaluate(LeftPath.GetValue(), RightPath.GetValue());
|
||||
return Operator.InternalEvaluate(LeftPath.GetValue(), RightPath.GetValue());
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@ -237,7 +244,7 @@ namespace Artemis.Core
|
||||
// Operator
|
||||
if (Entity.OperatorPluginGuid != null)
|
||||
{
|
||||
ConditionOperator? conditionOperator = ConditionOperatorStore.Get(Entity.OperatorPluginGuid.Value, Entity.OperatorType)?.ConditionOperator;
|
||||
BaseConditionOperator? conditionOperator = ConditionOperatorStore.Get(Entity.OperatorPluginGuid.Value, Entity.OperatorType)?.ConditionOperator;
|
||||
if (conditionOperator != null)
|
||||
UpdateOperator(conditionOperator);
|
||||
}
|
||||
@ -292,28 +299,31 @@ namespace Artemis.Core
|
||||
return;
|
||||
|
||||
Type leftType = LeftPath.GetPropertyType()!;
|
||||
if (!Operator.SupportsType(leftType))
|
||||
if (!Operator.SupportsType(leftType, ConditionParameterSide.Left))
|
||||
Operator = null;
|
||||
}
|
||||
|
||||
private void ValidateRightSide()
|
||||
{
|
||||
Type? leftType = LeftPath?.GetPropertyType();
|
||||
if (Operator == null)
|
||||
return;
|
||||
|
||||
if (PredicateType == ProfileRightSideType.Dynamic)
|
||||
{
|
||||
if (RightPath == null || !RightPath.IsValid)
|
||||
return;
|
||||
|
||||
Type rightSideType = RightPath.GetPropertyType()!;
|
||||
if (leftType != null && !leftType.IsCastableFrom(rightSideType))
|
||||
if (!Operator.SupportsType(rightSideType, ConditionParameterSide.Right))
|
||||
UpdateRightSideDynamic(null);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (RightStaticValue != null && (leftType == null || leftType.IsCastableFrom(RightStaticValue.GetType())))
|
||||
UpdateRightSideStatic(RightStaticValue);
|
||||
else
|
||||
UpdateRightSideStatic(null);
|
||||
if (RightStaticValue == null)
|
||||
return;
|
||||
|
||||
if (!Operator.SupportsType(RightStaticValue.GetType(), ConditionParameterSide.Right))
|
||||
UpdateRightSideDynamic(null);
|
||||
}
|
||||
}
|
||||
|
||||
@ -321,7 +331,7 @@ namespace Artemis.Core
|
||||
|
||||
private void ConditionOperatorStoreOnConditionOperatorAdded(object? sender, ConditionOperatorStoreEvent e)
|
||||
{
|
||||
ConditionOperator conditionOperator = e.Registration.ConditionOperator;
|
||||
BaseConditionOperator conditionOperator = e.Registration.ConditionOperator;
|
||||
if (Entity.OperatorPluginGuid == conditionOperator.PluginInfo.Guid && Entity.OperatorType == conditionOperator.GetType().Name)
|
||||
UpdateOperator(conditionOperator);
|
||||
}
|
||||
|
||||
@ -12,7 +12,7 @@ namespace Artemis.Core.Services
|
||||
RegisterBuiltInConditionOperators();
|
||||
}
|
||||
|
||||
public ConditionOperatorRegistration RegisterConditionOperator(PluginInfo pluginInfo, ConditionOperator conditionOperator)
|
||||
public ConditionOperatorRegistration RegisterConditionOperator(PluginInfo pluginInfo, BaseConditionOperator conditionOperator)
|
||||
{
|
||||
if (pluginInfo == null)
|
||||
throw new ArgumentNullException(nameof(pluginInfo));
|
||||
@ -30,12 +30,12 @@ namespace Artemis.Core.Services
|
||||
ConditionOperatorStore.Remove(registration);
|
||||
}
|
||||
|
||||
public List<ConditionOperator> GetConditionOperatorsForType(Type type)
|
||||
public List<BaseConditionOperator> GetConditionOperatorsForType(Type type, ConditionParameterSide side)
|
||||
{
|
||||
return ConditionOperatorStore.GetForType(type).Select(r => r.ConditionOperator).ToList();
|
||||
return ConditionOperatorStore.GetForType(type, side).Select(r => r.ConditionOperator).ToList();
|
||||
}
|
||||
|
||||
public ConditionOperator GetConditionOperator(Guid operatorPluginGuid, string operatorType)
|
||||
public BaseConditionOperator GetConditionOperator(Guid operatorPluginGuid, string operatorType)
|
||||
{
|
||||
return ConditionOperatorStore.Get(operatorPluginGuid, operatorType)?.ConditionOperator;
|
||||
}
|
||||
@ -47,6 +47,8 @@ namespace Artemis.Core.Services
|
||||
RegisterConditionOperator(Constants.CorePluginInfo, new NotEqualConditionOperator());
|
||||
|
||||
// Numeric operators
|
||||
RegisterConditionOperator(Constants.CorePluginInfo, new NumberEqualsConditionOperator());
|
||||
RegisterConditionOperator(Constants.CorePluginInfo, new NumberNotEqualConditionOperator());
|
||||
RegisterConditionOperator(Constants.CorePluginInfo, new LessThanConditionOperator());
|
||||
RegisterConditionOperator(Constants.CorePluginInfo, new GreaterThanConditionOperator());
|
||||
RegisterConditionOperator(Constants.CorePluginInfo, new LessThanOrEqualConditionOperator());
|
||||
|
||||
@ -14,7 +14,7 @@ namespace Artemis.Core.Services
|
||||
/// </summary>
|
||||
/// <param name="pluginInfo">The PluginInfo of the plugin this condition operator belongs to</param>
|
||||
/// <param name="conditionOperator">The condition operator to register</param>
|
||||
ConditionOperatorRegistration RegisterConditionOperator([NotNull] PluginInfo pluginInfo, [NotNull] ConditionOperator conditionOperator);
|
||||
ConditionOperatorRegistration RegisterConditionOperator([NotNull] PluginInfo pluginInfo, [NotNull] BaseConditionOperator conditionOperator);
|
||||
|
||||
/// <summary>
|
||||
/// Removes a condition operator so it is no longer available for use in layer conditions
|
||||
@ -25,13 +25,13 @@ namespace Artemis.Core.Services
|
||||
/// <summary>
|
||||
/// Returns all the condition operators compatible with the provided type
|
||||
/// </summary>
|
||||
List<ConditionOperator> GetConditionOperatorsForType(Type type);
|
||||
List<BaseConditionOperator> GetConditionOperatorsForType(Type type, ConditionParameterSide side);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a condition operator by its plugin GUID and type name
|
||||
/// </summary>
|
||||
/// <param name="operatorPluginGuid">The operator's plugin GUID</param>
|
||||
/// <param name="operatorType">The type name of the operator</param>
|
||||
ConditionOperator GetConditionOperator(Guid operatorPluginGuid, string operatorType);
|
||||
BaseConditionOperator GetConditionOperator(Guid operatorPluginGuid, string operatorType);
|
||||
}
|
||||
}
|
||||
@ -8,7 +8,7 @@ namespace Artemis.Core
|
||||
{
|
||||
private static readonly List<ConditionOperatorRegistration> Registrations = new List<ConditionOperatorRegistration>();
|
||||
|
||||
public static ConditionOperatorRegistration Add(ConditionOperator conditionOperator)
|
||||
public static ConditionOperatorRegistration Add(BaseConditionOperator conditionOperator)
|
||||
{
|
||||
ConditionOperatorRegistration registration;
|
||||
lock (Registrations)
|
||||
@ -46,19 +46,21 @@ namespace Artemis.Core
|
||||
}
|
||||
}
|
||||
|
||||
public static List<ConditionOperatorRegistration> GetForType(Type type)
|
||||
public static List<ConditionOperatorRegistration> GetForType(Type type, ConditionParameterSide side)
|
||||
{
|
||||
lock (Registrations)
|
||||
{
|
||||
if (type == null)
|
||||
return new List<ConditionOperatorRegistration>(Registrations);
|
||||
|
||||
List<ConditionOperatorRegistration> candidates = Registrations.Where(r => r.ConditionOperator.CompatibleTypes.Any(t => t.IsCastableFrom(type))).ToList();
|
||||
List<ConditionOperatorRegistration> candidates = Registrations.Where(r => r.ConditionOperator.SupportsType(type, side)).ToList();
|
||||
|
||||
// If there are multiple operators with the same description, use the closest match
|
||||
foreach (IGrouping<string, ConditionOperatorRegistration> candidate in candidates.GroupBy(r => r.ConditionOperator.Description).Where(g => g.Count() > 1).ToList())
|
||||
{
|
||||
ConditionOperatorRegistration closest = candidate.OrderByDescending(r => r.ConditionOperator.CompatibleTypes.Contains(type)).FirstOrDefault();
|
||||
ConditionOperatorRegistration closest = side == ConditionParameterSide.Left
|
||||
? candidate.OrderByDescending(r => r.ConditionOperator.LeftSideType.ScoreCastability(type)).First()
|
||||
: candidate.OrderByDescending(r => r.ConditionOperator.RightSideType!.ScoreCastability(type)).First();
|
||||
foreach (ConditionOperatorRegistration conditionOperator in candidate)
|
||||
{
|
||||
if (conditionOperator != closest)
|
||||
|
||||
@ -7,7 +7,7 @@ namespace Artemis.Core
|
||||
/// </summary>
|
||||
public class ConditionOperatorRegistration
|
||||
{
|
||||
internal ConditionOperatorRegistration(ConditionOperator conditionOperator, Plugin plugin)
|
||||
internal ConditionOperatorRegistration(BaseConditionOperator conditionOperator, Plugin plugin)
|
||||
{
|
||||
ConditionOperator = conditionOperator;
|
||||
Plugin = plugin;
|
||||
@ -18,7 +18,7 @@ namespace Artemis.Core
|
||||
/// <summary>
|
||||
/// Gets the condition operator that has been registered
|
||||
/// </summary>
|
||||
public ConditionOperator ConditionOperator { get; }
|
||||
public BaseConditionOperator ConditionOperator { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the plugin the condition operator is associated with
|
||||
|
||||
@ -21,10 +21,10 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions
|
||||
private readonly IProfileEditorService _profileEditorService;
|
||||
private bool _isPrimitiveList;
|
||||
private DataModelDynamicViewModel _leftSideSelectionViewModel;
|
||||
private BindableCollection<ConditionOperator> _operators;
|
||||
private BindableCollection<BaseConditionOperator> _operators;
|
||||
private DataModelStaticViewModel _rightSideInputViewModel;
|
||||
private DataModelDynamicViewModel _rightSideSelectionViewModel;
|
||||
private ConditionOperator _selectedOperator;
|
||||
private BaseConditionOperator _selectedOperator;
|
||||
|
||||
private List<Type> _supportedInputTypes;
|
||||
|
||||
@ -40,14 +40,14 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions
|
||||
_supportedInputTypes = new List<Type>();
|
||||
|
||||
SelectOperatorCommand = new DelegateCommand(ExecuteSelectOperatorCommand);
|
||||
Operators = new BindableCollection<ConditionOperator>();
|
||||
Operators = new BindableCollection<BaseConditionOperator>();
|
||||
|
||||
Initialize();
|
||||
}
|
||||
|
||||
public DataModelConditionListPredicate DataModelConditionListPredicate => (DataModelConditionListPredicate) Model;
|
||||
|
||||
public BindableCollection<ConditionOperator> Operators
|
||||
public BindableCollection<BaseConditionOperator> Operators
|
||||
{
|
||||
get => _operators;
|
||||
set => SetAndNotify(ref _operators, value);
|
||||
@ -71,7 +71,7 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions
|
||||
set => SetAndNotify(ref _rightSideInputViewModel, value);
|
||||
}
|
||||
|
||||
public ConditionOperator SelectedOperator
|
||||
public BaseConditionOperator SelectedOperator
|
||||
{
|
||||
get => _selectedOperator;
|
||||
set => SetAndNotify(ref _selectedOperator, value);
|
||||
@ -129,35 +129,41 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions
|
||||
|
||||
// Get the supported operators
|
||||
Operators.Clear();
|
||||
Operators.AddRange(_conditionOperatorService.GetConditionOperatorsForType(leftSideType ?? typeof(object)));
|
||||
Operators.AddRange(_conditionOperatorService.GetConditionOperatorsForType(leftSideType ?? typeof(object), ConditionParameterSide.Left));
|
||||
if (DataModelConditionListPredicate.Operator == null)
|
||||
DataModelConditionListPredicate.UpdateOperator(Operators.FirstOrDefault(o => o.SupportsType(leftSideType ?? typeof(object))));
|
||||
DataModelConditionListPredicate.UpdateOperator(Operators.FirstOrDefault(o => o.SupportsType(leftSideType ?? typeof(object), ConditionParameterSide.Left)));
|
||||
SelectedOperator = DataModelConditionListPredicate.Operator;
|
||||
if (SelectedOperator == null || !SelectedOperator.SupportsRightSide)
|
||||
|
||||
// Without a selected operator or one that supports a right side, leave the right side input empty
|
||||
if (SelectedOperator == null || SelectedOperator.RightSideType == null)
|
||||
{
|
||||
DisposeRightSideStaticViewModel();
|
||||
DisposeRightSideDynamicViewModel();
|
||||
return;
|
||||
}
|
||||
|
||||
// Ensure the right side has the proper VM
|
||||
if (DataModelConditionListPredicate.PredicateType == ProfileRightSideType.Dynamic && SelectedOperator.SupportsRightSide)
|
||||
if (DataModelConditionListPredicate.PredicateType == ProfileRightSideType.Dynamic)
|
||||
{
|
||||
DisposeRightSideStaticViewModel();
|
||||
if (RightSideSelectionViewModel == null)
|
||||
CreateRightSideSelectionViewModel();
|
||||
|
||||
RightSideSelectionViewModel.FilterTypes = new[] {leftSideType};
|
||||
RightSideSelectionViewModel.ChangeDataModelPath(DataModelConditionListPredicate.RightPath);
|
||||
RightSideSelectionViewModel.FilterTypes = new[] {SelectedOperator.RightSideType};
|
||||
}
|
||||
else if (SelectedOperator.SupportsRightSide)
|
||||
else
|
||||
{
|
||||
DisposeRightSideDynamicViewModel();
|
||||
if (RightSideInputViewModel == null)
|
||||
CreateRightSideInputViewModel(leftSideType);
|
||||
CreateRightSideInputViewModel(SelectedOperator.RightSideType);
|
||||
|
||||
RightSideInputViewModel.Value = DataModelConditionListPredicate.RightStaticValue;
|
||||
if (RightSideInputViewModel.TargetType != leftSideType)
|
||||
RightSideInputViewModel.UpdateTargetType(leftSideType);
|
||||
if (SelectedOperator.RightSideType.IsValueType && DataModelConditionListPredicate.RightStaticValue == null)
|
||||
RightSideInputViewModel.Value = SelectedOperator.RightSideType.GetDefault();
|
||||
else
|
||||
RightSideInputViewModel.Value = DataModelConditionListPredicate.RightStaticValue;
|
||||
if (RightSideInputViewModel.TargetType != SelectedOperator.RightSideType)
|
||||
RightSideInputViewModel.UpdateTargetType(SelectedOperator.RightSideType);
|
||||
}
|
||||
}
|
||||
|
||||
@ -210,7 +216,7 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions
|
||||
|
||||
private void ExecuteSelectOperatorCommand(object context)
|
||||
{
|
||||
if (!(context is ConditionOperator dataModelConditionOperator))
|
||||
if (!(context is BaseConditionOperator dataModelConditionOperator))
|
||||
return;
|
||||
|
||||
SelectedOperator = dataModelConditionOperator;
|
||||
@ -276,7 +282,7 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions
|
||||
|
||||
// Add an extra data model to the selection VM to allow self-referencing the current item
|
||||
// The safe cast prevents adding this extra VM on primitive lists where they serve no purpose
|
||||
if (GetListDataModel()?.Children?.FirstOrDefault() is DataModelPropertiesViewModel listValue)
|
||||
if (GetListDataModel()?.Children?.FirstOrDefault() is DataModelPropertiesViewModel listValue)
|
||||
RightSideSelectionViewModel.ExtraDataModelViewModels.Add(listValue);
|
||||
}
|
||||
|
||||
|
||||
@ -19,10 +19,10 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions
|
||||
private readonly IDataModelUIService _dataModelUIService;
|
||||
private readonly IProfileEditorService _profileEditorService;
|
||||
private DataModelDynamicViewModel _leftSideSelectionViewModel;
|
||||
private BindableCollection<ConditionOperator> _operators;
|
||||
private BindableCollection<BaseConditionOperator> _operators;
|
||||
private DataModelStaticViewModel _rightSideInputViewModel;
|
||||
private DataModelDynamicViewModel _rightSideSelectionViewModel;
|
||||
private ConditionOperator _selectedOperator;
|
||||
private BaseConditionOperator _selectedOperator;
|
||||
|
||||
private List<Type> _supportedInputTypes;
|
||||
|
||||
@ -39,7 +39,7 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions
|
||||
_supportedInputTypes = new List<Type>();
|
||||
|
||||
SelectOperatorCommand = new DelegateCommand(ExecuteSelectOperatorCommand);
|
||||
Operators = new BindableCollection<ConditionOperator>();
|
||||
Operators = new BindableCollection<BaseConditionOperator>();
|
||||
|
||||
ShowDataModelValues = settingsService.GetSetting<bool>("ProfileEditor.ShowDataModelValues");
|
||||
|
||||
@ -50,7 +50,7 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions
|
||||
public PluginSetting<bool> ShowDataModelValues { get; }
|
||||
|
||||
|
||||
public BindableCollection<ConditionOperator> Operators
|
||||
public BindableCollection<BaseConditionOperator> Operators
|
||||
{
|
||||
get => _operators;
|
||||
set => SetAndNotify(ref _operators, value);
|
||||
@ -62,7 +62,7 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions
|
||||
set => SetAndNotify(ref _leftSideSelectionViewModel, value);
|
||||
}
|
||||
|
||||
public ConditionOperator SelectedOperator
|
||||
public BaseConditionOperator SelectedOperator
|
||||
{
|
||||
get => _selectedOperator;
|
||||
set => SetAndNotify(ref _selectedOperator, value);
|
||||
@ -109,12 +109,13 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions
|
||||
|
||||
// Get the supported operators
|
||||
Operators.Clear();
|
||||
Operators.AddRange(_conditionOperatorService.GetConditionOperatorsForType(leftSideType ?? typeof(object)));
|
||||
Operators.AddRange(_conditionOperatorService.GetConditionOperatorsForType(leftSideType ?? typeof(object), ConditionParameterSide.Left));
|
||||
if (DataModelConditionPredicate.Operator == null)
|
||||
DataModelConditionPredicate.UpdateOperator(Operators.FirstOrDefault(o => o.SupportsType(leftSideType ?? typeof(object))));
|
||||
DataModelConditionPredicate.UpdateOperator(Operators.FirstOrDefault(o => o.SupportsType(leftSideType ?? typeof(object), ConditionParameterSide.Left)));
|
||||
SelectedOperator = DataModelConditionPredicate.Operator;
|
||||
|
||||
if (SelectedOperator == null || !SelectedOperator.SupportsRightSide)
|
||||
// Without a selected operator or one that supports a right side, leave the right side input empty
|
||||
if (SelectedOperator == null || SelectedOperator.RightSideType == null)
|
||||
{
|
||||
DisposeRightSideStaticViewModel();
|
||||
DisposeRightSideDynamicViewModel();
|
||||
@ -129,20 +130,20 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions
|
||||
CreateRightSideSelectionViewModel();
|
||||
|
||||
RightSideSelectionViewModel.ChangeDataModelPath(DataModelConditionPredicate.RightPath);
|
||||
RightSideSelectionViewModel.FilterTypes = new[] {leftSideType};
|
||||
RightSideSelectionViewModel.FilterTypes = new[] {SelectedOperator.RightSideType};
|
||||
}
|
||||
else
|
||||
{
|
||||
DisposeRightSideDynamicViewModel();
|
||||
if (RightSideInputViewModel == null)
|
||||
CreateRightSideInputViewModel(leftSideType);
|
||||
CreateRightSideInputViewModel(SelectedOperator.RightSideType);
|
||||
|
||||
if (leftSideType != null && leftSideType.IsValueType && DataModelConditionPredicate.RightStaticValue == null)
|
||||
RightSideInputViewModel.Value = leftSideType.GetDefault();
|
||||
if (SelectedOperator.RightSideType.IsValueType && DataModelConditionPredicate.RightStaticValue == null)
|
||||
RightSideInputViewModel.Value = SelectedOperator.RightSideType.GetDefault();
|
||||
else
|
||||
RightSideInputViewModel.Value = DataModelConditionPredicate.RightStaticValue;
|
||||
if (RightSideInputViewModel.TargetType != leftSideType)
|
||||
RightSideInputViewModel.UpdateTargetType(leftSideType);
|
||||
if (RightSideInputViewModel.TargetType != SelectedOperator.RightSideType)
|
||||
RightSideInputViewModel.UpdateTargetType(SelectedOperator.RightSideType);
|
||||
}
|
||||
}
|
||||
|
||||
@ -188,7 +189,7 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions
|
||||
|
||||
private void ExecuteSelectOperatorCommand(object context)
|
||||
{
|
||||
if (!(context is ConditionOperator DataModelConditionOperator))
|
||||
if (!(context is BaseConditionOperator DataModelConditionOperator))
|
||||
return;
|
||||
|
||||
SelectedOperator = DataModelConditionOperator;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user