// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using Microsoft.AspNetCore.Dispatcher.Abstractions; using Microsoft.Extensions.Internal; namespace Microsoft.AspNetCore.Dispatcher { /// /// An type for dispatcher values. /// public class DispatcherValueCollection : IDictionary, IReadOnlyDictionary { internal Storage _storage; /// /// Creates an empty . /// public DispatcherValueCollection() { _storage = EmptyStorage.Instance; } /// /// Creates a initialized with the specified . /// /// An object to initialize the dictionary. The value can be of type /// or /// or an object with public properties as key-value pairs. /// /// /// If the value is a dictionary or other of , /// then its entries are copied. Otherwise the object is interpreted as a set of key-value pairs where the /// property names are keys, and property values are the values, and copied into the dictionary. /// Only public instance non-index properties are considered. /// public DispatcherValueCollection(object values) { var dictionary = values as DispatcherValueCollection; if (dictionary != null) { var listStorage = dictionary._storage as ListStorage; if (listStorage != null) { _storage = new ListStorage(listStorage); return; } var propertyStorage = dictionary._storage as PropertyStorage; if (propertyStorage != null) { // PropertyStorage is immutable so we can just copy it. _storage = dictionary._storage; return; } // If we get here, it's an EmptyStorage. _storage = EmptyStorage.Instance; return; } var keyValueEnumerable = values as IEnumerable>; if (keyValueEnumerable != null) { var listStorage = new ListStorage(); _storage = listStorage; foreach (var kvp in keyValueEnumerable) { if (listStorage.ContainsKey(kvp.Key)) { var message = Resources.FormatDispatcherValueCollection_DuplicateKey(kvp.Key, nameof(DispatcherValueCollection)); throw new ArgumentException(message, nameof(values)); } listStorage.Add(kvp); } return; } var stringValueEnumerable = values as IEnumerable>; if (stringValueEnumerable != null) { var listStorage = new ListStorage(); _storage = listStorage; foreach (var kvp in stringValueEnumerable) { if (listStorage.ContainsKey(kvp.Key)) { var message = Resources.FormatDispatcherValueCollection_DuplicateKey(kvp.Key, nameof(DispatcherValueCollection)); throw new ArgumentException(message, nameof(values)); } listStorage.Add(new KeyValuePair(kvp.Key, kvp.Value)); } return; } if (values != null) { _storage = new PropertyStorage(values); return; } _storage = EmptyStorage.Instance; } /// public object this[string key] { get { if (string.IsNullOrEmpty(key)) { throw new ArgumentNullException(nameof(key)); } object value; TryGetValue(key, out value); return value; } set { if (string.IsNullOrEmpty(key)) { throw new ArgumentNullException(nameof(key)); } if (!_storage.TrySetValue(key, value)) { Upgrade(); _storage.TrySetValue(key, value); } } } /// /// Gets the comparer for this dictionary. /// /// /// This will always be a reference to /// public IEqualityComparer Comparer => StringComparer.OrdinalIgnoreCase; /// public int Count => _storage.Count; /// bool ICollection>.IsReadOnly => false; /// public ICollection Keys { get { Upgrade(); var list = (ListStorage)_storage; var keys = new string[list.Count]; for (var i = 0; i < keys.Length; i++) { keys[i] = list[i].Key; } return keys; } } IEnumerable IReadOnlyDictionary.Keys { get { return Keys; } } /// public ICollection Values { get { Upgrade(); var list = (ListStorage)_storage; var values = new object[list.Count]; for (var i = 0; i < values.Length; i++) { values[i] = list[i].Value; } return values; } } IEnumerable IReadOnlyDictionary.Values { get { return Values; } } /// void ICollection>.Add(KeyValuePair item) { Add(item.Key, item.Value); } /// public void Add(string key, object value) { if (key == null) { throw new ArgumentNullException(nameof(key)); } Upgrade(); var list = (ListStorage)_storage; for (var i = 0; i < list.Count; i++) { if (string.Equals(list[i].Key, key, StringComparison.OrdinalIgnoreCase)) { var message = Resources.FormatDispatcherValueCollection_DuplicateKey(key, nameof(DispatcherValueCollection)); throw new ArgumentException(message, nameof(key)); } } list.Add(new KeyValuePair(key, value)); } /// public void Clear() { if (_storage.Count == 0) { return; } Upgrade(); var list = (ListStorage)_storage; list.Clear(); } /// bool ICollection>.Contains(KeyValuePair item) { if (_storage.Count == 0) { return false; } Upgrade(); var list = (ListStorage)_storage; for (var i = 0; i < list.Count; i++) { if (string.Equals(list[i].Key, item.Key, StringComparison.OrdinalIgnoreCase)) { return EqualityComparer.Default.Equals(list[i].Value, item.Value); } } return false; } /// public bool ContainsKey(string key) { if (key == null) { throw new ArgumentNullException(nameof(key)); } return _storage.ContainsKey(key); } /// void ICollection>.CopyTo( KeyValuePair[] array, int arrayIndex) { if (array == null) { throw new ArgumentNullException(nameof(array)); } if (arrayIndex < 0 || arrayIndex > array.Length || array.Length - arrayIndex < this.Count) { throw new ArgumentOutOfRangeException(nameof(arrayIndex)); } if (_storage.Count == 0) { return; } Upgrade(); var list = (ListStorage)_storage; list.CopyTo(array, arrayIndex); } /// public Enumerator GetEnumerator() { return new Enumerator(this); } /// IEnumerator> IEnumerable>.GetEnumerator() { return GetEnumerator(); } /// IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } /// bool ICollection>.Remove(KeyValuePair item) { if (_storage.Count == 0) { return false; } Upgrade(); var list = (ListStorage)_storage; for (var i = 0; i < list.Count; i++) { if (string.Equals(list[i].Key, item.Key, StringComparison.OrdinalIgnoreCase) && EqualityComparer.Default.Equals(list[i].Value, item.Value)) { list.RemoveAt(i); return true; } } return false; } /// public bool Remove(string key) { if (key == null) { throw new ArgumentNullException(nameof(key)); } if (_storage.Count == 0) { return false; } Upgrade(); var list = (ListStorage)_storage; for (var i = 0; i < list.Count; i++) { if (string.Equals(list[i].Key, key, StringComparison.OrdinalIgnoreCase)) { list.RemoveAt(i); return true; } } return false; } /// public bool TryGetValue(string key, out object value) { if (key == null) { throw new ArgumentNullException(nameof(key)); } return _storage.TryGetValue(key, out value); } private void Upgrade() { _storage.Upgrade(ref _storage); } public struct Enumerator : IEnumerator> { private readonly Storage _storage; private int _index; public Enumerator(DispatcherValueCollection collection) { if (collection == null) { throw new ArgumentNullException(); } _storage = collection._storage; Current = default(KeyValuePair); _index = -1; } public KeyValuePair Current { get; private set; } object IEnumerator.Current => Current; public void Dispose() { } public bool MoveNext() { if (++_index < _storage.Count) { Current = _storage[_index]; return true; } Current = default(KeyValuePair); return false; } public void Reset() { Current = default(KeyValuePair); _index = -1; } } // Storage and its subclasses are internal for testing. internal abstract class Storage { public abstract int Count { get; } public abstract KeyValuePair this[int index] { get; set; } public abstract void Upgrade(ref Storage storage); public abstract bool TryGetValue(string key, out object value); public abstract bool ContainsKey(string key); public abstract bool TrySetValue(string key, object value); } internal class ListStorage : Storage { private KeyValuePair[] _items; private int _count; private static readonly KeyValuePair[] _emptyArray = new KeyValuePair[0]; public ListStorage() { _items = _emptyArray; } public ListStorage(int capacity) { if (capacity == 0) { _items = _emptyArray; } else { _items = new KeyValuePair[capacity]; } } public ListStorage(ListStorage other) { if (other.Count == 0) { _items = _emptyArray; } else { _items = new KeyValuePair[other.Count]; for (var i = 0; i < other.Count; i++) { this.Add(other[i]); } } } public int Capacity => _items.Length; public override int Count => _count; public override KeyValuePair this[int index] { get { if (index < 0 || index >= _count) { throw new ArgumentOutOfRangeException(nameof(index)); } return _items[index]; } set { if (index < 0 || index >= _count) { throw new ArgumentOutOfRangeException(nameof(index)); } _items[index] = value; } } public void Add(KeyValuePair item) { if (_count == _items.Length) { EnsureCapacity(_count + 1); } _items[_count++] = item; } public void RemoveAt(int index) { _count--; for (var i = index; i < _count; i++) { _items[i] = _items[i + 1]; } _items[_count] = default(KeyValuePair); } public void Clear() { for (var i = 0; i < _count; i++) { _items[i] = default(KeyValuePair); } _count = 0; } public void CopyTo(KeyValuePair[] array, int arrayIndex) { for (var i = 0; i < _count; i++) { array[arrayIndex++] = _items[i]; } } public override bool ContainsKey(string key) { for (var i = 0; i < Count; i++) { var kvp = _items[i]; if (string.Equals(key, kvp.Key, StringComparison.OrdinalIgnoreCase)) { return true; } } return false; } public override bool TrySetValue(string key, object value) { for (var i = 0; i < Count; i++) { var kvp = _items[i]; if (string.Equals(key, kvp.Key, StringComparison.OrdinalIgnoreCase)) { _items[i] = new KeyValuePair(key, value); return true; } } Add(new KeyValuePair(key, value)); return true; } public override bool TryGetValue(string key, out object value) { for (var i = 0; i < Count; i++) { var kvp = _items[i]; if (string.Equals(key, kvp.Key, StringComparison.OrdinalIgnoreCase)) { value = kvp.Value; return true; } } value = null; return false; } public override void Upgrade(ref Storage storage) { // Do nothing. } private void EnsureCapacity(int min) { var newLength = _items.Length == 0 ? 4 : _items.Length * 2; var newItems = new KeyValuePair[newLength]; for (var i = 0; i < _count; i++) { newItems[i] = _items[i]; } _items = newItems; } } internal class PropertyStorage : Storage { private static readonly PropertyCache _propertyCache = new PropertyCache(); internal readonly object _value; internal readonly PropertyHelper[] _properties; public PropertyStorage(object value) { Debug.Assert(value != null); _value = value; // Cache the properties so we can know if we've already validated them for duplicates. var type = _value.GetType(); if (!_propertyCache.TryGetValue(type, out _properties)) { _properties = PropertyHelper.GetVisibleProperties(type); ValidatePropertyNames(type, _properties); _propertyCache.TryAdd(type, _properties); } } public PropertyStorage(PropertyStorage propertyStorage) { _value = propertyStorage._value; _properties = propertyStorage._properties; } public override int Count => _properties.Length; public override KeyValuePair this[int index] { get { var property = _properties[index]; return new KeyValuePair(property.Name, property.GetValue(_value)); } set { // PropertyStorage never sets a value. throw new NotImplementedException(); } } public override bool TryGetValue(string key, out object value) { for (var i = 0; i < _properties.Length; i++) { var property = _properties[i]; if (string.Equals(key, property.Name, StringComparison.OrdinalIgnoreCase)) { value = property.GetValue(_value); return true; } } value = null; return false; } public override bool ContainsKey(string key) { for (var i = 0; i < _properties.Length; i++) { var property = _properties[i]; if (string.Equals(key, property.Name, StringComparison.OrdinalIgnoreCase)) { return true; } } return false; } public override bool TrySetValue(string key, object value) { // PropertyStorage never sets a value. return false; } public override void Upgrade(ref Storage storage) { storage = new ListStorage(Count); for (var i = 0; i < _properties.Length; i++) { var property = _properties[i]; storage.TrySetValue(property.Name, property.GetValue(_value)); } } private static void ValidatePropertyNames(Type type, PropertyHelper[] properties) { var names = new Dictionary(StringComparer.OrdinalIgnoreCase); for (var i = 0; i < properties.Length; i++) { var property = properties[i]; PropertyHelper duplicate; if (names.TryGetValue(property.Name, out duplicate)) { var message = Resources.FormatDispatcherValueCollection_DuplicatePropertyName( type.FullName, property.Name, duplicate.Name, nameof(DispatcherValueCollection)); throw new InvalidOperationException(message); } names.Add(property.Name, property); } } } internal class EmptyStorage : Storage { public static readonly EmptyStorage Instance = new EmptyStorage(); private EmptyStorage() { } public override int Count => 0; public override KeyValuePair this[int index] { get { throw new NotImplementedException(); } set { throw new NotImplementedException(); } } public override bool ContainsKey(string key) { return false; } public override bool TryGetValue(string key, out object value) { value = null; return false; } public override bool TrySetValue(string key, object value) { return false; } public override void Upgrade(ref Storage storage) { storage = new ListStorage(); } } private class PropertyCache : ConcurrentDictionary { } } }