using System; using System.Collections.Generic; using System.Linq; using ICD.Common.Properties; using ICD.Common.Utils.Collections; using ICD.Common.Utils.Extensions; using ICD.Common.Utils.Services; using ICD.Common.Utils.Services.Logging; #if SIMPLSHARP using Crestron.SimplSharp.Reflection; #else using System.Reflection; #endif namespace ICD.Common.Utils { /// /// Utility methods for browsing code attributes. /// Provides some basic caching for faster subsequent searches. /// public static class AttributeUtils { // Avoid caching the same assembly multiple times. private static readonly IcdHashSet s_CachedAssemblies; private static readonly IcdHashSet s_CachedTypes; private static readonly Dictionary s_AttributeToMethodCache; private static readonly Dictionary s_AttributeToTypeCache; private static readonly Dictionary> s_TypeToAttributesCache; private static ILoggerService Logger { get { return ServiceProvider.TryGetService(); } } /// /// Constructor. /// static AttributeUtils() { s_CachedAssemblies = new IcdHashSet(); s_CachedTypes = new IcdHashSet(); s_AttributeToMethodCache = new Dictionary(); s_AttributeToTypeCache = new Dictionary(); s_TypeToAttributesCache = new Dictionary>(); } #region Caching /// /// Pre-emptively caches the given assemblies for lookup. /// /// public static void CacheAssemblies(IEnumerable assemblies) { if (assemblies == null) throw new ArgumentNullException("assemblies"); foreach (Assembly assembly in assemblies) CacheAssembly(assembly); } /// /// Pre-emptively caches the given assembly for lookup. /// /// public static bool CacheAssembly(Assembly assembly) { if (assembly == null) throw new ArgumentNullException("assembly"); if (s_CachedAssemblies.Contains(assembly)) return true; #if SIMPLSHARP CType[] types; #else Type[] types; #endif try { types = assembly.GetTypes(); } #if STANDARD catch (ReflectionTypeLoadException e) { foreach (Exception inner in e.LoaderExceptions) { if (inner is System.IO.FileNotFoundException) { Logger.AddEntry(eSeverity.Error, "{0} failed to cache assembly {1} - Could not find one or more dependencies by path", typeof(AttributeUtils).Name, assembly.GetName().Name); continue; } Logger.AddEntry(eSeverity.Error, inner, "{0} failed to cache assembly {1}", typeof(AttributeUtils).Name, assembly.GetName().Name); } return false; } #endif catch (TypeLoadException e) { #if SIMPLSHARP Logger.AddEntry(eSeverity.Error, e, "{0} failed to cache assembly {1}", typeof(AttributeUtils).Name, assembly.GetName().Name); #else Logger.AddEntry(eSeverity.Error, e, "{0} failed to cache assembly {1} - could not load type {2}", typeof(AttributeUtils).Name, assembly.GetName().Name, e.TypeName); #endif return false; } foreach (var type in types) CacheType(type); s_CachedAssemblies.Add(assembly); return true; } /// /// Pre-emptively caches the given type for lookup. /// /// #if SIMPLSHARP public static void CacheType(CType type) #else public static void CacheType(Type type) #endif { if (type == null) throw new ArgumentNullException("type"); if (s_CachedTypes.Contains(type)) return; s_CachedTypes.Add(type); MethodInfo[] methods; try { s_TypeToAttributesCache[type] = new IcdHashSet(type.GetCustomAttributes(false)); foreach (Attribute attribute in s_TypeToAttributesCache[type]) s_AttributeToTypeCache[attribute] = type; methods = type.GetMethods(); } // GetMethods for Open Generic Types is not supported. catch (NotSupportedException) { return; } // Not sure why this happens :/ catch (InvalidProgramException) { return; } foreach (MethodInfo method in methods) CacheMethod(method); } /// /// Caches the method. /// /// private static void CacheMethod(MethodInfo method) { if (method == null) throw new ArgumentNullException("method"); foreach (Attribute attribute in ReflectionExtensions.GetCustomAttributes(method, false)) s_AttributeToMethodCache[attribute] = method; } #endregion #region Lookup /// /// Gets the class attributes of the given generic type. /// /// /// public static IEnumerable GetClassAttributes() where T : Attribute { return s_AttributeToTypeCache.Select(kvp => kvp.Key) .OfType(); } /// /// Gets the first attribute on the given class type matching the generic type. /// /// /// /// [CanBeNull] public static T GetClassAttribute(Type type) where T : Attribute { if (type == null) throw new ArgumentNullException("type"); return GetClassAttributes(type).FirstOrDefault(); } /// /// Gets the attributes on the given class type matching the generic type. /// /// /// /// public static IEnumerable GetClassAttributes(Type type) where T : Attribute { if (type == null) throw new ArgumentNullException("type"); return GetClassAttributes(type).OfType(); } /// /// Gets the attributes on the given class. /// /// /// public static IEnumerable GetClassAttributes(Type type) { if (type == null) throw new ArgumentNullException("type"); CacheType(type); return s_TypeToAttributesCache.ContainsKey(type) ? s_TypeToAttributesCache[type].ToArray() : Enumerable.Empty(); } /// /// Gets the type with the given attribute. /// /// /// public static Type GetClass(Attribute attribute) { if (attribute == null) throw new ArgumentNullException("attribute"); return s_AttributeToTypeCache[attribute]; } /// /// Gets all of the cached method attributes of the given type. /// /// /// public static IEnumerable GetMethodAttributes() where T : Attribute { return s_AttributeToMethodCache.Select(p => p.Key) .OfType() .ToArray(); } /// /// Gets the cached method for the given attribute. /// /// /// public static MethodInfo GetMethod(Attribute attribute) { if (attribute == null) throw new ArgumentNullException("attribute"); return s_AttributeToMethodCache[attribute]; } #endregion } }