using System; using Artemis.Core.DataModelExpansions; using Artemis.Storage.Entities.Profile.Abstract; using Artemis.Storage.Entities.Profile.Conditions; using Newtonsoft.Json; namespace Artemis.Core { /// /// A predicate in a data model condition using either two data model values or one data model value and a /// static value /// public class DataModelConditionPredicate : DataModelConditionPart { /// /// Creates a new instance of the class /// /// /// public DataModelConditionPredicate(DataModelConditionPart parent, ProfileRightSideType predicateType) { Parent = parent; PredicateType = predicateType; Entity = new DataModelConditionPredicateEntity(); Initialize(); } internal DataModelConditionPredicate(DataModelConditionPart parent, DataModelConditionPredicateEntity entity) { Parent = parent; Entity = entity; PredicateType = (ProfileRightSideType) entity.PredicateType; Initialize(); } /// /// Gets or sets the predicate type /// public ProfileRightSideType PredicateType { get; set; } /// /// Gets the operator /// public ConditionOperator? Operator { get; private set; } /// /// Gets the path of the left property /// public DataModelPath? LeftPath { get; private set; } /// /// Gets the path of the right property /// public DataModelPath? RightPath { get; private set; } /// /// Gets the right static value, only used it is /// /// public object? RightStaticValue { get; private set; } internal DataModelConditionPredicateEntity Entity { get; set; } /// /// Updates the left side of the predicate /// /// The path pointing to the left side value inside the data model public void UpdateLeftSide(DataModelPath? path) { if (path != null && !path.IsValid) throw new ArtemisCoreException("Cannot update left side of predicate to an invalid path"); LeftPath?.Dispose(); LeftPath = path != null ? new DataModelPath(path) : null; ValidateOperator(); ValidateRightSide(); } /// /// Updates the right side of the predicate, makes the predicate dynamic and re-compiles the expression /// /// The path pointing to the right side value inside the data model public void UpdateRightSideDynamic(DataModelPath? path) { if (path != null && !path.IsValid) throw new ArtemisCoreException("Cannot update right side of predicate to an invalid path"); RightPath?.Dispose(); RightPath = path != null ? new DataModelPath(path) : null; PredicateType = ProfileRightSideType.Dynamic; } /// /// Updates the right side of the predicate, makes the predicate static and re-compiles the expression /// /// The right side value to use public void UpdateRightSideStatic(object? staticValue) { PredicateType = ProfileRightSideType.Static; RightPath?.Dispose(); RightPath = null; // If the left side is empty simply apply the value, any validation will wait if (LeftPath == null || !LeftPath.IsValid) { RightStaticValue = staticValue; return; } // If the left path is valid we can expect a type Type leftSideType = LeftPath.GetPropertyType()!; // If not null ensure the types match and if not, convert it if (staticValue != null && staticValue.GetType() == leftSideType) RightStaticValue = staticValue; else if (staticValue != null) RightStaticValue = Convert.ChangeType(staticValue, leftSideType); // If null create a default instance for value types or simply make it null for reference types else if (leftSideType.IsValueType) RightStaticValue = Activator.CreateInstance(leftSideType); else RightStaticValue = null; } /// /// Updates the operator of the predicate and re-compiles the expression /// /// public void UpdateOperator(ConditionOperator? conditionOperator) { // Calling CreateExpression will clear compiled expressions if (conditionOperator == null) { Operator = null; return; } // No need to clear compiled expressions, without a left data model they are already null if (LeftPath == null || !LeftPath.IsValid) { Operator = conditionOperator; return; } Type leftType = LeftPath.GetPropertyType()!; if (!conditionOperator.SupportsType(leftType)) throw new ArtemisCoreException($"Cannot apply operator {conditionOperator.GetType().Name} to this predicate because " + $"it does not support left side type {leftType.Name}"); Operator = conditionOperator; } /// public override bool Evaluate() { if (Operator == null || LeftPath == null || !LeftPath.IsValid) return false; // Compare with a static value if (PredicateType == ProfileRightSideType.Static) { object? leftSideValue = LeftPath.GetValue(); if (leftSideValue != null && leftSideValue.GetType().IsValueType && RightStaticValue == null) return false; return Operator.Evaluate(leftSideValue, RightStaticValue); } if (RightPath == null || !RightPath.IsValid) return false; // Compare with dynamic values return Operator.Evaluate(LeftPath.GetValue(), RightPath.GetValue()); } /// public override string ToString() { if (PredicateType == ProfileRightSideType.Dynamic) return $"[Dynamic] {LeftPath} {Operator.Description} {RightPath}"; return $"[Static] {LeftPath} {Operator.Description} {RightStaticValue}"; } /// protected override void Dispose(bool disposing) { ConditionOperatorStore.ConditionOperatorAdded -= ConditionOperatorStoreOnConditionOperatorAdded; ConditionOperatorStore.ConditionOperatorRemoved -= ConditionOperatorStoreOnConditionOperatorRemoved; LeftPath?.Dispose(); RightPath?.Dispose(); base.Dispose(disposing); } /// internal override bool EvaluateObject(object target) { return false; } internal override void Save() { Entity.PredicateType = (int) PredicateType; LeftPath?.Save(); Entity.LeftPath = LeftPath?.Entity; RightPath?.Save(); Entity.RightPath = RightPath?.Entity; Entity.RightStaticValue = JsonConvert.SerializeObject(RightStaticValue); if (Operator != null) { Entity.OperatorPluginGuid = Operator.PluginInfo.Guid; Entity.OperatorType = Operator.GetType().Name; } } internal void Initialize() { ConditionOperatorStore.ConditionOperatorAdded += ConditionOperatorStoreOnConditionOperatorAdded; ConditionOperatorStore.ConditionOperatorRemoved += ConditionOperatorStoreOnConditionOperatorRemoved; // Left side if (Entity.LeftPath != null) LeftPath = new DataModelPath(null, Entity.LeftPath); // Operator if (Entity.OperatorPluginGuid != null) { ConditionOperator? conditionOperator = ConditionOperatorStore.Get(Entity.OperatorPluginGuid.Value, Entity.OperatorType)?.ConditionOperator; if (conditionOperator != null) UpdateOperator(conditionOperator); } // Right side dynamic if (PredicateType == ProfileRightSideType.Dynamic && Entity.RightPath != null) RightPath = new DataModelPath(null, Entity.RightPath); // Right side static else if (PredicateType == ProfileRightSideType.Static && Entity.RightStaticValue != null) try { if (LeftPath != null && LeftPath.IsValid) { // Use the left side type so JSON.NET has a better idea what to do Type leftSideType = LeftPath.GetPropertyType()!; object? rightSideValue; try { rightSideValue = JsonConvert.DeserializeObject(Entity.RightStaticValue, leftSideType); } // If deserialization fails, use the type's default catch (JsonSerializationException e) { DeserializationLogger.LogPredicateDeserializationFailure(this, e); rightSideValue = Activator.CreateInstance(leftSideType); } UpdateRightSideStatic(rightSideValue); } else { // Hope for the best... UpdateRightSideStatic(JsonConvert.DeserializeObject(Entity.RightStaticValue)); } } catch (JsonReaderException) { // ignored // TODO: Some logging would be nice } } internal override DataModelConditionPartEntity GetEntity() { return Entity; } private void ValidateOperator() { if (LeftPath == null || !LeftPath.IsValid || Operator == null) return; Type leftType = LeftPath.GetPropertyType()!; if (!Operator.SupportsType(leftType)) Operator = null; } private void ValidateRightSide() { Type? leftType = LeftPath?.GetPropertyType(); if (PredicateType == ProfileRightSideType.Dynamic) { if (RightPath == null || !RightPath.IsValid) return; Type rightSideType = RightPath.GetPropertyType()!; if (leftType != null && !leftType.IsCastableFrom(rightSideType)) UpdateRightSideDynamic(null); } else { if (RightStaticValue != null && (leftType == null || leftType.IsCastableFrom(RightStaticValue.GetType()))) UpdateRightSideStatic(RightStaticValue); else UpdateRightSideStatic(null); } } #region Event handlers private void ConditionOperatorStoreOnConditionOperatorAdded(object? sender, ConditionOperatorStoreEvent e) { ConditionOperator conditionOperator = e.Registration.ConditionOperator; if (Entity.OperatorPluginGuid == conditionOperator.PluginInfo.Guid && Entity.OperatorType == conditionOperator.GetType().Name) UpdateOperator(conditionOperator); } private void ConditionOperatorStoreOnConditionOperatorRemoved(object? sender, ConditionOperatorStoreEvent e) { if (e.Registration.ConditionOperator != Operator) return; Operator = null; } #endregion } }