Skip to content

Commit

Permalink
chore: reflection utility refactoring. proper exception throws added (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
alexey-yaremenko authored Apr 12, 2023
1 parent 29e4dce commit 99a71f5
Show file tree
Hide file tree
Showing 2 changed files with 69 additions and 25 deletions.
84 changes: 64 additions & 20 deletions Runtime/Utilities/ReflectionUtility.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,60 +11,74 @@ namespace StansAssets.Foundation
/// </summary>
public static class ReflectionUtility
{
static readonly string[] s_BuiltInAssemblyPrefixes = { "Mono.", "Unity.", "UnityEngine", "UnityEditor", "System", "mscorlib" };
/// <summary>
/// Collection of predefined built-in assembly prefixes. <c>Mono.</c>, <c>UnityEditor.</c>, <c>Unity.</c>, <c>UnityEngine</c>, <c>System</c> and <c>mscorlib</c> prefixes included.
/// </summary>
public static readonly string[] BuiltInAssemblyPrefixes = { "Mono.", "UnityEditor.", "Unity.", "UnityEngine", "System", "mscorlib" };

/// <summary>
/// Creates an instance of the specified <see cref="System.Type"/> using that type's parameterless constructor.
/// </summary>
/// <param name="typeFullName">Full type name of the instance to create.</param>
/// <returns>New <see cref="System.Object"/> instance of the specified type.</returns>
/// <exception cref="ArgumentNullException"><paramref name="typeFullName"/> parameter is <c>null</c>.</exception>
/// <exception cref="ArgumentException"><see cref="System.Type"/> specified with <paramref name="typeFullName"/> doesn't have default parameterless constructor.</exception>
public static object CreateInstance(string typeFullName)
{
if (typeFullName == null)
throw new ArgumentNullException(nameof(typeFullName));

var type = FindType(typeFullName);
return type != null && type.HasDefaultConstructor()
? Activator.CreateInstance(type)
: null;
if (!type.HasDefaultConstructor())
throw new ArgumentException($"Type {typeFullName} doesn't have default parameterless constructor.");

return Activator.CreateInstance(type);
}

/// <summary>
/// Searches for the specified <see cref="System.Type"/> in all assemblies of the current application domain.
/// </summary>
/// <param name="typeFullName">Full type's name to search for.</param>
/// <returns><see cref="System.Type"/> object found via specified <paramref name="typeFullName"/>.</returns>
/// <exception cref="ArgumentNullException"><paramref name="typeFullName"/> parameter is <c>null</c>.</exception>
/// <exception cref="InvalidOperationException">No types matching <paramref name="typeFullName"/> found in current application domain.</exception>
public static Type FindType(string typeFullName)
{
if (typeFullName == null)
throw new ArgumentNullException(nameof(typeFullName));

var assemblies = AppDomain.CurrentDomain.GetAssemblies();
return assemblies
.SelectMany(assembly => assembly.GetTypes())
.FirstOrDefault(type => type.FullName == null || type.FullName.Equals(typeFullName));
.First(type => type.FullName == null || type.FullName.Equals(typeFullName));
}

/// <summary>
/// Searched for the implementations of the <see cref="System.Type"/> specified with <typeparamref name="T"/>.
/// </summary>
/// <param name="ignoreBuiltIn"><c>true</c> if the built-in assemblies have to be skipped. If set to <c>false</c>, no assemblies will be skipped.</param>
/// <param name="ignoreAssemblyPrefixes">Collection of assembly prefixes to skip. The <see cref="System.Reflection.Assembly"/> will be ignored if its name <i><b>starts with</b></i> one of these prefixes.</param>
/// <typeparam name="T">Specifies the <see cref="System.Type"/> whose implementations to search for.</typeparam>
/// <returns>A collection of <see cref="System.Type"/> objects that are implementations of <typeparamref name="T"/>.</returns>
public static IEnumerable<Type> FindImplementationsOf<T>(bool ignoreBuiltIn = false)
public static IEnumerable<Type> FindImplementationsOf<T>(IEnumerable<string> ignoreAssemblyPrefixes = null)
{
var baseType = typeof(T);
return FindImplementationsOf(baseType, ignoreBuiltIn);
return FindImplementationsOf(baseType, ignoreAssemblyPrefixes);
}

/// <summary>
/// Gets the assemblies that have been loaded into the execution context of this application domain.
/// </summary>
/// <param name="ignoreBuiltIn"><c>true</c> if the built-in assemblies have to be skipped. If set to <c>false</c>, no assemblies will be skipped.</param>
/// <param name="ignoreAssemblyPrefixes">Collection of assembly prefixes to skip. The <see cref="System.Reflection.Assembly"/> will be ignored if its name <i><b>starts with</b></i> one of these prefixes.</param>
/// <returns>A collection of assemblies in this application domain.</returns>
public static IEnumerable<Assembly> GetAssemblies(bool ignoreBuiltIn = false)
public static IEnumerable<Assembly> GetAssemblies(IEnumerable<string> ignoreAssemblyPrefixes = null)
{
IEnumerable<Assembly> assemblies = AppDomain.CurrentDomain.GetAssemblies();

if (ignoreBuiltIn)
if (ignoreAssemblyPrefixes != null)
{
assemblies = assemblies.Where(assembly => {
var assemblyName = assembly.GetName().Name;
return !s_BuiltInAssemblyPrefixes.Any(prefix => assemblyName.StartsWith(prefix));
return !BuiltInAssemblyPrefixes.Any(prefix => assemblyName.StartsWith(prefix));
});
}

Expand All @@ -75,11 +89,15 @@ public static IEnumerable<Assembly> GetAssemblies(bool ignoreBuiltIn = false)
/// Searched for the implementations of the <see cref="System.Type"/> specified with <paramref name="baseType"/>.
/// </summary>
/// <param name="baseType">Specifies the <see cref="System.Type"/> whose implementations to search for.</param>
/// <param name="ignoreBuiltIn"><c>true</c> if the built-in assemblies have to be skipped. If set to <c>false</c>, no assemblies will be skipped.</param>
/// <param name="ignoreAssemblyPrefixes">Collection of assembly prefixes to skip. The <see cref="System.Reflection.Assembly"/> will be ignored if its name <i><b>starts with</b></i> one of these prefixes.</param>
/// <returns>A collection of <see cref="System.Type"/> objects that are implementations of <paramref name="baseType"/>.</returns>
public static IEnumerable<Type> FindImplementationsOf(Type baseType, bool ignoreBuiltIn = false)
/// <exception cref="ArgumentNullException"><paramref name="baseType"/> parameter is <c>null</c>.</exception>
public static IEnumerable<Type> FindImplementationsOf(Type baseType, IEnumerable<string> ignoreAssemblyPrefixes = null)
{
var assemblies = GetAssemblies(ignoreBuiltIn);
if (baseType == null)
throw new ArgumentNullException(nameof(baseType));

var assemblies = GetAssemblies(ignoreAssemblyPrefixes);

return assemblies
.SelectMany(assembly => assembly.GetTypes())
Expand All @@ -93,9 +111,22 @@ public static IEnumerable<Type> FindImplementationsOf(Type baseType, bool ignore
/// <param name="propName">The string containing the name of the public property to get.</param>
/// <param name="bindingAttr">A bitwise combination of the enumeration values that specify how the search is conducted.</param>
/// <returns>The property value of the specified object.</returns>
/// <exception cref="ArgumentNullException"><paramref name="propName"/> parameter is <c>null</c>.</exception>
/// <exception cref="TargetException">The target <paramref name="src"/> object is <c>null</c>.</exception>
/// <exception cref="ArgumentException">Property specified with the <paramref name="propName"/> not found.</exception>
public static object GetPropertyValue(object src, string propName, BindingFlags bindingAttr = BindingFlags.Instance | BindingFlags.Public)
{
return src.GetType().GetProperty(propName, bindingAttr)?.GetValue(src, null);
if (propName == null)
throw new ArgumentNullException(nameof(propName));

if (src == null)
throw new TargetException($"Target {nameof(src)} object is null.");

var property = src.GetType().GetProperty(propName, bindingAttr);
if (property == null)
throw new ArgumentException($"Property with '{propName}' name not found");

return property.GetValue(src, null);
}

/// <summary>
Expand All @@ -106,22 +137,35 @@ public static object GetPropertyValue(object src, string propName, BindingFlags
/// <param name="propValue">The new property value.</param>
/// <param name="bindingAttr">A bitwise combination of the enumeration values that specify how the search is conducted.</param>
/// <typeparam name="T">Specifies the <see cref="System.Type"/> of property value to set.</typeparam>
/// <exception cref="ArgumentNullException"><paramref name="propName"/> parameter is <c>null</c>.</exception>
/// <exception cref="TargetException">The target <paramref name="src"/> object is <c>null</c>.</exception>
/// <exception cref="ArgumentException">Property specified with the <paramref name="propName"/> not found.</exception>
public static void SetPropertyValue<T>(object src, string propName, T propValue, BindingFlags bindingAttr = BindingFlags.Instance | BindingFlags.Public)
{
src.GetType().GetProperty(propName, bindingAttr)?.SetValue(src, propValue);
if (propName == null)
throw new ArgumentNullException(nameof(propName));

if (src == null)
throw new TargetException($"Target {nameof(src)} object is null.");

var property = src.GetType().GetProperty(propName, bindingAttr);
if (property == null)
throw new ArgumentException($"Property with '{propName}' name not found");

property.SetValue(src, propValue);
}

/// <summary>
/// Searches for the methods with custom attribute of type <typeparamref name="T"/>.
/// </summary>
/// <param name="methodBindingFlags">A bitwise combination of the enumeration values that specify how the search is conducted.</param>
/// <param name="inherit"><c>true</c> to search this member's inheritance chain to find the attributes; otherwise, <c>false</c>.</param>
/// <param name="ignoreBuiltIn"><c>true</c> if the built-in assemblies have to be skipped. If set to <c>false</c>, no assemblies will be skipped.</param>
/// <param name="ignoreAssemblyPrefixes">Collection of assembly prefixes to skip. The <see cref="System.Reflection.Assembly"/> will be ignored if its name <i><b>starts with</b></i> one of these prefixes.</param>
/// <typeparam name="T">Specifies the <see cref="System.Type"/> of the custom attribute to search for.</typeparam>
/// <returns>A collection of <see cref="System.Reflection.MethodInfo"/> objects representing all methods defined for the current <see cref="System.Type"/> that match the specified binding constraints and attributes type.</returns>
public static IEnumerable<MethodInfo> FindMethodsWithCustomAttributes<T>(BindingFlags methodBindingFlags = BindingFlags.Instance | BindingFlags.Public, bool inherit = true, bool ignoreBuiltIn = false) where T : Attribute
public static IEnumerable<MethodInfo> FindMethodsWithCustomAttributes<T>(BindingFlags methodBindingFlags = BindingFlags.Instance | BindingFlags.Public, bool inherit = true, IEnumerable<string> ignoreAssemblyPrefixes = null) where T : Attribute
{
var assemblies = GetAssemblies(ignoreBuiltIn);
var assemblies = GetAssemblies(ignoreAssemblyPrefixes);

return assemblies
.SelectMany(assembly => assembly.GetTypes())
Expand Down
10 changes: 5 additions & 5 deletions Tests/Editor/Utilities/ReflectionUtilityTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ public void CreateInstanceValid(Type type)
[TestCase(typeof(ClassWithPrivateConstructor))]
public void CreateInstanceInvalid(Type type)
{
Assert.IsNull(ReflectionUtility.CreateInstance(type.FullName), $"Failed to create instance for:{type.FullName}");
Assert.Throws<ArgumentException>(() => ReflectionUtility.CreateInstance(type.FullName), $"Failed to create instance for:{type.FullName}");
}

[TestCase("System.Int32")]
Expand All @@ -108,7 +108,7 @@ public void FindTypeValid(string fullTypeName)
[TestCase("System.ClassDoesNotExist")]
public void FindTypeInvalid(string fullTypeName)
{
Assert.IsNull(ReflectionUtility.FindType(fullTypeName), $"Type {fullTypeName} not found");
Assert.Throws<InvalidOperationException>(() => ReflectionUtility.FindType(fullTypeName), $"Type {fullTypeName} should NOT be found in this case");
}

class TestAssembly
Expand Down Expand Up @@ -148,7 +148,7 @@ public void GetAllAssemblies()
[Test]
public void GetAllAssembliesWithoutBuiltIn()
{
var assemblies = ReflectionUtility.GetAssemblies(true).ToList();
var assemblies = ReflectionUtility.GetAssemblies(ReflectionUtility.BuiltInAssemblyPrefixes).ToList();
Assert.True(assemblies.Any(), "Assemblies collection is empty");

var assembliesSearchMap = new Dictionary<TestAssembly, bool>();
Expand Down Expand Up @@ -270,8 +270,8 @@ public void SetPropertyValue()
[Test]
public void FindMethodsWithCustomAttributes()
{
var allMethodInfos = ReflectionUtility.FindMethodsWithCustomAttributes<FancyAttribute>(ignoreBuiltIn: true).ToList();
var nonInheritedMethodInfos = ReflectionUtility.FindMethodsWithCustomAttributes<FancyAttribute>(inherit: false, ignoreBuiltIn: true).ToList();
var allMethodInfos = ReflectionUtility.FindMethodsWithCustomAttributes<FancyAttribute>(ignoreAssemblyPrefixes: ReflectionUtility.BuiltInAssemblyPrefixes).ToList();
var nonInheritedMethodInfos = ReflectionUtility.FindMethodsWithCustomAttributes<FancyAttribute>(inherit: false, ignoreAssemblyPrefixes: ReflectionUtility.BuiltInAssemblyPrefixes).ToList();

Assert.True(allMethodInfos.Count == 2, "Incorrect amount of methods with custom attribute (inheritances included)");
Assert.True(nonInheritedMethodInfos.Count == 1, "Incorrect amount of methods with custom attribute (inheritances NOT included)");
Expand Down

0 comments on commit 99a71f5

Please sign in to comment.