using System; using System.Collections; using System.Collections.Generic; using System.ComponentModel; using System.Linq; namespace Mustache { internal static class UpcastDictionary { public static IDictionary Create(object source) { if (source == null) { return null; } if (source is IDictionary sourceDictionary) { return sourceDictionary; } Type sourceType = source.GetType(); var types = getTypes(sourceType); return getDictionary(types, source); } private static IEnumerable getTypes(Type sourceType) { Queue pending = new Queue(); HashSet visited = new HashSet(); pending.Enqueue(sourceType); while (pending.Count != 0) { Type type = pending.Dequeue(); visited.Add(type); yield return type; if (type.BaseType != null) { if (!visited.Contains(type.BaseType)) { pending.Enqueue(type.BaseType); } } foreach (Type interfaceType in type.GetInterfaces()) { if (!visited.Contains(interfaceType)) { pending.Enqueue(interfaceType); } } } } private static IDictionary getDictionary(IEnumerable types, object source) { var dictionaries = from type in types let valueType = getValueType(type) where valueType != null let upcastType = typeof(UpcastDictionary<>).MakeGenericType(valueType) select (IDictionary)Activator.CreateInstance(upcastType, source); return dictionaries.FirstOrDefault(); } private static Type getValueType(Type type) { if (!type.IsGenericType) { return null; } Type[] argumentTypes = type.GetGenericArguments(); if (argumentTypes.Length != 2) { return null; } Type keyType = argumentTypes[0]; if (keyType != typeof(string)) { return null; } Type valueType = argumentTypes[1]; Type genericType = typeof(IDictionary<,>).MakeGenericType(typeof(string), valueType); if (!genericType.IsAssignableFrom(type)) { return null; } return valueType; } } internal class UpcastDictionary : IDictionary { private readonly IDictionary dictionary; public UpcastDictionary(IDictionary dictionary) { this.dictionary = dictionary; } [EditorBrowsable(EditorBrowsableState.Never)] void IDictionary.Add(string key, object value) { throw new NotSupportedException(); } public bool ContainsKey(string key) { return dictionary.ContainsKey(key); } public ICollection Keys { get { return dictionary.Keys; } } [EditorBrowsable(EditorBrowsableState.Never)] bool IDictionary.Remove(string key) { throw new NotSupportedException(); } public bool TryGetValue(string key, out object value) { if (dictionary.TryGetValue(key, out TValue result)) { value = result; return true; } else { value = null; return false; } } public ICollection Values { get { return dictionary.Values.Cast().ToArray(); } } public object this[string key] { get { return dictionary[key]; } [EditorBrowsable(EditorBrowsableState.Never)] set { throw new NotSupportedException(); } } [EditorBrowsable(EditorBrowsableState.Never)] void ICollection>.Add(KeyValuePair item) { throw new NotSupportedException(); } [EditorBrowsable(EditorBrowsableState.Never)] void ICollection>.Clear() { throw new NotSupportedException(); } bool ICollection>.Contains(KeyValuePair item) { if (!(item.Value is TValue)) { return false; } KeyValuePair pair = new KeyValuePair(item.Key, (TValue)item.Value); ICollection> collection = dictionary; return dictionary.Contains(pair); } void ICollection>.CopyTo(KeyValuePair[] array, int arrayIndex) { var pairs = dictionary.Select(p => new KeyValuePair(p.Key, p.Value)).ToArray(); pairs.CopyTo(array, arrayIndex); } public int Count { get { return dictionary.Count; } } bool ICollection>.IsReadOnly { get { return true; } } [EditorBrowsable(EditorBrowsableState.Never)] bool ICollection>.Remove(KeyValuePair item) { throw new NotSupportedException(); } public IEnumerator> GetEnumerator() { return dictionary.Select(p => new KeyValuePair(p.Key, p.Value)).GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } } }