using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using Robust.Shared.Interfaces.Reflection; using Robust.Shared.Log; using Robust.Shared.ViewVariables; namespace Robust.Shared.Reflection { public abstract class ReflectionManager : IReflectionManager { /// /// Enumerable over prefixes that are added to the type provided to /// if the type can't be found in any assemblies. /// /// /// First prefix should probably be "". /// protected abstract IEnumerable TypePrefixes { get; } private readonly List assemblies = new List(); public event EventHandler OnAssemblyAdded; [ViewVariables] public IReadOnlyList Assemblies => assemblies; /// public IEnumerable GetAllChildren(bool inclusive = false) { return GetAllChildren(typeof(T), inclusive); } /// public IEnumerable GetAllChildren(Type baseType, bool inclusive = false) { var typeLists = new List(Assemblies.Count); try { // There's very little assemblies, so storing these temporarily is cheap. // We need to do it ahead of time so that we can catch ReflectionTypeLoadException HERE, // so whoever is using us doesn't have to handle them. foreach (var assembly in Assemblies) { typeLists.Add(assembly.GetTypes()); } } catch (ReflectionTypeLoadException e) { Logger.Error("Caught ReflectionTypeLoadException! Dumping child exceptions:"); foreach (var inner in e.LoaderExceptions) { Logger.Error(inner.ToString()); } throw; } foreach (var t in typeLists) { foreach (var type in t) { if (!baseType.IsAssignableFrom(type) || type.IsAbstract) { continue; } var attribute = (ReflectAttribute) Attribute.GetCustomAttribute(type, typeof(ReflectAttribute)); if (!(attribute?.Discoverable ?? ReflectAttribute.DEFAULT_DISCOVERABLE)) { continue; } if (baseType == type && !inclusive) { continue; } yield return type; } } } public void LoadAssemblies(params Assembly[] args) => LoadAssemblies(args.AsEnumerable()); public void LoadAssemblies(IEnumerable assemblies) { this.assemblies.AddRange(assemblies); OnAssemblyAdded?.Invoke(this, new ReflectionUpdateEventArgs(this)); } /// public Type GetType(string name) { // The priority in which types are retrieved is based on the TypePrefixes list. // This is an implementation detail. If you need it: make a better API. foreach (string prefix in TypePrefixes) { string appendedName = prefix + name; foreach (var assembly in Assemblies) { var theType = assembly.GetType(appendedName); if (theType != null) { return theType; } } } return null; } /// public Type LooseGetType(string name) { if (TryLooseGetType(name, out var ret)) { return ret; } throw new ArgumentException("Unable to find type."); } public bool TryLooseGetType(string name, out Type type) { foreach (var assembly in assemblies) { foreach (var tryType in assembly.DefinedTypes) { if (tryType.FullName.EndsWith(name)) { type = tryType; return true; } } } type = default; return false; } /// public IEnumerable FindTypesWithAttribute() { var types = new List(); foreach (var assembly in Assemblies) { types.AddRange(assembly.GetTypes().Where(type => Attribute.IsDefined(type, typeof(T)))); } return types; } /// public bool TryParseEnumReference(string reference, out Enum @enum) { if (!reference.StartsWith("enum.")) { @enum = default; return false; } reference = reference.Substring(5); var dotIndex = reference.LastIndexOf('.'); var typeName = reference.Substring(0, dotIndex); var value = reference.Substring(dotIndex + 1); foreach (var assembly in assemblies) { foreach (var type in assembly.DefinedTypes) { if (!type.IsEnum || !type.FullName.EndsWith(typeName)) { continue; } @enum = (Enum) Enum.Parse(type, value); return true; } } throw new ArgumentException("Could not resolve enum reference."); } } }