using System; using UnityEditor; using System.Linq; using System.Text; using System.Reflection; using System.Collections; namespace EditorAttributes.Editor.Utility { public static class ReflectionUtility { public const BindingFlags BINDING_FLAGS = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.FlattenHierarchy; /// /// Finds a field inside a serialized object /// /// The name of the field to search /// The serialized property /// The field info of the desired field public static FieldInfo FindField(string fieldName, SerializedProperty property) { if (fieldName.Contains('.')) return GetStaticMemberInfoFromPath(fieldName, MemberTypes.Field) as FieldInfo; var fieldInfo = FindField(fieldName, property.serializedObject.targetObject); // If the field null we try to see if its inside a serialized object if (fieldInfo == null) { var serializedObjectType = GetNestedObjectType(property, out _); if (serializedObjectType != null) fieldInfo = serializedObjectType.GetField(fieldName, BINDING_FLAGS); } return fieldInfo; } internal static FieldInfo FindField(string fieldName, object targetObject) => FindMember(fieldName, targetObject?.GetType(), BINDING_FLAGS, MemberTypes.Field) as FieldInfo; /// /// Finds a property inside a serialized object /// /// The name of the property to search /// The serialized property /// The property info of the desired property public static PropertyInfo FindProperty(string propertyName, SerializedProperty property) { if (propertyName.Contains('.')) return GetStaticMemberInfoFromPath(propertyName, MemberTypes.Property) as PropertyInfo; var propertyInfo = FindProperty(propertyName, property.serializedObject.targetObject); // If the property null we try to see if its inside a serialized object if (propertyInfo == null) { var serializedObjectType = GetNestedObjectType(property, out _); if (serializedObjectType != null) propertyInfo = serializedObjectType.GetProperty(propertyName, BINDING_FLAGS); } return propertyInfo; } internal static PropertyInfo FindProperty(string propertyName, object targetObject) => FindMember(propertyName, targetObject?.GetType(), BINDING_FLAGS, MemberTypes.Property) as PropertyInfo; /// /// Finds a funciton inside a serialized object /// /// The name of the function to search /// The serialized property /// The method info of the desired function public static MethodInfo FindFunction(string functionName, SerializedProperty property) { if (functionName.Contains('.')) return GetStaticMemberInfoFromPath(functionName, MemberTypes.Method) as MethodInfo; MethodInfo methodInfo; methodInfo = FindFunction(functionName, property.serializedObject.targetObject); // If the method is null we try to see if its inside a serialized object if (methodInfo == null) { var serializedObjectType = GetNestedObjectType(property, out _); if (serializedObjectType == null) return methodInfo; try { methodInfo = serializedObjectType.GetMethod(functionName, BINDING_FLAGS); } catch (AmbiguousMatchException) { var functions = serializedObjectType.GetMethods(); foreach (var function in functions) { if (function.Name == functionName) methodInfo = function; } } } return methodInfo; } internal static MethodInfo FindFunction(string functionName, object targetObject) { try { return FindMember(functionName, targetObject?.GetType(), BINDING_FLAGS, MemberTypes.Method) as MethodInfo; } catch (AmbiguousMatchException) { var functions = targetObject?.GetType().GetMethods(); foreach (var function in functions) { if (function.Name == functionName) return function; } return null; } } /// /// Finds a member from the target and it's inherited types /// /// The name of the member to look for /// The type to get the member from /// The binding flags /// The type of the member to look for. Only Field, Property and Method types are supported /// The member info of the specified member type public static MemberInfo FindMember(string memberName, Type targetType, BindingFlags bindingFlags, MemberTypes memberType) { MemberInfo memberInfo = null; while (targetType != null) { switch (memberType) { case MemberTypes.Field: memberInfo = targetType.GetField(memberName, bindingFlags); break; case MemberTypes.Property: memberInfo = targetType.GetProperty(memberName, bindingFlags); break; case MemberTypes.Method: memberInfo = targetType.GetMethod(memberName, bindingFlags); break; } if (memberInfo != null) return memberInfo; targetType = targetType.BaseType; } return null; } /// /// Gets the info of a const or static member from the type specified in the path /// /// The path on which to locate the member /// The type of the member to look for. Only Field, Property and Method types are supported /// The member info of the specified member type public static MemberInfo GetStaticMemberInfoFromPath(string memberPath, MemberTypes memberTypes) { MemberInfo memberInfo = null; string[] splitPath = memberPath.Split('.'); string typeNamespace = GetNamespaceString(splitPath); string typeName = splitPath[^2]; string actualFieldName = splitPath[^1]; var matchingTypes = TypeCache.GetTypesDerivedFrom().Where((type) => type.Name == typeName && type.Namespace == typeNamespace); foreach (var type in matchingTypes) { memberInfo = FindMember(actualFieldName, type, BINDING_FLAGS ^ BindingFlags.Instance, memberTypes); if (memberInfo == null) { continue; } else { break; } } return memberInfo; } private static string GetNamespaceString(string[] splitMemberPath) { var stringBuilder = new StringBuilder(); string[] namespacePath = splitMemberPath[..^2]; for (int i = 0; i < namespacePath.Length; i++) { stringBuilder.Append(namespacePath[i]); if (i != namespacePath.Length - 1) stringBuilder.Append('.'); } return stringBuilder.Length == 0 ? null : stringBuilder.ToString(); } /// /// Tries to get a field from the target type /// /// The name of the field to search for /// The type to get the field from /// The binding flags /// The field info of the desired field /// True if the field was succesfully found, false otherwise public static bool TryGetField(string name, Type targetType, BindingFlags bindingFlags, out FieldInfo fieldInfo) { fieldInfo = targetType.GetField(name, bindingFlags); return fieldInfo != null; } /// /// Tries to get a property from the target type /// /// The name of the property to search for /// The type to get the property from /// The binding flags /// The property info of the desired property /// True if the property was succesfully found, false otherwise public static bool TryGetProperty(string name, Type targetType, BindingFlags bindingFlags, out PropertyInfo propertyInfo) { propertyInfo = targetType.GetProperty(name, bindingFlags); return propertyInfo != null; } /// /// Tries to get a function from the target type /// /// The name of the function to search for /// The type to get the function from /// The binding flags /// The method info of the desired function /// True if the function was succesfully found, false otherwise public static bool TryGetMethod(string name, Type targetType, BindingFlags bindingFlags, out MethodInfo methodInfo) { methodInfo = targetType.GetMethod(name, bindingFlags); return methodInfo != null; } /// /// Checks to see if a type is a list or array /// /// The type to check /// True if the type is a list or array public static bool IsTypeCollection(Type type) => type.IsArray || type.GetInterfaces().Contains(typeof(IList)); /// /// Checks to see if a member has one of the specified attributes /// /// The member to check /// The attribute types /// True if the member has at least one of specified attributes public static bool HasAnyAttributes(MemberInfo memberInfo, params Type[] attributeTypes) { if (memberInfo == null) return false; foreach (var attribute in attributeTypes) { if (memberInfo.GetCustomAttribute(attribute) != null) return true; } return false; } /// /// Finds a member inside a serialzied object /// /// The name of the member to look for /// The serialized property /// The member info of the member public static MemberInfo GetValidMemberInfo(string memberName, SerializedProperty serializedProperty) { MemberInfo memberInfo; memberInfo = FindField(memberName, serializedProperty); memberInfo ??= FindProperty(memberName, serializedProperty); memberInfo ??= FindFunction(memberName, serializedProperty); return memberInfo; } internal static MemberInfo GetValidMemberInfo(string memberName, object targetObject) { MemberInfo memberInfo; memberInfo = FindField(memberName, targetObject); memberInfo ??= FindProperty(memberName, targetObject); memberInfo ??= FindFunction(memberName, targetObject); return memberInfo; } /// /// Gets the type of a nested serialized object /// /// The serialized property /// Outputs the serialized nested object /// The nested object type public static Type GetNestedObjectType(SerializedProperty property, out object nestedObject) { try { nestedObject = property.serializedObject.targetObject; int cutPathIndex = property.propertyPath.LastIndexOf('.'); if (cutPathIndex == -1) // If the cutPathIndex is -1 it means that the member is not nested and we return null return null; string path = property.propertyPath[..cutPathIndex].Replace(".Array.data[", "["); string[] elements = path.Split('.'); foreach (var element in elements) { if (element.Contains("[")) { var elementName = element[..element.IndexOf("[")]; var index = Convert.ToInt32(element[element.IndexOf("[")..].Replace("[", "").Replace("]", "")); nestedObject = GetValue(nestedObject, elementName, index); } else { nestedObject = GetValue(nestedObject, element); } } return nestedObject?.GetType(); } catch (ObjectDisposedException) { nestedObject = null; return null; } } private static object GetValue(object source, string name, int index) { if (GetValue(source, name) is not IEnumerable enumerable) return null; var enumerator = enumerable.GetEnumerator(); for (int i = 0; i <= index; i++) { if (!enumerator.MoveNext()) return null; } return enumerator.Current; } private static object GetValue(object source, string name) { if (source == null) return null; var type = source.GetType(); while (type != null) { var field = FindMember(name, type, BINDING_FLAGS, MemberTypes.Field) as FieldInfo; if (field != null) return field.GetValue(source); type = type.BaseType; } return null; } /// /// Gets the type of a member /// /// The member to get the type from /// The type of the member public static Type GetMemberInfoType(MemberInfo memberInfo) { if (memberInfo is FieldInfo fieldInfo) { return fieldInfo.FieldType; } else if (memberInfo is PropertyInfo propertyInfo) { return propertyInfo.PropertyType; } else if (memberInfo is MethodInfo methodInfo) { return methodInfo.ReturnType; } return null; } /// /// Gets the value of a member /// /// The member to get the value from /// The serialized property /// The value of the member public static object GetMemberInfoValue(MemberInfo memberInfo, SerializedProperty property) { var targetObject = property.serializedObject.targetObject; if (targetObject == null) return null; try { if (memberInfo is FieldInfo fieldInfo) { return fieldInfo.GetValue(targetObject); } else if (memberInfo is PropertyInfo propertyInfo) { return propertyInfo.GetValue(targetObject); } else if (memberInfo is MethodInfo methodInfo) { return methodInfo.Invoke(targetObject, null); } } catch (Exception exception) { if (exception is ArgumentException or TargetException) // If these expections are thrown it means that the member we try to get the value from is inside a different target { GetNestedObjectType(property, out object serializedObjectTarget); if (serializedObjectTarget != null) { if (memberInfo is FieldInfo fieldInfo) { return fieldInfo.GetValue(serializedObjectTarget); } else if (memberInfo is PropertyInfo propertyInfo) { return propertyInfo.GetValue(serializedObjectTarget); } else if (memberInfo is MethodInfo methodInfo) { return methodInfo.Invoke(serializedObjectTarget, null); } } } else { throw; } } return null; } internal static object GetMemberInfoValue(MemberInfo memberInfo, object targetObject) { if (targetObject == null) return null; if (memberInfo is FieldInfo fieldInfo) { return fieldInfo.GetValue(targetObject); } else if (memberInfo is PropertyInfo propertyInfo) { return propertyInfo.GetValue(targetObject); } else if (memberInfo is MethodInfo methodInfo) { return methodInfo.Invoke(targetObject, null); } return null; } } }