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);
}
}
}