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.");
}
}
}