using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Globalization; using System.Linq; using ZeroLevel.Services.Collections; using ZeroLevel.Services.ObjectMapping; using ZeroLevel.Services.Reflection; using ZeroLevel.Services.Serialization; namespace ZeroLevel.Services.Config { /// /// Base configuration /// internal sealed class BaseConfiguration : IConfiguration { #region Private members /// /// When true, any changes disallow /// private bool _freezed = false; /// /// When true, freeze permanent, can't be canceled /// private bool _permanentFreezed = false; private readonly object _freezeLock = new object(); /// /// Key-values dictionary /// private readonly ConcurrentDictionary> _keyValues = new ConcurrentDictionary>(); /// /// Empty list /// private static readonly IEnumerable EmptyValuesList = new List(0); private static string GetKey(string key) { if (string.IsNullOrWhiteSpace(key)) { throw new ArgumentNullException("key"); } return key.Trim().ToLower(CultureInfo.InvariantCulture); } #endregion Private members #region Properties /// /// Get values by key /// /// key /// Values list public IEnumerable this[string key] { get { key = key.ToLower(CultureInfo.CurrentCulture); IList result; if (_keyValues.TryGetValue(key, out result)) { return result; } return EmptyValuesList; } } /// /// Keys list /// public IEnumerable Keys { get { return _keyValues.Keys; } } public bool Freezed { get { return _freezed; } } #endregion Properties #region Public methods #region Get /// /// Getting a list of the value corresponding to the specified key /// /// Key /// Values list public IEnumerable Items(string key) { return this[key]; } /// /// Getting the first value for the specified key /// /// Key /// The first value, or null if the key is, but there are no values, or KeyNotFoundException if there is no key public string First(string key) { IList result; if (_keyValues.TryGetValue(GetKey(key), out result)) { if (result.Count > 0) return result[0]; return null; } throw new KeyNotFoundException("Key not found: " + key); } public void DoWithFirst(string key, Action action) { if (Contains(key)) { action(First(key)); } } public void DoWithFirst(string key, Action action) { if (Contains(key)) { action(First(key)); } } /// /// Getting the first value for the specified key, with an attempt to convert to the specified type /// /// Expected type /// Key /// The first value, or default (T) if there is a key but no values, or KeyNotFoundException if there is no key public T First(string key) { IList result; if (_keyValues.TryGetValue(GetKey(key), out result)) { if (result.Count > 0) return (T)StringToTypeConverter.TryConvert(result[0], typeof(T)); return default(T); } throw new KeyNotFoundException("Parameter not found: " + key); } /// /// First value, or Default value if no value or key /// /// Key /// Default value /// First value, or Default value if no value or key public string FirstOrDefault(string key, string defaultValue) { IList result; if (_keyValues.TryGetValue(GetKey(key), out result)) { if (result.Count > 0) return result[0]; } return defaultValue; } /// /// Getting the first value for the specified key, or defaults, with an attempt to convert to the specified type /// /// Expected type /// Key /// The first value, or default (T) if there are no values or a key public T FirstOrDefault(string key) { IList result; if (_keyValues.TryGetValue(GetKey(key), out result)) { if (result.Count > 0) return (T)StringToTypeConverter.TryConvert(result[0], typeof(T)); } return default(T); } /// /// Getting the first value for the specified key, or defaults, with an attempt to convert to the specified type /// /// Expected type /// Key /// Default value /// First value, or Default value if no value or key public T FirstOrDefault(string key, T defaultValue) { IList result; if (_keyValues.TryGetValue(GetKey(key), out result)) { if (result.Count > 0) return (T)StringToTypeConverter.TryConvert(result[0], typeof(T)); } return defaultValue; } /// /// Check for the presence of a key and a non-empty list of values associated with it /// /// Key /// true - if a key exists and there is at least one value public bool Contains(string key) { key = GetKey(key); return _keyValues.ContainsKey(key) && _keyValues[key].Count > 0; } /// /// Check for one of the keys /// public bool Contains(params string[] keys) { foreach (var key in keys) if (Contains(key)) return true; return false; } /// /// Check for the presence of a key and its associated value /// public bool ContainsValue(string key, string value) { IList result; if (_keyValues.TryGetValue(GetKey(key), out result)) { return result.Contains(value); } return false; } /// /// The number of values associated with the specified key /// /// Key /// Number of values public int Count(string key) { key = GetKey(key); if (_keyValues.ContainsKey(key)) { return _keyValues[key].Count; } return 0; } #endregion Get /// /// Add key-value /// /// Key /// Value public IConfiguration Append(string key, string value) { if (false == _freezed) { key = GetKey(key); if (false == _keyValues.ContainsKey(key)) { _keyValues.TryAdd(key, new List()); } _keyValues[key].Add(value?.Trim() ?? null); } return this; } public IConfiguration Append(string key, IEnumerable values) { if (false == _freezed) { key = GetKey(key); if (false == _keyValues.ContainsKey(key)) { _keyValues.TryAdd(key, new List()); } foreach (var value in values) { _keyValues[key].Add(value?.Trim() ?? null); } } return this; } /// /// Set unique value for key /// public IConfiguration SetUnique(string key, string value) { if (false == _freezed) { key = GetKey(key); if (false == _keyValues.ContainsKey(key)) { _keyValues.TryAdd(key, new List()); } else { _keyValues[key].Clear(); } _keyValues[key].Add(value?.Trim() ?? null); } return this; } /// /// Clean values binded with key /// /// Key public IConfiguration Clear(string key) { if (false == _freezed) { key = GetKey(key); if (_keyValues.ContainsKey(key)) { _keyValues[key].Clear(); } } return this; } /// /// Configuration drop /// public IConfiguration Clear() { if (false == _freezed) { _keyValues.Clear(); } return this; } /// /// Remove key and binded values /// /// Key public IConfiguration Remove(string key) { if (false == _freezed) { IList removed; _keyValues.TryRemove(GetKey(key), out removed); } return this; } public bool Freeze(bool permanent = false) { lock (_freezeLock) { if (false == _freezed) { _freezed = true; _permanentFreezed = permanent; return true; } else if (_permanentFreezed == false && permanent) { _permanentFreezed = true; return true; } return false; } } public bool Unfreeze() { lock (_freezeLock) { if (_freezed && _permanentFreezed == false) { _freezed = false; return true; } return false; } } #endregion Public methods #region IEquatable public bool Equals(IConfiguration other) { if (other == null) { return false; } if (this.Keys.NoOrderingEquals(other.Keys) == false) { return false; } foreach (var key in Keys) { if (this[key].NoOrderingEquals(other[key]) == false) { return false; } } return true; } #endregion IEquatable #region Binary Serializable public void Serialize(IBinaryWriter writer) { writer.WriteBoolean(this._freezed); writer.WriteBoolean(this._permanentFreezed); writer.WriteInt32(_keyValues.Count); foreach (var pair in _keyValues) { writer.WriteString(pair.Key); writer.WriteInt32(pair.Value.Count); foreach (var value in pair.Value) { writer.WriteString(value); } } } public void Deserialize(IBinaryReader reader) { this._freezed = reader.ReadBoolean(); this._permanentFreezed = reader.ReadBoolean(); var count = reader.ReadInt32(); _keyValues.Clear(); for (int i = 0; i < count; i++) { var key = reader.ReadString(); var count_values = reader.ReadInt32(); var list_values = new List(); for (var k = 0; k < count_values; k++) { list_values.Add(reader.ReadString()); } _keyValues.TryAdd(key, list_values); } } #endregion Binary Serializable public void CopyTo(IConfiguration config) { foreach (var key in this.Keys) { config.Append(key, this[key]); } } public T Bind() => (T)Bind(typeof(T)); public object Bind(Type type) { var mapper = TypeMapper.Create(type, true); var instance = TypeHelpers.CreateInitialState(type); mapper.TraversalMembers(member => { if (Contains(member.Name)) { int count = Count(member.Name); switch (count) { case 0: return; case 1: // field if (TypeHelpers.IsArray(member.ClrType) && member.ClrType.GetArrayRank() == 1) { var itemType = member.ClrType.GetElementType(); var elements = SplitRange(First(member.Name), itemType).ToArray(); var arrayBuilder = CollectionFactory.CreateArray(itemType, elements.Length); int index = 0; foreach (var item in elements) { arrayBuilder.Set(item, index); index++; } member.Setter(instance, arrayBuilder.Complete()); } else if (TypeHelpers.IsEnumerable(member.ClrType) && member.ClrType != typeof(string)) { var itemType = member.ClrType.GenericTypeArguments.First(); var collectionBuilder = CollectionFactory.Create(itemType); foreach (var item in SplitRange(First(member.Name), itemType)) { collectionBuilder.Append(item); } member.Setter(instance, collectionBuilder.Complete()); } else { member.Setter(instance, First(member.Name)); } break; default: // array, or first if (TypeHelpers.IsArray(member.ClrType) && member.ClrType.GetArrayRank() == 1) { //throw new NotSupportedException("Multidimensions array not supported"); var itemType = member.ClrType.GetElementType(); if (itemType == typeof(string)) { var array = Items(member.Name).ToArray(); member.Setter(instance, array); } else { var arrayBuilder = CollectionFactory.CreateArray(itemType, count); int index = 0; foreach (var item in Items(member.Name)) { arrayBuilder.Set(StringToTypeConverter.TryConvert(item, itemType), index); index++; } member.Setter(instance, arrayBuilder.Complete()); } } else if (TypeHelpers.IsEnumerable(member.ClrType)) { var itemType = member.ClrType.GenericTypeArguments.First(); if (itemType == typeof(string)) { member.Setter(instance, Items(member.Name)); } else { var collectionBuilder = CollectionFactory.Create(itemType); foreach (var item in Items(member.Name)) { collectionBuilder.Append(StringToTypeConverter.TryConvert(item, itemType)); } member.Setter(instance, collectionBuilder.Complete()); } } else { member.Setter(instance, First(member.Name)); } break; } } }); return instance; } private static IEnumerable SplitRange(string line, Type elementType) { if (string.IsNullOrWhiteSpace(line)) yield return StringToTypeConverter.TryConvert(line, elementType); foreach (var part in line.Split(',')) { if (part.IndexOf('-') >= 0) { var lr = part.Split('-'); if (lr.Length == 2) { long left = (long)Convert.ChangeType(StringToTypeConverter.TryConvert(lr[0], elementType), typeof(long)); long right = (long)Convert.ChangeType(StringToTypeConverter.TryConvert(lr[1], elementType), typeof(long)); for (; left <= right; left++) { yield return Convert.ChangeType(left, elementType); } } else { // incorrect string yield break; } } else { yield return StringToTypeConverter.TryConvert(part, elementType); } } } } }