#nullable enable using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Reflection; namespace Barotrauma { public static class ReflectionUtils { private static readonly Dictionary> cachedNonAbstractTypes = new Dictionary>(); public static IEnumerable GetDerivedNonAbstract() { Assembly assembly = typeof(T).Assembly; lock (cachedNonAbstractTypes) { if (!cachedNonAbstractTypes.ContainsKey(assembly)) { AddNonAbstractAssemblyTypes(assembly); } } #warning TODO: Add safety checks in case an assembly is unloaded without being removed from the cache. return cachedNonAbstractTypes.Values.SelectMany(s => s.Where(t => t.IsSubclassOf(typeof(T)))); } /// /// Adds an assembly's Non-Abstract Types to the cache for Barotrauma's Type lookup. /// /// Assembly to be added /// Whether or not to overwrite an entry if the assembly already exists within it. public static void AddNonAbstractAssemblyTypes(Assembly assembly, bool overwrite = false) { if (cachedNonAbstractTypes.ContainsKey(assembly)) { if (!overwrite) { DebugConsole.LogError( $"ReflectionUtils::AddNonAbstractAssemblyTypes() | The assembly [{assembly.GetName()}] already exists in the cache."); return; } cachedNonAbstractTypes.Remove(assembly); } try { if (!cachedNonAbstractTypes.TryAdd(assembly, assembly.GetTypes().Where(t => !t.IsAbstract).ToImmutableArray())) { DebugConsole.LogError($"ReflectionUtils::AddNonAbstractAssemblyTypes() | Unable to add types from Assembly to cache."); } } catch (ReflectionTypeLoadException e) { DebugConsole.LogError($"ReflectionUtils::AddNonAbstractAssemblyTypes() | RTFException: Unable to load Assembly Types from {assembly.GetName()}."); } } /// /// Removes an assembly from the cache for Barotrauma's Type lookup. /// /// Assembly to remove. public static void RemoveAssemblyFromCache(Assembly assembly) => cachedNonAbstractTypes.Remove(assembly); public static Option ParseDerived(TInput input) where TInput : notnull { static Option none() => Option.None(); var derivedTypes = GetDerivedNonAbstract(); Option parseOfType(Type t) { //every TBase type is expected to have a method with the following signature: // public static Option Parse(TInput str) var parseFunc = t.GetMethod("Parse", BindingFlags.Public | BindingFlags.Static); if (parseFunc is null) { return none(); } var parameters = parseFunc.GetParameters(); if (parameters.Length != 1) { return none(); } var returnType = parseFunc.ReturnType; if (!returnType.IsConstructedGenericType) { return none(); } if (returnType.GetGenericTypeDefinition() != typeof(Option<>)) { return none(); } if (returnType.GenericTypeArguments[0] != t) { return none(); } //some hacky business to convert from Option to Option when we only know T2 at runtime static Option convert(Option option) where T2 : TBase => option.Select(v => (TBase)v); Func, Option> f = convert; var genericArgs = f.Method.GetGenericArguments(); genericArgs[^1] = t; var constructedConverter = f.Method.GetGenericMethodDefinition().MakeGenericMethod(genericArgs); return constructedConverter.Invoke(null, new[] { parseFunc.Invoke(null, new object[] { input }) }) as Option ?? none(); } return derivedTypes.Select(parseOfType).FirstOrDefault(t => t.IsSome()) ?? none(); } public static string NameWithGenerics(this Type t) { if (!t.IsGenericType) { return t.Name; } string result = t.Name[..t.Name.IndexOf('`')]; result += $"<{string.Join(", ", t.GetGenericArguments().Select(NameWithGenerics))}>"; return result; } } }