/*
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using Python.Runtime;
using QuantConnect.Data.Market;
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text;
namespace QuantConnect.Python
{
public partial class PandasData
{
private static DataTypeMember CreateDataTypeMember(MemberInfo member, DataTypeMember[] children = null)
{
return member switch
{
PropertyInfo property => new PropertyMember(property, children),
FieldInfo field => new FieldMember(field, children),
_ => throw new ArgumentException($"Member type {member.MemberType} is not supported")
};
}
///
/// Represents a member of a data type, either a property or a field and it's children members in case it's a complex type.
/// It contains logic to get the member name and the children names, taking into account the parent prefixes.
///
private abstract class DataTypeMember
{
private static readonly StringBuilder _stringBuilder = new StringBuilder();
private DataTypeMember _parent;
private string _name;
public MemberInfo Member { get; }
public DataTypeMember[] Children { get; }
public abstract bool IsProperty { get; }
public abstract bool IsField { get; }
///
/// The prefix to be used for the children members when a class being expanded has multiple properties/fields of the same type
///
public string Prefix { get; private set; }
public bool ShouldBeUnwrapped => Children != null && Children.Length > 0;
///
/// Whether this member is Tick.LastPrice or OpenInterest.LastPrice.
/// Saved to avoid MemberInfo comparisons in the future
///
public bool IsTickLastPrice { get; }
public bool IsTickProperty { get; }
public DataTypeMember(MemberInfo member, DataTypeMember[] children = null)
{
Member = member;
Children = children;
IsTickLastPrice = member == _tickLastPriceMember || member == _openInterestLastPriceMember;
IsTickProperty = IsProperty && member.DeclaringType == typeof(Tick);
if (Children != null)
{
foreach (var child in Children)
{
child._parent = this;
}
}
}
public void SetPrefix()
{
Prefix = Member.Name.ToLowerInvariant();
}
///
/// Gets the member name, adding the parent prefixes if necessary.
///
/// If passed, it will be used instead of the 's name
public string GetMemberName(string customName = null)
{
if (ShouldBeUnwrapped)
{
return string.Empty;
}
if (!string.IsNullOrEmpty(customName))
{
return BuildMemberName(customName);
}
if (string.IsNullOrEmpty(_name))
{
_name = BuildMemberName(GetBaseName());
}
return _name;
}
public IEnumerable GetMemberNames()
{
return GetMemberNames(null);
}
public abstract object GetValue(object instance);
public abstract Type GetMemberType();
public override string ToString()
{
return $"{GetMemberType().Name} {Member.Name}";
}
private string BuildMemberName(string baseName)
{
_stringBuilder.Clear();
while (_parent != null && _parent.ShouldBeUnwrapped)
{
_stringBuilder.Insert(0, _parent.Prefix);
_parent = _parent._parent;
}
_stringBuilder.Append(baseName.ToLowerInvariant());
return _stringBuilder.ToString();
}
private IEnumerable GetMemberNames(string parentPrefix)
{
// If there are no children, return the name of the member. Else ignore the member and return the children names
if (ShouldBeUnwrapped)
{
var prefix = parentPrefix ?? string.Empty;
if (!string.IsNullOrEmpty(Prefix))
{
prefix += Prefix;
}
foreach (var child in Children)
{
foreach (var childName in child.GetMemberNames(prefix))
{
yield return childName;
}
}
yield break;
}
var memberName = GetBaseName();
_name = string.IsNullOrEmpty(parentPrefix) ? memberName : $"{parentPrefix}{memberName}";
yield return _name;
}
private string GetBaseName()
{
var baseName = Member.GetCustomAttribute()?.Name;
if (string.IsNullOrEmpty(baseName))
{
baseName = Member.Name;
}
return baseName.ToLowerInvariant();
}
}
private class PropertyMember : DataTypeMember
{
private PropertyInfo _property;
public override bool IsProperty => true;
public override bool IsField => false;
public PropertyMember(PropertyInfo property, DataTypeMember[] children = null)
: base(property, children)
{
_property = property;
}
public override object GetValue(object instance)
{
return _property.GetValue(instance);
}
public override Type GetMemberType()
{
return _property.PropertyType;
}
}
private class FieldMember : DataTypeMember
{
private FieldInfo _field;
public override bool IsProperty => false;
public override bool IsField => true;
public FieldMember(FieldInfo field, DataTypeMember[] children = null)
: base(field, children)
{
_field = field;
}
public override object GetValue(object instance)
{
return _field.GetValue(instance);
}
public override Type GetMemberType()
{
return _field.FieldType;
}
}
}
}