diff --git a/LibreMetaverse.Tests/LibreMetaverse.Tests.csproj b/LibreMetaverse.Tests/LibreMetaverse.Tests.csproj index 9ecdbcf0..fd9815b9 100644 --- a/LibreMetaverse.Tests/LibreMetaverse.Tests.csproj +++ b/LibreMetaverse.Tests/LibreMetaverse.Tests.csproj @@ -38,9 +38,9 @@ - + - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/LibreMetaverse.Types/MultiValueDictionary.cs b/LibreMetaverse.Types/MultiValueDictionary.cs new file mode 100644 index 00000000..d4367ec0 --- /dev/null +++ b/LibreMetaverse.Types/MultiValueDictionary.cs @@ -0,0 +1,1051 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections; +using System.Collections.Generic; + +namespace LibreMetaverse +{ + /// + /// A MultiValueDictionary can be viewed as a that allows multiple + /// values for any given unique key. While the MultiValueDictionary API is + /// mostly the same as that of a regular , there is a distinction + /// in that getting the value for a key returns a of values + /// rather than a single value associated with that key. Additionally, + /// there is functionality to allow adding or removing more than a single + /// value at once. + /// + /// The MultiValueDictionary can also be viewed as a IReadOnlyDictionary<TKey,IReadOnlyCollection<TValue>t> + /// where the is abstracted from the view of the programmer. + /// + /// For a read-only MultiValueDictionary, see . + /// + /// The type of the key. + /// The type of the value. + public class MultiValueDictionary : + IReadOnlyDictionary> + { + #region Variables + /*====================================================================== + ** Variables + ======================================================================*/ + + /// + /// The private dictionary that this class effectively wraps around + /// + private readonly Dictionary _dictionary; + + /// + /// The function to construct a new + /// + /// + private Func> NewCollectionFactory = () => new List(); + + /// + /// The current version of this MultiValueDictionary used to determine MultiValueDictionary modification + /// during enumeration + /// + private int _version; + + #endregion + + #region Constructors + /*====================================================================== + ** Constructors + ======================================================================*/ + + /// + /// Initializes a new instance of the + /// class that is empty, has the default initial capacity, and uses the default + /// for . + /// + public MultiValueDictionary() + { + _dictionary = new Dictionary(); + } + + /// + /// Initializes a new instance of the class that is + /// empty, has the specified initial capacity, and uses the default + /// for . + /// + /// Initial number of keys that the will allocate space for + /// capacity must be >= 0 + public MultiValueDictionary(int capacity) + { + if (capacity < 0) + throw new ArgumentOutOfRangeException(nameof(capacity), "Non-negative number required."); + _dictionary = new Dictionary(capacity); + } + + /// + /// Initializes a new instance of the class + /// that is empty, has the default initial capacity, and uses the + /// specified . + /// + /// Specified comparer to use for the s + /// If is set to null, then the default for is used. + public MultiValueDictionary(IEqualityComparer comparer) + { + _dictionary = new Dictionary(comparer); + } + + /// + /// Initializes a new instance of the class + /// that is empty, has the specified initial capacity, and uses the + /// specified . + /// + /// Initial number of keys that the will allocate space for + /// Specified comparer to use for the s + /// Capacity must be >= 0 + /// If is set to null, then the default for is used. + public MultiValueDictionary(int capacity, IEqualityComparer comparer) + { + if (capacity < 0) + throw new ArgumentOutOfRangeException(nameof(capacity), "Non-negative number required."); + _dictionary = new Dictionary(capacity, comparer); + } + + /// + /// Initializes a new instance of the class that contains + /// elements copied from the specified IEnumerable<KeyValuePair<TKey, IReadOnlyCollection<TValue>>> and uses the + /// default for the type. + /// + /// IEnumerable to copy elements into this from + /// enumerable must be non-null + public MultiValueDictionary(IEnumerable>> enumerable) + : this(enumerable, null) + { } + + /// + /// Initializes a new instance of the class that contains + /// elements copied from the specified IEnumerable<KeyValuePair<TKey, IReadOnlyCollection<TValue>>> and uses the + /// specified . + /// + /// IEnumerable to copy elements into this from + /// Specified comparer to use for the s + /// enumerable must be non-null + /// If is set to null, then the default for is used. + public MultiValueDictionary(IEnumerable>> enumerable, IEqualityComparer comparer) + { + if (enumerable == null) + throw new ArgumentNullException(nameof(enumerable)); + + _dictionary = new Dictionary(comparer); + foreach (var pair in enumerable) + AddRange(pair.Key, pair.Value); + } + + #endregion + + #region Static Factories + /*====================================================================== + ** Static Factories + ======================================================================*/ + + /// + /// Creates a new new instance of the + /// class that is empty, has the default initial capacity, and uses the default + /// for . The + /// internal dictionary will use instances of the + /// class as its collection type. + /// + /// + /// The collection type that this + /// will contain in its internal dictionary. + /// + /// A new with the specified + /// parameters. + /// must not have + /// IsReadOnly set to true by default. + /// + /// Note that must implement + /// in addition to being constructable through new(). The collection returned from the constructor + /// must also not have IsReadOnly set to True by default. + /// + public static MultiValueDictionary Create() + where TValueCollection : ICollection, new() + { + if (new TValueCollection().IsReadOnly) + throw new InvalidOperationException( + "The specified TValueCollection creates collections that have IsReadOnly set to true by default. " + + "TValueCollection must be a mutable ICollection."); + + return new MultiValueDictionary + { + NewCollectionFactory = () => new TValueCollection() + }; + } + + /// + /// Creates a new new instance of the + /// class that is empty, has the specified initial capacity, and uses the default + /// for . The + /// internal dictionary will use instances of the + /// class as its collection type. + /// + /// + /// The collection type that this + /// will contain in its internal dictionary. + /// + /// Initial number of keys that the will allocate space for + /// A new with the specified + /// parameters. + /// Capacity must be >= 0 + /// must not have + /// IsReadOnly set to true by default. + /// + /// Note that must implement + /// in addition to being constructable through new(). The collection returned from the constructor + /// must also not have IsReadOnly set to True by default. + /// + public static MultiValueDictionary Create(int capacity) + where TValueCollection : ICollection, new() + { + if (capacity < 0) + throw new ArgumentOutOfRangeException(nameof(capacity), "Non-negative number required."); + if (new TValueCollection().IsReadOnly) + throw new InvalidOperationException("The specified TValueCollection creates collections that have IsReadOnly set to true by default. TValueCollection must be a mutable ICollection."); + + return new MultiValueDictionary(capacity) + { + NewCollectionFactory = () => new TValueCollection() + }; + } + + /// + /// Creates a new new instance of the + /// class that is empty, has the default initial capacity, and uses the specified + /// for . The + /// internal dictionary will use instances of the + /// class as its collection type. + /// + /// + /// The collection type that this + /// will contain in its internal dictionary. + /// + /// Specified comparer to use for the s + /// must not have + /// IsReadOnly set to true by default. + /// A new with the specified + /// parameters. + /// If is set to null, then the default for is used. + /// + /// Note that must implement + /// in addition to being constructable through new(). The collection returned from the constructor + /// must also not have IsReadOnly set to True by default. + /// + public static MultiValueDictionary Create(IEqualityComparer comparer) + where TValueCollection : ICollection, new() + { + if (new TValueCollection().IsReadOnly) + throw new InvalidOperationException("The specified TValueCollection creates collections that have IsReadOnly set to true by default. TValueCollection must be a mutable ICollection."); + + return new MultiValueDictionary(comparer) + { + NewCollectionFactory = () => new TValueCollection() + }; + } + + /// + /// Creates a new new instance of the + /// class that is empty, has the specified initial capacity, and uses the specified + /// for . The + /// internal dictionary will use instances of the + /// class as its collection type. + /// + /// + /// The collection type that this + /// will contain in its internal dictionary. + /// + /// Initial number of keys that the will allocate space for + /// Specified comparer to use for the s + /// A new with the specified + /// parameters. + /// must not have + /// IsReadOnly set to true by default. + /// Capacity must be >= 0 + /// If is set to null, then the default for is used. + /// + /// Note that must implement + /// in addition to being constructable through new(). The collection returned from the constructor + /// must also not have IsReadOnly set to True by default. + /// + public static MultiValueDictionary Create(int capacity, IEqualityComparer comparer) + where TValueCollection : ICollection, new() + { + if (capacity < 0) + throw new ArgumentOutOfRangeException(nameof(capacity), "Non-negative number required."); + if (new TValueCollection().IsReadOnly) + throw new InvalidOperationException("The specified TValueCollection creates collections that have IsReadOnly set to true by default. TValueCollection must be a mutable ICollection."); + + return new MultiValueDictionary(capacity, comparer) + { + NewCollectionFactory = () => new TValueCollection() + }; + } + + /// + /// Initializes a new instance of the class that contains + /// elements copied from the specified IEnumerable<KeyValuePair<TKey, IReadOnlyCollection<TValue>>> + /// and uses the default for the type. + /// The internal dictionary will use instances of the + /// class as its collection type. + /// + /// + /// The collection type that this + /// will contain in its internal dictionary. + /// + /// IEnumerable to copy elements into this from + /// A new with the specified + /// parameters. + /// must not have + /// IsReadOnly set to true by default. + /// enumerable must be non-null + /// + /// Note that must implement + /// in addition to being constructable through new(). The collection returned from the constructor + /// must also not have IsReadOnly set to True by default. + /// + public static MultiValueDictionary Create(IEnumerable>> enumerable) + where TValueCollection : ICollection, new() + { + if (enumerable == null) + throw new ArgumentNullException(nameof(enumerable)); + if (new TValueCollection().IsReadOnly) + throw new InvalidOperationException("The specified TValueCollection creates collections that have IsReadOnly set to true by default. TValueCollection must be a mutable ICollection."); + + var multiValueDictionary = new MultiValueDictionary + { + NewCollectionFactory = () => new TValueCollection() + }; + foreach (var pair in enumerable) + multiValueDictionary.AddRange(pair.Key, pair.Value); + return multiValueDictionary; + } + + /// + /// Initializes a new instance of the class that contains + /// elements copied from the specified IEnumerable<KeyValuePair<TKey, IReadOnlyCollection<TValue>>> + /// and uses the specified for the type. + /// The internal dictionary will use instances of the + /// class as its collection type. + /// + /// + /// The collection type that this + /// will contain in its internal dictionary. + /// + /// IEnumerable to copy elements into this from + /// Specified comparer to use for the s + /// A new with the specified + /// parameters. + /// must not have + /// IsReadOnly set to true by default. + /// enumerable must be non-null + /// If is set to null, then the default for is used. + /// + /// Note that must implement + /// in addition to being constructable through new(). The collection returned from the constructor + /// must also not have IsReadOnly set to True by default. + /// + public static MultiValueDictionary Create(IEnumerable>> enumerable, IEqualityComparer comparer) + where TValueCollection : ICollection, new() + { + if (enumerable == null) + throw new ArgumentNullException(nameof(enumerable)); + if (new TValueCollection().IsReadOnly) + throw new InvalidOperationException("The specified TValueCollection creates collections that have IsReadOnly set to true by default. TValueCollection must be a mutable ICollection."); + + var multiValueDictionary = new MultiValueDictionary(comparer) + { + NewCollectionFactory = () => new TValueCollection() + }; + foreach (var pair in enumerable) + multiValueDictionary.AddRange(pair.Key, pair.Value); + return multiValueDictionary; + } + + #endregion + + #region Static Factories with Func parameters + /*====================================================================== + ** Static Factories with Func parameters + ======================================================================*/ + + /// + /// Creates a new new instance of the + /// class that is empty, has the default initial capacity, and uses the default + /// for . The + /// internal dictionary will use instances of the + /// class as its collection type. + /// + /// + /// The collection type that this + /// will contain in its internal dictionary. + /// + /// A function to create a new to use + /// in the internal dictionary store of this . + /// A new with the specified + /// parameters. + /// must create collections with + /// IsReadOnly set to true by default. + /// + /// Note that must implement + /// in addition to being constructable through new(). The collection returned from the constructor + /// must also not have IsReadOnly set to True by default. + /// + public static MultiValueDictionary Create(Func collectionFactory) + where TValueCollection : ICollection + { + if (collectionFactory().IsReadOnly) + throw new InvalidOperationException(("The specified TValueCollection creates collections that have IsReadOnly set to true by default. TValueCollection must be a mutable ICollection.")); + + return new MultiValueDictionary + { + NewCollectionFactory = (Func>)(Delegate)collectionFactory + }; + } + + /// + /// Creates a new new instance of the + /// class that is empty, has the specified initial capacity, and uses the default + /// for . The + /// internal dictionary will use instances of the + /// class as its collection type. + /// + /// + /// The collection type that this + /// will contain in its internal dictionary. + /// + /// Initial number of keys that the will allocate space for + /// A function to create a new to use + /// in the internal dictionary store of this . + /// A new with the specified + /// parameters. + /// Capacity must be >= 0 + /// must create collections with + /// IsReadOnly set to true by default. + /// + /// Note that must implement + /// in addition to being constructable through new(). The collection returned from the constructor + /// must also not have IsReadOnly set to True by default. + /// + public static MultiValueDictionary Create(int capacity, Func collectionFactory) + where TValueCollection : ICollection + { + if (capacity < 0) + throw new ArgumentOutOfRangeException(nameof(capacity), "Non-negative number required."); + if (collectionFactory().IsReadOnly) + throw new InvalidOperationException(("The specified TValueCollection creates collections that have IsReadOnly set to true by default. TValueCollection must be a mutable ICollection.")); + + return new MultiValueDictionary(capacity) + { + NewCollectionFactory = (Func>)(Delegate)collectionFactory + }; + } + + /// + /// Creates a new new instance of the + /// class that is empty, has the default initial capacity, and uses the specified + /// for . The + /// internal dictionary will use instances of the + /// class as its collection type. + /// + /// + /// The collection type that this + /// will contain in its internal dictionary. + /// + /// Specified comparer to use for the s + /// A function to create a new to use + /// in the internal dictionary store of this . + /// must create collections with + /// IsReadOnly set to true by default. + /// A new with the specified + /// parameters. + /// If is set to null, then the default for is used. + /// + /// Note that must implement + /// in addition to being constructable through new(). The collection returned from the constructor + /// must also not have IsReadOnly set to True by default. + /// + public static MultiValueDictionary Create(IEqualityComparer comparer, Func collectionFactory) + where TValueCollection : ICollection + { + if (collectionFactory().IsReadOnly) + throw new InvalidOperationException(("The specified TValueCollection creates collections that have IsReadOnly set to true by default. TValueCollection must be a mutable ICollection.")); + + return new MultiValueDictionary(comparer) + { + NewCollectionFactory = (Func>)(Delegate)collectionFactory + }; + } + + /// + /// Creates a new new instance of the + /// class that is empty, has the specified initial capacity, and uses the specified + /// for . The + /// internal dictionary will use instances of the + /// class as its collection type. + /// + /// + /// The collection type that this + /// will contain in its internal dictionary. + /// + /// Initial number of keys that the will allocate space for + /// Specified comparer to use for the s + /// A function to create a new to use + /// in the internal dictionary store of this . + /// A new with the specified + /// parameters. + /// must create collections with + /// IsReadOnly set to true by default. + /// Capacity must be >= 0 + /// If is set to null, then the default for is used. + /// + /// Note that must implement + /// in addition to being constructable through new(). The collection returned from the constructor + /// must also not have IsReadOnly set to True by default. + /// + public static MultiValueDictionary Create(int capacity, IEqualityComparer comparer, Func collectionFactory) + where TValueCollection : ICollection + { + if (capacity < 0) + throw new ArgumentOutOfRangeException(nameof(capacity), "Non-negative number required."); + if (collectionFactory().IsReadOnly) + throw new InvalidOperationException(("The specified TValueCollection creates collections that have IsReadOnly set to true by default. TValueCollection must be a mutable ICollection.")); + + return new MultiValueDictionary(capacity, comparer) + { + NewCollectionFactory = (Func>)(Delegate)collectionFactory + }; + } + + /// + /// Initializes a new instance of the class that contains + /// elements copied from the specified IEnumerable<KeyValuePair<TKey, IReadOnlyCollection<TValue>>> + /// and uses the default for the type. + /// The internal dictionary will use instances of the + /// class as its collection type. + /// + /// + /// The collection type that this + /// will contain in its internal dictionary. + /// + /// IEnumerable to copy elements into this from + /// A function to create a new to use + /// in the internal dictionary store of this . + /// A new with the specified + /// parameters. + /// must create collections with + /// IsReadOnly set to true by default. + /// enumerable must be non-null + /// + /// Note that must implement + /// in addition to being constructable through new(). The collection returned from the constructor + /// must also not have IsReadOnly set to True by default. + /// + public static MultiValueDictionary Create(IEnumerable>> enumerable, Func collectionFactory) + where TValueCollection : ICollection + { + if (enumerable == null) + throw new ArgumentNullException(nameof(enumerable)); + if (collectionFactory().IsReadOnly) + throw new InvalidOperationException(("The specified TValueCollection creates collections that have IsReadOnly set to true by default. TValueCollection must be a mutable ICollection.")); + + var multiValueDictionary = new MultiValueDictionary + { + NewCollectionFactory = (Func>)(Delegate)collectionFactory + }; + foreach (var pair in enumerable) + multiValueDictionary.AddRange(pair.Key, pair.Value); + return multiValueDictionary; + } + + /// + /// Initializes a new instance of the class that contains + /// elements copied from the specified IEnumerable<KeyValuePair<TKey, IReadOnlyCollection<TValue>>> + /// and uses the specified for the type. + /// The internal dictionary will use instances of the + /// class as its collection type. + /// + /// + /// The collection type that this + /// will contain in its internal dictionary. + /// + /// IEnumerable to copy elements into this from + /// Specified comparer to use for the s + /// A function to create a new to use + /// in the internal dictionary store of this . + /// A new with the specified + /// parameters. + /// must create collections with + /// IsReadOnly set to true by default. + /// enumerable must be non-null + /// If is set to null, then the default for is used. + /// + /// Note that must implement + /// in addition to being constructable through new(). The collection returned from the constructor + /// must also not have IsReadOnly set to True by default. + /// + public static MultiValueDictionary Create(IEnumerable>> enumerable, IEqualityComparer comparer, Func collectionFactory) + where TValueCollection : ICollection + { + if (enumerable == null) + throw new ArgumentNullException(nameof(enumerable)); + if (collectionFactory().IsReadOnly) + throw new InvalidOperationException(("The specified TValueCollection creates collections that have IsReadOnly set to true by default. TValueCollection must be a mutable ICollection.")); + + var multiValueDictionary = new MultiValueDictionary(comparer) + { + NewCollectionFactory = (Func>)(Delegate)collectionFactory + }; + foreach (var pair in enumerable) + multiValueDictionary.AddRange(pair.Key, pair.Value); + return multiValueDictionary; + } + + #endregion + + #region Concrete Methods + /*====================================================================== + ** Concrete Methods + ======================================================================*/ + + /// + /// Adds the specified and to the . + /// + /// The of the element to add. + /// The of the element to add. + /// is null. + /// + /// Unlike the Add for , the Add will not + /// throw any exceptions. If the given is already in the , + /// then will be added to associated with + /// + /// + /// A call to this Add method will always invalidate any currently running enumeration regardless + /// of whether the Add method actually modified the . + /// + public void Add(TKey key, TValue value) + { + if (key == null) + throw new ArgumentNullException(nameof(key)); + if (!_dictionary.TryGetValue(key, out InnerCollectionView collection)) + { + collection = new InnerCollectionView(key, NewCollectionFactory()); + _dictionary.Add(key, collection); + } + collection.AddValue(value); + _version++; + } + + /// + /// Adds a number of key-value pairs to this , where + /// the key for each value is , and the value for a pair + /// is an element from + /// + /// The of all entries to add + /// An of values to add + /// and must be non-null + /// + /// A call to this AddRange method will always invalidate any currently running enumeration regardless + /// of whether the AddRange method actually modified the . + /// + public void AddRange(TKey key, IEnumerable values) + { + if (key == null) + throw new ArgumentNullException(nameof(key)); + if (values == null) + throw new ArgumentNullException(nameof(values)); + + if (!_dictionary.TryGetValue(key, out InnerCollectionView collection)) + { + collection = new InnerCollectionView(key, NewCollectionFactory()); + _dictionary.Add(key, collection); + } + foreach (TValue value in values) + { + collection.AddValue(value); + } + _version++; + } + + /// + /// Removes every associated with the given + /// from the . + /// + /// The of the elements to remove + /// true if the removal was successful; otherwise false + /// is null. + public bool Remove(TKey key) + { + if (key == null) + throw new ArgumentNullException(nameof(key)); + + if (_dictionary.TryGetValue(key, out InnerCollectionView _) && _dictionary.Remove(key)) + { + _version++; + return true; + } + return false; + } + + /// + /// Removes the first instance (if any) of the given - + /// pair from this . + /// + /// The of the element to remove + /// The of the element to remove + /// must be non-null + /// true if the removal was successful; otherwise false + /// + /// If the being removed is the last one associated with its , then that + /// will be removed from the and its + /// associated will be freed as if a call to + /// had been made. + /// + public bool Remove(TKey key, TValue value) + { + if (key == null) + throw new ArgumentNullException(nameof(key)); + + if (_dictionary.TryGetValue(key, out InnerCollectionView collection) && collection.RemoveValue(value)) + { + if (collection.Count == 0) + _dictionary.Remove(key); + _version++; + return true; + } + return false; + } + + /// + /// Determines if the given - + /// pair exists within this . + /// + /// The of the element. + /// The of the element. + /// true if found; otherwise false + /// must be non-null + public bool Contains(TKey key, TValue value) + { + if (key == null) + throw new ArgumentNullException(nameof(key)); + + return (_dictionary.TryGetValue(key, out InnerCollectionView collection) && collection.Contains(value)); + } + + /// + /// Determines if the given exists within this . + /// + /// A to search the for + /// true if the contains the ; otherwise false + public bool ContainsValue(TValue value) + { + foreach (InnerCollectionView sublist in _dictionary.Values) + if (sublist.Contains(value)) + return true; + return false; + } + + /// + /// Removes every and from this + /// . + /// + public void Clear() + { + _dictionary.Clear(); + _version++; + } + + #endregion + + #region Members implemented from IReadOnlyDictionary> + /*====================================================================== + ** Members implemented from IReadOnlyDictionary> + ======================================================================*/ + + /// + /// Determines if the given exists within this and has + /// at least one associated with it. + /// + /// The to search the for + /// true if the contains the requested ; + /// otherwise false. + /// must be non-null + public bool ContainsKey(TKey key) + { + if (key == null) + throw new ArgumentNullException(nameof(key)); + // Since modification to the MultiValueDictionary is only allowed through its own API, we + // can ensure that if a collection is in the internal dictionary then it must have at least one + // associated TValue, or else it would have been removed whenever its final TValue was removed. + return _dictionary.ContainsKey(key); + } + + /// + /// Gets each in this that + /// has one or more associated . + /// + /// + /// An containing each + /// in this that has one or more associated + /// . + /// + public IEnumerable Keys => _dictionary.Keys; + + /// + /// Attempts to get the associated with the given + /// and place it into . + /// + /// The of the element to retrieve + /// + /// When this method returns, contains the associated with the specified + /// if it is found; otherwise contains the default value of . + /// + /// + /// true if the contains an element with the specified + /// ; otherwise, false. + /// + /// must be non-null + public bool TryGetValue(TKey key, out IReadOnlyCollection value) + { + if (key == null) + throw new ArgumentNullException(nameof(key)); + + var success = _dictionary.TryGetValue(key, out InnerCollectionView collection); + value = collection; + return success; + } + + /// + /// Gets an enumerable of from this , + /// where each is the collection of every associated + /// with a present in the . + /// + /// An IEnumerable of each in this + /// + public IEnumerable> Values => _dictionary.Values; + + /// + /// Get every associated with the given . If + /// is not found in this , will + /// throw a . + /// + /// The of the elements to retrieve. + /// must be non-null + /// does not have any associated + /// s in this . + /// + /// An containing every + /// associated with . + /// + /// + /// Note that the returned will change alongside any changes + /// to the + /// + public IReadOnlyCollection this[TKey key] + { + get + { + if (key == null) + throw new ArgumentNullException(nameof(key)); + + if (_dictionary.TryGetValue(key, out InnerCollectionView collection)) + return collection; + + throw new KeyNotFoundException(); + } + } + + /// + /// Returns the number of s with one or more associated + /// in this . + /// + /// The number of s in this . + public int Count => _dictionary.Count; + + /// + /// Get an Enumerator over the - + /// pairs in this . + /// + /// an Enumerator over the - + /// pairs in this . + public IEnumerator>> GetEnumerator() => new Enumerator(this); + + IEnumerator IEnumerable.GetEnumerator() => new Enumerator(this); + + #endregion + + /// + /// The Enumerator class for a + /// that iterates over - + /// pairs. + /// + private class Enumerator : + IEnumerator>> + { + private readonly MultiValueDictionary _multiValueDictionary; + private readonly int _version; + private Dictionary.Enumerator _enumerator; + private EnumerationState _state; + + /// + /// Constructor for the enumerator + /// + /// A MultiValueDictionary to iterate over + internal Enumerator(MultiValueDictionary multiValueDictionary) + { + _multiValueDictionary = multiValueDictionary; + _version = multiValueDictionary._version; + _enumerator = multiValueDictionary._dictionary.GetEnumerator(); + _state = EnumerationState.BeforeFirst; + Current = default; + } + + public KeyValuePair> Current { get; private set; } + + object IEnumerator.Current + { + get + { + switch (_state) + { + case EnumerationState.BeforeFirst: + throw new InvalidOperationException("Enumeration has not started. Call MoveNext."); + case EnumerationState.AfterLast: + throw new InvalidOperationException("Enumeration already finished."); + default: + return Current; + } + } + } + + /// + /// Advances the enumerator to the next element of the collection. + /// + /// + /// true if the enumerator was successfully advanced to the next element; false if the enumerator has passed the end of the collection. + /// + /// The collection was modified after the enumerator was created. + public bool MoveNext() + { + if (_version != _multiValueDictionary._version) + throw new InvalidOperationException("Collection was modified; enumeration operation may not execute."); + + if (_enumerator.MoveNext()) + { + Current = new KeyValuePair>(_enumerator.Current.Key, _enumerator.Current.Value); + _state = EnumerationState.During; + return true; + } + + Current = default; + _state = EnumerationState.AfterLast; + return false; + } + + /// + /// Sets the enumerator to its initial position, which is before the first element in the collection. + /// + /// The collection was modified after the enumerator was created. + public void Reset() + { + if (_version != _multiValueDictionary._version) + throw new InvalidOperationException("Collection was modified; enumeration operation may not execute."); + _enumerator.Dispose(); + _enumerator = _multiValueDictionary._dictionary.GetEnumerator(); + Current = default; + _state = EnumerationState.BeforeFirst; + } + + /// + /// Frees resources associated with this Enumerator + /// + public void Dispose() => _enumerator.Dispose(); + + private enum EnumerationState + { + BeforeFirst, + During, + AfterLast + } + } + + /// + /// An inner class that functions as a view of an ICollection within a MultiValueDictionary + /// + private class InnerCollectionView : + ICollection, + IReadOnlyCollection + { + private readonly ICollection _collection; + + #region Private Concrete API + /*====================================================================== + ** Private Concrete API + ======================================================================*/ + + public InnerCollectionView(TKey key, ICollection collection) + { + Key = key; + _collection = collection; + } + + public void AddValue(TValue item) => _collection.Add(item); + + public bool RemoveValue(TValue item) => _collection.Remove(item); + + #endregion + + #region Shared API + /*====================================================================== + ** Shared API + ======================================================================*/ + + public bool Contains(TValue item) => _collection.Contains(item); + + public void CopyTo(TValue[] array, int arrayIndex) + { + if (array == null) + throw new ArgumentNullException(nameof(array)); + if (arrayIndex < 0) + throw new ArgumentOutOfRangeException(nameof(arrayIndex), "Non-negative number required."); + if (arrayIndex > array.Length) + throw new ArgumentOutOfRangeException(nameof(arrayIndex), "Index was out of range. Must be non-negative and less than the size of the collection."); + if (array.Length - arrayIndex < _collection.Count) + throw new ArgumentException("Destination array is not long enough to copy all the items in the collection. Check array index and length.", nameof(arrayIndex)); + + _collection.CopyTo(array, arrayIndex); + } + + public int Count => _collection.Count; + + public bool IsReadOnly => true; + + public IEnumerator GetEnumerator() => _collection.GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + public TKey Key { get; } + + #endregion + + #region Public-Facing API + /*====================================================================== + ** Public-Facing API + ======================================================================*/ + + void ICollection.Add(TValue item) => throw new NotSupportedException("The collection is read-only"); + + + void ICollection.Clear() => throw new NotSupportedException("The collection is read-only"); + + bool ICollection.Remove(TValue item) => throw new NotSupportedException("The collection is read-only"); + + #endregion + } + } +} \ No newline at end of file diff --git a/LibreMetaverse/AgentManager.cs b/LibreMetaverse/AgentManager.cs index c446dec9..010816ff 100644 --- a/LibreMetaverse/AgentManager.cs +++ b/LibreMetaverse/AgentManager.cs @@ -1,5 +1,5 @@ /* - * Copyright (c) 2006-2016, openmetaverse.co + * Copyright (c) 2006-2016, openmetaverse.co * Copyright (c) 2019-2022, Sjofn LLC * All rights reserved. * @@ -3291,27 +3291,27 @@ namespace OpenMetaverse Client.Network.SendPacket(aiup); } - /// - /// Update agent's private notes for target avatar - /// - /// target avatar for notes + /// + /// Update agent's private notes for target avatar + /// + /// target avatar for notes /// notes to store - public void UpdateProfileNotes(UUID target, string notes) - { - AvatarNotesUpdatePacket anup = new AvatarNotesUpdatePacket - { - AgentData = - { - AgentID = AgentID, - SessionID = SessionID - }, - Data = - { - TargetID = target, - Notes = Utils.StringToBytes(notes) - } - }; - Client.Network.SendPacket(anup); + public void UpdateProfileNotes(UUID target, string notes) + { + AvatarNotesUpdatePacket anup = new AvatarNotesUpdatePacket + { + AgentData = + { + AgentID = AgentID, + SessionID = SessionID + }, + Data = + { + TargetID = target, + Notes = Utils.StringToBytes(notes) + } + }; + Client.Network.SendPacket(anup); } /// @@ -3842,9 +3842,9 @@ namespace OpenMetaverse } }; - OSDMap req = new OSDMap - { - ["access_prefs"] = new OSDMap { ["max"] = access } + OSDMap req = new OSDMap + { + ["access_prefs"] = new OSDMap { ["max"] = access } }; request.PostRequestAsync(req, OSDFormat.Xml, Client.Settings.CAPS_TIMEOUT); @@ -3940,12 +3940,12 @@ namespace OpenMetaverse if (m_InstantMessage == null) return; // don't bother if we don't have any listeners - if (!(response is OSDMap respMap) || respMap.Count == 0 || respMap.ContainsKey("messages")) - { - Logger.Log("Failed to retrieve offline messages because the capability returned some goofy shit.", - Helpers.LogLevel.Warning); - RetrieveInstantMessagesLegacy(); - return; + if (!(response is OSDMap respMap) || respMap.Count == 0 || respMap.ContainsKey("messages")) + { + Logger.Log("Failed to retrieve offline messages because the capability returned some goofy shit.", + Helpers.LogLevel.Warning); + RetrieveInstantMessagesLegacy(); + return; } if (respMap["messages"] is OSDArray msgArray) @@ -3968,8 +3968,8 @@ namespace OpenMetaverse : InstantMessageOnline.Offline; message.ParentEstateID = msg.ContainsKey("parent_estate_id") ? msg["parent_estate_id"].AsUInteger() : 1; - message.Position = msg.ContainsKey("position") - ? msg["position"].AsVector3() + message.Position = msg.ContainsKey("position") + ? msg["position"].AsVector3() : new Vector3(msg["local_x"], msg["local_y"], msg["local_z"]); message.BinaryBucket = msg.ContainsKey("binary_bucket") ? msg["binary_bucket"].AsBinary() : new byte[] { 0 }; @@ -4776,17 +4776,17 @@ namespace OpenMetaverse string message = Utils.BytesToString(alert.AlertData.Message); - if (alert.AlertInfo.Length > 0) - { - string notificationid = Utils.BytesToString(alert.AlertInfo[0].Message); - OSDMap extra = (alert.AlertInfo[0].ExtraParams != null && alert.AlertInfo[0].ExtraParams.Length > 0) - ? OSDParser.Deserialize(alert.AlertInfo[0].ExtraParams) as OSDMap - : null; - OnAlertMessage(new AlertMessageEventArgs(message, notificationid, extra)); - } - else - { - OnAlertMessage(new AlertMessageEventArgs(message, null, null)); + if (alert.AlertInfo.Length > 0) + { + string notificationid = Utils.BytesToString(alert.AlertInfo[0].Message); + OSDMap extra = (alert.AlertInfo[0].ExtraParams != null && alert.AlertInfo[0].ExtraParams.Length > 0) + ? OSDParser.Deserialize(alert.AlertInfo[0].ExtraParams) as OSDMap + : null; + OnAlertMessage(new AlertMessageEventArgs(message, notificationid, extra)); + } + else + { + OnAlertMessage(new AlertMessageEventArgs(message, null, null)); } } @@ -5167,7 +5167,7 @@ namespace OpenMetaverse /// Get the InstantMessage object public InstantMessage IM { get; } - /// Get the simulator where the InstantMessage origniated + /// Get the simulator where the InstantMessage origniated public Simulator Simulator { get; } /// @@ -5420,17 +5420,17 @@ namespace OpenMetaverse public string NotificationId { get; } public OSDMap ExtraParams { get; } - /// - /// Construct a new instance of the AlertMessageEventArgs class - /// - /// user readable message - /// notification id for alert, may be null + /// + /// Construct a new instance of the AlertMessageEventArgs class + /// + /// user readable message + /// notification id for alert, may be null /// any extra params in OSD format, may be null - public AlertMessageEventArgs(string message, string notificationid, OSDMap extraparams) - { - Message = message; - NotificationId = notificationid; - ExtraParams = extraparams; + public AlertMessageEventArgs(string message, string notificationid, OSDMap extraparams) + { + Message = message; + NotificationId = notificationid; + ExtraParams = extraparams; } } diff --git a/LibreMetaverse/AppearanceManager.cs b/LibreMetaverse/AppearanceManager.cs index 45ae1867..b2378cfb 100644 --- a/LibreMetaverse/AppearanceManager.cs +++ b/LibreMetaverse/AppearanceManager.cs @@ -30,12 +30,12 @@ using System.Collections.Generic; using System.Threading; using System.Linq; using System.Threading.Tasks; -using Microsoft.Collections.Extensions; using OpenMetaverse.Packets; using OpenMetaverse.Imaging; using OpenMetaverse.Assets; using OpenMetaverse.Http; using OpenMetaverse.StructuredData; +using LibreMetaverse; namespace OpenMetaverse { diff --git a/LibreMetaverse/LibreMetaverse.csproj b/LibreMetaverse/LibreMetaverse.csproj index 45a60dfd..1ef36a79 100644 --- a/LibreMetaverse/LibreMetaverse.csproj +++ b/LibreMetaverse/LibreMetaverse.csproj @@ -43,9 +43,8 @@ True - - - + + diff --git a/Programs/GridProxy/GridProxy.csproj b/Programs/GridProxy/GridProxy.csproj index 2e0e7e7c..9916794d 100644 --- a/Programs/GridProxy/GridProxy.csproj +++ b/Programs/GridProxy/GridProxy.csproj @@ -11,7 +11,7 @@ false - +