using System; using System.Collections.Generic; using System.Linq.Expressions; using System.Reflection; using Artemis.Core.DataModelExpansions; namespace Artemis.Core { /// /// Represents a part of a data model path /// public class DataModelPathPart { internal DataModelPathPart(DataModelPath dataModelPath, string identifier, string path) { DataModelPath = dataModelPath; Identifier = identifier; Path = path; } /// /// Gets the data model path this is a part of /// public DataModelPath DataModelPath { get; } /// /// Gets the identifier that is associated with this part /// public string Identifier { get; } /// /// Gets the path that leads to this part /// public string Path { get; } /// /// Gets the type of data model this part of the path points to /// public DataModelPathPartType Type { get; private set; } /// /// Gets the type of dynamic data model this path points to /// Not used if the is /// public Type DynamicDataModelType { get; private set; } /// /// Gets the previous part in the path /// public DataModelPathPart Previous => Node.Previous?.Value; /// /// Gets the next part in the path /// public DataModelPathPart Next => Node.Next?.Value; internal Func Accessor { get; set; } internal LinkedListNode Node { get; set; } /// /// Returns the current value of the path up to this part /// /// public object GetValue() { return Type == DataModelPathPartType.Invalid ? null : Accessor(DataModelPath.DataModel); } /// public override string ToString() { return $"[{Type}] {Path}"; } internal Expression Initialize(ParameterExpression parameter, Expression expression, Expression nullCondition) { var previousValue = Previous != null ? Previous.GetValue() : DataModelPath.DataModel; if (previousValue == null) { Type = DataModelPathPartType.Invalid; return null; } // Determine this part's type by looking for a dynamic data model with the identifier if (previousValue is DataModel dataModel) { var hasDynamicDataModel = dataModel.DynamicDataModels.TryGetValue(Identifier, out var dynamicDataModel); // If a dynamic data model is found the use that if (hasDynamicDataModel) DetermineDynamicType(dynamicDataModel); // Otherwise look for a static type else DetermineStaticType(previousValue); } // Only data models can have dynamic types so if it is something else, its always going to be static else DetermineStaticType(previousValue); return CreateExpression(parameter, expression, nullCondition); } private Expression CreateExpression(ParameterExpression parameter, Expression expression, Expression nullCondition) { if (Type == DataModelPathPartType.Invalid) { Accessor = null; return null; } Expression accessorExpression; // A static part just needs to access the property or filed if (Type == DataModelPathPartType.Static) accessorExpression = Expression.PropertyOrField(expression, Identifier); // A dynamic part calls the generic method DataModel.DynamicChild and provides the identifier as an argument else { accessorExpression = Expression.Call( expression, nameof(DataModel.DynamicChild), new[] {DynamicDataModelType}, Expression.Constant(Identifier) ); } Accessor = Expression.Lambda>( // Wrap with a null check Expression.Condition( nullCondition, Expression.Convert(accessorExpression, typeof(object)), Expression.Convert(Expression.Default(accessorExpression.Type), typeof(object)) ), parameter ).Compile(); return accessorExpression; } private void DetermineDynamicType(DataModel dynamicDataModel) { Type = DataModelPathPartType.Dynamic; DynamicDataModelType = dynamicDataModel.GetType(); } private void DetermineStaticType(object previous) { var previousType = previous.GetType(); var property = previousType.GetProperty(Identifier, BindingFlags.Public | BindingFlags.Instance); Type = property == null ? DataModelPathPartType.Invalid : DataModelPathPartType.Static; } } }