using System; using System.Collections.Generic; using System.Collections.Specialized; using System.ComponentModel; using System.Runtime.InteropServices; using System.Xml.Serialization; using Microsoft.Win32.TaskScheduler.V2Interop; using JetBrains.Annotations; namespace Microsoft.Win32.TaskScheduler { /// /// Pair of name and value. /// [PublicAPI] public class NameValuePair : IXmlSerializable, INotifyPropertyChanged, ICloneable, IEquatable, IEquatable { private readonly ITaskNamedValuePair v2Pair; private string name, value; /// /// Occurs when a property has changed. /// public event PropertyChangedEventHandler PropertyChanged; /// /// Initializes a new instance of the class. /// public NameValuePair() { } internal NameValuePair([NotNull] ITaskNamedValuePair iPair) { v2Pair = iPair; } internal NameValuePair([NotNull] string name, [NotNull] string value) { if (string.IsNullOrEmpty(name) || string.IsNullOrEmpty(value)) throw new ArgumentException("Both name and value must be non-empty strings."); this.name = name; this.value = value; } [XmlIgnore] internal bool AttributedXmlFormat { get; set; } = true; /// /// Gets or sets the name. /// /// /// The name. /// [NotNull] public string Name { get { return v2Pair == null ? name : v2Pair.Name; } set { if (string.IsNullOrEmpty(value)) throw new ArgumentNullException(nameof(Name)); if (v2Pair == null) name = value; else v2Pair.Name = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Name))); } } /// /// Gets or sets the value. /// /// /// The value. /// [NotNull] public string Value { get { return v2Pair == null ? value : v2Pair.Value; } set { if (string.IsNullOrEmpty(value)) throw new ArgumentNullException(nameof(Value)); if (v2Pair == null) this.value = value; else v2Pair.Value = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Value))); } } /// /// Clones this instance. /// /// A copy of an unbound . [NotNull] public NameValuePair Clone() => new NameValuePair(Name, Value); object ICloneable.Clone() => Clone(); /// /// Determines whether the specified , is equal to this instance. /// /// The to compare with this instance. /// /// true if the specified is equal to this instance; otherwise, false. /// public override bool Equals(object obj) { var valuePair = obj as ITaskNamedValuePair; if (valuePair != null) return (this as IEquatable).Equals(valuePair); var pair = obj as NameValuePair; if (pair != null) return Equals(pair); return base.Equals(obj); } /// Indicates whether the current object is equal to another object of the same type. /// An object to compare with this object. /// true if the current object is equal to the parameter; otherwise, false. public bool Equals([NotNull] NameValuePair other) => other.Name == Name && other.Value == Value; /// Indicates whether the current object is equal to another object of the same type. /// An object to compare with this object. /// true if the current object is equal to the parameter; otherwise, false. bool IEquatable.Equals(ITaskNamedValuePair other) => other.Name == Name && other.Value == Value; /// /// Returns a hash code for this instance. /// /// /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. /// public override int GetHashCode() => new { A = Name, B = Value }.GetHashCode(); /// /// Returns a that represents this instance. /// /// /// A that represents this instance. /// public override string ToString() => $"{Name}={Value}"; /// /// Implements the operator implicit NameValuePair. /// /// The KeyValuePair. /// /// The result of the operator. /// public static implicit operator NameValuePair(KeyValuePair kvp) => new NameValuePair(kvp.Key, kvp.Value); System.Xml.Schema.XmlSchema IXmlSerializable.GetSchema() => null; void IXmlSerializable.ReadXml(System.Xml.XmlReader reader) { if (reader.MoveToContent() == System.Xml.XmlNodeType.Element && reader.LocalName == "Value") { Name = reader.GetAttribute("name"); Value = reader.ReadString(); reader.Read(); } else { reader.ReadStartElement(); XmlSerializationHelper.ReadObjectProperties(reader, this); reader.ReadEndElement(); } } void IXmlSerializable.WriteXml(System.Xml.XmlWriter writer) { if (AttributedXmlFormat) { writer.WriteAttributeString("name", Name); writer.WriteString(Value); } else { XmlSerializationHelper.WriteObjectProperties(writer, this); } } } /// /// Contains a collection of name-value pairs. /// [PublicAPI] public sealed class NamedValueCollection : IDisposable, ICollection, IDictionary, INotifyCollectionChanged, INotifyPropertyChanged { private ITaskNamedValueCollection v2Coll; private readonly List unboundDict; /// /// Occurs when the collection has changed. /// public event NotifyCollectionChangedEventHandler CollectionChanged; /// /// Occurs when a property has changed. /// public event PropertyChangedEventHandler PropertyChanged; internal NamedValueCollection([NotNull] ITaskNamedValueCollection iColl) { v2Coll = iColl; } internal NamedValueCollection() { unboundDict = new List(5); } [XmlIgnore] internal bool AttributedXmlFormat { get; set; } = true; internal void Bind([NotNull] ITaskNamedValueCollection iTaskNamedValueCollection) { v2Coll = iTaskNamedValueCollection; v2Coll.Clear(); foreach (var item in unboundDict) v2Coll.Create(item.Name, item.Value); } /// /// Copies current to another. /// /// The destination collection. public void CopyTo([NotNull] NamedValueCollection destCollection) { if (v2Coll != null) { for (var i = 1; i <= Count; i++) destCollection.Add(v2Coll[i].Name, v2Coll[i].Value); } else { foreach (var item in unboundDict) destCollection.Add(item.Name, item.Value); } } /// /// Releases all resources used by this class. /// public void Dispose() { if (v2Coll != null) Marshal.ReleaseComObject(v2Coll); } /// /// Gets the number of items in the collection. /// public int Count => v2Coll?.Count ?? unboundDict.Count; /// /// Gets a collection of the names. /// /// /// The names. /// [ItemNotNull, NotNull] public ICollection Names { get { if (v2Coll == null) return unboundDict.ConvertAll(p => p.Name); var ret = new List(v2Coll.Count); foreach (var item in this) ret.Add(item.Name); return ret; } } /// /// Gets a collection of the values. /// /// /// The values. /// [ItemNotNull, NotNull] public ICollection Values { get { if (v2Coll == null) return unboundDict.ConvertAll(p => p.Value); var ret = new List(v2Coll.Count); foreach (var item in this) ret.Add(item.Value); return ret; } } /// /// Gets the value of the item at the specified index. /// /// The index of the item being requested. /// The value of the name-value pair at the specified index. [NotNull] public string this[int index] { get { if (v2Coll != null) return v2Coll[++index].Value; return unboundDict[index].Value; } } /// /// Gets the value of the item with the specified name. /// /// Name to get the value for. /// Value for the name, or null if not found. public string this[string name] { [CanBeNull] get { string ret; TryGetValue(name, out ret); return ret; } [NotNull] set { int idx; NameValuePair old = null; var nvp = new NameValuePair(name, value); if (v2Coll == null) { idx = unboundDict.FindIndex(p => p.Name == name); if (idx == -1) unboundDict.Add(nvp); else { old = unboundDict[idx]; unboundDict[idx] = nvp; } } else { var array = new KeyValuePair[Count]; ((ICollection>)this).CopyTo(array, 0); idx = Array.FindIndex(array, p => p.Key == name); if (idx == -1) v2Coll.Create(name, value); else { old = array[idx]; array[idx] = new KeyValuePair(name, value); v2Coll.Clear(); foreach (KeyValuePair t in array) v2Coll.Create(t.Key, t.Value); } } if (idx == -1) OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, nvp)); else OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, nvp, old, idx)); } } /// /// Adds an item to the . /// /// The object to add to the . public void Add([NotNull] NameValuePair item) { if (v2Coll != null) v2Coll.Create(item.Name, item.Value); else unboundDict.Add(item); OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item)); } /// /// Adds a name-value pair to the collection. /// /// The name associated with a value in a name-value pair. /// The value associated with a name in a name-value pair. public void Add([NotNull] string name, [NotNull] string value) { Add(new NameValuePair(name, value)); } /// /// Adds the elements of the specified collection to the end of . /// /// The collection of whose elements should be added to the end of . public void AddRange([ItemNotNull, NotNull] IEnumerable items) { if (v2Coll != null) { foreach (var item in items) v2Coll.Create(item.Name, item.Value); } else unboundDict.AddRange(items); OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, items)); } /// /// Clears the entire collection of name-value pairs. /// public void Clear() { if (v2Coll != null) v2Coll.Clear(); else unboundDict.Clear(); PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Count))); PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Item[]")); OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); } /// /// Returns an enumerator that iterates through the collection. /// /// /// A that can be used to iterate through the collection. /// public IEnumerator GetEnumerator() { if (v2Coll == null) return unboundDict.GetEnumerator(); return new ComEnumerator(() => v2Coll.Count, i => v2Coll[i], o => new NameValuePair(o)); } private void OnCollectionChanged(NotifyCollectionChangedEventArgs e) { if (e.NewItems != null) foreach (NameValuePair item in e.NewItems) item.AttributedXmlFormat = AttributedXmlFormat; CollectionChanged?.Invoke(this, e); } /// /// Removes the name-value pair with the specified key from the collection. /// /// The name associated with a value in a name-value pair. /// true if item successfully removed; false otherwise. public bool Remove([NotNull] string name) { var i = -1; NameValuePair nvp = null; try { if (v2Coll == null) { i = unboundDict.FindIndex(p => p.Name == name); if (i != -1) { nvp = unboundDict[i]; unboundDict.RemoveAt(i); } return (i != -1); } for (i = 0; i < v2Coll.Count; i++) { if (name == v2Coll[i].Name) { nvp = new NameValuePair(v2Coll[i]).Clone(); v2Coll.Remove(i); return true; } } i = -1; } finally { if (i != -1) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Count))); PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Item[]")); OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, nvp, i)); } } return false; } /// /// Removes a selected name-value pair from the collection. /// /// Index of the pair to remove. public void RemoveAt(int index) { if (index < 0 || index >= Count) throw new ArgumentOutOfRangeException(nameof(index)); NameValuePair nvp; if (v2Coll != null) { nvp = new NameValuePair(v2Coll[index]).Clone(); v2Coll.Remove(index); } else { nvp = unboundDict[index]; unboundDict.RemoveAt(index); } PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Count))); PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Item[]")); OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, nvp, index)); } /// /// Gets the value associated with the specified name. /// /// The name whose value to get. /// When this method returns, the value associated with the specified name, if the name is found; otherwise, null. This parameter is passed uninitialized. /// true if the collection contains an element with the specified name; otherwise, false. public bool TryGetValue(string name, out string value) { if (v2Coll != null) { foreach (var item in this) { if (string.CompareOrdinal(item.Name, name) == 0) { value = item.Value; return true; } } value = null; return false; } var nvp = unboundDict.Find(p => p.Name == name); value = nvp?.Value; return nvp != null; } /// /// Gets the collection enumerator for the name-value collection. /// /// An for the collection. System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => GetEnumerator(); bool ICollection.Contains(NameValuePair item) { if (v2Coll == null) return unboundDict.Contains(item); foreach (var invp in this) if (Equals(item, invp)) return true; return false; } void ICollection.CopyTo(NameValuePair[] array, int arrayIndex) { if (v2Coll == null) unboundDict.CopyTo(array, arrayIndex); else { if (array.Length - arrayIndex < v2Coll.Count) throw new ArgumentException("Items in collection exceed available items in destination array."); if (arrayIndex < 0) throw new ArgumentException(@"Array index must be 0 or greater.", nameof(arrayIndex)); for (var i = 0; i < v2Coll.Count; i++) array[i + arrayIndex] = new NameValuePair(v2Coll[i]); } } bool ICollection.IsReadOnly => false; ICollection IDictionary.Keys => Names; bool ICollection>.IsReadOnly => false; bool ICollection.Remove(NameValuePair item) { var i = -1; try { if (v2Coll == null) { if ((i = unboundDict.IndexOf(item)) != -1) return unboundDict.Remove(item); } else { for (i = 0; i < v2Coll.Count; i++) { if (item.Equals(v2Coll[i])) { v2Coll.Remove(i); return true; } } } i = -1; } finally { if (i != -1) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Count))); PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Item[]")); OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item, i)); } } return false; } bool IDictionary.ContainsKey(string key) => Names.Contains(key); void ICollection>.Add(KeyValuePair item) { Add(item.Key, item.Value); } bool ICollection>.Contains(KeyValuePair item) => ((ICollection)this).Contains(new NameValuePair(item.Key, item.Value)); void ICollection>.CopyTo(KeyValuePair[] array, int arrayIndex) { if (array.Length < Count + arrayIndex) throw new ArgumentOutOfRangeException(nameof(array), @"Array has insufficient capacity to support copy."); foreach (var item in ((IEnumerable>)this)) array[arrayIndex++] = item; } bool ICollection>.Remove(KeyValuePair item) => ((ICollection)this).Remove(new NameValuePair(item.Key, item.Value)); IEnumerator> IEnumerable>.GetEnumerator() { foreach (var nvp in this) yield return new KeyValuePair(nvp.Name, nvp.Value); } } }