231 lines
11 KiB
C#
231 lines
11 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Reflection;
|
|
using RPGCoreCommon.Helpers;
|
|
using RPGCoreCommon.Helpers.Exceptions;
|
|
using UnityEngine;
|
|
|
|
namespace RPGCoreCommon.DynamicValues
|
|
{
|
|
public class MarkerParser
|
|
{
|
|
private static readonly BindingFlags BindingFlags = BindingFlags.Instance | BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly;
|
|
|
|
public string wholeMarker { get; private set; }
|
|
public string sourceMarker { get; private set; }
|
|
|
|
internal Type sourceType;
|
|
private MemberInfo[] _members;
|
|
private object[] _membersParameters;
|
|
private string[] _markers;
|
|
private string[] _parameters;
|
|
|
|
internal MarkerParser(string wholeMarker)
|
|
{
|
|
this.wholeMarker = wholeMarker;
|
|
var tempMarker = wholeMarker.Trim('{', '}').Split('.');
|
|
|
|
// Source marker should be always present (even if not it will be as empty string)
|
|
sourceMarker = tempMarker[0];
|
|
|
|
if (tempMarker.Length <= 1) return;
|
|
|
|
_markers = new string[tempMarker.Length - 1];
|
|
_parameters = new string[tempMarker.Length - 1];
|
|
|
|
for (var i = 1; i < tempMarker.Length; i++)
|
|
{
|
|
var valueMarker = tempMarker[i];
|
|
var tempValueMarker = valueMarker.TrimEnd(']').Split('[');
|
|
_markers[i - 1] = tempValueMarker[0];
|
|
if (tempValueMarker.Length > 1) _parameters[i - 1] = tempValueMarker[1];
|
|
}
|
|
}
|
|
|
|
/// <param name="type">Type that will be scanned</param>
|
|
/// <param name="attribute">Attribute with settings for this instance of <see cref="DynamicValue"/></param>
|
|
/// <returns>List of members that can be used as value providers for <see cref="DynamicValue"/>'s formula</returns>
|
|
internal static List<MemberInfo> GetMembersForType(Type type, DynamicValueAttribute attribute)
|
|
{
|
|
return type.GetAllTypes()
|
|
.SelectMany(t => t.GetMembers(BindingFlags))
|
|
.Where(member =>
|
|
{
|
|
if (attribute.ignoredDeclaringTypes.Contains(member.DeclaringType)) return false;
|
|
if (member is not MethodInfo and not PropertyInfo and not FieldInfo) return false;
|
|
var isProvider = member.GetCustomAttribute<DynamicValueProviderAttribute>() != null;
|
|
return isProvider || (member.IsMemberReadPublic() && member is not MethodInfo);
|
|
}).ToList();
|
|
}
|
|
|
|
/// <param name="member">Member to check if it can provide any subtypes besides itself</param>
|
|
/// <param name="isProvider">TRUE whenever given member is provider for <see cref="DynamicValue"/>'s formula</param>
|
|
/// <param name="isDynamic">TRUE if this member has parameter, but not specified with type, only by user's string</param>
|
|
/// <returns>All subtypes that this member can provide. If type can't be specified then it is dynamic.</returns>
|
|
internal static List<Type> GetSubTypesInProvider(MemberInfo member, out bool isProvider, out bool isDynamic)
|
|
{
|
|
isProvider = true;
|
|
isDynamic = false;
|
|
var providerAttribute = member.GetCustomAttribute<DynamicValueProviderAttribute>();
|
|
var methodInfo = member as MethodInfo;
|
|
|
|
// Only Methods support parameters, but only when attribute is also given
|
|
if (methodInfo == null || providerAttribute == null)
|
|
{
|
|
isProvider = false;
|
|
return new List<Type>();
|
|
}
|
|
|
|
var parameter = methodInfo.GetParameters().FirstOrDefault();
|
|
var isStringParameter = parameter?.ParameterType == typeof(string);
|
|
var isTypeParameter = parameter?.ParameterType == typeof(Type);
|
|
|
|
// BAD - invalid parameter definition in method!
|
|
if ((!isStringParameter && !isTypeParameter) || methodInfo.GetParameters().Length != 1)
|
|
{
|
|
Debug.LogError($"<b>{member.Name}</b> has invalid definition!<br>" +
|
|
$"[{nameof(DynamicValueProviderAttribute)}] Supports only methods with ONE parameter that is <b>string</b> or <b>type</b>!");
|
|
return new List<Type>();
|
|
}
|
|
|
|
// BAD - return type is required!
|
|
if (methodInfo.ReturnType == typeof(void))
|
|
{
|
|
Debug.LogError($"<b>{member.Name}</b> has invalid definition!<br>" +
|
|
$"[{nameof(DynamicValueProviderAttribute)}] Supports only methods with defined return type!");
|
|
return new List<Type>();
|
|
}
|
|
|
|
// GOOD by string - polymorphism not supported - parameter is dynamic, which means text can have anything
|
|
if (isStringParameter)
|
|
{
|
|
isDynamic = true;
|
|
return new List<Type> { methodInfo.ReturnType };
|
|
}
|
|
|
|
// GOOD by type - polymorphism supported
|
|
return UnityEditor.TypeCache.GetTypesDerivedFrom(methodInfo.ReturnType)
|
|
.Where(type => !type.IsAbstract && !type.IsGenericType)
|
|
.ToList();
|
|
}
|
|
|
|
/// <returns>Type of value that this marker leads to</returns>
|
|
/// <exception cref="MemberNotFoundException">When member at path could not be found</exception>
|
|
public Type GetValueType()
|
|
{
|
|
return GetMembers().Last().GetMemberValueType();
|
|
}
|
|
|
|
public string GetValue(object source)
|
|
{
|
|
// This will also refill _members and _membersParameters
|
|
GetMembers();
|
|
|
|
var tempObject = source;
|
|
for (var i = 0; i < _members.Length; i++)
|
|
{
|
|
if (_members[i] is FieldInfo or PropertyInfo)
|
|
tempObject = _members[i].GetMemberValue(tempObject);
|
|
else if (_members[i] is MethodInfo methodInfo)
|
|
tempObject = methodInfo.Invoke(tempObject, new[] { _membersParameters[i] });
|
|
}
|
|
|
|
return tempObject.ToString();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns chain of <see cref="MemberInfo"/> that will be used to get value for this marker.
|
|
/// </summary>
|
|
/// <returns>Array of <see cref="MemberInfo"/> used by this marker.</returns>
|
|
/// <exception cref="MemberNotFoundException">When given path from marker doesn't exist in source.</exception>
|
|
public MemberInfo[] GetMembers()
|
|
{
|
|
if (Application.isPlaying && _members != null) return _members.ToArray();
|
|
|
|
_members = new MemberInfo[_markers.Length];
|
|
_membersParameters = new object[_markers.Length];
|
|
|
|
var tempType = sourceType;
|
|
for (var i = 0; i < _markers.Length; i++)
|
|
{
|
|
var member = tempType.GetAllTypes()
|
|
.SelectMany(t => t.GetMember(_markers[i], BindingFlags)).FirstOrDefault();
|
|
|
|
// BAD - whole member doesn't exist
|
|
if (member == null)
|
|
throw new MemberNotFoundException($"Member [{_markers[i]}] is not found!", tempType);
|
|
|
|
var attribute = member.GetCustomAttribute<DynamicValueProviderAttribute>();
|
|
|
|
// BAD - non-public member without attribute
|
|
if (!member.IsMemberReadPublic() && attribute is null)
|
|
throw new MemberNotFoundException($"Member [{_markers[i]}] found, but its non-public without {nameof(DynamicValueProviderAttribute)}!", tempType);
|
|
|
|
// BAD - member is collection, collections aren't supported
|
|
if (typeof(Enumerable).IsAssignableFrom(member.GetMemberValueType()))
|
|
throw new MemberNotFoundException($"Member [{_markers[i]}] found, but it is collection. Collections aren't supported!", tempType);
|
|
|
|
tempType = member.GetMemberValueType();
|
|
|
|
if (member is not MethodInfo methodInfo)
|
|
{
|
|
// BAD - simple non-collection found, but marker has parameter
|
|
if (_parameters[i] != null)
|
|
throw new MemberNotFoundException($"Member [{_markers[i]}] found, but marker has parameter. Only methods can have parameters!", tempType);
|
|
|
|
// GOOD - simple non-collection field or property
|
|
_members[i] = member;
|
|
continue;
|
|
}
|
|
|
|
// BAD - is method, but without required attribute
|
|
if (attribute is null)
|
|
throw new MemberNotFoundException($"Member [{_markers[i]}] found, but without required {nameof(DynamicValueProviderAttribute)}!", tempType);
|
|
|
|
var validReturnType = methodInfo.ReturnType != typeof(void);
|
|
var parameter = methodInfo.GetParameters().FirstOrDefault();
|
|
var isStringParameter = parameter?.ParameterType == typeof(string);
|
|
var isTypeParameter = parameter?.ParameterType == typeof(Type);
|
|
|
|
// BAD - provider method has invalid definition
|
|
if (!validReturnType || (!isStringParameter && !isTypeParameter))
|
|
throw new MemberNotFoundException($"Member [{_markers[i]}] found, but definition is invalid! Must contain only one parameter <b>string</b> or <b>Type</b> and also must return something.", tempType);
|
|
|
|
_members[i] = methodInfo;
|
|
|
|
// GOOD - parameter is string, which means it is dynamic, only body of method can use it.
|
|
// Polymorphism is NOT supported here, return value is always method's return type
|
|
if (isStringParameter)
|
|
{
|
|
_membersParameters[i] = _parameters[i];
|
|
tempType = methodInfo.ReturnType;
|
|
continue;
|
|
}
|
|
|
|
// GOOD - parameter is Type, which we can match by its name.
|
|
// Polymorphism is supported here, dynamic value know type referenced by marker.
|
|
tempType = UnityEditor.TypeCache.GetTypesDerivedFrom(methodInfo.ReturnType)
|
|
.FirstOrDefault(t => t.Name.Equals(_parameters[i], StringComparison.OrdinalIgnoreCase));
|
|
_membersParameters[i] = tempType;
|
|
}
|
|
|
|
return _members.ToArray();
|
|
}
|
|
|
|
/// <summary>
|
|
/// <inheritdoc cref="GetMembers()"/>
|
|
/// </summary>
|
|
/// <param name="membersParameters">
|
|
/// <b>[Only when MethodInfo]</b> List of parameters that will be used for specific member.
|
|
/// Can be <b>string</b> or <b>Type</b>.
|
|
/// </param>
|
|
/// <inheritdoc cref="GetMembers()"/>
|
|
public MemberInfo[] GetMembers(out object[] membersParameters)
|
|
{
|
|
var members = GetMembers();
|
|
membersParameters = _membersParameters.ToArray();
|
|
return members;
|
|
}
|
|
}
|
|
} |