#nullable enable using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Reflection; namespace Swan.Reflection { /// <summary> /// A thread-safe cache of attributes belonging to a given key (MemberInfo or Type). /// /// The Retrieve method is the most useful one in this class as it /// calls the retrieval process if the type is not contained /// in the cache. /// </summary> public class AttributeCache { private readonly Lazy<ConcurrentDictionary<Tuple<Object, Type>, IEnumerable<Object>>> _data = new Lazy<ConcurrentDictionary<Tuple<Object, Type>, IEnumerable<Object>>>(() => new ConcurrentDictionary<Tuple<Object, Type>, IEnumerable<Object>>(), true); /// <summary> /// Initializes a new instance of the <see cref="AttributeCache"/> class. /// </summary> /// <param name="propertyCache">The property cache object.</param> public AttributeCache(PropertyTypeCache? propertyCache = null) => this.PropertyTypeCache = propertyCache ?? PropertyTypeCache.DefaultCache.Value; /// <summary> /// Gets the default cache. /// </summary> /// <value> /// The default cache. /// </value> public static Lazy<AttributeCache> DefaultCache { get; } = new Lazy<AttributeCache>(() => new AttributeCache()); /// <summary> /// A PropertyTypeCache object for caching properties and their attributes. /// </summary> public PropertyTypeCache PropertyTypeCache { get; } /// <summary> /// Determines whether [contains] [the specified member]. /// </summary> /// <typeparam name="T">The type of the attribute to be retrieved.</typeparam> /// <param name="member">The member.</param> /// <returns> /// <c>true</c> if [contains] [the specified member]; otherwise, <c>false</c>. /// </returns> public Boolean Contains<T>(MemberInfo member) => this._data.Value.ContainsKey(new Tuple<Object, Type>(member, typeof(T))); /// <summary> /// Gets specific attributes from a member constrained to an attribute. /// </summary> /// <typeparam name="T">The type of the attribute to be retrieved.</typeparam> /// <param name="member">The member.</param> /// <param name="inherit"><c>true</c> to inspect the ancestors of element; otherwise, <c>false</c>.</param> /// <returns>An array of the attributes stored for the specified type.</returns> public IEnumerable<Object> Retrieve<T>(MemberInfo member, Boolean inherit = false) where T : Attribute { if(member == null) { throw new ArgumentNullException(nameof(member)); } return this.Retrieve(new Tuple<Object, Type>(member, typeof(T)), t => member.GetCustomAttributes<T>(inherit)); } /// <summary> /// Gets all attributes of a specific type from a member. /// </summary> /// <param name="member">The member.</param> /// <param name="type">The attribute type.</param> /// <param name="inherit"><c>true</c> to inspect the ancestors of element; otherwise, <c>false</c>.</param> /// <returns>An array of the attributes stored for the specified type.</returns> public IEnumerable<Object> Retrieve(MemberInfo member, Type type, Boolean inherit = false) { if(member == null) { throw new ArgumentNullException(nameof(member)); } if(type == null) { throw new ArgumentNullException(nameof(type)); } return this.Retrieve(new Tuple<Object, Type>(member, type), t => member.GetCustomAttributes(type, inherit)); } /// <summary> /// Gets one attribute of a specific type from a member. /// </summary> /// <typeparam name="T">The attribute type.</typeparam> /// <param name="member">The member.</param> /// <param name="inherit"><c>true</c> to inspect the ancestors of element; otherwise, <c>false</c>.</param> /// <returns>An attribute stored for the specified type.</returns> public T RetrieveOne<T>(MemberInfo member, Boolean inherit = false) where T : Attribute { if(member == null) { return default!; } IEnumerable<Object> attr = this.Retrieve(new Tuple<Object, Type>(member, typeof(T)), t => member.GetCustomAttributes(typeof(T), inherit)); return ConvertToAttribute<T>(attr); } /// <summary> /// Gets one attribute of a specific type from a generic type. /// </summary> /// <typeparam name="TAttribute">The type of the attribute.</typeparam> /// <typeparam name="T">The type to retrieve the attribute.</typeparam> /// <param name="inherit">if set to <c>true</c> [inherit].</param> /// <returns>An attribute stored for the specified type.</returns> public TAttribute RetrieveOne<TAttribute, T>(Boolean inherit = false) where TAttribute : Attribute { IEnumerable<Object> attr = this.Retrieve(new Tuple<Object, Type>(typeof(T), typeof(TAttribute)), t => typeof(T).GetCustomAttributes(typeof(TAttribute), inherit)); return ConvertToAttribute<TAttribute>(attr); } /// <summary> /// Gets all properties an their attributes of a given type constrained to only attributes. /// </summary> /// <typeparam name="T">The type of the attribute to retrieve.</typeparam> /// <param name="type">The type of the object.</param> /// <param name="inherit"><c>true</c> to inspect the ancestors of element; otherwise, <c>false</c>.</param> /// <returns>A dictionary of the properties and their attributes stored for the specified type.</returns> public Dictionary<PropertyInfo, IEnumerable<Object>> Retrieve<T>(Type type, Boolean inherit = false) where T : Attribute => this.PropertyTypeCache.RetrieveAllProperties(type, true).ToDictionary(x => x, x => this.Retrieve<T>(x, inherit)); /// <summary> /// Gets all properties and their attributes of a given type. /// </summary> /// <typeparam name="T">The object type used to extract the properties from.</typeparam> /// <typeparam name="TAttribute">The type of the attribute.</typeparam> /// <param name="inherit"><c>true</c> to inspect the ancestors of element; otherwise, <c>false</c>.</param> /// <returns> /// A dictionary of the properties and their attributes stored for the specified type. /// </returns> public Dictionary<PropertyInfo, IEnumerable<Object>> RetrieveFromType<T, TAttribute>(Boolean inherit = false) => this.RetrieveFromType<T>(typeof(TAttribute), inherit); /// <summary> /// Gets all properties and their attributes of a given type. /// </summary> /// <typeparam name="T">The object type used to extract the properties from.</typeparam> /// <param name="attributeType">Type of the attribute.</param> /// <param name="inherit"><c>true</c> to inspect the ancestors of element; otherwise, <c>false</c>.</param> /// <returns> /// A dictionary of the properties and their attributes stored for the specified type. /// </returns> public Dictionary<PropertyInfo, IEnumerable<Object>> RetrieveFromType<T>(Type attributeType, Boolean inherit = false) { if(attributeType == null) { throw new ArgumentNullException(nameof(attributeType)); } return this.PropertyTypeCache.RetrieveAllProperties<T>(true).ToDictionary(x => x, x => this.Retrieve(x, attributeType, inherit)); } private static T ConvertToAttribute<T>(IEnumerable<Object> attr) where T : Attribute => attr?.Any() != true ? (default!) : attr.Count() == 1 ? (T)Convert.ChangeType(attr.First(), typeof(T)) : throw new AmbiguousMatchException("Multiple custom attributes of the same type found."); private IEnumerable<Object> Retrieve(Tuple<Object, Type> key, Func<Tuple<Object, Type>, IEnumerable<Object>> factory) { if(factory == null) { throw new ArgumentNullException(nameof(factory)); } return this._data.Value.GetOrAdd(key, k => factory.Invoke(k).Where(item => item != null)); } } }