using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using System.Reflection; using Artemis.Core.DataModelExpansions; namespace Artemis.Core { /// /// Represents a path that points to a property in data model /// public class DataModelPath { private readonly LinkedList _segments; /// /// Creates a new instance of the class /// /// The data model at which this path starts /// A string representation of the public DataModelPath(object dataModel, string path) { Target = dataModel ?? throw new ArgumentNullException(nameof(dataModel)); Path = path ?? throw new ArgumentNullException(nameof(path)); if (string.IsNullOrWhiteSpace(Path)) throw new ArgumentException("Path cannot be empty"); _segments = new LinkedList(); Initialize(path); } /// /// Gets the data model at which this path starts /// public object Target { get; } /// /// Gets a string representation of the /// public string Path { get; } /// /// Gets a boolean indicating whether all are valid /// public bool IsValid => Segments.All(p => p.Type != DataModelPathSegmentType.Invalid); /// /// Gets a read-only list of all segments of this path /// public IReadOnlyCollection Segments => _segments.ToList().AsReadOnly(); /// /// Gets a boolean indicating whether this data model path can have an inner path because it points to a list /// public bool CanHaveInnerPath => Segments.LastOrDefault()?.GetPropertyType()?.IsAssignableFrom(typeof(IList)) ?? false; /// /// Gets the inner path of this path, only available if this path points to a list /// public DataModelPath InnerPath { get; internal set; } internal Func Accessor { get; private set; } public void SetInnerPath(string path) { if (!CanHaveInnerPath) { var type = Segments.LastOrDefault()?.GetPropertyType(); throw new ArtemisCoreException($"Cannot set an inner path on a data model path if it does not point to a list (value is of type: {type?.Name})"); } InnerPath = new DataModelPath(GetValue(), path); } /// /// Gets the current value of the path /// public object GetValue() { return Accessor?.Invoke(Target); } /// /// Gets the property info of the property this path points to /// /// If static, the property info. If dynamic, null public PropertyInfo GetPropertyInfo() { if (InnerPath != null) return InnerPath.GetPropertyInfo(); return Segments.LastOrDefault()?.GetPropertyInfo(); } /// /// Gets the type of the property this path points to /// /// If possible, the property type public Type GetPropertyType() { if (InnerPath != null) return InnerPath.GetPropertyType(); return Segments.LastOrDefault()?.GetPropertyType(); } /// /// Gets the property description of the property this path points to /// /// If found, the data model property description public DataModelPropertyAttribute GetPropertyDescription() { if (InnerPath != null) return InnerPath.GetPropertyDescription(); return Segments.LastOrDefault()?.GetPropertyDescription(); } /// public override string ToString() { if (InnerPath != null) return $"{Path} > {InnerPath}"; return Path; } private void Initialize(string path) { var segments = path.Split("."); for (var index = 0; index < segments.Length; index++) { var identifier = segments[index]; var node = _segments.AddLast(new DataModelPathSegment(this, identifier, string.Join('.', segments.Take(index + 1)))); node.Value.Node = node; } var parameter = Expression.Parameter(typeof(object), "t"); Expression expression = Expression.Convert(parameter, Target.GetType()); Expression nullCondition = null; foreach (var segment in _segments) { var notNull = Expression.NotEqual(expression, Expression.Default(expression.Type)); nullCondition = nullCondition != null ? Expression.AndAlso(nullCondition, notNull) : notNull; expression = segment.Initialize(parameter, expression, nullCondition); if (expression == null) return; } Accessor = Expression.Lambda>( // Wrap with a null check Expression.Condition( nullCondition, Expression.Convert(expression, typeof(object)), Expression.Convert(Expression.Default(expression.Type), typeof(object)) ), parameter ).Compile(); } } }