using System;
using System.Collections.Generic;
using System.Globalization;
using System.Reflection;
using System.Text.RegularExpressions;
using UnityEngine;
namespace Unity.XR.CoreUtils
{
///
/// Utility methods for common reflection-based operations.
///
public static class ReflectionUtils
{
static Assembly[] s_Assemblies;
static List s_TypesPerAssembly;
static List> s_AssemblyTypeMaps;
static Assembly[] GetCachedAssemblies() { return s_Assemblies ?? (s_Assemblies = AppDomain.CurrentDomain.GetAssemblies()); }
static List GetCachedTypesPerAssembly()
{
if (s_TypesPerAssembly == null)
{
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
s_TypesPerAssembly = new List(assemblies.Length);
foreach (var assembly in assemblies)
{
try
{
s_TypesPerAssembly.Add(assembly.GetTypes());
}
catch (ReflectionTypeLoadException)
{
// Skip any assemblies that don't load properly -- suppress errors
}
}
}
return s_TypesPerAssembly;
}
static List> GetCachedAssemblyTypeMaps()
{
if (s_AssemblyTypeMaps == null)
{
var typesPerAssembly = GetCachedTypesPerAssembly();
s_AssemblyTypeMaps = new List>(typesPerAssembly.Count);
foreach (var types in typesPerAssembly)
{
try
{
var typeMap = new Dictionary();
foreach (var type in types)
{
typeMap[type.FullName] = type;
}
s_AssemblyTypeMaps.Add(typeMap);
}
catch (ReflectionTypeLoadException)
{
// Skip any assemblies that don't load properly -- suppress errors
}
}
}
return s_AssemblyTypeMaps;
}
///
/// Caches type information from all currently loaded assemblies.
///
public static void PreWarmTypeCache() { GetCachedAssemblyTypeMaps(); }
///
/// Executes a delegate function for every assembly that can be loaded.
///
///
/// `ForEachAssembly` iterates through all assemblies and executes a method on each one.
/// If an is thrown, it is caught and ignored.
///
/// The callback method to execute for each assembly.
public static void ForEachAssembly(Action callback)
{
var assemblies = GetCachedAssemblies();
foreach (var assembly in assemblies)
{
try
{
callback(assembly);
}
catch (ReflectionTypeLoadException)
{
// Skip any assemblies that don't load properly -- suppress errors
}
}
}
///
/// Executes a delegate function for each type in every assembly.
///
/// The callback to execute.
public static void ForEachType(Action callback)
{
var typesPerAssembly = GetCachedTypesPerAssembly();
foreach (var types in typesPerAssembly)
{
foreach (var type in types)
{
callback(type);
}
}
}
///
/// Search all assemblies for a type that matches a given predicate delegate.
///
/// The predicate function. Must return for the type that matches the search.
/// The first type for which returns , or `null` if no matching type exists.
public static Type FindType(Func predicate)
{
var typesPerAssembly = GetCachedTypesPerAssembly();
foreach (var types in typesPerAssembly)
{
foreach (var type in types)
{
if (predicate(type))
return type;
}
}
return null;
}
///
/// Find a type in any assembly by its full name.
///
/// The name of the type as returned by .
/// The type found, or null if no matching type exists.
public static Type FindTypeByFullName(string fullName)
{
var typesPerAssembly = GetCachedAssemblyTypeMaps();
foreach (var assemblyTypes in typesPerAssembly)
{
if (assemblyTypes.TryGetValue(fullName, out var type))
return type;
}
return null;
}
///
/// Search all assemblies for a set of types that matches any one of a set of predicates.
///
///
/// This function tests each predicate against each type in each assembly. If the predicate returns
/// for a type, then that object is assigned to the corresponding index of
/// the . If a predicate returns for more than one type, then the
/// last matching result is used. If no type matches the predicate, then that index of
/// is left unchanged.
///
/// The predicate functions. A predicate function must return
/// for the type that matches the search and should only match one type.
/// The list to which found types will be added. The list must have
/// the same number of elements as the list.
public static void FindTypesBatch(List> predicates, List resultList)
{
var typesPerAssembly = GetCachedTypesPerAssembly();
for (var i = 0; i < predicates.Count; i++)
{
var predicate = predicates[i];
foreach (var assemblyTypes in typesPerAssembly)
{
foreach (var type in assemblyTypes)
{
if (predicate(type))
resultList[i] = type;
}
}
}
}
///
/// Searches all assemblies for a set of types by their strings.
///
///
/// If a type name in is not found, then the corresponding index of
/// is set to `null`.
///
/// A list containing the strings of the types to find.
/// An empty list to which any matching objects are added. A
/// result in has the same index as corresponding name in .
public static void FindTypesByFullNameBatch(List typeNames, List resultList)
{
var assemblyTypeMap = GetCachedAssemblyTypeMaps();
foreach (var typeName in typeNames)
{
var found = false;
foreach (var typeMap in assemblyTypeMap)
{
if (typeMap.TryGetValue(typeName, out var type))
{
resultList.Add(type);
found = true;
break;
}
}
// If a type can't be found, add a null entry to the list to ensure indexes match
if (!found)
resultList.Add(null);
}
}
///
/// Searches for a type by assembly simple name and its .
/// an assembly with the given simple name and returns the type with the given full name in that assembly
///
/// Simple name of the assembly ().
/// Full name of the type to find ().
/// The type if found, otherwise null
public static Type FindTypeInAssemblyByFullName(string assemblyName, string typeName)
{
var assemblies = GetCachedAssemblies();
var assemblyTypeMaps = GetCachedAssemblyTypeMaps();
for (var i = 0; i < assemblies.Length; i++)
{
if (assemblies[i].GetName().Name != assemblyName)
continue;
return assemblyTypeMaps[i].TryGetValue(typeName, out var type) ? type : null;
}
return null;
}
///
/// Cleans up a variable name for display in UI.
///
/// The variable name to clean up.
/// The display name for the variable.
public static string NicifyVariableName(string name)
{
if (name.StartsWith("m_"))
name = name.Substring(2, name.Length - 2);
else if (name.StartsWith("_"))
name = name.Substring(1, name.Length - 1);
if (name[0] == 'k' && name[1] >= 'A' && name[1] <= 'Z')
name = name.Substring(1, name.Length - 1);
// Insert a space before any capital letter unless it is the beginning or end of a word
name = Regex.Replace(name, @"(\B[A-Z]+?(?=[A-Z][^A-Z])|\B[A-Z]+?(?=[^A-Z]))", " $1");
name = CultureInfo.CurrentCulture.TextInfo.ToTitleCase(name);
return name;
}
}
}