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; } } }