//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
#nullable disable
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Text;
using Microsoft.SqlServer.Management.Sdk.Sfc;
namespace Microsoft.SqlTools.ServiceLayer.Utility
{
///
/// Provides a sorted collection of associated String keys and Object values that can be accessed either with the key or with the index.
///
public class NameObjectCollection
{
#region struct NameValuePair
[DebuggerDisplay("{Name}:{Value}")]
internal struct NameValuePair
{
private string name;
private object value;
internal NameValuePair(string name)
{
this.name = name;
this.value = null;
}
internal NameValuePair(string name, object value)
{
this.name = name;
this.value = ConvertValue(value);
}
internal string Name
{
get { return this.name; }
}
internal object Value
{
get { return this.value; }
set { this.value = ConvertValue(value); }
}
public override bool Equals(object obj)
{
if (obj is NameValuePair)
{
return Equals((NameValuePair)obj);
}
return false;
}
public bool Equals(NameValuePair other)
{
return Name == other.Name;
}
public override int GetHashCode()
{
return this.name.GetHashCode();
}
public static bool operator ==(NameValuePair p1, NameValuePair p2)
{
return p1.Equals(p2);
}
public static bool operator !=(NameValuePair p1, NameValuePair p2)
{
return !p1.Equals(p2);
}
private static object ConvertValue(object value)
{
// Depending of whether values come from a DataTable or
// a data reader, some property values can be either enum values or integers.
// For example the value of LoginType can be either SqlLogin or 1.
// Enums cause a problems because they gets converted to a string rather than an
// integer value in OE expression evaluation code.
// Since originally all the values came from data tables and the rest of OE code expects
// integers we are going to conver any enum valus to integers here
if (value != null && value.GetType().IsEnum)
{
value = Convert.ToInt32(value);
}
return value;
}
}
#endregion
#region Property
internal class Property : ISfcProperty
{
private NameValuePair pair;
internal Property(NameValuePair pair)
{
this.pair = pair;
}
#region ISfcProperty implementation
///
/// Name of property
///
public string Name
{
get { return pair.Name; }
}
///
/// Type of property
///
public Type Type
{
get { return pair.Value.GetType(); }
}
///
/// Check whether the value is enabled or not
///
public bool Enabled
{
get { return true; }
}
///
/// Value of property
///
public object Value
{
get { return pair.Value; }
set { throw new NotSupportedException(); }
}
///
/// Indicates whether the property is required to persist the current state of the object
///
public bool Required
{
get { return false; }
}
///
/// Indicates that Consumer should be theat this property as read-only
///
public bool Writable
{
get { return false; }
}
///
/// Indicates whether the property value has been changed.
///
public bool Dirty
{
get { return false; }
}
///
/// Indicates whether the properties data has been read, and is null
///
public bool IsNull
{
get { return pair.Value == null || pair.Value is DBNull; }
}
///
/// Aggregated list of custom attributes associated with property
///
public AttributeCollection Attributes
{
get { return null; }
}
#endregion
}
#endregion
private List pairs;
///
/// Initializes a new instance of the NameObjectCollection class that is empty.
///
public NameObjectCollection()
{
this.pairs = new List();
}
///
/// Initializes a new instance of the NameObjectCollection class that is empty and has the specified initial capacity.
///
/// The approximate number of entries that the NameObjectCollection instance can initially contain.
public NameObjectCollection(int capacity)
{
this.pairs = new List(capacity);
}
///
/// Adds an entry with the specified key and value into the NameObjectCollection instance.
///
/// The String key of the entry to add. The key can be null.
/// The Object value of the entry to add. The value can be null.
public void Add(string name, object value)
{
this.pairs.Add(new NameValuePair(name, value));
}
///
/// Removes all entries from the NameObjectCollection instance.
///
public void Clear()
{
this.pairs.Clear();
}
///
/// Gets the value of the specified entry from the NameObjectCollection instance. C# indexer
///
public object this[int index]
{
get
{
return Get(index);
}
set
{
Set(index, value);
}
}
///
/// Gets the value of the specified entry from the NameObjectCollection instance. C# indexer
///
public object this[string name]
{
get
{
return Get(name);
}
set
{
Set(name, value);
}
}
///
/// Gets the value of the specified entry from the NameObjectCollection instance.
///
/// Gets the value of the entry at the specified index of the NameObjectCollection instance.
/// An Object that represents the value of the first entry with the specified key, if found; otherwise null
public object Get(int index)
{
return this.pairs[index].Value;
}
///
/// Gets the value of the first entry with the specified key from the NameObjectCollection instance.
///
/// The String key of the entry to get. The key can be a null reference (Nothing in Visual Basic).
/// An Object that represents the value of the first entry with the specified key, if found; otherwise null
public object Get(string name)
{
int index = IndexOf(name);
return index >= 0 ? this.pairs[index].Value : null;
}
///
/// Returns a String array that contains all the keys in the NameObjectCollection instance.
///
/// A String array that contains all the keys in the NameObjectCollection instance.
public string[] GetAllKeys()
{
string[] keys = new string[this.pairs.Count];
for (int i = 0; i < this.pairs.Count; i++)
{
keys[i] = this.pairs[i].Name;
}
return keys;
}
///
/// Returns an array that contains all the values in the NameObjectCollection instance.
///
/// An Object array that contains all the values in the NameObjectCollection instance.
public object[] GetAllValues()
{
object[] values = new object[this.pairs.Count];
for (int i = 0; i < this.pairs.Count; i++)
{
values[i] = this.pairs[i].Value;
}
return values;
}
///
/// Gets the key of the entry at the specified index of the NameObjectCollection instance.
///
/// The zero-based index of the key to get.
/// A String that represents the key of the entry at the specified index.
public string GetKey(int index)
{
return this.pairs[index].Name;
}
///
/// Gets a value indicating whether the NameObjectCollection instance contains entries whose keys are not null.
///
/// true if the NameObjectCollection instance contains entries whose keys are not a null reference (Nothing in Visual Basic); otherwise, false.
public bool HasKeys()
{
return this.pairs.Count > 0;
}
///
/// Removes the entries with the specified key from the NameObjectCollection instance.
///
///
public void Remove(string name)
{
RemoveAt(IndexOf(name));
}
///
/// Removes the entry at the specified index of the NameObjectCollection instance.
///
/// The zero-based index of the entry to remove.
public void RemoveAt(int index)
{
this.pairs.RemoveAt(index);
}
///
/// Sets the value of the entry at the specified index of the NameObjectCollection instance.
///
/// The zero-based index of the entry to set.
/// The Object that represents the new value of the entry to set. The value can be null.
public void Set(int index, object value)
{
NameValuePair pair = this.pairs[index];
pair.Value = value;
this.pairs[index] = pair;
}
///
/// Sets the value of the first entry with the specified key in the NameObjectCollection instance, if found; otherwise, adds an entry with the specified key and value into the NameObjectCollection instance.
///
/// The String key of the entry to set. The key can be null.
/// The Object that represents the new value of the entry to set. The value can be null.
public void Set(string name, object value)
{
int index = IndexOf(name);
if (index >= 0)
{
Set(index, value);
}
else
{
Add(name, value);
}
}
///
/// Copies elements of this collection to an Array starting at a particular array index
///
/// The one-dimensional Array that is the destination of the elements copied from NameObjectCollection. The Array must have zero-based indexing.
/// The zero-based index in array at which copying begins.
public void CopyTo(object[] array, int index)
{
GetAllValues().CopyTo(array, index);
}
///
/// Gets internal index of NameValuePair
///
///
///
private int IndexOf(string name)
{
// This version does simple iteration. It relies on GetHashCode for optimization
NameValuePair pair = new NameValuePair(name);
for (int i = 0; i < this.pairs.Count; i++)
{
if (this.pairs[i].Equals(pair))
{
return i;
}
}
return -1;
}
#region ISfcPropertySet implementation
///
/// Checks if the property with specified name exists
///
/// property name
/// true if succeeded
public bool Contains(string propertyName)
{
return IndexOf(propertyName) >= 0;
}
///
/// Checks if the property with specified metadata exists
///
/// Property
/// true if succeeded
public bool Contains(ISfcProperty property)
{
return Contains(property.Name);
}
///
/// Checks if the property with specified name and type exists
///
/// property type
/// property name
/// true if succeeded
public bool Contains(string name)
{
int index = IndexOf(name);
return index >= 0 && this.pairs[index].Value != null && this.pairs[index].Value.GetType() == typeof(T);
}
///
/// Attempts to get property value from provider
///
/// property type
/// name name
/// property value
/// true if succeeded
public bool TryGetPropertyValue(string name, out T value)
{
value = default(T);
int index = IndexOf(name);
if (index >= 0)
{
value = (T)this.pairs[index].Value;
return true;
}
return false;
}
///
/// Attempts to get property value from provider
///
/// property name
/// property value
/// true if succeeded
public bool TryGetPropertyValue(string name, out object value)
{
value = null;
int index = IndexOf(name);
if (index >= 0)
{
value = this.pairs[index].Value;
return true;
}
return false;
}
///
/// Attempts to get property metadata
///
/// property name
/// propetty information
///
public bool TryGetProperty(string name, out ISfcProperty property)
{
property = null;
int index = IndexOf(name);
if (index >= 0)
{
property = new Property(this.pairs[index]);
return true;
}
return false;
}
///
/// Enumerates all properties
///
///
public IEnumerable EnumProperties()
{
foreach (NameValuePair pair in this.pairs)
{
yield return new Property(pair);
}
}
#endregion
#region ICollection implementation
public void CopyTo(Array array, int index)
{
Array.Copy(this.pairs.ToArray(), array, index);
}
public IEnumerator GetEnumerator()
{
// Existing code expects this to enumerate property names
return GetAllKeys().GetEnumerator();
}
public int Count
{
get { return this.pairs.Count; }
}
public bool IsSynchronized
{
get { return false; }
}
public object SyncRoot
{
get { return null; }
}
#endregion
public override string ToString()
{
StringBuilder textBuilder = new StringBuilder();
foreach (NameValuePair pair in this.pairs)
{
if (textBuilder.Length > 0)
{
textBuilder.Append(", ");
}
textBuilder.AppendFormat("{0}={1}", pair.Name, pair.Value);
}
return textBuilder.ToString();
}
}
}