coding styles nullable

This commit is contained in:
BlubbFish 2019-12-08 19:54:52 +01:00
parent c78348324a
commit aa9fcd4a36
109 changed files with 12952 additions and 14462 deletions

View File

@ -3,24 +3,20 @@ using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
namespace Swan.Collections namespace Swan.Collections {
{
/// <summary> /// <summary>
/// A thread-safe collection cache repository for types. /// A thread-safe collection cache repository for types.
/// </summary> /// </summary>
/// <typeparam name="TValue">The type of member to cache.</typeparam> /// <typeparam name="TValue">The type of member to cache.</typeparam>
public class CollectionCacheRepository<TValue> public class CollectionCacheRepository<TValue> {
{ private readonly Lazy<ConcurrentDictionary<Type, IEnumerable<TValue>>> _data = new Lazy<ConcurrentDictionary<Type, IEnumerable<TValue>>>(() => new ConcurrentDictionary<Type, IEnumerable<TValue>>(), true);
private readonly Lazy<ConcurrentDictionary<Type, IEnumerable<TValue>>> _data =
new Lazy<ConcurrentDictionary<Type, IEnumerable<TValue>>>(() =>
new ConcurrentDictionary<Type, IEnumerable<TValue>>(), true);
/// <summary> /// <summary>
/// Determines whether the cache contains the specified key. /// Determines whether the cache contains the specified key.
/// </summary> /// </summary>
/// <param name="key">The key.</param> /// <param name="key">The key.</param>
/// <returns><c>true</c> if the cache contains the key, otherwise <c>false</c>.</returns> /// <returns><c>true</c> if the cache contains the key, otherwise <c>false</c>.</returns>
public bool ContainsKey(Type key) => _data.Value.ContainsKey(key); public Boolean ContainsKey(Type key) => this._data.Value.ContainsKey(key);
/// <summary> /// <summary>
/// Retrieves the properties stored for the specified type. /// Retrieves the properties stored for the specified type.
@ -38,15 +34,16 @@ namespace Swan.Collections
/// factory. /// factory.
/// </exception> /// </exception>
/// <exception cref="System.ArgumentNullException">type.</exception> /// <exception cref="System.ArgumentNullException">type.</exception>
public IEnumerable<TValue> Retrieve(Type key, Func<Type, IEnumerable<TValue>> factory) public IEnumerable<TValue> Retrieve(Type key, Func<Type, IEnumerable<TValue>> factory) {
{ if(key == null) {
if (key == null)
throw new ArgumentNullException(nameof(key)); throw new ArgumentNullException(nameof(key));
}
if (factory == null) if(factory == null) {
throw new ArgumentNullException(nameof(factory)); throw new ArgumentNullException(nameof(factory));
}
return _data.Value.GetOrAdd(key, k => factory.Invoke(k).Where(item => item != null)); return this._data.Value.GetOrAdd(key, k => factory.Invoke(k).Where(item => item != null));
} }
} }
} }

View File

@ -3,74 +3,75 @@ using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using Swan.Configuration; using Swan.Configuration;
namespace Swan.Collections namespace Swan.Collections {
{
/// <summary> /// <summary>
/// <para>Implements a collection of components.</para> /// <para>Implements a collection of components.</para>
/// <para>Each component in the collection may be given a unique name for later retrieval.</para> /// <para>Each component in the collection may be given a unique name for later retrieval.</para>
/// </summary> /// </summary>
/// <typeparam name="T">The type of components in the collection.</typeparam> /// <typeparam name="T">The type of components in the collection.</typeparam>
/// <seealso cref="IComponentCollection{T}" /> /// <seealso cref="IComponentCollection{T}" />
public class ComponentCollection<T> : ConfiguredObject, IComponentCollection<T> public class ComponentCollection<T> : ConfiguredObject, IComponentCollection<T> {
{
private readonly List<T> _components = new List<T>(); private readonly List<T> _components = new List<T>();
private readonly List<(string, T)> _componentsWithSafeNames = new List<(string, T)>(); private readonly List<(String, T)> _componentsWithSafeNames = new List<(String, T)>();
private readonly Dictionary<string, T> _namedComponents = new Dictionary<string, T>(); private readonly Dictionary<String, T> _namedComponents = new Dictionary<String, T>();
/// <inheritdoc /> /// <inheritdoc />
public int Count => _components.Count; public Int32 Count => this._components.Count;
/// <inheritdoc /> /// <inheritdoc />
public IReadOnlyDictionary<string, T> Named => _namedComponents; public IReadOnlyDictionary<String, T> Named => this._namedComponents;
/// <inheritdoc /> /// <inheritdoc />
public IReadOnlyList<(string SafeName, T Component)> WithSafeNames => _componentsWithSafeNames; public IReadOnlyList<(String SafeName, T Component)> WithSafeNames => this._componentsWithSafeNames;
/// <inheritdoc /> /// <inheritdoc />
public T this[int index] => _components[index]; public T this[Int32 index] => this._components[index];
/// <inheritdoc /> /// <inheritdoc />
public T this[string key] => _namedComponents[key]; public T this[String key] => this._namedComponents[key];
/// <inheritdoc /> /// <inheritdoc />
public IEnumerator<T> GetEnumerator() => _components.GetEnumerator(); public IEnumerator<T> GetEnumerator() => this._components.GetEnumerator();
/// <inheritdoc /> /// <inheritdoc />
IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)_components).GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)this._components).GetEnumerator();
/// <inheritdoc /> /// <inheritdoc />
/// <exception cref="InvalidOperationException">The collection is locked.</exception> /// <exception cref="InvalidOperationException">The collection is locked.</exception>
public void Add(string name, T component) public void Add(String name, T component) {
{ this.EnsureConfigurationNotLocked();
EnsureConfigurationNotLocked();
if (name != null) if(name != null) {
{ if(name.Length == 0) {
if (name.Length == 0)
throw new ArgumentException("Component name is empty.", nameof(name)); throw new ArgumentException("Component name is empty.", nameof(name));
if (_namedComponents.ContainsKey(name))
throw new ArgumentException("Duplicate component name.", nameof(name));
} }
if (component == null) if(this._namedComponents.ContainsKey(name)) {
throw new ArgumentException("Duplicate component name.", nameof(name));
}
}
if(component == null) {
throw new ArgumentNullException(nameof(component)); throw new ArgumentNullException(nameof(component));
}
if (_components.Contains(component)) if(this._components.Contains(component)) {
throw new ArgumentException("Component has already been added.", nameof(component)); throw new ArgumentException("Component has already been added.", nameof(component));
}
_components.Add(component); this._components.Add(component);
_componentsWithSafeNames.Add((name ?? $"<{component.GetType().Name}>", component)); this._componentsWithSafeNames.Add((name ?? $"<{component.GetType().Name}>", component));
if (name != null) if(name != null) {
_namedComponents.Add(name, component); this._namedComponents.Add(name, component);
}
} }
/// <summary> /// <summary>
/// Locks the collection, preventing further additions. /// Locks the collection, preventing further additions.
/// </summary> /// </summary>
public void Lock() => LockConfiguration(); public void Lock() => this.LockConfiguration();
} }
} }

View File

@ -4,8 +4,7 @@ using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
namespace Swan.Collections namespace Swan.Collections {
{
/// <summary> /// <summary>
/// Represents a thread-safe collection of key/value pairs that does not store null values /// Represents a thread-safe collection of key/value pairs that does not store null values
/// and can be accessed by multiple threads concurrently. /// and can be accessed by multiple threads concurrently.
@ -13,10 +12,7 @@ namespace Swan.Collections
/// <typeparam name="TKey">The type of keys in the dictionary. This must be a reference type.</typeparam> /// <typeparam name="TKey">The type of keys in the dictionary. This must be a reference type.</typeparam>
/// <typeparam name="TValue">The type of values in the dictionary. This must be a reference type.</typeparam> /// <typeparam name="TValue">The type of values in the dictionary. This must be a reference type.</typeparam>
/// <seealso cref="IDataDictionary{TKey,TValue}"/> /// <seealso cref="IDataDictionary{TKey,TValue}"/>
public sealed class ConcurrentDataDictionary<TKey, TValue> : IDataDictionary<TKey, TValue> public sealed class ConcurrentDataDictionary<TKey, TValue> : IDataDictionary<TKey, TValue> where TKey : class where TValue : class {
where TKey : class
where TValue : class
{
#region Private data #region Private data
private readonly ConcurrentDictionary<TKey, TValue> _dictionary; private readonly ConcurrentDictionary<TKey, TValue> _dictionary;
@ -31,10 +27,7 @@ namespace Swan.Collections
/// and uses the default comparer for <typeparamref name="TKey"/>. /// and uses the default comparer for <typeparamref name="TKey"/>.
/// </summary> /// </summary>
/// <see cref="ConcurrentDictionary{TKey,TValue}()"/> /// <see cref="ConcurrentDictionary{TKey,TValue}()"/>
public ConcurrentDataDictionary() public ConcurrentDataDictionary() => this._dictionary = new ConcurrentDictionary<TKey, TValue>();
{
_dictionary = new ConcurrentDictionary<TKey, TValue>();
}
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ConcurrentDataDictionary{TKey,TValue}"/> class /// Initializes a new instance of the <see cref="ConcurrentDataDictionary{TKey,TValue}"/> class
@ -49,12 +42,12 @@ namespace Swan.Collections
/// key/value pairs whose value is <see langword="null"/> will not be copied from <paramref name="collection"/>.</para> /// key/value pairs whose value is <see langword="null"/> will not be copied from <paramref name="collection"/>.</para>
/// </remarks> /// </remarks>
/// <see cref="ConcurrentDictionary{TKey,TValue}(IEnumerable{KeyValuePair{TKey,TValue}})"/> /// <see cref="ConcurrentDictionary{TKey,TValue}(IEnumerable{KeyValuePair{TKey,TValue}})"/>
public ConcurrentDataDictionary(IEnumerable<KeyValuePair<TKey, TValue>> collection) public ConcurrentDataDictionary(IEnumerable<KeyValuePair<TKey, TValue>> collection) {
{ if(collection == null) {
if (collection == null)
throw new ArgumentNullException(nameof(collection)); throw new ArgumentNullException(nameof(collection));
}
_dictionary = new ConcurrentDictionary<TKey, TValue>(collection.Where(pair => pair.Value != null)); this._dictionary = new ConcurrentDictionary<TKey, TValue>(collection.Where(pair => pair.Value != null));
} }
/// <summary> /// <summary>
@ -64,10 +57,7 @@ namespace Swan.Collections
/// <param name="comparer">The equality comparison implementation to use when comparing keys.</param> /// <param name="comparer">The equality comparison implementation to use when comparing keys.</param>
/// <exception cref="ArgumentNullException"><paramref name="comparer"/> is <see langword="null"/>.</exception> /// <exception cref="ArgumentNullException"><paramref name="comparer"/> is <see langword="null"/>.</exception>
/// <see cref="ConcurrentDictionary{TKey,TValue}(IEqualityComparer{TKey})"/> /// <see cref="ConcurrentDictionary{TKey,TValue}(IEqualityComparer{TKey})"/>
public ConcurrentDataDictionary(IEqualityComparer<TKey> comparer) public ConcurrentDataDictionary(IEqualityComparer<TKey> comparer) => this._dictionary = new ConcurrentDictionary<TKey, TValue>(comparer);
{
_dictionary = new ConcurrentDictionary<TKey, TValue>(comparer);
}
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ConcurrentDataDictionary{TKey, TValue}"/> class /// Initializes a new instance of the <see cref="ConcurrentDataDictionary{TKey, TValue}"/> class
@ -87,12 +77,12 @@ namespace Swan.Collections
/// <para><paramref name="comparer"/> is <see langword="null"/>.</para> /// <para><paramref name="comparer"/> is <see langword="null"/>.</para>
/// </exception> /// </exception>
/// <see cref="ConcurrentDictionary{TKey,TValue}(IEnumerable{KeyValuePair{TKey,TValue}},IEqualityComparer{TKey})"/> /// <see cref="ConcurrentDictionary{TKey,TValue}(IEnumerable{KeyValuePair{TKey,TValue}},IEqualityComparer{TKey})"/>
public ConcurrentDataDictionary(IEnumerable<KeyValuePair<TKey, TValue>> collection, IEqualityComparer<TKey> comparer) public ConcurrentDataDictionary(IEnumerable<KeyValuePair<TKey, TValue>> collection, IEqualityComparer<TKey> comparer) {
{ if(collection == null) {
if (collection == null)
throw new ArgumentNullException(nameof(collection)); throw new ArgumentNullException(nameof(collection));
}
_dictionary = new ConcurrentDictionary<TKey, TValue>(collection.Where(pair => pair.Value != null), comparer); this._dictionary = new ConcurrentDictionary<TKey, TValue>(collection.Where(pair => pair.Value != null), comparer);
} }
/// <summary> /// <summary>
@ -107,11 +97,8 @@ namespace Swan.Collections
/// <para>- or -.</para> /// <para>- or -.</para>
/// <para><paramref name="capacity"/> is less than 0.</para> /// <para><paramref name="capacity"/> is less than 0.</para>
/// </exception> /// </exception>
/// <see cref="ConcurrentDictionary{TKey,TValue}(int,int)"/> /// <see cref="ConcurrentDictionary{TKey,TValue}(Int32,Int32)"/>
public ConcurrentDataDictionary(int concurrencyLevel, int capacity) public ConcurrentDataDictionary(Int32 concurrencyLevel, Int32 capacity) => this._dictionary = new ConcurrentDictionary<TKey, TValue>(concurrencyLevel, capacity);
{
_dictionary = new ConcurrentDictionary<TKey, TValue>(concurrencyLevel, capacity);
}
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ConcurrentDataDictionary{TKey, TValue}"/> class /// Initializes a new instance of the <see cref="ConcurrentDataDictionary{TKey, TValue}"/> class
@ -133,16 +120,13 @@ namespace Swan.Collections
/// <para><paramref name="comparer"/> is <see langword="null"/>.</para> /// <para><paramref name="comparer"/> is <see langword="null"/>.</para>
/// </exception> /// </exception>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="concurrencyLevel"/> is less than 1.</exception> /// <exception cref="ArgumentOutOfRangeException"><paramref name="concurrencyLevel"/> is less than 1.</exception>
/// <see cref="ConcurrentDictionary{TKey,TValue}(int,IEnumerable{KeyValuePair{TKey,TValue}},IEqualityComparer{TKey})"/> /// <see cref="ConcurrentDictionary{TKey,TValue}(Int32,IEnumerable{KeyValuePair{TKey,TValue}},IEqualityComparer{TKey})"/>
public ConcurrentDataDictionary(int concurrencyLevel, IEnumerable<KeyValuePair<TKey, TValue>> collection, IEqualityComparer<TKey> comparer) public ConcurrentDataDictionary(Int32 concurrencyLevel, IEnumerable<KeyValuePair<TKey, TValue>> collection, IEqualityComparer<TKey> comparer) {
{ if(collection == null) {
if (collection == null)
throw new ArgumentNullException(nameof(collection)); throw new ArgumentNullException(nameof(collection));
}
_dictionary = new ConcurrentDictionary<TKey, TValue>( this._dictionary = new ConcurrentDictionary<TKey, TValue>(concurrencyLevel, collection.Where(pair => pair.Value != null), comparer);
concurrencyLevel,
collection.Where(pair => pair.Value != null),
comparer);
} }
#endregion #endregion
@ -150,77 +134,69 @@ namespace Swan.Collections
#region Public APIs #region Public APIs
/// <inheritdoc cref="IDataDictionary{TKey,TValue}.Count"/> /// <inheritdoc cref="IDataDictionary{TKey,TValue}.Count"/>
public int Count => _dictionary.Count; public Int32 Count => this._dictionary.Count;
/// <inheritdoc cref="IDataDictionary{TKey,TValue}.IsEmpty"/> /// <inheritdoc cref="IDataDictionary{TKey,TValue}.IsEmpty"/>
public bool IsEmpty => _dictionary.IsEmpty; public Boolean IsEmpty => this._dictionary.IsEmpty;
/// <inheritdoc cref="IDictionary{TKey,TValue}.Keys"/> /// <inheritdoc cref="IDictionary{TKey,TValue}.Keys"/>
public ICollection<TKey> Keys => _dictionary.Keys; public ICollection<TKey> Keys => this._dictionary.Keys;
/// <inheritdoc cref="IDictionary{TKey,TValue}.Values"/> /// <inheritdoc cref="IDictionary{TKey,TValue}.Values"/>
public ICollection<TValue> Values => _dictionary.Values; public ICollection<TValue> Values => this._dictionary.Values;
/// <inheritdoc cref="IDataDictionary{TKey,TValue}.this"/> /// <inheritdoc cref="IDataDictionary{TKey,TValue}.this"/>
public TValue? this[TKey key] public TValue this[TKey key] {
{ get => this._dictionary.TryGetValue(key ?? throw new ArgumentNullException(nameof(key)), out TValue value) ? value : null;
get => _dictionary.TryGetValue(key ?? throw new ArgumentNullException(nameof(key)), out var value) ? value : null; set {
set if(value != null) {
{ this._dictionary[key] = value;
if (value != null) } else {
{ _ = this._dictionary.TryRemove(key, out _);
_dictionary[key] = value;
}
else
{
_dictionary.TryRemove(key, out _);
} }
} }
} }
/// <inheritdoc cref="IDataDictionary{TKey,TValue}.Clear"/> /// <inheritdoc cref="IDataDictionary{TKey,TValue}.Clear"/>
public void Clear() => _dictionary.Clear(); public void Clear() => this._dictionary.Clear();
/// <inheritdoc cref="IDataDictionary{TKey,TValue}.ContainsKey"/> /// <inheritdoc cref="IDataDictionary{TKey,TValue}.ContainsKey"/>
public bool ContainsKey(TKey key) => _dictionary.ContainsKey(key); public Boolean ContainsKey(TKey key) => this._dictionary.ContainsKey(key);
/// <inheritdoc cref="ConcurrentDictionary{TKey,TValue}.GetOrAdd(TKey,TValue)"/> /// <inheritdoc cref="ConcurrentDictionary{TKey,TValue}.GetOrAdd(TKey,TValue)"/>
public TValue? GetOrAdd(TKey key, TValue value) public TValue GetOrAdd(TKey key, TValue value) {
{ if(key == null) {
if (key == null)
throw new ArgumentNullException(nameof(key)); throw new ArgumentNullException(nameof(key));
}
if (value != null) return value != null ? this._dictionary.GetOrAdd(key, value) : this._dictionary.TryGetValue(key, out TValue retrievedValue) ? retrievedValue : null;
return _dictionary.GetOrAdd(key, value);
return _dictionary.TryGetValue(key, out var retrievedValue) ? retrievedValue : null;
} }
/// <inheritdoc cref="IDictionary{TKey,TValue}.Remove(TKey)"/> /// <inheritdoc cref="IDictionary{TKey,TValue}.Remove(TKey)"/>
public bool Remove(TKey key) => _dictionary.TryRemove(key, out _); public Boolean Remove(TKey key) => this._dictionary.TryRemove(key, out _);
/// <inheritdoc cref="ConcurrentDictionary{TKey,TValue}.TryAdd"/> /// <inheritdoc cref="ConcurrentDictionary{TKey,TValue}.TryAdd"/>
public bool TryAdd(TKey key, TValue value) public Boolean TryAdd(TKey key, TValue value) {
{ if(key == null) {
if (key == null)
throw new ArgumentNullException(nameof(key)); throw new ArgumentNullException(nameof(key));
}
return value == null || _dictionary.TryAdd(key, value); return value == null || this._dictionary.TryAdd(key, value);
} }
/// <inheritdoc cref="IDataDictionary{TKey,TValue}.TryGetValue"/> /// <inheritdoc cref="IDataDictionary{TKey,TValue}.TryGetValue"/>
public bool TryGetValue(TKey key, out TValue value) => _dictionary.TryGetValue(key, out value); public Boolean TryGetValue(TKey key, out TValue value) => this._dictionary.TryGetValue(key, out value);
/// <inheritdoc cref="IDataDictionary{TKey,TValue}.TryRemove"/> /// <inheritdoc cref="IDataDictionary{TKey,TValue}.TryRemove"/>
public bool TryRemove(TKey key, out TValue value) => _dictionary.TryRemove(key, out value); public Boolean TryRemove(TKey key, out TValue value) => this._dictionary.TryRemove(key, out value);
/// <inheritdoc cref="ConcurrentDictionary{TKey,TValue}.TryUpdate"/> /// <inheritdoc cref="ConcurrentDictionary{TKey,TValue}.TryUpdate"/>
public bool TryUpdate(TKey key, TValue newValue, TValue comparisonValue) public Boolean TryUpdate(TKey key, TValue newValue, TValue comparisonValue) {
{ if(key == null) {
if (key == null)
throw new ArgumentNullException(nameof(key)); throw new ArgumentNullException(nameof(key));
}
return newValue != null && comparisonValue != null && _dictionary.TryUpdate(key, newValue, comparisonValue); return newValue != null && comparisonValue != null && this._dictionary.TryUpdate(key, newValue, comparisonValue);
} }
#endregion #endregion
@ -228,15 +204,11 @@ namespace Swan.Collections
#region Implementation of IDictionary<TKey, TValue> #region Implementation of IDictionary<TKey, TValue>
/// <inheritdoc cref="IDictionary{TKey,TValue}.Add(TKey,TValue)"/> /// <inheritdoc cref="IDictionary{TKey,TValue}.Add(TKey,TValue)"/>
void IDictionary<TKey, TValue>.Add(TKey key, TValue value) void IDictionary<TKey, TValue>.Add(TKey key, TValue value) {
{ if(value != null) {
if (value != null) ((IDictionary<TKey, TValue>)this._dictionary).Add(key, value);
{ } else {
((IDictionary<TKey, TValue>)_dictionary).Add(key, value); _ = this._dictionary.TryRemove(key, out _);
}
else
{
_dictionary.TryRemove(key, out _);
} }
} }
@ -245,10 +217,10 @@ namespace Swan.Collections
#region Implementation of IReadOnlyDictionary<TKey, TValue> #region Implementation of IReadOnlyDictionary<TKey, TValue>
/// <inheritdoc cref="IReadOnlyDictionary{TKey,TValue}.Keys"/> /// <inheritdoc cref="IReadOnlyDictionary{TKey,TValue}.Keys"/>
IEnumerable<TKey> IReadOnlyDictionary<TKey, TValue>.Keys => _dictionary.Keys; IEnumerable<TKey> IReadOnlyDictionary<TKey, TValue>.Keys => this._dictionary.Keys;
/// <inheritdoc cref="IReadOnlyDictionary{TKey,TValue}.Values"/> /// <inheritdoc cref="IReadOnlyDictionary{TKey,TValue}.Values"/>
IEnumerable<TValue> IReadOnlyDictionary<TKey, TValue>.Values => _dictionary.Values; IEnumerable<TValue> IReadOnlyDictionary<TKey, TValue>.Values => this._dictionary.Values;
#endregion #endregion
@ -258,46 +230,39 @@ namespace Swan.Collections
/// <remarks> /// <remarks>
/// <para>This property is always <see langword="false"/> for a <see cref="ConcurrentDataDictionary{TKey,TValue}"/>.</para> /// <para>This property is always <see langword="false"/> for a <see cref="ConcurrentDataDictionary{TKey,TValue}"/>.</para>
/// </remarks> /// </remarks>
bool ICollection<KeyValuePair<TKey, TValue>>.IsReadOnly => false; Boolean ICollection<KeyValuePair<TKey, TValue>>.IsReadOnly => false;
/// <inheritdoc cref="ICollection{T}.Add"/> /// <inheritdoc cref="ICollection{T}.Add"/>
void ICollection<KeyValuePair<TKey, TValue>>.Add(KeyValuePair<TKey, TValue> item) void ICollection<KeyValuePair<TKey, TValue>>.Add(KeyValuePair<TKey, TValue> item) {
{ if(item.Value != null) {
if (item.Value != null) ((ICollection<KeyValuePair<TKey, TValue>>)this._dictionary).Add(item);
{ } else {
((ICollection<KeyValuePair<TKey, TValue>>)_dictionary).Add(item); _ = this._dictionary.TryRemove(item.Key, out _);
}
else
{
_dictionary.TryRemove(item.Key, out _);
} }
} }
/// <inheritdoc cref="ICollection{T}.Contains"/> /// <inheritdoc cref="ICollection{T}.Contains"/>
bool ICollection<KeyValuePair<TKey, TValue>>.Contains(KeyValuePair<TKey, TValue> item) Boolean ICollection<KeyValuePair<TKey, TValue>>.Contains(KeyValuePair<TKey, TValue> item) => ((ICollection<KeyValuePair<TKey, TValue>>)this._dictionary).Contains(item);
=> ((ICollection<KeyValuePair<TKey, TValue>>)_dictionary).Contains(item);
/// <inheritdoc cref="ICollection{T}.CopyTo"/> /// <inheritdoc cref="ICollection{T}.CopyTo"/>
void ICollection<KeyValuePair<TKey, TValue>>.CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex) void ICollection<KeyValuePair<TKey, TValue>>.CopyTo(KeyValuePair<TKey, TValue>[] array, Int32 arrayIndex) => ((ICollection<KeyValuePair<TKey, TValue>>)this._dictionary).CopyTo(array, arrayIndex);
=> ((ICollection<KeyValuePair<TKey, TValue>>)_dictionary).CopyTo(array, arrayIndex);
/// <inheritdoc cref="ICollection{T}.Remove"/> /// <inheritdoc cref="ICollection{T}.Remove"/>
bool ICollection<KeyValuePair<TKey, TValue>>.Remove(KeyValuePair<TKey, TValue> item) Boolean ICollection<KeyValuePair<TKey, TValue>>.Remove(KeyValuePair<TKey, TValue> item) => ((ICollection<KeyValuePair<TKey, TValue>>)this._dictionary).Remove(item);
=> ((ICollection<KeyValuePair<TKey, TValue>>)_dictionary).Remove(item);
#endregion #endregion
#region Implementation of IEnumerable<KeyValuePair<TKey, TValue>> #region Implementation of IEnumerable<KeyValuePair<TKey, TValue>>
/// <inheritdoc cref="IEnumerable{T}.GetEnumerator"/> /// <inheritdoc cref="IEnumerable{T}.GetEnumerator"/>
IEnumerator<KeyValuePair<TKey, TValue>> IEnumerable<KeyValuePair<TKey, TValue>>.GetEnumerator() => _dictionary.GetEnumerator(); IEnumerator<KeyValuePair<TKey, TValue>> IEnumerable<KeyValuePair<TKey, TValue>>.GetEnumerator() => this._dictionary.GetEnumerator();
#endregion #endregion
#region Implementation of IEnumerable #region Implementation of IEnumerable
/// <inheritdoc cref="IEnumerable.GetEnumerator"/> /// <inheritdoc cref="IEnumerable.GetEnumerator"/>
IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)_dictionary).GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)this._dictionary).GetEnumerator();
#endregion #endregion
} }

View File

@ -1,11 +1,11 @@
using System; 
using System;
using System.Collections; using System.Collections;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
namespace Swan.Collections namespace Swan.Collections {
{
/// <summary> /// <summary>
/// Represents a non-thread-safe collection of key/value pairs that does not store null values. /// Represents a non-thread-safe collection of key/value pairs that does not store null values.
/// </summary> /// </summary>
@ -14,8 +14,7 @@ namespace Swan.Collections
/// <seealso cref="IDataDictionary{TKey,TValue}"/> /// <seealso cref="IDataDictionary{TKey,TValue}"/>
public sealed class DataDictionary<TKey, TValue> : IDataDictionary<TKey, TValue> public sealed class DataDictionary<TKey, TValue> : IDataDictionary<TKey, TValue>
where TKey : class where TKey : class
where TValue : class where TValue : class {
{
#region Private data #region Private data
private readonly Dictionary<TKey, TValue> _dictionary; private readonly Dictionary<TKey, TValue> _dictionary;
@ -30,10 +29,7 @@ namespace Swan.Collections
/// and uses the default comparer for <typeparamref name="TKey"/>. /// and uses the default comparer for <typeparamref name="TKey"/>.
/// </summary> /// </summary>
/// <see cref="Dictionary{TKey,TValue}()"/> /// <see cref="Dictionary{TKey,TValue}()"/>
public DataDictionary() public DataDictionary() => this._dictionary = new Dictionary<TKey, TValue>();
{
_dictionary = new Dictionary<TKey, TValue>();
}
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="DataDictionary{TKey,TValue}"/> class /// Initializes a new instance of the <see cref="DataDictionary{TKey,TValue}"/> class
@ -48,15 +44,14 @@ namespace Swan.Collections
/// key/value pairs whose value is <see langword="null"/> will not be copied from <paramref name="collection"/>.</para> /// key/value pairs whose value is <see langword="null"/> will not be copied from <paramref name="collection"/>.</para>
/// </remarks> /// </remarks>
/// <see cref="Dictionary{TKey,TValue}()"/> /// <see cref="Dictionary{TKey,TValue}()"/>
public DataDictionary(IEnumerable<KeyValuePair<TKey, TValue>> collection) public DataDictionary(IEnumerable<KeyValuePair<TKey, TValue>> collection) {
{ if(collection == null) {
if (collection == null)
throw new ArgumentNullException(nameof(collection)); throw new ArgumentNullException(nameof(collection));
}
_dictionary = new Dictionary<TKey, TValue>(); this._dictionary = new Dictionary<TKey, TValue>();
foreach (var pair in collection.Where(pair => pair.Value != null)) foreach(KeyValuePair<TKey, TValue> pair in collection.Where(pair => pair.Value != null)) {
{ this._dictionary.Add(pair.Key, pair.Value);
_dictionary.Add(pair.Key, pair.Value);
} }
} }
@ -67,10 +62,7 @@ namespace Swan.Collections
/// <param name="comparer">The equality comparison implementation to use when comparing keys.</param> /// <param name="comparer">The equality comparison implementation to use when comparing keys.</param>
/// <exception cref="ArgumentNullException"><paramref name="comparer"/> is <see langword="null"/>.</exception> /// <exception cref="ArgumentNullException"><paramref name="comparer"/> is <see langword="null"/>.</exception>
/// <see cref="Dictionary{TKey,TValue}(IEqualityComparer{TKey})"/> /// <see cref="Dictionary{TKey,TValue}(IEqualityComparer{TKey})"/>
public DataDictionary(IEqualityComparer<TKey> comparer) public DataDictionary(IEqualityComparer<TKey> comparer) => this._dictionary = new Dictionary<TKey, TValue>(comparer);
{
_dictionary = new Dictionary<TKey, TValue>(comparer);
}
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="DataDictionary{TKey, TValue}"/> class /// Initializes a new instance of the <see cref="DataDictionary{TKey, TValue}"/> class
@ -90,15 +82,14 @@ namespace Swan.Collections
/// <para><paramref name="comparer"/> is <see langword="null"/>.</para> /// <para><paramref name="comparer"/> is <see langword="null"/>.</para>
/// </exception> /// </exception>
/// <see cref="Dictionary{TKey,TValue}(IEqualityComparer{TKey})"/> /// <see cref="Dictionary{TKey,TValue}(IEqualityComparer{TKey})"/>
public DataDictionary(IEnumerable<KeyValuePair<TKey, TValue>> collection, IEqualityComparer<TKey> comparer) public DataDictionary(IEnumerable<KeyValuePair<TKey, TValue>> collection, IEqualityComparer<TKey> comparer) {
{ if(collection == null) {
if (collection == null)
throw new ArgumentNullException(nameof(collection)); throw new ArgumentNullException(nameof(collection));
}
_dictionary = new Dictionary<TKey, TValue>(comparer); this._dictionary = new Dictionary<TKey, TValue>(comparer);
foreach (var pair in collection.Where(pair => pair.Value != null)) foreach(KeyValuePair<TKey, TValue> pair in collection.Where(pair => pair.Value != null)) {
{ this._dictionary.Add(pair.Key, pair.Value);
_dictionary.Add(pair.Key, pair.Value);
} }
} }
@ -108,11 +99,8 @@ namespace Swan.Collections
/// </summary> /// </summary>
/// <param name="capacity">The initial number of elements that the <see cref="DataDictionary{TKey, TValue}"/> can contain.</param> /// <param name="capacity">The initial number of elements that the <see cref="DataDictionary{TKey, TValue}"/> can contain.</param>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="capacity"/> is less than 0.</exception> /// <exception cref="ArgumentOutOfRangeException"><paramref name="capacity"/> is less than 0.</exception>
/// <see cref="Dictionary{TKey,TValue}(int)"/> /// <see cref="Dictionary{TKey,TValue}(Int32)"/>
public DataDictionary(int capacity) public DataDictionary(Int32 capacity) => this._dictionary = new Dictionary<TKey, TValue>(capacity);
{
_dictionary = new Dictionary<TKey, TValue>(capacity);
}
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="DataDictionary{TKey, TValue}"/> class /// Initializes a new instance of the <see cref="DataDictionary{TKey, TValue}"/> class
@ -133,16 +121,15 @@ namespace Swan.Collections
/// <para><paramref name="comparer"/> is <see langword="null"/>.</para> /// <para><paramref name="comparer"/> is <see langword="null"/>.</para>
/// </exception> /// </exception>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="capacity"/> is less than 0.</exception> /// <exception cref="ArgumentOutOfRangeException"><paramref name="capacity"/> is less than 0.</exception>
/// <see cref="Dictionary{TKey,TValue}(int,IEqualityComparer{TKey})"/> /// <see cref="Dictionary{TKey,TValue}(Int32,IEqualityComparer{TKey})"/>
public DataDictionary(int capacity, IEnumerable<KeyValuePair<TKey, TValue>> collection, IEqualityComparer<TKey> comparer) public DataDictionary(Int32 capacity, IEnumerable<KeyValuePair<TKey, TValue>> collection, IEqualityComparer<TKey> comparer) {
{ if(collection == null) {
if (collection == null)
throw new ArgumentNullException(nameof(collection)); throw new ArgumentNullException(nameof(collection));
}
_dictionary = new Dictionary<TKey, TValue>(capacity, comparer); this._dictionary = new Dictionary<TKey, TValue>(capacity, comparer);
foreach (var pair in collection.Where(pair => pair.Value != null)) foreach(KeyValuePair<TKey, TValue> pair in collection.Where(pair => pair.Value != null)) {
{ this._dictionary.Add(pair.Key, pair.Value);
_dictionary.Add(pair.Key, pair.Value);
} }
} }
@ -151,103 +138,97 @@ namespace Swan.Collections
#region Public APIs #region Public APIs
/// <inheritdoc cref="IDataDictionary{TKey,TValue}.Count"/> /// <inheritdoc cref="IDataDictionary{TKey,TValue}.Count"/>
public int Count => _dictionary.Count; public Int32 Count => this._dictionary.Count;
/// <inheritdoc cref="IDataDictionary{TKey,TValue}.IsEmpty"/> /// <inheritdoc cref="IDataDictionary{TKey,TValue}.IsEmpty"/>
public bool IsEmpty => _dictionary.Count == 0; public Boolean IsEmpty => this._dictionary.Count == 0;
/// <inheritdoc cref="IDictionary{TKey,TValue}.Keys"/> /// <inheritdoc cref="IDictionary{TKey,TValue}.Keys"/>
public ICollection<TKey> Keys => _dictionary.Keys; public ICollection<TKey> Keys => this._dictionary.Keys;
/// <inheritdoc cref="IDictionary{TKey,TValue}.Values"/> /// <inheritdoc cref="IDictionary{TKey,TValue}.Values"/>
public ICollection<TValue> Values => _dictionary.Values; public ICollection<TValue> Values => this._dictionary.Values;
/// <inheritdoc cref="IDataDictionary{TKey,TValue}.this"/> /// <inheritdoc cref="IDataDictionary{TKey,TValue}.this"/>
public TValue? this[TKey key] public TValue this[TKey key] {
{ get => this._dictionary.TryGetValue(key ?? throw new ArgumentNullException(nameof(key)), out TValue value) ? value : null;
get => _dictionary.TryGetValue(key ?? throw new ArgumentNullException(nameof(key)), out var value) ? value : null; set {
set if(value != null) {
{ this._dictionary[key] = value;
if (value != null) } else {
{ _ = this._dictionary.Remove(key);
_dictionary[key] = value;
}
else
{
_dictionary.Remove(key);
} }
} }
} }
/// <inheritdoc cref="IDataDictionary{TKey,TValue}.Clear"/> /// <inheritdoc cref="IDataDictionary{TKey,TValue}.Clear"/>
public void Clear() => _dictionary.Clear(); public void Clear() => this._dictionary.Clear();
/// <inheritdoc cref="IDataDictionary{TKey,TValue}.ContainsKey"/> /// <inheritdoc cref="IDataDictionary{TKey,TValue}.ContainsKey"/>
public bool ContainsKey(TKey key) public Boolean ContainsKey(TKey key) =>
{
// _dictionary.ContainsKey will take care of throwing on a null key. // _dictionary.ContainsKey will take care of throwing on a null key.
return _dictionary.ContainsKey(key); this._dictionary.ContainsKey(key);
}
/// <inheritdoc cref="ConcurrentDictionary{TKey,TValue}.GetOrAdd(TKey,TValue)"/> /// <inheritdoc cref="ConcurrentDictionary{TKey,TValue}.GetOrAdd(TKey,TValue)"/>
public TValue? GetOrAdd(TKey key, TValue value) public TValue GetOrAdd(TKey key, TValue value) {
{
// _dictionary.TryGetValue will take care of throwing on a null key. // _dictionary.TryGetValue will take care of throwing on a null key.
if (_dictionary.TryGetValue(key, out var result)) if(this._dictionary.TryGetValue(key, out TValue result)) {
return result; return result;
}
if (value == null) if(value == null) {
return null; return null;
}
_dictionary.Add(key, value); this._dictionary.Add(key, value);
return value; return value;
} }
/// <inheritdoc cref="IDictionary{TKey,TValue}.Remove(TKey)"/> /// <inheritdoc cref="IDictionary{TKey,TValue}.Remove(TKey)"/>
public bool Remove(TKey key) public Boolean Remove(TKey key) =>
{
// _dictionary.Remove will take care of throwing on a null key. // _dictionary.Remove will take care of throwing on a null key.
return _dictionary.Remove(key); this._dictionary.Remove(key);
}
/// <inheritdoc cref="ConcurrentDictionary{TKey,TValue}.TryAdd"/> /// <inheritdoc cref="ConcurrentDictionary{TKey,TValue}.TryAdd"/>
public bool TryAdd(TKey key, TValue value) public Boolean TryAdd(TKey key, TValue value) {
{
// _dictionary.ContainsKey will take care of throwing on a null key. // _dictionary.ContainsKey will take care of throwing on a null key.
if (_dictionary.ContainsKey(key)) if(this._dictionary.ContainsKey(key)) {
return false; return false;
}
if (value != null) if(value != null) {
_dictionary.Add(key, value); this._dictionary.Add(key, value);
}
return true; return true;
} }
/// <inheritdoc cref="IDataDictionary{TKey,TValue}.TryGetValue"/> /// <inheritdoc cref="IDataDictionary{TKey,TValue}.TryGetValue"/>
public bool TryGetValue(TKey key, out TValue value) => _dictionary.TryGetValue(key, out value); public Boolean TryGetValue(TKey key, out TValue value) => this._dictionary.TryGetValue(key, out value);
/// <inheritdoc cref="IDataDictionary{TKey,TValue}.TryRemove"/> /// <inheritdoc cref="IDataDictionary{TKey,TValue}.TryRemove"/>
public bool TryRemove(TKey key, out TValue value) public Boolean TryRemove(TKey key, out TValue value) {
{
// TryGetValue will take care of throwing on a null key. // TryGetValue will take care of throwing on a null key.
if (!_dictionary.TryGetValue(key, out value)) if(!this._dictionary.TryGetValue(key, out value)) {
return false; return false;
}
_dictionary.Remove(key); _ = this._dictionary.Remove(key);
return true; return true;
} }
/// <inheritdoc cref="ConcurrentDictionary{TKey,TValue}.TryUpdate"/> /// <inheritdoc cref="ConcurrentDictionary{TKey,TValue}.TryUpdate"/>
public bool TryUpdate(TKey key, TValue newValue, TValue comparisonValue) public Boolean TryUpdate(TKey key, TValue newValue, TValue comparisonValue) {
{
// TryGetValue will take care of throwing on a null key. // TryGetValue will take care of throwing on a null key.
if (!_dictionary.TryGetValue(key, out var value)) if(!this._dictionary.TryGetValue(key, out TValue value)) {
return false; return false;
}
if (value != comparisonValue) if(value != comparisonValue) {
return false; return false;
}
_dictionary[key] = newValue; this._dictionary[key] = newValue;
return true; return true;
} }
@ -256,24 +237,21 @@ namespace Swan.Collections
#region Implementation of IDictionary<TKey, TValue> #region Implementation of IDictionary<TKey, TValue>
/// <inheritdoc cref="IDictionary{TKey,TValue}.Add(TKey,TValue)"/> /// <inheritdoc cref="IDictionary{TKey,TValue}.Add(TKey,TValue)"/>
void IDictionary<TKey, TValue>.Add(TKey key, TValue value) void IDictionary<TKey, TValue>.Add(TKey key, TValue value) {
{
// Validating the key seems redundant, because both Add and Remove // Validating the key seems redundant, because both Add and Remove
// will throw on a null key. // will throw on a null key.
// This way, though, the code path on null key does not depend on value. // This way, though, the code path on null key does not depend on value.
// Without this validation, there should be two unit tests for null key, // Without this validation, there should be two unit tests for null key,
// one with a null value and one with a non-null value, // one with a null value and one with a non-null value,
// which makes no sense. // which makes no sense.
if (key == null) if(key == null) {
throw new ArgumentNullException(nameof(key)); throw new ArgumentNullException(nameof(key));
if (value != null)
{
_dictionary.Add(key, value);
} }
else
{ if(value != null) {
_dictionary.Remove(key); this._dictionary.Add(key, value);
} else {
_ = this._dictionary.Remove(key);
} }
} }
@ -282,10 +260,10 @@ namespace Swan.Collections
#region Implementation of IReadOnlyDictionary<TKey, TValue> #region Implementation of IReadOnlyDictionary<TKey, TValue>
/// <inheritdoc cref="IReadOnlyDictionary{TKey,TValue}.Keys"/> /// <inheritdoc cref="IReadOnlyDictionary{TKey,TValue}.Keys"/>
IEnumerable<TKey> IReadOnlyDictionary<TKey, TValue>.Keys => _dictionary.Keys; IEnumerable<TKey> IReadOnlyDictionary<TKey, TValue>.Keys => this._dictionary.Keys;
/// <inheritdoc cref="IReadOnlyDictionary{TKey,TValue}.Values"/> /// <inheritdoc cref="IReadOnlyDictionary{TKey,TValue}.Values"/>
IEnumerable<TValue> IReadOnlyDictionary<TKey, TValue>.Values => _dictionary.Values; IEnumerable<TValue> IReadOnlyDictionary<TKey, TValue>.Values => this._dictionary.Values;
#endregion #endregion
@ -295,46 +273,39 @@ namespace Swan.Collections
/// <remarks> /// <remarks>
/// <para>This property is always <see langword="false"/> for a <see cref="DataDictionary{TKey,TValue}"/>.</para> /// <para>This property is always <see langword="false"/> for a <see cref="DataDictionary{TKey,TValue}"/>.</para>
/// </remarks> /// </remarks>
bool ICollection<KeyValuePair<TKey, TValue>>.IsReadOnly => false; Boolean ICollection<KeyValuePair<TKey, TValue>>.IsReadOnly => false;
/// <inheritdoc cref="ICollection{T}.Add"/> /// <inheritdoc cref="ICollection{T}.Add"/>
void ICollection<KeyValuePair<TKey, TValue>>.Add(KeyValuePair<TKey, TValue> item) void ICollection<KeyValuePair<TKey, TValue>>.Add(KeyValuePair<TKey, TValue> item) {
{ if(item.Value != null) {
if (item.Value != null) ((ICollection<KeyValuePair<TKey, TValue>>)this._dictionary).Add(item);
{ } else {
((ICollection<KeyValuePair<TKey, TValue>>)_dictionary).Add(item); _ = this._dictionary.Remove(item.Key);
}
else
{
_dictionary.Remove(item.Key);
} }
} }
/// <inheritdoc cref="ICollection{T}.Contains"/> /// <inheritdoc cref="ICollection{T}.Contains"/>
bool ICollection<KeyValuePair<TKey, TValue>>.Contains(KeyValuePair<TKey, TValue> item) Boolean ICollection<KeyValuePair<TKey, TValue>>.Contains(KeyValuePair<TKey, TValue> item) => ((ICollection<KeyValuePair<TKey, TValue>>)this._dictionary).Contains(item);
=> ((ICollection<KeyValuePair<TKey, TValue>>)_dictionary).Contains(item);
/// <inheritdoc cref="ICollection{T}.CopyTo"/> /// <inheritdoc cref="ICollection{T}.CopyTo"/>
void ICollection<KeyValuePair<TKey, TValue>>.CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex) void ICollection<KeyValuePair<TKey, TValue>>.CopyTo(KeyValuePair<TKey, TValue>[] array, Int32 arrayIndex) => ((ICollection<KeyValuePair<TKey, TValue>>)this._dictionary).CopyTo(array, arrayIndex);
=> ((ICollection<KeyValuePair<TKey, TValue>>)_dictionary).CopyTo(array, arrayIndex);
/// <inheritdoc cref="ICollection{T}.Remove"/> /// <inheritdoc cref="ICollection{T}.Remove"/>
bool ICollection<KeyValuePair<TKey, TValue>>.Remove(KeyValuePair<TKey, TValue> item) Boolean ICollection<KeyValuePair<TKey, TValue>>.Remove(KeyValuePair<TKey, TValue> item) => ((ICollection<KeyValuePair<TKey, TValue>>)this._dictionary).Remove(item);
=> ((ICollection<KeyValuePair<TKey, TValue>>) _dictionary).Remove(item);
#endregion #endregion
#region Implementation of IEnumerable<KeyValuePair<TKey, TValue>> #region Implementation of IEnumerable<KeyValuePair<TKey, TValue>>
/// <inheritdoc cref="IEnumerable{T}.GetEnumerator"/> /// <inheritdoc cref="IEnumerable{T}.GetEnumerator"/>
IEnumerator<KeyValuePair<TKey, TValue>> IEnumerable<KeyValuePair<TKey, TValue>>.GetEnumerator() => _dictionary.GetEnumerator(); IEnumerator<KeyValuePair<TKey, TValue>> IEnumerable<KeyValuePair<TKey, TValue>>.GetEnumerator() => this._dictionary.GetEnumerator();
#endregion #endregion
#region Implementation of IEnumerable #region Implementation of IEnumerable
/// <inheritdoc cref="IEnumerable.GetEnumerator"/> /// <inheritdoc cref="IEnumerable.GetEnumerator"/>
IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)_dictionary).GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)this._dictionary).GetEnumerator();
#endregion #endregion
} }

View File

@ -1,7 +1,6 @@
using System; using System;
namespace Swan.Collections namespace Swan.Collections {
{
/// <summary> /// <summary>
/// <para>Implements a collection of components that automatically disposes each component /// <para>Implements a collection of components that automatically disposes each component
/// implementing <see cref="IDisposable"/>.</para> /// implementing <see cref="IDisposable"/>.</para>
@ -10,22 +9,19 @@ namespace Swan.Collections
/// <typeparam name="T">The type of components in the collection.</typeparam> /// <typeparam name="T">The type of components in the collection.</typeparam>
/// <seealso cref="ComponentCollection{T}" /> /// <seealso cref="ComponentCollection{T}" />
/// <seealso cref="IComponentCollection{T}" /> /// <seealso cref="IComponentCollection{T}" />
public class DisposableComponentCollection<T> : ComponentCollection<T>, IDisposable public class DisposableComponentCollection<T> : ComponentCollection<T>, IDisposable {
{
/// <summary> /// <summary>
/// Finalizes an instance of the <see cref="DisposableComponentCollection{T}"/> class. /// Finalizes an instance of the <see cref="DisposableComponentCollection{T}"/> class.
/// </summary> /// </summary>
~DisposableComponentCollection() ~DisposableComponentCollection() {
{ this.Dispose(false);
Dispose(false);
} }
/// <summary> /// <summary>
/// Releases unmanaged and - optionally - managed resources. /// Releases unmanaged and - optionally - managed resources.
/// </summary> /// </summary>
public void Dispose() public void Dispose() {
{ this.Dispose(true);
Dispose(true);
GC.SuppressFinalize(this); GC.SuppressFinalize(this);
} }
@ -35,15 +31,16 @@ namespace Swan.Collections
/// <param name="disposing"> /// <param name="disposing">
/// <see langword="true"/> to release both managed and unmanaged resources; <see langword="true"/> to release only unmanaged resources. /// <see langword="true"/> to release both managed and unmanaged resources; <see langword="true"/> to release only unmanaged resources.
/// </param> /// </param>
protected virtual void Dispose(bool disposing) protected virtual void Dispose(Boolean disposing) {
{ if(!disposing) {
if (!disposing) return; return;
}
foreach (var component in this) foreach(T component in this) {
{ if(component is IDisposable disposable) {
if (component is IDisposable disposable)
disposable.Dispose(); disposable.Dispose();
} }
} }
} }
} }
}

View File

@ -1,22 +1,22 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
namespace Swan.Collections namespace Swan.Collections {
{
/// <summary> /// <summary>
/// <para>Represents a collection of components.</para> /// <para>Represents a collection of components.</para>
/// <para>Each component in the collection may be given a unique name for later retrieval.</para> /// <para>Each component in the collection may be given a unique name for later retrieval.</para>
/// </summary> /// </summary>
/// <typeparam name="T">The type of components in the collection.</typeparam> /// <typeparam name="T">The type of components in the collection.</typeparam>
public interface IComponentCollection<T> : IReadOnlyList<T> public interface IComponentCollection<T> : IReadOnlyList<T> {
{
/// <summary> /// <summary>
/// Gets an <see cref="IReadOnlyDictionary{TKey,TValue}"/> interface representing the named components. /// Gets an <see cref="IReadOnlyDictionary{TKey,TValue}"/> interface representing the named components.
/// </summary> /// </summary>
/// <value> /// <value>
/// The named components. /// The named components.
/// </value> /// </value>
IReadOnlyDictionary<string, T> Named { get; } IReadOnlyDictionary<String, T> Named {
get;
}
/// <summary> /// <summary>
/// <para>Gets an <see cref="IReadOnlyList{T}"/> interface representing all components /// <para>Gets an <see cref="IReadOnlyList{T}"/> interface representing all components
@ -29,7 +29,9 @@ namespace Swan.Collections
/// <value> /// <value>
/// A list of <see cref="ValueTuple{T1,T2}"/>s, each containing a safe name and a component. /// A list of <see cref="ValueTuple{T1,T2}"/>s, each containing a safe name and a component.
/// </value> /// </value>
IReadOnlyList<(string SafeName, T Component)> WithSafeNames { get; } IReadOnlyList<(String SafeName, T Component)> WithSafeNames {
get;
}
/// <summary> /// <summary>
/// Gets the component with the specified name. /// Gets the component with the specified name.
@ -41,7 +43,7 @@ namespace Swan.Collections
/// <returns>The component with the specified <paramref name="name"/>.</returns> /// <returns>The component with the specified <paramref name="name"/>.</returns>
/// <exception cref="ArgumentNullException"><paramref name="name"/> is null.</exception> /// <exception cref="ArgumentNullException"><paramref name="name"/> is null.</exception>
/// <exception cref="KeyNotFoundException">The property is retrieved and <paramref name="name"/> is not found.</exception> /// <exception cref="KeyNotFoundException">The property is retrieved and <paramref name="name"/> is not found.</exception>
T this[string name] { get; } T this[String name] { get; }
/// <summary> /// <summary>
/// Adds a component to the collection, /// Adds a component to the collection,
@ -49,6 +51,6 @@ namespace Swan.Collections
/// </summary> /// </summary>
/// <param name="name">The name given to the module, or <see langword="null"/>.</param> /// <param name="name">The name given to the module, or <see langword="null"/>.</param>
/// <param name="component">The component.</param> /// <param name="component">The component.</param>
void Add(string name, T component); void Add(String name, T component);
} }
} }

View File

@ -1,25 +1,23 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
namespace Swan.Collections namespace Swan.Collections {
{
/// <summary> /// <summary>
/// Represents a generic collection of key/value pairs that does not store /// Represents a generic collection of key/value pairs that does not store
/// null values. /// null values.
/// </summary> /// </summary>
/// <typeparam name="TKey">The type of keys in the dictionary. This must be a reference type.</typeparam> /// <typeparam name="TKey">The type of keys in the dictionary. This must be a reference type.</typeparam>
/// <typeparam name="TValue">The type of values in the dictionary. This must be a reference type.</typeparam> /// <typeparam name="TValue">The type of values in the dictionary. This must be a reference type.</typeparam>
public interface IDataDictionary<TKey, TValue> : IDictionary<TKey, TValue>, IReadOnlyDictionary<TKey, TValue> public interface IDataDictionary<TKey, TValue> : IDictionary<TKey, TValue>, IReadOnlyDictionary<TKey, TValue> where TKey : class where TValue : class {
where TKey : class
where TValue : class
{
/// <summary> /// <summary>
/// Gets a value that indicates whether the <see cref="IDataDictionary{TKey,TValue}"/> is empty. /// Gets a value that indicates whether the <see cref="IDataDictionary{TKey,TValue}"/> is empty.
/// </summary> /// </summary>
/// <value> /// <value>
/// <see langword="true"/> if the <see cref="IDataDictionary{TKey,TValue}"/> is empty; otherwise, <see langword="false"/>. /// <see langword="true"/> if the <see cref="IDataDictionary{TKey,TValue}"/> is empty; otherwise, <see langword="false"/>.
/// </value> /// </value>
bool IsEmpty { get; } Boolean IsEmpty {
get;
}
/// <summary> /// <summary>
/// Attempts to remove and return the value that has the specified key from the <see cref="IDataDictionary{TKey,TValue}"/>. /// Attempts to remove and return the value that has the specified key from the <see cref="IDataDictionary{TKey,TValue}"/>.
@ -29,6 +27,6 @@ namespace Swan.Collections
/// if the key is found; otherwise, <see langword="null"/>. This parameter is passed uninitialized.</param> /// if the key is found; otherwise, <see langword="null"/>. This parameter is passed uninitialized.</param>
/// <returns><see langword="true"/> if the value was removed successfully; otherwise, <see langword="false"/>.</returns> /// <returns><see langword="true"/> if the value was removed successfully; otherwise, <see langword="false"/>.</returns>
/// <exception cref="ArgumentNullException"><paramref name="key"/> is <see langword="null"/>.</exception> /// <exception cref="ArgumentNullException"><paramref name="key"/> is <see langword="null"/>.</exception>
bool TryRemove(TKey key, out TValue value); Boolean TryRemove(TKey key, out TValue value);
} }
} }

View File

@ -1,15 +1,13 @@
using System; using System;
namespace Swan.Configuration namespace Swan.Configuration {
{
/// <summary> /// <summary>
/// Base class for objects whose configuration may be locked, /// Base class for objects whose configuration may be locked,
/// thus becoming read-only, at a certain moment in their lifetime. /// thus becoming read-only, at a certain moment in their lifetime.
/// </summary> /// </summary>
public abstract class ConfiguredObject public abstract class ConfiguredObject {
{ private readonly Object _syncRoot = new Object();
private readonly object _syncRoot = new object(); private Boolean _configurationLocked;
private bool _configurationLocked;
/// <summary> /// <summary>
/// Gets a value indicating whether s configuration has already been locked /// Gets a value indicating whether s configuration has already been locked
@ -19,13 +17,10 @@ namespace Swan.Configuration
/// <see langword="true"/> if the configuration is locked; otherwise, <see langword="false"/>. /// <see langword="true"/> if the configuration is locked; otherwise, <see langword="false"/>.
/// </value> /// </value>
/// <seealso cref="EnsureConfigurationNotLocked"/> /// <seealso cref="EnsureConfigurationNotLocked"/>
protected bool ConfigurationLocked protected Boolean ConfigurationLocked {
{ get {
get lock(this._syncRoot) {
{ return this._configurationLocked;
lock (_syncRoot)
{
return _configurationLocked;
} }
} }
} }
@ -42,15 +37,14 @@ namespace Swan.Configuration
/// as a last chance to validate configuration data, and to lock the configuration of contained objects.</para> /// as a last chance to validate configuration data, and to lock the configuration of contained objects.</para>
/// </remarks> /// </remarks>
/// <seealso cref="OnBeforeLockConfiguration"/> /// <seealso cref="OnBeforeLockConfiguration"/>
protected void LockConfiguration() protected void LockConfiguration() {
{ lock(this._syncRoot) {
lock (_syncRoot) if(this._configurationLocked) {
{
if (_configurationLocked)
return; return;
}
OnBeforeLockConfiguration(); this.OnBeforeLockConfiguration();
_configurationLocked = true; this._configurationLocked = true;
} }
} }
@ -58,8 +52,7 @@ namespace Swan.Configuration
/// Called immediately before locking the configuration. /// Called immediately before locking the configuration.
/// </summary> /// </summary>
/// <seealso cref="LockConfiguration"/> /// <seealso cref="LockConfiguration"/>
protected virtual void OnBeforeLockConfiguration() protected virtual void OnBeforeLockConfiguration() {
{
} }
/// <summary> /// <summary>
@ -68,10 +61,10 @@ namespace Swan.Configuration
/// </summary> /// </summary>
/// <exception cref="InvalidOperationException">The configuration is locked.</exception> /// <exception cref="InvalidOperationException">The configuration is locked.</exception>
/// <seealso cref="ConfigurationLocked"/> /// <seealso cref="ConfigurationLocked"/>
protected void EnsureConfigurationNotLocked() protected void EnsureConfigurationNotLocked() {
{ if(this.ConfigurationLocked) {
if (ConfigurationLocked) throw new InvalidOperationException($"Configuration of this {this.GetType().Name} instance is locked.");
throw new InvalidOperationException($"Configuration of this {GetType().Name} instance is locked."); }
} }
} }
} }

View File

@ -1,7 +1,6 @@
using System; using System;
namespace Swan.Configuration namespace Swan.Configuration {
{
/// <summary> /// <summary>
/// An attribute used to include additional information to a Property for serialization. /// An attribute used to include additional information to a Property for serialization.
/// ///
@ -9,15 +8,16 @@ namespace Swan.Configuration
/// </summary> /// </summary>
/// <seealso cref="System.Attribute" /> /// <seealso cref="System.Attribute" />
[AttributeUsage(AttributeTargets.Property)] [AttributeUsage(AttributeTargets.Property)]
public sealed class PropertyDisplayAttribute : Attribute public sealed class PropertyDisplayAttribute : Attribute {
{
/// <summary> /// <summary>
/// Gets or sets the name. /// Gets or sets the name.
/// </summary> /// </summary>
/// <value> /// <value>
/// The name. /// The name.
/// </value> /// </value>
public string Name { get; set; } public String Name {
get; set;
}
/// <summary> /// <summary>
/// Gets or sets the description. /// Gets or sets the description.
@ -25,7 +25,9 @@ namespace Swan.Configuration
/// <value> /// <value>
/// The description. /// The description.
/// </value> /// </value>
public string Description { get; set; } public String Description {
get; set;
}
/// <summary> /// <summary>
/// Gets or sets the name of the group. /// Gets or sets the name of the group.
@ -33,7 +35,9 @@ namespace Swan.Configuration
/// <value> /// <value>
/// The name of the group. /// The name of the group.
/// </value> /// </value>
public string GroupName { get; set; } public String GroupName {
get; set;
}
/// <summary> /// <summary>
/// Gets or sets the default value. /// Gets or sets the default value.
@ -41,7 +45,9 @@ namespace Swan.Configuration
/// <value> /// <value>
/// The default value. /// The default value.
/// </value> /// </value>
public object DefaultValue { get; set; } public Object DefaultValue {
get; set;
}
/// <summary> /// <summary>
/// Gets or sets the format string to call with method <c>ToString</c>. /// Gets or sets the format string to call with method <c>ToString</c>.
@ -49,6 +55,8 @@ namespace Swan.Configuration
/// <value> /// <value>
/// The format. /// The format.
/// </value> /// </value>
public string Format { get; set; } public String Format {
get; set;
}
} }
} }

View File

@ -41,10 +41,9 @@ namespace Swan.Configuration
/// </code> /// </code>
/// </example> /// </example>
/// <typeparam name="T">The type of settings model.</typeparam> /// <typeparam name="T">The type of settings model.</typeparam>
public sealed class SettingsProvider<T> public sealed class SettingsProvider<T> : SingletonBase<SettingsProvider<T>>
: SingletonBase<SettingsProvider<T>>
{ {
private readonly object _syncRoot = new object(); private readonly Object _syncRoot = new Object();
private T _global; private T _global;
@ -55,8 +54,7 @@ namespace Swan.Configuration
/// <value> /// <value>
/// The configuration file path. /// The configuration file path.
/// </value> /// </value>
public string ConfigurationFilePath { get; set; } = public String ConfigurationFilePath { get; set; } = Path.Combine(SwanRuntime.EntryAssemblyDirectory, "appsettings.json");
Path.Combine(SwanRuntime.EntryAssemblyDirectory, "appsettings.json");
/// <summary> /// <summary>
/// Gets the global settings object. /// Gets the global settings object.
@ -68,12 +66,13 @@ namespace Swan.Configuration
{ {
get get
{ {
lock (_syncRoot) lock (this._syncRoot)
{ {
if (Equals(_global, default(T))) if (Equals(this._global, default(T)!)) {
ReloadGlobalSettings(); this.ReloadGlobalSettings();
}
return _global; return this._global;
} }
} }
} }
@ -83,20 +82,21 @@ namespace Swan.Configuration
/// </summary> /// </summary>
public void ReloadGlobalSettings() public void ReloadGlobalSettings()
{ {
if (File.Exists(ConfigurationFilePath) == false || File.ReadAllText(ConfigurationFilePath).Length == 0) if (File.Exists(this.ConfigurationFilePath) == false || File.ReadAllText(this.ConfigurationFilePath).Length == 0)
{ {
ResetGlobalSettings(); this.ResetGlobalSettings();
return; return;
} }
lock (_syncRoot) lock (this._syncRoot) {
_global = Json.Deserialize<T>(File.ReadAllText(ConfigurationFilePath)); this._global = Json.Deserialize<T>(File.ReadAllText(this.ConfigurationFilePath));
}
} }
/// <summary> /// <summary>
/// Persists the global settings. /// Persists the global settings.
/// </summary> /// </summary>
public void PersistGlobalSettings() => File.WriteAllText(ConfigurationFilePath, Json.Serialize(Global, true)); public void PersistGlobalSettings() => File.WriteAllText(this.ConfigurationFilePath, Json.Serialize(this.Global, true));
/// <summary> /// <summary>
/// Updates settings from list. /// Updates settings from list.
@ -106,29 +106,34 @@ namespace Swan.Configuration
/// A list of settings of type ref="ExtendedPropertyInfo". /// A list of settings of type ref="ExtendedPropertyInfo".
/// </returns> /// </returns>
/// <exception cref="ArgumentNullException">propertyList.</exception> /// <exception cref="ArgumentNullException">propertyList.</exception>
public List<string> RefreshFromList(List<ExtendedPropertyInfo<T>> propertyList) public List<String> RefreshFromList(List<ExtendedPropertyInfo<T>> propertyList)
{ {
if (propertyList == null) if (propertyList == null) {
throw new ArgumentNullException(nameof(propertyList)); throw new ArgumentNullException(nameof(propertyList));
}
var changedSettings = new List<string>(); List<String> changedSettings = new List<global::System.String>();
var globalProps = PropertyTypeCache.DefaultCache.Value.RetrieveAllProperties<T>(); IEnumerable<PropertyInfo> globalProps = PropertyTypeCache.DefaultCache.Value.RetrieveAllProperties<T>();
foreach (var property in propertyList) foreach (ExtendedPropertyInfo<T> property in propertyList)
{ {
var propertyInfo = globalProps.FirstOrDefault(x => x.Name == property.Property); PropertyInfo propertyInfo = globalProps.FirstOrDefault(x => x.Name == property.Property);
if (propertyInfo == null) continue; if (propertyInfo == null) {
continue;
}
var originalValue = propertyInfo.GetValue(Global); Object originalValue = propertyInfo.GetValue(this.Global);
var isChanged = propertyInfo.PropertyType.IsArray Boolean isChanged = propertyInfo.PropertyType.IsArray
? property.Value is IEnumerable enumerable && propertyInfo.TrySetArray(enumerable.Cast<object>(), Global) ? property.Value is IEnumerable enumerable && propertyInfo.TrySetArray(enumerable.Cast<Object>(), this.Global)
: SetValue(property.Value, originalValue, propertyInfo); : this.SetValue(property.Value, originalValue, propertyInfo);
if (!isChanged) continue; if (!isChanged) {
continue;
}
changedSettings.Add(property.Property); changedSettings.Add(property.Property);
PersistGlobalSettings(); this.PersistGlobalSettings();
} }
return changedSettings; return changedSettings;
@ -138,9 +143,9 @@ namespace Swan.Configuration
/// Gets the list. /// Gets the list.
/// </summary> /// </summary>
/// <returns>A List of ExtendedPropertyInfo of the type T.</returns> /// <returns>A List of ExtendedPropertyInfo of the type T.</returns>
public List<ExtendedPropertyInfo<T>>? GetList() public List<ExtendedPropertyInfo<T>> GetList()
{ {
var jsonData = Json.Deserialize(Json.Serialize(Global)) as Dictionary<string, object>; Dictionary<String, Object> jsonData = Json.Deserialize(Json.Serialize(this.Global)) as Dictionary<global::System.String, global::System.Object>;
return jsonData?.Keys return jsonData?.Keys
.Select(p => new ExtendedPropertyInfo<T>(p) { Value = jsonData[p] }) .Select(p => new ExtendedPropertyInfo<T>(p) { Value = jsonData[p] })
@ -152,26 +157,27 @@ namespace Swan.Configuration
/// </summary> /// </summary>
public void ResetGlobalSettings() public void ResetGlobalSettings()
{ {
lock (_syncRoot) lock (this._syncRoot) {
_global = Activator.CreateInstance<T>(); this._global = Activator.CreateInstance<T>();
PersistGlobalSettings();
} }
private bool SetValue(object property, object originalValue, PropertyInfo propertyInfo) this.PersistGlobalSettings();
}
private Boolean SetValue(Object property, Object originalValue, PropertyInfo propertyInfo)
{ {
switch (property) switch (property)
{ {
case null when originalValue == null: case null when originalValue == null:
break; break;
case null: case null:
propertyInfo.SetValue(Global, null); propertyInfo.SetValue(this.Global, null);
return true; return true;
default: default:
if (propertyInfo.PropertyType.TryParseBasicType(property, out var propertyValue) && if (propertyInfo.PropertyType.TryParseBasicType(property, out Object propertyValue) &&
!propertyValue.Equals(originalValue)) !propertyValue.Equals(originalValue))
{ {
propertyInfo.SetValue(Global, propertyValue); propertyInfo.SetValue(this.Global, propertyValue);
return true; return true;
} }

View File

@ -3,13 +3,11 @@ using System.IO;
using System.Security.Cryptography; using System.Security.Cryptography;
using System.Text; using System.Text;
namespace Swan.Cryptography namespace Swan.Cryptography {
{
/// <summary> /// <summary>
/// Use this class to compute a hash in MD4, SHA1, SHA256 or SHA512. /// Use this class to compute a hash in MD4, SHA1, SHA256 or SHA512.
/// </summary> /// </summary>
public static class Hasher public static class Hasher {
{
private static readonly Lazy<MD5> Md5Hasher = new Lazy<MD5>(MD5.Create, true); private static readonly Lazy<MD5> Md5Hasher = new Lazy<MD5>(MD5.Create, true);
private static readonly Lazy<SHA1> SHA1Hasher = new Lazy<SHA1>(SHA1.Create, true); private static readonly Lazy<SHA1> SHA1Hasher = new Lazy<SHA1>(SHA1.Create, true);
private static readonly Lazy<SHA256> SHA256Hasher = new Lazy<SHA256>(SHA256.Create, true); private static readonly Lazy<SHA256> SHA256Hasher = new Lazy<SHA256>(SHA256.Create, true);
@ -26,29 +24,31 @@ namespace Swan.Cryptography
/// </returns> /// </returns>
/// <exception cref="ArgumentNullException">stream.</exception> /// <exception cref="ArgumentNullException">stream.</exception>
[Obsolete("Use a better hasher.")] [Obsolete("Use a better hasher.")]
public static byte[] ComputeMD5(Stream @this, bool createHasher = false) [System.Diagnostics.CodeAnalysis.SuppressMessage("Codequalität", "IDE0067:Objekte verwerfen, bevor Bereich verloren geht", Justification = "<Ausstehend>")]
{ [System.Diagnostics.CodeAnalysis.SuppressMessage("Stil", "IDE0045:In bedingten Ausdruck konvertieren", Justification = "<Ausstehend>")]
if (@this == null) public static Byte[] ComputeMD5(Stream @this, Boolean createHasher = false) {
if(@this == null) {
throw new ArgumentNullException(nameof(@this)); throw new ArgumentNullException(nameof(@this));
}
var md5 = MD5.Create(); MD5 md5 = MD5.Create();
const int bufferSize = 4096; const Int32 bufferSize = 4096;
var readAheadBuffer = new byte[bufferSize]; Byte[] readAheadBuffer = new Byte[bufferSize];
var readAheadBytesRead = @this.Read(readAheadBuffer, 0, readAheadBuffer.Length); Int32 readAheadBytesRead = @this.Read(readAheadBuffer, 0, readAheadBuffer.Length);
do do {
{ Int32 bytesRead = readAheadBytesRead;
var bytesRead = readAheadBytesRead; Byte[] buffer = readAheadBuffer;
var buffer = readAheadBuffer;
readAheadBuffer = new byte[bufferSize]; readAheadBuffer = new Byte[bufferSize];
readAheadBytesRead = @this.Read(readAheadBuffer, 0, readAheadBuffer.Length); readAheadBytesRead = @this.Read(readAheadBuffer, 0, readAheadBuffer.Length);
if (readAheadBytesRead == 0) if(readAheadBytesRead == 0) {
md5.TransformFinalBlock(buffer, 0, bytesRead); _ = md5.TransformFinalBlock(buffer, 0, bytesRead);
else } else {
md5.TransformBlock(buffer, 0, bytesRead, buffer, 0); _ = md5.TransformBlock(buffer, 0, bytesRead, buffer, 0);
}
} }
while(readAheadBytesRead != 0); while(readAheadBytesRead != 0);
@ -62,8 +62,7 @@ namespace Swan.Cryptography
/// <param name="createHasher">if set to <c>true</c> [create hasher].</param> /// <param name="createHasher">if set to <c>true</c> [create hasher].</param>
/// <returns>The computed hash code.</returns> /// <returns>The computed hash code.</returns>
[Obsolete("Use a better hasher.")] [Obsolete("Use a better hasher.")]
public static byte[] ComputeMD5(string value, bool createHasher = false) => public static Byte[] ComputeMD5(String value, Boolean createHasher = false) => ComputeMD5(Encoding.UTF8.GetBytes(value), createHasher);
ComputeMD5(Encoding.UTF8.GetBytes(value), createHasher);
/// <summary> /// <summary>
/// Computes the MD5 hash of the given byte array. /// Computes the MD5 hash of the given byte array.
@ -72,8 +71,8 @@ namespace Swan.Cryptography
/// <param name="createHasher">if set to <c>true</c> [create hasher].</param> /// <param name="createHasher">if set to <c>true</c> [create hasher].</param>
/// <returns>The computed hash code.</returns> /// <returns>The computed hash code.</returns>
[Obsolete("Use a better hasher.")] [Obsolete("Use a better hasher.")]
public static byte[] ComputeMD5(byte[] data, bool createHasher = false) => [System.Diagnostics.CodeAnalysis.SuppressMessage("Codequalität", "IDE0067:Objekte verwerfen, bevor Bereich verloren geht", Justification = "<Ausstehend>")]
(createHasher ? MD5.Create() : Md5Hasher.Value).ComputeHash(data); public static Byte[] ComputeMD5(Byte[] data, Boolean createHasher = false) => (createHasher ? MD5.Create() : Md5Hasher.Value).ComputeHash(data);
/// <summary> /// <summary>
/// Computes the SHA-1 hash of the given string using UTF8 byte encoding. /// Computes the SHA-1 hash of the given string using UTF8 byte encoding.
@ -85,12 +84,13 @@ namespace Swan.Cryptography
/// using the SHA1 hash function. /// using the SHA1 hash function.
/// </returns> /// </returns>
[Obsolete("Use a better hasher.")] [Obsolete("Use a better hasher.")]
public static byte[] ComputeSha1(string @this, bool createHasher = false) [System.Diagnostics.CodeAnalysis.SuppressMessage("Codequalität", "IDE0067:Objekte verwerfen, bevor Bereich verloren geht", Justification = "<Ausstehend>")]
{ public static Byte[] ComputeSha1(String @this, Boolean createHasher = false) {
var inputBytes = Encoding.UTF8.GetBytes(@this); Byte[] inputBytes = Encoding.UTF8.GetBytes(@this);
return (createHasher ? SHA1.Create() : SHA1Hasher.Value).ComputeHash(inputBytes); return (createHasher ? SHA1.Create() : SHA1Hasher.Value).ComputeHash(inputBytes);
} }
/// <summary> /// <summary>
/// Computes the SHA-256 hash of the given string using UTF8 byte encoding. /// Computes the SHA-256 hash of the given string using UTF8 byte encoding.
/// </summary> /// </summary>
@ -100,12 +100,13 @@ namespace Swan.Cryptography
/// The computes a Hash-based Message Authentication Code (HMAC) /// The computes a Hash-based Message Authentication Code (HMAC)
/// by using the SHA256 hash function. /// by using the SHA256 hash function.
/// </returns> /// </returns>
public static byte[] ComputeSha256(string value, bool createHasher = false) [System.Diagnostics.CodeAnalysis.SuppressMessage("Codequalität", "IDE0067:Objekte verwerfen, bevor Bereich verloren geht", Justification = "<Ausstehend>")]
{ public static Byte[] ComputeSha256(String value, Boolean createHasher = false) {
var inputBytes = Encoding.UTF8.GetBytes(value); Byte[] inputBytes = Encoding.UTF8.GetBytes(value);
return (createHasher ? SHA256.Create() : SHA256Hasher.Value).ComputeHash(inputBytes); return (createHasher ? SHA256.Create() : SHA256Hasher.Value).ComputeHash(inputBytes);
} }
/// <summary> /// <summary>
/// Computes the SHA-512 hash of the given string using UTF8 byte encoding. /// Computes the SHA-512 hash of the given string using UTF8 byte encoding.
/// </summary> /// </summary>
@ -115,9 +116,9 @@ namespace Swan.Cryptography
/// The computes a Hash-based Message Authentication Code (HMAC) /// The computes a Hash-based Message Authentication Code (HMAC)
/// using the SHA512 hash function. /// using the SHA512 hash function.
/// </returns> /// </returns>
public static byte[] ComputeSha512(string value, bool createHasher = false) [System.Diagnostics.CodeAnalysis.SuppressMessage("Codequalität", "IDE0067:Objekte verwerfen, bevor Bereich verloren geht", Justification = "<Ausstehend>")]
{ public static Byte[] ComputeSha512(String value, Boolean createHasher = false) {
var inputBytes = Encoding.UTF8.GetBytes(value); Byte[] inputBytes = Encoding.UTF8.GetBytes(value);
return (createHasher ? SHA512.Create() : SHA512Hasher.Value).ComputeHash(inputBytes); return (createHasher ? SHA512.Create() : SHA512Hasher.Value).ComputeHash(inputBytes);
} }
} }

View File

@ -1,15 +1,13 @@
using System; using System;
namespace Swan namespace Swan {
{
/// <summary> /// <summary>
/// Represents a struct of DateTimeSpan to compare dates and get in /// Represents a struct of DateTimeSpan to compare dates and get in
/// separate fields the amount of time between those dates. /// separate fields the amount of time between those dates.
/// ///
/// Based on https://stackoverflow.com/a/9216404/1096693. /// Based on https://stackoverflow.com/a/9216404/1096693.
/// </summary> /// </summary>
public struct DateTimeSpan public struct DateTimeSpan {
{
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="DateTimeSpan"/> struct. /// Initializes a new instance of the <see cref="DateTimeSpan"/> struct.
/// </summary> /// </summary>
@ -20,15 +18,14 @@ namespace Swan
/// <param name="minutes">The minutes.</param> /// <param name="minutes">The minutes.</param>
/// <param name="seconds">The seconds.</param> /// <param name="seconds">The seconds.</param>
/// <param name="milliseconds">The milliseconds.</param> /// <param name="milliseconds">The milliseconds.</param>
public DateTimeSpan(int years, int months, int days, int hours, int minutes, int seconds, int milliseconds) public DateTimeSpan(Int32 years, Int32 months, Int32 days, Int32 hours, Int32 minutes, Int32 seconds, Int32 milliseconds) {
{ this.Years = years;
Years = years; this.Months = months;
Months = months; this.Days = days;
Days = days; this.Hours = hours;
Hours = hours; this.Minutes = minutes;
Minutes = minutes; this.Seconds = seconds;
Seconds = seconds; this.Milliseconds = milliseconds;
Milliseconds = milliseconds;
} }
/// <summary> /// <summary>
@ -37,7 +34,9 @@ namespace Swan
/// <value> /// <value>
/// The years. /// The years.
/// </value> /// </value>
public int Years { get; } public Int32 Years {
get;
}
/// <summary> /// <summary>
/// Gets the months. /// Gets the months.
@ -45,7 +44,9 @@ namespace Swan
/// <value> /// <value>
/// The months. /// The months.
/// </value> /// </value>
public int Months { get; } public Int32 Months {
get;
}
/// <summary> /// <summary>
/// Gets the days. /// Gets the days.
@ -53,7 +54,9 @@ namespace Swan
/// <value> /// <value>
/// The days. /// The days.
/// </value> /// </value>
public int Days { get; } public Int32 Days {
get;
}
/// <summary> /// <summary>
/// Gets the hours. /// Gets the hours.
@ -61,7 +64,9 @@ namespace Swan
/// <value> /// <value>
/// The hours. /// The hours.
/// </value> /// </value>
public int Hours { get; } public Int32 Hours {
get;
}
/// <summary> /// <summary>
/// Gets the minutes. /// Gets the minutes.
@ -69,7 +74,9 @@ namespace Swan
/// <value> /// <value>
/// The minutes. /// The minutes.
/// </value> /// </value>
public int Minutes { get; } public Int32 Minutes {
get;
}
/// <summary> /// <summary>
/// Gets the seconds. /// Gets the seconds.
@ -77,7 +84,9 @@ namespace Swan
/// <value> /// <value>
/// The seconds. /// The seconds.
/// </value> /// </value>
public int Seconds { get; } public Int32 Seconds {
get;
}
/// <summary> /// <summary>
/// Gets the milliseconds. /// Gets the milliseconds.
@ -85,74 +94,57 @@ namespace Swan
/// <value> /// <value>
/// The milliseconds. /// The milliseconds.
/// </value> /// </value>
public int Milliseconds { get; } public Int32 Milliseconds {
get;
}
internal static DateTimeSpan CompareDates(DateTime date1, DateTime date2) internal static DateTimeSpan CompareDates(DateTime date1, DateTime date2) {
{ if(date2 < date1) {
if (date2 < date1) DateTime sub = date1;
{
var sub = date1;
date1 = date2; date1 = date2;
date2 = sub; date2 = sub;
} }
var current = date1; DateTime current = date1;
var years = 0; Int32 years = 0;
var months = 0; Int32 months = 0;
var days = 0; Int32 days = 0;
var phase = Phase.Years; Phase phase = Phase.Years;
var span = new DateTimeSpan(); DateTimeSpan span = new DateTimeSpan();
var officialDay = current.Day; Int32 officialDay = current.Day;
while (phase != Phase.Done) while(phase != Phase.Done) {
{ switch(phase) {
switch (phase)
{
case Phase.Years: case Phase.Years:
if (current.AddYears(years + 1) > date2) if(current.AddYears(years + 1) > date2) {
{
phase = Phase.Months; phase = Phase.Months;
current = current.AddYears(years); current = current.AddYears(years);
} } else {
else
{
years++; years++;
} }
break; break;
case Phase.Months: case Phase.Months:
if (current.AddMonths(months + 1) > date2) if(current.AddMonths(months + 1) > date2) {
{
phase = Phase.Days; phase = Phase.Days;
current = current.AddMonths(months); current = current.AddMonths(months);
if(current.Day < officialDay && if(current.Day < officialDay &&
officialDay <= DateTime.DaysInMonth(current.Year, current.Month)) officialDay <= DateTime.DaysInMonth(current.Year, current.Month)) {
current = current.AddDays(officialDay - current.Day); current = current.AddDays(officialDay - current.Day);
} }
else } else {
{
months++; months++;
} }
break; break;
case Phase.Days: case Phase.Days:
if (current.AddDays(days + 1) > date2) if(current.AddDays(days + 1) > date2) {
{
current = current.AddDays(days); current = current.AddDays(days);
var timespan = date2 - current; TimeSpan timespan = date2 - current;
span = new DateTimeSpan( span = new DateTimeSpan(years, months, days, timespan.Hours, timespan.Minutes, timespan.Seconds, timespan.Milliseconds);
years,
months,
days,
timespan.Hours,
timespan.Minutes,
timespan.Seconds,
timespan.Milliseconds);
phase = Phase.Done; phase = Phase.Done;
} } else {
else
{
days++; days++;
} }
@ -163,8 +155,7 @@ namespace Swan
return span; return span;
} }
private enum Phase private enum Phase {
{
Years, Years,
Months, Months,
Days, Days,

View File

@ -3,58 +3,55 @@ using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
using Swan.Reflection; using Swan.Reflection;
namespace Swan namespace Swan {
{
/// <summary> /// <summary>
/// Contains useful constants and definitions. /// Contains useful constants and definitions.
/// </summary> /// </summary>
public static partial class Definitions public static partial class Definitions {
{
#region Main Dictionary Definition #region Main Dictionary Definition
/// <summary> /// <summary>
/// The basic types information. /// The basic types information.
/// </summary> /// </summary>
public static readonly Lazy<Dictionary<Type, ExtendedTypeInfo>> BasicTypesInfo = new Lazy<Dictionary<Type, ExtendedTypeInfo>>(() => public static readonly Lazy<Dictionary<Type, ExtendedTypeInfo>> BasicTypesInfo = new Lazy<Dictionary<Type, ExtendedTypeInfo>>(() => new Dictionary<Type, ExtendedTypeInfo> {
new Dictionary<Type, ExtendedTypeInfo>
{
// Non-Nullables // Non-Nullables
{typeof(DateTime), new ExtendedTypeInfo<DateTime>()}, {typeof(DateTime), new ExtendedTypeInfo<DateTime>()},
{typeof(byte), new ExtendedTypeInfo<byte>()}, {typeof(Byte), new ExtendedTypeInfo<Byte>()},
{typeof(sbyte), new ExtendedTypeInfo<sbyte>()}, {typeof(SByte), new ExtendedTypeInfo<SByte>()},
{typeof(int), new ExtendedTypeInfo<int>()}, {typeof(Int32), new ExtendedTypeInfo<Int32>()},
{typeof(uint), new ExtendedTypeInfo<uint>()}, {typeof(UInt32), new ExtendedTypeInfo<UInt32>()},
{typeof(short), new ExtendedTypeInfo<short>()}, {typeof(Int16), new ExtendedTypeInfo<Int16>()},
{typeof(ushort), new ExtendedTypeInfo<ushort>()}, {typeof(UInt16), new ExtendedTypeInfo<UInt16>()},
{typeof(long), new ExtendedTypeInfo<long>()}, {typeof(Int64), new ExtendedTypeInfo<Int64>()},
{typeof(ulong), new ExtendedTypeInfo<ulong>()}, {typeof(UInt64), new ExtendedTypeInfo<UInt64>()},
{typeof(float), new ExtendedTypeInfo<float>()}, {typeof(Single), new ExtendedTypeInfo<Single>()},
{typeof(double), new ExtendedTypeInfo<double>()}, {typeof(Double), new ExtendedTypeInfo<Double>()},
{typeof(char), new ExtendedTypeInfo<char>()}, {typeof(Char), new ExtendedTypeInfo<Char>()},
{typeof(bool), new ExtendedTypeInfo<bool>()}, {typeof(Boolean), new ExtendedTypeInfo<Boolean>()},
{typeof(decimal), new ExtendedTypeInfo<decimal>()}, {typeof(Decimal), new ExtendedTypeInfo<Decimal>()},
{typeof(Guid), new ExtendedTypeInfo<Guid>()}, {typeof(Guid), new ExtendedTypeInfo<Guid>()},
// Strings is also considered a basic type (it's the only basic reference type) // Strings is also considered a basic type (it's the only basic reference type)
{typeof(string), new ExtendedTypeInfo<string>()}, {typeof(String), new ExtendedTypeInfo<String>()},
// Nullables // Nullables
{typeof(DateTime?), new ExtendedTypeInfo<DateTime?>()}, {typeof(DateTime?), new ExtendedTypeInfo<DateTime?>()},
{typeof(byte?), new ExtendedTypeInfo<byte?>()}, {typeof(Byte?), new ExtendedTypeInfo<Byte?>()},
{typeof(sbyte?), new ExtendedTypeInfo<sbyte?>()}, {typeof(SByte?), new ExtendedTypeInfo<SByte?>()},
{typeof(int?), new ExtendedTypeInfo<int?>()}, {typeof(Int32?), new ExtendedTypeInfo<Int32?>()},
{typeof(uint?), new ExtendedTypeInfo<uint?>()}, {typeof(UInt32?), new ExtendedTypeInfo<UInt32?>()},
{typeof(short?), new ExtendedTypeInfo<short?>()}, {typeof(Int16?), new ExtendedTypeInfo<Int16?>()},
{typeof(ushort?), new ExtendedTypeInfo<ushort?>()}, {typeof(UInt16?), new ExtendedTypeInfo<UInt16?>()},
{typeof(long?), new ExtendedTypeInfo<long?>()}, {typeof(Int64?), new ExtendedTypeInfo<Int64?>()},
{typeof(ulong?), new ExtendedTypeInfo<ulong?>()}, {typeof(UInt64?), new ExtendedTypeInfo<UInt64?>()},
{typeof(float?), new ExtendedTypeInfo<float?>()}, {typeof(Single?), new ExtendedTypeInfo<Single?>()},
{typeof(double?), new ExtendedTypeInfo<double?>()}, {typeof(Double?), new ExtendedTypeInfo<Double?>()},
{typeof(char?), new ExtendedTypeInfo<char?>()}, {typeof(Char?), new ExtendedTypeInfo<Char?>()},
{typeof(bool?), new ExtendedTypeInfo<bool?>()}, {typeof(Boolean?), new ExtendedTypeInfo<Boolean?>()},
{typeof(decimal?), new ExtendedTypeInfo<decimal?>()}, {typeof(Decimal?), new ExtendedTypeInfo<Decimal?>()},
{typeof(Guid?), new ExtendedTypeInfo<Guid?>()}, {typeof(Guid?), new ExtendedTypeInfo<Guid?>()},
// Additional Types // Additional Types
@ -80,11 +77,9 @@ namespace Swan
/// <value> /// <value>
/// All numeric types. /// All numeric types.
/// </value> /// </value>
public static IReadOnlyCollection<Type> AllNumericTypes { get; } = new ReadOnlyCollection<Type>( public static IReadOnlyCollection<Type> AllNumericTypes {
BasicTypesInfo get;
.Value } = new ReadOnlyCollection<Type>(BasicTypesInfo.Value.Where(kvp => kvp.Value.IsNumeric).Select(kvp => kvp.Key).ToArray());
.Where(kvp => kvp.Value.IsNumeric)
.Select(kvp => kvp.Key).ToArray());
/// <summary> /// <summary>
/// Gets all numeric types without their nullable counterparts. /// Gets all numeric types without their nullable counterparts.
@ -93,11 +88,9 @@ namespace Swan
/// <value> /// <value>
/// All numeric value types. /// All numeric value types.
/// </value> /// </value>
public static IReadOnlyCollection<Type> AllNumericValueTypes { get; } = new ReadOnlyCollection<Type>( public static IReadOnlyCollection<Type> AllNumericValueTypes {
BasicTypesInfo get;
.Value } = new ReadOnlyCollection<Type>(BasicTypesInfo.Value.Where(kvp => kvp.Value.IsNumeric && !kvp.Value.IsNullableValueType).Select(kvp => kvp.Key).ToArray());
.Where(kvp => kvp.Value.IsNumeric && !kvp.Value.IsNullableValueType)
.Select(kvp => kvp.Key).ToArray());
/// <summary> /// <summary>
/// Contains all basic value types. i.e. excludes string and nullables. /// Contains all basic value types. i.e. excludes string and nullables.
@ -105,11 +98,9 @@ namespace Swan
/// <value> /// <value>
/// All basic value types. /// All basic value types.
/// </value> /// </value>
public static IReadOnlyCollection<Type> AllBasicValueTypes { get; } = new ReadOnlyCollection<Type>( public static IReadOnlyCollection<Type> AllBasicValueTypes {
BasicTypesInfo get;
.Value } = new ReadOnlyCollection<Type>(BasicTypesInfo.Value.Where(kvp => kvp.Value.IsValueType).Select(kvp => kvp.Key).ToArray());
.Where(kvp => kvp.Value.IsValueType)
.Select(kvp => kvp.Key).ToArray());
/// <summary> /// <summary>
/// Contains all basic value types including the string type. i.e. excludes nullables. /// Contains all basic value types including the string type. i.e. excludes nullables.
@ -117,11 +108,9 @@ namespace Swan
/// <value> /// <value>
/// All basic value and string types. /// All basic value and string types.
/// </value> /// </value>
public static IReadOnlyCollection<Type> AllBasicValueAndStringTypes { get; } = new ReadOnlyCollection<Type>( public static IReadOnlyCollection<Type> AllBasicValueAndStringTypes {
BasicTypesInfo get;
.Value } = new ReadOnlyCollection<Type>(BasicTypesInfo.Value.Where(kvp => kvp.Value.IsValueType || kvp.Key == typeof(String)).Select(kvp => kvp.Key).ToArray());
.Where(kvp => kvp.Value.IsValueType || kvp.Key == typeof(string))
.Select(kvp => kvp.Key).ToArray());
/// <summary> /// <summary>
/// Gets all nullable value types. i.e. excludes string and all basic value types. /// Gets all nullable value types. i.e. excludes string and all basic value types.
@ -129,10 +118,8 @@ namespace Swan
/// <value> /// <value>
/// All basic nullable value types. /// All basic nullable value types.
/// </value> /// </value>
public static IReadOnlyCollection<Type> AllBasicNullableValueTypes { get; } = new ReadOnlyCollection<Type>( public static IReadOnlyCollection<Type> AllBasicNullableValueTypes {
BasicTypesInfo get;
.Value } = new ReadOnlyCollection<Type>(BasicTypesInfo.Value.Where(kvp => kvp.Value.IsNullableValueType).Select(kvp => kvp.Key).ToArray());
.Where(kvp => kvp.Value.IsNullableValueType)
.Select(kvp => kvp.Key).ToArray());
} }
} }

View File

@ -1,12 +1,11 @@
using System.Text; using System;
using System.Text;
namespace Swan namespace Swan {
{
/// <summary> /// <summary>
/// Contains useful constants and definitions. /// Contains useful constants and definitions.
/// </summary> /// </summary>
public static partial class Definitions public static partial class Definitions {
{
/// <summary> /// <summary>
/// The MS Windows codepage 1252 encoding used in some legacy scenarios /// The MS Windows codepage 1252 encoding used in some legacy scenarios
/// such as default CSV text encoding from Excel. /// such as default CSV text encoding from Excel.
@ -22,15 +21,11 @@ namespace Swan
/// <summary> /// <summary>
/// Initializes the <see cref="Definitions"/> class. /// Initializes the <see cref="Definitions"/> class.
/// </summary> /// </summary>
static Definitions() static Definitions() {
{ CurrentAnsiEncoding = Encoding.GetEncoding(default(Int32));
CurrentAnsiEncoding = Encoding.GetEncoding(default(int)); try {
try
{
Windows1252Encoding = Encoding.GetEncoding(1252); Windows1252Encoding = Encoding.GetEncoding(1252);
} } catch {
catch
{
// ignore, the codepage is not available use default // ignore, the codepage is not available use default
Windows1252Encoding = CurrentAnsiEncoding; Windows1252Encoding = CurrentAnsiEncoding;
} }

View File

@ -4,8 +4,7 @@ using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
namespace Swan.Diagnostics namespace Swan.Diagnostics {
{
/// <summary> /// <summary>
/// A simple benchmarking class. /// A simple benchmarking class.
/// </summary> /// </summary>
@ -33,36 +32,27 @@ namespace Swan.Diagnostics
/// } /// }
/// </code> /// </code>
/// </example> /// </example>
public static partial class Benchmark public static partial class Benchmark {
{ private static readonly Object SyncLock = new Object();
private static readonly object SyncLock = new object(); private static readonly Dictionary<String, List<TimeSpan>> Measures = new Dictionary<String, List<TimeSpan>>();
private static readonly Dictionary<string, List<TimeSpan>> Measures = new Dictionary<string, List<TimeSpan>>();
/// <summary> /// <summary>
/// Starts measuring with the given identifier. /// Starts measuring with the given identifier.
/// </summary> /// </summary>
/// <param name="identifier">The identifier.</param> /// <param name="identifier">The identifier.</param>
/// <returns>A disposable object that when disposed, adds a benchmark result.</returns> /// <returns>A disposable object that when disposed, adds a benchmark result.</returns>
public static IDisposable Start(string identifier) => new BenchmarkUnit(identifier); public static IDisposable Start(String identifier) => new BenchmarkUnit(identifier);
/// <summary> /// <summary>
/// Outputs the benchmark statistics. /// Outputs the benchmark statistics.
/// </summary> /// </summary>
/// <returns>A string containing human-readable statistics.</returns> /// <returns>A string containing human-readable statistics.</returns>
public static string Dump() public static String Dump() {
{ StringBuilder builder = new StringBuilder();
var builder = new StringBuilder();
lock (SyncLock) lock(SyncLock) {
{ foreach(KeyValuePair<String, List<TimeSpan>> kvp in Measures) {
foreach (var kvp in Measures) _ = builder.Append($"BID: {kvp.Key,-30} | ").Append($"CNT: {kvp.Value.Count,6} | ").Append($"AVG: {kvp.Value.Average(t => t.TotalMilliseconds),8:0.000} ms. | ").Append($"MAX: {kvp.Value.Max(t => t.TotalMilliseconds),8:0.000} ms. | ").Append($"MIN: {kvp.Value.Min(t => t.TotalMilliseconds),8:0.000} ms. | ").Append(Environment.NewLine);
{
builder.Append($"BID: {kvp.Key,-30} | ")
.Append($"CNT: {kvp.Value.Count,6} | ")
.Append($"AVG: {kvp.Value.Average(t => t.TotalMilliseconds),8:0.000} ms. | ")
.Append($"MAX: {kvp.Value.Max(t => t.TotalMilliseconds),8:0.000} ms. | ")
.Append($"MIN: {kvp.Value.Min(t => t.TotalMilliseconds),8:0.000} ms. | ")
.Append(Environment.NewLine);
} }
} }
@ -78,24 +68,19 @@ namespace Swan.Diagnostics
/// A time interval that represents a specified time, where the specification is in units of ticks. /// A time interval that represents a specified time, where the specification is in units of ticks.
/// </returns> /// </returns>
/// <exception cref="ArgumentNullException">target.</exception> /// <exception cref="ArgumentNullException">target.</exception>
public static TimeSpan BenchmarkAction(Action target) public static TimeSpan BenchmarkAction(Action target) {
{ if(target == null) {
if (target == null)
throw new ArgumentNullException(nameof(target)); throw new ArgumentNullException(nameof(target));
}
var sw = Stopwatch.IsHighResolution ? new HighResolutionTimer() : new Stopwatch(); Stopwatch sw = Stopwatch.IsHighResolution ? new HighResolutionTimer() : new Stopwatch();
try try {
{
sw.Start(); sw.Start();
target.Invoke(); target.Invoke();
} } catch {
catch
{
// swallow // swallow
} } finally {
finally
{
sw.Stop(); sw.Stop();
} }
@ -107,12 +92,11 @@ namespace Swan.Diagnostics
/// </summary> /// </summary>
/// <param name="identifier">The identifier.</param> /// <param name="identifier">The identifier.</param>
/// <param name="elapsed">The elapsed.</param> /// <param name="elapsed">The elapsed.</param>
private static void Add(string identifier, TimeSpan elapsed) private static void Add(String identifier, TimeSpan elapsed) {
{ lock(SyncLock) {
lock (SyncLock) if(Measures.ContainsKey(identifier) == false) {
{
if (Measures.ContainsKey(identifier) == false)
Measures[identifier] = new List<TimeSpan>(1024 * 1024); Measures[identifier] = new List<TimeSpan>(1024 * 1024);
}
Measures[identifier].Add(elapsed); Measures[identifier].Add(elapsed);
} }

View File

@ -1,49 +1,46 @@
using System; #nullable enable
using System;
using System.Diagnostics; using System.Diagnostics;
namespace Swan.Diagnostics namespace Swan.Diagnostics {
{ public static partial class Benchmark {
public static partial class Benchmark
{
/// <summary> /// <summary>
/// Represents a disposable benchmark unit. /// Represents a disposable benchmark unit.
/// </summary> /// </summary>
/// <seealso cref="IDisposable" /> /// <seealso cref="IDisposable" />
private sealed class BenchmarkUnit : IDisposable private sealed class BenchmarkUnit : IDisposable {
{ private readonly String _identifier;
private readonly string _identifier; private Boolean _isDisposed; // To detect redundant calls
private bool _isDisposed; // To detect redundant calls
private Stopwatch? _stopwatch = new Stopwatch(); private Stopwatch? _stopwatch = new Stopwatch();
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="BenchmarkUnit" /> class. /// Initializes a new instance of the <see cref="BenchmarkUnit" /> class.
/// </summary> /// </summary>
/// <param name="identifier">The identifier.</param> /// <param name="identifier">The identifier.</param>
public BenchmarkUnit(string identifier) public BenchmarkUnit(String identifier) {
{ this._identifier = identifier;
_identifier = identifier; this._stopwatch?.Start();
_stopwatch?.Start();
} }
/// <inheritdoc /> /// <inheritdoc />
public void Dispose() => Dispose(true); public void Dispose() => this.Dispose(true);
/// <summary> /// <summary>
/// Releases unmanaged and - optionally - managed resources. /// Releases unmanaged and - optionally - managed resources.
/// </summary> /// </summary>
/// <param name="alsoManaged"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param> /// <param name="alsoManaged"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
private void Dispose(bool alsoManaged) private void Dispose(Boolean alsoManaged) {
{ if(this._isDisposed) {
if (_isDisposed) return; return;
if (alsoManaged)
{
Add(_identifier, _stopwatch?.Elapsed ?? default);
_stopwatch?.Stop();
} }
_stopwatch = null; if(alsoManaged) {
_isDisposed = true; Add(this._identifier, this._stopwatch?.Elapsed ?? default);
this._stopwatch?.Stop();
}
this._stopwatch = null;
this._isDisposed = true;
} }
} }
} }

View File

@ -1,32 +1,30 @@
namespace Swan.Diagnostics using System;
{
using System;
using System.Diagnostics; using System.Diagnostics;
namespace Swan.Diagnostics {
/// <summary> /// <summary>
/// Provides access to a high-resolution, time measuring device. /// Provides access to a high-resolution, time measuring device.
/// </summary> /// </summary>
/// <seealso cref="Stopwatch" /> /// <seealso cref="Stopwatch" />
public class HighResolutionTimer : Stopwatch public class HighResolutionTimer : Stopwatch {
{
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="HighResolutionTimer"/> class. /// Initializes a new instance of the <see cref="HighResolutionTimer"/> class.
/// </summary> /// </summary>
/// <exception cref="NotSupportedException">High-resolution timer not available.</exception> /// <exception cref="NotSupportedException">High-resolution timer not available.</exception>
public HighResolutionTimer() public HighResolutionTimer() {
{ if(!IsHighResolution) {
if (!IsHighResolution)
throw new NotSupportedException("High-resolution timer not available"); throw new NotSupportedException("High-resolution timer not available");
} }
}
/// <summary> /// <summary>
/// Gets the number of microseconds per timer tick. /// Gets the number of microseconds per timer tick.
/// </summary> /// </summary>
public static double MicrosecondsPerTick { get; } = 1000000d / Frequency; public static Double MicrosecondsPerTick { get; } = 1000000d / Frequency;
/// <summary> /// <summary>
/// Gets the elapsed microseconds. /// Gets the elapsed microseconds.
/// </summary> /// </summary>
public long ElapsedMicroseconds => (long)(ElapsedTicks * MicrosecondsPerTick); public Int64 ElapsedMicroseconds => (Int64)(this.ElapsedTicks * MicrosecondsPerTick);
} }
} }

View File

@ -1,28 +1,21 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Swan.Collections; using Swan.Collections;
namespace Swan namespace Swan {
{
/// <summary> /// <summary>
/// Provide Enumerations helpers with internal cache. /// Provide Enumerations helpers with internal cache.
/// </summary> /// </summary>
public class EnumHelper public class EnumHelper
: SingletonBase<CollectionCacheRepository<Tuple<string, object>>> : SingletonBase<CollectionCacheRepository<Tuple<String, Object>>> {
{
/// <summary> /// <summary>
/// Gets all the names and enumerators from a specific Enum type. /// Gets all the names and enumerators from a specific Enum type.
/// </summary> /// </summary>
/// <typeparam name="T">The type of the attribute to be retrieved.</typeparam> /// <typeparam name="T">The type of the attribute to be retrieved.</typeparam>
/// <returns>A tuple of enumerator names and their value stored for the specified type.</returns> /// <returns>A tuple of enumerator names and their value stored for the specified type.</returns>
public static IEnumerable<Tuple<string, object>> Retrieve<T>() public static IEnumerable<Tuple<String, Object>> Retrieve<T>() where T : struct, IConvertible => Instance.Retrieve(typeof(T), t => Enum.GetValues(t).Cast<Object>().Select(item => Tuple.Create(Enum.GetName(t, item), item)));
where T : struct, IConvertible
{
return Instance.Retrieve(typeof(T), t => Enum.GetValues(t)
.Cast<object>()
.Select(item => Tuple.Create(Enum.GetName(t, item), item)));
}
/// <summary> /// <summary>
/// Gets the cached items with the enum item value. /// Gets the cached items with the enum item value.
@ -33,12 +26,7 @@ namespace Swan
/// A collection of Type/Tuple pairs /// A collection of Type/Tuple pairs
/// that represents items with the enum item value. /// that represents items with the enum item value.
/// </returns> /// </returns>
public static IEnumerable<Tuple<int, string>> GetItemsWithValue<T>(bool humanize = true) public static IEnumerable<Tuple<Int32, String>> GetItemsWithValue<T>(Boolean humanize = true) where T : struct, IConvertible => Retrieve<T>().Select(x => Tuple.Create((Int32)x.Item2, humanize ? x.Item1.Humanize() : x.Item1));
where T : struct, IConvertible
{
return Retrieve<T>()
.Select(x => Tuple.Create((int) x.Item2, humanize ? x.Item1.Humanize() : x.Item1));
}
/// <summary> /// <summary>
/// Gets the flag values. /// Gets the flag values.
@ -49,14 +37,7 @@ namespace Swan
/// <returns> /// <returns>
/// A list of values in the flag. /// A list of values in the flag.
/// </returns> /// </returns>
public static IEnumerable<int> GetFlagValues<TEnum>(int value, bool ignoreZero = false) public static IEnumerable<Int32> GetFlagValues<TEnum>(Int32 value, Boolean ignoreZero = false) where TEnum : struct, IConvertible => Retrieve<TEnum>().Select(x => (Int32)x.Item2).When(() => ignoreZero, q => q.Where(f => f != 0)).Where(x => (x & value) == x);
where TEnum : struct, IConvertible
{
return Retrieve<TEnum>()
.Select(x => (int) x.Item2)
.When(() => ignoreZero, q => q.Where(f => f != 0))
.Where(x => (x & value) == x);
}
/// <summary> /// <summary>
/// Gets the flag values. /// Gets the flag values.
@ -67,14 +48,7 @@ namespace Swan
/// <returns> /// <returns>
/// A list of values in the flag. /// A list of values in the flag.
/// </returns> /// </returns>
public static IEnumerable<long> GetFlagValues<TEnum>(long value, bool ignoreZero = false) public static IEnumerable<Int64> GetFlagValues<TEnum>(Int64 value, Boolean ignoreZero = false) where TEnum : struct, IConvertible => Retrieve<TEnum>().Select(x => (Int64)x.Item2).When(() => ignoreZero, q => q.Where(f => f != 0)).Where(x => (x & value) == x);
where TEnum : struct, IConvertible
{
return Retrieve<TEnum>()
.Select(x => (long) x.Item2)
.When(() => ignoreZero, q => q.Where(f => f != 0))
.Where(x => (x & value) == x);
}
/// <summary> /// <summary>
/// Gets the flag values. /// Gets the flag values.
@ -85,14 +59,7 @@ namespace Swan
/// <returns> /// <returns>
/// A list of values in the flag. /// A list of values in the flag.
/// </returns> /// </returns>
public static IEnumerable<byte> GetFlagValues<TEnum>(byte value, bool ignoreZero = false) public static IEnumerable<Byte> GetFlagValues<TEnum>(Byte value, Boolean ignoreZero = false) where TEnum : struct, IConvertible => Retrieve<TEnum>().Select(x => (Byte)x.Item2).When(() => ignoreZero, q => q.Where(f => f != 0)).Where(x => (x & value) == x);
where TEnum : struct, IConvertible
{
return Retrieve<TEnum>()
.Select(x => (byte) x.Item2)
.When(() => ignoreZero, q => q.Where(f => f != 0))
.Where(x => (x & value) == x);
}
/// <summary> /// <summary>
/// Gets the flag names. /// Gets the flag names.
@ -104,14 +71,7 @@ namespace Swan
/// <returns> /// <returns>
/// A list of flag names. /// A list of flag names.
/// </returns> /// </returns>
public static IEnumerable<string> GetFlagNames<TEnum>(int value, bool ignoreZero = false, bool humanize = true) public static IEnumerable<String> GetFlagNames<TEnum>(Int32 value, Boolean ignoreZero = false, Boolean humanize = true) where TEnum : struct, IConvertible => Retrieve<TEnum>().When(() => ignoreZero, q => q.Where(f => (Int32)f.Item2 != 0)).Where(x => ((Int32)x.Item2 & value) == (Int32)x.Item2).Select(x => humanize ? x.Item1.Humanize() : x.Item1);
where TEnum : struct, IConvertible
{
return Retrieve<TEnum>()
.When(() => ignoreZero, q => q.Where(f => (int) f.Item2 != 0))
.Where(x => ((int) x.Item2 & value) == (int) x.Item2)
.Select(x => humanize ? x.Item1.Humanize() : x.Item1);
}
/// <summary> /// <summary>
/// Gets the flag names. /// Gets the flag names.
@ -123,14 +83,7 @@ namespace Swan
/// <returns> /// <returns>
/// A list of flag names. /// A list of flag names.
/// </returns> /// </returns>
public static IEnumerable<string> GetFlagNames<TEnum>(long value, bool ignoreZero = false, bool humanize = true) public static IEnumerable<String> GetFlagNames<TEnum>(Int64 value, Boolean ignoreZero = false, Boolean humanize = true) where TEnum : struct, IConvertible => Retrieve<TEnum>().When(() => ignoreZero, q => q.Where(f => (Int64)f.Item2 != 0)).Where(x => ((Int64)x.Item2 & value) == (Int64)x.Item2).Select(x => humanize ? x.Item1.Humanize() : x.Item1);
where TEnum : struct, IConvertible
{
return Retrieve<TEnum>()
.When(() => ignoreZero, q => q.Where(f => (long) f.Item2 != 0))
.Where(x => ((long) x.Item2 & value) == (long) x.Item2)
.Select(x => humanize ? x.Item1.Humanize() : x.Item1);
}
/// <summary> /// <summary>
/// Gets the flag names. /// Gets the flag names.
@ -142,14 +95,7 @@ namespace Swan
/// <returns> /// <returns>
/// A list of flag names. /// A list of flag names.
/// </returns> /// </returns>
public static IEnumerable<string> GetFlagNames<TEnum>(byte value, bool ignoreZero = false, bool humanize = true) public static IEnumerable<String> GetFlagNames<TEnum>(Byte value, Boolean ignoreZero = false, Boolean humanize = true) where TEnum : struct, IConvertible => Retrieve<TEnum>().When(() => ignoreZero, q => q.Where(f => (Byte)f.Item2 != 0)).Where(x => ((Byte)x.Item2 & value) == (Byte)x.Item2).Select(x => humanize ? x.Item1.Humanize() : x.Item1);
where TEnum : struct, IConvertible
{
return Retrieve<TEnum>()
.When(() => ignoreZero, q => q.Where(f => (byte) f.Item2 != 0))
.Where(x => ((byte) x.Item2 & value) == (byte) x.Item2)
.Select(x => humanize ? x.Item1.Humanize() : x.Item1);
}
/// <summary> /// <summary>
/// Gets the cached items with the enum item index. /// Gets the cached items with the enum item index.
@ -159,13 +105,10 @@ namespace Swan
/// <returns> /// <returns>
/// A collection of Type/Tuple pairs that represents items with the enum item value. /// A collection of Type/Tuple pairs that represents items with the enum item value.
/// </returns> /// </returns>
public static IEnumerable<Tuple<int, string>> GetItemsWithIndex<T>(bool humanize = true) public static IEnumerable<Tuple<Int32, String>> GetItemsWithIndex<T>(Boolean humanize = true) where T : struct, IConvertible {
where T : struct, IConvertible Int32 i = 0;
{
var i = 0;
return Retrieve<T>() return Retrieve<T>().Select(x => Tuple.Create(i++, humanize ? x.Item1.Humanize() : x.Item1));
.Select(x => Tuple.Create(i++, humanize ? x.Item1.Humanize() : x.Item1));
} }
} }
} }

View File

@ -1,10 +1,8 @@
namespace Swan namespace Swan {
{
/// <summary> /// <summary>
/// Enumeration of Operating Systems. /// Enumeration of Operating Systems.
/// </summary> /// </summary>
public enum OperatingSystem public enum OperatingSystem {
{
/// <summary> /// <summary>
/// Unknown OS /// Unknown OS
/// </summary> /// </summary>
@ -29,8 +27,7 @@
/// <summary> /// <summary>
/// Defines Endianness, big or little. /// Defines Endianness, big or little.
/// </summary> /// </summary>
public enum Endianness public enum Endianness {
{
/// <summary> /// <summary>
/// In big endian, you store the most significant byte in the smallest address. /// In big endian, you store the most significant byte in the smallest address.
/// </summary> /// </summary>
@ -45,8 +42,7 @@
/// <summary> /// <summary>
/// Enumerates the JSON serializer cases to use: None (keeps the same case), PascalCase, or camelCase. /// Enumerates the JSON serializer cases to use: None (keeps the same case), PascalCase, or camelCase.
/// </summary> /// </summary>
public enum JsonSerializerCase public enum JsonSerializerCase {
{
/// <summary> /// <summary>
/// The none /// The none
/// </summary> /// </summary>

View File

@ -7,13 +7,11 @@ using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Swan namespace Swan {
{
/// <summary> /// <summary>
/// Provides various extension methods for byte arrays and streams. /// Provides various extension methods for byte arrays and streams.
/// </summary> /// </summary>
public static class ByteArrayExtensions public static class ByteArrayExtensions {
{
/// <summary> /// <summary>
/// Converts an array of bytes to its lower-case, hexadecimal representation. /// Converts an array of bytes to its lower-case, hexadecimal representation.
/// </summary> /// </summary>
@ -23,8 +21,7 @@ namespace Swan
/// The specified string instance; no actual conversion is performed. /// The specified string instance; no actual conversion is performed.
/// </returns> /// </returns>
/// <exception cref="ArgumentNullException">bytes.</exception> /// <exception cref="ArgumentNullException">bytes.</exception>
public static string ToLowerHex(this byte[] bytes, bool addPrefix = false) public static String ToLowerHex(this Byte[] bytes, Boolean addPrefix = false) => ToHex(bytes, addPrefix, "x2");
=> ToHex(bytes, addPrefix, "x2");
/// <summary> /// <summary>
/// Converts an array of bytes to its upper-case, hexadecimal representation. /// Converts an array of bytes to its upper-case, hexadecimal representation.
@ -35,8 +32,7 @@ namespace Swan
/// The specified string instance; no actual conversion is performed. /// The specified string instance; no actual conversion is performed.
/// </returns> /// </returns>
/// <exception cref="ArgumentNullException">bytes.</exception> /// <exception cref="ArgumentNullException">bytes.</exception>
public static string ToUpperHex(this byte[] bytes, bool addPrefix = false) public static String ToUpperHex(this Byte[] bytes, Boolean addPrefix = false) => ToHex(bytes, addPrefix, "X2");
=> ToHex(bytes, addPrefix, "X2");
/// <summary> /// <summary>
/// Converts an array of bytes to a sequence of dash-separated, hexadecimal, /// Converts an array of bytes to a sequence of dash-separated, hexadecimal,
@ -47,14 +43,14 @@ namespace Swan
/// A string of hexadecimal pairs separated by hyphens, where each pair represents /// A string of hexadecimal pairs separated by hyphens, where each pair represents
/// the corresponding element in value; for example, "7F-2C-4A-00". /// the corresponding element in value; for example, "7F-2C-4A-00".
/// </returns> /// </returns>
public static string ToDashedHex(this byte[] bytes) => BitConverter.ToString(bytes); public static String ToDashedHex(this Byte[] bytes) => BitConverter.ToString(bytes);
/// <summary> /// <summary>
/// Converts an array of bytes to a base-64 encoded string. /// Converts an array of bytes to a base-64 encoded string.
/// </summary> /// </summary>
/// <param name="bytes">The bytes.</param> /// <param name="bytes">The bytes.</param>
/// <returns>A <see cref="string" /> converted from an array of bytes.</returns> /// <returns>A <see cref="String" /> converted from an array of bytes.</returns>
public static string ToBase64(this byte[] bytes) => Convert.ToBase64String(bytes); public static String ToBase64(this Byte[] bytes) => Convert.ToBase64String(bytes);
/// <summary> /// <summary>
/// Converts a set of hexadecimal characters (uppercase or lowercase) /// Converts a set of hexadecimal characters (uppercase or lowercase)
@ -66,15 +62,12 @@ namespace Swan
/// A byte array containing the results of encoding the specified set of characters. /// A byte array containing the results of encoding the specified set of characters.
/// </returns> /// </returns>
/// <exception cref="ArgumentNullException">hex.</exception> /// <exception cref="ArgumentNullException">hex.</exception>
public static byte[] ConvertHexadecimalToBytes(this string @this) public static Byte[] ConvertHexadecimalToBytes(this String @this) {
{ if(String.IsNullOrWhiteSpace(@this)) {
if (string.IsNullOrWhiteSpace(@this))
throw new ArgumentNullException(nameof(@this)); throw new ArgumentNullException(nameof(@this));
}
return Enumerable return Enumerable.Range(0, @this.Length / 2).Select(x => Convert.ToByte(@this.Substring(x * 2, 2), 16)).ToArray();
.Range(0, @this.Length / 2)
.Select(x => Convert.ToByte(@this.Substring(x * 2, 2), 16))
.ToArray();
} }
/// <summary> /// <summary>
@ -86,7 +79,7 @@ namespace Swan
/// <returns> /// <returns>
/// Bit value at the given offset. /// Bit value at the given offset.
/// </returns> /// </returns>
public static byte GetBitValueAt(this byte @this, byte offset, byte length = 1) => (byte)((@this >> offset) & ~(0xff << length)); public static Byte GetBitValueAt(this Byte @this, Byte offset, Byte length = 1) => (Byte)((@this >> offset) & ~(0xff << length));
/// <summary> /// <summary>
/// Sets the bit value at the given offset. /// Sets the bit value at the given offset.
@ -96,12 +89,11 @@ namespace Swan
/// <param name="length">The length.</param> /// <param name="length">The length.</param>
/// <param name="value">The value.</param> /// <param name="value">The value.</param>
/// <returns>Bit value at the given offset.</returns> /// <returns>Bit value at the given offset.</returns>
public static byte SetBitValueAt(this byte @this, byte offset, byte length, byte value) public static Byte SetBitValueAt(this Byte @this, Byte offset, Byte length, Byte value) {
{ Int32 mask = ~(0xff << length);
var mask = ~(0xff << length); Byte valueAt = (Byte)(value & mask);
var valueAt = (byte)(value & mask);
return (byte)((valueAt << offset) | (@this & ~(mask << offset))); return (Byte)((valueAt << offset) | (@this & ~(mask << offset)));
} }
/// <summary> /// <summary>
@ -111,7 +103,7 @@ namespace Swan
/// <param name="offset">The offset.</param> /// <param name="offset">The offset.</param>
/// <param name="value">The value.</param> /// <param name="value">The value.</param>
/// <returns>Bit value at the given offset.</returns> /// <returns>Bit value at the given offset.</returns>
public static byte SetBitValueAt(this byte @this, byte offset, byte value) => @this.SetBitValueAt(offset, 1, value); public static Byte SetBitValueAt(this Byte @this, Byte offset, Byte value) => @this.SetBitValueAt(offset, 1, value);
/// <summary> /// <summary>
/// Splits a byte array delimited by the specified sequence of bytes. /// Splits a byte array delimited by the specified sequence of bytes.
@ -130,32 +122,29 @@ namespace Swan
/// or /// or
/// sequence. /// sequence.
/// </exception> /// </exception>
public static List<byte[]> Split(this byte[] @this, int offset, params byte[] sequence) public static List<Byte[]> Split(this Byte[] @this, Int32 offset, params Byte[] sequence) {
{ if(@this == null) {
if (@this == null)
throw new ArgumentNullException(nameof(@this)); throw new ArgumentNullException(nameof(@this));
}
if (sequence == null) if(sequence == null) {
throw new ArgumentNullException(nameof(sequence)); throw new ArgumentNullException(nameof(sequence));
}
var seqOffset = offset.Clamp(0, @this.Length - 1); Int32 seqOffset = offset.Clamp(0, @this.Length - 1);
var result = new List<byte[]>(); List<Byte[]> result = new List<Byte[]>();
while (seqOffset < @this.Length) while(seqOffset < @this.Length) {
{ Int32 separatorStartIndex = @this.GetIndexOf(sequence, seqOffset);
var separatorStartIndex = @this.GetIndexOf(sequence, seqOffset);
if (separatorStartIndex >= 0) if(separatorStartIndex >= 0) {
{ Byte[] item = new Byte[separatorStartIndex - seqOffset + sequence.Length];
var item = new byte[separatorStartIndex - seqOffset + sequence.Length];
Array.Copy(@this, seqOffset, item, 0, item.Length); Array.Copy(@this, seqOffset, item, 0, item.Length);
result.Add(item); result.Add(item);
seqOffset += item.Length; seqOffset += item.Length;
} } else {
else Byte[] item = new Byte[@this.Length - seqOffset];
{
var item = new byte[@this.Length - seqOffset];
Array.Copy(@this, seqOffset, item, 0, item.Length); Array.Copy(@this, seqOffset, item, 0, item.Length);
result.Add(item); result.Add(item);
break; break;
@ -173,12 +162,12 @@ namespace Swan
/// A byte array containing the results of encoding the specified set of characters. /// A byte array containing the results of encoding the specified set of characters.
/// </returns> /// </returns>
/// <exception cref="System.ArgumentNullException">this</exception> /// <exception cref="System.ArgumentNullException">this</exception>
public static byte[] DeepClone(this byte[] @this) public static Byte[] DeepClone(this Byte[] @this) {
{ if(@this == null) {
if (@this == null)
throw new ArgumentNullException(nameof(@this)); throw new ArgumentNullException(nameof(@this));
}
var result = new byte[@this.Length]; Byte[] result = new Byte[@this.Length];
Array.Copy(@this, result, @this.Length); Array.Copy(@this, result, @this.Length);
return result; return result;
} }
@ -192,15 +181,16 @@ namespace Swan
/// A new trimmed byte array. /// A new trimmed byte array.
/// </returns> /// </returns>
/// <exception cref="ArgumentNullException">buffer.</exception> /// <exception cref="ArgumentNullException">buffer.</exception>
public static byte[] TrimStart(this byte[] buffer, params byte[] sequence) public static Byte[] TrimStart(this Byte[] buffer, params Byte[] sequence) {
{ if(buffer == null) {
if (buffer == null)
throw new ArgumentNullException(nameof(buffer)); throw new ArgumentNullException(nameof(buffer));
}
if (buffer.StartsWith(sequence) == false) if(buffer.StartsWith(sequence) == false) {
return buffer.DeepClone(); return buffer.DeepClone();
}
var result = new byte[buffer.Length - sequence.Length]; Byte[] result = new Byte[buffer.Length - sequence.Length];
Array.Copy(buffer, sequence.Length, result, 0, result.Length); Array.Copy(buffer, sequence.Length, result, 0, result.Length);
return result; return result;
} }
@ -214,15 +204,16 @@ namespace Swan
/// A byte array containing the results of encoding the specified set of characters. /// A byte array containing the results of encoding the specified set of characters.
/// </returns> /// </returns>
/// <exception cref="ArgumentNullException">buffer.</exception> /// <exception cref="ArgumentNullException">buffer.</exception>
public static byte[] TrimEnd(this byte[] buffer, params byte[] sequence) public static Byte[] TrimEnd(this Byte[] buffer, params Byte[] sequence) {
{ if(buffer == null) {
if (buffer == null)
throw new ArgumentNullException(nameof(buffer)); throw new ArgumentNullException(nameof(buffer));
}
if (buffer.EndsWith(sequence) == false) if(buffer.EndsWith(sequence) == false) {
return buffer.DeepClone(); return buffer.DeepClone();
}
var result = new byte[buffer.Length - sequence.Length]; Byte[] result = new Byte[buffer.Length - sequence.Length];
Array.Copy(buffer, 0, result, 0, result.Length); Array.Copy(buffer, 0, result, 0, result.Length);
return result; return result;
} }
@ -234,9 +225,8 @@ namespace Swan
/// <param name="buffer">The buffer.</param> /// <param name="buffer">The buffer.</param>
/// <param name="sequence">The sequence.</param> /// <param name="sequence">The sequence.</param>
/// <returns>A byte array containing the results of encoding the specified set of characters.</returns> /// <returns>A byte array containing the results of encoding the specified set of characters.</returns>
public static byte[] Trim(this byte[] buffer, params byte[] sequence) public static Byte[] Trim(this Byte[] buffer, params Byte[] sequence) {
{ Byte[] trimStart = buffer.TrimStart(sequence);
var trimStart = buffer.TrimStart(sequence);
return trimStart.TrimEnd(sequence); return trimStart.TrimEnd(sequence);
} }
@ -249,12 +239,12 @@ namespace Swan
/// True if the specified buffer is ends; otherwise, false. /// True if the specified buffer is ends; otherwise, false.
/// </returns> /// </returns>
/// <exception cref="ArgumentNullException">buffer.</exception> /// <exception cref="ArgumentNullException">buffer.</exception>
public static bool EndsWith(this byte[] buffer, params byte[] sequence) public static Boolean EndsWith(this Byte[] buffer, params Byte[] sequence) {
{ if(buffer == null) {
if (buffer == null)
throw new ArgumentNullException(nameof(buffer)); throw new ArgumentNullException(nameof(buffer));
}
var startIndex = buffer.Length - sequence.Length; Int32 startIndex = buffer.Length - sequence.Length;
return buffer.GetIndexOf(sequence, startIndex) == startIndex; return buffer.GetIndexOf(sequence, startIndex) == startIndex;
} }
@ -264,7 +254,7 @@ namespace Swan
/// <param name="buffer">The buffer.</param> /// <param name="buffer">The buffer.</param>
/// <param name="sequence">The sequence.</param> /// <param name="sequence">The sequence.</param>
/// <returns><c>true</c> if the specified buffer starts; otherwise, <c>false</c>.</returns> /// <returns><c>true</c> if the specified buffer starts; otherwise, <c>false</c>.</returns>
public static bool StartsWith(this byte[] buffer, params byte[] sequence) => buffer.GetIndexOf(sequence) == 0; public static Boolean StartsWith(this Byte[] buffer, params Byte[] sequence) => buffer.GetIndexOf(sequence) == 0;
/// <summary> /// <summary>
/// Determines whether the buffer contains the specified sequence. /// Determines whether the buffer contains the specified sequence.
@ -274,7 +264,7 @@ namespace Swan
/// <returns> /// <returns>
/// <c>true</c> if [contains] [the specified sequence]; otherwise, <c>false</c>. /// <c>true</c> if [contains] [the specified sequence]; otherwise, <c>false</c>.
/// </returns> /// </returns>
public static bool Contains(this byte[] buffer, params byte[] sequence) => buffer.GetIndexOf(sequence) >= 0; public static Boolean Contains(this Byte[] buffer, params Byte[] sequence) => buffer.GetIndexOf(sequence) >= 0;
/// <summary> /// <summary>
/// Determines whether the buffer exactly matches, byte by byte the specified sequence. /// Determines whether the buffer exactly matches, byte by byte the specified sequence.
@ -285,13 +275,14 @@ namespace Swan
/// <c>true</c> if [is equal to] [the specified sequence]; otherwise, <c>false</c>. /// <c>true</c> if [is equal to] [the specified sequence]; otherwise, <c>false</c>.
/// </returns> /// </returns>
/// <exception cref="ArgumentNullException">buffer.</exception> /// <exception cref="ArgumentNullException">buffer.</exception>
public static bool IsEqualTo(this byte[] buffer, params byte[] sequence) public static Boolean IsEqualTo(this Byte[] buffer, params Byte[] sequence) {
{ if(ReferenceEquals(buffer, sequence)) {
if (ReferenceEquals(buffer, sequence))
return true; return true;
}
if (buffer == null) if(buffer == null) {
throw new ArgumentNullException(nameof(buffer)); throw new ArgumentNullException(nameof(buffer));
}
return buffer.Length == sequence.Length && buffer.GetIndexOf(sequence) == 0; return buffer.Length == sequence.Length && buffer.GetIndexOf(sequence) == 0;
} }
@ -309,30 +300,37 @@ namespace Swan
/// or /// or
/// sequence. /// sequence.
/// </exception> /// </exception>
public static int GetIndexOf(this byte[] buffer, byte[] sequence, int offset = 0) public static Int32 GetIndexOf(this Byte[] buffer, Byte[] sequence, Int32 offset = 0) {
{ if(buffer == null) {
if (buffer == null)
throw new ArgumentNullException(nameof(buffer)); throw new ArgumentNullException(nameof(buffer));
if (sequence == null) }
if(sequence == null) {
throw new ArgumentNullException(nameof(sequence)); throw new ArgumentNullException(nameof(sequence));
if (sequence.Length == 0) }
return -1;
if (sequence.Length > buffer.Length)
return -1;
var seqOffset = offset < 0 ? 0 : offset; if(sequence.Length == 0) {
return -1;
}
var matchedCount = 0; if(sequence.Length > buffer.Length) {
for (var i = seqOffset; i < buffer.Length; i++) return -1;
{ }
if (buffer[i] == sequence[matchedCount])
Int32 seqOffset = offset < 0 ? 0 : offset;
Int32 matchedCount = 0;
for(Int32 i = seqOffset; i < buffer.Length; i++) {
if(buffer[i] == sequence[matchedCount]) {
matchedCount++; matchedCount++;
else } else {
matchedCount = 0; matchedCount = 0;
}
if (matchedCount == sequence.Length) if(matchedCount == sequence.Length) {
return i - (matchedCount - 1); return i - (matchedCount - 1);
} }
}
return -1; return -1;
} }
@ -350,13 +348,14 @@ namespace Swan
/// or /// or
/// buffer. /// buffer.
/// </exception> /// </exception>
public static MemoryStream Append(this MemoryStream stream, byte[] buffer) public static MemoryStream Append(this MemoryStream stream, Byte[] buffer) {
{ if(stream == null) {
if (stream == null)
throw new ArgumentNullException(nameof(stream)); throw new ArgumentNullException(nameof(stream));
}
if (buffer == null) if(buffer == null) {
throw new ArgumentNullException(nameof(buffer)); throw new ArgumentNullException(nameof(buffer));
}
stream.Write(buffer, 0, buffer.Length); stream.Write(buffer, 0, buffer.Length);
return stream; return stream;
@ -371,7 +370,7 @@ namespace Swan
/// Block of bytes to the current stream using data read from a buffer. /// Block of bytes to the current stream using data read from a buffer.
/// </returns> /// </returns>
/// <exception cref="ArgumentNullException">buffer.</exception> /// <exception cref="ArgumentNullException">buffer.</exception>
public static MemoryStream Append(this MemoryStream stream, IEnumerable<byte> buffer) => Append(stream, buffer?.ToArray()); public static MemoryStream Append(this MemoryStream stream, IEnumerable<Byte> buffer) => Append(stream, buffer?.ToArray());
/// <summary> /// <summary>
/// Appends the Memory Stream with the specified set of buffers. /// Appends the Memory Stream with the specified set of buffers.
@ -382,13 +381,14 @@ namespace Swan
/// Block of bytes to the current stream using data read from a buffer. /// Block of bytes to the current stream using data read from a buffer.
/// </returns> /// </returns>
/// <exception cref="ArgumentNullException">buffers.</exception> /// <exception cref="ArgumentNullException">buffers.</exception>
public static MemoryStream Append(this MemoryStream stream, IEnumerable<byte[]> buffers) public static MemoryStream Append(this MemoryStream stream, IEnumerable<Byte[]> buffers) {
{ if(buffers == null) {
if (buffers == null)
throw new ArgumentNullException(nameof(buffers)); throw new ArgumentNullException(nameof(buffers));
}
foreach (var buffer in buffers) foreach(Byte[] buffer in buffers) {
Append(stream, buffer); _ = Append(stream, buffer);
}
return stream; return stream;
} }
@ -399,14 +399,14 @@ namespace Swan
/// <param name="buffer">The buffer.</param> /// <param name="buffer">The buffer.</param>
/// <param name="encoding">The encoding.</param> /// <param name="encoding">The encoding.</param>
/// <returns>A <see cref="System.String" /> that contains the results of decoding the specified sequence of bytes.</returns> /// <returns>A <see cref="System.String" /> that contains the results of decoding the specified sequence of bytes.</returns>
public static string ToText(this IEnumerable<byte> buffer, Encoding encoding) => encoding.GetString(buffer.ToArray()); public static String ToText(this IEnumerable<Byte> buffer, Encoding encoding) => encoding.GetString(buffer.ToArray());
/// <summary> /// <summary>
/// Converts an array of bytes into text with UTF8 encoding. /// Converts an array of bytes into text with UTF8 encoding.
/// </summary> /// </summary>
/// <param name="buffer">The buffer.</param> /// <param name="buffer">The buffer.</param>
/// <returns>A <see cref="System.String" /> that contains the results of decoding the specified sequence of bytes.</returns> /// <returns>A <see cref="System.String" /> that contains the results of decoding the specified sequence of bytes.</returns>
public static string ToText(this IEnumerable<byte> buffer) => buffer.ToText(Encoding.UTF8); public static String ToText(this IEnumerable<Byte> buffer) => buffer.ToText(Encoding.UTF8);
/// <summary> /// <summary>
/// Reads the bytes asynchronous. /// Reads the bytes asynchronous.
@ -419,37 +419,33 @@ namespace Swan
/// A byte array containing the results of encoding the specified set of characters. /// A byte array containing the results of encoding the specified set of characters.
/// </returns> /// </returns>
/// <exception cref="ArgumentNullException">stream.</exception> /// <exception cref="ArgumentNullException">stream.</exception>
public static async Task<byte[]> ReadBytesAsync(this Stream stream, long length, int bufferLength, CancellationToken cancellationToken = default) public static async Task<Byte[]> ReadBytesAsync(this Stream stream, Int64 length, Int32 bufferLength, CancellationToken cancellationToken = default) {
{ if(stream == null) {
if (stream == null)
throw new ArgumentNullException(nameof(stream)); throw new ArgumentNullException(nameof(stream));
}
using (var dest = new MemoryStream()) using MemoryStream dest = new MemoryStream();
{ try {
try Byte[] buff = new Byte[bufferLength];
{ while(length > 0) {
var buff = new byte[bufferLength]; if(length < bufferLength) {
while (length > 0) bufferLength = (Int32)length;
{ }
if (length < bufferLength)
bufferLength = (int)length;
var nread = await stream.ReadAsync(buff, 0, bufferLength, cancellationToken).ConfigureAwait(false); Int32 nread = await stream.ReadAsync(buff, 0, bufferLength, cancellationToken).ConfigureAwait(false);
if (nread == 0) if(nread == 0) {
break; break;
}
dest.Write(buff, 0, nread); dest.Write(buff, 0, nread);
length -= nread; length -= nread;
} }
} } catch {
catch
{
// ignored // ignored
} }
return dest.ToArray(); return dest.ToArray();
} }
}
/// <summary> /// <summary>
/// Reads the bytes asynchronous. /// Reads the bytes asynchronous.
@ -461,44 +457,42 @@ namespace Swan
/// A byte array containing the results of encoding the specified set of characters. /// A byte array containing the results of encoding the specified set of characters.
/// </returns> /// </returns>
/// <exception cref="ArgumentNullException">stream.</exception> /// <exception cref="ArgumentNullException">stream.</exception>
public static async Task<byte[]> ReadBytesAsync(this Stream stream, int length, CancellationToken cancellationToken = default) public static async Task<Byte[]> ReadBytesAsync(this Stream stream, Int32 length, CancellationToken cancellationToken = default) {
{ if(stream == null) {
if (stream == null)
throw new ArgumentNullException(nameof(stream)); throw new ArgumentNullException(nameof(stream));
}
var buff = new byte[length]; Byte[] buff = new Byte[length];
var offset = 0; Int32 offset = 0;
try try {
{ while(length > 0) {
while (length > 0) Int32 nread = await stream.ReadAsync(buff, offset, length, cancellationToken).ConfigureAwait(false);
{ if(nread == 0) {
var nread = await stream.ReadAsync(buff, offset, length, cancellationToken).ConfigureAwait(false);
if (nread == 0)
break; break;
}
offset += nread; offset += nread;
length -= nread; length -= nread;
} }
} } catch {
catch
{
// ignored // ignored
} }
return new ArraySegment<byte>(buff, 0, offset).ToArray(); return new ArraySegment<Byte>(buff, 0, offset).ToArray();
} }
private static string ToHex(byte[] bytes, bool addPrefix, string format) private static String ToHex(Byte[] bytes, Boolean addPrefix, String format) {
{ if(bytes == null) {
if (bytes == null)
throw new ArgumentNullException(nameof(bytes)); throw new ArgumentNullException(nameof(bytes));
}
var sb = new StringBuilder(bytes.Length * 2); StringBuilder sb = new StringBuilder(bytes.Length * 2);
foreach (var item in bytes) foreach(Byte item in bytes) {
sb.Append(item.ToString(format, CultureInfo.InvariantCulture)); _ = sb.Append(item.ToString(format, CultureInfo.InvariantCulture));
}
return $"{(addPrefix ? "0x" : string.Empty)}{sb}"; return $"{(addPrefix ? "0x" : String.Empty)}{sb}";
} }
} }
} }

View File

@ -1,13 +1,12 @@
using System; using System;
using Swan.Collections; using Swan.Collections;
namespace Swan namespace Swan {
{
/// <summary> /// <summary>
/// Provides extension methods for types implementing <see cref="IComponentCollection{T}"/>. /// Provides extension methods for types implementing <see cref="IComponentCollection{T}"/>.
/// </summary> /// </summary>
public static class ComponentCollectionExtensions public static class ComponentCollectionExtensions {
{
/// <summary> /// <summary>
/// Adds the specified component to a collection, without giving it a name. /// Adds the specified component to a collection, without giving it a name.
/// </summary> /// </summary>

View File

@ -3,15 +3,12 @@ using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
namespace Swan namespace Swan {
{
/// <summary> /// <summary>
/// Provides extension methods for <see cref="DateTime"/>. /// Provides extension methods for <see cref="DateTime"/>.
/// </summary> /// </summary>
public static class DateExtensions public static class DateExtensions {
{ private static readonly Dictionary<String, Int32> DateRanges = new Dictionary<String, Int32>() {
private static readonly Dictionary<string, int> DateRanges = new Dictionary<string, int>()
{
{ "minute", 59}, { "minute", 59},
{ "hour", 23}, { "hour", 23},
{ "dayOfMonth", 31}, { "dayOfMonth", 31},
@ -24,16 +21,14 @@ namespace Swan
/// </summary> /// </summary>
/// <param name="this">The <see cref="DateTime"/> on which this method is called.</param> /// <param name="this">The <see cref="DateTime"/> on which this method is called.</param>
/// <returns>The concatenation of date.Year, date.Month and date.Day.</returns> /// <returns>The concatenation of date.Year, date.Month and date.Day.</returns>
public static string ToSortableDate(this DateTime @this) public static String ToSortableDate(this DateTime @this) => $"{@this.Year:0000}-{@this.Month:00}-{@this.Day:00}";
=> $"{@this.Year:0000}-{@this.Month:00}-{@this.Day:00}";
/// <summary> /// <summary>
/// Converts the date to a YYYY-MM-DD HH:II:SS string. /// Converts the date to a YYYY-MM-DD HH:II:SS string.
/// </summary> /// </summary>
/// <param name="this">The <see cref="DateTime"/> on which this method is called.</param> /// <param name="this">The <see cref="DateTime"/> on which this method is called.</param>
/// <returns>The concatenation of date.Year, date.Month, date.Day, date.Hour, date.Minute and date.Second.</returns> /// <returns>The concatenation of date.Year, date.Month, date.Day, date.Hour, date.Minute and date.Second.</returns>
public static string ToSortableDateTime(this DateTime @this) public static String ToSortableDateTime(this DateTime @this) => $"{@this.Year:0000}-{@this.Month:00}-{@this.Day:00} {@this.Hour:00}:{@this.Minute:00}:{@this.Second:00}";
=> $"{@this.Year:0000}-{@this.Month:00}-{@this.Day:00} {@this.Hour:00}:{@this.Minute:00}:{@this.Second:00}";
/// <summary> /// <summary>
/// Parses a YYYY-MM-DD and optionally it time part, HH:II:SS into a DateTime. /// Parses a YYYY-MM-DD and optionally it time part, HH:II:SS into a DateTime.
@ -50,43 +45,44 @@ namespace Swan
/// <exception cref="ArgumentException"> /// <exception cref="ArgumentException">
/// Unable to parse sortable date and time. - sortableDate. /// Unable to parse sortable date and time. - sortableDate.
/// </exception> /// </exception>
public static DateTime ToDateTime(this string @this) public static DateTime ToDateTime(this String @this) {
{ if(String.IsNullOrWhiteSpace(@this)) {
if (string.IsNullOrWhiteSpace(@this))
throw new ArgumentNullException(nameof(@this)); throw new ArgumentNullException(nameof(@this));
}
var hour = 0; Int32 hour = 0;
var minute = 0; Int32 minute = 0;
var second = 0; Int32 second = 0;
var dateTimeParts = @this.Split(' '); String[] dateTimeParts = @this.Split(' ');
try try {
{ if(dateTimeParts.Length != 1 && dateTimeParts.Length != 2) {
if (dateTimeParts.Length != 1 && dateTimeParts.Length != 2)
throw new Exception(); throw new Exception();
}
var dateParts = dateTimeParts[0].Split('-'); String[] dateParts = dateTimeParts[0].Split('-');
if (dateParts.Length != 3) throw new Exception(); if(dateParts.Length != 3) {
throw new Exception();
}
var year = int.Parse(dateParts[0]); Int32 year = Int32.Parse(dateParts[0]);
var month = int.Parse(dateParts[1]); Int32 month = Int32.Parse(dateParts[1]);
var day = int.Parse(dateParts[2]); Int32 day = Int32.Parse(dateParts[2]);
if (dateTimeParts.Length > 1) if(dateTimeParts.Length > 1) {
{ String[] timeParts = dateTimeParts[1].Split(':');
var timeParts = dateTimeParts[1].Split(':'); if(timeParts.Length != 3) {
if (timeParts.Length != 3) throw new Exception(); throw new Exception();
}
hour = int.Parse(timeParts[0]); hour = Int32.Parse(timeParts[0]);
minute = int.Parse(timeParts[1]); minute = Int32.Parse(timeParts[1]);
second = int.Parse(timeParts[2]); second = Int32.Parse(timeParts[2]);
} }
return new DateTime(year, month, day, hour, minute, second); return new DateTime(year, month, day, hour, minute, second);
} } catch(Exception) {
catch (Exception)
{
throw new ArgumentException("Unable to parse sortable date and time.", nameof(@this)); throw new ArgumentException("Unable to parse sortable date and time.", nameof(@this));
} }
} }
@ -99,8 +95,7 @@ namespace Swan
/// <returns> /// <returns>
/// A sequence of integral numbers within a specified date's range. /// A sequence of integral numbers within a specified date's range.
/// </returns> /// </returns>
public static IEnumerable<DateTime> DateRange(this DateTime startDate, DateTime endDate) public static IEnumerable<DateTime> DateRange(this DateTime startDate, DateTime endDate) => Enumerable.Range(0, (endDate - startDate).Days + 1).Select(d => startDate.AddDays(d));
=> Enumerable.Range(0, (endDate - startDate).Days + 1).Select(d => startDate.AddDays(d));
/// <summary> /// <summary>
/// Rounds up a date to match a timespan. /// Rounds up a date to match a timespan.
@ -110,15 +105,14 @@ namespace Swan
/// <returns> /// <returns>
/// A new instance of the DateTime structure to the specified datetime and timespan ticks. /// A new instance of the DateTime structure to the specified datetime and timespan ticks.
/// </returns> /// </returns>
public static DateTime RoundUp(this DateTime date, TimeSpan timeSpan) public static DateTime RoundUp(this DateTime date, TimeSpan timeSpan) => new DateTime(((date.Ticks + timeSpan.Ticks - 1) / timeSpan.Ticks) * timeSpan.Ticks);
=> new DateTime(((date.Ticks + timeSpan.Ticks - 1) / timeSpan.Ticks) * timeSpan.Ticks);
/// <summary> /// <summary>
/// Get this datetime as a Unix epoch timestamp (seconds since Jan 1, 1970, midnight UTC). /// Get this datetime as a Unix epoch timestamp (seconds since Jan 1, 1970, midnight UTC).
/// </summary> /// </summary>
/// <param name="this">The <see cref="DateTime"/> on which this method is called.</param> /// <param name="this">The <see cref="DateTime"/> on which this method is called.</param>
/// <returns>Seconds since Unix epoch.</returns> /// <returns>Seconds since Unix epoch.</returns>
public static long ToUnixEpochDate(this DateTime @this) => new DateTimeOffset(@this).ToUniversalTime().ToUnixTimeSeconds(); public static Int64 ToUnixEpochDate(this DateTime @this) => new DateTimeOffset(@this).ToUniversalTime().ToUnixTimeSeconds();
/// <summary> /// <summary>
/// Compares a Date to another and returns a <c>DateTimeSpan</c>. /// Compares a Date to another and returns a <c>DateTimeSpan</c>.
@ -126,8 +120,7 @@ namespace Swan
/// <param name="dateStart">The date start.</param> /// <param name="dateStart">The date start.</param>
/// <param name="dateEnd">The date end.</param> /// <param name="dateEnd">The date end.</param>
/// <returns>A DateTimeSpan with the Years, Months, Days, Hours, Minutes, Seconds and Milliseconds between the dates.</returns> /// <returns>A DateTimeSpan with the Years, Months, Days, Hours, Minutes, Seconds and Milliseconds between the dates.</returns>
public static DateTimeSpan GetDateTimeSpan(this DateTime dateStart, DateTime dateEnd) public static DateTimeSpan GetDateTimeSpan(this DateTime dateStart, DateTime dateEnd) => DateTimeSpan.CompareDates(dateStart, dateEnd);
=> DateTimeSpan.CompareDates(dateStart, dateEnd);
/// <summary> /// <summary>
/// Compare the Date elements(Months, Days, Hours, Minutes). /// Compare the Date elements(Months, Days, Hours, Minutes).
@ -139,15 +132,13 @@ namespace Swan
/// <param name="month">The month. (1-12).</param> /// <param name="month">The month. (1-12).</param>
/// <param name="dayOfWeek">The day of week. (0-6)(Sunday = 0).</param> /// <param name="dayOfWeek">The day of week. (0-6)(Sunday = 0).</param>
/// <returns>Returns <c>true</c> if Months, Days, Hours and Minutes match, otherwise <c>false</c>.</returns> /// <returns>Returns <c>true</c> if Months, Days, Hours and Minutes match, otherwise <c>false</c>.</returns>
public static bool AsCronCanRun(this DateTime @this, int? minute = null, int? hour = null, int? dayOfMonth = null, int? month = null, int? dayOfWeek = null) public static Boolean AsCronCanRun(this DateTime @this, Int32? minute = null, Int32? hour = null, Int32? dayOfMonth = null, Int32? month = null, Int32? dayOfWeek = null) {
{ List<Boolean?> results = new List<Boolean?> {
var results = new List<bool?>
{
GetElementParts(minute, @this.Minute), GetElementParts(minute, @this.Minute),
GetElementParts(hour, @this.Hour), GetElementParts(hour, @this.Hour),
GetElementParts(dayOfMonth, @this.Day), GetElementParts(dayOfMonth, @this.Day),
GetElementParts(month, @this.Month), GetElementParts(month, @this.Month),
GetElementParts(dayOfWeek, (int) @this.DayOfWeek), GetElementParts(dayOfWeek, (Int32) @this.DayOfWeek),
}; };
return results.Any(x => x != false); return results.Any(x => x != false);
@ -163,15 +154,13 @@ namespace Swan
/// <param name="month">The month. (1-12).</param> /// <param name="month">The month. (1-12).</param>
/// <param name="dayOfWeek">The day of week. (0-6)(Sunday = 0).</param> /// <param name="dayOfWeek">The day of week. (0-6)(Sunday = 0).</param>
/// <returns>Returns <c>true</c> if Months, Days, Hours and Minutes match, otherwise <c>false</c>.</returns> /// <returns>Returns <c>true</c> if Months, Days, Hours and Minutes match, otherwise <c>false</c>.</returns>
public static bool AsCronCanRun(this DateTime @this, string minute = "*", string hour = "*", string dayOfMonth = "*", string month = "*", string dayOfWeek = "*") public static Boolean AsCronCanRun(this DateTime @this, String minute = "*", String hour = "*", String dayOfMonth = "*", String month = "*", String dayOfWeek = "*") {
{ List<Boolean?> results = new List<Boolean?> {
var results = new List<bool?>
{
GetElementParts(minute, nameof(minute), @this.Minute), GetElementParts(minute, nameof(minute), @this.Minute),
GetElementParts(hour, nameof(hour), @this.Hour), GetElementParts(hour, nameof(hour), @this.Hour),
GetElementParts(dayOfMonth, nameof(dayOfMonth), @this.Day), GetElementParts(dayOfMonth, nameof(dayOfMonth), @this.Day),
GetElementParts(month, nameof(month), @this.Month), GetElementParts(month, nameof(month), @this.Month),
GetElementParts(dayOfWeek, nameof(dayOfWeek), (int) @this.DayOfWeek), GetElementParts(dayOfWeek, nameof(dayOfWeek), (Int32) @this.DayOfWeek),
}; };
return results.Any(x => x != false); return results.Any(x => x != false);
@ -185,50 +174,53 @@ namespace Swan
/// <remarks> /// <remarks>
/// <para>If <paramref name="this"/> is not a UTC date / time, its UTC equivalent is converted, leaving <paramref name="this"/> unchanged.</para> /// <para>If <paramref name="this"/> is not a UTC date / time, its UTC equivalent is converted, leaving <paramref name="this"/> unchanged.</para>
/// </remarks> /// </remarks>
public static string ToRfc1123String(this DateTime @this) public static String ToRfc1123String(this DateTime @this) => @this.ToUniversalTime().ToString("R", CultureInfo.InvariantCulture);
=> @this.ToUniversalTime().ToString("R", CultureInfo.InvariantCulture);
private static bool? GetElementParts(int? status, int value) => status.HasValue ? status.Value == value : (bool?) null; private static Boolean? GetElementParts(Int32? status, Int32 value) => status.HasValue ? status.Value == value : (Boolean?)null;
private static bool? GetElementParts(string parts, string type, int value) private static Boolean? GetElementParts(String parts, String type, Int32 value) {
{ if(String.IsNullOrWhiteSpace(parts) || parts == "*") {
if (string.IsNullOrWhiteSpace(parts) || parts == "*")
return null; return null;
if (parts.Contains(","))
{
return parts.Split(',').Select(int.Parse).Contains(value);
} }
var stop = DateRanges[type]; if(parts.Contains(",")) {
return parts.Split(',').Select(Int32.Parse).Contains(value);
}
if (parts.Contains("/")) Int32 stop = DateRanges[type];
{
var multiple = int.Parse(parts.Split('/').Last());
var start = type == "dayOfMonth" || type == "month" ? 1 : 0;
for (var i = start; i <= stop; i += multiple) if(parts.Contains("/")) {
if (i == value) return true; Int32 multiple = Int32.Parse(parts.Split('/').Last());
Int32 start = type == "dayOfMonth" || type == "month" ? 1 : 0;
for(Int32 i = start; i <= stop; i += multiple) {
if(i == value) {
return true;
}
}
return false; return false;
} }
if (parts.Contains("-")) if(parts.Contains("-")) {
{ String[] range = parts.Split('-');
var range = parts.Split('-'); Int32 start = Int32.Parse(range.First());
var start = int.Parse(range.First()); stop = Math.Max(stop, Int32.Parse(range.Last()));
stop = Math.Max(stop, int.Parse(range.Last()));
if ((type == "dayOfMonth" || type == "month") && start == 0) if((type == "dayOfMonth" || type == "month") && start == 0) {
start = 1; start = 1;
}
for (var i = start; i <= stop; i++) for(Int32 i = start; i <= stop; i++) {
if (i == value) return true; if(i == value) {
return true;
}
}
return false; return false;
} }
return int.Parse(parts) == value; return Int32.Parse(parts) == value;
} }
} }
} }

View File

@ -1,13 +1,11 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
namespace Swan namespace Swan {
{
/// <summary> /// <summary>
/// Extension methods. /// Extension methods.
/// </summary> /// </summary>
public static partial class Extensions public static partial class Extensions {
{
/// <summary> /// <summary>
/// Gets the value if exists or default. /// Gets the value if exists or default.
/// </summary> /// </summary>
@ -20,10 +18,10 @@ namespace Swan
/// The value of the provided key or default. /// The value of the provided key or default.
/// </returns> /// </returns>
/// <exception cref="ArgumentNullException">dict.</exception> /// <exception cref="ArgumentNullException">dict.</exception>
public static TValue GetValueOrDefault<TKey, TValue>(this IDictionary<TKey, TValue> dict, TKey key, TValue defaultValue = default) public static TValue GetValueOrDefault<TKey, TValue>(this IDictionary<TKey, TValue> dict, TKey key, TValue defaultValue = default) {
{ if(dict == null) {
if (dict == null)
throw new ArgumentNullException(nameof(dict)); throw new ArgumentNullException(nameof(dict));
}
return dict.ContainsKey(key) ? dict[key] : defaultValue; return dict.ContainsKey(key) ? dict[key] : defaultValue;
} }
@ -46,18 +44,21 @@ namespace Swan
/// or /// or
/// valueFactory. /// valueFactory.
/// </exception> /// </exception>
public static TValue GetOrAdd<TKey, TValue>(this IDictionary<TKey, TValue> dict, TKey key, Func<TKey, TValue> valueFactory) public static TValue GetOrAdd<TKey, TValue>(this IDictionary<TKey, TValue> dict, TKey key, Func<TKey, TValue> valueFactory) {
{ if(dict == null) {
if (dict == null)
throw new ArgumentNullException(nameof(dict)); throw new ArgumentNullException(nameof(dict));
}
if (valueFactory == null) if(valueFactory == null) {
throw new ArgumentNullException(nameof(valueFactory)); throw new ArgumentNullException(nameof(valueFactory));
}
if(!dict.ContainsKey(key)) {
TValue value = valueFactory(key);
if(Equals(value, default)) {
return default;
}
if (!dict.ContainsKey(key))
{
var value = valueFactory(key);
if (Equals(value, default)) return default;
dict[key] = value; dict[key] = value;
} }
@ -72,13 +73,12 @@ namespace Swan
/// <param name="dict">The dictionary.</param> /// <param name="dict">The dictionary.</param>
/// <param name="itemAction">The item action.</param> /// <param name="itemAction">The item action.</param>
/// <exception cref="ArgumentNullException">dict.</exception> /// <exception cref="ArgumentNullException">dict.</exception>
public static void ForEach<TKey, TValue>(this IDictionary<TKey, TValue> dict, Action<TKey, TValue> itemAction) public static void ForEach<TKey, TValue>(this IDictionary<TKey, TValue> dict, Action<TKey, TValue> itemAction) {
{ if(dict == null) {
if (dict == null)
throw new ArgumentNullException(nameof(dict)); throw new ArgumentNullException(nameof(dict));
}
foreach (var kvp in dict) foreach(KeyValuePair<TKey, TValue> kvp in dict) {
{
itemAction(kvp.Key, kvp.Value); itemAction(kvp.Key, kvp.Value);
} }
} }

View File

@ -2,13 +2,11 @@
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
namespace Swan namespace Swan {
{
/// <summary> /// <summary>
/// Provides extension methods for <see cref="Exception"/>. /// Provides extension methods for <see cref="Exception"/>.
/// </summary> /// </summary>
public static class ExceptionExtensions public static class ExceptionExtensions {
{
/// <summary> /// <summary>
/// Returns a value that tells whether an <see cref="Exception"/> is of a type that /// Returns a value that tells whether an <see cref="Exception"/> is of a type that
/// we better not catch and ignore. /// we better not catch and ignore.
@ -16,10 +14,7 @@ namespace Swan
/// <param name="this">The exception being thrown.</param> /// <param name="this">The exception being thrown.</param>
/// <returns><see langword="true"/> if <paramref name="this"/> is a critical exception; /// <returns><see langword="true"/> if <paramref name="this"/> is a critical exception;
/// otherwise, <see langword="false"/>.</returns> /// otherwise, <see langword="false"/>.</returns>
public static bool IsCriticalException(this Exception @this) public static Boolean IsCriticalException(this Exception @this) => @this.IsCriticalExceptionCore() || (@this.InnerException?.IsCriticalException() ?? false) || @this is AggregateException aggregateException && aggregateException.InnerExceptions.Any(e => e.IsCriticalException());
=> @this.IsCriticalExceptionCore()
|| (@this.InnerException?.IsCriticalException() ?? false)
|| (@this is AggregateException aggregateException && aggregateException.InnerExceptions.Any(e => e.IsCriticalException()));
/// <summary> /// <summary>
/// Returns a value that tells whether an <see cref="Exception"/> is of a type that /// Returns a value that tells whether an <see cref="Exception"/> is of a type that
@ -28,23 +23,10 @@ namespace Swan
/// <param name="this">The exception being thrown.</param> /// <param name="this">The exception being thrown.</param>
/// <returns><see langword="true"/> if <paramref name="this"/> is a fatal exception; /// <returns><see langword="true"/> if <paramref name="this"/> is a fatal exception;
/// otherwise, <see langword="false"/>.</returns> /// otherwise, <see langword="false"/>.</returns>
public static bool IsFatalException(this Exception @this) public static Boolean IsFatalException(this Exception @this) => @this.IsFatalExceptionCore() || (@this.InnerException?.IsFatalException() ?? false) || @this is AggregateException aggregateException && aggregateException.InnerExceptions.Any(e => e.IsFatalException());
=> @this.IsFatalExceptionCore()
|| (@this.InnerException?.IsFatalException() ?? false)
|| (@this is AggregateException aggregateException && aggregateException.InnerExceptions.Any(e => e.IsFatalException()));
private static bool IsCriticalExceptionCore(this Exception @this) private static Boolean IsCriticalExceptionCore(this Exception @this) => IsFatalExceptionCore(@this) || @this is AppDomainUnloadedException || @this is BadImageFormatException || @this is CannotUnloadAppDomainException || @this is InvalidProgramException || @this is NullReferenceException;
=> IsFatalExceptionCore(@this)
|| @this is AppDomainUnloadedException
|| @this is BadImageFormatException
|| @this is CannotUnloadAppDomainException
|| @this is InvalidProgramException
|| @this is NullReferenceException;
private static bool IsFatalExceptionCore(this Exception @this) private static Boolean IsFatalExceptionCore(this Exception @this) => @this is StackOverflowException || @this is OutOfMemoryException || @this is ThreadAbortException || @this is AccessViolationException;
=> @this is StackOverflowException
|| @this is OutOfMemoryException
|| @this is ThreadAbortException
|| @this is AccessViolationException;
} }
} }

View File

@ -2,13 +2,11 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
namespace Swan namespace Swan {
{
/// <summary> /// <summary>
/// Functional programming extension methods. /// Functional programming extension methods.
/// </summary> /// </summary>
public static class FunctionalExtensions public static class FunctionalExtensions {
{
/// <summary> /// <summary>
/// Whens the specified condition. /// Whens the specified condition.
/// </summary> /// </summary>
@ -26,19 +24,18 @@ namespace Swan
/// or /// or
/// fn. /// fn.
/// </exception> /// </exception>
public static IQueryable<T> When<T>( public static IQueryable<T> When<T>(this IQueryable<T> list, Func<Boolean> condition, Func<IQueryable<T>, IQueryable<T>> fn) {
this IQueryable<T> list, if(list == null) {
Func<bool> condition,
Func<IQueryable<T>, IQueryable<T>> fn)
{
if (list == null)
throw new ArgumentNullException(nameof(list)); throw new ArgumentNullException(nameof(list));
}
if (condition == null) if(condition == null) {
throw new ArgumentNullException(nameof(condition)); throw new ArgumentNullException(nameof(condition));
}
if (fn == null) if(fn == null) {
throw new ArgumentNullException(nameof(fn)); throw new ArgumentNullException(nameof(fn));
}
return condition() ? fn(list) : list; return condition() ? fn(list) : list;
} }
@ -60,19 +57,18 @@ namespace Swan
/// or /// or
/// fn. /// fn.
/// </exception> /// </exception>
public static IEnumerable<T> When<T>( public static IEnumerable<T> When<T>(this IEnumerable<T> list, Func<Boolean> condition, Func<IEnumerable<T>, IEnumerable<T>> fn) {
this IEnumerable<T> list, if(list == null) {
Func<bool> condition,
Func<IEnumerable<T>, IEnumerable<T>> fn)
{
if (list == null)
throw new ArgumentNullException(nameof(list)); throw new ArgumentNullException(nameof(list));
}
if (condition == null) if(condition == null) {
throw new ArgumentNullException(nameof(condition)); throw new ArgumentNullException(nameof(condition));
}
if (fn == null) if(fn == null) {
throw new ArgumentNullException(nameof(fn)); throw new ArgumentNullException(nameof(fn));
}
return condition() ? fn(list) : list; return condition() ? fn(list) : list;
} }
@ -94,22 +90,22 @@ namespace Swan
/// or /// or
/// value. /// value.
/// </exception> /// </exception>
public static IList<T> AddWhen<T>( public static IList<T> AddWhen<T>(this IList<T> list, Func<Boolean> condition, Func<T> value) {
this IList<T> list, if(list == null) {
Func<bool> condition,
Func<T> value)
{
if (list == null)
throw new ArgumentNullException(nameof(list)); throw new ArgumentNullException(nameof(list));
}
if (condition == null) if(condition == null) {
throw new ArgumentNullException(nameof(condition)); throw new ArgumentNullException(nameof(condition));
}
if (value == null) if(value == null) {
throw new ArgumentNullException(nameof(value)); throw new ArgumentNullException(nameof(value));
}
if (condition()) if(condition()) {
list.Add(value()); list.Add(value());
}
return list; return list;
} }
@ -125,16 +121,14 @@ namespace Swan
/// The IList. /// The IList.
/// </returns> /// </returns>
/// <exception cref="ArgumentNullException">list.</exception> /// <exception cref="ArgumentNullException">list.</exception>
public static IList<T> AddWhen<T>( public static IList<T> AddWhen<T>(this IList<T> list, Boolean condition, T value) {
this IList<T> list, if(list == null) {
bool condition,
T value)
{
if (list == null)
throw new ArgumentNullException(nameof(list)); throw new ArgumentNullException(nameof(list));
}
if (condition) if(condition) {
list.Add(value); list.Add(value);
}
return list; return list;
} }
@ -156,22 +150,22 @@ namespace Swan
/// or /// or
/// value. /// value.
/// </exception> /// </exception>
public static List<T> AddRangeWhen<T>( public static List<T> AddRangeWhen<T>(this List<T> list, Func<Boolean> condition, Func<IEnumerable<T>> value) {
this List<T> list, if(list == null) {
Func<bool> condition,
Func<IEnumerable<T>> value)
{
if (list == null)
throw new ArgumentNullException(nameof(list)); throw new ArgumentNullException(nameof(list));
}
if (condition == null) if(condition == null) {
throw new ArgumentNullException(nameof(condition)); throw new ArgumentNullException(nameof(condition));
}
if (value == null) if(value == null) {
throw new ArgumentNullException(nameof(value)); throw new ArgumentNullException(nameof(value));
}
if (condition()) if(condition()) {
list.AddRange(value()); list.AddRange(value());
}
return list; return list;
} }

View File

@ -1,25 +1,23 @@
using System; #nullable enable
using System;
using System.Collections; using System.Collections;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using Swan.Configuration; using Swan.Configuration;
using Swan.Reflection; using Swan.Reflection;
namespace Swan namespace Swan {
{
/// <summary> /// <summary>
/// Provides various extension methods for Reflection and Types. /// Provides various extension methods for Reflection and Types.
/// </summary> /// </summary>
public static class ReflectionExtensions public static class ReflectionExtensions {
{ private static readonly Lazy<ConcurrentDictionary<Tuple<Boolean, PropertyInfo>, Func<Object, Object>>> CacheGetMethods = new Lazy<ConcurrentDictionary<Tuple<Boolean, PropertyInfo>, Func<Object, Object>>>(() => new ConcurrentDictionary<Tuple<Boolean, PropertyInfo>, Func<Object, Object>>(), true);
private static readonly Lazy<ConcurrentDictionary<Tuple<bool, PropertyInfo>, Func<object, object>>> CacheGetMethods =
new Lazy<ConcurrentDictionary<Tuple<bool, PropertyInfo>, Func<object, object>>>(() => new ConcurrentDictionary<Tuple<bool, PropertyInfo>, Func<object, object>>(), true);
private static readonly Lazy<ConcurrentDictionary<Tuple<bool, PropertyInfo>, Action<object, object[]>>> CacheSetMethods = private static readonly Lazy<ConcurrentDictionary<Tuple<Boolean, PropertyInfo>, Action<Object, Object[]>>> CacheSetMethods = new Lazy<ConcurrentDictionary<Tuple<Boolean, PropertyInfo>, Action<Object, Object[]>>>(() => new ConcurrentDictionary<Tuple<Boolean, PropertyInfo>, Action<Object, Object[]>>(), true);
new Lazy<ConcurrentDictionary<Tuple<bool, PropertyInfo>, Action<object, object[]>>>(() => new ConcurrentDictionary<Tuple<bool, PropertyInfo>, Action<object, object[]>>(), true);
#region Assembly Extensions #region Assembly Extensions
@ -31,17 +29,14 @@ namespace Swan
/// Array of Type objects representing the types specified by an assembly. /// Array of Type objects representing the types specified by an assembly.
/// </returns> /// </returns>
/// <exception cref="ArgumentNullException">assembly.</exception> /// <exception cref="ArgumentNullException">assembly.</exception>
public static IEnumerable<Type> GetAllTypes(this Assembly assembly) public static IEnumerable<Type> GetAllTypes(this Assembly assembly) {
{ if(assembly == null) {
if (assembly == null)
throw new ArgumentNullException(nameof(assembly)); throw new ArgumentNullException(nameof(assembly));
try
{
return assembly.GetTypes();
} }
catch (ReflectionTypeLoadException e)
{ try {
return assembly.GetTypes();
} catch(ReflectionTypeLoadException e) {
return e.Types.Where(t => t != null); return e.Types.Where(t => t != null);
} }
} }
@ -58,10 +53,10 @@ namespace Swan
/// Default value of this type. /// Default value of this type.
/// </returns> /// </returns>
/// <exception cref="ArgumentNullException">type.</exception> /// <exception cref="ArgumentNullException">type.</exception>
public static object? GetDefault(this Type type) public static Object? GetDefault(this Type type) {
{ if(type == null) {
if (type == null)
throw new ArgumentNullException(nameof(type)); throw new ArgumentNullException(nameof(type));
}
return type.IsValueType ? Activator.CreateInstance(type) : default; return type.IsValueType ? Activator.CreateInstance(type) : default;
} }
@ -74,13 +69,12 @@ namespace Swan
/// <c>true</c> if the specified source type is collection; otherwise, <c>false</c>. /// <c>true</c> if the specified source type is collection; otherwise, <c>false</c>.
/// </returns> /// </returns>
/// <exception cref="ArgumentNullException">sourceType.</exception> /// <exception cref="ArgumentNullException">sourceType.</exception>
public static bool IsCollection(this Type sourceType) public static Boolean IsCollection(this Type sourceType) {
{ if(sourceType == null) {
if (sourceType == null)
throw new ArgumentNullException(nameof(sourceType)); throw new ArgumentNullException(nameof(sourceType));
}
return sourceType != typeof(string) && return sourceType != typeof(String) && typeof(IEnumerable).IsAssignableFrom(sourceType);
typeof(IEnumerable).IsAssignableFrom(sourceType);
} }
/// <summary> /// <summary>
@ -98,34 +92,29 @@ namespace Swan
/// The exception that is thrown when binding to a member results in more than one member matching the /// The exception that is thrown when binding to a member results in more than one member matching the
/// binding criteria. This class cannot be inherited. /// binding criteria. This class cannot be inherited.
/// </exception> /// </exception>
public static MethodInfo GetMethod( public static MethodInfo GetMethod(this Type type, BindingFlags bindingFlags, String methodName, Type[] genericTypes, Type[] parameterTypes) {
this Type type, if(type == null) {
BindingFlags bindingFlags,
string methodName,
Type[] genericTypes,
Type[] parameterTypes)
{
if (type == null)
throw new ArgumentNullException(nameof(type)); throw new ArgumentNullException(nameof(type));
}
if (methodName == null) if(methodName == null) {
throw new ArgumentNullException(nameof(methodName)); throw new ArgumentNullException(nameof(methodName));
}
if (genericTypes == null) if(genericTypes == null) {
throw new ArgumentNullException(nameof(genericTypes)); throw new ArgumentNullException(nameof(genericTypes));
}
if (parameterTypes == null) if(parameterTypes == null) {
throw new ArgumentNullException(nameof(parameterTypes)); throw new ArgumentNullException(nameof(parameterTypes));
}
var methods = type List<MethodInfo> methods = type.GetMethods(bindingFlags)
.GetMethods(bindingFlags) .Where(mi => String.Equals(methodName, mi.Name, StringComparison.Ordinal))
.Where(mi => string.Equals(methodName, mi.Name, StringComparison.Ordinal))
.Where(mi => mi.ContainsGenericParameters) .Where(mi => mi.ContainsGenericParameters)
.Where(mi => mi.GetGenericArguments().Length == genericTypes.Length) .Where(mi => mi.GetGenericArguments().Length == genericTypes.Length)
.Where(mi => mi.GetParameters().Length == parameterTypes.Length) .Where(mi => mi.GetParameters().Length == parameterTypes.Length).Select(mi => mi.MakeGenericMethod(genericTypes))
.Select(mi => mi.MakeGenericMethod(genericTypes)) .Where(mi => mi.GetParameters().Select(pi => pi.ParameterType).SequenceEqual(parameterTypes)).ToList();
.Where(mi => mi.GetParameters().Select(pi => pi.ParameterType).SequenceEqual(parameterTypes))
.ToList();
return methods.Count > 1 ? throw new AmbiguousMatchException() : methods.FirstOrDefault(); return methods.Count > 1 ? throw new AmbiguousMatchException() : methods.FirstOrDefault();
} }
@ -138,10 +127,7 @@ namespace Swan
/// <c>true</c> if [is i enumerable request] [the specified type]; otherwise, <c>false</c>. /// <c>true</c> if [is i enumerable request] [the specified type]; otherwise, <c>false</c>.
/// </returns> /// </returns>
/// <exception cref="ArgumentNullException">type.</exception> /// <exception cref="ArgumentNullException">type.</exception>
public static bool IsIEnumerable(this Type type) public static Boolean IsIEnumerable(this Type type) => type == null ? throw new ArgumentNullException(nameof(type)) : type.IsGenericType && type.GetGenericTypeDefinition() == typeof(IEnumerable<>);
=> type == null
? throw new ArgumentNullException(nameof(type))
: type.IsGenericType && type.GetGenericTypeDefinition() == typeof(IEnumerable<>);
#endregion #endregion
@ -155,13 +141,14 @@ namespace Swan
/// <c>true</c> if parsing was successful; otherwise, <c>false</c>. /// <c>true</c> if parsing was successful; otherwise, <c>false</c>.
/// </returns> /// </returns>
/// <exception cref="ArgumentNullException">type</exception> /// <exception cref="ArgumentNullException">type</exception>
public static bool TryParseBasicType(this Type type, object value, out object? result) public static Boolean TryParseBasicType(this Type type, Object value, out Object? result) {
{ if(type == null) {
if (type == null)
throw new ArgumentNullException(nameof(type)); throw new ArgumentNullException(nameof(type));
}
if (type != typeof(bool)) if(type != typeof(Boolean)) {
return TryParseBasicType(type, value.ToStringInvariant(), out result); return TryParseBasicType(type, value.ToStringInvariant(), out result);
}
result = value.ToBoolean(); result = value.ToBoolean();
return true; return true;
@ -177,10 +164,10 @@ namespace Swan
/// <c>true</c> if parsing was successful; otherwise, <c>false</c>. /// <c>true</c> if parsing was successful; otherwise, <c>false</c>.
/// </returns> /// </returns>
/// <exception cref="ArgumentNullException">type</exception> /// <exception cref="ArgumentNullException">type</exception>
public static bool TryParseBasicType(this Type type, string value, out object? result) public static Boolean TryParseBasicType(this Type type, String value, out Object? result) {
{ if(type == null) {
if (type == null)
throw new ArgumentNullException(nameof(type)); throw new ArgumentNullException(nameof(type));
}
result = null; result = null;
@ -197,21 +184,17 @@ namespace Swan
/// <c>true</c> if parsing was successful; otherwise, <c>false</c>. /// <c>true</c> if parsing was successful; otherwise, <c>false</c>.
/// </returns> /// </returns>
/// <exception cref="ArgumentNullException">propertyInfo.</exception> /// <exception cref="ArgumentNullException">propertyInfo.</exception>
public static bool TrySetBasicType(this PropertyInfo propertyInfo, object value, object target) public static Boolean TrySetBasicType(this PropertyInfo propertyInfo, Object value, Object target) {
{ if(propertyInfo == null) {
if (propertyInfo == null)
throw new ArgumentNullException(nameof(propertyInfo)); throw new ArgumentNullException(nameof(propertyInfo));
}
try try {
{ if(propertyInfo.PropertyType.TryParseBasicType(value, out Object? propertyValue)) {
if (propertyInfo.PropertyType.TryParseBasicType(value, out var propertyValue))
{
propertyInfo.SetValue(target, propertyValue); propertyInfo.SetValue(target, propertyValue);
return true; return true;
} }
} } catch {
catch
{
// swallow // swallow
} }
@ -229,36 +212,31 @@ namespace Swan
/// <c>true</c> if parsing was successful; otherwise, <c>false</c>. /// <c>true</c> if parsing was successful; otherwise, <c>false</c>.
/// </returns> /// </returns>
/// <exception cref="ArgumentNullException">type</exception> /// <exception cref="ArgumentNullException">type</exception>
public static bool TrySetArrayBasicType(this Type type, object value, Array target, int index) public static Boolean TrySetArrayBasicType(this Type type, Object value, Array target, Int32 index) {
{ if(type == null) {
if (type == null)
throw new ArgumentNullException(nameof(type)); throw new ArgumentNullException(nameof(type));
}
if (target == null) if(target == null) {
return false; return false;
}
try try {
{ if(value == null) {
if (value == null)
{
target.SetValue(null, index); target.SetValue(null, index);
return true; return true;
} }
if (type.TryParseBasicType(value, out var propertyValue)) if(type.TryParseBasicType(value, out Object? propertyValue)) {
{
target.SetValue(propertyValue, index); target.SetValue(propertyValue, index);
return true; return true;
} }
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>)) if(type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>)) {
{
target.SetValue(null, index); target.SetValue(null, index);
return true; return true;
} }
} } catch {
catch
{
// swallow // swallow
} }
@ -275,25 +253,27 @@ namespace Swan
/// <c>true</c> if parsing was successful; otherwise, <c>false</c>. /// <c>true</c> if parsing was successful; otherwise, <c>false</c>.
/// </returns> /// </returns>
/// <exception cref="ArgumentNullException">propertyInfo.</exception> /// <exception cref="ArgumentNullException">propertyInfo.</exception>
public static bool TrySetArray(this PropertyInfo propertyInfo, IEnumerable<object>? value, object obj) public static Boolean TrySetArray(this PropertyInfo propertyInfo, IEnumerable<Object>? value, Object obj) {
{ if(propertyInfo == null) {
if (propertyInfo == null)
throw new ArgumentNullException(nameof(propertyInfo)); throw new ArgumentNullException(nameof(propertyInfo));
}
var elementType = propertyInfo.PropertyType.GetElementType(); Type? elementType = propertyInfo.PropertyType.GetElementType();
if (elementType == null || value == null) if(elementType == null || value == null) {
return false; return false;
}
var targetArray = Array.CreateInstance(elementType, value.Count()); Array targetArray = Array.CreateInstance(elementType, value.Count());
var i = 0; Int32 i = 0;
foreach (var sourceElement in value) foreach(Object sourceElement in value) {
{ Boolean result = elementType.TrySetArrayBasicType(sourceElement, targetArray, i++);
var result = elementType.TrySetArrayBasicType(sourceElement, targetArray, i++);
if (!result) return false; if(!result) {
return false;
}
} }
propertyInfo.SetValue(obj, targetArray); propertyInfo.SetValue(obj, targetArray);
@ -313,26 +293,23 @@ namespace Swan
/// <param name="target">The object.</param> /// <param name="target">The object.</param>
/// <returns>The property value or null.</returns> /// <returns>The property value or null.</returns>
/// <exception cref="ArgumentNullException">propertyInfo.</exception> /// <exception cref="ArgumentNullException">propertyInfo.</exception>
public static string? ToFormattedString(this PropertyInfo propertyInfo, object target) public static String? ToFormattedString(this PropertyInfo propertyInfo, Object target) {
{ if(propertyInfo == null) {
if (propertyInfo == null)
throw new ArgumentNullException(nameof(propertyInfo)); throw new ArgumentNullException(nameof(propertyInfo));
try
{
var value = propertyInfo.GetValue(target);
var attr = AttributeCache.DefaultCache.Value.RetrieveOne<PropertyDisplayAttribute>(propertyInfo);
if (attr == null) return value?.ToString() ?? string.Empty;
var valueToFormat = value ?? attr.DefaultValue;
return string.IsNullOrEmpty(attr.Format)
? (valueToFormat?.ToString() ?? string.Empty)
: ConvertObjectAndFormat(propertyInfo.PropertyType, valueToFormat, attr.Format);
} }
catch
{ try {
Object? value = propertyInfo.GetValue(target);
PropertyDisplayAttribute attr = AttributeCache.DefaultCache.Value.RetrieveOne<PropertyDisplayAttribute>(propertyInfo);
if(attr == null) {
return value?.ToString() ?? String.Empty;
}
Object valueToFormat = value ?? attr.DefaultValue;
return String.IsNullOrEmpty(attr.Format) ? (valueToFormat?.ToString() ?? String.Empty) : ConvertObjectAndFormat(propertyInfo.PropertyType, valueToFormat, attr.Format);
} catch {
return null; return null;
} }
} }
@ -345,16 +322,11 @@ namespace Swan
/// <returns> /// <returns>
/// The cached MethodInfo. /// The cached MethodInfo.
/// </returns> /// </returns>
public static Func<object, object>? GetCacheGetMethod(this PropertyInfo propertyInfo, bool nonPublic = false) public static Func<Object, Object>? GetCacheGetMethod(this PropertyInfo propertyInfo, Boolean nonPublic = false) {
{ Tuple<Boolean, PropertyInfo> key = Tuple.Create(!nonPublic, propertyInfo);
var key = Tuple.Create(!nonPublic, propertyInfo);
// TODO: Fix public logic // TODO: Fix public logic
return !nonPublic && !CacheGetMethods.Value.ContainsKey(key) && !propertyInfo.GetGetMethod(true).IsPublic return !nonPublic && !CacheGetMethods.Value.ContainsKey(key) && !propertyInfo.GetGetMethod(true)!.IsPublic ? null : CacheGetMethods.Value.GetOrAdd(key, x => y => x.Item2.GetGetMethod(nonPublic)?.Invoke(y, null)!);
? null
: CacheGetMethods.Value
.GetOrAdd(key,
x => y => x.Item2.GetGetMethod(nonPublic).Invoke(y, null));
//y => x => y.Item2.CreatePropertyProxy().GetValue(x)); //y => x => y.Item2.CreatePropertyProxy().GetValue(x));
} }
@ -366,15 +338,10 @@ namespace Swan
/// <returns> /// <returns>
/// The cached MethodInfo. /// The cached MethodInfo.
/// </returns> /// </returns>
public static Action<object, object[]>? GetCacheSetMethod(this PropertyInfo propertyInfo, bool nonPublic = false) public static Action<Object, Object[]>? GetCacheSetMethod(this PropertyInfo propertyInfo, Boolean nonPublic = false) {
{ Tuple<Boolean, PropertyInfo> key = Tuple.Create(!nonPublic, propertyInfo);
var key = Tuple.Create(!nonPublic, propertyInfo);
return !nonPublic && !CacheSetMethods.Value.ContainsKey(key) && !propertyInfo.GetSetMethod(true).IsPublic return !nonPublic && !CacheSetMethods.Value.ContainsKey(key) && !propertyInfo.GetSetMethod(true)!.IsPublic ? null : CacheSetMethods.Value.GetOrAdd(key, x => (obj, args) => x.Item2.GetSetMethod(nonPublic)!.Invoke(obj, args));
? null
: CacheSetMethods.Value
.GetOrAdd(key,
x => (obj, args) => x.Item2.GetSetMethod(nonPublic).Invoke(obj, args));
//y => (obj, args) => y.Item2.CreatePropertyProxy().SetValue(obj, args)); //y => (obj, args) => y.Item2.CreatePropertyProxy().SetValue(obj, args));
} }
@ -385,23 +352,16 @@ namespace Swan
/// <returns> /// <returns>
/// <c>true</c> if the string represents a valid truly value, otherwise <c>false</c>. /// <c>true</c> if the string represents a valid truly value, otherwise <c>false</c>.
/// </returns> /// </returns>
public static bool ToBoolean(this string str) public static Boolean ToBoolean(this String str) {
{ try {
try
{
return Convert.ToBoolean(str); return Convert.ToBoolean(str);
} } catch(FormatException) {
catch (FormatException)
{
// ignored // ignored
} }
try try {
{
return Convert.ToBoolean(Convert.ToInt32(str)); return Convert.ToBoolean(Convert.ToInt32(str));
} } catch {
catch
{
// ignored // ignored
} }
@ -416,13 +376,12 @@ namespace Swan
/// The property proxy. /// The property proxy.
/// </returns> /// </returns>
/// <exception cref="ArgumentNullException">this.</exception> /// <exception cref="ArgumentNullException">this.</exception>
public static IPropertyProxy? CreatePropertyProxy(this PropertyInfo @this) public static IPropertyProxy? CreatePropertyProxy(this PropertyInfo @this) {
{ if(@this == null) {
if (@this == null)
throw new ArgumentNullException(nameof(@this)); throw new ArgumentNullException(nameof(@this));
}
var genericType = typeof(PropertyProxy<,>) Type genericType = typeof(PropertyProxy<,>).MakeGenericType(@this.DeclaringType!, @this.PropertyType);
.MakeGenericType(@this.DeclaringType, @this.PropertyType);
return Activator.CreateInstance(genericType, @this) as IPropertyProxy; return Activator.CreateInstance(genericType, @this) as IPropertyProxy;
} }
@ -434,22 +393,19 @@ namespace Swan
/// <returns> /// <returns>
/// <c>true</c> if the string represents a valid truly value, otherwise <c>false</c>. /// <c>true</c> if the string represents a valid truly value, otherwise <c>false</c>.
/// </returns> /// </returns>
public static bool ToBoolean(this object value) => value.ToStringInvariant().ToBoolean(); public static Boolean ToBoolean(this Object value) => value.ToStringInvariant().ToBoolean();
private static string ConvertObjectAndFormat(Type propertyType, object value, string format) private static String ConvertObjectAndFormat(Type propertyType, Object value, String format) =>
{ propertyType == typeof(DateTime) || propertyType == typeof(DateTime?)
if (propertyType == typeof(DateTime) || propertyType == typeof(DateTime?)) ? Convert.ToDateTime(value, CultureInfo.InvariantCulture).ToString(format)
return Convert.ToDateTime(value, CultureInfo.InvariantCulture).ToString(format); : propertyType == typeof(Int32) || propertyType == typeof(Int32?)
if (propertyType == typeof(int) || propertyType == typeof(int?)) ? Convert.ToInt32(value, CultureInfo.InvariantCulture).ToString(format)
return Convert.ToInt32(value, CultureInfo.InvariantCulture).ToString(format); : propertyType == typeof(Decimal) || propertyType == typeof(Decimal?)
if (propertyType == typeof(decimal) || propertyType == typeof(decimal?)) ? Convert.ToDecimal(value, CultureInfo.InvariantCulture).ToString(format)
return Convert.ToDecimal(value, CultureInfo.InvariantCulture).ToString(format); : propertyType == typeof(Double) || propertyType == typeof(Double?)
if (propertyType == typeof(double) || propertyType == typeof(double?)) ? Convert.ToDouble(value, CultureInfo.InvariantCulture).ToString(format)
return Convert.ToDouble(value, CultureInfo.InvariantCulture).ToString(format); : propertyType == typeof(Byte) || propertyType == typeof(Byte?)
if (propertyType == typeof(byte) || propertyType == typeof(byte?)) ? Convert.ToByte(value, CultureInfo.InvariantCulture).ToString(format)
return Convert.ToByte(value, CultureInfo.InvariantCulture).ToString(format); : value?.ToString() ?? String.Empty;
return value?.ToString() ?? string.Empty;
}
} }
} }

View File

@ -1,41 +1,35 @@
using System; #nullable enable
using System;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using Swan.Formatters; using Swan.Formatters;
namespace Swan namespace Swan {
{
/// <summary> /// <summary>
/// String related extension methods. /// String related extension methods.
/// </summary> /// </summary>
public static class StringExtensions public static class StringExtensions {
{
#region Private Declarations #region Private Declarations
private const RegexOptions StandardRegexOptions = private const RegexOptions StandardRegexOptions = RegexOptions.Multiline | RegexOptions.Compiled | RegexOptions.CultureInvariant;
RegexOptions.Multiline | RegexOptions.Compiled | RegexOptions.CultureInvariant;
private static readonly string[] ByteSuffixes = { "B", "KB", "MB", "GB", "TB" }; private static readonly String[] ByteSuffixes = { "B", "KB", "MB", "GB", "TB" };
private static readonly Lazy<Regex> SplitLinesRegex = private static readonly Lazy<Regex> SplitLinesRegex = new Lazy<Regex>(() => new Regex("\r\n|\r|\n", StandardRegexOptions));
new Lazy<Regex>(() => new Regex("\r\n|\r|\n", StandardRegexOptions));
private static readonly Lazy<Regex> UnderscoreRegex = private static readonly Lazy<Regex> UnderscoreRegex = new Lazy<Regex>(() => new Regex(@"_", StandardRegexOptions));
new Lazy<Regex>(() => new Regex(@"_", StandardRegexOptions));
private static readonly Lazy<Regex> CamelCaseRegEx = private static readonly Lazy<Regex> CamelCaseRegEx = new Lazy<Regex>(() => new Regex(@"[a-z][A-Z]", StandardRegexOptions));
new Lazy<Regex>(() => new Regex(@"[a-z][A-Z]", StandardRegexOptions));
private static readonly Lazy<MatchEvaluator> SplitCamelCaseString = new Lazy<MatchEvaluator>(() => m => private static readonly Lazy<MatchEvaluator> SplitCamelCaseString = new Lazy<MatchEvaluator>(() => m => {
{ String x = m.ToString();
var x = m.ToString(); return x[0] + " " + x[1..];
return x[0] + " " + x.Substring(1, x.Length - 1);
}); });
private static readonly Lazy<string[]> InvalidFilenameChars = private static readonly Lazy<String[]> InvalidFilenameChars = new Lazy<String[]>(() => Path.GetInvalidFileNameChars().Select(c => c.ToString()).ToArray());
new Lazy<string[]>(() => Path.GetInvalidFileNameChars().Select(c => c.ToString()).ToArray());
#endregion #endregion
@ -45,20 +39,15 @@ namespace Swan
/// overload exists. /// overload exists.
/// </summary> /// </summary>
/// <param name="this">The item.</param> /// <param name="this">The item.</param>
/// <returns>A <see cref="string" /> that represents the current object.</returns> /// <returns>A <see cref="String" /> that represents the current object.</returns>
public static string ToStringInvariant(this object? @this) public static String ToStringInvariant(this Object? @this) {
{ if(@this == null) {
if (@this == null) return String.Empty;
return string.Empty; }
var itemType = @this.GetType(); Type itemType = @this.GetType();
if (itemType == typeof(string)) return itemType == typeof(String) ? @this as String ?? String.Empty : Definitions.BasicTypesInfo.Value.ContainsKey(itemType) ? Definitions.BasicTypesInfo.Value[itemType].ToStringInvariant(@this) : @this.ToString()!;
return @this as string ?? string.Empty;
return Definitions.BasicTypesInfo.Value.ContainsKey(itemType)
? Definitions.BasicTypesInfo.Value[itemType].ToStringInvariant(@this)
: @this.ToString();
} }
/// <summary> /// <summary>
@ -68,9 +57,8 @@ namespace Swan
/// </summary> /// </summary>
/// <typeparam name="T">The type to get the string.</typeparam> /// <typeparam name="T">The type to get the string.</typeparam>
/// <param name="item">The item.</param> /// <param name="item">The item.</param>
/// <returns>A <see cref="string" /> that represents the current object.</returns> /// <returns>A <see cref="String" /> that represents the current object.</returns>
public static string ToStringInvariant<T>(this T item) public static String ToStringInvariant<T>(this T item) => typeof(String) == typeof(T) ? item as String ?? String.Empty : ToStringInvariant(item as Object);
=> typeof(string) == typeof(T) ? item as string ?? string.Empty : ToStringInvariant(item as object);
/// <summary> /// <summary>
/// Removes the control characters from a string except for those specified. /// Removes the control characters from a string except for those specified.
@ -81,35 +69,33 @@ namespace Swan
/// A string that represents the current object. /// A string that represents the current object.
/// </returns> /// </returns>
/// <exception cref="ArgumentNullException">input.</exception> /// <exception cref="ArgumentNullException">input.</exception>
public static string RemoveControlCharsExcept(this string value, params char[] excludeChars) public static String RemoveControlCharsExcept(this String value, params Char[]? excludeChars) {
{ if(value == null) {
if (value == null)
throw new ArgumentNullException(nameof(value)); throw new ArgumentNullException(nameof(value));
}
if (excludeChars == null) if(excludeChars == null) {
excludeChars = Array.Empty<char>(); excludeChars = Array.Empty<Char>();
}
return new string(value return new String(value.Where(c => Char.IsControl(c) == false || excludeChars.Contains(c)).ToArray());
.Where(c => char.IsControl(c) == false || excludeChars.Contains(c))
.ToArray());
} }
/// <summary> /// <summary>
/// Removes all control characters from a string, including new line sequences. /// Removes all control characters from a string, including new line sequences.
/// </summary> /// </summary>
/// <param name="value">The input.</param> /// <param name="value">The input.</param>
/// <returns>A <see cref="string" /> that represents the current object.</returns> /// <returns>A <see cref="String" /> that represents the current object.</returns>
/// <exception cref="ArgumentNullException">input.</exception> /// <exception cref="ArgumentNullException">input.</exception>
public static string RemoveControlChars(this string value) => value.RemoveControlCharsExcept(null); public static String RemoveControlChars(this String value) => value.RemoveControlCharsExcept(null);
/// <summary> /// <summary>
/// Outputs JSON string representing this object. /// Outputs JSON string representing this object.
/// </summary> /// </summary>
/// <param name="this">The object.</param> /// <param name="this">The object.</param>
/// <param name="format">if set to <c>true</c> format the output.</param> /// <param name="format">if set to <c>true</c> format the output.</param>
/// <returns>A <see cref="string" /> that represents the current object.</returns> /// <returns>A <see cref="String" /> that represents the current object.</returns>
public static string ToJson(this object @this, bool format = true) => public static String ToJson(this Object @this, Boolean format = true) => @this == null ? String.Empty : Json.Serialize(@this, format);
@this == null ? string.Empty : Json.Serialize(@this, format);
/// <summary> /// <summary>
/// Returns text representing the properties of the specified object in a human-readable format. /// Returns text representing the properties of the specified object in a human-readable format.
@ -117,21 +103,18 @@ namespace Swan
/// examine objects. /// examine objects.
/// </summary> /// </summary>
/// <param name="this">The object.</param> /// <param name="this">The object.</param>
/// <returns>A <see cref="string" /> that represents the current object.</returns> /// <returns>A <see cref="String" /> that represents the current object.</returns>
public static string Stringify(this object @this) public static String Stringify(this Object @this) {
{ if(@this == null) {
if (@this == null)
return "(null)"; return "(null)";
}
try try {
{ String jsonText = Json.Serialize(@this, false, "$type");
var jsonText = Json.Serialize(@this, false, "$type"); Object? jsonData = Json.Deserialize(jsonText);
var jsonData = Json.Deserialize(jsonText);
return new HumanizeJson(jsonData, 0).GetResult(); return new HumanizeJson(jsonData, 0).GetResult();
} } catch {
catch
{
return @this.ToStringInvariant(); return @this.ToStringInvariant();
} }
} }
@ -145,14 +128,14 @@ namespace Swan
/// <param name="startIndex">The start index.</param> /// <param name="startIndex">The start index.</param>
/// <param name="endIndex">The end index.</param> /// <param name="endIndex">The end index.</param>
/// <returns>Retrieves a substring from this instance.</returns> /// <returns>Retrieves a substring from this instance.</returns>
public static string Slice(this string @this, int startIndex, int endIndex) public static String Slice(this String @this, Int32 startIndex, Int32 endIndex) {
{ if(@this == null) {
if (@this == null) return String.Empty;
return string.Empty; }
var end = endIndex.Clamp(startIndex, @this.Length - 1); Int32 end = endIndex.Clamp(startIndex, @this.Length - 1);
return startIndex >= end ? string.Empty : @this.Substring(startIndex, (end - startIndex) + 1); return startIndex >= end ? String.Empty : @this.Substring(startIndex, end - startIndex + 1);
} }
/// <summary> /// <summary>
@ -164,15 +147,15 @@ namespace Swan
/// <param name="startIndex">The start index.</param> /// <param name="startIndex">The start index.</param>
/// <param name="length">The length.</param> /// <param name="length">The length.</param>
/// <returns>Retrieves a substring from this instance.</returns> /// <returns>Retrieves a substring from this instance.</returns>
public static string SliceLength(this string @this, int startIndex, int length) public static String SliceLength(this String @this, Int32 startIndex, Int32 length) {
{ if(@this == null) {
if (@this == null) return String.Empty;
return string.Empty; }
var start = startIndex.Clamp(0, @this.Length - 1); Int32 start = startIndex.Clamp(0, @this.Length - 1);
var len = length.Clamp(0, @this.Length - start); Int32 len = length.Clamp(0, @this.Length - start);
return len == 0 ? string.Empty : @this.Substring(start, len); return len == 0 ? String.Empty : @this.Substring(start, len);
} }
/// <summary> /// <summary>
@ -183,8 +166,7 @@ namespace Swan
/// An array whose elements contain the substrings from this instance /// An array whose elements contain the substrings from this instance
/// that are delimited by one or more characters in separator. /// that are delimited by one or more characters in separator.
/// </returns> /// </returns>
public static string[] ToLines(this string @this) => public static String[] ToLines(this String @this) => @this == null ? Array.Empty<String>() : SplitLinesRegex.Value.Split(@this);
@this == null ? Array.Empty<string>() : SplitLinesRegex.Value.Split(@this);
/// <summary> /// <summary>
/// Humanizes (make more human-readable) an identifier-style string /// Humanizes (make more human-readable) an identifier-style string
@ -192,13 +174,13 @@ namespace Swan
/// Camel Case and Snake_Case will be converted to Snake Case. /// Camel Case and Snake_Case will be converted to Snake Case.
/// </summary> /// </summary>
/// <param name="value">The identifier-style string.</param> /// <param name="value">The identifier-style string.</param>
/// <returns>A <see cref="string" /> humanized.</returns> /// <returns>A <see cref="String" /> humanized.</returns>
public static string Humanize(this string value) public static String Humanize(this String value) {
{ if(value == null) {
if (value == null) return String.Empty;
return string.Empty; }
var returnValue = UnderscoreRegex.Value.Replace(value, " "); String returnValue = UnderscoreRegex.Value.Replace(value, " ");
returnValue = CamelCaseRegEx.Value.Replace(returnValue, SplitCamelCaseString.Value); returnValue = CamelCaseRegEx.Value.Replace(returnValue, SplitCamelCaseString.Value);
return returnValue; return returnValue;
} }
@ -207,19 +189,19 @@ namespace Swan
/// Humanizes (make more human-readable) an boolean. /// Humanizes (make more human-readable) an boolean.
/// </summary> /// </summary>
/// <param name="value">if set to <c>true</c> [value].</param> /// <param name="value">if set to <c>true</c> [value].</param>
/// <returns>A <see cref="string" /> that represents the current boolean.</returns> /// <returns>A <see cref="String" /> that represents the current boolean.</returns>
public static string Humanize(this bool value) => value ? "Yes" : "No"; public static String Humanize(this Boolean value) => value ? "Yes" : "No";
/// <summary> /// <summary>
/// Humanizes (make more human-readable) the specified value. /// Humanizes (make more human-readable) the specified value.
/// </summary> /// </summary>
/// <param name="value">The value.</param> /// <param name="value">The value.</param>
/// <returns>A <see cref="string" /> that represents the current object.</returns> /// <returns>A <see cref="String" /> that represents the current object.</returns>
public static string Humanize(this object value) => public static String Humanize(this Object value) =>
value switch value switch
{ {
string stringValue => stringValue.Humanize(), String stringValue => stringValue.Humanize(),
bool boolValue => boolValue.Humanize(), Boolean boolValue => boolValue.Humanize(),
_ => value.Stringify() _ => value.Stringify()
}; };
@ -229,19 +211,22 @@ namespace Swan
/// </summary> /// </summary>
/// <param name="value">The text.</param> /// <param name="value">The text.</param>
/// <param name="spaces">The spaces.</param> /// <param name="spaces">The spaces.</param>
/// <returns>A <see cref="string" /> that represents the current object.</returns> /// <returns>A <see cref="String" /> that represents the current object.</returns>
public static string Indent(this string value, int spaces = 4) public static String Indent(this String value, Int32 spaces = 4) {
{ if(value == null) {
if (value == null) value = string.Empty; value = String.Empty;
if (spaces <= 0) return value; }
var lines = value.ToLines(); if(spaces <= 0) {
var builder = new StringBuilder(); return value;
var indentStr = new string(' ', spaces); }
foreach (var line in lines) String[] lines = value.ToLines();
{ StringBuilder builder = new StringBuilder();
builder.AppendLine($"{indentStr}{line}"); String indentStr = new String(' ', spaces);
foreach(String line in lines) {
_ = builder.AppendLine($"{indentStr}{line}");
} }
return builder.ToString().TrimEnd(); return builder.ToString().TrimEnd();
@ -257,28 +242,27 @@ namespace Swan
/// <param name="value">The string.</param> /// <param name="value">The string.</param>
/// <param name="charIndex">Index of the character.</param> /// <param name="charIndex">Index of the character.</param>
/// <returns>A 2-tuple whose value is (item1, item2).</returns> /// <returns>A 2-tuple whose value is (item1, item2).</returns>
public static Tuple<int, int> TextPositionAt(this string value, int charIndex) public static Tuple<Int32, Int32> TextPositionAt(this String value, Int32 charIndex) {
{ if(value == null) {
if (value == null)
return Tuple.Create(0, 0); return Tuple.Create(0, 0);
}
var index = charIndex.Clamp(0, value.Length - 1); Int32 index = charIndex.Clamp(0, value.Length - 1);
var lineIndex = 0; Int32 lineIndex = 0;
var colNumber = 0; Int32 colNumber = 0;
for (var i = 0; i <= index; i++) for(Int32 i = 0; i <= index; i++) {
{ if(value[i] == '\n') {
if (value[i] == '\n')
{
lineIndex++; lineIndex++;
colNumber = 0; colNumber = 0;
continue; continue;
} }
if (value[i] != '\r') if(value[i] != '\r') {
colNumber++; colNumber++;
} }
}
return Tuple.Create(lineIndex + 1, colNumber); return Tuple.Create(lineIndex + 1, colNumber);
} }
@ -291,12 +275,7 @@ namespace Swan
/// A string with a safe file name. /// A string with a safe file name.
/// </returns> /// </returns>
/// <exception cref="ArgumentNullException">s.</exception> /// <exception cref="ArgumentNullException">s.</exception>
public static string ToSafeFilename(this string value) => public static String ToSafeFilename(this String value) => value == null ? throw new ArgumentNullException(nameof(value)) : InvalidFilenameChars.Value.Aggregate(value, (current, c) => current.Replace(c, String.Empty)).Slice(0, 220);
value == null
? throw new ArgumentNullException(nameof(value))
: InvalidFilenameChars.Value
.Aggregate(value, (current, c) => current.Replace(c, string.Empty))
.Slice(0, 220);
/// <summary> /// <summary>
/// Formats a long into the closest bytes string. /// Formats a long into the closest bytes string.
@ -305,7 +284,7 @@ namespace Swan
/// <returns> /// <returns>
/// The string representation of the current Byte object, formatted as specified by the format parameter. /// The string representation of the current Byte object, formatted as specified by the format parameter.
/// </returns> /// </returns>
public static string FormatBytes(this long bytes) => ((ulong)bytes).FormatBytes(); public static String FormatBytes(this Int64 bytes) => ((UInt64)bytes).FormatBytes();
/// <summary> /// <summary>
/// Formats a long into the closest bytes string. /// Formats a long into the closest bytes string.
@ -315,13 +294,11 @@ namespace Swan
/// A copy of format in which the format items have been replaced by the string /// A copy of format in which the format items have been replaced by the string
/// representations of the corresponding arguments. /// representations of the corresponding arguments.
/// </returns> /// </returns>
public static string FormatBytes(this ulong bytes) public static String FormatBytes(this UInt64 bytes) {
{ Int32 i;
int i; Double dblSByte = bytes;
double dblSByte = bytes;
for (i = 0; i < ByteSuffixes.Length && bytes >= 1024; i++, bytes /= 1024) for(i = 0; i < ByteSuffixes.Length && bytes >= 1024; i++, bytes /= 1024) {
{
dblSByte = bytes / 1024.0; dblSByte = bytes / 1024.0;
} }
@ -337,8 +314,7 @@ namespace Swan
/// Retrieves a substring from this instance. /// Retrieves a substring from this instance.
/// The substring starts at a specified character position and has a specified length. /// The substring starts at a specified character position and has a specified length.
/// </returns> /// </returns>
public static string? Truncate(this string value, int maximumLength) => public static String? Truncate(this String value, Int32 maximumLength) => Truncate(value, maximumLength, String.Empty);
Truncate(value, maximumLength, string.Empty);
/// <summary> /// <summary>
/// Truncates the specified value and append the omission last. /// Truncates the specified value and append the omission last.
@ -350,32 +326,23 @@ namespace Swan
/// Retrieves a substring from this instance. /// Retrieves a substring from this instance.
/// The substring starts at a specified character position and has a specified length. /// The substring starts at a specified character position and has a specified length.
/// </returns> /// </returns>
public static string? Truncate(this string value, int maximumLength, string omission) public static String? Truncate(this String value, Int32 maximumLength, String omission) => value == null ? null : value.Length > maximumLength ? value.Substring(0, maximumLength) + (omission ?? String.Empty) : value;
{
if (value == null)
return null;
return value.Length > maximumLength
? value.Substring(0, maximumLength) + (omission ?? string.Empty)
: value;
}
/// <summary> /// <summary>
/// Determines whether the specified <see cref="string"/> contains any of characters in /// Determines whether the specified <see cref="String"/> contains any of characters in
/// the specified array of <see cref="char"/>. /// the specified array of <see cref="Char"/>.
/// </summary> /// </summary>
/// <returns> /// <returns>
/// <c>true</c> if <paramref name="value"/> contains any of <paramref name="chars"/>; /// <c>true</c> if <paramref name="value"/> contains any of <paramref name="chars"/>;
/// otherwise, <c>false</c>. /// otherwise, <c>false</c>.
/// </returns> /// </returns>
/// <param name="value"> /// <param name="value">
/// A <see cref="string"/> to test. /// A <see cref="String"/> to test.
/// </param> /// </param>
/// <param name="chars"> /// <param name="chars">
/// An array of <see cref="char"/> that contains characters to find. /// An array of <see cref="Char"/> that contains characters to find.
/// </param> /// </param>
public static bool Contains(this string value, params char[] chars) => public static Boolean Contains(this String value, params Char[] chars) => chars?.Length == 0 || !String.IsNullOrEmpty(value) && chars != null && value.IndexOfAny(chars) > -1;
chars?.Length == 0 || (!string.IsNullOrEmpty(value) && value.IndexOfAny(chars) > -1);
/// <summary> /// <summary>
/// Replaces all chars in a string. /// Replaces all chars in a string.
@ -384,8 +351,7 @@ namespace Swan
/// <param name="replaceValue">The replace value.</param> /// <param name="replaceValue">The replace value.</param>
/// <param name="chars">The chars.</param> /// <param name="chars">The chars.</param>
/// <returns>The string with the characters replaced.</returns> /// <returns>The string with the characters replaced.</returns>
public static string ReplaceAll(this string value, string replaceValue, params char[] chars) => public static String ReplaceAll(this String value, String replaceValue, params Char[] chars) => chars.Aggregate(value, (current, c) => current.Replace(new String(new[] { c }), replaceValue));
chars.Aggregate(value, (current, c) => current.Replace(new string(new[] { c }), replaceValue));
/// <summary> /// <summary>
/// Convert hex character to an integer. Return -1 if char is something /// Convert hex character to an integer. Return -1 if char is something
@ -393,13 +359,6 @@ namespace Swan
/// </summary> /// </summary>
/// <param name="value">The c.</param> /// <param name="value">The c.</param>
/// <returns>Converted integer.</returns> /// <returns>Converted integer.</returns>
public static int Hex2Int(this char value) => public static Int32 Hex2Int(this Char value) => value >= '0' && value <= '9' ? value - '0' : value >= 'A' && value <= 'F' ? value - 'A' + 10 : value >= 'a' && value <= 'f' ? value - 'a' + 10 : -1;
value >= '0' && value <= '9'
? value - '0'
: value >= 'A' && value <= 'F'
? value - 'A' + 10
: value >= 'a' && value <= 'f'
? value - 'a' + 10
: -1;
} }
} }

View File

@ -1,13 +1,11 @@
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Swan namespace Swan {
{
/// <summary> /// <summary>
/// Provides extension methods for <see cref="Task"/> and <see cref="Task{TResult}"/>. /// Provides extension methods for <see cref="Task"/> and <see cref="Task{TResult}"/>.
/// </summary> /// </summary>
public static class TaskExtensions public static class TaskExtensions {
{
/// <summary> /// <summary>
/// <para>Suspends execution until the specified <see cref="Task"/> is completed.</para> /// <para>Suspends execution until the specified <see cref="Task"/> is completed.</para>
/// <para>This method operates similarly to the <see langword="await"/> C# operator, /// <para>This method operates similarly to the <see langword="await"/> C# operator,
@ -40,8 +38,7 @@ namespace Swan
/// This parameter has the same effect as calling the <see cref="Task.ConfigureAwait"/> /// This parameter has the same effect as calling the <see cref="Task.ConfigureAwait"/>
/// method.</param> /// method.</param>
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception> /// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
public static void Await(this Task @this, bool continueOnCapturedContext) public static void Await(this Task @this, Boolean continueOnCapturedContext) => @this.ConfigureAwait(continueOnCapturedContext).GetAwaiter().GetResult();
=> @this.ConfigureAwait(continueOnCapturedContext).GetAwaiter().GetResult();
/// <summary> /// <summary>
/// <para>Suspends execution until the specified <see cref="Task"/> is completed /// <para>Suspends execution until the specified <see cref="Task"/> is completed
@ -57,7 +54,6 @@ namespace Swan
/// method.</param> /// method.</param>
/// <returns>The result of <paramref name="this"/>.</returns> /// <returns>The result of <paramref name="this"/>.</returns>
/// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception> /// <exception cref="NullReferenceException"><paramref name="this"/> is <see langword="null"/>.</exception>
public static TResult Await<TResult>(this Task<TResult> @this, bool continueOnCapturedContext) public static TResult Await<TResult>(this Task<TResult> @this, Boolean continueOnCapturedContext) => @this.ConfigureAwait(continueOnCapturedContext).GetAwaiter().GetResult();
=> @this.ConfigureAwait(continueOnCapturedContext).GetAwaiter().GetResult();
} }
} }

View File

@ -1,15 +1,14 @@
using System; using System;
using System.Reflection; using System.Reflection;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using Swan.Reflection; using Swan.Reflection;
namespace Swan namespace Swan {
{
/// <summary> /// <summary>
/// Provides various extension methods for value types and structs. /// Provides various extension methods for value types and structs.
/// </summary> /// </summary>
public static class ValueTypeExtensions public static class ValueTypeExtensions {
{
/// <summary> /// <summary>
/// Clamps the specified value between the minimum and the maximum. /// Clamps the specified value between the minimum and the maximum.
/// </summary> /// </summary>
@ -18,13 +17,7 @@ namespace Swan
/// <param name="min">The minimum.</param> /// <param name="min">The minimum.</param>
/// <param name="max">The maximum.</param> /// <param name="max">The maximum.</param>
/// <returns>A value that indicates the relative order of the objects being compared.</returns> /// <returns>A value that indicates the relative order of the objects being compared.</returns>
public static T Clamp<T>(this T @this, T min, T max) public static T Clamp<T>(this T @this, T min, T max) where T : struct, IComparable => @this.CompareTo(min) < 0 ? min : @this.CompareTo(max) > 0 ? max : @this;
where T : struct, IComparable
{
if (@this.CompareTo(min) < 0) return min;
return @this.CompareTo(max) > 0 ? max : @this;
}
/// <summary> /// <summary>
/// Clamps the specified value between the minimum and the maximum. /// Clamps the specified value between the minimum and the maximum.
@ -33,8 +26,7 @@ namespace Swan
/// <param name="min">The minimum.</param> /// <param name="min">The minimum.</param>
/// <param name="max">The maximum.</param> /// <param name="max">The maximum.</param>
/// <returns>A value that indicates the relative order of the objects being compared.</returns> /// <returns>A value that indicates the relative order of the objects being compared.</returns>
public static int Clamp(this int @this, int min, int max) public static Int32 Clamp(this Int32 @this, Int32 min, Int32 max) => @this < min ? min : (@this > max ? max : @this);
=> @this < min ? min : (@this > max ? max : @this);
/// <summary> /// <summary>
/// Determines whether the specified value is between a minimum and a maximum value. /// Determines whether the specified value is between a minimum and a maximum value.
@ -46,11 +38,7 @@ namespace Swan
/// <returns> /// <returns>
/// <c>true</c> if the specified minimum is between; otherwise, <c>false</c>. /// <c>true</c> if the specified minimum is between; otherwise, <c>false</c>.
/// </returns> /// </returns>
public static bool IsBetween<T>(this T @this, T min, T max) public static Boolean IsBetween<T>(this T @this, T min, T max) where T : struct, IComparable => @this.CompareTo(min) >= 0 && @this.CompareTo(max) <= 0;
where T : struct, IComparable
{
return @this.CompareTo(min) >= 0 && @this.CompareTo(max) <= 0;
}
/// <summary> /// <summary>
/// Converts an array of bytes into the given struct type. /// Converts an array of bytes into the given struct type.
@ -58,11 +46,7 @@ namespace Swan
/// <typeparam name="T">The type of structure to convert.</typeparam> /// <typeparam name="T">The type of structure to convert.</typeparam>
/// <param name="this">The data.</param> /// <param name="this">The data.</param>
/// <returns>a struct type derived from convert an array of bytes ref=ToStruct".</returns> /// <returns>a struct type derived from convert an array of bytes ref=ToStruct".</returns>
public static T ToStruct<T>(this byte[] @this) public static T ToStruct<T>(this Byte[] @this) where T : struct => @this == null ? throw new ArgumentNullException(nameof(@this)) : ToStruct<T>(@this, 0, @this.Length);
where T : struct
{
return @this == null ? throw new ArgumentNullException(nameof(@this)) : ToStruct<T>(@this, 0, @this.Length);
}
/// <summary> /// <summary>
/// Converts an array of bytes into the given struct type. /// Converts an array of bytes into the given struct type.
@ -75,22 +59,18 @@ namespace Swan
/// A managed object containing the data pointed to by the ptr parameter. /// A managed object containing the data pointed to by the ptr parameter.
/// </returns> /// </returns>
/// <exception cref="ArgumentNullException">data.</exception> /// <exception cref="ArgumentNullException">data.</exception>
public static T ToStruct<T>(this byte[] @this, int offset, int length) public static T ToStruct<T>(this Byte[] @this, Int32 offset, Int32 length) where T : struct {
where T : struct if(@this == null) {
{
if (@this == null)
throw new ArgumentNullException(nameof(@this)); throw new ArgumentNullException(nameof(@this));
var buffer = new byte[length];
Array.Copy(@this, offset, buffer, 0, buffer.Length);
var handle = GCHandle.Alloc(GetStructBytes<T>(buffer), GCHandleType.Pinned);
try
{
return Marshal.PtrToStructure<T>(handle.AddrOfPinnedObject());
} }
finally
{ Byte[] buffer = new Byte[length];
Array.Copy(@this, offset, buffer, 0, buffer.Length);
GCHandle handle = GCHandle.Alloc(GetStructBytes<T>(buffer), GCHandleType.Pinned);
try {
return Marshal.PtrToStructure<T>(handle.AddrOfPinnedObject());
} finally {
handle.Free(); handle.Free();
} }
} }
@ -101,19 +81,14 @@ namespace Swan
/// <typeparam name="T">The type of structure to convert.</typeparam> /// <typeparam name="T">The type of structure to convert.</typeparam>
/// <param name="this">The object.</param> /// <param name="this">The object.</param>
/// <returns>A byte array containing the results of encoding the specified set of characters.</returns> /// <returns>A byte array containing the results of encoding the specified set of characters.</returns>
public static byte[] ToBytes<T>(this T @this) public static Byte[] ToBytes<T>(this T @this) where T : struct {
where T : struct Byte[] data = new Byte[Marshal.SizeOf(@this)];
{ GCHandle handle = GCHandle.Alloc(data, GCHandleType.Pinned);
var data = new byte[Marshal.SizeOf(@this)];
var handle = GCHandle.Alloc(data, GCHandleType.Pinned);
try try {
{
Marshal.StructureToPtr(@this, handle.AddrOfPinnedObject(), false); Marshal.StructureToPtr(@this, handle.AddrOfPinnedObject(), false);
return GetStructBytes<T>(data); return GetStructBytes<T>(data);
} } finally {
finally
{
handle.Free(); handle.Free();
} }
} }
@ -126,35 +101,30 @@ namespace Swan
/// A 32-bit unsigned integer equivalent to the ulong /// A 32-bit unsigned integer equivalent to the ulong
/// contained in longBytes. /// contained in longBytes.
/// </returns> /// </returns>
public static uint SwapEndianness(this ulong @this) public static UInt32 SwapEndianness(this UInt64 @this) => (UInt32)(((@this & 0x000000ff) << 24) + ((@this & 0x0000ff00) << 8) + ((@this & 0x00ff0000) >> 8) + ((@this & 0xff000000) >> 24));
=> (uint)(((@this & 0x000000ff) << 24) +
((@this & 0x0000ff00) << 8) +
((@this & 0x00ff0000) >> 8) +
((@this & 0xff000000) >> 24));
private static byte[] GetStructBytes<T>(byte[] data) private static Byte[] GetStructBytes<T>(Byte[] data) {
{ if(data == null) {
if (data == null)
throw new ArgumentNullException(nameof(data)); throw new ArgumentNullException(nameof(data));
}
var fields = typeof(T).GetTypeInfo() FieldInfo[] fields = typeof(T).GetTypeInfo()
.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); .GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
var endian = AttributeCache.DefaultCache.Value.RetrieveOne<StructEndiannessAttribute, T>(); StructEndiannessAttribute endian = AttributeCache.DefaultCache.Value.RetrieveOne<StructEndiannessAttribute, T>();
foreach (var field in fields) foreach(FieldInfo field in fields) {
{ if(endian == null && !field.IsDefined(typeof(StructEndiannessAttribute), false)) {
if (endian == null && !field.IsDefined(typeof(StructEndiannessAttribute), false))
continue; continue;
}
var offset = Marshal.OffsetOf<T>(field.Name).ToInt32(); Int32 offset = Marshal.OffsetOf<T>(field.Name).ToInt32();
var length = Marshal.SizeOf(field.FieldType); Int32 length = Marshal.SizeOf(field.FieldType);
endian = endian ?? AttributeCache.DefaultCache.Value.RetrieveOne<StructEndiannessAttribute>(field); endian ??= AttributeCache.DefaultCache.Value.RetrieveOne<StructEndiannessAttribute>(field);
if(endian != null && (endian.Endianness == Endianness.Big && BitConverter.IsLittleEndian || if(endian != null && (endian.Endianness == Endianness.Big && BitConverter.IsLittleEndian ||
endian.Endianness == Endianness.Little && !BitConverter.IsLittleEndian)) endian.Endianness == Endianness.Little && !BitConverter.IsLittleEndian)) {
{
Array.Reverse(data, offset, length); Array.Reverse(data, offset, length);
} }
} }

View File

@ -1,19 +1,19 @@
using System; #nullable enable
using System;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Swan.Lite.Reflection; using Swan.Lite.Reflection;
using Swan.Mappers; using Swan.Mappers;
using Swan.Reflection; using Swan.Reflection;
namespace Swan namespace Swan {
{
/// <summary> /// <summary>
/// Extension methods. /// Extension methods.
/// </summary> /// </summary>
public static partial class Extensions public static partial class Extensions {
{
/// <summary> /// <summary>
/// Iterates over the public, instance, readable properties of the source and /// Iterates over the public, instance, readable properties of the source and
/// tries to write a compatible value to a public, instance, writable property in the destination. /// tries to write a compatible value to a public, instance, writable property in the destination.
@ -25,9 +25,7 @@ namespace Swan
/// <returns> /// <returns>
/// Number of properties that was copied successful. /// Number of properties that was copied successful.
/// </returns> /// </returns>
public static int CopyPropertiesTo<T>(this T source, object target, params string[]? ignoreProperties) public static Int32 CopyPropertiesTo<T>(this T source, Object? target, params String[]? ignoreProperties) where T : class => ObjectMapper.Copy(source, target, GetCopyableProperties(target), ignoreProperties);
where T : class =>
ObjectMapper.Copy(source, target, GetCopyableProperties(target), ignoreProperties);
/// <summary> /// <summary>
/// Iterates over the public, instance, readable properties of the source and /// Iterates over the public, instance, readable properties of the source and
@ -39,8 +37,7 @@ namespace Swan
/// <returns> /// <returns>
/// Number of properties that were successfully copied. /// Number of properties that were successfully copied.
/// </returns> /// </returns>
public static int CopyOnlyPropertiesTo(this object source, object target, params string[]? propertiesToCopy) public static Int32 CopyOnlyPropertiesTo(this Object source, Object target, params String[]? propertiesToCopy) => ObjectMapper.Copy(source, target, propertiesToCopy);
=> ObjectMapper.Copy(source, target, propertiesToCopy);
/// <summary> /// <summary>
/// Copies the properties to new instance of T. /// Copies the properties to new instance of T.
@ -52,14 +49,13 @@ namespace Swan
/// The specified type with properties copied. /// The specified type with properties copied.
/// </returns> /// </returns>
/// <exception cref="ArgumentNullException">source.</exception> /// <exception cref="ArgumentNullException">source.</exception>
public static T CopyPropertiesToNew<T>(this object source, string[]? ignoreProperties = null) public static T CopyPropertiesToNew<T>(this Object source, String[]? ignoreProperties = null) where T : class {
where T : class if(source == null) {
{
if (source == null)
throw new ArgumentNullException(nameof(source)); throw new ArgumentNullException(nameof(source));
}
var target = Activator.CreateInstance<T>(); T target = Activator.CreateInstance<T>();
ObjectMapper.Copy(source, target, GetCopyableProperties(target), ignoreProperties); _ = ObjectMapper.Copy(source, target, GetCopyableProperties(target), ignoreProperties);
return target; return target;
} }
@ -74,14 +70,13 @@ namespace Swan
/// The specified type with properties copied. /// The specified type with properties copied.
/// </returns> /// </returns>
/// <exception cref="ArgumentNullException">source.</exception> /// <exception cref="ArgumentNullException">source.</exception>
public static T CopyOnlyPropertiesToNew<T>(this object source, params string[] propertiesToCopy) public static T CopyOnlyPropertiesToNew<T>(this Object source, params String[] propertiesToCopy) where T : class {
where T : class if(source == null) {
{
if (source == null)
throw new ArgumentNullException(nameof(source)); throw new ArgumentNullException(nameof(source));
}
var target = Activator.CreateInstance<T>(); T target = Activator.CreateInstance<T>();
ObjectMapper.Copy(source, target, propertiesToCopy); _ = ObjectMapper.Copy(source, target, propertiesToCopy);
return target; return target;
} }
@ -94,13 +89,7 @@ namespace Swan
/// <param name="target">The target.</param> /// <param name="target">The target.</param>
/// <param name="ignoreKeys">The ignore keys.</param> /// <param name="ignoreKeys">The ignore keys.</param>
/// <returns>Number of properties that was copied successful.</returns> /// <returns>Number of properties that was copied successful.</returns>
public static int CopyKeyValuePairTo( public static Int32 CopyKeyValuePairTo(this IDictionary<String, Object> source, Object? target, params String[] ignoreKeys) => source == null ? throw new ArgumentNullException(nameof(source)) : ObjectMapper.Copy(source, target, null, ignoreKeys);
this IDictionary<string, object> source,
object target,
params string[] ignoreKeys) =>
source == null
? throw new ArgumentNullException(nameof(source))
: ObjectMapper.Copy(source, target, null, ignoreKeys);
/// <summary> /// <summary>
/// Iterates over the keys of the source and tries to write a compatible value to a public, /// Iterates over the keys of the source and tries to write a compatible value to a public,
@ -112,15 +101,13 @@ namespace Swan
/// <returns> /// <returns>
/// The specified type with properties copied. /// The specified type with properties copied.
/// </returns> /// </returns>
public static T CopyKeyValuePairToNew<T>( public static T CopyKeyValuePairToNew<T>(this IDictionary<String, Object> source, params String[] ignoreKeys) {
this IDictionary<string, object> source, if(source == null) {
params string[] ignoreKeys)
{
if (source == null)
throw new ArgumentNullException(nameof(source)); throw new ArgumentNullException(nameof(source));
}
var target = Activator.CreateInstance<T>(); T target = Activator.CreateInstance<T>();
source.CopyKeyValuePairTo(target, ignoreKeys); _ = source.CopyKeyValuePairTo(target, ignoreKeys);
return target; return target;
} }
@ -130,21 +117,12 @@ namespace Swan
/// <param name="action">The action.</param> /// <param name="action">The action.</param>
/// <param name="retryInterval">The retry interval.</param> /// <param name="retryInterval">The retry interval.</param>
/// <param name="retryCount">The retry count.</param> /// <param name="retryCount">The retry count.</param>
public static void Retry( public static void Retry(this Action action, TimeSpan retryInterval = default, Int32 retryCount = 3) {
this Action action, if(action == null) {
TimeSpan retryInterval = default,
int retryCount = 3)
{
if (action == null)
throw new ArgumentNullException(nameof(action)); throw new ArgumentNullException(nameof(action));
}
Retry<object?>(() => _ = Retry<Object?>(() => { action(); return null; }, retryInterval, retryCount);
{
action();
return null;
},
retryInterval,
retryCount);
} }
/// <summary> /// <summary>
@ -159,30 +137,25 @@ namespace Swan
/// </returns> /// </returns>
/// <exception cref="ArgumentNullException">action.</exception> /// <exception cref="ArgumentNullException">action.</exception>
/// <exception cref="AggregateException">Represents one or many errors that occur during application execution.</exception> /// <exception cref="AggregateException">Represents one or many errors that occur during application execution.</exception>
public static T Retry<T>( public static T Retry<T>(this Func<T> action, TimeSpan retryInterval = default, Int32 retryCount = 3) {
this Func<T> action, if(action == null) {
TimeSpan retryInterval = default,
int retryCount = 3)
{
if (action == null)
throw new ArgumentNullException(nameof(action)); throw new ArgumentNullException(nameof(action));
}
if (retryInterval == default) if(retryInterval == default) {
retryInterval = TimeSpan.FromSeconds(1); retryInterval = TimeSpan.FromSeconds(1);
}
var exceptions = new List<Exception>(); global::System.Collections.Generic.List<global::System.Exception> exceptions = new List<Exception>();
for (var retry = 0; retry < retryCount; retry++) for(Int32 retry = 0; retry < retryCount; retry++) {
{ try {
try if(retry > 0) {
{
if (retry > 0)
Task.Delay(retryInterval).Wait(); Task.Delay(retryInterval).Wait();
}
return action(); return action();
} } catch(Exception ex) {
catch (Exception ex)
{
exceptions.Add(ex); exceptions.Add(ex);
} }
} }
@ -201,74 +174,55 @@ namespace Swan
/// </returns> /// </returns>
/// <exception cref="ArgumentNullException">model.</exception> /// <exception cref="ArgumentNullException">model.</exception>
/// <seealso cref="AttributeCache"/> /// <seealso cref="AttributeCache"/>
public static IEnumerable<string> GetCopyableProperties(this object @this) public static IEnumerable<String> GetCopyableProperties(this Object? @this) {
{ if(@this == null) {
if (@this == null)
throw new ArgumentNullException(nameof(@this)); throw new ArgumentNullException(nameof(@this));
var collection = PropertyTypeCache.DefaultCache.Value
.RetrieveAllProperties(@this.GetType(), true);
var properties = collection
.Select(x => new
{
x.Name,
HasAttribute = AttributeCache.DefaultCache.Value.RetrieveOne<CopyableAttribute>(x) != null,
})
.Where(x => x.HasAttribute)
.Select(x => x.Name);
return properties.Any()
? properties
: collection.Select(x => x.Name);
} }
internal static void CreateTarget( global::System.Collections.Generic.IEnumerable<global::System.Reflection.PropertyInfo> collection = PropertyTypeCache.DefaultCache.Value.RetrieveAllProperties(@this.GetType(), true);
this object source,
Type targetType, global::System.Collections.Generic.IEnumerable<global::System.String> properties = collection.Select(x => new {
bool includeNonPublic, x.Name,
ref object? target) HasAttribute = AttributeCache.DefaultCache.Value.RetrieveOne<CopyableAttribute>(x) != null,
{ }).Where(x => x.HasAttribute).Select(x => x.Name);
switch (source)
{ return properties.Any() ? properties : collection.Select(x => x.Name);
}
internal static void CreateTarget(this Object source, Type targetType, Boolean includeNonPublic, ref Object? target) {
switch(source) {
// do nothing. Simply skip creation // do nothing. Simply skip creation
case string _: case String _:
break; break;
// When using arrays, there is no default constructor, attempt to build a compatible array // When using arrays, there is no default constructor, attempt to build a compatible array
case IList sourceObjectList when targetType.IsArray: case IList sourceObjectList when targetType.IsArray:
var elementType = targetType.GetElementType(); Type? elementType = targetType.GetElementType();
if (elementType != null) if(elementType != null) {
target = Array.CreateInstance(elementType, sourceObjectList.Count); target = Array.CreateInstance(elementType, sourceObjectList.Count);
}
break; break;
default: default:
var constructors = ConstructorTypeCache.DefaultCache.Value IEnumerable<Tuple<System.Reflection.ConstructorInfo, System.Reflection.ParameterInfo[]>> constructors = ConstructorTypeCache.DefaultCache.Value.RetrieveAllConstructors(targetType, includeNonPublic);
.RetrieveAllConstructors(targetType, includeNonPublic);
// Try to check if empty constructor is available // Try to check if empty constructor is available
if (constructors.Any(x => x.Item2.Length == 0)) if(constructors.Any(x => x.Item2.Length == 0)) {
{
target = Activator.CreateInstance(targetType, includeNonPublic); target = Activator.CreateInstance(targetType, includeNonPublic);
} } else {
else Tuple<System.Reflection.ConstructorInfo, System.Reflection.ParameterInfo[]> firstCtor = constructors.OrderBy(x => x.Item2.Length).FirstOrDefault();
{
var firstCtor = constructors
.OrderBy(x => x.Item2.Length)
.FirstOrDefault();
target = Activator.CreateInstance(targetType, target = Activator.CreateInstance(targetType, firstCtor?.Item2.Select(arg => arg.GetType().GetDefault()).ToArray());
firstCtor?.Item2.Select(arg => arg.GetType().GetDefault()).ToArray());
} }
break; break;
} }
} }
internal static string GetNameWithCase(this string name, JsonSerializerCase jsonSerializerCase) => internal static String GetNameWithCase(this String name, JsonSerializerCase jsonSerializerCase) => jsonSerializerCase switch
jsonSerializerCase switch
{ {
JsonSerializerCase.PascalCase => char.ToUpperInvariant(name[0]) + name.Substring(1), JsonSerializerCase.PascalCase => Char.ToUpperInvariant(name[0]) + name.Substring(1),
JsonSerializerCase.CamelCase => char.ToLowerInvariant(name[0]) + name.Substring(1), JsonSerializerCase.CamelCase => Char.ToLowerInvariant(name[0]) + name.Substring(1),
JsonSerializerCase.None => name, JsonSerializerCase.None => name,
_ => throw new ArgumentOutOfRangeException(nameof(jsonSerializerCase), jsonSerializerCase, null) _ => throw new ArgumentOutOfRangeException(nameof(jsonSerializerCase), jsonSerializerCase, null)
}; };

View File

@ -5,8 +5,7 @@ using System.Linq;
using System.Text; using System.Text;
using Swan.Reflection; using Swan.Reflection;
namespace Swan.Formatters namespace Swan.Formatters {
{
/// <summary> /// <summary>
/// Represents a reader designed for CSV text. /// Represents a reader designed for CSV text.
/// It is capable of deserializing objects from individual lines of CSV text, /// It is capable of deserializing objects from individual lines of CSV text,
@ -65,19 +64,18 @@ namespace Swan.Formatters
/// } /// }
/// </code> /// </code>
/// </example> /// </example>
public class CsvReader : IDisposable public class CsvReader : IDisposable {
{
private static readonly PropertyTypeCache TypeCache = new PropertyTypeCache(); private static readonly PropertyTypeCache TypeCache = new PropertyTypeCache();
private readonly object _syncLock = new object(); private readonly Object _syncLock = new Object();
private ulong _count; private UInt64 _count;
private char _escapeCharacter = '"'; private Char _escapeCharacter = '"';
private char _separatorCharacter = ','; private Char _separatorCharacter = ',';
private bool _hasDisposed; // To detect redundant calls private Boolean _hasDisposed; // To detect redundant calls
private string[] _headings; private String[] _headings;
private Dictionary<string, string> _defaultMap; private Dictionary<String, String> _defaultMap;
private StreamReader _reader; private StreamReader _reader;
#region Constructors #region Constructors
@ -88,15 +86,16 @@ namespace Swan.Formatters
/// <param name="inputStream">The stream.</param> /// <param name="inputStream">The stream.</param>
/// <param name="leaveOpen">if set to <c>true</c> leaves the input stream open.</param> /// <param name="leaveOpen">if set to <c>true</c> leaves the input stream open.</param>
/// <param name="textEncoding">The text encoding.</param> /// <param name="textEncoding">The text encoding.</param>
public CsvReader(Stream inputStream, bool leaveOpen, Encoding textEncoding) public CsvReader(Stream inputStream, Boolean leaveOpen, Encoding textEncoding) {
{ if(inputStream == null) {
if (inputStream == null)
throw new ArgumentNullException(nameof(inputStream)); throw new ArgumentNullException(nameof(inputStream));
}
if (textEncoding == null) if(textEncoding == null) {
throw new ArgumentNullException(nameof(textEncoding)); throw new ArgumentNullException(nameof(textEncoding));
}
_reader = new StreamReader(inputStream, textEncoding, true, 2048, leaveOpen); this._reader = new StreamReader(inputStream, textEncoding, true, 2048, leaveOpen);
} }
/// <summary> /// <summary>
@ -105,9 +104,7 @@ namespace Swan.Formatters
/// </summary> /// </summary>
/// <param name="stream">The stream.</param> /// <param name="stream">The stream.</param>
/// <param name="textEncoding">The text encoding.</param> /// <param name="textEncoding">The text encoding.</param>
public CsvReader(Stream stream, Encoding textEncoding) public CsvReader(Stream stream, Encoding textEncoding) : this(stream, false, textEncoding) {
: this(stream, false, textEncoding)
{
// placeholder // placeholder
} }
@ -117,9 +114,7 @@ namespace Swan.Formatters
/// and uses the Windows 1253 encoding. /// and uses the Windows 1253 encoding.
/// </summary> /// </summary>
/// <param name="stream">The stream.</param> /// <param name="stream">The stream.</param>
public CsvReader(Stream stream) public CsvReader(Stream stream) : this(stream, false, Definitions.Windows1252Encoding) {
: this(stream, false, Definitions.Windows1252Encoding)
{
} }
/// <summary> /// <summary>
@ -128,9 +123,7 @@ namespace Swan.Formatters
/// when this reader is disposed of. /// when this reader is disposed of.
/// </summary> /// </summary>
/// <param name="filename">The filename.</param> /// <param name="filename">The filename.</param>
public CsvReader(string filename) public CsvReader(String filename) : this(File.OpenRead(filename), false, Definitions.Windows1252Encoding) {
: this(File.OpenRead(filename), false, Definitions.Windows1252Encoding)
{
// placeholder // placeholder
} }
@ -140,9 +133,7 @@ namespace Swan.Formatters
/// </summary> /// </summary>
/// <param name="filename">The filename.</param> /// <param name="filename">The filename.</param>
/// <param name="encoding">The encoding.</param> /// <param name="encoding">The encoding.</param>
public CsvReader(string filename, Encoding encoding) public CsvReader(String filename, Encoding encoding) : this(File.OpenRead(filename), false, encoding) {
: this(File.OpenRead(filename), false, encoding)
{
// placeholder // placeholder
} }
@ -156,13 +147,10 @@ namespace Swan.Formatters
/// <value> /// <value>
/// The count. /// The count.
/// </value> /// </value>
public ulong Count public UInt64 Count {
{ get {
get lock(this._syncLock) {
{ return this._count;
lock (_syncLock)
{
return _count;
} }
} }
} }
@ -174,14 +162,11 @@ namespace Swan.Formatters
/// <value> /// <value>
/// The escape character. /// The escape character.
/// </value> /// </value>
public char EscapeCharacter public Char EscapeCharacter {
{ get => this._escapeCharacter;
get => _escapeCharacter; set {
set lock(this._syncLock) {
{ this._escapeCharacter = value;
lock (_syncLock)
{
_escapeCharacter = value;
} }
} }
} }
@ -193,14 +178,11 @@ namespace Swan.Formatters
/// <value> /// <value>
/// The separator character. /// The separator character.
/// </value> /// </value>
public char SeparatorCharacter public Char SeparatorCharacter {
{ get => this._separatorCharacter;
get => _separatorCharacter; set {
set lock(this._syncLock) {
{ this._separatorCharacter = value;
lock (_syncLock)
{
_separatorCharacter = value;
} }
} }
} }
@ -212,13 +194,10 @@ namespace Swan.Formatters
/// <value> /// <value>
/// <c>true</c> if [end of stream]; otherwise, <c>false</c>. /// <c>true</c> if [end of stream]; otherwise, <c>false</c>.
/// </value> /// </value>
public bool EndOfStream public Boolean EndOfStream {
{ get {
get lock(this._syncLock) {
{ return this._reader.EndOfStream;
lock (_syncLock)
{
return _reader.EndOfStream;
} }
} }
} }
@ -232,15 +211,14 @@ namespace Swan.Formatters
/// </summary> /// </summary>
/// <returns>An array of the specified element type containing copies of the elements of the ArrayList.</returns> /// <returns>An array of the specified element type containing copies of the elements of the ArrayList.</returns>
/// <exception cref="System.IO.EndOfStreamException">Cannot read past the end of the stream.</exception> /// <exception cref="System.IO.EndOfStreamException">Cannot read past the end of the stream.</exception>
public string[] ReadLine() public String[] ReadLine() {
{ lock(this._syncLock) {
lock (_syncLock) if(this._reader.EndOfStream) {
{
if (_reader.EndOfStream)
throw new EndOfStreamException("Cannot read past the end of the stream"); throw new EndOfStreamException("Cannot read past the end of the stream");
}
var values = ParseRecord(_reader, _escapeCharacter, _separatorCharacter); String[] values = ParseRecord(this._reader, this._escapeCharacter, this._separatorCharacter);
_count++; this._count++;
return values; return values;
} }
} }
@ -256,14 +234,13 @@ namespace Swan.Formatters
/// as the first read operation (i.e. while count is still 0). /// as the first read operation (i.e. while count is still 0).
/// </summary> /// </summary>
/// <exception cref="System.IO.EndOfStreamException">Cannot read past the end of the stream.</exception> /// <exception cref="System.IO.EndOfStreamException">Cannot read past the end of the stream.</exception>
public void SkipRecord() public void SkipRecord() {
{ lock(this._syncLock) {
lock (_syncLock) if(this._reader.EndOfStream) {
{
if (_reader.EndOfStream)
throw new EndOfStreamException("Cannot read past the end of the stream"); throw new EndOfStreamException("Cannot read past the end of the stream");
}
ParseRecord(_reader, _escapeCharacter, _separatorCharacter); _ = ParseRecord(this._reader, this._escapeCharacter, this._separatorCharacter);
} }
} }
@ -278,20 +255,20 @@ namespace Swan.Formatters
/// ReadHeadings. /// ReadHeadings.
/// </exception> /// </exception>
/// <exception cref="System.IO.EndOfStreamException">Cannot read past the end of the stream.</exception> /// <exception cref="System.IO.EndOfStreamException">Cannot read past the end of the stream.</exception>
public string[] ReadHeadings() public String[] ReadHeadings() {
{ lock(this._syncLock) {
lock (_syncLock) if(this._headings != null) {
{
if (_headings != null)
throw new InvalidOperationException($"The {nameof(ReadHeadings)} method had already been called."); throw new InvalidOperationException($"The {nameof(ReadHeadings)} method had already been called.");
}
if (_count != 0) if(this._count != 0) {
throw new InvalidOperationException("Reading headings is only supported as the first read operation."); throw new InvalidOperationException("Reading headings is only supported as the first read operation.");
}
_headings = ReadLine(); this._headings = this.ReadLine();
_defaultMap = _headings.ToDictionary(x => x, x => x); this._defaultMap = this._headings.ToDictionary(x => x, x => x);
return _headings.ToArray(); return this._headings.ToArray();
} }
} }
@ -303,25 +280,25 @@ namespace Swan.Formatters
/// <exception cref="System.InvalidOperationException">ReadHeadings.</exception> /// <exception cref="System.InvalidOperationException">ReadHeadings.</exception>
/// <exception cref="System.IO.EndOfStreamException">Cannot read past the end of the stream.</exception> /// <exception cref="System.IO.EndOfStreamException">Cannot read past the end of the stream.</exception>
/// <exception cref="System.ArgumentNullException">map.</exception> /// <exception cref="System.ArgumentNullException">map.</exception>
public IDictionary<string, object> ReadObject(IDictionary<string, string> map) public IDictionary<String, Object> ReadObject(IDictionary<String, String> map) {
{ lock(this._syncLock) {
lock (_syncLock) if(this._headings == null) {
{
if (_headings == null)
throw new InvalidOperationException($"Call the {nameof(ReadHeadings)} method before reading as an object."); throw new InvalidOperationException($"Call the {nameof(ReadHeadings)} method before reading as an object.");
}
if (map == null) if(map == null) {
throw new ArgumentNullException(nameof(map)); throw new ArgumentNullException(nameof(map));
}
var result = new Dictionary<string, object>(); Dictionary<String, Object> result = new Dictionary<String, Object>();
var values = ReadLine(); String[] values = this.ReadLine();
for (var i = 0; i < _headings.Length; i++) for(Int32 i = 0; i < this._headings.Length; i++) {
{ if(i > values.Length - 1) {
if (i > values.Length - 1)
break; break;
}
result[_headings[i]] = values[i]; result[this._headings[i]] = values[i];
} }
return result; return result;
@ -333,7 +310,7 @@ namespace Swan.Formatters
/// The property names correspond to the names of the CSV headings. /// The property names correspond to the names of the CSV headings.
/// </summary> /// </summary>
/// <returns>Object of the type of the elements in the collection of key/value pairs.</returns> /// <returns>Object of the type of the elements in the collection of key/value pairs.</returns>
public IDictionary<string, object> ReadObject() => ReadObject(_defaultMap); public IDictionary<String, Object> ReadObject() => this.ReadObject(this._defaultMap);
/// <summary> /// <summary>
/// Reads a line of CSV text converting it into an object of the given type, using a map (or Dictionary) /// Reads a line of CSV text converting it into an object of the given type, using a map (or Dictionary)
@ -348,51 +325,50 @@ namespace Swan.Formatters
/// result.</exception> /// result.</exception>
/// <exception cref="System.InvalidOperationException">ReadHeadings.</exception> /// <exception cref="System.InvalidOperationException">ReadHeadings.</exception>
/// <exception cref="System.IO.EndOfStreamException">Cannot read past the end of the stream.</exception> /// <exception cref="System.IO.EndOfStreamException">Cannot read past the end of the stream.</exception>
public void ReadObject<T>(IDictionary<string, string> map, ref T result) public void ReadObject<T>(IDictionary<String, String> map, ref T result) {
{ lock(this._syncLock) {
lock (_syncLock)
{
// Check arguments // Check arguments
{ {
if (map == null) if(map == null) {
throw new ArgumentNullException(nameof(map)); throw new ArgumentNullException(nameof(map));
}
if (_reader.EndOfStream) if(this._reader.EndOfStream) {
throw new EndOfStreamException("Cannot read past the end of the stream"); throw new EndOfStreamException("Cannot read past the end of the stream");
}
if (_headings == null) if(this._headings == null) {
throw new InvalidOperationException($"Call the {nameof(ReadHeadings)} method before reading as an object."); throw new InvalidOperationException($"Call the {nameof(ReadHeadings)} method before reading as an object.");
}
if (Equals(result, default(T))) if(Equals(result, default(T))) {
throw new ArgumentNullException(nameof(result)); throw new ArgumentNullException(nameof(result));
} }
}
// Read line and extract values // Read line and extract values
var values = ReadLine(); String[] values = this.ReadLine();
// Extract properties from cache // Extract properties from cache
var properties = TypeCache IEnumerable<System.Reflection.PropertyInfo> properties = TypeCache .RetrieveFilteredProperties(typeof(T), true, x => x.CanWrite && Definitions.BasicTypesInfo.Value.ContainsKey(x.PropertyType));
.RetrieveFilteredProperties(typeof(T), true, x => x.CanWrite && Definitions.BasicTypesInfo.Value.ContainsKey(x.PropertyType));
// Assign property values for each heading // Assign property values for each heading
for (var i = 0; i < _headings.Length; i++) for(Int32 i = 0; i < this._headings.Length; i++) {
{
// break if no more headings are matched // break if no more headings are matched
if (i > values.Length - 1) if(i > values.Length - 1) {
break; break;
}
// skip if no heading is available or the heading is empty // skip if no heading is available or the heading is empty
if (map.ContainsKey(_headings[i]) == false && if(map.ContainsKey(this._headings[i]) == false && String.IsNullOrWhiteSpace(map[this._headings[i]]) == false) {
string.IsNullOrWhiteSpace(map[_headings[i]]) == false)
continue; continue;
}
// Prepare the target property // Prepare the target property
var propertyName = map[_headings[i]]; String propertyName = map[this._headings[i]];
// Parse and assign the basic type value to the property if exists // Parse and assign the basic type value to the property if exists
properties _ = properties .FirstOrDefault(p => p.Name == propertyName)? .TrySetBasicType(values[i], result);
.FirstOrDefault(p => p.Name == propertyName)?
.TrySetBasicType(values[i], result);
} }
} }
} }
@ -408,11 +384,9 @@ namespace Swan.Formatters
/// <exception cref="System.ArgumentNullException">map.</exception> /// <exception cref="System.ArgumentNullException">map.</exception>
/// <exception cref="System.InvalidOperationException">ReadHeadings.</exception> /// <exception cref="System.InvalidOperationException">ReadHeadings.</exception>
/// <exception cref="System.IO.EndOfStreamException">Cannot read past the end of the stream.</exception> /// <exception cref="System.IO.EndOfStreamException">Cannot read past the end of the stream.</exception>
public T ReadObject<T>(IDictionary<string, string> map) public T ReadObject<T>(IDictionary<String, String> map) where T : new() {
where T : new() T result = Activator.CreateInstance<T>();
{ this.ReadObject(map, ref result);
var result = Activator.CreateInstance<T>();
ReadObject(map, ref result);
return result; return result;
} }
@ -422,11 +396,7 @@ namespace Swan.Formatters
/// </summary> /// </summary>
/// <typeparam name="T">The type of object.</typeparam> /// <typeparam name="T">The type of object.</typeparam>
/// <returns>The conversion of specific type of object.</returns> /// <returns>The conversion of specific type of object.</returns>
public T ReadObject<T>() public T ReadObject<T>() where T : new() => this.ReadObject<T>(this._defaultMap);
where T : new()
{
return ReadObject<T>(_defaultMap);
}
#endregion #endregion
@ -440,89 +410,76 @@ namespace Swan.Formatters
/// <param name="escapeCharacter">The escape character.</param> /// <param name="escapeCharacter">The escape character.</param>
/// <param name="separatorCharacter">The separator character.</param> /// <param name="separatorCharacter">The separator character.</param>
/// <returns>An array of the specified element type containing copies of the elements of the ArrayList.</returns> /// <returns>An array of the specified element type containing copies of the elements of the ArrayList.</returns>
private static string[] ParseRecord(StreamReader reader, char escapeCharacter = '"', char separatorCharacter = ',') private static String[] ParseRecord(StreamReader reader, Char escapeCharacter = '"', Char separatorCharacter = ',') {
{ List<String> values = new List<String>();
var values = new List<string>(); StringBuilder currentValue = new StringBuilder(1024);
var currentValue = new StringBuilder(1024); ReadState currentState = ReadState.WaitingForNewField;
var currentState = ReadState.WaitingForNewField; String line;
string line;
while ((line = reader.ReadLine()) != null) while((line = reader.ReadLine()) != null) {
{ for(Int32 charIndex = 0; charIndex < line.Length; charIndex++) {
for (var charIndex = 0; charIndex < line.Length; charIndex++)
{
// Get the current and next character // Get the current and next character
var currentChar = line[charIndex]; Char currentChar = line[charIndex];
var nextChar = charIndex < line.Length - 1 ? line[charIndex + 1] : new char?(); Char? nextChar = charIndex < line.Length - 1 ? line[charIndex + 1] : new global::System.Char?();
// Perform logic based on state and decide on next state // Perform logic based on state and decide on next state
switch (currentState) switch(currentState) {
{ case ReadState.WaitingForNewField: {
case ReadState.WaitingForNewField: _ = currentValue.Clear();
{
currentValue.Clear();
if (currentChar == escapeCharacter) if(currentChar == escapeCharacter) {
{
currentState = ReadState.PushingQuoted; currentState = ReadState.PushingQuoted;
continue; continue;
} }
if (currentChar == separatorCharacter) if(currentChar == separatorCharacter) {
{
values.Add(currentValue.ToString()); values.Add(currentValue.ToString());
currentState = ReadState.WaitingForNewField; currentState = ReadState.WaitingForNewField;
continue; continue;
} }
currentValue.Append(currentChar); _ = currentValue.Append(currentChar);
currentState = ReadState.PushingNormal; currentState = ReadState.PushingNormal;
continue; continue;
} }
case ReadState.PushingNormal: case ReadState.PushingNormal: {
{
// Handle field content delimiter by comma // Handle field content delimiter by comma
if (currentChar == separatorCharacter) if(currentChar == separatorCharacter) {
{
currentState = ReadState.WaitingForNewField; currentState = ReadState.WaitingForNewField;
values.Add(currentValue.ToString()); values.Add(currentValue.ToString());
currentValue.Clear(); _ = currentValue.Clear();
continue; continue;
} }
// Handle double quote escaping // Handle double quote escaping
if (currentChar == escapeCharacter && nextChar.HasValue && nextChar == escapeCharacter) if(currentChar == escapeCharacter && nextChar.HasValue && nextChar == escapeCharacter) {
{
// advance 1 character now. The loop will advance one more. // advance 1 character now. The loop will advance one more.
currentValue.Append(currentChar); _ = currentValue.Append(currentChar);
charIndex++; charIndex++;
continue; continue;
} }
currentValue.Append(currentChar); _ = currentValue.Append(currentChar);
break; break;
} }
case ReadState.PushingQuoted: case ReadState.PushingQuoted: {
{
// Handle field content delimiter by ending double quotes // Handle field content delimiter by ending double quotes
if (currentChar == escapeCharacter && (nextChar.HasValue == false || nextChar != escapeCharacter)) if(currentChar == escapeCharacter && (nextChar.HasValue == false || nextChar != escapeCharacter)) {
{
currentState = ReadState.PushingNormal; currentState = ReadState.PushingNormal;
continue; continue;
} }
// Handle double quote escaping // Handle double quote escaping
if (currentChar == escapeCharacter && nextChar.HasValue && nextChar == escapeCharacter) if(currentChar == escapeCharacter && nextChar.HasValue && nextChar == escapeCharacter) {
{
// advance 1 character now. The loop will advance one more. // advance 1 character now. The loop will advance one more.
currentValue.Append(currentChar); _ = currentValue.Append(currentChar);
charIndex++; charIndex++;
continue; continue;
} }
currentValue.Append(currentChar); _ = currentValue.Append(currentChar);
break; break;
} }
} }
@ -530,17 +487,14 @@ namespace Swan.Formatters
// determine if we need to continue reading a new line if it is part of the quoted // determine if we need to continue reading a new line if it is part of the quoted
// field value // field value
if (currentState == ReadState.PushingQuoted) if(currentState == ReadState.PushingQuoted) {
{
// we need to add the new line sequence to the output of the field // we need to add the new line sequence to the output of the field
// because we were pushing a quoted value // because we were pushing a quoted value
currentValue.Append(Environment.NewLine); _ = currentValue.Append(Environment.NewLine);
} } else {
else
{
// push anything that has not been pushed (flush) into a last value // push anything that has not been pushed (flush) into a last value
values.Add(currentValue.ToString()); values.Add(currentValue.ToString());
currentValue.Clear(); _ = currentValue.Clear();
// stop reading more lines we have reached the end of the CSV record // stop reading more lines we have reached the end of the CSV record
break; break;
@ -549,8 +503,7 @@ namespace Swan.Formatters
// If we ended up pushing quoted and no closing quotes we might // If we ended up pushing quoted and no closing quotes we might
// have additional text in yt // have additional text in yt
if (currentValue.Length > 0) if(currentValue.Length > 0) {
{
values.Add(currentValue.ToString()); values.Add(currentValue.ToString());
} }
@ -568,16 +521,12 @@ namespace Swan.Formatters
/// <typeparam name="T">The type of IList items to load.</typeparam> /// <typeparam name="T">The type of IList items to load.</typeparam>
/// <param name="stream">The stream.</param> /// <param name="stream">The stream.</param>
/// <returns>A generic collection of objects that can be individually accessed by index.</returns> /// <returns>A generic collection of objects that can be individually accessed by index.</returns>
public static IList<T> LoadRecords<T>(Stream stream) public static IList<T> LoadRecords<T>(Stream stream) where T : new() {
where T : new() List<T> result = new List<T>();
{
var result = new List<T>();
using (var reader = new CsvReader(stream)) using(CsvReader reader = new CsvReader(stream)) {
{ _ = reader.ReadHeadings();
reader.ReadHeadings(); while(!reader.EndOfStream) {
while (!reader.EndOfStream)
{
result.Add(reader.ReadObject<T>()); result.Add(reader.ReadObject<T>());
} }
} }
@ -592,11 +541,7 @@ namespace Swan.Formatters
/// <typeparam name="T">The type of IList items to load.</typeparam> /// <typeparam name="T">The type of IList items to load.</typeparam>
/// <param name="filePath">The file path.</param> /// <param name="filePath">The file path.</param>
/// <returns>A generic collection of objects that can be individually accessed by index.</returns> /// <returns>A generic collection of objects that can be individually accessed by index.</returns>
public static IList<T> LoadRecords<T>(string filePath) public static IList<T> LoadRecords<T>(String filePath) where T : new() => LoadRecords<T>(File.OpenRead(filePath));
where T : new()
{
return LoadRecords<T>(File.OpenRead(filePath));
}
#endregion #endregion
@ -606,29 +551,25 @@ namespace Swan.Formatters
/// Releases unmanaged and - optionally - managed resources. /// Releases unmanaged and - optionally - managed resources.
/// </summary> /// </summary>
/// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param> /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
protected virtual void Dispose(bool disposing) protected virtual void Dispose(Boolean disposing) {
{ if(this._hasDisposed) {
if (_hasDisposed) return; return;
if (disposing)
{
try
{
_reader.Dispose();
} }
finally
{ if(disposing) {
_reader = null; try {
this._reader.Dispose();
} finally {
this._reader = null;
} }
} }
_hasDisposed = true; this._hasDisposed = true;
} }
/// <inheritdoc /> /// <inheritdoc />
public void Dispose() public void Dispose() {
{ this.Dispose(true);
Dispose(true);
GC.SuppressFinalize(this); GC.SuppressFinalize(this);
} }
@ -638,8 +579,7 @@ namespace Swan.Formatters
/// Defines the 3 different read states /// Defines the 3 different read states
/// for the parsing state machine. /// for the parsing state machine.
/// </summary> /// </summary>
private enum ReadState private enum ReadState {
{
WaitingForNewField, WaitingForNewField,
PushingNormal, PushingNormal,
PushingQuoted, PushingQuoted,

View File

@ -7,8 +7,7 @@ using System.Reflection;
using System.Text; using System.Text;
using Swan.Reflection; using Swan.Reflection;
namespace Swan.Formatters namespace Swan.Formatters {
{
/// <summary> /// <summary>
/// A CSV writer useful for exporting a set of objects. /// A CSV writer useful for exporting a set of objects.
/// </summary> /// </summary>
@ -46,16 +45,15 @@ namespace Swan.Formatters
/// } /// }
/// </code> /// </code>
/// </example> /// </example>
public class CsvWriter : IDisposable public class CsvWriter : IDisposable {
{
private static readonly PropertyTypeCache TypeCache = new PropertyTypeCache(); private static readonly PropertyTypeCache TypeCache = new PropertyTypeCache();
private readonly object _syncLock = new object(); private readonly Object _syncLock = new Object();
private readonly Stream _outputStream; private readonly Stream _outputStream;
private readonly Encoding _encoding; private readonly Encoding _encoding;
private readonly bool _leaveStreamOpen; private readonly Boolean _leaveStreamOpen;
private bool _isDisposing; private Boolean _isDisposing;
private ulong _mCount; private UInt64 _mCount;
#region Constructors #region Constructors
@ -65,11 +63,10 @@ namespace Swan.Formatters
/// <param name="outputStream">The output stream.</param> /// <param name="outputStream">The output stream.</param>
/// <param name="leaveOpen">if set to <c>true</c> [leave open].</param> /// <param name="leaveOpen">if set to <c>true</c> [leave open].</param>
/// <param name="encoding">The encoding.</param> /// <param name="encoding">The encoding.</param>
public CsvWriter(Stream outputStream, bool leaveOpen, Encoding encoding) public CsvWriter(Stream outputStream, Boolean leaveOpen, Encoding encoding) {
{ this._outputStream = outputStream;
_outputStream = outputStream; this._encoding = encoding;
_encoding = encoding; this._leaveStreamOpen = leaveOpen;
_leaveStreamOpen = leaveOpen;
} }
/// <summary> /// <summary>
@ -78,9 +75,7 @@ namespace Swan.Formatters
/// </summary> /// </summary>
/// <param name="outputStream">The output stream.</param> /// <param name="outputStream">The output stream.</param>
/// <param name="encoding">The encoding.</param> /// <param name="encoding">The encoding.</param>
public CsvWriter(Stream outputStream, Encoding encoding) public CsvWriter(Stream outputStream, Encoding encoding) : this(outputStream, false, encoding) {
: this(outputStream, false, encoding)
{
// placeholder // placeholder
} }
@ -90,9 +85,7 @@ namespace Swan.Formatters
/// the stream upon disposing this writer. /// the stream upon disposing this writer.
/// </summary> /// </summary>
/// <param name="outputStream">The output stream.</param> /// <param name="outputStream">The output stream.</param>
public CsvWriter(Stream outputStream) public CsvWriter(Stream outputStream) : this(outputStream, false, Definitions.Windows1252Encoding) {
: this(outputStream, false, Definitions.Windows1252Encoding)
{
// placeholder // placeholder
} }
@ -102,9 +95,7 @@ namespace Swan.Formatters
/// disposing of this writer, and uses the Windows 1252 encoding. /// disposing of this writer, and uses the Windows 1252 encoding.
/// </summary> /// </summary>
/// <param name="filename">The filename.</param> /// <param name="filename">The filename.</param>
public CsvWriter(string filename) public CsvWriter(String filename) : this(File.OpenWrite(filename), false, Definitions.Windows1252Encoding) {
: this(File.OpenWrite(filename), false, Definitions.Windows1252Encoding)
{
// placeholder // placeholder
} }
@ -115,9 +106,7 @@ namespace Swan.Formatters
/// </summary> /// </summary>
/// <param name="filename">The filename.</param> /// <param name="filename">The filename.</param>
/// <param name="encoding">The encoding.</param> /// <param name="encoding">The encoding.</param>
public CsvWriter(string filename, Encoding encoding) public CsvWriter(String filename, Encoding encoding) : this(File.OpenWrite(filename), false, encoding) {
: this(File.OpenWrite(filename), false, encoding)
{
// placeholder // placeholder
} }
@ -131,7 +120,7 @@ namespace Swan.Formatters
/// <value> /// <value>
/// The separator character. /// The separator character.
/// </value> /// </value>
public char SeparatorCharacter { get; set; } = ','; public Char SeparatorCharacter { get; set; } = ',';
/// <summary> /// <summary>
/// Gets or sets the escape character to use to escape field values. /// Gets or sets the escape character to use to escape field values.
@ -139,7 +128,7 @@ namespace Swan.Formatters
/// <value> /// <value>
/// The escape character. /// The escape character.
/// </value> /// </value>
public char EscapeCharacter { get; set; } = '"'; public Char EscapeCharacter { get; set; } = '"';
/// <summary> /// <summary>
/// Gets or sets the new line character sequence to use when writing a line. /// Gets or sets the new line character sequence to use when writing a line.
@ -147,7 +136,7 @@ namespace Swan.Formatters
/// <value> /// <value>
/// The new line sequence. /// The new line sequence.
/// </value> /// </value>
public string NewLineSequence { get; set; } = Environment.NewLine; public String NewLineSequence { get; set; } = Environment.NewLine;
/// <summary> /// <summary>
/// Defines a list of properties to ignore when outputting CSV lines. /// Defines a list of properties to ignore when outputting CSV lines.
@ -155,7 +144,7 @@ namespace Swan.Formatters
/// <value> /// <value>
/// The ignore property names. /// The ignore property names.
/// </value> /// </value>
public List<string> IgnorePropertyNames { get; } = new List<string>(); public List<String> IgnorePropertyNames { get; } = new List<String>();
/// <summary> /// <summary>
/// Gets number of lines that have been written, including the headings line. /// Gets number of lines that have been written, including the headings line.
@ -163,13 +152,10 @@ namespace Swan.Formatters
/// <value> /// <value>
/// The count. /// The count.
/// </value> /// </value>
public ulong Count public UInt64 Count {
{ get {
get lock(this._syncLock) {
{ return this._mCount;
lock (_syncLock)
{
return _mCount;
} }
} }
} }
@ -187,18 +173,16 @@ namespace Swan.Formatters
/// <param name="stream">The stream.</param> /// <param name="stream">The stream.</param>
/// <param name="truncateData"><c>true</c> if stream is truncated, default <c>false</c>.</param> /// <param name="truncateData"><c>true</c> if stream is truncated, default <c>false</c>.</param>
/// <returns>Number of item saved.</returns> /// <returns>Number of item saved.</returns>
public static int SaveRecords<T>(IEnumerable<T> items, Stream stream, bool truncateData = false) public static Int32 SaveRecords<T>(IEnumerable<T> items, Stream stream, Boolean truncateData = false) {
{
// truncate the file if it had data // truncate the file if it had data
if (truncateData && stream.Length > 0) if(truncateData && stream.Length > 0) {
stream.SetLength(0); stream.SetLength(0);
}
using (var writer = new CsvWriter(stream)) using CsvWriter writer = new CsvWriter(stream);
{
writer.WriteHeadings<T>(); writer.WriteHeadings<T>();
writer.WriteObjects(items); writer.WriteObjects(items);
return (int)writer.Count; return (Int32)writer.Count;
}
} }
/// <summary> /// <summary>
@ -210,7 +194,7 @@ namespace Swan.Formatters
/// <param name="items">The items.</param> /// <param name="items">The items.</param>
/// <param name="filePath">The file path.</param> /// <param name="filePath">The file path.</param>
/// <returns>Number of item saved.</returns> /// <returns>Number of item saved.</returns>
public static int SaveRecords<T>(IEnumerable<T> items, string filePath) => SaveRecords(items, File.OpenWrite(filePath), true); public static Int32 SaveRecords<T>(IEnumerable<T> items, String filePath) => SaveRecords(items, File.OpenWrite(filePath), true);
#endregion #endregion
@ -222,8 +206,7 @@ namespace Swan.Formatters
/// If items are not string, the ToStringInvariant() method is called on them. /// If items are not string, the ToStringInvariant() method is called on them.
/// </summary> /// </summary>
/// <param name="items">The items.</param> /// <param name="items">The items.</param>
public void WriteLine(params object[] items) public void WriteLine(params Object[] items) => this.WriteLine(items.Select(x => x == null ? String.Empty : x.ToStringInvariant()));
=> WriteLine(items.Select(x => x == null ? string.Empty : x.ToStringInvariant()));
/// <summary> /// <summary>
/// Writes a line of CSV text. Items are converted to strings. /// Writes a line of CSV text. Items are converted to strings.
@ -231,67 +214,61 @@ namespace Swan.Formatters
/// If items are not string, the ToStringInvariant() method is called on them. /// If items are not string, the ToStringInvariant() method is called on them.
/// </summary> /// </summary>
/// <param name="items">The items.</param> /// <param name="items">The items.</param>
public void WriteLine(IEnumerable<object> items) public void WriteLine(IEnumerable<Object> items) => this.WriteLine(items.Select(x => x == null ? String.Empty : x.ToStringInvariant()));
=> WriteLine(items.Select(x => x == null ? string.Empty : x.ToStringInvariant()));
/// <summary> /// <summary>
/// Writes a line of CSV text. /// Writes a line of CSV text.
/// If items are found to be null, empty strings are written out. /// If items are found to be null, empty strings are written out.
/// </summary> /// </summary>
/// <param name="items">The items.</param> /// <param name="items">The items.</param>
public void WriteLine(params string[] items) => WriteLine((IEnumerable<string>) items); public void WriteLine(params String[] items) => this.WriteLine((IEnumerable<String>)items);
/// <summary> /// <summary>
/// Writes a line of CSV text. /// Writes a line of CSV text.
/// If items are found to be null, empty strings are written out. /// If items are found to be null, empty strings are written out.
/// </summary> /// </summary>
/// <param name="items">The items.</param> /// <param name="items">The items.</param>
public void WriteLine(IEnumerable<string> items) public void WriteLine(IEnumerable<String> items) {
{ lock(this._syncLock) {
lock (_syncLock) Int32 length = items.Count();
{ Byte[] separatorBytes = this._encoding.GetBytes(new[] { this.SeparatorCharacter });
var length = items.Count(); Byte[] endOfLineBytes = this._encoding.GetBytes(this.NewLineSequence);
var separatorBytes = _encoding.GetBytes(new[] { SeparatorCharacter });
var endOfLineBytes = _encoding.GetBytes(NewLineSequence);
// Declare state variables here to avoid recreation, allocation and // Declare state variables here to avoid recreation, allocation and
// reassignment in every loop // reassignment in every loop
bool needsEnclosing; Boolean needsEnclosing;
string textValue; String textValue;
byte[] output; Byte[] output;
for (var i = 0; i < length; i++) for(Int32 i = 0; i < length; i++) {
{
textValue = items.ElementAt(i); textValue = items.ElementAt(i);
// Determine if we need the string to be enclosed // Determine if we need the string to be enclosed
// (it either contains an escape, new line, or separator char) // (it either contains an escape, new line, or separator char)
needsEnclosing = textValue.IndexOf(SeparatorCharacter) >= 0 needsEnclosing = textValue.IndexOf(this.SeparatorCharacter) >= 0 || textValue.IndexOf(this.EscapeCharacter) >= 0 || textValue.IndexOf('\r') >= 0 || textValue.IndexOf('\n') >= 0;
|| textValue.IndexOf(EscapeCharacter) >= 0
|| textValue.IndexOf('\r') >= 0
|| textValue.IndexOf('\n') >= 0;
// Escape the escape characters by repeating them twice for every instance // Escape the escape characters by repeating them twice for every instance
textValue = textValue.Replace($"{EscapeCharacter}", textValue = textValue.Replace($"{this.EscapeCharacter}", $"{this.EscapeCharacter}{this.EscapeCharacter}");
$"{EscapeCharacter}{EscapeCharacter}");
// Enclose the text value if we need to // Enclose the text value if we need to
if (needsEnclosing) if(needsEnclosing) {
textValue = string.Format($"{EscapeCharacter}{textValue}{EscapeCharacter}", textValue); textValue = String.Format($"{this.EscapeCharacter}{textValue}{this.EscapeCharacter}", textValue);
}
// Get the bytes to write to the stream and write them // Get the bytes to write to the stream and write them
output = _encoding.GetBytes(textValue); output = this._encoding.GetBytes(textValue);
_outputStream.Write(output, 0, output.Length); this._outputStream.Write(output, 0, output.Length);
// only write a separator if we are moving in between values. // only write a separator if we are moving in between values.
// the last value should not be written. // the last value should not be written.
if (i < length - 1) if(i < length - 1) {
_outputStream.Write(separatorBytes, 0, separatorBytes.Length); this._outputStream.Write(separatorBytes, 0, separatorBytes.Length);
}
} }
// output the newline sequence // output the newline sequence
_outputStream.Write(endOfLineBytes, 0, endOfLineBytes.Length); this._outputStream.Write(endOfLineBytes, 0, endOfLineBytes.Length);
_mCount += 1; this._mCount += 1;
} }
} }
@ -307,24 +284,21 @@ namespace Swan.Formatters
/// </summary> /// </summary>
/// <param name="item">The item.</param> /// <param name="item">The item.</param>
/// <exception cref="System.ArgumentNullException">item.</exception> /// <exception cref="System.ArgumentNullException">item.</exception>
public void WriteObject(object item) public void WriteObject(Object item) {
{ if(item == null) {
if (item == null)
throw new ArgumentNullException(nameof(item)); throw new ArgumentNullException(nameof(item));
}
lock (_syncLock) lock(this._syncLock) {
{ switch(item) {
switch (item)
{
case IDictionary typedItem: case IDictionary typedItem:
WriteLine(GetFilteredDictionary(typedItem)); this.WriteLine(this.GetFilteredDictionary(typedItem));
return; return;
case ICollection typedItem: case ICollection typedItem:
WriteLine(typedItem.Cast<object>()); this.WriteLine(typedItem.Cast<Object>());
return; return;
default: default:
WriteLine(GetFilteredTypeProperties(item.GetType()) this.WriteLine(this.GetFilteredTypeProperties(item.GetType()).Select(x => x.ToFormattedString(item)));
.Select(x => x.ToFormattedString(item)));
break; break;
} }
} }
@ -338,7 +312,7 @@ namespace Swan.Formatters
/// </summary> /// </summary>
/// <typeparam name="T">The type of object to write.</typeparam> /// <typeparam name="T">The type of object to write.</typeparam>
/// <param name="item">The item.</param> /// <param name="item">The item.</param>
public void WriteObject<T>(T item) => WriteObject(item as object); public void WriteObject<T>(T item) => this.WriteObject(item as Object);
/// <summary> /// <summary>
/// Writes a set of items, one per line and atomically by repeatedly calling the /// Writes a set of items, one per line and atomically by repeatedly calling the
@ -347,12 +321,11 @@ namespace Swan.Formatters
/// </summary> /// </summary>
/// <typeparam name="T">The type of object to write.</typeparam> /// <typeparam name="T">The type of object to write.</typeparam>
/// <param name="items">The items.</param> /// <param name="items">The items.</param>
public void WriteObjects<T>(IEnumerable<T> items) public void WriteObjects<T>(IEnumerable<T> items) {
{ lock(this._syncLock) {
lock (_syncLock) foreach(T item in items) {
{ this.WriteObject(item);
foreach (var item in items) }
WriteObject(item);
} }
} }
@ -365,32 +338,32 @@ namespace Swan.Formatters
/// </summary> /// </summary>
/// <param name="type">The type of object to extract headings.</param> /// <param name="type">The type of object to extract headings.</param>
/// <exception cref="System.ArgumentNullException">type.</exception> /// <exception cref="System.ArgumentNullException">type.</exception>
public void WriteHeadings(Type type) public void WriteHeadings(Type type) {
{ if(type == null) {
if (type == null)
throw new ArgumentNullException(nameof(type)); throw new ArgumentNullException(nameof(type));
}
var properties = GetFilteredTypeProperties(type).Select(p => p.Name).Cast<object>(); IEnumerable<Object> properties = this.GetFilteredTypeProperties(type).Select(p => p.Name).Cast<Object>();
WriteLine(properties); this.WriteLine(properties);
} }
/// <summary> /// <summary>
/// Writes the headings. /// Writes the headings.
/// </summary> /// </summary>
/// <typeparam name="T">The type of object to extract headings.</typeparam> /// <typeparam name="T">The type of object to extract headings.</typeparam>
public void WriteHeadings<T>() => WriteHeadings(typeof(T)); public void WriteHeadings<T>() => this.WriteHeadings(typeof(T));
/// <summary> /// <summary>
/// Writes the headings. /// Writes the headings.
/// </summary> /// </summary>
/// <param name="dictionary">The dictionary to extract headings.</param> /// <param name="dictionary">The dictionary to extract headings.</param>
/// <exception cref="System.ArgumentNullException">dictionary.</exception> /// <exception cref="System.ArgumentNullException">dictionary.</exception>
public void WriteHeadings(IDictionary dictionary) public void WriteHeadings(IDictionary dictionary) {
{ if(dictionary == null) {
if (dictionary == null)
throw new ArgumentNullException(nameof(dictionary)); throw new ArgumentNullException(nameof(dictionary));
}
WriteLine(GetFilteredDictionary(dictionary, true)); this.WriteLine(this.GetFilteredDictionary(dictionary, true));
} }
/// <summary> /// <summary>
@ -398,12 +371,12 @@ namespace Swan.Formatters
/// </summary> /// </summary>
/// <param name="obj">The object to extract headings.</param> /// <param name="obj">The object to extract headings.</param>
/// <exception cref="ArgumentNullException">obj.</exception> /// <exception cref="ArgumentNullException">obj.</exception>
public void WriteHeadings(object obj) public void WriteHeadings(Object obj) {
{ if(obj == null) {
if (obj == null)
throw new ArgumentNullException(nameof(obj)); throw new ArgumentNullException(nameof(obj));
}
WriteHeadings(obj.GetType()); this.WriteHeadings(obj.GetType());
} }
#endregion #endregion
@ -411,47 +384,36 @@ namespace Swan.Formatters
#region IDisposable Support #region IDisposable Support
/// <inheritdoc /> /// <inheritdoc />
public void Dispose() => Dispose(true); public void Dispose() => this.Dispose(true);
/// <summary> /// <summary>
/// Releases unmanaged and - optionally - managed resources. /// Releases unmanaged and - optionally - managed resources.
/// </summary> /// </summary>
/// <param name="disposeAlsoManaged"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param> /// <param name="disposeAlsoManaged"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
protected virtual void Dispose(bool disposeAlsoManaged) protected virtual void Dispose(Boolean disposeAlsoManaged) {
{ if(this._isDisposing) {
if (_isDisposing) return; return;
}
if (disposeAlsoManaged) if(disposeAlsoManaged) {
{ if(this._leaveStreamOpen == false) {
if (_leaveStreamOpen == false) this._outputStream.Dispose();
{
_outputStream.Dispose();
} }
} }
_isDisposing = true; this._isDisposing = true;
} }
#endregion #endregion
#region Support Methods #region Support Methods
private IEnumerable<string> GetFilteredDictionary(IDictionary dictionary, bool filterKeys = false) private IEnumerable<String> GetFilteredDictionary(IDictionary dictionary, Boolean filterKeys = false) => dictionary.Keys.Cast<Object>()
=> dictionary .Select(key => key == null ? String.Empty : key.ToStringInvariant())
.Keys .Where(stringKey => !this.IgnorePropertyNames.Contains(stringKey))
.Cast<object>() .Select(stringKey => filterKeys ? stringKey : dictionary[stringKey] == null ? String.Empty : dictionary[stringKey].ToStringInvariant());
.Select(key => key == null ? string.Empty : key.ToStringInvariant())
.Where(stringKey => !IgnorePropertyNames.Contains(stringKey))
.Select(stringKey =>
filterKeys
? stringKey
: dictionary[stringKey] == null ? string.Empty : dictionary[stringKey].ToStringInvariant());
private IEnumerable<PropertyInfo> GetFilteredTypeProperties(Type type) private IEnumerable<PropertyInfo> GetFilteredTypeProperties(Type type) => TypeCache.Retrieve(type, t => t.GetProperties(BindingFlags.Public | BindingFlags.Instance).Where(p => p.CanRead)).Where(p => !this.IgnorePropertyNames.Contains(p.Name));
=> TypeCache.Retrieve(type, t =>
t.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Where(p => p.CanRead))
.Where(p => !IgnorePropertyNames.Contains(p.Name));
#endregion #endregion

View File

@ -1,149 +1,125 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System;
namespace Swan.Formatters namespace Swan.Formatters {
{ internal class HumanizeJson {
internal class HumanizeJson
{
private readonly StringBuilder _builder = new StringBuilder(); private readonly StringBuilder _builder = new StringBuilder();
private readonly int _indent; private readonly Int32 _indent;
private readonly string _indentStr; private readonly String _indentStr;
private readonly object _obj; private readonly Object _obj;
public HumanizeJson(object obj, int indent) public HumanizeJson(Object obj, Int32 indent) {
{ if(obj == null) {
if (obj == null)
{
return; return;
} }
_indent = indent; this._indent = indent;
_indentStr = new string(' ', indent * 4); this._indentStr = new String(' ', indent * 4);
_obj = obj; this._obj = obj;
ParseObject(); this.ParseObject();
} }
public string GetResult() => _builder == null ? string.Empty : _builder.ToString().TrimEnd(); public String GetResult() => this._builder == null ? String.Empty : this._builder.ToString().TrimEnd();
private void ParseObject() private void ParseObject() {
{ switch(this._obj) {
switch (_obj) case Dictionary<String, Object> dictionary:
{ this.AppendDictionary(dictionary);
case Dictionary<string, object> dictionary:
AppendDictionary(dictionary);
break; break;
case List<object> list: case List<Object> list:
AppendList(list); this.AppendList(list);
break; break;
default: default:
AppendString(); this.AppendString();
break; break;
} }
} }
private void AppendDictionary(Dictionary<string, object> objects) private void AppendDictionary(Dictionary<String, Object> objects) {
{ foreach(KeyValuePair<String, Object> kvp in objects) {
foreach (var kvp in objects) if(kvp.Value == null) {
{ continue;
if (kvp.Value == null) continue; }
var writeOutput = false; Boolean writeOutput = false;
switch (kvp.Value) switch(kvp.Value) {
{ case Dictionary<String, Object> valueDictionary:
case Dictionary<string, object> valueDictionary: if(valueDictionary.Count > 0) {
if (valueDictionary.Count > 0)
{
writeOutput = true; writeOutput = true;
_builder _ = this._builder.Append($"{this._indentStr}{kvp.Key,-16}: object").AppendLine();
.Append($"{_indentStr}{kvp.Key,-16}: object")
.AppendLine();
} }
break; break;
case List<object> valueList: case List<Object> valueList:
if (valueList.Count > 0) if(valueList.Count > 0) {
{
writeOutput = true; writeOutput = true;
_builder _ = this._builder.Append($"{this._indentStr}{kvp.Key,-16}: array[{valueList.Count}]").AppendLine();
.Append($"{_indentStr}{kvp.Key,-16}: array[{valueList.Count}]")
.AppendLine();
} }
break; break;
default: default:
writeOutput = true; writeOutput = true;
_builder.Append($"{_indentStr}{kvp.Key,-16}: "); _ = this._builder.Append($"{this._indentStr}{kvp.Key,-16}: ");
break; break;
} }
if (writeOutput) if(writeOutput) {
_builder.AppendLine(new HumanizeJson(kvp.Value, _indent + 1).GetResult()); _ = this._builder.AppendLine(new HumanizeJson(kvp.Value, this._indent + 1).GetResult());
}
} }
} }
private void AppendList(List<object> objects) private void AppendList(List<Object> objects) {
{ Int32 index = 0;
var index = 0; foreach(Object value in objects) {
foreach (var value in objects) Boolean writeOutput = false;
{
var writeOutput = false;
switch (value) switch(value) {
{ case Dictionary<String, Object> valueDictionary:
case Dictionary<string, object> valueDictionary: if(valueDictionary.Count > 0) {
if (valueDictionary.Count > 0)
{
writeOutput = true; writeOutput = true;
_builder _ = this._builder.Append($"{this._indentStr}[{index}]: object").AppendLine();
.Append($"{_indentStr}[{index}]: object")
.AppendLine();
} }
break; break;
case List<object> valueList: case List<Object> valueList:
if (valueList.Count > 0) if(valueList.Count > 0) {
{
writeOutput = true; writeOutput = true;
_builder _ = this._builder.Append($"{this._indentStr}[{index}]: array[{valueList.Count}]").AppendLine();
.Append($"{_indentStr}[{index}]: array[{valueList.Count}]")
.AppendLine();
} }
break; break;
default: default:
writeOutput = true; writeOutput = true;
_builder.Append($"{_indentStr}[{index}]: "); _ = this._builder.Append($"{this._indentStr}[{index}]: ");
break; break;
} }
index++; index++;
if (writeOutput) if(writeOutput) {
_builder.AppendLine(new HumanizeJson(value, _indent + 1).GetResult()); _ = this._builder.AppendLine(new HumanizeJson(value, this._indent + 1).GetResult());
}
} }
} }
private void AppendString() private void AppendString() {
{ String stringValue = this._obj.ToString();
var stringValue = _obj.ToString();
if (stringValue.Length + _indentStr.Length > 96 || stringValue.IndexOf('\r') >= 0 || if(stringValue.Length + this._indentStr.Length > 96 || stringValue.IndexOf('\r') >= 0 ||
stringValue.IndexOf('\n') >= 0) stringValue.IndexOf('\n') >= 0) {
{ _ = this._builder.AppendLine();
_builder.AppendLine(); IEnumerable<String> stringLines = stringValue.ToLines().Select(l => l.Trim());
var stringLines = stringValue.ToLines().Select(l => l.Trim());
foreach (var line in stringLines) foreach(String line in stringLines) {
{ _ = this._builder.AppendLine($"{this._indentStr}{line}");
_builder.AppendLine($"{_indentStr}{line}");
} }
} } else {
else _ = this._builder.Append($"{stringValue}");
{
_builder.Append($"{stringValue}");
} }
} }
} }

View File

@ -1,4 +1,5 @@
using System; #nullable enable
using System;
using System.Collections; using System.Collections;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
@ -7,8 +8,7 @@ using System.Reflection;
using System.Text; using System.Text;
using Swan.Reflection; using Swan.Reflection;
namespace Swan.Formatters namespace Swan.Formatters {
{
/// <summary> /// <summary>
/// A very simple, light-weight JSON library written by Mario /// A very simple, light-weight JSON library written by Mario
/// to teach Geo how things are done /// to teach Geo how things are done
@ -16,319 +16,240 @@ namespace Swan.Formatters
/// This is an useful helper for small tasks but it doesn't represent a full-featured /// This is an useful helper for small tasks but it doesn't represent a full-featured
/// serializer such as the beloved Json.NET. /// serializer such as the beloved Json.NET.
/// </summary> /// </summary>
public static partial class Json public static partial class Json {
{ private class Converter {
private class Converter private static readonly ConcurrentDictionary<MemberInfo, String> MemberInfoNameCache = new ConcurrentDictionary<MemberInfo, global::System.String>();
{
private static readonly ConcurrentDictionary<MemberInfo, string> MemberInfoNameCache =
new ConcurrentDictionary<MemberInfo, string>();
private static readonly ConcurrentDictionary<Type, Type> ListAddMethodCache = new ConcurrentDictionary<Type, Type>(); private static readonly ConcurrentDictionary<Type, Type> ListAddMethodCache = new ConcurrentDictionary<Type, Type>();
private readonly object? _target; private readonly Object? _target;
private readonly Type _targetType; private readonly Type _targetType;
private readonly bool _includeNonPublic; private readonly Boolean _includeNonPublic;
private readonly JsonSerializerCase _jsonSerializerCase; private readonly JsonSerializerCase _jsonSerializerCase;
private Converter( private Converter(Object? source, Type targetType, ref Object? targetInstance, Boolean includeNonPublic, JsonSerializerCase jsonSerializerCase) {
object? source, this._targetType = targetInstance != null ? targetInstance.GetType() : targetType;
Type targetType, this._includeNonPublic = includeNonPublic;
ref object? targetInstance, this._jsonSerializerCase = jsonSerializerCase;
bool includeNonPublic,
JsonSerializerCase jsonSerializerCase)
{
_targetType = targetInstance != null ? targetInstance.GetType() : targetType;
_includeNonPublic = includeNonPublic;
_jsonSerializerCase = jsonSerializerCase;
if (source == null) if(source == null) {
{
return; return;
} }
var sourceType = source.GetType(); Type sourceType = source.GetType();
if (_targetType == null || _targetType == typeof(object)) _targetType = sourceType; if(this._targetType == null || this._targetType == typeof(Object)) {
if (sourceType == _targetType) this._targetType = sourceType;
{ }
_target = source;
if(sourceType == this._targetType) {
this._target = source;
return; return;
} }
if (!TrySetInstance(targetInstance, source, ref _target)) if(!this.TrySetInstance(targetInstance, source, ref this._target)) {
return; return;
ResolveObject(source, ref _target);
} }
internal static object? FromJsonResult( this.ResolveObject(source, ref this._target);
object? source,
JsonSerializerCase jsonSerializerCase,
Type? targetType = null,
bool includeNonPublic = false)
{
object? nullRef = null;
return new Converter(source, targetType ?? typeof(object), ref nullRef, includeNonPublic, jsonSerializerCase).GetResult();
} }
private static object? FromJsonResult(object source, internal static Object? FromJsonResult(Object? source, JsonSerializerCase jsonSerializerCase, Type? targetType = null, Boolean includeNonPublic = false) {
Type targetType, Object? nullRef = null;
ref object? targetInstance, return new Converter(source, targetType ?? typeof(Object), ref nullRef, includeNonPublic, jsonSerializerCase).GetResult();
bool includeNonPublic)
{
return new Converter(source, targetType, ref targetInstance, includeNonPublic, JsonSerializerCase.None).GetResult();
} }
private static Type? GetAddMethodParameterType(Type targetType) private static Object? FromJsonResult(Object source, Type targetType, ref Object? targetInstance, Boolean includeNonPublic) => new Converter(source, targetType, ref targetInstance, includeNonPublic, JsonSerializerCase.None).GetResult();
=> ListAddMethodCache.GetOrAdd(targetType,
x => x.GetMethods()
.FirstOrDefault(
m => m.Name == AddMethodName && m.IsPublic && m.GetParameters().Length == 1)?
.GetParameters()[0]
.ParameterType);
private static void GetByteArray(string sourceString, ref object? target) private static Type? GetAddMethodParameterType(Type targetType) => ListAddMethodCache.GetOrAdd(targetType, x => x.GetMethods().FirstOrDefault(m => m.Name == AddMethodName && m.IsPublic && m.GetParameters().Length == 1)?.GetParameters()[0].ParameterType!);
{
try private static void GetByteArray(String sourceString, ref Object? target) {
{ try {
target = Convert.FromBase64String(sourceString); target = Convert.FromBase64String(sourceString);
} // Try conversion from Base 64 } // Try conversion from Base 64
catch (FormatException) catch(FormatException) {
{
target = Encoding.UTF8.GetBytes(sourceString); target = Encoding.UTF8.GetBytes(sourceString);
} // Get the string bytes in UTF8 } // Get the string bytes in UTF8
} }
private object GetSourcePropertyValue( private Object GetSourcePropertyValue(IDictionary<String, Object> sourceProperties, MemberInfo targetProperty) {
IDictionary<string, object> sourceProperties, String targetPropertyName = MemberInfoNameCache.GetOrAdd(targetProperty, x => AttributeCache.DefaultCache.Value.RetrieveOne<JsonPropertyAttribute>(x)?.PropertyName ?? x.Name.GetNameWithCase(this._jsonSerializerCase));
MemberInfo targetProperty)
{
var targetPropertyName = MemberInfoNameCache.GetOrAdd(
targetProperty,
x => AttributeCache.DefaultCache.Value.RetrieveOne<JsonPropertyAttribute>(x)?.PropertyName ?? x.Name.GetNameWithCase(_jsonSerializerCase));
return sourceProperties.GetValueOrDefault(targetPropertyName); return sourceProperties.GetValueOrDefault(targetPropertyName);
} }
private bool TrySetInstance(object? targetInstance, object source, ref object? target) private Boolean TrySetInstance(Object? targetInstance, Object source, ref Object? target) {
{ if(targetInstance == null) {
if (targetInstance == null)
{
// Try to create a default instance // Try to create a default instance
try try {
{ source.CreateTarget(this._targetType, this._includeNonPublic, ref target);
source.CreateTarget(_targetType, _includeNonPublic, ref target); } catch {
}
catch
{
return false; return false;
} }
} } else {
else
{
target = targetInstance; target = targetInstance;
} }
return true; return true;
} }
private object? GetResult() => _target ?? _targetType.GetDefault(); private Object? GetResult() => this._target ?? this._targetType.GetDefault();
private void ResolveObject(object source, ref object? target) private void ResolveObject(Object source, ref Object? target) {
{ switch(source) {
switch (source)
{
// Case 0: Special Cases Handling (Source and Target are of specific convertible types) // Case 0: Special Cases Handling (Source and Target are of specific convertible types)
// Case 0.1: Source is string, Target is byte[] // Case 0.1: Source is string, Target is byte[]
case string sourceString when _targetType == typeof(byte[]): case String sourceString when this._targetType == typeof(Byte[]):
GetByteArray(sourceString, ref target); GetByteArray(sourceString, ref target);
break; break;
// Case 1.1: Source is Dictionary, Target is IDictionary // Case 1.1: Source is Dictionary, Target is IDictionary
case Dictionary<string, object> sourceProperties when target is IDictionary targetDictionary: case Dictionary<String, Object> sourceProperties when target is IDictionary targetDictionary:
PopulateDictionary(sourceProperties, targetDictionary); this.PopulateDictionary(sourceProperties, targetDictionary);
break; break;
// Case 1.2: Source is Dictionary, Target is not IDictionary (i.e. it is a complex type) // Case 1.2: Source is Dictionary, Target is not IDictionary (i.e. it is a complex type)
case Dictionary<string, object> sourceProperties: case Dictionary<String, Object> sourceProperties:
PopulateObject(sourceProperties); this.PopulateObject(sourceProperties);
break; break;
// Case 2.1: Source is List, Target is Array // Case 2.1: Source is List, Target is Array
case List<object> sourceList when target is Array targetArray: case List<Object> sourceList when target is Array targetArray:
PopulateArray(sourceList, targetArray); this.PopulateArray(sourceList, targetArray);
break; break;
// Case 2.2: Source is List, Target is IList // Case 2.2: Source is List, Target is IList
case List<object> sourceList when target is IList targetList: case List<Object> sourceList when target is IList targetList:
PopulateIList(sourceList, targetList); this.PopulateIList(sourceList, targetList);
break; break;
// Case 3: Source is a simple type; Attempt conversion // Case 3: Source is a simple type; Attempt conversion
default: default:
var sourceStringValue = source.ToStringInvariant(); String sourceStringValue = source.ToStringInvariant();
// Handle basic types or enumerations if not // Handle basic types or enumerations if not
if (!_targetType.TryParseBasicType(sourceStringValue, out target)) if(!this._targetType.TryParseBasicType(sourceStringValue, out target)) {
GetEnumValue(sourceStringValue, ref target); this.GetEnumValue(sourceStringValue, ref target);
}
break; break;
} }
} }
private void PopulateIList(IEnumerable<object> objects, IList list) private void PopulateIList(IEnumerable<Object> objects, IList list) {
{ Type? parameterType = GetAddMethodParameterType(this._targetType);
var parameterType = GetAddMethodParameterType(_targetType); if(parameterType == null) {
if (parameterType == null) return; return;
foreach (var item in objects)
{
try
{
list.Add(FromJsonResult(
item,
_jsonSerializerCase,
parameterType,
_includeNonPublic));
} }
catch
{ foreach(Object item in objects) {
try {
_ = list.Add(FromJsonResult(item, this._jsonSerializerCase, parameterType, this._includeNonPublic));
} catch {
// ignored // ignored
} }
} }
} }
private void PopulateArray(IList<object> objects, Array array) private void PopulateArray(IList<Object> objects, Array array) {
{ Type? elementType = this._targetType.GetElementType();
var elementType = _targetType.GetElementType();
for (var i = 0; i < objects.Count; i++) for(Int32 i = 0; i < objects.Count; i++) {
{ try {
try Object? targetItem = FromJsonResult(objects[i], this._jsonSerializerCase, elementType, this._includeNonPublic);
{
var targetItem = FromJsonResult(
objects[i],
_jsonSerializerCase,
elementType,
_includeNonPublic);
array.SetValue(targetItem, i); array.SetValue(targetItem, i);
} } catch {
catch
{
// ignored // ignored
} }
} }
} }
private void GetEnumValue(string sourceStringValue, ref object? target) private void GetEnumValue(String sourceStringValue, ref Object? target) {
{ Type? enumType = Nullable.GetUnderlyingType(this._targetType);
var enumType = Nullable.GetUnderlyingType(_targetType); if(enumType == null && this._targetType.IsEnum) {
if (enumType == null && _targetType.IsEnum) enumType = _targetType; enumType = this._targetType;
if (enumType == null) return; }
try if(enumType == null) {
{ return;
}
try {
target = Enum.Parse(enumType, sourceStringValue); target = Enum.Parse(enumType, sourceStringValue);
} } catch {
catch
{
// ignored // ignored
} }
} }
private void PopulateDictionary(IDictionary<string, object> sourceProperties, IDictionary targetDictionary) private void PopulateDictionary(IDictionary<String, Object> sourceProperties, IDictionary targetDictionary) {
{
// find the add method of the target dictionary // find the add method of the target dictionary
var addMethod = _targetType.GetMethods() MethodInfo addMethod = this._targetType.GetMethods().FirstOrDefault(m => m.Name == AddMethodName && m.IsPublic && m.GetParameters().Length == 2);
.FirstOrDefault(
m => m.Name == AddMethodName && m.IsPublic && m.GetParameters().Length == 2);
// skip if we don't have a compatible add method // skip if we don't have a compatible add method
if (addMethod == null) return; if(addMethod == null) {
var addMethodParameters = addMethod.GetParameters(); return;
if (addMethodParameters[0].ParameterType != typeof(string)) return; }
global::System.Reflection.ParameterInfo[] addMethodParameters = addMethod.GetParameters();
if(addMethodParameters[0].ParameterType != typeof(String)) {
return;
}
// Retrieve the target entry type // Retrieve the target entry type
var targetEntryType = addMethodParameters[1].ParameterType; Type targetEntryType = addMethodParameters[1].ParameterType;
// Add the items to the target dictionary // Add the items to the target dictionary
foreach (var sourceProperty in sourceProperties) foreach(KeyValuePair<String, Object> sourceProperty in sourceProperties) {
{ try {
try Object? targetEntryValue = FromJsonResult(sourceProperty.Value, this._jsonSerializerCase, targetEntryType, this._includeNonPublic);
{
var targetEntryValue = FromJsonResult(
sourceProperty.Value,
_jsonSerializerCase,
targetEntryType,
_includeNonPublic);
targetDictionary.Add(sourceProperty.Key, targetEntryValue); targetDictionary.Add(sourceProperty.Key, targetEntryValue);
} } catch {
catch
{
// ignored // ignored
} }
} }
} }
private void PopulateObject(IDictionary<string, object> sourceProperties) private void PopulateObject(IDictionary<String, Object> sourceProperties) {
{ if(this._targetType.IsValueType) {
if (_targetType.IsValueType) this.PopulateFields(sourceProperties);
{
PopulateFields(sourceProperties);
} }
PopulateProperties(sourceProperties); this.PopulateProperties(sourceProperties);
} }
private void PopulateProperties(IDictionary<string, object> sourceProperties) private void PopulateProperties(IDictionary<String, Object> sourceProperties) {
{ global::System.Collections.Generic.IEnumerable<global::System.Reflection.PropertyInfo> properties = PropertyTypeCache.DefaultCache.Value.RetrieveFilteredProperties(this._targetType, false, p => p.CanWrite);
var properties = PropertyTypeCache.DefaultCache.Value.RetrieveFilteredProperties(_targetType, false, p => p.CanWrite);
foreach (var property in properties) foreach(PropertyInfo property in properties) {
{ Object sourcePropertyValue = this.GetSourcePropertyValue(sourceProperties, property);
var sourcePropertyValue = GetSourcePropertyValue(sourceProperties, property); if(sourcePropertyValue == null) {
if (sourcePropertyValue == null) continue; continue;
try
{
var currentPropertyValue = !property.PropertyType.IsArray
? property.GetCacheGetMethod(_includeNonPublic)(_target)
: null;
var targetPropertyValue = FromJsonResult(
sourcePropertyValue,
property.PropertyType,
ref currentPropertyValue,
_includeNonPublic);
property.GetCacheSetMethod(_includeNonPublic)(_target, new[] { targetPropertyValue });
} }
catch
{ try {
Object? currentPropertyValue = !property.PropertyType.IsArray ? property?.GetCacheGetMethod(this._includeNonPublic)!(this._target!) : null;
Object? targetPropertyValue = FromJsonResult(sourcePropertyValue, property.PropertyType, ref currentPropertyValue, this._includeNonPublic);
property?.GetCacheSetMethod(this._includeNonPublic)!(this._target!, new[] { targetPropertyValue }!);
} catch {
// ignored // ignored
} }
} }
} }
private void PopulateFields(IDictionary<string, object> sourceProperties) private void PopulateFields(IDictionary<String, Object> sourceProperties) {
{ foreach(FieldInfo field in FieldTypeCache.DefaultCache.Value.RetrieveAllFields(this._targetType)) {
foreach (var field in FieldTypeCache.DefaultCache.Value.RetrieveAllFields(_targetType)) Object sourcePropertyValue = this.GetSourcePropertyValue(sourceProperties, field);
{ if(sourcePropertyValue == null) {
var sourcePropertyValue = GetSourcePropertyValue(sourceProperties, field); continue;
if (sourcePropertyValue == null) continue;
var targetPropertyValue = FromJsonResult(
sourcePropertyValue,
_jsonSerializerCase,
field.FieldType,
_includeNonPublic);
try
{
field.SetValue(_target, targetPropertyValue);
} }
catch
{ Object? targetPropertyValue = FromJsonResult(sourcePropertyValue, this._jsonSerializerCase, field.FieldType, this._includeNonPublic);
try {
field.SetValue(this._target, targetPropertyValue);
} catch {
// ignored // ignored
} }
} }

View File

@ -1,9 +1,9 @@
using System; #nullable enable
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text; using System.Text;
namespace Swan.Formatters namespace Swan.Formatters {
{
/// <summary> /// <summary>
/// A very simple, light-weight JSON library written by Mario /// A very simple, light-weight JSON library written by Mario
/// to teach Geo how things are done /// to teach Geo how things are done
@ -11,204 +11,194 @@ namespace Swan.Formatters
/// This is an useful helper for small tasks but it doesn't represent a full-featured /// This is an useful helper for small tasks but it doesn't represent a full-featured
/// serializer such as the beloved Json.NET. /// serializer such as the beloved Json.NET.
/// </summary> /// </summary>
public partial class Json public partial class Json {
{
/// <summary> /// <summary>
/// A simple JSON Deserializer. /// A simple JSON Deserializer.
/// </summary> /// </summary>
private class Deserializer private class Deserializer {
{
#region State Variables #region State Variables
private readonly object? _result; private readonly Object? _result;
private readonly string _json; private readonly String _json;
private Dictionary<string, object> _resultObject; private Dictionary<String, Object?>? _resultObject;
private List<object> _resultArray; private List<Object?>? _resultArray;
private ReadState _state = ReadState.WaitingForRootOpen; private ReadState _state = ReadState.WaitingForRootOpen;
private string? _currentFieldName; private String? _currentFieldName;
private int _index; private Int32 _index;
#endregion #endregion
private Deserializer(string json, int startIndex) private Deserializer(String? json, Int32 startIndex) {
{ if(json == null) {
_json = json; this._json = "";
for (_index = startIndex; _index < _json.Length; _index++)
{
switch (_state)
{
case ReadState.WaitingForRootOpen:
WaitForRootOpen();
continue;
case ReadState.WaitingForField when char.IsWhiteSpace(_json, _index):
continue;
case ReadState.WaitingForField when (_resultObject != null && _json[_index] == CloseObjectChar)
|| (_resultArray != null && _json[_index] == CloseArrayChar):
// Handle empty arrays and empty objects
_result = _resultObject ?? _resultArray as object;
return; return;
case ReadState.WaitingForField when _json[_index] != StringQuotedChar: }
throw CreateParserException($"'{StringQuotedChar}'"); this._json = json;
case ReadState.WaitingForField:
{
var charCount = GetFieldNameCount();
_currentFieldName = Unescape(_json.SliceLength(_index + 1, charCount)); for(this._index = startIndex; this._index < this._json.Length; this._index++) {
_index += charCount + 1; switch(this._state) {
_state = ReadState.WaitingForColon; case ReadState.WaitingForRootOpen:
this.WaitForRootOpen();
continue;
case ReadState.WaitingForField when Char.IsWhiteSpace(this._json, this._index):
continue;
case ReadState.WaitingForField when this._resultObject != null && this._json[this._index] == CloseObjectChar || this._resultArray != null && this._json[this._index] == CloseArrayChar:
// Handle empty arrays and empty objects
this._result = this._resultObject ?? this._resultArray as Object;
return;
case ReadState.WaitingForField when this._json[this._index] != StringQuotedChar:
throw this.CreateParserException($"'{StringQuotedChar}'");
case ReadState.WaitingForField: {
Int32 charCount = this.GetFieldNameCount();
this._currentFieldName = Unescape(this._json.SliceLength(this._index + 1, charCount));
this._index += charCount + 1;
this._state = ReadState.WaitingForColon;
continue; continue;
} }
case ReadState.WaitingForColon when char.IsWhiteSpace(_json, _index): case ReadState.WaitingForColon when Char.IsWhiteSpace(this._json, this._index):
continue; continue;
case ReadState.WaitingForColon when _json[_index] != ValueSeparatorChar: case ReadState.WaitingForColon when this._json[this._index] != ValueSeparatorChar:
throw CreateParserException($"'{ValueSeparatorChar}'"); throw this.CreateParserException($"'{ValueSeparatorChar}'");
case ReadState.WaitingForColon: case ReadState.WaitingForColon:
_state = ReadState.WaitingForValue; this._state = ReadState.WaitingForValue;
continue; continue;
case ReadState.WaitingForValue when char.IsWhiteSpace(_json, _index): case ReadState.WaitingForValue when Char.IsWhiteSpace(this._json, this._index):
continue; continue;
case ReadState.WaitingForValue when (_resultObject != null && _json[_index] == CloseObjectChar) case ReadState.WaitingForValue when this._resultObject != null && this._json[this._index] == CloseObjectChar || this._resultArray != null && this._json[this._index] == CloseArrayChar:
|| (_resultArray != null && _json[_index] == CloseArrayChar):
// Handle empty arrays and empty objects // Handle empty arrays and empty objects
_result = _resultObject ?? _resultArray as object; this._result = this._resultObject ?? this._resultArray as Object;
return; return;
case ReadState.WaitingForValue: case ReadState.WaitingForValue:
ExtractValue(); this.ExtractValue();
continue; continue;
} }
if (_state != ReadState.WaitingForNextOrRootClose || char.IsWhiteSpace(_json, _index)) continue; if(this._state != ReadState.WaitingForNextOrRootClose || Char.IsWhiteSpace(this._json, this._index)) {
if (_json[_index] == FieldSeparatorChar)
{
if (_resultObject != null)
{
_state = ReadState.WaitingForField;
_currentFieldName = null;
continue; continue;
} }
_state = ReadState.WaitingForValue; if(this._json[this._index] == FieldSeparatorChar) {
if(this._resultObject != null) {
this._state = ReadState.WaitingForField;
this._currentFieldName = null;
continue; continue;
} }
if ((_resultObject == null || _json[_index] != CloseObjectChar) && this._state = ReadState.WaitingForValue;
(_resultArray == null || _json[_index] != CloseArrayChar)) continue;
{
throw CreateParserException($"'{FieldSeparatorChar}' '{CloseObjectChar}' or '{CloseArrayChar}'");
} }
_result = _resultObject ?? _resultArray as object; if((this._resultObject == null || this._json[this._index] != CloseObjectChar) && (this._resultArray == null || this._json[this._index] != CloseArrayChar)) {
throw this.CreateParserException($"'{FieldSeparatorChar}' '{CloseObjectChar}' or '{CloseArrayChar}'");
}
this._result = this._resultObject ?? this._resultArray as Object;
return; return;
} }
} }
internal static object? DeserializeInternal(string json) => new Deserializer(json, 0)._result; internal static Object? DeserializeInternal(String? json) => new Deserializer(json, 0)._result;
private void WaitForRootOpen() private void WaitForRootOpen() {
{ if(Char.IsWhiteSpace(this._json, this._index)) {
if (char.IsWhiteSpace(_json, _index)) return; return;
}
switch (_json[_index]) switch(this._json[this._index]) {
{
case OpenObjectChar: case OpenObjectChar:
_resultObject = new Dictionary<string, object>(); this._resultObject = new Dictionary<String, Object?>();
_state = ReadState.WaitingForField; this._state = ReadState.WaitingForField;
return; return;
case OpenArrayChar: case OpenArrayChar:
_resultArray = new List<object>(); this._resultArray = new List<Object?>();
_state = ReadState.WaitingForValue; this._state = ReadState.WaitingForValue;
return; return;
default: default:
throw CreateParserException($"'{OpenObjectChar}' or '{OpenArrayChar}'"); throw this.CreateParserException($"'{OpenObjectChar}' or '{OpenArrayChar}'");
} }
} }
private void ExtractValue() private void ExtractValue() {
{
// determine the value based on what it starts with // determine the value based on what it starts with
switch (_json[_index]) switch(this._json[this._index]) {
{
case StringQuotedChar: // expect a string case StringQuotedChar: // expect a string
ExtractStringQuoted(); this.ExtractStringQuoted();
break; break;
case OpenObjectChar: // expect object case OpenObjectChar: // expect object
case OpenArrayChar: // expect array case OpenArrayChar: // expect array
ExtractObject(); this.ExtractObject();
break; break;
case 't': // expect true case 't': // expect true
ExtractConstant(TrueLiteral, true); this.ExtractConstant(TrueLiteral, true);
break; break;
case 'f': // expect false case 'f': // expect false
ExtractConstant(FalseLiteral, false); this.ExtractConstant(FalseLiteral, false);
break; break;
case 'n': // expect null case 'n': // expect null
ExtractConstant(NullLiteral, null); this.ExtractConstant(NullLiteral, null);
break; break;
default: // expect number default: // expect number
ExtractNumber(); this.ExtractNumber();
break; break;
} }
_currentFieldName = null; this._currentFieldName = null;
_state = ReadState.WaitingForNextOrRootClose; this._state = ReadState.WaitingForNextOrRootClose;
} }
private static string Unescape(string str) private static String Unescape(String str) {
{
// check if we need to unescape at all // check if we need to unescape at all
if (str.IndexOf(StringEscapeChar) < 0) if(str.IndexOf(StringEscapeChar) < 0) {
return str; return str;
}
var builder = new StringBuilder(str.Length); StringBuilder builder = new StringBuilder(str.Length);
for (var i = 0; i < str.Length; i++) for(Int32 i = 0; i < str.Length; i++) {
{ if(str[i] != StringEscapeChar) {
if (str[i] != StringEscapeChar) _ = builder.Append(str[i]);
{
builder.Append(str[i]);
continue; continue;
} }
if (i + 1 > str.Length - 1) if(i + 1 > str.Length - 1) {
break; break;
}
// escape sequence begins here // escape sequence begins here
switch (str[i + 1]) switch(str[i + 1]) {
{
case 'u': case 'u':
i = ExtractEscapeSequence(str, i, builder); i = ExtractEscapeSequence(str, i, builder);
break; break;
case 'b': case 'b':
builder.Append('\b'); _ = builder.Append('\b');
i += 1; i += 1;
break; break;
case 't': case 't':
builder.Append('\t'); _ = builder.Append('\t');
i += 1; i += 1;
break; break;
case 'n': case 'n':
builder.Append('\n'); _ = builder.Append('\n');
i += 1; i += 1;
break; break;
case 'f': case 'f':
builder.Append('\f'); _ = builder.Append('\f');
i += 1; i += 1;
break; break;
case 'r': case 'r':
builder.Append('\r'); _ = builder.Append('\r');
i += 1; i += 1;
break; break;
default: default:
builder.Append(str[i + 1]); _ = builder.Append(str[i + 1]);
i += 1; i += 1;
break; break;
} }
@ -217,30 +207,27 @@ namespace Swan.Formatters
return builder.ToString(); return builder.ToString();
} }
private static int ExtractEscapeSequence(string str, int i, StringBuilder builder) private static Int32 ExtractEscapeSequence(String str, Int32 i, StringBuilder builder) {
{ Int32 startIndex = i + 2;
var startIndex = i + 2; Int32 endIndex = i + 5;
var endIndex = i + 5; if(endIndex > str.Length - 1) {
if (endIndex > str.Length - 1) _ = builder.Append(str[i + 1]);
{
builder.Append(str[i + 1]);
i += 1; i += 1;
return i; return i;
} }
var hexCode = str.Slice(startIndex, endIndex).ConvertHexadecimalToBytes(); Byte[] hexCode = str.Slice(startIndex, endIndex).ConvertHexadecimalToBytes();
builder.Append(Encoding.BigEndianUnicode.GetChars(hexCode)); _ = builder.Append(Encoding.BigEndianUnicode.GetChars(hexCode));
i += 5; i += 5;
return i; return i;
} }
private int GetFieldNameCount() private Int32 GetFieldNameCount() {
{ Int32 charCount = 0;
var charCount = 0; for(Int32 j = this._index + 1; j < this._json.Length; j++) {
for (var j = _index + 1; j < _json.Length; j++) if(this._json[j] == StringQuotedChar && this._json[j - 1] != StringEscapeChar) {
{
if (_json[j] == StringQuotedChar && _json[j - 1] != StringEscapeChar)
break; break;
}
charCount++; charCount++;
} }
@ -248,95 +235,92 @@ namespace Swan.Formatters
return charCount; return charCount;
} }
private void ExtractObject() private void ExtractObject() {
{
// Extract and set the value // Extract and set the value
var deserializer = new Deserializer(_json, _index); Deserializer deserializer = new Deserializer(this._json, this._index);
if (_currentFieldName != null) if(this._currentFieldName != null) {
_resultObject[_currentFieldName] = deserializer._result; this._resultObject![this._currentFieldName] = deserializer._result!;
else } else {
_resultArray.Add(deserializer._result); this._resultArray!.Add(deserializer._result!);
_index = deserializer._index;
} }
private void ExtractNumber() this._index = deserializer._index;
{ }
var charCount = 0;
for (var j = _index; j < _json.Length; j++) private void ExtractNumber() {
{ Int32 charCount = 0;
if (char.IsWhiteSpace(_json[j]) || _json[j] == FieldSeparatorChar for(Int32 j = this._index; j < this._json.Length; j++) {
|| (_resultObject != null && _json[j] == CloseObjectChar) if(Char.IsWhiteSpace(this._json[j]) || this._json[j] == FieldSeparatorChar || this._resultObject != null && this._json[j] == CloseObjectChar || this._resultArray != null && this._json[j] == CloseArrayChar) {
|| (_resultArray != null && _json[j] == CloseArrayChar))
break; break;
}
charCount++; charCount++;
} }
// Extract and set the value // Extract and set the value
var stringValue = _json.SliceLength(_index, charCount); String stringValue = this._json.SliceLength(this._index, charCount);
if (decimal.TryParse(stringValue, System.Globalization.NumberStyles.Number, System.Globalization.CultureInfo.InvariantCulture, out var value) == false) if(Decimal.TryParse(stringValue, System.Globalization.NumberStyles.Number, System.Globalization.CultureInfo.InvariantCulture, out Decimal value) == false) {
throw CreateParserException("[number]"); throw this.CreateParserException("[number]");
if (_currentFieldName != null)
_resultObject[_currentFieldName] = value;
else
_resultArray.Add(value);
_index += charCount - 1;
} }
private void ExtractConstant(string boolValue, bool? value) if(this._currentFieldName != null) {
{ this._resultObject![this._currentFieldName] = value;
if (_json.SliceLength(_index, boolValue.Length) != boolValue) } else {
throw CreateParserException($"'{ValueSeparatorChar}'"); this._resultArray!.Add(value);
}
this._index += charCount - 1;
}
private void ExtractConstant(String boolValue, Boolean? value) {
if(this._json.SliceLength(this._index, boolValue.Length) != boolValue) {
throw this.CreateParserException($"'{ValueSeparatorChar}'");
}
// Extract and set the value // Extract and set the value
if (_currentFieldName != null) if(this._currentFieldName != null) {
_resultObject[_currentFieldName] = value; this._resultObject![this._currentFieldName] = value;
else } else {
_resultArray.Add(value); this._resultArray!.Add(value);
_index += boolValue.Length - 1;
} }
private void ExtractStringQuoted() this._index += boolValue.Length - 1;
{ }
var charCount = 0;
var escapeCharFound = false;
for (var j = _index + 1; j < _json.Length; j++)
{
if (_json[j] == StringQuotedChar && !escapeCharFound)
break;
escapeCharFound = _json[j] == StringEscapeChar && !escapeCharFound; private void ExtractStringQuoted() {
Int32 charCount = 0;
Boolean escapeCharFound = false;
for(Int32 j = this._index + 1; j < this._json.Length; j++) {
if(this._json[j] == StringQuotedChar && !escapeCharFound) {
break;
}
escapeCharFound = this._json[j] == StringEscapeChar && !escapeCharFound;
charCount++; charCount++;
} }
// Extract and set the value // Extract and set the value
var value = Unescape(_json.SliceLength(_index + 1, charCount)); String value = Unescape(this._json.SliceLength(this._index + 1, charCount));
if (_currentFieldName != null) if(this._currentFieldName != null) {
_resultObject[_currentFieldName] = value; this._resultObject![this._currentFieldName] = value;
else } else {
_resultArray.Add(value); this._resultArray!.Add(value);
_index += charCount + 1;
} }
private FormatException CreateParserException(string expected) this._index += charCount + 1;
{ }
var textPosition = _json.TextPositionAt(_index);
return new FormatException( private FormatException CreateParserException(String expected) {
$"Parser error (Line {textPosition.Item1}, Col {textPosition.Item2}, State {_state}): Expected {expected} but got '{_json[_index]}'."); Tuple<Int32, Int32> textPosition = this._json.TextPositionAt(this._index);
return new FormatException($"Parser error (Line {textPosition.Item1}, Col {textPosition.Item2}, State {this._state}): Expected {expected} but got '{this._json[this._index]}'.");
} }
/// <summary> /// <summary>
/// Defines the different JSON read states. /// Defines the different JSON read states.
/// </summary> /// </summary>
private enum ReadState private enum ReadState {
{
WaitingForRootOpen, WaitingForRootOpen,
WaitingForField, WaitingForField,
WaitingForColon, WaitingForColon,

View File

@ -1,12 +1,12 @@
using System; #nullable enable
using System;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using System.Text; using System.Text;
namespace Swan.Formatters namespace Swan.Formatters {
{
/// <summary> /// <summary>
/// A very simple, light-weight JSON library written by Mario /// A very simple, light-weight JSON library written by Mario
/// to teach Geo how things are done /// to teach Geo how things are done
@ -14,21 +14,19 @@ namespace Swan.Formatters
/// This is an useful helper for small tasks but it doesn't represent a full-featured /// This is an useful helper for small tasks but it doesn't represent a full-featured
/// serializer such as the beloved Json.NET. /// serializer such as the beloved Json.NET.
/// </summary> /// </summary>
public partial class Json public partial class Json {
{
/// <summary> /// <summary>
/// A simple JSON serializer. /// A simple JSON serializer.
/// </summary> /// </summary>
private class Serializer private class Serializer {
{
#region Private Declarations #region Private Declarations
private static readonly Dictionary<int, string> IndentStrings = new Dictionary<int, string>(); private static readonly Dictionary<Int32, String> IndentStrings = new Dictionary<global::System.Int32, global::System.String>();
private readonly SerializerOptions _options; private readonly SerializerOptions? _options;
private readonly string _result; private readonly String _result;
private readonly StringBuilder _builder; private readonly StringBuilder? _builder;
private readonly string _lastCommaSearch; private readonly String? _lastCommaSearch;
#endregion #endregion
@ -40,144 +38,140 @@ namespace Swan.Formatters
/// <param name="obj">The object.</param> /// <param name="obj">The object.</param>
/// <param name="depth">The depth.</param> /// <param name="depth">The depth.</param>
/// <param name="options">The options.</param> /// <param name="options">The options.</param>
private Serializer(object? obj, int depth, SerializerOptions options) private Serializer(Object? obj, Int32 depth, SerializerOptions options) {
{ if(depth > 20) {
if (depth > 20) throw new InvalidOperationException("The max depth (20) has been reached. Serializer can not continue.");
{
throw new InvalidOperationException(
"The max depth (20) has been reached. Serializer can not continue.");
} }
// Basic Type Handling (nulls, strings, number, date and bool) // Basic Type Handling (nulls, strings, number, date and bool)
_result = ResolveBasicType(obj); this._result = ResolveBasicType(obj);
if (!string.IsNullOrWhiteSpace(_result)) if(!String.IsNullOrWhiteSpace(this._result)) {
return; return;
}
_options = options; this._options = options;
// Handle circular references correctly and avoid them // Handle circular references correctly and avoid them
if (options.IsObjectPresent(obj!)) if(options.IsObjectPresent(obj!)) {
{ this._result = $"{{ \"$circref\": \"{Escape(obj!.GetHashCode().ToStringInvariant(), false)}\" }}";
_result = $"{{ \"$circref\": \"{Escape(obj!.GetHashCode().ToStringInvariant(), false)}\" }}";
return; return;
} }
// At this point, we will need to construct the object with a StringBuilder. // At this point, we will need to construct the object with a StringBuilder.
_lastCommaSearch = FieldSeparatorChar + (_options.Format ? Environment.NewLine : string.Empty); this._lastCommaSearch = FieldSeparatorChar + (this._options.Format ? Environment.NewLine : String.Empty);
_builder = new StringBuilder(); this._builder = new StringBuilder();
_result = obj switch this._result = obj switch
{ {
IDictionary itemsZero when itemsZero.Count == 0 => EmptyObjectLiteral, IDictionary itemsZero when itemsZero.Count == 0 => EmptyObjectLiteral,
IDictionary items => ResolveDictionary(items, depth), IDictionary items => this.ResolveDictionary(items, depth),
IEnumerable enumerableZero when !enumerableZero.Cast<object>().Any() => EmptyArrayLiteral, IEnumerable enumerableZero when !enumerableZero.Cast<Object>().Any() => EmptyArrayLiteral,
IEnumerable enumerableBytes when enumerableBytes is byte[] bytes => Serialize(bytes.ToBase64(), depth, _options), IEnumerable enumerableBytes when enumerableBytes is Byte[] bytes => Serialize(bytes.ToBase64(), depth, this._options),
IEnumerable enumerable => ResolveEnumerable(enumerable, depth), IEnumerable enumerable => this.ResolveEnumerable(enumerable, depth),
_ => ResolveObject(obj!, depth) _ => this.ResolveObject(obj!, depth)
}; };
} }
internal static string Serialize(object? obj, int depth, SerializerOptions options) => new Serializer(obj, depth, options)._result; internal static String Serialize(Object? obj, Int32 depth, SerializerOptions options) => new Serializer(obj, depth, options)._result;
#endregion #endregion
#region Helper Methods #region Helper Methods
private static string ResolveBasicType(object? obj) private static String ResolveBasicType(Object? obj) {
{ switch(obj) {
switch (obj)
{
case null: case null:
return NullLiteral; return NullLiteral;
case string s: case String s:
return Escape(s, true); return Escape(s, true);
case bool b: case Boolean b:
return b ? TrueLiteral : FalseLiteral; return b ? TrueLiteral : FalseLiteral;
case Type _: case Type _:
case Assembly _: case Assembly _:
case MethodInfo _: case MethodInfo _:
case PropertyInfo _: case PropertyInfo _:
case EventInfo _: case EventInfo _:
return Escape(obj.ToString(), true); return Escape(obj.ToString()!, true);
case DateTime d: case DateTime d:
return $"{StringQuotedChar}{d:s}{StringQuotedChar}"; return $"{StringQuotedChar}{d:s}{StringQuotedChar}";
default: default:
var targetType = obj.GetType(); Type targetType = obj.GetType();
if (!Definitions.BasicTypesInfo.Value.ContainsKey(targetType)) if(!Definitions.BasicTypesInfo.Value.ContainsKey(targetType)) {
return string.Empty; return String.Empty;
}
var escapedValue = Escape(Definitions.BasicTypesInfo.Value[targetType].ToStringInvariant(obj), false); String escapedValue = Escape(Definitions.BasicTypesInfo.Value[targetType].ToStringInvariant(obj), false);
return decimal.TryParse(escapedValue, out _) return Decimal.TryParse(escapedValue, out _) ? $"{escapedValue}" : $"{StringQuotedChar}{escapedValue}{StringQuotedChar}";
? $"{escapedValue}"
: $"{StringQuotedChar}{escapedValue}{StringQuotedChar}";
} }
} }
private static bool IsNonEmptyJsonArrayOrObject(string serialized) private static Boolean IsNonEmptyJsonArrayOrObject(String serialized) {
{ if(serialized == EmptyObjectLiteral || serialized == EmptyArrayLiteral) {
if (serialized == EmptyObjectLiteral || serialized == EmptyArrayLiteral) return false; return false;
}
// find the first position the character is not a space // find the first position the character is not a space
return serialized.Where(c => c != ' ').Select(c => c == OpenObjectChar || c == OpenArrayChar).FirstOrDefault(); return serialized.Where(c => c != ' ').Select(c => c == OpenObjectChar || c == OpenArrayChar).FirstOrDefault();
} }
private static string Escape(string str, bool quoted) private static String Escape(String str, Boolean quoted) {
{ if(str == null) {
if (str == null) return String.Empty;
return string.Empty; }
StringBuilder builder = new StringBuilder(str.Length * 2);
if(quoted) {
_ = builder.Append(StringQuotedChar);
}
var builder = new StringBuilder(str.Length * 2);
if (quoted) builder.Append(StringQuotedChar);
Escape(str, builder); Escape(str, builder);
if (quoted) builder.Append(StringQuotedChar); if(quoted) {
_ = builder.Append(StringQuotedChar);
}
return builder.ToString(); return builder.ToString();
} }
private static void Escape(string str, StringBuilder builder) private static void Escape(String str, StringBuilder builder) {
{ foreach(Char currentChar in str) {
foreach (var currentChar in str) switch(currentChar) {
{
switch (currentChar)
{
case '\\': case '\\':
case '"': case '"':
case '/': case '/':
builder _ = builder
.Append('\\') .Append('\\')
.Append(currentChar); .Append(currentChar);
break; break;
case '\b': case '\b':
builder.Append("\\b"); _ = builder.Append("\\b");
break; break;
case '\t': case '\t':
builder.Append("\\t"); _ = builder.Append("\\t");
break; break;
case '\n': case '\n':
builder.Append("\\n"); _ = builder.Append("\\n");
break; break;
case '\f': case '\f':
builder.Append("\\f"); _ = builder.Append("\\f");
break; break;
case '\r': case '\r':
builder.Append("\\r"); _ = builder.Append("\\r");
break; break;
default: default:
if (currentChar < ' ') if(currentChar < ' ') {
{ Byte[] escapeBytes = BitConverter.GetBytes((UInt16)currentChar);
var escapeBytes = BitConverter.GetBytes((ushort)currentChar); if(BitConverter.IsLittleEndian == false) {
if (BitConverter.IsLittleEndian == false)
Array.Reverse(escapeBytes); Array.Reverse(escapeBytes);
}
builder.Append("\\u") _ = builder.Append("\\u")
.Append(escapeBytes[1].ToString("X").PadLeft(2, '0')) .Append(escapeBytes[1].ToString("X").PadLeft(2, '0'))
.Append(escapeBytes[0].ToString("X").PadLeft(2, '0')); .Append(escapeBytes[0].ToString("X").PadLeft(2, '0'));
} } else {
else _ = builder.Append(currentChar);
{
builder.Append(currentChar);
} }
break; break;
@ -185,29 +179,20 @@ namespace Swan.Formatters
} }
} }
private Dictionary<string, object?> CreateDictionary( private Dictionary<String, Object?> CreateDictionary(Dictionary<String, MemberInfo> fields, String targetType, Object target) {
Dictionary<string, MemberInfo> fields,
string targetType,
object target)
{
// Create the dictionary and extract the properties // Create the dictionary and extract the properties
var objectDictionary = new Dictionary<string, object?>(); global::System.Collections.Generic.Dictionary<global::System.String, global::System.Object?> objectDictionary = new Dictionary<global::System.String, global::System.Object?>();
if (string.IsNullOrWhiteSpace(_options.TypeSpecifier) == false) if(String.IsNullOrWhiteSpace(this._options?.TypeSpecifier) == false) {
objectDictionary[_options.TypeSpecifier] = targetType; objectDictionary[this._options?.TypeSpecifier!] = targetType;
}
foreach (var field in fields) foreach(global::System.Collections.Generic.KeyValuePair<global::System.String, global::System.Reflection.MemberInfo> field in fields) {
{
// Build the dictionary using property names and values // Build the dictionary using property names and values
// Note: used to be: property.GetValue(target); but we would be reading private properties // Note: used to be: property.GetValue(target); but we would be reading private properties
try try {
{ objectDictionary[field.Key] = field.Value is PropertyInfo property ? property.GetCacheGetMethod((Boolean)(this._options?.IncludeNonPublic)!)?.Invoke(target) : (field.Value as FieldInfo)?.GetValue(target);
objectDictionary[field.Key] = field.Value is PropertyInfo property } catch {
? property.GetCacheGetMethod(_options.IncludeNonPublic)?.Invoke(target)
: (field.Value as FieldInfo)?.GetValue(target);
}
catch
{
/* ignored */ /* ignored */
} }
} }
@ -215,131 +200,129 @@ namespace Swan.Formatters
return objectDictionary; return objectDictionary;
} }
private string ResolveDictionary(IDictionary items, int depth) private String ResolveDictionary(IDictionary items, Int32 depth) {
{ this.Append(OpenObjectChar, depth);
Append(OpenObjectChar, depth); this.AppendLine();
AppendLine();
// Iterate through the elements and output recursively // Iterate through the elements and output recursively
var writeCount = 0; Int32 writeCount = 0;
foreach (var key in items.Keys) foreach(Object? key in items.Keys) {
{
// Serialize and append the key (first char indented) // Serialize and append the key (first char indented)
Append(StringQuotedChar, depth + 1); this.Append(StringQuotedChar, depth + 1);
Escape(key.ToString(), _builder); Escape(key?.ToString()!, this._builder!);
_builder _ = this._builder?.Append(StringQuotedChar).Append(ValueSeparatorChar).Append(" ");
.Append(StringQuotedChar)
.Append(ValueSeparatorChar)
.Append(" ");
// Serialize and append the value // Serialize and append the value
var serializedValue = Serialize(items[key], depth + 1, _options); String serializedValue = Serialize(items[key!], depth + 1, this._options!);
if (IsNonEmptyJsonArrayOrObject(serializedValue)) AppendLine(); if(IsNonEmptyJsonArrayOrObject(serializedValue)) {
Append(serializedValue, 0); this.AppendLine();
}
this.Append(serializedValue, 0);
// Add a comma and start a new line -- We will remove the last one when we are done writing the elements // Add a comma and start a new line -- We will remove the last one when we are done writing the elements
Append(FieldSeparatorChar, 0); this.Append(FieldSeparatorChar, 0);
AppendLine(); this.AppendLine();
writeCount++; writeCount++;
} }
// Output the end of the object and set the result // Output the end of the object and set the result
RemoveLastComma(); this.RemoveLastComma();
Append(CloseObjectChar, writeCount > 0 ? depth : 0); this.Append(CloseObjectChar, writeCount > 0 ? depth : 0);
return _builder.ToString(); return this._builder!.ToString();
} }
private string ResolveObject(object target, int depth) private String ResolveObject(Object target, Int32 depth) {
{ Type targetType = target.GetType();
var targetType = target.GetType();
if (targetType.IsEnum) if(targetType.IsEnum) {
return Convert.ToInt64(target, System.Globalization.CultureInfo.InvariantCulture).ToString(); return Convert.ToInt64(target, System.Globalization.CultureInfo.InvariantCulture).ToString();
}
var fields = _options.GetProperties(targetType); global::System.Collections.Generic.Dictionary<global::System.String, global::System.Reflection.MemberInfo> fields = this._options!.GetProperties(targetType);
if (fields.Count == 0 && string.IsNullOrWhiteSpace(_options.TypeSpecifier)) if(fields.Count == 0 && String.IsNullOrWhiteSpace(this._options.TypeSpecifier)) {
return EmptyObjectLiteral; return EmptyObjectLiteral;
}
// If we arrive here, then we convert the object into a // If we arrive here, then we convert the object into a
// dictionary of property names and values and call the serialization // dictionary of property names and values and call the serialization
// function again // function again
var objectDictionary = CreateDictionary(fields, targetType.ToString(), target); global::System.Collections.Generic.Dictionary<global::System.String, global::System.Object?> objectDictionary = this.CreateDictionary(fields, targetType.ToString(), target);
return Serialize(objectDictionary, depth, _options); return Serialize(objectDictionary, depth, this._options);
} }
private string ResolveEnumerable(IEnumerable target, int depth) private String ResolveEnumerable(IEnumerable target, Int32 depth) {
{
// Cast the items as a generic object array // Cast the items as a generic object array
var items = target.Cast<object>(); global::System.Collections.Generic.IEnumerable<global::System.Object> items = target.Cast<global::System.Object>();
Append(OpenArrayChar, depth); this.Append(OpenArrayChar, depth);
AppendLine(); this.AppendLine();
// Iterate through the elements and output recursively // Iterate through the elements and output recursively
var writeCount = 0; Int32 writeCount = 0;
foreach (var entry in items) foreach(Object entry in items) {
{ String serializedValue = Serialize(entry, depth + 1, this._options!);
var serializedValue = Serialize(entry, depth + 1, _options);
if (IsNonEmptyJsonArrayOrObject(serializedValue)) if(IsNonEmptyJsonArrayOrObject(serializedValue)) {
Append(serializedValue, 0); this.Append(serializedValue, 0);
else } else {
Append(serializedValue, depth + 1); this.Append(serializedValue, depth + 1);
}
Append(FieldSeparatorChar, 0); this.Append(FieldSeparatorChar, 0);
AppendLine(); this.AppendLine();
writeCount++; writeCount++;
} }
// Output the end of the array and set the result // Output the end of the array and set the result
RemoveLastComma(); this.RemoveLastComma();
Append(CloseArrayChar, writeCount > 0 ? depth : 0); this.Append(CloseArrayChar, writeCount > 0 ? depth : 0);
return _builder.ToString(); return this._builder!.ToString();
} }
private void SetIndent(int depth) private void SetIndent(Int32 depth) {
{ if(this._options!.Format == false || depth <= 0) {
if (_options.Format == false || depth <= 0) return; return;
}
_builder.Append(IndentStrings.GetOrAdd(depth, x => new string(' ', x * 4))); _ = this._builder!.Append(IndentStrings.GetOrAdd(depth, x => new String(' ', x * 4)));
} }
/// <summary> /// <summary>
/// Removes the last comma in the current string builder. /// Removes the last comma in the current string builder.
/// </summary> /// </summary>
private void RemoveLastComma() private void RemoveLastComma() {
{ if(this._builder!.Length < this._lastCommaSearch!.Length) {
if (_builder.Length < _lastCommaSearch.Length)
return; return;
}
if (_lastCommaSearch.Where((t, i) => _builder[_builder.Length - _lastCommaSearch.Length + i] != t).Any()) if(this._lastCommaSearch.Where((t, i) => this._builder[this._builder.Length - this._lastCommaSearch.Length + i] != t).Any()) {
{
return; return;
} }
// If we got this far, we simply remove the comma character // If we got this far, we simply remove the comma character
_builder.Remove(_builder.Length - _lastCommaSearch.Length, 1); _ = this._builder.Remove(this._builder.Length - this._lastCommaSearch.Length, 1);
} }
private void Append(string text, int depth) private void Append(String text, Int32 depth) {
{ this.SetIndent(depth);
SetIndent(depth); _ = this._builder!.Append(text);
_builder.Append(text);
} }
private void Append(char text, int depth) private void Append(Char text, Int32 depth) {
{ this.SetIndent(depth);
SetIndent(depth); _ = this._builder!.Append(text);
_builder.Append(text);
} }
private void AppendLine() private void AppendLine() {
{ if(this._options!.Format == false) {
if (_options.Format == false) return; return;
_builder.Append(Environment.NewLine); }
_ = this._builder!.Append(Environment.NewLine);
} }
#endregion #endregion

View File

@ -1,12 +1,12 @@
using System; #nullable enable
using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using Swan.Reflection; using Swan.Reflection;
namespace Swan.Formatters namespace Swan.Formatters {
{
/// <summary> /// <summary>
/// A very simple, light-weight JSON library written by Mario /// A very simple, light-weight JSON library written by Mario
/// to teach Geo how things are done /// to teach Geo how things are done
@ -14,14 +14,13 @@ namespace Swan.Formatters
/// This is an useful helper for small tasks but it doesn't represent a full-featured /// This is an useful helper for small tasks but it doesn't represent a full-featured
/// serializer such as the beloved Json.NET. /// serializer such as the beloved Json.NET.
/// </summary> /// </summary>
public class SerializerOptions public class SerializerOptions {
{ private static readonly ConcurrentDictionary<Type, Dictionary<Tuple<String, String>, MemberInfo>>
private static readonly ConcurrentDictionary<Type, Dictionary<Tuple<string, string>, MemberInfo>> TypeCache = new ConcurrentDictionary<Type, Dictionary<Tuple<String, String>, MemberInfo>>();
TypeCache = new ConcurrentDictionary<Type, Dictionary<Tuple<string, string>, MemberInfo>>();
private readonly string[]? _includeProperties; private readonly String[]? _includeProperties;
private readonly string[]? _excludeProperties; private readonly String[]? _excludeProperties;
private readonly Dictionary<int, List<WeakReference>> _parentReferences = new Dictionary<int, List<WeakReference>>(); private readonly Dictionary<Int32, List<WeakReference>> _parentReferences = new Dictionary<Int32, List<WeakReference>>();
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="SerializerOptions"/> class. /// Initializes a new instance of the <see cref="SerializerOptions"/> class.
@ -33,29 +32,21 @@ namespace Swan.Formatters
/// <param name="includeNonPublic">if set to <c>true</c> [include non public].</param> /// <param name="includeNonPublic">if set to <c>true</c> [include non public].</param>
/// <param name="parentReferences">The parent references.</param> /// <param name="parentReferences">The parent references.</param>
/// <param name="jsonSerializerCase">The json serializer case.</param> /// <param name="jsonSerializerCase">The json serializer case.</param>
public SerializerOptions( public SerializerOptions(Boolean format, String? typeSpecifier, String[]? includeProperties, String[]? excludeProperties = null, Boolean includeNonPublic = true, IReadOnlyCollection<WeakReference>? parentReferences = null, JsonSerializerCase jsonSerializerCase = JsonSerializerCase.None) {
bool format, this._includeProperties = includeProperties;
string? typeSpecifier, this._excludeProperties = excludeProperties;
string[]? includeProperties,
string[]? excludeProperties = null,
bool includeNonPublic = true,
IReadOnlyCollection<WeakReference>? parentReferences = null,
JsonSerializerCase jsonSerializerCase = JsonSerializerCase.None)
{
_includeProperties = includeProperties;
_excludeProperties = excludeProperties;
IncludeNonPublic = includeNonPublic; this.IncludeNonPublic = includeNonPublic;
Format = format; this.Format = format;
TypeSpecifier = typeSpecifier; this.TypeSpecifier = typeSpecifier;
JsonSerializerCase = jsonSerializerCase; this.JsonSerializerCase = jsonSerializerCase;
if (parentReferences == null) if(parentReferences == null) {
return; return;
}
foreach (var parentReference in parentReferences.Where(x => x.IsAlive)) foreach(WeakReference parentReference in parentReferences.Where(x => x.IsAlive)) {
{ _ = this.IsObjectPresent(parentReference.Target);
IsObjectPresent(parentReference.Target);
} }
} }
@ -65,7 +56,9 @@ namespace Swan.Formatters
/// <value> /// <value>
/// <c>true</c> if format; otherwise, <c>false</c>. /// <c>true</c> if format; otherwise, <c>false</c>.
/// </value> /// </value>
public bool Format { get; } public Boolean Format {
get;
}
/// <summary> /// <summary>
/// Gets the type specifier. /// Gets the type specifier.
@ -73,7 +66,9 @@ namespace Swan.Formatters
/// <value> /// <value>
/// The type specifier. /// The type specifier.
/// </value> /// </value>
public string? TypeSpecifier { get; } public String? TypeSpecifier {
get;
}
/// <summary> /// <summary>
/// Gets a value indicating whether [include non public]. /// Gets a value indicating whether [include non public].
@ -81,7 +76,9 @@ namespace Swan.Formatters
/// <value> /// <value>
/// <c>true</c> if [include non public]; otherwise, <c>false</c>. /// <c>true</c> if [include non public]; otherwise, <c>false</c>.
/// </value> /// </value>
public bool IncludeNonPublic { get; } public Boolean IncludeNonPublic {
get;
}
/// <summary> /// <summary>
/// Gets the json serializer case. /// Gets the json serializer case.
@ -89,52 +86,44 @@ namespace Swan.Formatters
/// <value> /// <value>
/// The json serializer case. /// The json serializer case.
/// </value> /// </value>
public JsonSerializerCase JsonSerializerCase { get; } public JsonSerializerCase JsonSerializerCase {
get;
}
internal bool IsObjectPresent(object target) internal Boolean IsObjectPresent(Object? target) {
{ if(target == null) {
var hashCode = target.GetHashCode(); return false;
}
Int32 hashCode = target.GetHashCode();
if (_parentReferences.ContainsKey(hashCode)) if(this._parentReferences.ContainsKey(hashCode)) {
{ if(this._parentReferences[hashCode].Any(p => ReferenceEquals(p.Target, target))) {
if (_parentReferences[hashCode].Any(p => ReferenceEquals(p.Target, target)))
return true; return true;
}
_parentReferences[hashCode].Add(new WeakReference(target)); this._parentReferences[hashCode].Add(new WeakReference(target));
return false; return false;
} }
_parentReferences.Add(hashCode, new List<WeakReference> { new WeakReference(target) }); this._parentReferences.Add(hashCode, new List<WeakReference> { new WeakReference(target) });
return false; return false;
} }
internal Dictionary<string, MemberInfo> GetProperties(Type targetType) internal Dictionary<String, MemberInfo> GetProperties(Type targetType) => this.GetPropertiesCache(targetType).When(() => this._includeProperties?.Length > 0, query => query.Where(p => this._includeProperties.Contains(p.Key.Item1))).When(() => this._excludeProperties?.Length > 0, query => query.Where(p => !this._excludeProperties.Contains(p.Key.Item1))).ToDictionary(x => x.Key.Item2, x => x.Value);
=> GetPropertiesCache(targetType)
.When(() => _includeProperties?.Length > 0,
query => query.Where(p => _includeProperties.Contains(p.Key.Item1)))
.When(() => _excludeProperties?.Length > 0,
query => query.Where(p => !_excludeProperties.Contains(p.Key.Item1)))
.ToDictionary(x => x.Key.Item2, x => x.Value);
private Dictionary<Tuple<string, string>, MemberInfo> GetPropertiesCache(Type targetType) private Dictionary<Tuple<String, String>, MemberInfo> GetPropertiesCache(Type targetType) {
{ if(TypeCache.TryGetValue(targetType, out Dictionary<Tuple<String, String>, MemberInfo>? current)) {
if (TypeCache.TryGetValue(targetType, out var current))
return current; return current;
}
var fields = List<MemberInfo> fields = new List<MemberInfo>(PropertyTypeCache.DefaultCache.Value.RetrieveAllProperties(targetType).Where(p => p.CanRead));
new List<MemberInfo>(PropertyTypeCache.DefaultCache.Value.RetrieveAllProperties(targetType).Where(p => p.CanRead));
// If the target is a struct (value type) navigate the fields. // If the target is a struct (value type) navigate the fields.
if (targetType.IsValueType) if(targetType.IsValueType) {
{
fields.AddRange(FieldTypeCache.DefaultCache.Value.RetrieveAllFields(targetType)); fields.AddRange(FieldTypeCache.DefaultCache.Value.RetrieveAllFields(targetType));
} }
var value = fields Dictionary<Tuple<String, String>, MemberInfo> value = fields.ToDictionary(x => Tuple.Create(x.Name, x.GetCustomAttribute<JsonPropertyAttribute>()?.PropertyName ?? x.Name.GetNameWithCase(this.JsonSerializerCase)), x => x);
.ToDictionary(
x => Tuple.Create(x.Name,
x.GetCustomAttribute<JsonPropertyAttribute>()?.PropertyName ?? x.Name.GetNameWithCase(JsonSerializerCase)),
x => x);
TypeCache.TryAdd(targetType, value); TypeCache.TryAdd(targetType, value);

View File

@ -1,11 +1,11 @@
using System; #nullable enable
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Swan.Collections; using Swan.Collections;
using Swan.Reflection; using Swan.Reflection;
namespace Swan.Formatters namespace Swan.Formatters {
{
/// <summary> /// <summary>
/// A very simple, light-weight JSON library written by Mario /// A very simple, light-weight JSON library written by Mario
/// to teach Geo how things are done /// to teach Geo how things are done
@ -13,33 +13,32 @@ namespace Swan.Formatters
/// This is an useful helper for small tasks but it doesn't represent a full-featured /// This is an useful helper for small tasks but it doesn't represent a full-featured
/// serializer such as the beloved Json.NET. /// serializer such as the beloved Json.NET.
/// </summary> /// </summary>
public static partial class Json public static partial class Json {
{
#region Constants #region Constants
internal const string AddMethodName = "Add"; internal const String AddMethodName = "Add";
private const char OpenObjectChar = '{'; private const Char OpenObjectChar = '{';
private const char CloseObjectChar = '}'; private const Char CloseObjectChar = '}';
private const char OpenArrayChar = '['; private const Char OpenArrayChar = '[';
private const char CloseArrayChar = ']'; private const Char CloseArrayChar = ']';
private const char FieldSeparatorChar = ','; private const Char FieldSeparatorChar = ',';
private const char ValueSeparatorChar = ':'; private const Char ValueSeparatorChar = ':';
private const char StringEscapeChar = '\\'; private const Char StringEscapeChar = '\\';
private const char StringQuotedChar = '"'; private const Char StringQuotedChar = '"';
private const string EmptyObjectLiteral = "{ }"; private const String EmptyObjectLiteral = "{ }";
private const string EmptyArrayLiteral = "[ ]"; private const String EmptyArrayLiteral = "[ ]";
private const string TrueLiteral = "true"; private const String TrueLiteral = "true";
private const string FalseLiteral = "false"; private const String FalseLiteral = "false";
private const string NullLiteral = "null"; private const String NullLiteral = "null";
#endregion #endregion
private static readonly CollectionCacheRepository<string> IgnoredPropertiesCache = new CollectionCacheRepository<string>(); private static readonly CollectionCacheRepository<String> IgnoredPropertiesCache = new CollectionCacheRepository<global::System.String>();
#region Public API #region Public API
@ -98,16 +97,7 @@ namespace Swan.Formatters
/// } /// }
/// </code> /// </code>
/// </example> /// </example>
public static string Serialize( public static String Serialize(Object? obj, Boolean format = false, String? typeSpecifier = null, Boolean includeNonPublic = false, String[]? includedNames = null, params String[] excludedNames) => Serialize(obj, format, typeSpecifier, includeNonPublic, includedNames, excludedNames, null, JsonSerializerCase.None);
object? obj,
bool format = false,
string? typeSpecifier = null,
bool includeNonPublic = false,
string[]? includedNames = null,
params string[] excludedNames)
{
return Serialize(obj, format, typeSpecifier, includeNonPublic, includedNames, excludedNames, null, JsonSerializerCase.None);
}
/// <summary> /// <summary>
/// Serializes the specified object into a JSON string. /// Serializes the specified object into a JSON string.
@ -119,11 +109,7 @@ namespace Swan.Formatters
/// <returns> /// <returns>
/// A <see cref="System.String" /> that represents the current object. /// A <see cref="System.String" /> that represents the current object.
/// </returns> /// </returns>
public static string Serialize( public static String Serialize(Object? obj, JsonSerializerCase jsonSerializerCase, Boolean format = false, String? typeSpecifier = null) => Serialize(obj, format, typeSpecifier, false, null, null, null, jsonSerializerCase);
object? obj,
JsonSerializerCase jsonSerializerCase,
bool format = false,
string? typeSpecifier = null) => Serialize(obj, format, typeSpecifier, false, null, null, null, jsonSerializerCase);
/// <summary> /// <summary>
/// Serializes the specified object into a JSON string. /// Serializes the specified object into a JSON string.
@ -139,29 +125,12 @@ namespace Swan.Formatters
/// <returns> /// <returns>
/// A <see cref="System.String" /> that represents the current object. /// A <see cref="System.String" /> that represents the current object.
/// </returns> /// </returns>
public static string Serialize( public static String Serialize(Object? obj, Boolean format, String? typeSpecifier, Boolean includeNonPublic, String[]? includedNames, String[]? excludedNames, List<WeakReference>? parentReferences, JsonSerializerCase jsonSerializerCase) {
object? obj, if(obj != null && (obj is String || Definitions.AllBasicValueTypes.Contains(obj.GetType()))) {
bool format,
string? typeSpecifier,
bool includeNonPublic,
string[]? includedNames,
string[]? excludedNames,
List<WeakReference>? parentReferences,
JsonSerializerCase jsonSerializerCase)
{
if (obj != null && (obj is string || Definitions.AllBasicValueTypes.Contains(obj.GetType())))
{
return SerializePrimitiveValue(obj); return SerializePrimitiveValue(obj);
} }
var options = new SerializerOptions( SerializerOptions options = new SerializerOptions(format, typeSpecifier, includedNames, GetExcludedNames(obj?.GetType(), excludedNames), includeNonPublic, parentReferences, jsonSerializerCase);
format,
typeSpecifier,
includedNames,
GetExcludedNames(obj?.GetType(), excludedNames),
includeNonPublic,
parentReferences,
jsonSerializerCase);
return Serialize(obj, options); return Serialize(obj, options);
} }
@ -172,9 +141,9 @@ namespace Swan.Formatters
/// <param name="obj">The object.</param> /// <param name="obj">The object.</param>
/// <param name="options">The options.</param> /// <param name="options">The options.</param>
/// <returns> /// <returns>
/// A <see cref="string" /> that represents the current object. /// A <see cref="String" /> that represents the current object.
/// </returns> /// </returns>
public static string Serialize(object? obj, SerializerOptions options) => Serializer.Serialize(obj, 0, options); public static String Serialize(Object? obj, SerializerOptions options) => Serializer.Serialize(obj, 0, options);
/// <summary> /// <summary>
/// Serializes the specified object only including the specified property names. /// Serializes the specified object only including the specified property names.
@ -182,7 +151,7 @@ namespace Swan.Formatters
/// <param name="obj">The object.</param> /// <param name="obj">The object.</param>
/// <param name="format">if set to <c>true</c> it formats and indents the output.</param> /// <param name="format">if set to <c>true</c> it formats and indents the output.</param>
/// <param name="includeNames">The include names.</param> /// <param name="includeNames">The include names.</param>
/// <returns>A <see cref="string" /> that represents the current object.</returns> /// <returns>A <see cref="String" /> that represents the current object.</returns>
/// <example> /// <example>
/// The following example shows how to serialize a simple object including the specified properties. /// The following example shows how to serialize a simple object including the specified properties.
/// <code> /// <code>
@ -205,8 +174,7 @@ namespace Swan.Formatters
/// } /// }
/// </code> /// </code>
/// </example> /// </example>
public static string SerializeOnly(object? obj, bool format, params string[] includeNames) public static String SerializeOnly(Object? obj, Boolean format, params String[] includeNames) => Serialize(obj, new SerializerOptions(format, null, includeNames));
=> Serialize(obj, new SerializerOptions(format, null, includeNames));
/// <summary> /// <summary>
/// Serializes the specified object excluding the specified property names. /// Serializes the specified object excluding the specified property names.
@ -214,7 +182,7 @@ namespace Swan.Formatters
/// <param name="obj">The object.</param> /// <param name="obj">The object.</param>
/// <param name="format">if set to <c>true</c> it formats and indents the output.</param> /// <param name="format">if set to <c>true</c> it formats and indents the output.</param>
/// <param name="excludeNames">The exclude names.</param> /// <param name="excludeNames">The exclude names.</param>
/// <returns>A <see cref="string" /> that represents the current object.</returns> /// <returns>A <see cref="String" /> that represents the current object.</returns>
/// <example> /// <example>
/// The following code shows how to serialize a simple object excluding the specified properties. /// The following code shows how to serialize a simple object excluding the specified properties.
/// <code> /// <code>
@ -237,8 +205,7 @@ namespace Swan.Formatters
/// } /// }
/// </code> /// </code>
/// </example> /// </example>
public static string SerializeExcluding(object? obj, bool format, params string[] excludeNames) public static String SerializeExcluding(Object? obj, Boolean format, params String[] excludeNames) => Serialize(obj, new SerializerOptions(format, null, null, excludeNames));
=> Serialize(obj, new SerializerOptions(format, null, null, excludeNames));
/// <summary> /// <summary>
/// Deserializes the specified json string as either a Dictionary[string, object] or as a List[object] /// Deserializes the specified json string as either a Dictionary[string, object] or as a List[object]
@ -264,10 +231,7 @@ namespace Swan.Formatters
/// } /// }
/// } /// }
/// </code></example> /// </code></example>
public static object? Deserialize( public static Object? Deserialize(String? json, JsonSerializerCase jsonSerializerCase) => Converter.FromJsonResult(Deserializer.DeserializeInternal(json), jsonSerializerCase);
string json,
JsonSerializerCase jsonSerializerCase)
=> Converter.FromJsonResult(Deserializer.DeserializeInternal(json), jsonSerializerCase);
/// <summary> /// <summary>
/// Deserializes the specified json string as either a Dictionary[string, object] or as a List[object] /// Deserializes the specified json string as either a Dictionary[string, object] or as a List[object]
@ -292,8 +256,7 @@ namespace Swan.Formatters
/// } /// }
/// } /// }
/// </code></example> /// </code></example>
public static object? Deserialize(string json) public static Object? Deserialize(String? json) => Deserialize(json, JsonSerializerCase.None);
=> Deserialize(json, JsonSerializerCase.None);
/// <summary> /// <summary>
/// Deserializes the specified JSON string and converts it to the specified object type. /// Deserializes the specified JSON string and converts it to the specified object type.
@ -320,8 +283,7 @@ namespace Swan.Formatters
/// } /// }
/// } /// }
/// </code></example> /// </code></example>
public static T Deserialize<T>(string json, JsonSerializerCase jsonSerializerCase = JsonSerializerCase.None) public static T Deserialize<T>(String json, JsonSerializerCase jsonSerializerCase = JsonSerializerCase.None) where T : notnull => (T)Deserialize(json, typeof(T), jsonSerializerCase: jsonSerializerCase)!;
=> (T)Deserialize(json, typeof(T), jsonSerializerCase: jsonSerializerCase);
/// <summary> /// <summary>
/// Deserializes the specified JSON string and converts it to the specified object type. /// Deserializes the specified JSON string and converts it to the specified object type.
@ -330,7 +292,7 @@ namespace Swan.Formatters
/// <param name="json">The JSON string.</param> /// <param name="json">The JSON string.</param>
/// <param name="includeNonPublic">if set to true, it also uses the non-public constructors and property setters.</param> /// <param name="includeNonPublic">if set to true, it also uses the non-public constructors and property setters.</param>
/// <returns>The deserialized specified type object.</returns> /// <returns>The deserialized specified type object.</returns>
public static T Deserialize<T>(string json, bool includeNonPublic) => (T)Deserialize(json, typeof(T), includeNonPublic); public static T Deserialize<T>(String json, Boolean includeNonPublic) where T : notnull => (T)Deserialize(json, typeof(T), includeNonPublic)!;
/// <summary> /// <summary>
/// Deserializes the specified JSON string and converts it to the specified object type. /// Deserializes the specified JSON string and converts it to the specified object type.
@ -342,36 +304,35 @@ namespace Swan.Formatters
/// <returns> /// <returns>
/// Type of the current conversion from json result. /// Type of the current conversion from json result.
/// </returns> /// </returns>
public static object? Deserialize(string json, Type resultType, bool includeNonPublic = false, JsonSerializerCase jsonSerializerCase = JsonSerializerCase.None) public static Object? Deserialize(String json, Type resultType, Boolean includeNonPublic = false, JsonSerializerCase jsonSerializerCase = JsonSerializerCase.None) => Converter.FromJsonResult(Deserializer.DeserializeInternal(json), jsonSerializerCase, resultType, includeNonPublic);
=> Converter.FromJsonResult(Deserializer.DeserializeInternal(json), jsonSerializerCase, resultType, includeNonPublic);
#endregion #endregion
#region Private API #region Private API
private static string[]? GetExcludedNames(Type? type, string[]? excludedNames) private static String[]? GetExcludedNames(Type? type, String[]? excludedNames) {
{ if(type == null) {
if (type == null)
return excludedNames; return excludedNames;
}
var excludedByAttr = IgnoredPropertiesCache.Retrieve(type, t => t.GetProperties() global::System.Collections.Generic.IEnumerable<global::System.String> excludedByAttr = IgnoredPropertiesCache.Retrieve(type, t => t.GetProperties()
.Where(x => AttributeCache.DefaultCache.Value.RetrieveOne<JsonPropertyAttribute>(x)?.Ignored == true) .Where(x => AttributeCache.DefaultCache.Value.RetrieveOne<JsonPropertyAttribute>(x)?.Ignored == true)
.Select(x => x.Name)); .Select(x => x.Name));
if (excludedByAttr?.Any() != true) if(excludedByAttr?.Any() != true) {
return excludedNames; return excludedNames;
}
return excludedNames?.Any(string.IsNullOrWhiteSpace) == true return excludedNames?.Any(String.IsNullOrWhiteSpace) == true
? excludedByAttr.Intersect(excludedNames.Where(y => !string.IsNullOrWhiteSpace(y))).ToArray() ? excludedByAttr.Intersect(excludedNames.Where(y => !String.IsNullOrWhiteSpace(y))).ToArray()
: excludedByAttr.ToArray(); : excludedByAttr.ToArray();
} }
private static string SerializePrimitiveValue(object obj) => private static String SerializePrimitiveValue(Object obj) => obj switch
obj switch
{ {
string stringValue => stringValue, String stringValue => stringValue,
bool boolValue => boolValue ? TrueLiteral : FalseLiteral, Boolean boolValue => boolValue ? TrueLiteral : FalseLiteral,
_ => obj.ToString() _ => obj.ToString()!
}; };
#endregion #endregion

View File

@ -1,23 +1,20 @@
using System; using System;
namespace Swan.Formatters namespace Swan.Formatters {
{
/// <summary> /// <summary>
/// An attribute used to help setup a property behavior when serialize/deserialize JSON. /// An attribute used to help setup a property behavior when serialize/deserialize JSON.
/// </summary> /// </summary>
/// <seealso cref="Attribute" /> /// <seealso cref="Attribute" />
[AttributeUsage(AttributeTargets.Property)] [AttributeUsage(AttributeTargets.Property)]
public sealed class JsonPropertyAttribute : Attribute public sealed class JsonPropertyAttribute : Attribute {
{
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="JsonPropertyAttribute" /> class. /// Initializes a new instance of the <see cref="JsonPropertyAttribute" /> class.
/// </summary> /// </summary>
/// <param name="propertyName">Name of the property.</param> /// <param name="propertyName">Name of the property.</param>
/// <param name="ignored">if set to <c>true</c> [ignored].</param> /// <param name="ignored">if set to <c>true</c> [ignored].</param>
public JsonPropertyAttribute(string propertyName, bool ignored = false) public JsonPropertyAttribute(String propertyName, Boolean ignored = false) {
{ this.PropertyName = propertyName ?? throw new ArgumentNullException(nameof(propertyName));
PropertyName = propertyName ?? throw new ArgumentNullException(nameof(propertyName)); this.Ignored = ignored;
Ignored = ignored;
} }
/// <summary> /// <summary>
@ -26,7 +23,9 @@ namespace Swan.Formatters
/// <value> /// <value>
/// The name of the property. /// The name of the property.
/// </value> /// </value>
public string PropertyName { get; } public String PropertyName {
get;
}
/// <summary> /// <summary>
/// Gets or sets a value indicating whether this <see cref="JsonPropertyAttribute" /> is ignored. /// Gets or sets a value indicating whether this <see cref="JsonPropertyAttribute" /> is ignored.
@ -34,6 +33,8 @@ namespace Swan.Formatters
/// <value> /// <value>
/// <c>true</c> if ignored; otherwise, <c>false</c>. /// <c>true</c> if ignored; otherwise, <c>false</c>.
/// </value> /// </value>
public bool Ignored { get; } public Boolean Ignored {
get;
}
} }
} }

View File

@ -1,31 +1,25 @@
using System; #nullable enable
using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.ComponentModel; using System.ComponentModel;
using System.Linq.Expressions; using System.Linq.Expressions;
using System.Reflection; using System.Reflection;
namespace Swan namespace Swan {
{
/// <summary> /// <summary>
/// Provides a standard way to convert strings to different types. /// Provides a standard way to convert strings to different types.
/// </summary> /// </summary>
public static class FromString public static class FromString {
{
// It doesn't matter which converter we get here: ConvertFromInvariantString is not virtual. // It doesn't matter which converter we get here: ConvertFromInvariantString is not virtual.
private static readonly MethodInfo ConvertFromInvariantStringMethod private static readonly MethodInfo ConvertFromInvariantStringMethod = new Func<String, Object>(TypeDescriptor.GetConverter(typeof(Int32)).ConvertFromInvariantString).Method;
= new Func<string, object>(TypeDescriptor.GetConverter(typeof(int)).ConvertFromInvariantString).Method;
private static readonly MethodInfo TryConvertToInternalMethod private static readonly MethodInfo? TryConvertToInternalMethod = typeof(FromString).GetMethod(nameof(TryConvertToInternal), BindingFlags.Static | BindingFlags.NonPublic);
= typeof(FromString).GetMethod(nameof(TryConvertToInternal), BindingFlags.Static | BindingFlags.NonPublic);
private static readonly MethodInfo ConvertToInternalMethod private static readonly MethodInfo? ConvertToInternalMethod = typeof(FromString).GetMethod(nameof(ConvertToInternal), BindingFlags.Static | BindingFlags.NonPublic);
= typeof(FromString).GetMethod(nameof(ConvertToInternal), BindingFlags.Static | BindingFlags.NonPublic);
private static readonly ConcurrentDictionary<Type, Func<string[], (bool Success, object Result)>> GenericTryConvertToMethods private static readonly ConcurrentDictionary<Type, Func<String[], (Boolean Success, Object Result)>> GenericTryConvertToMethods = new ConcurrentDictionary<Type, Func<String[], (Boolean Success, Object Result)>>();
= new ConcurrentDictionary<Type, Func<string[], (bool Success, object Result)>>();
private static readonly ConcurrentDictionary<Type, Func<string[], object>> GenericConvertToMethods private static readonly ConcurrentDictionary<Type, Func<String[], Object>> GenericConvertToMethods = new ConcurrentDictionary<Type, Func<String[], Object>>();
= new ConcurrentDictionary<Type, Func<string[], object>>();
/// <summary> /// <summary>
/// Determines whether a string can be converted to the specified type. /// Determines whether a string can be converted to the specified type.
@ -34,8 +28,7 @@ namespace Swan
/// <returns><see langword="true" /> if the conversion is possible; /// <returns><see langword="true" /> if the conversion is possible;
/// otherwise, <see langword="false" />.</returns> /// otherwise, <see langword="false" />.</returns>
/// <exception cref="ArgumentNullException"><paramref name="type" /> is <see langword="null" />.</exception> /// <exception cref="ArgumentNullException"><paramref name="type" /> is <see langword="null" />.</exception>
public static bool CanConvertTo(Type type) public static Boolean CanConvertTo(Type type) => TypeDescriptor.GetConverter(type).CanConvertFrom(typeof(String));
=> TypeDescriptor.GetConverter(type).CanConvertFrom(typeof(string));
/// <summary> /// <summary>
/// Determines whether a string can be converted to the specified type. /// Determines whether a string can be converted to the specified type.
@ -43,8 +36,7 @@ namespace Swan
/// <typeparam name="TResult">The type resulting from the conversion.</typeparam> /// <typeparam name="TResult">The type resulting from the conversion.</typeparam>
/// <returns><see langword="true" /> if the conversion is possible; /// <returns><see langword="true" /> if the conversion is possible;
/// otherwise, <see langword="false" />.</returns> /// otherwise, <see langword="false" />.</returns>
public static bool CanConvertTo<TResult>() public static Boolean CanConvertTo<TResult>() => TypeDescriptor.GetConverter(typeof(TResult)).CanConvertFrom(typeof(String));
=> TypeDescriptor.GetConverter(typeof(TResult)).CanConvertFrom(typeof(string));
/// <summary> /// <summary>
/// Attempts to convert a string to the specified type. /// Attempts to convert a string to the specified type.
@ -56,22 +48,17 @@ namespace Swan
/// <returns><see langword="true" /> if the conversion is successful; /// <returns><see langword="true" /> if the conversion is successful;
/// otherwise, <see langword="false" />.</returns> /// otherwise, <see langword="false" />.</returns>
/// <exception cref="ArgumentNullException"><paramref name="type" /> is <see langword="null" />.</exception> /// <exception cref="ArgumentNullException"><paramref name="type" /> is <see langword="null" />.</exception>
public static bool TryConvertTo(Type type, string str, out object? result) public static Boolean TryConvertTo(Type type, String str, out Object? result) {
{ TypeConverter converter = TypeDescriptor.GetConverter(type);
var converter = TypeDescriptor.GetConverter(type); if(!converter.CanConvertFrom(typeof(String))) {
if (!converter.CanConvertFrom(typeof(string)))
{
result = null; result = null;
return false; return false;
} }
try try {
{
result = converter.ConvertFromInvariantString(str); result = converter.ConvertFromInvariantString(str);
return true; return true;
} } catch(Exception e) when(!e.IsCriticalException()) {
catch (Exception e) when (!e.IsCriticalException())
{
result = null; result = null;
return false; return false;
} }
@ -86,23 +73,18 @@ namespace Swan
/// the result of the conversion. This parameter is passed uninitialized.</param> /// the result of the conversion. This parameter is passed uninitialized.</param>
/// <returns><see langword="true" /> if the conversion is successful; /// <returns><see langword="true" /> if the conversion is successful;
/// otherwise, <see langword="false" />.</returns> /// otherwise, <see langword="false" />.</returns>
public static bool TryConvertTo<TResult>(string str, out TResult result) public static Boolean TryConvertTo<TResult>(String str, out TResult result) where TResult : notnull {
{ TypeConverter converter = TypeDescriptor.GetConverter(typeof(TResult));
var converter = TypeDescriptor.GetConverter(typeof(TResult)); if(!converter.CanConvertFrom(typeof(String))) {
if (!converter.CanConvertFrom(typeof(string))) result = default!;
{
result = default;
return false; return false;
} }
try try {
{
result = (TResult)converter.ConvertFromInvariantString(str); result = (TResult)converter.ConvertFromInvariantString(str);
return true; return true;
} } catch(Exception e) when(!e.IsCriticalException()) {
catch (Exception e) when (!e.IsCriticalException()) result = default!;
{
result = default;
return false; return false;
} }
} }
@ -115,17 +97,14 @@ namespace Swan
/// <returns>An instance of <paramref name="type" />.</returns> /// <returns>An instance of <paramref name="type" />.</returns>
/// <exception cref="ArgumentNullException"><paramref name="type" /> is <see langword="null" />.</exception> /// <exception cref="ArgumentNullException"><paramref name="type" /> is <see langword="null" />.</exception>
/// <exception cref="StringConversionException">The conversion was not successful.</exception> /// <exception cref="StringConversionException">The conversion was not successful.</exception>
public static object ConvertTo(Type type, string str) public static Object ConvertTo(Type type, String str) {
{ if(type == null) {
if (type == null)
throw new ArgumentNullException(nameof(type)); throw new ArgumentNullException(nameof(type));
try
{
return TypeDescriptor.GetConverter(type).ConvertFromInvariantString(str);
} }
catch (Exception e) when (!e.IsCriticalException())
{ try {
return TypeDescriptor.GetConverter(type).ConvertFromInvariantString(str);
} catch(Exception e) when(!e.IsCriticalException()) {
throw new StringConversionException(type, e); throw new StringConversionException(type, e);
} }
} }
@ -139,14 +118,10 @@ namespace Swan
/// <exception cref="StringConversionException"> /// <exception cref="StringConversionException">
/// The conversion was not successful. /// The conversion was not successful.
/// </exception> /// </exception>
public static TResult ConvertTo<TResult>(string str) public static TResult ConvertTo<TResult>(String str) {
{ try {
try
{
return (TResult)TypeDescriptor.GetConverter(typeof(TResult)).ConvertFromInvariantString(str); return (TResult)TypeDescriptor.GetConverter(typeof(TResult)).ConvertFromInvariantString(str);
} } catch(Exception e) when(!e.IsCriticalException()) {
catch (Exception e) when (!e.IsCriticalException())
{
throw new StringConversionException(typeof(TResult), e); throw new StringConversionException(typeof(TResult), e);
} }
} }
@ -162,16 +137,14 @@ namespace Swan
/// <returns><see langword="true" /> if the conversion is successful; /// <returns><see langword="true" /> if the conversion is successful;
/// otherwise, <see langword="false" />.</returns> /// otherwise, <see langword="false" />.</returns>
/// <exception cref="ArgumentNullException"><paramref name="type" /> is <see langword="null" />.</exception> /// <exception cref="ArgumentNullException"><paramref name="type" /> is <see langword="null" />.</exception>
public static bool TryConvertTo(Type type, string[] strings, out object? result) public static Boolean TryConvertTo(Type type, String[] strings, out Object? result) {
{ if(strings == null) {
if (strings == null)
{
result = null; result = null;
return false; return false;
} }
var method = GenericTryConvertToMethods.GetOrAdd(type, BuildNonGenericTryConvertLambda); Func<String[], (Boolean Success, Object Result)> method = GenericTryConvertToMethods.GetOrAdd(type, BuildNonGenericTryConvertLambda);
var (success, methodResult) = method(strings); (Boolean success, Object methodResult) = method(strings);
result = methodResult; result = methodResult;
return success; return success;
} }
@ -186,32 +159,27 @@ namespace Swan
/// the result of the conversion. This parameter is passed uninitialized.</param> /// the result of the conversion. This parameter is passed uninitialized.</param>
/// <returns><see langword="true" /> if the conversion is successful; /// <returns><see langword="true" /> if the conversion is successful;
/// otherwise, <see langword="false" />.</returns> /// otherwise, <see langword="false" />.</returns>
public static bool TryConvertTo<TResult>(string[] strings, out TResult[]? result) public static Boolean TryConvertTo<TResult>(String[] strings, out TResult[]? result) {
{ if(strings == null) {
if (strings == null)
{
result = null; result = null;
return false; return false;
} }
var converter = TypeDescriptor.GetConverter(typeof(TResult)); TypeConverter converter = TypeDescriptor.GetConverter(typeof(TResult));
if (!converter.CanConvertFrom(typeof(string))) if(!converter.CanConvertFrom(typeof(String))) {
{
result = null; result = null;
return false; return false;
} }
try try {
{
result = new TResult[strings.Length]; result = new TResult[strings.Length];
var i = 0; Int32 i = 0;
foreach (var str in strings) foreach(String str in strings) {
result[i++] = (TResult)converter.ConvertFromInvariantString(str); result[i++] = (TResult)converter.ConvertFromInvariantString(str);
}
return true; return true;
} } catch(Exception e) when(!e.IsCriticalException()) {
catch (Exception e) when (!e.IsCriticalException())
{
result = null; result = null;
return false; return false;
} }
@ -227,12 +195,12 @@ namespace Swan
/// <exception cref="ArgumentNullException"><paramref name="type" /> is <see langword="null" />.</exception> /// <exception cref="ArgumentNullException"><paramref name="type" /> is <see langword="null" />.</exception>
/// <exception cref="StringConversionException">The conversion of at least one /// <exception cref="StringConversionException">The conversion of at least one
/// of the elements of <paramref name="strings"/>was not successful.</exception> /// of the elements of <paramref name="strings"/>was not successful.</exception>
public static object? ConvertTo(Type type, string[] strings) public static Object? ConvertTo(Type type, String[] strings) {
{ if(strings == null) {
if (strings == null)
return null; return null;
}
var method = GenericConvertToMethods.GetOrAdd(type, BuildNonGenericConvertLambda); Func<String[], Object> method = GenericConvertToMethods.GetOrAdd(type, BuildNonGenericConvertLambda);
return method(strings); return method(strings);
} }
@ -245,21 +213,19 @@ namespace Swan
/// <returns>An array of <typeparamref name="TResult" />.</returns> /// <returns>An array of <typeparamref name="TResult" />.</returns>
/// <exception cref="StringConversionException">The conversion of at least one /// <exception cref="StringConversionException">The conversion of at least one
/// of the elements of <paramref name="strings"/>was not successful.</exception> /// of the elements of <paramref name="strings"/>was not successful.</exception>
public static TResult[]? ConvertTo<TResult>(string[] strings) public static TResult[]? ConvertTo<TResult>(String[] strings) {
{ if(strings == null) {
if (strings == null)
return null; return null;
}
var converter = TypeDescriptor.GetConverter(typeof(TResult)); TypeConverter converter = TypeDescriptor.GetConverter(typeof(TResult));
var result = new TResult[strings.Length]; TResult[] result = new TResult[strings.Length];
var i = 0; Int32 i = 0;
try try {
{ foreach(String str in strings) {
foreach (var str in strings)
result[i++] = (TResult)converter.ConvertFromInvariantString(str); result[i++] = (TResult)converter.ConvertFromInvariantString(str);
} }
catch (Exception e) when (!e.IsCriticalException()) } catch(Exception e) when(!e.IsCriticalException()) {
{
throw new StringConversionException(typeof(TResult), e); throw new StringConversionException(typeof(TResult), e);
} }
@ -273,70 +239,62 @@ namespace Swan
/// <param name="type">The type.</param> /// <param name="type">The type.</param>
/// <param name="str">The string.</param> /// <param name="str">The string.</param>
/// <returns>A new expression where the previous expression is converted to string.</returns> /// <returns>A new expression where the previous expression is converted to string.</returns>
public static Expression? ConvertExpressionTo(Type type, Expression str) public static Expression? ConvertExpressionTo(Type type, Expression str) {
{ TypeConverter converter = TypeDescriptor.GetConverter(type);
var converter = TypeDescriptor.GetConverter(type);
return converter.CanConvertFrom(typeof(string)) return converter.CanConvertFrom(typeof(String))
? Expression.Convert( ? Expression.Convert(
Expression.Call(Expression.Constant(converter), ConvertFromInvariantStringMethod, str), Expression.Call(Expression.Constant(converter), ConvertFromInvariantStringMethod, str),
type) type)
: null; : null;
} }
private static Func<string[], (bool Success, object Result)> BuildNonGenericTryConvertLambda(Type type) private static Func<String[], (Boolean Success, Object Result)> BuildNonGenericTryConvertLambda(Type type) {
{ MethodInfo? methodInfo = TryConvertToInternalMethod?.MakeGenericMethod(type);
var methodInfo = TryConvertToInternalMethod.MakeGenericMethod(type); ParameterExpression parameter = Expression.Parameter(typeof(String[]));
var parameter = Expression.Parameter(typeof(string[])); MethodCallExpression body = Expression.Call(methodInfo, parameter);
var body = Expression.Call(methodInfo, parameter); Expression<Func<String[], (Boolean Success, Object Result)>> lambda = Expression.Lambda<Func<String[], (Boolean Success, Object Result)>>(body, parameter);
var lambda = Expression.Lambda<Func<string[], (bool Success, object Result)>>(body, parameter);
return lambda.Compile(); return lambda.Compile();
} }
private static (bool Success, object? Result) TryConvertToInternal<TResult>(string[] strings) private static (Boolean Success, Object? Result) TryConvertToInternal<TResult>(String[] strings) {
{ TypeConverter converter = TypeDescriptor.GetConverter(typeof(TResult));
var converter = TypeDescriptor.GetConverter(typeof(TResult)); if(!converter.CanConvertFrom(typeof(String))) {
if (!converter.CanConvertFrom(typeof(string)))
return (false, null); return (false, null);
}
var result = new TResult[strings.Length]; TResult[] result = new TResult[strings.Length];
var i = 0; Int32 i = 0;
try try {
{ foreach(String str in strings) {
foreach (var str in strings)
result[i++] = (TResult)converter.ConvertFromInvariantString(str); result[i++] = (TResult)converter.ConvertFromInvariantString(str);
}
return (true, result); return (true, result);
} } catch(Exception e) when(!e.IsCriticalException()) {
catch (Exception e) when (!e.IsCriticalException())
{
return (false, null); return (false, null);
} }
} }
private static Func<string[], object> BuildNonGenericConvertLambda(Type type) private static Func<String[], Object> BuildNonGenericConvertLambda(Type type) {
{ MethodInfo? methodInfo = ConvertToInternalMethod?.MakeGenericMethod(type);
var methodInfo = ConvertToInternalMethod.MakeGenericMethod(type); ParameterExpression parameter = Expression.Parameter(typeof(String[]));
var parameter = Expression.Parameter(typeof(string[])); MethodCallExpression body = Expression.Call(methodInfo, parameter);
var body = Expression.Call(methodInfo, parameter); Expression<Func<String[], Object>> lambda = Expression.Lambda<Func<String[], Object>>(body, parameter);
var lambda = Expression.Lambda<Func<string[], object>>(body, parameter);
return lambda.Compile(); return lambda.Compile();
} }
private static object ConvertToInternal<TResult>(string[] strings) private static Object ConvertToInternal<TResult>(String[] strings) {
{ TypeConverter converter = TypeDescriptor.GetConverter(typeof(TResult));
var converter = TypeDescriptor.GetConverter(typeof(TResult)); TResult[] result = new TResult[strings.Length];
var result = new TResult[strings.Length]; Int32 i = 0;
var i = 0; try {
try foreach(String str in strings) {
{
foreach (var str in strings)
result[i++] = (TResult)converter.ConvertFromInvariantString(str); result[i++] = (TResult)converter.ConvertFromInvariantString(str);
}
return result; return result;
} } catch(Exception e) when(!e.IsCriticalException()) {
catch (Exception e) when (!e.IsCriticalException())
{
throw new StringConversionException(typeof(TResult), e); throw new StringConversionException(typeof(TResult), e);
} }
} }

View File

@ -1,19 +1,16 @@
using System; using System;
using Swan.Lite.Logging; using Swan.Lite.Logging;
namespace Swan.Logging namespace Swan.Logging {
{
/// <summary> /// <summary>
/// Represents a Console implementation of <c>ILogger</c>. /// Represents a Console implementation of <c>ILogger</c>.
/// </summary> /// </summary>
/// <seealso cref="ILogger" /> /// <seealso cref="ILogger" />
public class ConsoleLogger : TextLogger, ILogger public class ConsoleLogger : TextLogger, ILogger {
{
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ConsoleLogger"/> class. /// Initializes a new instance of the <see cref="ConsoleLogger"/> class.
/// </summary> /// </summary>
protected ConsoleLogger() protected ConsoleLogger() {
{
// Empty // Empty
} }
@ -31,7 +28,7 @@ namespace Swan.Logging
/// <value> /// <value>
/// The debug prefix. /// The debug prefix.
/// </value> /// </value>
public static string DebugPrefix { get; set; } = "DBG"; public static String DebugPrefix { get; set; } = "DBG";
/// <summary> /// <summary>
/// Gets or sets the trace logging prefix. /// Gets or sets the trace logging prefix.
@ -39,7 +36,7 @@ namespace Swan.Logging
/// <value> /// <value>
/// The trace prefix. /// The trace prefix.
/// </value> /// </value>
public static string TracePrefix { get; set; } = "TRC"; public static String TracePrefix { get; set; } = "TRC";
/// <summary> /// <summary>
/// Gets or sets the warning logging prefix. /// Gets or sets the warning logging prefix.
@ -47,7 +44,7 @@ namespace Swan.Logging
/// <value> /// <value>
/// The warn prefix. /// The warn prefix.
/// </value> /// </value>
public static string WarnPrefix { get; set; } = "WRN"; public static String WarnPrefix { get; set; } = "WRN";
/// <summary> /// <summary>
/// Gets or sets the fatal logging prefix. /// Gets or sets the fatal logging prefix.
@ -55,7 +52,7 @@ namespace Swan.Logging
/// <value> /// <value>
/// The fatal prefix. /// The fatal prefix.
/// </value> /// </value>
public static string FatalPrefix { get; set; } = "FAT"; public static String FatalPrefix { get; set; } = "FAT";
/// <summary> /// <summary>
/// Gets or sets the error logging prefix. /// Gets or sets the error logging prefix.
@ -63,7 +60,7 @@ namespace Swan.Logging
/// <value> /// <value>
/// The error prefix. /// The error prefix.
/// </value> /// </value>
public static string ErrorPrefix { get; set; } = "ERR"; public static String ErrorPrefix { get; set; } = "ERR";
/// <summary> /// <summary>
/// Gets or sets the information logging prefix. /// Gets or sets the information logging prefix.
@ -71,7 +68,7 @@ namespace Swan.Logging
/// <value> /// <value>
/// The information prefix. /// The information prefix.
/// </value> /// </value>
public static string InfoPrefix { get; set; } = "INF"; public static String InfoPrefix { get; set; } = "INF";
/// <summary> /// <summary>
/// Gets or sets the color of the information output logging. /// Gets or sets the color of the information output logging.
@ -125,21 +122,17 @@ namespace Swan.Logging
public LogLevel LogLevel { get; set; } = DebugLogger.IsDebuggerAttached ? LogLevel.Trace : LogLevel.Info; public LogLevel LogLevel { get; set; } = DebugLogger.IsDebuggerAttached ? LogLevel.Trace : LogLevel.Info;
/// <inheritdoc /> /// <inheritdoc />
public void Log(LogMessageReceivedEventArgs logEvent) public void Log(LogMessageReceivedEventArgs logEvent) {
{
// Select the writer based on the message type // Select the writer based on the message type
var writer = logEvent.MessageType == LogLevel.Error TerminalWriters writer = logEvent.MessageType == LogLevel.Error ? TerminalWriters.StandardError : TerminalWriters.StandardOutput;
? TerminalWriters.StandardError
: TerminalWriters.StandardOutput;
var (outputMessage, color) = GetOutputAndColor(logEvent); (String outputMessage, ConsoleColor color) = this.GetOutputAndColor(logEvent);
Terminal.Write(outputMessage, color, writer); Terminal.Write(outputMessage, color, writer);
} }
/// <inheritdoc /> /// <inheritdoc />
public void Dispose() public void Dispose() {
{
// Do nothing // Do nothing
} }
} }

View File

@ -1,19 +1,17 @@
using Swan.Lite.Logging; using System;
using Swan.Lite.Logging;
namespace Swan.Logging namespace Swan.Logging {
{
/// <summary> /// <summary>
/// Represents a logger target. This target will write to the /// Represents a logger target. This target will write to the
/// Debug console using System.Diagnostics.Debug. /// Debug console using System.Diagnostics.Debug.
/// </summary> /// </summary>
/// <seealso cref="ILogger" /> /// <seealso cref="ILogger" />
public class DebugLogger : TextLogger, ILogger public class DebugLogger : TextLogger, ILogger {
{
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="DebugLogger"/> class. /// Initializes a new instance of the <see cref="DebugLogger"/> class.
/// </summary> /// </summary>
protected DebugLogger() protected DebugLogger() {
{
// Empty // Empty
} }
@ -31,22 +29,20 @@ namespace Swan.Logging
/// <value> /// <value>
/// <c>true</c> if this instance is debugger attached; otherwise, <c>false</c>. /// <c>true</c> if this instance is debugger attached; otherwise, <c>false</c>.
/// </value> /// </value>
public static bool IsDebuggerAttached => System.Diagnostics.Debugger.IsAttached; public static Boolean IsDebuggerAttached => System.Diagnostics.Debugger.IsAttached;
/// <inheritdoc/> /// <inheritdoc/>
public LogLevel LogLevel { get; set; } = IsDebuggerAttached ? LogLevel.Trace : LogLevel.None; public LogLevel LogLevel { get; set; } = IsDebuggerAttached ? LogLevel.Trace : LogLevel.None;
/// <inheritdoc/> /// <inheritdoc/>
public void Log(LogMessageReceivedEventArgs logEvent) public void Log(LogMessageReceivedEventArgs logEvent) {
{ (String outputMessage, ConsoleColor _) = this.GetOutputAndColor(logEvent);
var (outputMessage, _) = GetOutputAndColor(logEvent);
System.Diagnostics.Debug.Write(outputMessage); System.Diagnostics.Debug.Write(outputMessage);
} }
/// <inheritdoc/> /// <inheritdoc/>
public void Dispose() public void Dispose() {
{
// do nothing // do nothing
} }
} }

View File

@ -6,27 +6,23 @@ using System.Threading.Tasks;
using Swan.Lite.Logging; using Swan.Lite.Logging;
using Swan.Threading; using Swan.Threading;
namespace Swan.Logging namespace Swan.Logging {
{
/// <summary> /// <summary>
/// A helper class to write into files the messages sent by the <see cref="Terminal" />. /// A helper class to write into files the messages sent by the <see cref="Terminal" />.
/// </summary> /// </summary>
/// <seealso cref="ILogger" /> /// <seealso cref="ILogger" />
public class FileLogger : TextLogger, ILogger public class FileLogger : TextLogger, ILogger {
{
private readonly ManualResetEventSlim _doneEvent = new ManualResetEventSlim(true); private readonly ManualResetEventSlim _doneEvent = new ManualResetEventSlim(true);
private readonly ConcurrentQueue<string> _logQueue = new ConcurrentQueue<string>(); private readonly ConcurrentQueue<String> _logQueue = new ConcurrentQueue<String>();
private readonly ExclusiveTimer _timer; private readonly ExclusiveTimer _timer;
private readonly string _filePath; private readonly String _filePath;
private bool _disposedValue; // To detect redundant calls private Boolean _disposedValue; // To detect redundant calls
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="FileLogger"/> class. /// Initializes a new instance of the <see cref="FileLogger"/> class.
/// </summary> /// </summary>
public FileLogger() public FileLogger() : this(SwanRuntime.EntryAssemblyDirectory, true) {
: this(SwanRuntime.EntryAssemblyDirectory, true)
{
} }
/// <summary> /// <summary>
@ -34,19 +30,17 @@ namespace Swan.Logging
/// </summary> /// </summary>
/// <param name="filePath">The filePath.</param> /// <param name="filePath">The filePath.</param>
/// <param name="dailyFile">if set to <c>true</c> [daily file].</param> /// <param name="dailyFile">if set to <c>true</c> [daily file].</param>
public FileLogger(string filePath, bool dailyFile) public FileLogger(String filePath, Boolean dailyFile) {
{ this._filePath = filePath;
_filePath = filePath; this.DailyFile = dailyFile;
DailyFile = dailyFile;
_timer = new ExclusiveTimer( this._timer = new ExclusiveTimer(async () => await this.WriteLogEntries().ConfigureAwait(false), TimeSpan.Zero, TimeSpan.FromSeconds(5));
async () => await WriteLogEntries().ConfigureAwait(false),
TimeSpan.Zero,
TimeSpan.FromSeconds(5));
} }
/// <inheritdoc /> /// <inheritdoc />
public LogLevel LogLevel { get; set; } public LogLevel LogLevel {
get; set;
}
/// <summary> /// <summary>
/// Gets the file path. /// Gets the file path.
@ -54,9 +48,7 @@ namespace Swan.Logging
/// <value> /// <value>
/// The file path. /// The file path.
/// </value> /// </value>
public string FilePath => DailyFile public String FilePath => this.DailyFile ? Path.Combine(Path.GetDirectoryName(this._filePath), Path.GetFileNameWithoutExtension(this._filePath) + $"_{DateTime.UtcNow:yyyyMMdd}" + Path.GetExtension(this._filePath)) : this._filePath;
? Path.Combine(Path.GetDirectoryName(_filePath), Path.GetFileNameWithoutExtension(_filePath) + $"_{DateTime.UtcNow:yyyyMMdd}" + Path.GetExtension(_filePath))
: _filePath;
/// <summary> /// <summary>
/// Gets a value indicating whether [daily file]. /// Gets a value indicating whether [daily file].
@ -64,20 +56,20 @@ namespace Swan.Logging
/// <value> /// <value>
/// <c>true</c> if [daily file]; otherwise, <c>false</c>. /// <c>true</c> if [daily file]; otherwise, <c>false</c>.
/// </value> /// </value>
public bool DailyFile { get; } public Boolean DailyFile {
get;
/// <inheritdoc />
public void Log(LogMessageReceivedEventArgs logEvent)
{
var (outputMessage, _) = GetOutputAndColor(logEvent);
_logQueue.Enqueue(outputMessage);
} }
/// <inheritdoc /> /// <inheritdoc />
public void Dispose() public void Log(LogMessageReceivedEventArgs logEvent) {
{ (String outputMessage, ConsoleColor _) = this.GetOutputAndColor(logEvent);
Dispose(true);
this._logQueue.Enqueue(outputMessage);
}
/// <inheritdoc />
public void Dispose() {
this.Dispose(true);
GC.SuppressFinalize(this); GC.SuppressFinalize(this);
} }
@ -85,49 +77,46 @@ namespace Swan.Logging
/// Releases unmanaged and - optionally - managed resources. /// Releases unmanaged and - optionally - managed resources.
/// </summary> /// </summary>
/// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param> /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
protected virtual void Dispose(bool disposing) protected virtual void Dispose(Boolean disposing) {
{ if(this._disposedValue) {
if (_disposedValue) return; return;
if (disposing)
{
_timer.Pause();
_timer.Dispose();
_doneEvent.Wait();
_doneEvent.Reset();
WriteLogEntries(true).Await();
_doneEvent.Dispose();
} }
_disposedValue = true; if(disposing) {
this._timer.Pause();
this._timer.Dispose();
this._doneEvent.Wait();
this._doneEvent.Reset();
this.WriteLogEntries(true).Await();
this._doneEvent.Dispose();
} }
private async Task WriteLogEntries(bool finalCall = false) this._disposedValue = true;
{ }
if (_logQueue.IsEmpty)
private async Task WriteLogEntries(Boolean finalCall = false) {
if(this._logQueue.IsEmpty) {
return; return;
}
if (!finalCall && !_doneEvent.IsSet) if(!finalCall && !this._doneEvent.IsSet) {
return; return;
}
_doneEvent.Reset(); this._doneEvent.Reset();
try try {
{ using StreamWriter file = File.AppendText(this.FilePath);
using (var file = File.AppendText(FilePath)) while(!this._logQueue.IsEmpty) {
{ if(this._logQueue.TryDequeue(out String entry)) {
while (!_logQueue.IsEmpty)
{
if (_logQueue.TryDequeue(out var entry))
await file.WriteAsync(entry).ConfigureAwait(false); await file.WriteAsync(entry).ConfigureAwait(false);
} }
} }
} } finally {
finally if(!finalCall) {
{ this._doneEvent.Set();
if (!finalCall) }
_doneEvent.Set();
} }
} }
} }

View File

@ -1,19 +1,19 @@
namespace Swan.Logging using System;
{
using System;
namespace Swan.Logging {
/// <summary> /// <summary>
/// Interface for a logger implementation. /// Interface for a logger implementation.
/// </summary> /// </summary>
public interface ILogger : IDisposable public interface ILogger : IDisposable {
{
/// <summary> /// <summary>
/// Gets the log level. /// Gets the log level.
/// </summary> /// </summary>
/// <value> /// <value>
/// The log level. /// The log level.
/// </value> /// </value>
LogLevel LogLevel { get; } LogLevel LogLevel {
get;
}
/// <summary> /// <summary>
/// Logs the specified log event. /// Logs the specified log event.

View File

@ -1,10 +1,8 @@
namespace Swan namespace Swan {
{
/// <summary> /// <summary>
/// Defines the log levels. /// Defines the log levels.
/// </summary> /// </summary>
public enum LogLevel public enum LogLevel {
{
/// <summary> /// <summary>
/// The none message type /// The none message type
/// </summary> /// </summary>

View File

@ -1,15 +1,14 @@
using System; #nullable enable
using System;
namespace Swan namespace Swan {
{
/// <summary> /// <summary>
/// Event arguments representing the message that is logged /// Event arguments representing the message that is logged
/// on to the terminal. Use the properties to forward the data to /// on to the terminal. Use the properties to forward the data to
/// your logger of choice. /// your logger of choice.
/// </summary> /// </summary>
/// <seealso cref="System.EventArgs" /> /// <seealso cref="System.EventArgs" />
public class LogMessageReceivedEventArgs : EventArgs public class LogMessageReceivedEventArgs : EventArgs {
{
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="LogMessageReceivedEventArgs" /> class. /// Initializes a new instance of the <see cref="LogMessageReceivedEventArgs" /> class.
/// </summary> /// </summary>
@ -23,25 +22,24 @@ namespace Swan
/// <param name="callerFilePath">The caller file path.</param> /// <param name="callerFilePath">The caller file path.</param>
/// <param name="callerLineNumber">The caller line number.</param> /// <param name="callerLineNumber">The caller line number.</param>
public LogMessageReceivedEventArgs( public LogMessageReceivedEventArgs(
ulong sequence, UInt64 sequence,
LogLevel messageType, LogLevel messageType,
DateTime utcDate, DateTime utcDate,
string source, String source,
string message, String message,
object? extendedData, Object? extendedData,
string callerMemberName, String callerMemberName,
string callerFilePath, String callerFilePath,
int callerLineNumber) Int32 callerLineNumber) {
{ this.Sequence = sequence;
Sequence = sequence; this.MessageType = messageType;
MessageType = messageType; this.UtcDate = utcDate;
UtcDate = utcDate; this.Source = source;
Source = source; this.Message = message;
Message = message; this.CallerMemberName = callerMemberName;
CallerMemberName = callerMemberName; this.CallerFilePath = callerFilePath;
CallerFilePath = callerFilePath; this.CallerLineNumber = callerLineNumber;
CallerLineNumber = callerLineNumber; this.ExtendedData = extendedData;
ExtendedData = extendedData;
} }
/// <summary> /// <summary>
@ -50,7 +48,9 @@ namespace Swan
/// <value> /// <value>
/// The sequence. /// The sequence.
/// </value> /// </value>
public ulong Sequence { get; } public UInt64 Sequence {
get;
}
/// <summary> /// <summary>
/// Gets the type of the message. /// Gets the type of the message.
@ -59,7 +59,9 @@ namespace Swan
/// <value> /// <value>
/// The type of the message. /// The type of the message.
/// </value> /// </value>
public LogLevel MessageType { get; } public LogLevel MessageType {
get;
}
/// <summary> /// <summary>
/// Gets the UTC date at which the event at which the message was logged. /// Gets the UTC date at which the event at which the message was logged.
@ -67,7 +69,9 @@ namespace Swan
/// <value> /// <value>
/// The UTC date. /// The UTC date.
/// </value> /// </value>
public DateTime UtcDate { get; } public DateTime UtcDate {
get;
}
/// <summary> /// <summary>
/// Gets the name of the source where the logging message /// Gets the name of the source where the logging message
@ -76,7 +80,9 @@ namespace Swan
/// <value> /// <value>
/// The source. /// The source.
/// </value> /// </value>
public string Source { get; } public String Source {
get;
}
/// <summary> /// <summary>
/// Gets the body of the message. /// Gets the body of the message.
@ -84,7 +90,9 @@ namespace Swan
/// <value> /// <value>
/// The message. /// The message.
/// </value> /// </value>
public string Message { get; } public String Message {
get;
}
/// <summary> /// <summary>
/// Gets the name of the caller member. /// Gets the name of the caller member.
@ -92,7 +100,9 @@ namespace Swan
/// <value> /// <value>
/// The name of the caller member. /// The name of the caller member.
/// </value> /// </value>
public string CallerMemberName { get; } public String CallerMemberName {
get;
}
/// <summary> /// <summary>
/// Gets the caller file path. /// Gets the caller file path.
@ -100,7 +110,9 @@ namespace Swan
/// <value> /// <value>
/// The caller file path. /// The caller file path.
/// </value> /// </value>
public string CallerFilePath { get; } public String CallerFilePath {
get;
}
/// <summary> /// <summary>
/// Gets the caller line number. /// Gets the caller line number.
@ -108,7 +120,9 @@ namespace Swan
/// <value> /// <value>
/// The caller line number. /// The caller line number.
/// </value> /// </value>
public int CallerLineNumber { get; } public Int32 CallerLineNumber {
get;
}
/// <summary> /// <summary>
/// Gets an object representing extended data. /// Gets an object representing extended data.
@ -117,7 +131,9 @@ namespace Swan
/// <value> /// <value>
/// The extended data. /// The extended data.
/// </value> /// </value>
public object? ExtendedData { get; } public Object? ExtendedData {
get;
}
/// <summary> /// <summary>
/// Gets the Extended Data properties cast as an Exception (if possible) /// Gets the Extended Data properties cast as an Exception (if possible)
@ -126,6 +142,6 @@ namespace Swan
/// <value> /// <value>
/// The exception. /// The exception.
/// </value> /// </value>
public Exception? Exception => ExtendedData as Exception; public Exception? Exception => this.ExtendedData as Exception;
} }
} }

View File

@ -4,27 +4,26 @@ using System.Linq;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Swan.Logging namespace Swan.Logging {
{
/// <summary> /// <summary>
/// Entry-point for logging. Use this static class to register/unregister /// Entry-point for logging. Use this static class to register/unregister
/// loggers instances. By default, the <c>ConsoleLogger</c> is registered. /// loggers instances. By default, the <c>ConsoleLogger</c> is registered.
/// </summary> /// </summary>
public static class Logger public static class Logger {
{ private static readonly Object SyncLock = new Object();
private static readonly object SyncLock = new object();
private static readonly List<ILogger> Loggers = new List<ILogger>(); private static readonly List<ILogger> Loggers = new List<ILogger>();
private static ulong _loggingSequence; private static UInt64 _loggingSequence;
static Logger() static Logger() {
{ if(Terminal.IsConsolePresent) {
if (Terminal.IsConsolePresent)
Loggers.Add(ConsoleLogger.Instance); Loggers.Add(ConsoleLogger.Instance);
}
if (DebugLogger.IsDebuggerAttached) if(DebugLogger.IsDebuggerAttached) {
Loggers.Add(DebugLogger.Instance); Loggers.Add(DebugLogger.Instance);
} }
}
#region Standard Public API #region Standard Public API
@ -33,15 +32,13 @@ namespace Swan.Logging
/// </summary> /// </summary>
/// <typeparam name="T">The type of logger to register.</typeparam> /// <typeparam name="T">The type of logger to register.</typeparam>
/// <exception cref="InvalidOperationException">There is already a logger with that class registered.</exception> /// <exception cref="InvalidOperationException">There is already a logger with that class registered.</exception>
public static void RegisterLogger<T>() public static void RegisterLogger<T>() where T : ILogger {
where T : ILogger lock(SyncLock) {
{ ILogger loggerInstance = Loggers.FirstOrDefault(x => x.GetType() == typeof(T));
lock (SyncLock)
{
var loggerInstance = Loggers.FirstOrDefault(x => x.GetType() == typeof(T));
if (loggerInstance != null) if(loggerInstance != null) {
throw new InvalidOperationException("There is already a logger with that class registered."); throw new InvalidOperationException("There is already a logger with that class registered.");
}
Loggers.Add(Activator.CreateInstance<T>()); Loggers.Add(Activator.CreateInstance<T>());
} }
@ -51,11 +48,11 @@ namespace Swan.Logging
/// Registers the logger. /// Registers the logger.
/// </summary> /// </summary>
/// <param name="logger">The logger.</param> /// <param name="logger">The logger.</param>
public static void RegisterLogger(ILogger logger) public static void RegisterLogger(ILogger logger) {
{ lock(SyncLock) {
lock (SyncLock)
Loggers.Add(logger); Loggers.Add(logger);
} }
}
/// <summary> /// <summary>
/// Unregisters the logger. /// Unregisters the logger.
@ -73,11 +70,11 @@ namespace Swan.Logging
/// <summary> /// <summary>
/// Remove all the loggers. /// Remove all the loggers.
/// </summary> /// </summary>
public static void NoLogging() public static void NoLogging() {
{ lock(SyncLock) {
lock (SyncLock)
Loggers.Clear(); Loggers.Clear();
} }
}
#region Debug #region Debug
@ -90,16 +87,7 @@ namespace Swan.Logging
/// <param name="callerMemberName">Name of the caller member. This is automatically populated.</param> /// <param name="callerMemberName">Name of the caller member. This is automatically populated.</param>
/// <param name="callerFilePath">The caller file path. This is automatically populated.</param> /// <param name="callerFilePath">The caller file path. This is automatically populated.</param>
/// <param name="callerLineNumber">The caller line number. This is automatically populated.</param> /// <param name="callerLineNumber">The caller line number. This is automatically populated.</param>
public static void Debug( public static void Debug(this String message, String source = null, Object extendedData = null, [CallerMemberName] String callerMemberName = "", [CallerFilePath] String callerFilePath = "", [CallerLineNumber] Int32 callerLineNumber = 0) => LogMessage(LogLevel.Debug, message, source, extendedData, callerMemberName, callerFilePath, callerLineNumber);
this string message,
string source = null,
object extendedData = null,
[CallerMemberName] string callerMemberName = "",
[CallerFilePath] string callerFilePath = "",
[CallerLineNumber] int callerLineNumber = 0)
{
LogMessage(LogLevel.Debug, message, source, extendedData, callerMemberName, callerFilePath, callerLineNumber);
}
/// <summary> /// <summary>
/// Logs a debug message to the console. /// Logs a debug message to the console.
@ -110,16 +98,7 @@ namespace Swan.Logging
/// <param name="callerMemberName">Name of the caller member.</param> /// <param name="callerMemberName">Name of the caller member.</param>
/// <param name="callerFilePath">The caller file path.</param> /// <param name="callerFilePath">The caller file path.</param>
/// <param name="callerLineNumber">The caller line number.</param> /// <param name="callerLineNumber">The caller line number.</param>
public static void Debug( public static void Debug(this String message, Type source, Object extendedData = null, [CallerMemberName] String callerMemberName = "", [CallerFilePath] String callerFilePath = "", [CallerLineNumber] Int32 callerLineNumber = 0) => LogMessage(LogLevel.Debug, message, source?.FullName, extendedData, callerMemberName, callerFilePath, callerLineNumber);
this string message,
Type source,
object extendedData = null,
[CallerMemberName] string callerMemberName = "",
[CallerFilePath] string callerFilePath = "",
[CallerLineNumber] int callerLineNumber = 0)
{
LogMessage(LogLevel.Debug, message, source?.FullName, extendedData, callerMemberName, callerFilePath, callerLineNumber);
}
/// <summary> /// <summary>
/// Logs a debug message to the console. /// Logs a debug message to the console.
@ -130,16 +109,7 @@ namespace Swan.Logging
/// <param name="callerMemberName">Name of the caller member. This is automatically populated.</param> /// <param name="callerMemberName">Name of the caller member. This is automatically populated.</param>
/// <param name="callerFilePath">The caller file path. This is automatically populated.</param> /// <param name="callerFilePath">The caller file path. This is automatically populated.</param>
/// <param name="callerLineNumber">The caller line number. This is automatically populated.</param> /// <param name="callerLineNumber">The caller line number. This is automatically populated.</param>
public static void Debug( public static void Debug(this Exception extendedData, String source, String message, [CallerMemberName] String callerMemberName = "", [CallerFilePath] String callerFilePath = "", [CallerLineNumber] Int32 callerLineNumber = 0) => LogMessage(LogLevel.Debug, message, source, extendedData, callerMemberName, callerFilePath, callerLineNumber);
this Exception extendedData,
string source,
string message,
[CallerMemberName] string callerMemberName = "",
[CallerFilePath] string callerFilePath = "",
[CallerLineNumber] int callerLineNumber = 0)
{
LogMessage(LogLevel.Debug, message, source, extendedData, callerMemberName, callerFilePath, callerLineNumber);
}
#endregion #endregion
@ -154,16 +124,7 @@ namespace Swan.Logging
/// <param name="callerMemberName">Name of the caller member. This is automatically populated.</param> /// <param name="callerMemberName">Name of the caller member. This is automatically populated.</param>
/// <param name="callerFilePath">The caller file path. This is automatically populated.</param> /// <param name="callerFilePath">The caller file path. This is automatically populated.</param>
/// <param name="callerLineNumber">The caller line number. This is automatically populated.</param> /// <param name="callerLineNumber">The caller line number. This is automatically populated.</param>
public static void Trace( public static void Trace(this String message, String source = null, Object extendedData = null, [CallerMemberName] String callerMemberName = "", [CallerFilePath] String callerFilePath = "", [CallerLineNumber] Int32 callerLineNumber = 0) => LogMessage(LogLevel.Trace, message, source, extendedData, callerMemberName, callerFilePath, callerLineNumber);
this string message,
string source = null,
object extendedData = null,
[CallerMemberName] string callerMemberName = "",
[CallerFilePath] string callerFilePath = "",
[CallerLineNumber] int callerLineNumber = 0)
{
LogMessage(LogLevel.Trace, message, source, extendedData, callerMemberName, callerFilePath, callerLineNumber);
}
/// <summary> /// <summary>
/// Logs a trace message to the console. /// Logs a trace message to the console.
@ -174,16 +135,7 @@ namespace Swan.Logging
/// <param name="callerMemberName">Name of the caller member.</param> /// <param name="callerMemberName">Name of the caller member.</param>
/// <param name="callerFilePath">The caller file path.</param> /// <param name="callerFilePath">The caller file path.</param>
/// <param name="callerLineNumber">The caller line number.</param> /// <param name="callerLineNumber">The caller line number.</param>
public static void Trace( public static void Trace(this String message, Type source, Object extendedData = null, [CallerMemberName] String callerMemberName = "", [CallerFilePath] String callerFilePath = "", [CallerLineNumber] Int32 callerLineNumber = 0) => LogMessage(LogLevel.Trace, message, source?.FullName, extendedData, callerMemberName, callerFilePath, callerLineNumber);
this string message,
Type source,
object extendedData = null,
[CallerMemberName] string callerMemberName = "",
[CallerFilePath] string callerFilePath = "",
[CallerLineNumber] int callerLineNumber = 0)
{
LogMessage(LogLevel.Trace, message, source?.FullName, extendedData, callerMemberName, callerFilePath, callerLineNumber);
}
/// <summary> /// <summary>
/// Logs a trace message to the console. /// Logs a trace message to the console.
@ -194,16 +146,7 @@ namespace Swan.Logging
/// <param name="callerMemberName">Name of the caller member. This is automatically populated.</param> /// <param name="callerMemberName">Name of the caller member. This is automatically populated.</param>
/// <param name="callerFilePath">The caller file path. This is automatically populated.</param> /// <param name="callerFilePath">The caller file path. This is automatically populated.</param>
/// <param name="callerLineNumber">The caller line number. This is automatically populated.</param> /// <param name="callerLineNumber">The caller line number. This is automatically populated.</param>
public static void Trace( public static void Trace(this Exception extendedData, String source, String message, [CallerMemberName] String callerMemberName = "", [CallerFilePath] String callerFilePath = "", [CallerLineNumber] Int32 callerLineNumber = 0) => LogMessage(LogLevel.Trace, message, source, extendedData, callerMemberName, callerFilePath, callerLineNumber);
this Exception extendedData,
string source,
string message,
[CallerMemberName] string callerMemberName = "",
[CallerFilePath] string callerFilePath = "",
[CallerLineNumber] int callerLineNumber = 0)
{
LogMessage(LogLevel.Trace, message, source, extendedData, callerMemberName, callerFilePath, callerLineNumber);
}
#endregion #endregion
@ -218,16 +161,7 @@ namespace Swan.Logging
/// <param name="callerMemberName">Name of the caller member. This is automatically populated.</param> /// <param name="callerMemberName">Name of the caller member. This is automatically populated.</param>
/// <param name="callerFilePath">The caller file path. This is automatically populated.</param> /// <param name="callerFilePath">The caller file path. This is automatically populated.</param>
/// <param name="callerLineNumber">The caller line number. This is automatically populated.</param> /// <param name="callerLineNumber">The caller line number. This is automatically populated.</param>
public static void Warn( public static void Warn(this String message, String source = null, Object extendedData = null, [CallerMemberName] String callerMemberName = "", [CallerFilePath] String callerFilePath = "", [CallerLineNumber] Int32 callerLineNumber = 0) => LogMessage(LogLevel.Warning, message, source, extendedData, callerMemberName, callerFilePath, callerLineNumber);
this string message,
string source = null,
object extendedData = null,
[CallerMemberName] string callerMemberName = "",
[CallerFilePath] string callerFilePath = "",
[CallerLineNumber] int callerLineNumber = 0)
{
LogMessage(LogLevel.Warning, message, source, extendedData, callerMemberName, callerFilePath, callerLineNumber);
}
/// <summary> /// <summary>
/// Logs a warning message to the console. /// Logs a warning message to the console.
@ -238,16 +172,7 @@ namespace Swan.Logging
/// <param name="callerMemberName">Name of the caller member.</param> /// <param name="callerMemberName">Name of the caller member.</param>
/// <param name="callerFilePath">The caller file path.</param> /// <param name="callerFilePath">The caller file path.</param>
/// <param name="callerLineNumber">The caller line number.</param> /// <param name="callerLineNumber">The caller line number.</param>
public static void Warn( public static void Warn(this String message, Type source, Object extendedData = null, [CallerMemberName] String callerMemberName = "", [CallerFilePath] String callerFilePath = "", [CallerLineNumber] Int32 callerLineNumber = 0) => LogMessage(LogLevel.Warning, message, source?.FullName, extendedData, callerMemberName, callerFilePath, callerLineNumber);
this string message,
Type source,
object extendedData = null,
[CallerMemberName] string callerMemberName = "",
[CallerFilePath] string callerFilePath = "",
[CallerLineNumber] int callerLineNumber = 0)
{
LogMessage(LogLevel.Warning, message, source?.FullName, extendedData, callerMemberName, callerFilePath, callerLineNumber);
}
/// <summary> /// <summary>
/// Logs a warning message to the console. /// Logs a warning message to the console.
@ -258,16 +183,7 @@ namespace Swan.Logging
/// <param name="callerMemberName">Name of the caller member. This is automatically populated.</param> /// <param name="callerMemberName">Name of the caller member. This is automatically populated.</param>
/// <param name="callerFilePath">The caller file path. This is automatically populated.</param> /// <param name="callerFilePath">The caller file path. This is automatically populated.</param>
/// <param name="callerLineNumber">The caller line number. This is automatically populated.</param> /// <param name="callerLineNumber">The caller line number. This is automatically populated.</param>
public static void Warn( public static void Warn(this Exception extendedData, String source, String message, [CallerMemberName] String callerMemberName = "", [CallerFilePath] String callerFilePath = "", [CallerLineNumber] Int32 callerLineNumber = 0) => LogMessage(LogLevel.Warning, message, source, extendedData, callerMemberName, callerFilePath, callerLineNumber);
this Exception extendedData,
string source,
string message,
[CallerMemberName] string callerMemberName = "",
[CallerFilePath] string callerFilePath = "",
[CallerLineNumber] int callerLineNumber = 0)
{
LogMessage(LogLevel.Warning, message, source, extendedData, callerMemberName, callerFilePath, callerLineNumber);
}
#endregion #endregion
@ -282,16 +198,7 @@ namespace Swan.Logging
/// <param name="callerMemberName">Name of the caller member. This is automatically populated.</param> /// <param name="callerMemberName">Name of the caller member. This is automatically populated.</param>
/// <param name="callerFilePath">The caller file path. This is automatically populated.</param> /// <param name="callerFilePath">The caller file path. This is automatically populated.</param>
/// <param name="callerLineNumber">The caller line number. This is automatically populated.</param> /// <param name="callerLineNumber">The caller line number. This is automatically populated.</param>
public static void Fatal( public static void Fatal(this String message, String source = null, Object extendedData = null, [CallerMemberName] String callerMemberName = "", [CallerFilePath] String callerFilePath = "", [CallerLineNumber] Int32 callerLineNumber = 0) => LogMessage(LogLevel.Fatal, message, source, extendedData, callerMemberName, callerFilePath, callerLineNumber);
this string message,
string source = null,
object extendedData = null,
[CallerMemberName] string callerMemberName = "",
[CallerFilePath] string callerFilePath = "",
[CallerLineNumber] int callerLineNumber = 0)
{
LogMessage(LogLevel.Fatal, message, source, extendedData, callerMemberName, callerFilePath, callerLineNumber);
}
/// <summary> /// <summary>
/// Logs a warning message to the console. /// Logs a warning message to the console.
@ -302,16 +209,7 @@ namespace Swan.Logging
/// <param name="callerMemberName">Name of the caller member.</param> /// <param name="callerMemberName">Name of the caller member.</param>
/// <param name="callerFilePath">The caller file path.</param> /// <param name="callerFilePath">The caller file path.</param>
/// <param name="callerLineNumber">The caller line number.</param> /// <param name="callerLineNumber">The caller line number.</param>
public static void Fatal( public static void Fatal(this String message, Type source, Object extendedData = null, [CallerMemberName] String callerMemberName = "", [CallerFilePath] String callerFilePath = "", [CallerLineNumber] Int32 callerLineNumber = 0) => LogMessage(LogLevel.Fatal, message, source?.FullName, extendedData, callerMemberName, callerFilePath, callerLineNumber);
this string message,
Type source,
object extendedData = null,
[CallerMemberName] string callerMemberName = "",
[CallerFilePath] string callerFilePath = "",
[CallerLineNumber] int callerLineNumber = 0)
{
LogMessage(LogLevel.Fatal, message, source?.FullName, extendedData, callerMemberName, callerFilePath, callerLineNumber);
}
/// <summary> /// <summary>
/// Logs a warning message to the console. /// Logs a warning message to the console.
@ -322,16 +220,7 @@ namespace Swan.Logging
/// <param name="callerMemberName">Name of the caller member. This is automatically populated.</param> /// <param name="callerMemberName">Name of the caller member. This is automatically populated.</param>
/// <param name="callerFilePath">The caller file path. This is automatically populated.</param> /// <param name="callerFilePath">The caller file path. This is automatically populated.</param>
/// <param name="callerLineNumber">The caller line number. This is automatically populated.</param> /// <param name="callerLineNumber">The caller line number. This is automatically populated.</param>
public static void Fatal( public static void Fatal(this Exception extendedData, String source, String message, [CallerMemberName] String callerMemberName = "", [CallerFilePath] String callerFilePath = "", [CallerLineNumber] Int32 callerLineNumber = 0) => LogMessage(LogLevel.Fatal, message, source, extendedData, callerMemberName, callerFilePath, callerLineNumber);
this Exception extendedData,
string source,
string message,
[CallerMemberName] string callerMemberName = "",
[CallerFilePath] string callerFilePath = "",
[CallerLineNumber] int callerLineNumber = 0)
{
LogMessage(LogLevel.Fatal, message, source, extendedData, callerMemberName, callerFilePath, callerLineNumber);
}
#endregion #endregion
@ -346,16 +235,7 @@ namespace Swan.Logging
/// <param name="callerMemberName">Name of the caller member. This is automatically populated.</param> /// <param name="callerMemberName">Name of the caller member. This is automatically populated.</param>
/// <param name="callerFilePath">The caller file path. This is automatically populated.</param> /// <param name="callerFilePath">The caller file path. This is automatically populated.</param>
/// <param name="callerLineNumber">The caller line number. This is automatically populated.</param> /// <param name="callerLineNumber">The caller line number. This is automatically populated.</param>
public static void Info( public static void Info(this String message, String source = null, Object extendedData = null, [CallerMemberName] String callerMemberName = "", [CallerFilePath] String callerFilePath = "", [CallerLineNumber] Int32 callerLineNumber = 0) => LogMessage(LogLevel.Info, message, source, extendedData, callerMemberName, callerFilePath, callerLineNumber);
this string message,
string source = null,
object extendedData = null,
[CallerMemberName] string callerMemberName = "",
[CallerFilePath] string callerFilePath = "",
[CallerLineNumber] int callerLineNumber = 0)
{
LogMessage(LogLevel.Info, message, source, extendedData, callerMemberName, callerFilePath, callerLineNumber);
}
/// <summary> /// <summary>
/// Logs an info message to the console. /// Logs an info message to the console.
@ -366,16 +246,7 @@ namespace Swan.Logging
/// <param name="callerMemberName">Name of the caller member.</param> /// <param name="callerMemberName">Name of the caller member.</param>
/// <param name="callerFilePath">The caller file path.</param> /// <param name="callerFilePath">The caller file path.</param>
/// <param name="callerLineNumber">The caller line number.</param> /// <param name="callerLineNumber">The caller line number.</param>
public static void Info( public static void Info(this String message, Type source, Object extendedData = null, [CallerMemberName] String callerMemberName = "", [CallerFilePath] String callerFilePath = "", [CallerLineNumber] Int32 callerLineNumber = 0) => LogMessage(LogLevel.Info, message, source?.FullName, extendedData, callerMemberName, callerFilePath, callerLineNumber);
this string message,
Type source,
object extendedData = null,
[CallerMemberName] string callerMemberName = "",
[CallerFilePath] string callerFilePath = "",
[CallerLineNumber] int callerLineNumber = 0)
{
LogMessage(LogLevel.Info, message, source?.FullName, extendedData, callerMemberName, callerFilePath, callerLineNumber);
}
/// <summary> /// <summary>
/// Logs an info message to the console. /// Logs an info message to the console.
@ -386,16 +257,7 @@ namespace Swan.Logging
/// <param name="callerMemberName">Name of the caller member. This is automatically populated.</param> /// <param name="callerMemberName">Name of the caller member. This is automatically populated.</param>
/// <param name="callerFilePath">The caller file path. This is automatically populated.</param> /// <param name="callerFilePath">The caller file path. This is automatically populated.</param>
/// <param name="callerLineNumber">The caller line number. This is automatically populated.</param> /// <param name="callerLineNumber">The caller line number. This is automatically populated.</param>
public static void Info( public static void Info(this Exception extendedData, String source, String message, [CallerMemberName] String callerMemberName = "", [CallerFilePath] String callerFilePath = "", [CallerLineNumber] Int32 callerLineNumber = 0) => LogMessage(LogLevel.Info, message, source, extendedData, callerMemberName, callerFilePath, callerLineNumber);
this Exception extendedData,
string source,
string message,
[CallerMemberName] string callerMemberName = "",
[CallerFilePath] string callerFilePath = "",
[CallerLineNumber] int callerLineNumber = 0)
{
LogMessage(LogLevel.Info, message, source, extendedData, callerMemberName, callerFilePath, callerLineNumber);
}
#endregion #endregion
@ -410,16 +272,7 @@ namespace Swan.Logging
/// <param name="callerMemberName">Name of the caller member. This is automatically populated.</param> /// <param name="callerMemberName">Name of the caller member. This is automatically populated.</param>
/// <param name="callerFilePath">The caller file path. This is automatically populated.</param> /// <param name="callerFilePath">The caller file path. This is automatically populated.</param>
/// <param name="callerLineNumber">The caller line number. This is automatically populated.</param> /// <param name="callerLineNumber">The caller line number. This is automatically populated.</param>
public static void Error( public static void Error(this String message, String source = null, Object extendedData = null, [CallerMemberName] String callerMemberName = "", [CallerFilePath] String callerFilePath = "", [CallerLineNumber] Int32 callerLineNumber = 0) => LogMessage(LogLevel.Error, message, source, extendedData, callerMemberName, callerFilePath, callerLineNumber);
this string message,
string source = null,
object extendedData = null,
[CallerMemberName] string callerMemberName = "",
[CallerFilePath] string callerFilePath = "",
[CallerLineNumber] int callerLineNumber = 0)
{
LogMessage(LogLevel.Error, message, source, extendedData, callerMemberName, callerFilePath, callerLineNumber);
}
/// <summary> /// <summary>
/// Logs an error message to the console's standard error. /// Logs an error message to the console's standard error.
@ -430,16 +283,7 @@ namespace Swan.Logging
/// <param name="callerMemberName">Name of the caller member.</param> /// <param name="callerMemberName">Name of the caller member.</param>
/// <param name="callerFilePath">The caller file path.</param> /// <param name="callerFilePath">The caller file path.</param>
/// <param name="callerLineNumber">The caller line number.</param> /// <param name="callerLineNumber">The caller line number.</param>
public static void Error( public static void Error(this String message, Type source, Object extendedData = null, [CallerMemberName] String callerMemberName = "", [CallerFilePath] String callerFilePath = "", [CallerLineNumber] Int32 callerLineNumber = 0) => LogMessage(LogLevel.Error, message, source?.FullName, extendedData, callerMemberName, callerFilePath, callerLineNumber);
this string message,
Type source,
object extendedData = null,
[CallerMemberName] string callerMemberName = "",
[CallerFilePath] string callerFilePath = "",
[CallerLineNumber] int callerLineNumber = 0)
{
LogMessage(LogLevel.Error, message, source?.FullName, extendedData, callerMemberName, callerFilePath, callerLineNumber);
}
/// <summary> /// <summary>
/// Logs an error message to the console's standard error. /// Logs an error message to the console's standard error.
@ -450,16 +294,7 @@ namespace Swan.Logging
/// <param name="callerMemberName">Name of the caller member. This is automatically populated.</param> /// <param name="callerMemberName">Name of the caller member. This is automatically populated.</param>
/// <param name="callerFilePath">The caller file path. This is automatically populated.</param> /// <param name="callerFilePath">The caller file path. This is automatically populated.</param>
/// <param name="callerLineNumber">The caller line number. This is automatically populated.</param> /// <param name="callerLineNumber">The caller line number. This is automatically populated.</param>
public static void Error( public static void Error(this Exception ex, String source, String message, [CallerMemberName] String callerMemberName = "", [CallerFilePath] String callerFilePath = "", [CallerLineNumber] Int32 callerLineNumber = 0) => LogMessage(LogLevel.Error, message, source, ex, callerMemberName, callerFilePath, callerLineNumber);
this Exception ex,
string source,
string message,
[CallerMemberName] string callerMemberName = "",
[CallerFilePath] string callerFilePath = "",
[CallerLineNumber] int callerLineNumber = 0)
{
LogMessage(LogLevel.Error, message, source, ex, callerMemberName, callerFilePath, callerLineNumber);
}
#endregion #endregion
@ -477,17 +312,7 @@ namespace Swan.Logging
/// <param name="callerMemberName">Name of the caller member. This is automatically populated.</param> /// <param name="callerMemberName">Name of the caller member. This is automatically populated.</param>
/// <param name="callerFilePath">The caller file path. This is automatically populated.</param> /// <param name="callerFilePath">The caller file path. This is automatically populated.</param>
/// <param name="callerLineNumber">The caller line number. This is automatically populated.</param> /// <param name="callerLineNumber">The caller line number. This is automatically populated.</param>
public static void Log( public static void Log(this String message, String source, LogLevel messageType, Object extendedData = null, [CallerMemberName] String callerMemberName = "", [CallerFilePath] String callerFilePath = "", [CallerLineNumber] Int32 callerLineNumber = 0) => LogMessage(messageType, message, source, extendedData, callerMemberName, callerFilePath, callerLineNumber);
this string message,
string source,
LogLevel messageType,
object extendedData = null,
[CallerMemberName] string callerMemberName = "",
[CallerFilePath] string callerFilePath = "",
[CallerLineNumber] int callerLineNumber = 0)
{
LogMessage(messageType, message, source, extendedData, callerMemberName, callerFilePath, callerLineNumber);
}
/// <summary> /// <summary>
/// Logs the specified message. /// Logs the specified message.
@ -499,17 +324,7 @@ namespace Swan.Logging
/// <param name="callerMemberName">Name of the caller member.</param> /// <param name="callerMemberName">Name of the caller member.</param>
/// <param name="callerFilePath">The caller file path.</param> /// <param name="callerFilePath">The caller file path.</param>
/// <param name="callerLineNumber">The caller line number.</param> /// <param name="callerLineNumber">The caller line number.</param>
public static void Log( public static void Log(this String message, Type source, LogLevel messageType, Object extendedData = null, [CallerMemberName] String callerMemberName = "", [CallerFilePath] String callerFilePath = "", [CallerLineNumber] Int32 callerLineNumber = 0) => LogMessage(messageType, message, source?.FullName, extendedData, callerMemberName, callerFilePath, callerLineNumber);
this string message,
Type source,
LogLevel messageType,
object extendedData = null,
[CallerMemberName] string callerMemberName = "",
[CallerFilePath] string callerFilePath = "",
[CallerLineNumber] int callerLineNumber = 0)
{
LogMessage(messageType, message, source?.FullName, extendedData, callerMemberName, callerFilePath, callerLineNumber);
}
/// <summary> /// <summary>
/// Logs an error message to the console's standard error. /// Logs an error message to the console's standard error.
@ -520,16 +335,7 @@ namespace Swan.Logging
/// <param name="callerMemberName">Name of the caller member. This is automatically populated.</param> /// <param name="callerMemberName">Name of the caller member. This is automatically populated.</param>
/// <param name="callerFilePath">The caller file path. This is automatically populated.</param> /// <param name="callerFilePath">The caller file path. This is automatically populated.</param>
/// <param name="callerLineNumber">The caller line number. This is automatically populated.</param> /// <param name="callerLineNumber">The caller line number. This is automatically populated.</param>
public static void Log( public static void Log(this Exception ex, String source = null, String message = null, [CallerMemberName] String callerMemberName = "", [CallerFilePath] String callerFilePath = "", [CallerLineNumber] Int32 callerLineNumber = 0) => LogMessage(LogLevel.Error, message ?? ex.Message, source ?? ex.Source, ex, callerMemberName, callerFilePath, callerLineNumber);
this Exception ex,
string source = null,
string message = null,
[CallerMemberName] string callerMemberName = "",
[CallerFilePath] string callerFilePath = "",
[CallerLineNumber] int callerLineNumber = 0)
{
LogMessage(LogLevel.Error, message ?? ex.Message, source ?? ex.Source, ex, callerMemberName, callerFilePath, callerLineNumber);
}
/// <summary> /// <summary>
/// Logs an error message to the console's standard error. /// Logs an error message to the console's standard error.
@ -540,16 +346,7 @@ namespace Swan.Logging
/// <param name="callerMemberName">Name of the caller member. This is automatically populated.</param> /// <param name="callerMemberName">Name of the caller member. This is automatically populated.</param>
/// <param name="callerFilePath">The caller file path. This is automatically populated.</param> /// <param name="callerFilePath">The caller file path. This is automatically populated.</param>
/// <param name="callerLineNumber">The caller line number. This is automatically populated.</param> /// <param name="callerLineNumber">The caller line number. This is automatically populated.</param>
public static void Log( public static void Log(this Exception ex, Type source = null, String message = null, [CallerMemberName] String callerMemberName = "", [CallerFilePath] String callerFilePath = "", [CallerLineNumber] Int32 callerLineNumber = 0) => LogMessage(LogLevel.Error, message ?? ex.Message, source?.FullName ?? ex.Source, ex, callerMemberName, callerFilePath, callerLineNumber);
this Exception ex,
Type source = null,
string message = null,
[CallerMemberName] string callerMemberName = "",
[CallerFilePath] string callerFilePath = "",
[CallerLineNumber] int callerLineNumber = 0)
{
LogMessage(LogLevel.Error, message ?? ex.Message, source?.FullName ?? ex.Source, ex, callerMemberName, callerFilePath, callerLineNumber);
}
/// <summary> /// <summary>
/// Logs a trace message showing all possible non-null properties of the given object /// Logs a trace message showing all possible non-null properties of the given object
@ -561,16 +358,12 @@ namespace Swan.Logging
/// <param name="callerMemberName">Name of the caller member. This is automatically populated.</param> /// <param name="callerMemberName">Name of the caller member. This is automatically populated.</param>
/// <param name="callerFilePath">The caller file path. This is automatically populated.</param> /// <param name="callerFilePath">The caller file path. This is automatically populated.</param>
/// <param name="callerLineNumber">The caller line number. This is automatically populated.</param> /// <param name="callerLineNumber">The caller line number. This is automatically populated.</param>
public static void Dump( public static void Dump(this Object obj, String source, String text = "Object Dump", [CallerMemberName] String callerMemberName = "", [CallerFilePath] String callerFilePath = "", [CallerLineNumber] Int32 callerLineNumber = 0) {
this object obj, if(obj == null) {
string source, return;
string text = "Object Dump", }
[CallerMemberName] string callerMemberName = "",
[CallerFilePath] string callerFilePath = "", String message = $"{text} ({obj.GetType()}): {Environment.NewLine}{obj.Stringify().Indent(5)}";
[CallerLineNumber] int callerLineNumber = 0)
{
if (obj == null) return;
var message = $"{text} ({obj.GetType()}): {Environment.NewLine}{obj.Stringify().Indent(5)}";
LogMessage(LogLevel.Trace, message, source, obj, callerMemberName, callerFilePath, callerLineNumber); LogMessage(LogLevel.Trace, message, source, obj, callerMemberName, callerFilePath, callerLineNumber);
} }
@ -584,69 +377,45 @@ namespace Swan.Logging
/// <param name="callerMemberName">Name of the caller member.</param> /// <param name="callerMemberName">Name of the caller member.</param>
/// <param name="callerFilePath">The caller file path.</param> /// <param name="callerFilePath">The caller file path.</param>
/// <param name="callerLineNumber">The caller line number.</param> /// <param name="callerLineNumber">The caller line number.</param>
public static void Dump( public static void Dump(this Object obj, Type source, String text = "Object Dump", [CallerMemberName] String callerMemberName = "", [CallerFilePath] String callerFilePath = "", [CallerLineNumber] Int32 callerLineNumber = 0) {
this object obj, if(obj == null) {
Type source, return;
string text = "Object Dump", }
[CallerMemberName] string callerMemberName = "",
[CallerFilePath] string callerFilePath = "", String message = $"{text} ({obj.GetType()}): {Environment.NewLine}{obj.Stringify().Indent(5)}";
[CallerLineNumber] int callerLineNumber = 0)
{
if (obj == null) return;
var message = $"{text} ({obj.GetType()}): {Environment.NewLine}{obj.Stringify().Indent(5)}";
LogMessage(LogLevel.Trace, message, source?.FullName, obj, callerMemberName, callerFilePath, callerLineNumber); LogMessage(LogLevel.Trace, message, source?.FullName, obj, callerMemberName, callerFilePath, callerLineNumber);
} }
#endregion #endregion
private static void RemoveLogger(Func<ILogger, bool> criteria) private static void RemoveLogger(Func<ILogger, Boolean> criteria) {
{ lock(SyncLock) {
lock (SyncLock) ILogger loggerInstance = Loggers.FirstOrDefault(criteria);
{
var loggerInstance = Loggers.FirstOrDefault(criteria);
if (loggerInstance == null) if(loggerInstance == null) {
throw new InvalidOperationException("The logger is not registered."); throw new InvalidOperationException("The logger is not registered.");
}
loggerInstance.Dispose(); loggerInstance.Dispose();
Loggers.Remove(loggerInstance); _ = Loggers.Remove(loggerInstance);
} }
} }
private static void LogMessage( private static void LogMessage(LogLevel logLevel, String message, String sourceName, Object extendedData, String callerMemberName, String callerFilePath, Int32 callerLineNumber) {
LogLevel logLevel, UInt64 sequence = _loggingSequence;
string message, DateTime date = DateTime.UtcNow;
string sourceName,
object extendedData,
string callerMemberName,
string callerFilePath,
int callerLineNumber)
{
var sequence = _loggingSequence;
var date = DateTime.UtcNow;
_loggingSequence++; _loggingSequence++;
var loggerMessage = string.IsNullOrWhiteSpace(message) ? String loggerMessage = String.IsNullOrWhiteSpace(message) ? String.Empty : message.RemoveControlCharsExcept('\n');
string.Empty : message.RemoveControlCharsExcept('\n');
var eventArgs = new LogMessageReceivedEventArgs( LogMessageReceivedEventArgs eventArgs = new LogMessageReceivedEventArgs(sequence, logLevel, date, sourceName, loggerMessage, extendedData, callerMemberName, callerFilePath, callerLineNumber);
sequence,
logLevel,
date,
sourceName,
loggerMessage,
extendedData,
callerMemberName,
callerFilePath,
callerLineNumber);
foreach (var logger in Loggers) foreach(ILogger logger in Loggers) {
{ _ = Task.Run(() => {
Task.Run(() => if(logger.LogLevel <= logLevel) {
{
if (logger.LogLevel <= logLevel)
logger.Log(eventArgs); logger.Log(eventArgs);
}
}); });
} }
} }

View File

@ -1,13 +1,11 @@
using Swan.Logging; using Swan.Logging;
using System; using System;
namespace Swan.Lite.Logging namespace Swan.Lite.Logging {
{
/// <summary> /// <summary>
/// Use this class for text-based logger. /// Use this class for text-based logger.
/// </summary> /// </summary>
public abstract class TextLogger public abstract class TextLogger {
{
/// <summary> /// <summary>
/// Gets or sets the logging time format. /// Gets or sets the logging time format.
/// set to null or empty to prevent output. /// set to null or empty to prevent output.
@ -15,7 +13,7 @@ namespace Swan.Lite.Logging
/// <value> /// <value>
/// The logging time format. /// The logging time format.
/// </value> /// </value>
public static string LoggingTimeFormat { get; set; } = "HH:mm:ss.fff"; public static String LoggingTimeFormat { get; set; } = "HH:mm:ss.fff";
/// <summary> /// <summary>
/// Gets the color of the output of the message (the output message has a new line char in the end). /// Gets the color of the output of the message (the output message has a new line char in the end).
@ -24,25 +22,18 @@ namespace Swan.Lite.Logging
/// <returns> /// <returns>
/// The output message formatted and the color of the console to be used. /// The output message formatted and the color of the console to be used.
/// </returns> /// </returns>
protected (string outputMessage, ConsoleColor color) GetOutputAndColor(LogMessageReceivedEventArgs logEvent) protected (String outputMessage, ConsoleColor color) GetOutputAndColor(LogMessageReceivedEventArgs logEvent) {
{ (String prefix, ConsoleColor color) = GetConsoleColorAndPrefix(logEvent.MessageType);
var (prefix , color) = GetConsoleColorAndPrefix(logEvent.MessageType);
var loggerMessage = string.IsNullOrWhiteSpace(logEvent.Message) String loggerMessage = String.IsNullOrWhiteSpace(logEvent.Message) ? String.Empty : logEvent.Message.RemoveControlCharsExcept('\n');
? string.Empty
: logEvent.Message.RemoveControlCharsExcept('\n');
var outputMessage = CreateOutputMessage(logEvent.Source, loggerMessage, prefix, logEvent.UtcDate); String outputMessage = CreateOutputMessage(logEvent.Source, loggerMessage, prefix, logEvent.UtcDate);
// Further format the output in the case there is an exception being logged // Further format the output in the case there is an exception being logged
if (logEvent.MessageType == LogLevel.Error && logEvent.Exception != null) if(logEvent.MessageType == LogLevel.Error && logEvent.Exception != null) {
{ try {
try
{
outputMessage += $"{logEvent.Exception.Stringify().Indent()}{Environment.NewLine}"; outputMessage += $"{logEvent.Exception.Stringify().Indent()}{Environment.NewLine}";
} } catch {
catch
{
// Ignore // Ignore
} }
} }
@ -50,40 +41,23 @@ namespace Swan.Lite.Logging
return (outputMessage, color); return (outputMessage, color);
} }
private static (string Prefix, ConsoleColor color) GetConsoleColorAndPrefix(LogLevel messageType) private static (String Prefix, ConsoleColor color) GetConsoleColorAndPrefix(LogLevel messageType) => messageType switch
{ {
switch (messageType) LogLevel.Debug => (ConsoleLogger.DebugPrefix, ConsoleLogger.DebugColor),
{ LogLevel.Error => (ConsoleLogger.ErrorPrefix, ConsoleLogger.ErrorColor),
case LogLevel.Debug: LogLevel.Info => (ConsoleLogger.InfoPrefix, ConsoleLogger.InfoColor),
return (ConsoleLogger.DebugPrefix, ConsoleLogger.DebugColor); LogLevel.Trace => (ConsoleLogger.TracePrefix, ConsoleLogger.TraceColor),
case LogLevel.Error: LogLevel.Warning => (ConsoleLogger.WarnPrefix, ConsoleLogger.WarnColor),
return (ConsoleLogger.ErrorPrefix, ConsoleLogger.ErrorColor); LogLevel.Fatal => (ConsoleLogger.FatalPrefix, ConsoleLogger.FatalColor),
case LogLevel.Info: _ => (new String(' ', ConsoleLogger.InfoPrefix.Length), Terminal.Settings.DefaultColor),
return (ConsoleLogger.InfoPrefix, ConsoleLogger.InfoColor); };
case LogLevel.Trace:
return (ConsoleLogger.TracePrefix, ConsoleLogger.TraceColor);
case LogLevel.Warning:
return (ConsoleLogger.WarnPrefix, ConsoleLogger.WarnColor);
case LogLevel.Fatal:
return (ConsoleLogger.FatalPrefix, ConsoleLogger.FatalColor);
default:
return (new string(' ', ConsoleLogger.InfoPrefix.Length), Terminal.Settings.DefaultColor);
}
}
private static string CreateOutputMessage(string sourceName, string loggerMessage, string prefix, DateTime date) private static String CreateOutputMessage(String sourceName, String loggerMessage, String prefix, DateTime date) {
{ String friendlySourceName = String.IsNullOrWhiteSpace(sourceName) ? String.Empty : sourceName.SliceLength(sourceName.LastIndexOf('.') + 1, sourceName.Length);
var friendlySourceName = string.IsNullOrWhiteSpace(sourceName)
? string.Empty
: sourceName.SliceLength(sourceName.LastIndexOf('.') + 1, sourceName.Length);
var outputMessage = string.IsNullOrWhiteSpace(sourceName) String outputMessage = String.IsNullOrWhiteSpace(sourceName) ? loggerMessage : $"[{friendlySourceName}] {loggerMessage}";
? loggerMessage
: $"[{friendlySourceName}] {loggerMessage}";
return string.IsNullOrWhiteSpace(LoggingTimeFormat) return String.IsNullOrWhiteSpace(LoggingTimeFormat) ? $" {prefix} >> {outputMessage}{Environment.NewLine}" : $" {date.ToLocalTime().ToString(LoggingTimeFormat)} {prefix} >> {outputMessage}{Environment.NewLine}";
? $" {prefix} >> {outputMessage}{Environment.NewLine}"
: $" {date.ToLocalTime().ToString(LoggingTimeFormat)} {prefix} >> {outputMessage}{Environment.NewLine}";
} }
} }
} }

View File

@ -1,13 +1,11 @@
using System; using System;
namespace Swan.Mappers namespace Swan.Mappers {
{
/// <summary> /// <summary>
/// Represents an attribute to select which properties are copyable between objects. /// Represents an attribute to select which properties are copyable between objects.
/// </summary> /// </summary>
/// <seealso cref="Attribute" /> /// <seealso cref="Attribute" />
[AttributeUsage(AttributeTargets.Property)] [AttributeUsage(AttributeTargets.Property)]
public class CopyableAttribute : Attribute public class CopyableAttribute : Attribute {
{
} }
} }

View File

@ -2,26 +2,30 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Reflection; using System.Reflection;
namespace Swan.Mappers namespace Swan.Mappers {
{
/// <summary> /// <summary>
/// Interface object map. /// Interface object map.
/// </summary> /// </summary>
public interface IObjectMap public interface IObjectMap {
{
/// <summary> /// <summary>
/// Gets or sets the map. /// Gets or sets the map.
/// </summary> /// </summary>
Dictionary<PropertyInfo, List<PropertyInfo>> Map { get; } Dictionary<PropertyInfo, List<PropertyInfo>> Map {
get;
}
/// <summary> /// <summary>
/// Gets or sets the type of the source. /// Gets or sets the type of the source.
/// </summary> /// </summary>
Type SourceType { get; } Type SourceType {
get;
}
/// <summary> /// <summary>
/// Gets or sets the type of the destination. /// Gets or sets the type of the destination.
/// </summary> /// </summary>
Type DestinationType { get; } Type DestinationType {
get;
}
} }
} }

View File

@ -4,33 +4,34 @@ using System.Linq;
using System.Linq.Expressions; using System.Linq.Expressions;
using System.Reflection; using System.Reflection;
namespace Swan.Mappers namespace Swan.Mappers {
{
/// <summary> /// <summary>
/// Represents an object map. /// Represents an object map.
/// </summary> /// </summary>
/// <typeparam name="TSource">The type of the source.</typeparam> /// <typeparam name="TSource">The type of the source.</typeparam>
/// <typeparam name="TDestination">The type of the destination.</typeparam> /// <typeparam name="TDestination">The type of the destination.</typeparam>
/// <seealso cref="IObjectMap" /> /// <seealso cref="IObjectMap" />
public class ObjectMap<TSource, TDestination> : IObjectMap public class ObjectMap<TSource, TDestination> : IObjectMap {
{ internal ObjectMap(IEnumerable<PropertyInfo> intersect) {
internal ObjectMap(IEnumerable<PropertyInfo> intersect) this.SourceType = typeof(TSource);
{ this.DestinationType = typeof(TDestination);
SourceType = typeof(TSource); this.Map = intersect.ToDictionary(property => this.DestinationType.GetProperty(property.Name), property => new List<PropertyInfo> { this.SourceType.GetProperty(property.Name) });
DestinationType = typeof(TDestination);
Map = intersect.ToDictionary(
property => DestinationType.GetProperty(property.Name),
property => new List<PropertyInfo> {SourceType.GetProperty(property.Name)});
} }
/// <inheritdoc/> /// <inheritdoc/>
public Dictionary<PropertyInfo, List<PropertyInfo>> Map { get; } public Dictionary<PropertyInfo, List<PropertyInfo>> Map {
get;
}
/// <inheritdoc/> /// <inheritdoc/>
public Type SourceType { get; } public Type SourceType {
get;
}
/// <inheritdoc/> /// <inheritdoc/>
public Type DestinationType { get; } public Type DestinationType {
get;
}
/// <summary> /// <summary>
/// Maps the property. /// Maps the property.
@ -43,28 +44,22 @@ namespace Swan.Mappers
/// An object map representation of type of the destination property /// An object map representation of type of the destination property
/// and type of the source property. /// and type of the source property.
/// </returns> /// </returns>
public ObjectMap<TSource, TDestination> MapProperty public ObjectMap<TSource, TDestination> MapProperty<TDestinationProperty, TSourceProperty>(Expression<Func<TDestination, TDestinationProperty>> destinationProperty, Expression<Func<TSource, TSourceProperty>> sourceProperty) {
<TDestinationProperty, TSourceProperty>( PropertyInfo propertyDestinationInfo = (destinationProperty.Body as MemberExpression)?.Member as PropertyInfo;
Expression<Func<TDestination, TDestinationProperty>> destinationProperty,
Expression<Func<TSource, TSourceProperty>> sourceProperty)
{
var propertyDestinationInfo = (destinationProperty.Body as MemberExpression)?.Member as PropertyInfo;
if (propertyDestinationInfo == null) if(propertyDestinationInfo == null) {
{
throw new ArgumentException("Invalid destination expression", nameof(destinationProperty)); throw new ArgumentException("Invalid destination expression", nameof(destinationProperty));
} }
var sourceMembers = GetSourceMembers(sourceProperty); List<PropertyInfo> sourceMembers = GetSourceMembers(sourceProperty);
if (sourceMembers.Any() == false) if(sourceMembers.Any() == false) {
{
throw new ArgumentException("Invalid source expression", nameof(sourceProperty)); throw new ArgumentException("Invalid source expression", nameof(sourceProperty));
} }
// reverse order // reverse order
sourceMembers.Reverse(); sourceMembers.Reverse();
Map[propertyDestinationInfo] = sourceMembers; this.Map[propertyDestinationInfo] = sourceMembers;
return this; return this;
} }
@ -79,32 +74,31 @@ namespace Swan.Mappers
/// and type of the source property. /// and type of the source property.
/// </returns> /// </returns>
/// <exception cref="System.Exception">Invalid destination expression.</exception> /// <exception cref="System.Exception">Invalid destination expression.</exception>
public ObjectMap<TSource, TDestination> RemoveMapProperty<TDestinationProperty>( public ObjectMap<TSource, TDestination> RemoveMapProperty<TDestinationProperty>(Expression<Func<TDestination, TDestinationProperty>> destinationProperty) {
Expression<Func<TDestination, TDestinationProperty>> destinationProperty) PropertyInfo propertyDestinationInfo = (destinationProperty.Body as MemberExpression)?.Member as PropertyInfo;
{
var propertyDestinationInfo = (destinationProperty.Body as MemberExpression)?.Member as PropertyInfo;
if (propertyDestinationInfo == null) if(propertyDestinationInfo == null) {
throw new ArgumentException("Invalid destination expression", nameof(destinationProperty)); throw new ArgumentException("Invalid destination expression", nameof(destinationProperty));
}
if (Map.ContainsKey(propertyDestinationInfo)) if(this.Map.ContainsKey(propertyDestinationInfo)) {
{ _ = this.Map.Remove(propertyDestinationInfo);
Map.Remove(propertyDestinationInfo);
} }
return this; return this;
} }
private static List<PropertyInfo> GetSourceMembers<TSourceProperty>(Expression<Func<TSource, TSourceProperty>> sourceProperty) private static List<PropertyInfo> GetSourceMembers<TSourceProperty>(Expression<Func<TSource, TSourceProperty>> sourceProperty) {
{ List<PropertyInfo> sourceMembers = new List<PropertyInfo>();
var sourceMembers = new List<PropertyInfo>(); MemberExpression initialExpression = sourceProperty.Body as MemberExpression;
var initialExpression = sourceProperty.Body as MemberExpression;
while (true) while(true) {
{ PropertyInfo propertySourceInfo = initialExpression?.Member as PropertyInfo;
var propertySourceInfo = initialExpression?.Member as PropertyInfo;
if(propertySourceInfo == null) {
break;
}
if (propertySourceInfo == null) break;
sourceMembers.Add(propertySourceInfo); sourceMembers.Add(propertySourceInfo);
initialExpression = initialExpression.Expression as MemberExpression; initialExpression = initialExpression.Expression as MemberExpression;
} }

View File

@ -1,8 +1,8 @@
using System.Collections.Generic; using System;
using System.Collections.Generic;
using System.Reflection; using System.Reflection;
namespace Swan.Mappers namespace Swan.Mappers {
{
/// <summary> /// <summary>
/// Represents an AutoMapper-like object to map from one object type /// Represents an AutoMapper-like object to map from one object type
/// to another using defined properties map or using the default behaviour /// to another using defined properties map or using the default behaviour
@ -10,15 +10,11 @@ namespace Swan.Mappers
/// ///
/// The extension methods like CopyPropertiesTo use the default behaviour. /// The extension methods like CopyPropertiesTo use the default behaviour.
/// </summary> /// </summary>
public partial class ObjectMapper public partial class ObjectMapper {
{ internal class PropertyInfoComparer : IEqualityComparer<PropertyInfo> {
internal class PropertyInfoComparer : IEqualityComparer<PropertyInfo> public Boolean Equals(PropertyInfo x, PropertyInfo y) => x != null && y != null && x.Name == y.Name && x.PropertyType == y.PropertyType;
{
public bool Equals(PropertyInfo x, PropertyInfo y)
=> x != null && y != null && x.Name == y.Name && x.PropertyType == y.PropertyType;
public int GetHashCode(PropertyInfo obj) public Int32 GetHashCode(PropertyInfo obj) => obj.Name.GetHashCode() + obj.PropertyType.Name.GetHashCode();
=> obj.Name.GetHashCode() + obj.PropertyType.Name.GetHashCode();
} }
} }
} }

View File

@ -1,12 +1,12 @@
using System; #nullable enable
using System;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using Swan.Reflection; using Swan.Reflection;
namespace Swan.Mappers namespace Swan.Mappers {
{
/// <summary> /// <summary>
/// Represents an AutoMapper-like object to map from one object type /// Represents an AutoMapper-like object to map from one object type
/// to another using defined properties map or using the default behaviour /// to another using defined properties map or using the default behaviour
@ -78,8 +78,7 @@ namespace Swan.Mappers
/// } /// }
/// </code> /// </code>
/// </example> /// </example>
public partial class ObjectMapper public partial class ObjectMapper {
{
private static readonly Lazy<ObjectMapper> LazyInstance = new Lazy<ObjectMapper>(() => new ObjectMapper()); private static readonly Lazy<ObjectMapper> LazyInstance = new Lazy<ObjectMapper>(() => new ObjectMapper());
private readonly List<IObjectMap> _maps = new List<IObjectMap>(); private readonly List<IObjectMap> _maps = new List<IObjectMap>();
@ -107,23 +106,16 @@ namespace Swan.Mappers
/// or /// or
/// target. /// target.
/// </exception> /// </exception>
public static int Copy( public static Int32 Copy(Object source, Object? target, IEnumerable<String>? propertiesToCopy = null, params String[]? ignoreProperties) {
object source, if(source == null) {
object target,
IEnumerable<string>? propertiesToCopy = null,
params string[]? ignoreProperties)
{
if (source == null)
throw new ArgumentNullException(nameof(source)); throw new ArgumentNullException(nameof(source));
}
if (target == null) if(target == null) {
throw new ArgumentNullException(nameof(target)); throw new ArgumentNullException(nameof(target));
}
return CopyInternal( return CopyInternal(target, GetSourceMap(source), propertiesToCopy, ignoreProperties);
target,
GetSourceMap(source),
propertiesToCopy,
ignoreProperties);
} }
/// <summary> /// <summary>
@ -141,25 +133,16 @@ namespace Swan.Mappers
/// or /// or
/// target. /// target.
/// </exception> /// </exception>
public static int Copy( public static Int32 Copy(IDictionary<String, Object> source, Object? target, IEnumerable<String>? propertiesToCopy = null, params String[] ignoreProperties) {
IDictionary<string, object> source, if(source == null) {
object target,
IEnumerable<string>? propertiesToCopy = null,
params string[] ignoreProperties)
{
if (source == null)
throw new ArgumentNullException(nameof(source)); throw new ArgumentNullException(nameof(source));
}
if (target == null) if(target == null) {
throw new ArgumentNullException(nameof(target)); throw new ArgumentNullException(nameof(target));
}
return CopyInternal( return CopyInternal(target, source.ToDictionary(x => x.Key.ToLowerInvariant(), x => Tuple.Create(typeof(Object), x.Value)), propertiesToCopy, ignoreProperties);
target,
source.ToDictionary(
x => x.Key.ToLowerInvariant(),
x => Tuple.Create(typeof(object), x.Value)),
propertiesToCopy,
ignoreProperties);
} }
/// <summary> /// <summary>
@ -176,22 +159,23 @@ namespace Swan.Mappers
/// or /// or
/// Types doesn't match. /// Types doesn't match.
/// </exception> /// </exception>
public ObjectMap<TSource, TDestination> CreateMap<TSource, TDestination>() public ObjectMap<TSource, TDestination> CreateMap<TSource, TDestination>() {
{ if(this._maps.Any(x => x.SourceType == typeof(TSource) && x.DestinationType == typeof(TDestination))) {
if (_maps.Any(x => x.SourceType == typeof(TSource) && x.DestinationType == typeof(TDestination)))
throw new InvalidOperationException("You can't create an existing map"); throw new InvalidOperationException("You can't create an existing map");
}
var sourceType = PropertyTypeCache.DefaultCache.Value.RetrieveAllProperties<TSource>(true); IEnumerable<PropertyInfo> sourceType = PropertyTypeCache.DefaultCache.Value.RetrieveAllProperties<TSource>(true);
var destinationType = PropertyTypeCache.DefaultCache.Value.RetrieveAllProperties<TDestination>(true); IEnumerable<PropertyInfo> destinationType = PropertyTypeCache.DefaultCache.Value.RetrieveAllProperties<TDestination>(true);
var intersect = sourceType.Intersect(destinationType, new PropertyInfoComparer()).ToArray(); PropertyInfo[] intersect = sourceType.Intersect(destinationType, new PropertyInfoComparer()).ToArray();
if (!intersect.Any()) if(!intersect.Any()) {
throw new InvalidOperationException("Types doesn't match"); throw new InvalidOperationException("Types doesn't match");
}
var map = new ObjectMap<TSource, TDestination>(intersect); ObjectMap<TSource, TDestination> map = new ObjectMap<TSource, TDestination>(intersect);
_maps.Add(map); this._maps.Add(map);
return map; return map;
} }
@ -207,166 +191,119 @@ namespace Swan.Mappers
/// </returns> /// </returns>
/// <exception cref="ArgumentNullException">source.</exception> /// <exception cref="ArgumentNullException">source.</exception>
/// <exception cref="InvalidOperationException">You can't map from type {source.GetType().Name} to {typeof(TDestination).Name}.</exception> /// <exception cref="InvalidOperationException">You can't map from type {source.GetType().Name} to {typeof(TDestination).Name}.</exception>
public TDestination Map<TDestination>(object source, bool autoResolve = true) public TDestination Map<TDestination>(Object source, Boolean autoResolve = true) {
{ if(source == null) {
if (source == null)
{
throw new ArgumentNullException(nameof(source)); throw new ArgumentNullException(nameof(source));
} }
var destination = Activator.CreateInstance<TDestination>(); TDestination destination = Activator.CreateInstance<TDestination>();
var map = _maps IObjectMap map = this._maps.FirstOrDefault(x => x.SourceType == source.GetType() && x.DestinationType == typeof(TDestination));
.FirstOrDefault(x => x.SourceType == source.GetType() && x.DestinationType == typeof(TDestination));
if (map != null) if(map != null) {
{ foreach(KeyValuePair<PropertyInfo, List<PropertyInfo>> property in map.Map) {
foreach (var property in map.Map) Object finalSource = property.Value.Aggregate(source, (current, sourceProperty) => sourceProperty.GetValue(current)!);
{
var finalSource = property.Value.Aggregate(source,
(current, sourceProperty) => sourceProperty.GetValue(current));
property.Key.SetValue(destination, finalSource); property.Key.SetValue(destination, finalSource);
} }
} } else {
else if(!autoResolve) {
{ throw new InvalidOperationException($"You can't map from type {source.GetType().Name} to {typeof(TDestination).Name}");
if (!autoResolve)
{
throw new InvalidOperationException(
$"You can't map from type {source.GetType().Name} to {typeof(TDestination).Name}");
} }
// Missing mapping, try to use default behavior // Missing mapping, try to use default behavior
Copy(source, destination); _ = Copy(source, destination);
} }
return destination; return destination;
} }
private static int CopyInternal( private static Int32 CopyInternal(Object target, Dictionary<String, Tuple<Type, Object>> sourceProperties, IEnumerable<String>? propertiesToCopy, IEnumerable<String>? ignoreProperties) {
object target,
Dictionary<string, Tuple<Type, object>> sourceProperties,
IEnumerable<string>? propertiesToCopy,
IEnumerable<string>? ignoreProperties)
{
// Filter properties // Filter properties
var requiredProperties = propertiesToCopy? IEnumerable<String>? requiredProperties = propertiesToCopy?.Where(p => !String.IsNullOrWhiteSpace(p)).Select(p => p.ToLowerInvariant());
.Where(p => !string.IsNullOrWhiteSpace(p))
.Select(p => p.ToLowerInvariant());
var ignoredProperties = ignoreProperties? IEnumerable<String>? ignoredProperties = ignoreProperties?.Where(p => !String.IsNullOrWhiteSpace(p)).Select(p => p.ToLowerInvariant());
.Where(p => !string.IsNullOrWhiteSpace(p))
.Select(p => p.ToLowerInvariant());
var properties = PropertyTypeCache.DefaultCache.Value IEnumerable<PropertyInfo> properties = PropertyTypeCache.DefaultCache.Value.RetrieveFilteredProperties(target.GetType(), true, x => x.CanWrite);
.RetrieveFilteredProperties(target.GetType(), true, x => x.CanWrite);
return properties return properties.Select(x => x.Name).Distinct().ToDictionary(x => x.ToLowerInvariant(), x => properties.First(y => y.Name == x)).Where(x => sourceProperties.Keys.Contains(x.Key)).When(() => requiredProperties != null, q => q.Where(y => requiredProperties.Contains(y.Key))).When(() => ignoredProperties != null, q => q.Where(y => !ignoredProperties.Contains(y.Key))).ToDictionary(x => x.Value, x => sourceProperties[x.Key]).Sum(x => TrySetValue(x.Key, x.Value, target) ? 1 : 0);
.Select(x => x.Name)
.Distinct()
.ToDictionary(x => x.ToLowerInvariant(), x => properties.First(y => y.Name == x))
.Where(x => sourceProperties.Keys.Contains(x.Key))
.When(() => requiredProperties != null, q => q.Where(y => requiredProperties.Contains(y.Key)))
.When(() => ignoredProperties != null, q => q.Where(y => !ignoredProperties.Contains(y.Key)))
.ToDictionary(x => x.Value, x => sourceProperties[x.Key])
.Sum(x => TrySetValue(x.Key, x.Value, target) ? 1 : 0);
} }
private static bool TrySetValue(PropertyInfo propertyInfo, Tuple<Type, object> property, object target) private static Boolean TrySetValue(PropertyInfo propertyInfo, Tuple<Type, Object> property, Object target) {
{ try {
try (Type type, Object value) = property;
{
var (type, value) = property;
if (type.IsEnum) if(type.IsEnum) {
{
propertyInfo.SetValue(target, propertyInfo.SetValue(target,
Enum.ToObject(propertyInfo.PropertyType, value)); Enum.ToObject(propertyInfo.PropertyType, value));
return true; return true;
} }
if (type.IsValueType || propertyInfo.PropertyType != type) if(type.IsValueType || propertyInfo.PropertyType != type) {
return propertyInfo.TrySetBasicType(value, target); return propertyInfo.TrySetBasicType(value, target);
}
if (propertyInfo.PropertyType.IsArray) if(propertyInfo.PropertyType.IsArray) {
{ _ = propertyInfo.TrySetArray(value as IEnumerable<Object>, target);
propertyInfo.TrySetArray(value as IEnumerable<object>, target);
return true; return true;
} }
propertyInfo.SetValue(target, GetValue(value, propertyInfo.PropertyType)); propertyInfo.SetValue(target, GetValue(value, propertyInfo.PropertyType));
return true; return true;
} } catch {
catch
{
// swallow // swallow
} }
return false; return false;
} }
private static object? GetValue(object source, Type targetType) private static Object? GetValue(Object source, Type targetType) {
{ if(source == null) {
if (source == null)
return null; return null;
}
object? target = null; Object? target = null;
source.CreateTarget(targetType, false, ref target); source.CreateTarget(targetType, false, ref target);
switch (source) switch(source) {
{ case String _:
case string _:
target = source; target = source;
break; break;
case IList sourceList when target is IList targetList: case IList sourceList when target is IList targetList:
var addMethod = targetType.GetMethods() MethodInfo addMethod = targetType.GetMethods().FirstOrDefault(m => m.Name == Formatters.Json.AddMethodName && m.IsPublic && m.GetParameters().Length == 1);
.FirstOrDefault(
m => m.Name == Formatters.Json.AddMethodName && m.IsPublic && m.GetParameters().Length == 1);
if (addMethod == null) return target; if(addMethod == null) {
return target;
var isItemValueType = targetList.GetType().GetElementType().IsValueType;
foreach (var item in sourceList)
{
try
{
targetList.Add(isItemValueType
? item
: item.CopyPropertiesToNew<object>());
} }
catch
{ Boolean? isItemValueType = targetList.GetType().GetElementType()?.IsValueType;
foreach(Object? item in sourceList) {
try {
if(isItemValueType != null) {
_ = targetList.Add((Boolean)isItemValueType ? item : item?.CopyPropertiesToNew<Object>());
}
} catch {
// ignored // ignored
} }
} }
break; break;
default: default:
source.CopyPropertiesTo(target); _ = source.CopyPropertiesTo(target);
break; break;
} }
return target; return target;
} }
private static Dictionary<string, Tuple<Type, object>> GetSourceMap(object source) private static Dictionary<String, Tuple<Type, Object>> GetSourceMap(Object source) {
{
// select distinct properties because they can be duplicated by inheritance // select distinct properties because they can be duplicated by inheritance
var sourceProperties = PropertyTypeCache.DefaultCache.Value PropertyInfo[] sourceProperties = PropertyTypeCache.DefaultCache.Value.RetrieveFilteredProperties(source.GetType(), true, x => x.CanRead).ToArray();
.RetrieveFilteredProperties(source.GetType(), true, x => x.CanRead)
.ToArray();
return sourceProperties return sourceProperties.Select(x => x.Name).Distinct().ToDictionary(x => x.ToLowerInvariant(), x => Tuple.Create(sourceProperties.First(y => y.Name == x).PropertyType, sourceProperties.First(y => y.Name == x).GetValue(source)))!;
.Select(x => x.Name)
.Distinct()
.ToDictionary(
x => x.ToLowerInvariant(),
x => Tuple.Create(sourceProperties.First(y => y.Name == x).PropertyType,
sourceProperties.First(y => y.Name == x).GetValue(source)));
} }
} }
} }

View File

@ -5,14 +5,12 @@ using System.Linq;
using System.Reflection; using System.Reflection;
using Swan.Reflection; using Swan.Reflection;
namespace Swan namespace Swan {
{
/// <summary> /// <summary>
/// Represents a quick object comparer using the public properties of an object /// Represents a quick object comparer using the public properties of an object
/// or the public members in a structure. /// or the public members in a structure.
/// </summary> /// </summary>
public static class ObjectComparer public static class ObjectComparer {
{
/// <summary> /// <summary>
/// Compare if two variables of the same type are equal. /// Compare if two variables of the same type are equal.
/// </summary> /// </summary>
@ -20,7 +18,7 @@ namespace Swan
/// <param name="left">The left.</param> /// <param name="left">The left.</param>
/// <param name="right">The right.</param> /// <param name="right">The right.</param>
/// <returns><c>true</c> if the variables are equal; otherwise, <c>false</c>.</returns> /// <returns><c>true</c> if the variables are equal; otherwise, <c>false</c>.</returns>
public static bool AreEqual<T>(T left, T right) => AreEqual(left, right, typeof(T)); public static Boolean AreEqual<T>(T left, T right) => AreEqual(left, right, typeof(T));
/// <summary> /// <summary>
/// Compare if two variables of the same type are equal. /// Compare if two variables of the same type are equal.
@ -32,17 +30,16 @@ namespace Swan
/// <c>true</c> if the variables are equal; otherwise, <c>false</c>. /// <c>true</c> if the variables are equal; otherwise, <c>false</c>.
/// </returns> /// </returns>
/// <exception cref="ArgumentNullException">targetType.</exception> /// <exception cref="ArgumentNullException">targetType.</exception>
public static bool AreEqual(object left, object right, Type targetType) public static Boolean AreEqual(Object left, Object right, Type targetType) {
{ if(targetType == null) {
if (targetType == null)
throw new ArgumentNullException(nameof(targetType)); throw new ArgumentNullException(nameof(targetType));
}
if (Definitions.BasicTypesInfo.Value.ContainsKey(targetType)) if(Definitions.BasicTypesInfo.Value.ContainsKey(targetType)) {
return Equals(left, right); return Equals(left, right);
}
return targetType.IsValueType || targetType.IsArray return targetType.IsValueType || targetType.IsArray ? AreStructsEqual(left, right, targetType) : AreObjectsEqual(left, right, targetType);
? AreStructsEqual(left, right, targetType)
: AreObjectsEqual(left, right, targetType);
} }
/// <summary> /// <summary>
@ -52,11 +49,7 @@ namespace Swan
/// <param name="left">The left.</param> /// <param name="left">The left.</param>
/// <param name="right">The right.</param> /// <param name="right">The right.</param>
/// <returns><c>true</c> if the objects are equal; otherwise, <c>false</c>.</returns> /// <returns><c>true</c> if the objects are equal; otherwise, <c>false</c>.</returns>
public static bool AreObjectsEqual<T>(T left, T right) public static Boolean AreObjectsEqual<T>(T left, T right) where T : class => AreObjectsEqual(left, right, typeof(T));
where T : class
{
return AreObjectsEqual(left, right, typeof(T));
}
/// <summary> /// <summary>
/// Compare if two objects of the same type are equal. /// Compare if two objects of the same type are equal.
@ -66,31 +59,29 @@ namespace Swan
/// <param name="targetType">Type of the target.</param> /// <param name="targetType">Type of the target.</param>
/// <returns><c>true</c> if the objects are equal; otherwise, <c>false</c>.</returns> /// <returns><c>true</c> if the objects are equal; otherwise, <c>false</c>.</returns>
/// <exception cref="ArgumentNullException">targetType.</exception> /// <exception cref="ArgumentNullException">targetType.</exception>
public static bool AreObjectsEqual(object left, object right, Type targetType) public static Boolean AreObjectsEqual(Object left, Object right, Type targetType) {
{ if(targetType == null) {
if (targetType == null)
throw new ArgumentNullException(nameof(targetType)); throw new ArgumentNullException(nameof(targetType));
}
var properties = PropertyTypeCache.DefaultCache.Value.RetrieveAllProperties(targetType).ToArray(); PropertyInfo[] properties = PropertyTypeCache.DefaultCache.Value.RetrieveAllProperties(targetType).ToArray();
foreach (var propertyTarget in properties) foreach(PropertyInfo propertyTarget in properties) {
{ Func<Object, Object> targetPropertyGetMethod = propertyTarget.GetCacheGetMethod();
var targetPropertyGetMethod = propertyTarget.GetCacheGetMethod();
if (propertyTarget.PropertyType.IsArray) if(propertyTarget.PropertyType.IsArray) {
{ IEnumerable leftObj = targetPropertyGetMethod(left) as IEnumerable;
var leftObj = targetPropertyGetMethod(left) as IEnumerable; IEnumerable rightObj = targetPropertyGetMethod(right) as IEnumerable;
var rightObj = targetPropertyGetMethod(right) as IEnumerable;
if (!AreEnumerationsEquals(leftObj, rightObj)) if(!AreEnumerationsEquals(leftObj, rightObj)) {
return false; return false;
} }
else } else {
{ if(!Equals(targetPropertyGetMethod(left), targetPropertyGetMethod(right))) {
if (!Equals(targetPropertyGetMethod(left), targetPropertyGetMethod(right)))
return false; return false;
} }
} }
}
return true; return true;
} }
@ -102,11 +93,7 @@ namespace Swan
/// <param name="left">The left.</param> /// <param name="left">The left.</param>
/// <param name="right">The right.</param> /// <param name="right">The right.</param>
/// <returns><c>true</c> if the structs are equal; otherwise, <c>false</c>.</returns> /// <returns><c>true</c> if the structs are equal; otherwise, <c>false</c>.</returns>
public static bool AreStructsEqual<T>(T left, T right) public static Boolean AreStructsEqual<T>(T left, T right) where T : struct => AreStructsEqual(left, right, typeof(T));
where T : struct
{
return AreStructsEqual(left, right, typeof(T));
}
/// <summary> /// <summary>
/// Compare if two structures of the same type are equal. /// Compare if two structures of the same type are equal.
@ -118,28 +105,29 @@ namespace Swan
/// <c>true</c> if the structs are equal; otherwise, <c>false</c>. /// <c>true</c> if the structs are equal; otherwise, <c>false</c>.
/// </returns> /// </returns>
/// <exception cref="ArgumentNullException">targetType.</exception> /// <exception cref="ArgumentNullException">targetType.</exception>
public static bool AreStructsEqual(object left, object right, Type targetType) public static Boolean AreStructsEqual(Object left, Object right, Type targetType) {
{ if(targetType == null) {
if (targetType == null)
throw new ArgumentNullException(nameof(targetType)); throw new ArgumentNullException(nameof(targetType));
}
var fields = new List<MemberInfo>(FieldTypeCache.DefaultCache.Value.RetrieveAllFields(targetType)) IEnumerable<MemberInfo> fields = new List<MemberInfo>(FieldTypeCache.DefaultCache.Value.RetrieveAllFields(targetType)).Union(PropertyTypeCache.DefaultCache.Value.RetrieveAllProperties(targetType));
.Union(PropertyTypeCache.DefaultCache.Value.RetrieveAllProperties(targetType));
foreach (var targetMember in fields) foreach(MemberInfo targetMember in fields) {
{ switch(targetMember) {
switch (targetMember)
{
case FieldInfo field: case FieldInfo field:
if (Equals(field.GetValue(left), field.GetValue(right)) == false) if(Equals(field.GetValue(left), field.GetValue(right)) == false) {
return false; return false;
}
break; break;
case PropertyInfo property: case PropertyInfo property:
var targetPropertyGetMethod = property.GetCacheGetMethod(); Func<Object, Object> targetPropertyGetMethod = property.GetCacheGetMethod();
if(targetPropertyGetMethod != null && if(targetPropertyGetMethod != null &&
!Equals(targetPropertyGetMethod(left), targetPropertyGetMethod(right))) !Equals(targetPropertyGetMethod(left), targetPropertyGetMethod(right))) {
return false; return false;
}
break; break;
} }
} }
@ -161,28 +149,27 @@ namespace Swan
/// or /// or
/// right. /// right.
/// </exception> /// </exception>
public static bool AreEnumerationsEquals<T>(T left, T right) public static Boolean AreEnumerationsEquals<T>(T left, T right) where T : IEnumerable {
where T : IEnumerable if(Equals(left, default(T))) {
{
if (Equals(left, default(T)))
throw new ArgumentNullException(nameof(left)); throw new ArgumentNullException(nameof(left));
}
if (Equals(right, default(T))) if(Equals(right, default(T))) {
throw new ArgumentNullException(nameof(right)); throw new ArgumentNullException(nameof(right));
}
var leftEnumerable = left.Cast<object>().ToArray(); Object[] leftEnumerable = left.Cast<Object>().ToArray();
var rightEnumerable = right.Cast<object>().ToArray(); Object[] rightEnumerable = right.Cast<Object>().ToArray();
if (leftEnumerable.Length != rightEnumerable.Length) if(leftEnumerable.Length != rightEnumerable.Length) {
return false; return false;
}
for (var i = 0; i < leftEnumerable.Length; i++) for(Int32 i = 0; i < leftEnumerable.Length; i++) {
{ Object leftEl = leftEnumerable[i];
var leftEl = leftEnumerable[i]; Object rightEl = rightEnumerable[i];
var rightEl = rightEnumerable[i];
if (!AreEqual(leftEl, rightEl, leftEl.GetType())) if(!AreEqual(leftEl, rightEl, leftEl.GetType())) {
{
return false; return false;
} }
} }

View File

@ -1,48 +1,50 @@
using System; using System;
namespace Swan namespace Swan {
{
/// <summary> /// <summary>
/// A utility class to compute paging or batching offsets. /// A utility class to compute paging or batching offsets.
/// </summary> /// </summary>
public class Paginator public class Paginator {
{
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Paginator" /> class. /// Initializes a new instance of the <see cref="Paginator" /> class.
/// </summary> /// </summary>
/// <param name="totalCount">The total count of items to page over.</param> /// <param name="totalCount">The total count of items to page over.</param>
/// <param name="pageSize">The desired size of individual pages.</param> /// <param name="pageSize">The desired size of individual pages.</param>
public Paginator(int totalCount, int pageSize) public Paginator(Int32 totalCount, Int32 pageSize) {
{ this.TotalCount = totalCount;
TotalCount = totalCount; this.PageSize = pageSize;
PageSize = pageSize; this.PageCount = this.ComputePageCount();
PageCount = ComputePageCount();
} }
/// <summary> /// <summary>
/// Gets the desired number of items per page. /// Gets the desired number of items per page.
/// </summary> /// </summary>
public int PageSize { get; } public Int32 PageSize {
get;
}
/// <summary> /// <summary>
/// Gets the total number of items to page over. /// Gets the total number of items to page over.
/// </summary> /// </summary>
public int TotalCount { get; } public Int32 TotalCount {
get;
}
/// <summary> /// <summary>
/// Gets the computed number of pages. /// Gets the computed number of pages.
/// </summary> /// </summary>
public int PageCount { get; } public Int32 PageCount {
get;
}
/// <summary> /// <summary>
/// Gets the start item index of the given page. /// Gets the start item index of the given page.
/// </summary> /// </summary>
/// <param name="pageIndex">Zero-based index of the page.</param> /// <param name="pageIndex">Zero-based index of the page.</param>
/// <returns>The start item index.</returns> /// <returns>The start item index.</returns>
public int GetFirstItemIndex(int pageIndex) public Int32 GetFirstItemIndex(Int32 pageIndex) {
{ pageIndex = this.FixPageIndex(pageIndex);
pageIndex = FixPageIndex(pageIndex); return pageIndex * this.PageSize;
return pageIndex * PageSize;
} }
/// <summary> /// <summary>
@ -50,10 +52,9 @@ namespace Swan
/// </summary> /// </summary>
/// <param name="pageIndex">Zero-based index of the page.</param> /// <param name="pageIndex">Zero-based index of the page.</param>
/// <returns>The end item index.</returns> /// <returns>The end item index.</returns>
public int GetLastItemIndex(int pageIndex) public Int32 GetLastItemIndex(Int32 pageIndex) {
{ Int32 startIndex = this.GetFirstItemIndex(pageIndex);
var startIndex = GetFirstItemIndex(pageIndex); return Math.Min(startIndex + this.PageSize - 1, this.TotalCount - 1);
return Math.Min(startIndex + PageSize - 1, TotalCount - 1);
} }
/// <summary> /// <summary>
@ -61,12 +62,9 @@ namespace Swan
/// </summary> /// </summary>
/// <param name="pageIndex">Zero-based index of the page.</param> /// <param name="pageIndex">Zero-based index of the page.</param>
/// <returns>The number of items that the page contains.</returns> /// <returns>The number of items that the page contains.</returns>
public int GetItemCount(int pageIndex) public Int32 GetItemCount(Int32 pageIndex) {
{ pageIndex = this.FixPageIndex(pageIndex);
pageIndex = FixPageIndex(pageIndex); return (pageIndex >= this.PageCount - 1) ? this.GetLastItemIndex(pageIndex) - this.GetFirstItemIndex(pageIndex) + 1 : this.PageSize;
return (pageIndex >= PageCount - 1)
? GetLastItemIndex(pageIndex) - GetFirstItemIndex(pageIndex) + 1
: PageSize;
} }
/// <summary> /// <summary>
@ -74,26 +72,14 @@ namespace Swan
/// </summary> /// </summary>
/// <param name="pageIndex">Index of the page.</param> /// <param name="pageIndex">Index of the page.</param>
/// <returns>A limit-bound index.</returns> /// <returns>A limit-bound index.</returns>
private int FixPageIndex(int pageIndex) private Int32 FixPageIndex(Int32 pageIndex) => pageIndex < 0 ? 0 : pageIndex >= this.PageCount ? this.PageCount - 1 : pageIndex;
{
if (pageIndex < 0) return 0;
return pageIndex >= PageCount ? PageCount - 1 : pageIndex;
}
/// <summary> /// <summary>
/// Computes the number of pages for the paginator. /// Computes the number of pages for the paginator.
/// </summary> /// </summary>
/// <returns>The page count.</returns> /// <returns>The page count.</returns>
private int ComputePageCount() private Int32 ComputePageCount() =>
{
// include this if when you always want at least 1 page // include this if when you always want at least 1 page
if (TotalCount == 0) this.TotalCount == 0 ? 0 : this.TotalCount % this.PageSize != 0 ? (this.TotalCount / this.PageSize) + 1 : this.TotalCount / this.PageSize;
return 0;
return TotalCount % PageSize != 0
? (TotalCount / PageSize) + 1
: TotalCount / PageSize;
}
} }
} }

View File

@ -1,31 +1,27 @@
using System; using System;
namespace Swan.Parsers namespace Swan.Parsers {
{
/// <summary> /// <summary>
/// Models an option specification. /// Models an option specification.
/// Based on CommandLine (Copyright 2005-2015 Giacomo Stelluti Scala and Contributors.). /// Based on CommandLine (Copyright 2005-2015 Giacomo Stelluti Scala and Contributors.).
/// </summary> /// </summary>
[AttributeUsage(AttributeTargets.Property)] [AttributeUsage(AttributeTargets.Property)]
public sealed class ArgumentOptionAttribute public sealed class ArgumentOptionAttribute
: Attribute : Attribute {
{
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ArgumentOptionAttribute"/> class. /// Initializes a new instance of the <see cref="ArgumentOptionAttribute"/> class.
/// The default long name will be inferred from target property. /// The default long name will be inferred from target property.
/// </summary> /// </summary>
public ArgumentOptionAttribute() public ArgumentOptionAttribute()
: this(string.Empty, string.Empty) : this(String.Empty, String.Empty) {
{
} }
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ArgumentOptionAttribute"/> class. /// Initializes a new instance of the <see cref="ArgumentOptionAttribute"/> class.
/// </summary> /// </summary>
/// <param name="longName">The long name of the option.</param> /// <param name="longName">The long name of the option.</param>
public ArgumentOptionAttribute(string longName) public ArgumentOptionAttribute(String longName)
: this(string.Empty, longName) : this(String.Empty, longName) {
{
} }
/// <summary> /// <summary>
@ -33,24 +29,21 @@ namespace Swan.Parsers
/// </summary> /// </summary>
/// <param name="shortName">The short name of the option.</param> /// <param name="shortName">The short name of the option.</param>
/// <param name="longName">The long name of the option or null if not used.</param> /// <param name="longName">The long name of the option or null if not used.</param>
public ArgumentOptionAttribute(char shortName, string longName) public ArgumentOptionAttribute(Char shortName, String longName)
: this(new string(shortName, 1), longName) : this(new String(shortName, 1), longName) {
{
} }
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ArgumentOptionAttribute"/> class. /// Initializes a new instance of the <see cref="ArgumentOptionAttribute"/> class.
/// </summary> /// </summary>
/// <param name="shortName">The short name of the option..</param> /// <param name="shortName">The short name of the option..</param>
public ArgumentOptionAttribute(char shortName) public ArgumentOptionAttribute(Char shortName)
: this(new string(shortName, 1), string.Empty) : this(new String(shortName, 1), String.Empty) {
{
} }
private ArgumentOptionAttribute(string shortName, string longName) private ArgumentOptionAttribute(String shortName, String longName) {
{ this.ShortName = shortName ?? throw new ArgumentNullException(nameof(shortName));
ShortName = shortName ?? throw new ArgumentNullException(nameof(shortName)); this.LongName = longName ?? throw new ArgumentNullException(nameof(longName));
LongName = longName ?? throw new ArgumentNullException(nameof(longName));
} }
/// <summary> /// <summary>
@ -59,7 +52,9 @@ namespace Swan.Parsers
/// <value> /// <value>
/// The long name. /// The long name.
/// </value> /// </value>
public string LongName { get; } public String LongName {
get;
}
/// <summary> /// <summary>
/// Gets a short name of this command line option, made of one character. /// Gets a short name of this command line option, made of one character.
@ -67,13 +62,15 @@ namespace Swan.Parsers
/// <value> /// <value>
/// The short name. /// The short name.
/// </value> /// </value>
public string ShortName { get; } public String ShortName {
get;
}
/// <summary> /// <summary>
/// When applying attribute to <see cref="System.Collections.Generic.IEnumerable{T}"/> target properties, /// When applying attribute to <see cref="System.Collections.Generic.IEnumerable{T}"/> target properties,
/// it allows you to split an argument and consume its content as a sequence. /// it allows you to split an argument and consume its content as a sequence.
/// </summary> /// </summary>
public char Separator { get; set; } = '\0'; public Char Separator { get; set; } = '\0';
/// <summary> /// <summary>
/// Gets or sets mapped property default value. /// Gets or sets mapped property default value.
@ -81,7 +78,9 @@ namespace Swan.Parsers
/// <value> /// <value>
/// The default value. /// The default value.
/// </value> /// </value>
public object DefaultValue { get; set; } public Object DefaultValue {
get; set;
}
/// <summary> /// <summary>
/// Gets or sets a value indicating whether a command line option is required. /// Gets or sets a value indicating whether a command line option is required.
@ -89,7 +88,9 @@ namespace Swan.Parsers
/// <value> /// <value>
/// <c>true</c> if required; otherwise, <c>false</c>. /// <c>true</c> if required; otherwise, <c>false</c>.
/// </value> /// </value>
public bool Required { get; set; } public Boolean Required {
get; set;
}
/// <summary> /// <summary>
/// Gets or sets a short description of this command line option. Usually a sentence summary. /// Gets or sets a short description of this command line option. Usually a sentence summary.
@ -97,6 +98,8 @@ namespace Swan.Parsers
/// <value> /// <value>
/// The help text. /// The help text.
/// </value> /// </value>
public string HelpText { get; set; } public String HelpText {
get; set;
}
} }
} }

View File

@ -4,157 +4,126 @@ using System.Linq;
using System.Reflection; using System.Reflection;
using Swan.Reflection; using Swan.Reflection;
namespace Swan.Parsers namespace Swan.Parsers {
{
/// <summary> /// <summary>
/// Provides methods to parse command line arguments. /// Provides methods to parse command line arguments.
/// ///
/// Based on CommandLine (Copyright 2005-2015 Giacomo Stelluti Scala and Contributors). /// Based on CommandLine (Copyright 2005-2015 Giacomo Stelluti Scala and Contributors).
/// </summary> /// </summary>
public partial class ArgumentParser public partial class ArgumentParser {
{ private sealed class Validator {
private sealed class Validator private readonly Object _instance;
{ private readonly IEnumerable<String> _args;
private readonly object _instance;
private readonly IEnumerable<string> _args;
private readonly List<PropertyInfo> _updatedList = new List<PropertyInfo>(); private readonly List<PropertyInfo> _updatedList = new List<PropertyInfo>();
private readonly ArgumentParserSettings _settings; private readonly ArgumentParserSettings _settings;
private readonly PropertyInfo[] _properties; private readonly PropertyInfo[] _properties;
public Validator( public Validator(PropertyInfo[] properties, IEnumerable<String> args, Object instance, ArgumentParserSettings settings) {
PropertyInfo[] properties, this._args = args;
IEnumerable<string> args, this._instance = instance;
object instance, this._settings = settings;
ArgumentParserSettings settings) this._properties = properties;
{
_args = args;
_instance = instance;
_settings = settings;
_properties = properties;
PopulateInstance(); this.PopulateInstance();
SetDefaultValues(); this.SetDefaultValues();
GetRequiredList(); this.GetRequiredList();
} }
public List<string> UnknownList { get; } = new List<string>(); public List<String> UnknownList { get; } = new List<String>();
public List<string> RequiredList { get; } = new List<string>(); public List<String> RequiredList { get; } = new List<String>();
public bool IsValid() => (_settings.IgnoreUnknownArguments || !UnknownList.Any()) && !RequiredList.Any(); public Boolean IsValid() => (this._settings.IgnoreUnknownArguments || !this.UnknownList.Any()) && !this.RequiredList.Any();
public IEnumerable<ArgumentOptionAttribute> GetPropertiesOptions() public IEnumerable<ArgumentOptionAttribute> GetPropertiesOptions() => this._properties.Select(p => AttributeCache.DefaultCache.Value.RetrieveOne<ArgumentOptionAttribute>(p)).Where(x => x != null);
=> _properties.Select(p => AttributeCache.DefaultCache.Value.RetrieveOne<ArgumentOptionAttribute>(p))
.Where(x => x != null);
private void GetRequiredList() private void GetRequiredList() {
{ foreach(PropertyInfo targetProperty in this._properties) {
foreach (var targetProperty in _properties) ArgumentOptionAttribute optionAttr = AttributeCache.DefaultCache.Value.RetrieveOne<ArgumentOptionAttribute>(targetProperty);
{
var optionAttr = AttributeCache.DefaultCache.Value.RetrieveOne<ArgumentOptionAttribute>(targetProperty);
if (optionAttr == null || optionAttr.Required == false) if(optionAttr == null || optionAttr.Required == false) {
continue; continue;
}
if (targetProperty.GetValue(_instance) == null) if(targetProperty.GetValue(this._instance) == null) {
{ this.RequiredList.Add(optionAttr.LongName ?? optionAttr.ShortName);
RequiredList.Add(optionAttr.LongName ?? optionAttr.ShortName);
} }
} }
} }
private void SetDefaultValues() private void SetDefaultValues() {
{ foreach(PropertyInfo targetProperty in this._properties.Except(this._updatedList)) {
foreach (var targetProperty in _properties.Except(_updatedList)) ArgumentOptionAttribute optionAttr = AttributeCache.DefaultCache.Value.RetrieveOne<ArgumentOptionAttribute>(targetProperty);
{
var optionAttr = AttributeCache.DefaultCache.Value.RetrieveOne<ArgumentOptionAttribute>(targetProperty);
var defaultValue = optionAttr?.DefaultValue; Object defaultValue = optionAttr?.DefaultValue;
if (defaultValue == null) if(defaultValue == null) {
continue; continue;
}
if (SetPropertyValue(targetProperty, defaultValue.ToString(), _instance, optionAttr)) if(this.SetPropertyValue(targetProperty, defaultValue.ToString(), this._instance, optionAttr)) {
_updatedList.Add(targetProperty); this._updatedList.Add(targetProperty);
}
} }
} }
private void PopulateInstance() private void PopulateInstance() {
{ const Char dash = '-';
const char dash = '-'; String propertyName = String.Empty;
var propertyName = string.Empty;
foreach (var arg in _args) foreach(String arg in this._args) {
{ Boolean ignoreSetValue = String.IsNullOrWhiteSpace(propertyName);
var ignoreSetValue = string.IsNullOrWhiteSpace(propertyName);
if (ignoreSetValue) if(ignoreSetValue) {
{ if(String.IsNullOrWhiteSpace(arg) || arg[0] != dash) {
if (string.IsNullOrWhiteSpace(arg) || arg[0] != dash) continue; continue;
}
propertyName = arg.Substring(1); propertyName = arg.Substring(1);
if (!string.IsNullOrWhiteSpace(propertyName) && propertyName[0] == dash) if(!String.IsNullOrWhiteSpace(propertyName) && propertyName[0] == dash) {
propertyName = propertyName.Substring(1); propertyName = propertyName.Substring(1);
} }
}
var targetProperty = TryGetProperty(propertyName); PropertyInfo targetProperty = this.TryGetProperty(propertyName);
if (targetProperty == null) if(targetProperty == null) {
{
// Skip if the property is not found // Skip if the property is not found
UnknownList.Add(propertyName); this.UnknownList.Add(propertyName);
continue; continue;
} }
if (!ignoreSetValue && SetPropertyValue(targetProperty, arg, _instance)) if(!ignoreSetValue && this.SetPropertyValue(targetProperty, arg, this._instance)) {
{ this._updatedList.Add(targetProperty);
_updatedList.Add(targetProperty); propertyName = String.Empty;
propertyName = string.Empty; } else if(targetProperty.PropertyType == typeof(Boolean)) {
}
else if (targetProperty.PropertyType == typeof(bool))
{
// If the arg is a boolean property set it to true. // If the arg is a boolean property set it to true.
targetProperty.SetValue(_instance, true); targetProperty.SetValue(this._instance, true);
_updatedList.Add(targetProperty); this._updatedList.Add(targetProperty);
propertyName = string.Empty; propertyName = String.Empty;
} }
} }
if (!string.IsNullOrEmpty(propertyName)) if(!String.IsNullOrEmpty(propertyName)) {
{ this.UnknownList.Add(propertyName);
UnknownList.Add(propertyName);
} }
} }
private bool SetPropertyValue( private Boolean SetPropertyValue(PropertyInfo targetProperty, String propertyValueString, Object result, ArgumentOptionAttribute optionAttr = null) {
PropertyInfo targetProperty, if(!targetProperty.PropertyType.IsEnum) {
string propertyValueString, return targetProperty.PropertyType.IsArray ? targetProperty.TrySetArray(propertyValueString.Split(optionAttr?.Separator ?? ','), result) : targetProperty.TrySetBasicType(propertyValueString, result);
object result,
ArgumentOptionAttribute optionAttr = null)
{
if (!targetProperty.PropertyType.IsEnum)
{
return targetProperty.PropertyType.IsArray
? targetProperty.TrySetArray(propertyValueString.Split(optionAttr?.Separator ?? ','), result)
: targetProperty.TrySetBasicType(propertyValueString, result);
} }
var parsedValue = Enum.Parse( Object parsedValue = Enum.Parse(targetProperty.PropertyType, propertyValueString, this._settings.CaseInsensitiveEnumValues);
targetProperty.PropertyType,
propertyValueString,
_settings.CaseInsensitiveEnumValues);
targetProperty.SetValue(result, Enum.ToObject(targetProperty.PropertyType, parsedValue)); targetProperty.SetValue(result, Enum.ToObject(targetProperty.PropertyType, parsedValue));
return true; return true;
} }
private PropertyInfo TryGetProperty(string propertyName) private PropertyInfo TryGetProperty(String propertyName) => this._properties.FirstOrDefault(p => String.Equals(AttributeCache.DefaultCache.Value.RetrieveOne<ArgumentOptionAttribute>(p)?.LongName, propertyName, this._settings.NameComparer) || String.Equals(AttributeCache.DefaultCache.Value.RetrieveOne<ArgumentOptionAttribute>(p)?.ShortName, propertyName, this._settings.NameComparer));
=> _properties.FirstOrDefault(p =>
string.Equals(AttributeCache.DefaultCache.Value.RetrieveOne<ArgumentOptionAttribute>(p)?.LongName, propertyName, _settings.NameComparer) ||
string.Equals(AttributeCache.DefaultCache.Value.RetrieveOne<ArgumentOptionAttribute>(p)?.ShortName, propertyName, _settings.NameComparer));
} }
} }
} }

View File

@ -1,54 +1,46 @@
using System; #nullable enable
using System;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using Swan.Reflection; using Swan.Reflection;
namespace Swan.Parsers namespace Swan.Parsers {
{
/// <summary> /// <summary>
/// Provides methods to parse command line arguments. /// Provides methods to parse command line arguments.
/// </summary> /// </summary>
public partial class ArgumentParser public partial class ArgumentParser {
{ private sealed class TypeResolver<T> {
private sealed class TypeResolver<T> private readonly String _selectedVerb;
{
private readonly string _selectedVerb;
private PropertyInfo[]? _properties; private PropertyInfo[]? _properties;
public TypeResolver(string selectedVerb) public TypeResolver(String selectedVerb) => this._selectedVerb = selectedVerb;
{
_selectedVerb = selectedVerb; public PropertyInfo[]? Properties => this._properties?.Any() == true ? this._properties : null;
public Object? GetOptionsObject(T instance) {
this._properties = PropertyTypeCache.DefaultCache.Value.RetrieveAllProperties<T>(true).ToArray();
if(!this._properties.Any(x => x.GetCustomAttributes(typeof(VerbOptionAttribute), false).Any())) {
return instance;
} }
public PropertyInfo[]? Properties => _properties?.Any() == true ? _properties : null; PropertyInfo? selectedVerb = String.IsNullOrWhiteSpace(this._selectedVerb) ? null : this._properties.FirstOrDefault(x => AttributeCache.DefaultCache.Value.RetrieveOne<VerbOptionAttribute>(x).Name == this._selectedVerb);
public object? GetOptionsObject(T instance) if(selectedVerb == null) {
{ return null;
_properties = PropertyTypeCache.DefaultCache.Value.RetrieveAllProperties<T>(true).ToArray(); }
if (!_properties.Any(x => x.GetCustomAttributes(typeof(VerbOptionAttribute), false).Any())) Type? type = instance?.GetType();
return instance;
var selectedVerb = string.IsNullOrWhiteSpace(_selectedVerb) PropertyInfo? verbProperty = type?.GetProperty(selectedVerb.Name);
? null
: _properties.FirstOrDefault(x =>
AttributeCache.DefaultCache.Value.RetrieveOne<VerbOptionAttribute>(x).Name == _selectedVerb);
if (selectedVerb == null) return null; if(verbProperty?.GetValue(instance) == null) {
Object? propertyInstance = Activator.CreateInstance(selectedVerb.PropertyType);
var type = instance.GetType();
var verbProperty = type.GetProperty(selectedVerb.Name);
if (verbProperty?.GetValue(instance) == null)
{
var propertyInstance = Activator.CreateInstance(selectedVerb.PropertyType);
verbProperty?.SetValue(instance, propertyInstance); verbProperty?.SetValue(instance, propertyInstance);
} }
_properties = PropertyTypeCache.DefaultCache.Value.RetrieveAllProperties(selectedVerb.PropertyType, true) this._properties = PropertyTypeCache.DefaultCache.Value.RetrieveAllProperties(selectedVerb.PropertyType, true).ToArray();
.ToArray();
return verbProperty?.GetValue(instance); return verbProperty?.GetValue(instance);
} }

View File

@ -3,8 +3,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
namespace Swan.Parsers namespace Swan.Parsers {
{
/// <summary> /// <summary>
/// Provides methods to parse command line arguments. /// Provides methods to parse command line arguments.
/// Based on CommandLine (Copyright 2005-2015 Giacomo Stelluti Scala and Contributors.). /// Based on CommandLine (Copyright 2005-2015 Giacomo Stelluti Scala and Contributors.).
@ -102,14 +101,11 @@ namespace Swan.Parsers
/// } /// }
/// </code> /// </code>
/// </example> /// </example>
public partial class ArgumentParser public partial class ArgumentParser {
{
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ArgumentParser"/> class. /// Initializes a new instance of the <see cref="ArgumentParser"/> class.
/// </summary> /// </summary>
public ArgumentParser() public ArgumentParser() : this(new ArgumentParserSettings()) {
: this(new ArgumentParserSettings())
{
} }
/// <summary> /// <summary>
@ -117,10 +113,7 @@ namespace Swan.Parsers
/// configurable with <see cref="ArgumentParserSettings" /> using a delegate. /// configurable with <see cref="ArgumentParserSettings" /> using a delegate.
/// </summary> /// </summary>
/// <param name="parseSettings">The parse settings.</param> /// <param name="parseSettings">The parse settings.</param>
public ArgumentParser(ArgumentParserSettings parseSettings) public ArgumentParser(ArgumentParserSettings parseSettings) => this.Settings = parseSettings ?? throw new ArgumentNullException(nameof(parseSettings));
{
Settings = parseSettings ?? throw new ArgumentNullException(nameof(parseSettings));
}
/// <summary> /// <summary>
/// Gets the current. /// Gets the current.
@ -136,28 +129,8 @@ namespace Swan.Parsers
/// <value> /// <value>
/// The settings. /// The settings.
/// </value> /// </value>
public ArgumentParserSettings Settings { get; } public ArgumentParserSettings Settings {
get;
/// <summary>
/// Parses a string array of command line arguments constructing values in an instance of type <typeparamref name="T" />.
/// </summary>
/// <typeparam name="T">The type of the options.</typeparam>
/// <param name="args">The arguments.</param>
/// <param name="instance">The instance.</param>
/// <returns>
/// <c>true</c> if was converted successfully; otherwise, <c>false</c>.
/// </returns>
/// <exception cref="ArgumentNullException">
/// The exception that is thrown when a null reference (Nothing in Visual Basic)
/// is passed to a method that does not accept it as a valid argument.
/// </exception>
/// <exception cref="InvalidOperationException">
/// The exception that is thrown when a method call is invalid for the object's current state.
/// </exception>
public bool ParseArguments<T>(IEnumerable<string> args, out T instance)
{
instance = Activator.CreateInstance<T>();
return ParseArguments(args, instance);
} }
/// <summary> /// <summary>
@ -176,78 +149,94 @@ namespace Swan.Parsers
/// <exception cref="InvalidOperationException"> /// <exception cref="InvalidOperationException">
/// The exception that is thrown when a method call is invalid for the object's current state. /// The exception that is thrown when a method call is invalid for the object's current state.
/// </exception> /// </exception>
public bool ParseArguments<T>(IEnumerable<string> args, T instance) public Boolean ParseArguments<T>(IEnumerable<String> args, out T instance) {
{ instance = Activator.CreateInstance<T>();
if (args == null) return this.ParseArguments(args, instance);
}
/// <summary>
/// Parses a string array of command line arguments constructing values in an instance of type <typeparamref name="T" />.
/// </summary>
/// <typeparam name="T">The type of the options.</typeparam>
/// <param name="args">The arguments.</param>
/// <param name="instance">The instance.</param>
/// <returns>
/// <c>true</c> if was converted successfully; otherwise, <c>false</c>.
/// </returns>
/// <exception cref="ArgumentNullException">
/// The exception that is thrown when a null reference (Nothing in Visual Basic)
/// is passed to a method that does not accept it as a valid argument.
/// </exception>
/// <exception cref="InvalidOperationException">
/// The exception that is thrown when a method call is invalid for the object's current state.
/// </exception>
public Boolean ParseArguments<T>(IEnumerable<String> args, T instance) {
if(args == null) {
throw new ArgumentNullException(nameof(args)); throw new ArgumentNullException(nameof(args));
}
if (Equals(instance, default(T))) if(Equals(instance, default(T))) {
throw new ArgumentNullException(nameof(instance)); throw new ArgumentNullException(nameof(instance));
}
var typeResolver = new TypeResolver<T>(args.FirstOrDefault()); TypeResolver<T> typeResolver = new TypeResolver<T>(args.FirstOrDefault());
var options = typeResolver.GetOptionsObject(instance); Object options = typeResolver.GetOptionsObject(instance);
if (options == null) if(options == null) {
{
ReportUnknownVerb<T>(); ReportUnknownVerb<T>();
return false; return false;
} }
if (typeResolver.Properties == null) if(typeResolver.Properties == null) {
throw new InvalidOperationException($"Type {typeof(T).Name} is not valid"); throw new InvalidOperationException($"Type {typeof(T).Name} is not valid");
}
var validator = new Validator(typeResolver.Properties, args, options, Settings); Validator validator = new Validator(typeResolver.Properties, args, options, this.Settings);
if (validator.IsValid()) if(validator.IsValid()) {
return true; return true;
}
ReportIssues(validator); this.ReportIssues(validator);
return false; return false;
} }
private static void ReportUnknownVerb<T>() private static void ReportUnknownVerb<T>() {
{
Terminal.WriteLine("No verb was specified", ConsoleColor.Red); Terminal.WriteLine("No verb was specified", ConsoleColor.Red);
Terminal.WriteLine("Valid verbs:", ConsoleColor.Cyan); Terminal.WriteLine("Valid verbs:", ConsoleColor.Cyan);
PropertyTypeCache.DefaultCache.Value PropertyTypeCache.DefaultCache.Value.RetrieveAllProperties<T>(true).Select(x => AttributeCache.DefaultCache.Value.RetrieveOne<VerbOptionAttribute>(x)).Where(x => x != null).ToList().ForEach(x => Terminal.WriteLine(x.ToString(), ConsoleColor.Cyan));
.RetrieveAllProperties<T>(true)
.Select(x => AttributeCache.DefaultCache.Value.RetrieveOne<VerbOptionAttribute>(x))
.Where(x => x != null)
.ToList()
.ForEach(x => Terminal.WriteLine(x.ToString(), ConsoleColor.Cyan));
} }
private void ReportIssues(Validator validator) private void ReportIssues(Validator validator) {
{ if(this.Settings.WriteBanner) {
if (Settings.WriteBanner)
Terminal.WriteWelcomeBanner(); Terminal.WriteWelcomeBanner();
}
var options = validator.GetPropertiesOptions(); IEnumerable<ArgumentOptionAttribute> options = validator.GetPropertiesOptions();
foreach (var option in options) foreach(ArgumentOptionAttribute option in options) {
{ Terminal.WriteLine(String.Empty);
Terminal.WriteLine(string.Empty);
// TODO: If Enum list values // TODO: If Enum list values
var shortName = string.IsNullOrWhiteSpace(option.ShortName) ? string.Empty : $"-{option.ShortName}"; String shortName = String.IsNullOrWhiteSpace(option.ShortName) ? String.Empty : $"-{option.ShortName}";
var longName = string.IsNullOrWhiteSpace(option.LongName) ? string.Empty : $"--{option.LongName}"; String longName = String.IsNullOrWhiteSpace(option.LongName) ? String.Empty : $"--{option.LongName}";
var comma = string.IsNullOrWhiteSpace(shortName) || string.IsNullOrWhiteSpace(longName) String comma = String.IsNullOrWhiteSpace(shortName) || String.IsNullOrWhiteSpace(longName) ? String.Empty : ", ";
? string.Empty String defaultValue = option.DefaultValue == null ? String.Empty : $"(Default: {option.DefaultValue}) ";
: ", ";
var defaultValue = option.DefaultValue == null ? string.Empty : $"(Default: {option.DefaultValue}) ";
Terminal.WriteLine($" {shortName}{comma}{longName}\t\t{defaultValue}{option.HelpText}", ConsoleColor.Cyan); Terminal.WriteLine($" {shortName}{comma}{longName}\t\t{defaultValue}{option.HelpText}", ConsoleColor.Cyan);
} }
Terminal.WriteLine(string.Empty); Terminal.WriteLine(String.Empty);
Terminal.WriteLine(" --help\t\tDisplay this help screen.", ConsoleColor.Cyan); Terminal.WriteLine(" --help\t\tDisplay this help screen.", ConsoleColor.Cyan);
if (validator.UnknownList.Any()) if(validator.UnknownList.Any()) {
Terminal.WriteLine($"Unknown arguments: {string.Join(", ", validator.UnknownList)}", ConsoleColor.Red); Terminal.WriteLine($"Unknown arguments: {String.Join(", ", validator.UnknownList)}", ConsoleColor.Red);
}
if (validator.RequiredList.Any()) if(validator.RequiredList.Any()) {
Terminal.WriteLine($"Required arguments: {string.Join(", ", validator.RequiredList)}", ConsoleColor.Red); Terminal.WriteLine($"Required arguments: {String.Join(", ", validator.RequiredList)}", ConsoleColor.Red);
}
} }
} }
} }

View File

@ -1,20 +1,18 @@
using System; using System;
namespace Swan.Parsers namespace Swan.Parsers {
{
/// <summary> /// <summary>
/// Provides settings for <see cref="ArgumentParser"/>. /// Provides settings for <see cref="ArgumentParser"/>.
/// Based on CommandLine (Copyright 2005-2015 Giacomo Stelluti Scala and Contributors.). /// Based on CommandLine (Copyright 2005-2015 Giacomo Stelluti Scala and Contributors.).
/// </summary> /// </summary>
public class ArgumentParserSettings public class ArgumentParserSettings {
{
/// <summary> /// <summary>
/// Gets or sets a value indicating whether [write banner]. /// Gets or sets a value indicating whether [write banner].
/// </summary> /// </summary>
/// <value> /// <value>
/// <c>true</c> if [write banner]; otherwise, <c>false</c>. /// <c>true</c> if [write banner]; otherwise, <c>false</c>.
/// </value> /// </value>
public bool WriteBanner { get; set; } = true; public Boolean WriteBanner { get; set; } = true;
/// <summary> /// <summary>
/// Gets or sets a value indicating whether perform case sensitive comparisons. /// Gets or sets a value indicating whether perform case sensitive comparisons.
@ -24,7 +22,7 @@ namespace Swan.Parsers
/// <value> /// <value>
/// <c>true</c> if [case sensitive]; otherwise, <c>false</c>. /// <c>true</c> if [case sensitive]; otherwise, <c>false</c>.
/// </value> /// </value>
public bool CaseSensitive { get; set; } = false; public Boolean CaseSensitive { get; set; } = false;
/// <summary> /// <summary>
/// Gets or sets a value indicating whether perform case sensitive comparisons of <i>values</i>. /// Gets or sets a value indicating whether perform case sensitive comparisons of <i>values</i>.
@ -33,7 +31,7 @@ namespace Swan.Parsers
/// <value> /// <value>
/// <c>true</c> if [case insensitive enum values]; otherwise, <c>false</c>. /// <c>true</c> if [case insensitive enum values]; otherwise, <c>false</c>.
/// </value> /// </value>
public bool CaseInsensitiveEnumValues { get; set; } = true; public Boolean CaseInsensitiveEnumValues { get; set; } = true;
/// <summary> /// <summary>
/// Gets or sets a value indicating whether the parser shall move on to the next argument and ignore the given argument if it /// Gets or sets a value indicating whether the parser shall move on to the next argument and ignore the given argument if it
@ -46,8 +44,8 @@ namespace Swan.Parsers
/// This allows fragmented version class parsing, useful for project with add-on where add-ons also requires command line arguments but /// This allows fragmented version class parsing, useful for project with add-on where add-ons also requires command line arguments but
/// when these are unknown by the main program at build time. /// when these are unknown by the main program at build time.
/// </remarks> /// </remarks>
public bool IgnoreUnknownArguments { get; set; } = true; public Boolean IgnoreUnknownArguments { get; set; } = true;
internal StringComparison NameComparer => CaseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase; internal StringComparison NameComparer => this.CaseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase;
} }
} }

View File

@ -3,21 +3,18 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Linq.Expressions; using System.Linq.Expressions;
namespace Swan.Parsers namespace Swan.Parsers {
{
/// <summary> /// <summary>
/// Represents a generic expression parser. /// Represents a generic expression parser.
/// </summary> /// </summary>
public abstract class ExpressionParser public abstract class ExpressionParser {
{
/// <summary> /// <summary>
/// Resolves the expression. /// Resolves the expression.
/// </summary> /// </summary>
/// <typeparam name="T">The type of expression result.</typeparam> /// <typeparam name="T">The type of expression result.</typeparam>
/// <param name="tokens">The tokens.</param> /// <param name="tokens">The tokens.</param>
/// <returns>The representation of the expression parsed.</returns> /// <returns>The representation of the expression parsed.</returns>
public virtual T ResolveExpression<T>(IEnumerable<Token> tokens) => public virtual T ResolveExpression<T>(IEnumerable<Token> tokens) => this.ResolveExpression<T>(tokens, System.Globalization.CultureInfo.InvariantCulture);
ResolveExpression<T>(tokens, System.Globalization.CultureInfo.InvariantCulture);
/// <summary> /// <summary>
/// Resolves the expression. /// Resolves the expression.
@ -26,9 +23,8 @@ namespace Swan.Parsers
/// <param name="tokens">The tokens.</param> /// <param name="tokens">The tokens.</param>
/// <param name="formatProvider">The format provider.</param> /// <param name="formatProvider">The format provider.</param>
/// <returns>The representation of the expression parsed.</returns> /// <returns>The representation of the expression parsed.</returns>
public virtual T ResolveExpression<T>(IEnumerable<Token> tokens, IFormatProvider formatProvider) public virtual T ResolveExpression<T>(IEnumerable<Token> tokens, IFormatProvider formatProvider) {
{ UnaryExpression conversion = Expression.Convert(this.Parse(tokens, formatProvider), typeof(T));
var conversion = Expression.Convert(Parse(tokens,formatProvider), typeof(T));
return Expression.Lambda<Func<T>>(conversion).Compile()(); return Expression.Lambda<Func<T>>(conversion).Compile()();
} }
@ -39,8 +35,7 @@ namespace Swan.Parsers
/// <returns> /// <returns>
/// The final expression. /// The final expression.
/// </returns> /// </returns>
public virtual Expression Parse(IEnumerable<Token> tokens) => public virtual Expression Parse(IEnumerable<Token> tokens) => this.Parse(tokens, System.Globalization.CultureInfo.InvariantCulture);
Parse(tokens, System.Globalization.CultureInfo.InvariantCulture);
/// <summary> /// <summary>
/// Parses the specified tokens. /// Parses the specified tokens.
@ -50,17 +45,15 @@ namespace Swan.Parsers
/// <returns> /// <returns>
/// The final expression. /// The final expression.
/// </returns> /// </returns>
public virtual Expression Parse(IEnumerable<Token> tokens, IFormatProvider formatProvider) public virtual Expression Parse(IEnumerable<Token> tokens, IFormatProvider formatProvider) {
{ List<Stack<Expression>> expressionStack = new List<Stack<Expression>>();
var expressionStack = new List<Stack<Expression>>();
foreach (var token in tokens) foreach(Token token in tokens) {
{ if(expressionStack.Any() == false) {
if (expressionStack.Any() == false)
expressionStack.Add(new Stack<Expression>()); expressionStack.Add(new Stack<Expression>());
}
switch (token.Type) switch(token.Type) {
{
case TokenType.Wall: case TokenType.Wall:
expressionStack.Add(new Stack<Expression>()); expressionStack.Add(new Stack<Expression>());
break; break;
@ -68,21 +61,20 @@ namespace Swan.Parsers
expressionStack.Last().Push(Expression.Constant(Convert.ToDecimal(token.Value, formatProvider))); expressionStack.Last().Push(Expression.Constant(Convert.ToDecimal(token.Value, formatProvider)));
break; break;
case TokenType.Variable: case TokenType.Variable:
ResolveVariable(token.Value, expressionStack.Last()); this.ResolveVariable(token.Value, expressionStack.Last());
break; break;
case TokenType.String: case TokenType.String:
expressionStack.Last().Push(Expression.Constant(token.Value)); expressionStack.Last().Push(Expression.Constant(token.Value));
break; break;
case TokenType.Operator: case TokenType.Operator:
ResolveOperator(token.Value, expressionStack.Last()); this.ResolveOperator(token.Value, expressionStack.Last());
break; break;
case TokenType.Function: case TokenType.Function:
ResolveFunction(token.Value, expressionStack.Last()); this.ResolveFunction(token.Value, expressionStack.Last());
if (expressionStack.Count > 1 && expressionStack.Last().Count == 1) if(expressionStack.Count > 1 && expressionStack.Last().Count == 1) {
{ Expression lastValue = expressionStack.Last().Pop();
var lastValue = expressionStack.Last().Pop(); _ = expressionStack.Remove(expressionStack.Last());
expressionStack.Remove(expressionStack.Last());
expressionStack.Last().Push(lastValue); expressionStack.Last().Push(lastValue);
} }
@ -98,20 +90,20 @@ namespace Swan.Parsers
/// </summary> /// </summary>
/// <param name="value">The value.</param> /// <param name="value">The value.</param>
/// <param name="expressionStack">The expression stack.</param> /// <param name="expressionStack">The expression stack.</param>
public abstract void ResolveVariable(string value, Stack<Expression> expressionStack); public abstract void ResolveVariable(String value, Stack<Expression> expressionStack);
/// <summary> /// <summary>
/// Resolves the operator. /// Resolves the operator.
/// </summary> /// </summary>
/// <param name="value">The value.</param> /// <param name="value">The value.</param>
/// <param name="expressionStack">The expression stack.</param> /// <param name="expressionStack">The expression stack.</param>
public abstract void ResolveOperator(string value, Stack<Expression> expressionStack); public abstract void ResolveOperator(String value, Stack<Expression> expressionStack);
/// <summary> /// <summary>
/// Resolves the function. /// Resolves the function.
/// </summary> /// </summary>
/// <param name="value">The value.</param> /// <param name="value">The value.</param>
/// <param name="expressionStack">The expression stack.</param> /// <param name="expressionStack">The expression stack.</param>
public abstract void ResolveFunction(string value, Stack<Expression> expressionStack); public abstract void ResolveFunction(String value, Stack<Expression> expressionStack);
} }
} }

View File

@ -1,17 +1,19 @@
namespace Swan.Parsers using System;
{
namespace Swan.Parsers {
/// <summary> /// <summary>
/// Represents an operator with precedence. /// Represents an operator with precedence.
/// </summary> /// </summary>
public class Operator public class Operator {
{
/// <summary> /// <summary>
/// Gets or sets the name. /// Gets or sets the name.
/// </summary> /// </summary>
/// <value> /// <value>
/// The name. /// The name.
/// </value> /// </value>
public string Name { get; set; } public String Name {
get; set;
}
/// <summary> /// <summary>
/// Gets or sets the precedence. /// Gets or sets the precedence.
@ -19,7 +21,9 @@
/// <value> /// <value>
/// The precedence. /// The precedence.
/// </value> /// </value>
public int Precedence { get; set; } public Int32 Precedence {
get; set;
}
/// <summary> /// <summary>
/// Gets or sets a value indicating whether [right associative]. /// Gets or sets a value indicating whether [right associative].
@ -27,6 +31,8 @@
/// <value> /// <value>
/// <c>true</c> if [right associative]; otherwise, <c>false</c>. /// <c>true</c> if [right associative]; otherwise, <c>false</c>.
/// </value> /// </value>
public bool RightAssociative { get; set; } public Boolean RightAssociative {
get; set;
}
} }
} }

View File

@ -1,19 +1,18 @@
namespace Swan.Parsers using System;
{
namespace Swan.Parsers {
/// <summary> /// <summary>
/// Represents a Token structure. /// Represents a Token structure.
/// </summary> /// </summary>
public struct Token public struct Token {
{
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Token"/> struct. /// Initializes a new instance of the <see cref="Token"/> struct.
/// </summary> /// </summary>
/// <param name="type">The type.</param> /// <param name="type">The type.</param>
/// <param name="value">The value.</param> /// <param name="value">The value.</param>
public Token(TokenType type, string value) public Token(TokenType type, String value) {
{ this.Type = type;
Type = type; this.Value = type == TokenType.Function || type == TokenType.Operator ? value.ToLowerInvariant() : value;
Value = type == TokenType.Function || type == TokenType.Operator ? value.ToLowerInvariant() : value;
} }
/// <summary> /// <summary>
@ -22,7 +21,9 @@
/// <value> /// <value>
/// The type. /// The type.
/// </value> /// </value>
public TokenType Type { get; set; } public TokenType Type {
get; set;
}
/// <summary> /// <summary>
/// Gets the value. /// Gets the value.
@ -30,6 +31,8 @@
/// <value> /// <value>
/// The value. /// The value.
/// </value> /// </value>
public string Value { get; } public String Value {
get;
}
} }
} }

View File

@ -1,10 +1,8 @@
namespace Swan.Parsers namespace Swan.Parsers {
{
/// <summary> /// <summary>
/// Enums the token types. /// Enums the token types.
/// </summary> /// </summary>
public enum TokenType public enum TokenType {
{
/// <summary> /// <summary>
/// The number /// The number
/// </summary> /// </summary>

View File

@ -2,21 +2,19 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
namespace Swan.Parsers namespace Swan.Parsers {
{
/// <summary> /// <summary>
/// Represents a generic tokenizer. /// Represents a generic tokenizer.
/// </summary> /// </summary>
public abstract class Tokenizer public abstract class Tokenizer {
{ private const Char PeriodChar = '.';
private const char PeriodChar = '.'; private const Char CommaChar = ',';
private const char CommaChar = ','; private const Char StringQuotedChar = '"';
private const char StringQuotedChar = '"'; private const Char OpenFuncChar = '(';
private const char OpenFuncChar = '('; private const Char CloseFuncChar = ')';
private const char CloseFuncChar = ')'; private const Char NegativeChar = '-';
private const char NegativeChar = '-';
private const string OpenFuncStr = "("; private const String OpenFuncStr = "(";
private readonly List<Operator> _operators = new List<Operator>(); private readonly List<Operator> _operators = new List<Operator>();
@ -84,10 +82,9 @@ namespace Swan.Parsers
/// </list> /// </list>
/// </summary> /// </summary>
/// <param name="input">The input.</param> /// <param name="input">The input.</param>
protected Tokenizer(string input) protected Tokenizer(String input) {
{ this._operators.AddRange(this.GetDefaultOperators());
_operators.AddRange(GetDefaultOperators()); this.Tokenize(input);
Tokenize(input);
} }
/// <summary> /// <summary>
@ -95,10 +92,9 @@ namespace Swan.Parsers
/// </summary> /// </summary>
/// <param name="input">The input.</param> /// <param name="input">The input.</param>
/// <param name="operators">The operators to use.</param> /// <param name="operators">The operators to use.</param>
protected Tokenizer(string input, IEnumerable<Operator> operators) protected Tokenizer(String input, IEnumerable<Operator> operators) {
{ this._operators.AddRange(operators);
_operators.AddRange(operators); this.Tokenize(input);
Tokenize(input);
} }
/// <summary> /// <summary>
@ -115,14 +111,14 @@ namespace Swan.Parsers
/// <param name="input">The input.</param> /// <param name="input">The input.</param>
/// <param name="startIndex">The start index.</param> /// <param name="startIndex">The start index.</param>
/// <returns><c>true</c> if the input is valid, otherwise <c>false</c>.</returns> /// <returns><c>true</c> if the input is valid, otherwise <c>false</c>.</returns>
public abstract bool ValidateInput(string input, out int startIndex); public abstract Boolean ValidateInput(String input, out Int32 startIndex);
/// <summary> /// <summary>
/// Resolves the type of the function or member. /// Resolves the type of the function or member.
/// </summary> /// </summary>
/// <param name="input">The input.</param> /// <param name="input">The input.</param>
/// <returns>The token type.</returns> /// <returns>The token type.</returns>
public abstract TokenType ResolveFunctionOrMemberType(string input); public abstract TokenType ResolveFunctionOrMemberType(String input);
/// <summary> /// <summary>
/// Evaluates the function or member. /// Evaluates the function or member.
@ -130,7 +126,7 @@ namespace Swan.Parsers
/// <param name="input">The input.</param> /// <param name="input">The input.</param>
/// <param name="position">The position.</param> /// <param name="position">The position.</param>
/// <returns><c>true</c> if the input is a valid function or variable, otherwise <c>false</c>.</returns> /// <returns><c>true</c> if the input is a valid function or variable, otherwise <c>false</c>.</returns>
public virtual bool EvaluateFunctionOrMember(string input, int position) => false; public virtual Boolean EvaluateFunctionOrMember(String input, Int32 position) => false;
/// <summary> /// <summary>
/// Gets the default operators. /// Gets the default operators.
@ -165,14 +161,11 @@ namespace Swan.Parsers
/// or /// or
/// Mismatched parenthesis. /// Mismatched parenthesis.
/// </exception> /// </exception>
public virtual IEnumerable<Token> ShuntingYard(bool includeFunctionStopper = true) public virtual IEnumerable<Token> ShuntingYard(Boolean includeFunctionStopper = true) {
{ Stack<Token> stack = new Stack<Token>();
var stack = new Stack<Token>();
foreach (var tok in Tokens) foreach(Token tok in this.Tokens) {
{ switch(tok.Type) {
switch (tok.Type)
{
case TokenType.Number: case TokenType.Number:
case TokenType.Variable: case TokenType.Variable:
case TokenType.String: case TokenType.String:
@ -182,38 +175,35 @@ namespace Swan.Parsers
stack.Push(tok); stack.Push(tok);
break; break;
case TokenType.Operator: case TokenType.Operator:
while (stack.Any() && stack.Peek().Type == TokenType.Operator && while(stack.Any() && stack.Peek().Type == TokenType.Operator && this.CompareOperators(tok.Value, stack.Peek().Value)) {
CompareOperators(tok.Value, stack.Peek().Value))
yield return stack.Pop(); yield return stack.Pop();
}
stack.Push(tok); stack.Push(tok);
break; break;
case TokenType.Comma: case TokenType.Comma:
while (stack.Any() && (stack.Peek().Type != TokenType.Comma && while(stack.Any() && stack.Peek().Type != TokenType.Comma && stack.Peek().Type != TokenType.Parenthesis) {
stack.Peek().Type != TokenType.Parenthesis))
yield return stack.Pop(); yield return stack.Pop();
}
break; break;
case TokenType.Parenthesis: case TokenType.Parenthesis:
if (tok.Value == OpenFuncStr) if(tok.Value == OpenFuncStr) {
{ if(stack.Any() && stack.Peek().Type == TokenType.Function) {
if (stack.Any() && stack.Peek().Type == TokenType.Function) if(includeFunctionStopper) {
{
if (includeFunctionStopper)
yield return new Token(TokenType.Wall, tok.Value); yield return new Token(TokenType.Wall, tok.Value);
} }
}
stack.Push(tok); stack.Push(tok);
} } else {
else while(stack.Peek().Value != OpenFuncStr) {
{
while (stack.Peek().Value != OpenFuncStr)
yield return stack.Pop(); yield return stack.Pop();
}
stack.Pop(); _ = stack.Pop();
if (stack.Any() && stack.Peek().Type == TokenType.Function) if(stack.Any() && stack.Peek().Type == TokenType.Function) {
{
yield return stack.Pop(); yield return stack.Pop();
} }
} }
@ -224,138 +214,95 @@ namespace Swan.Parsers
} }
} }
while (stack.Any()) while(stack.Any()) {
{ Token tok = stack.Pop();
var tok = stack.Pop(); if(tok.Type == TokenType.Parenthesis) {
if (tok.Type == TokenType.Parenthesis)
throw new InvalidOperationException("Mismatched parenthesis"); throw new InvalidOperationException("Mismatched parenthesis");
}
yield return tok; yield return tok;
} }
} }
private static bool CompareOperators(Operator op1, Operator op2) => op1.RightAssociative private static Boolean CompareOperators(Operator op1, Operator op2) => op1.RightAssociative ? op1.Precedence < op2.Precedence : op1.Precedence <= op2.Precedence;
? op1.Precedence < op2.Precedence
: op1.Precedence <= op2.Precedence;
private void Tokenize(string input) private void Tokenize(String input) {
{ if(!this.ValidateInput(input, out Int32 startIndex)) {
if (!ValidateInput(input, out var startIndex))
{
return; return;
} }
for (var i = startIndex; i < input.Length; i++) for(Int32 i = startIndex; i < input.Length; i++) {
{ if(Char.IsWhiteSpace(input, i)) {
if (char.IsWhiteSpace(input, i)) continue;
if (input[i] == CommaChar)
{
Tokens.Add(new Token(TokenType.Comma, new string(new[] { input[i] })));
continue; continue;
} }
if (input[i] == StringQuotedChar) if(input[i] == CommaChar) {
{ this.Tokens.Add(new Token(TokenType.Comma, new String(new[] { input[i] })));
i = ExtractString(input, i);
continue; continue;
} }
if (char.IsLetter(input, i) || EvaluateFunctionOrMember(input, i)) if(input[i] == StringQuotedChar) {
{ i = this.ExtractString(input, i);
i = ExtractFunctionOrMember(input, i); continue;
}
if(Char.IsLetter(input, i) || this.EvaluateFunctionOrMember(input, i)) {
i = this.ExtractFunctionOrMember(input, i);
continue; continue;
} }
if (char.IsNumber(input, i) || ( if(Char.IsNumber(input, i) || input[i] == NegativeChar && (this.Tokens.Any() && this.Tokens.Last().Type != TokenType.Number || !this.Tokens.Any())) {
input[i] == NegativeChar && i = this.ExtractNumber(input, i);
((Tokens.Any() && Tokens.Last().Type != TokenType.Number) || !Tokens.Any())))
{
i = ExtractNumber(input, i);
continue; continue;
} }
if (input[i] == OpenFuncChar || if(input[i] == OpenFuncChar || input[i] == CloseFuncChar) {
input[i] == CloseFuncChar) this.Tokens.Add(new Token(TokenType.Parenthesis, new String(new[] { input[i] })));
{
Tokens.Add(new Token(TokenType.Parenthesis, new string(new[] { input[i] })));
continue; continue;
} }
i = ExtractOperator(input, i); i = this.ExtractOperator(input, i);
} }
} }
private int ExtractData( private Int32 ExtractData(String input, Int32 i, Func<String, TokenType> tokenTypeEvaluation, Func<Char, Boolean> evaluation, Int32 right = 0, Int32 left = -1) {
string input, Int32 charCount = 0;
int i, for(Int32 j = i + right; j < input.Length; j++) {
Func<string, TokenType> tokenTypeEvaluation, if(evaluation(input[j])) {
Func<char, bool> evaluation,
int right = 0,
int left = -1)
{
var charCount = 0;
for (var j = i + right; j < input.Length; j++)
{
if (evaluation(input[j]))
break; break;
}
charCount++; charCount++;
} }
// Extract and set the value // Extract and set the value
var value = input.SliceLength(i + right, charCount); String value = input.SliceLength(i + right, charCount);
Tokens.Add(new Token(tokenTypeEvaluation(value), value)); this.Tokens.Add(new Token(tokenTypeEvaluation(value), value));
i += charCount + left; i += charCount + left;
return i; return i;
} }
private int ExtractOperator(string input, int i) => private Int32 ExtractOperator(String input, Int32 i) => this.ExtractData(input, i, x => TokenType.Operator, x => x == OpenFuncChar || x == CommaChar || x == PeriodChar || x == StringQuotedChar || Char.IsWhiteSpace(x) || Char.IsNumber(x));
ExtractData(
input,
i,
x => TokenType.Operator,
x => x == OpenFuncChar ||
x == CommaChar ||
x == PeriodChar ||
x == StringQuotedChar ||
char.IsWhiteSpace(x) ||
char.IsNumber(x));
private int ExtractFunctionOrMember(string input, int i) => private Int32 ExtractFunctionOrMember(String input, Int32 i) => this.ExtractData(input, i, this.ResolveFunctionOrMemberType, x => x == OpenFuncChar || x == CloseFuncChar || x == CommaChar || Char.IsWhiteSpace(x));
ExtractData(
input,
i,
ResolveFunctionOrMemberType,
x => x == OpenFuncChar ||
x == CloseFuncChar ||
x == CommaChar ||
char.IsWhiteSpace(x));
private int ExtractNumber(string input, int i) => private Int32 ExtractNumber(String input, Int32 i) => this.ExtractData(input, i, x => TokenType.Number, x => !Char.IsNumber(x) && x != PeriodChar && x != NegativeChar);
ExtractData(
input,
i,
x => TokenType.Number,
x => !char.IsNumber(x) && x != PeriodChar && x != NegativeChar);
private int ExtractString(string input, int i) private Int32 ExtractString(String input, Int32 i) {
{ Int32 length = this.ExtractData(input, i, x => TokenType.String, x => x == StringQuotedChar, 1, 1);
var length = ExtractData(input, i, x => TokenType.String, x => x == StringQuotedChar, 1, 1);
// open string, report issue // open string, report issue
if (length == input.Length && input[length - 1] != StringQuotedChar) if(length == input.Length && input[length - 1] != StringQuotedChar) {
throw new FormatException($"Parser error (Position {i}): Expected '\"' but got '{input[length - 1]}'."); throw new FormatException($"Parser error (Position {i}): Expected '\"' but got '{input[length - 1]}'.");
}
return length; return length;
} }
private bool CompareOperators(string op1, string op2) private Boolean CompareOperators(String op1, String op2) => CompareOperators(this.GetOperatorOrDefault(op1), this.GetOperatorOrDefault(op2));
=> CompareOperators(GetOperatorOrDefault(op1), GetOperatorOrDefault(op2));
private Operator GetOperatorOrDefault(string op) private Operator GetOperatorOrDefault(String op) => this._operators.FirstOrDefault(x => x.Name == op) ?? new Operator { Name = op, Precedence = 0 };
=> _operators.FirstOrDefault(x => x.Name == op) ?? new Operator { Name = op, Precedence = 0 };
} }
} }

View File

@ -1,22 +1,17 @@
using System; using System;
namespace Swan.Parsers namespace Swan.Parsers {
{
/// <summary> /// <summary>
/// Models a verb option. /// Models a verb option.
/// </summary> /// </summary>
[AttributeUsage(AttributeTargets.Property)] [AttributeUsage(AttributeTargets.Property)]
public sealed class VerbOptionAttribute : Attribute public sealed class VerbOptionAttribute : Attribute {
{
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="VerbOptionAttribute" /> class. /// Initializes a new instance of the <see cref="VerbOptionAttribute" /> class.
/// </summary> /// </summary>
/// <param name="name">The name.</param> /// <param name="name">The name.</param>
/// <exception cref="ArgumentNullException">name.</exception> /// <exception cref="ArgumentNullException">name.</exception>
public VerbOptionAttribute(string name) public VerbOptionAttribute(String name) => this.Name = name ?? throw new ArgumentNullException(nameof(name));
{
Name = name ?? throw new ArgumentNullException(nameof(name));
}
/// <summary> /// <summary>
/// Gets the name of the verb option. /// Gets the name of the verb option.
@ -24,7 +19,9 @@ namespace Swan.Parsers
/// <value> /// <value>
/// Name. /// Name.
/// </value> /// </value>
public string Name { get; } public String Name {
get;
}
/// <summary> /// <summary>
/// Gets or sets a short description of this command line verb. Usually a sentence summary. /// Gets or sets a short description of this command line verb. Usually a sentence summary.
@ -32,9 +29,11 @@ namespace Swan.Parsers
/// <value> /// <value>
/// The help text. /// The help text.
/// </value> /// </value>
public string HelpText { get; set; } public String HelpText {
get; set;
}
/// <inheritdoc /> /// <inheritdoc />
public override string ToString() => $" {Name}\t\t{HelpText}"; public override String ToString() => $" {this.Name}\t\t{this.HelpText}";
} }
} }

View File

@ -1,11 +1,11 @@
using System; #nullable enable
using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
namespace Swan.Reflection namespace Swan.Reflection {
{
/// <summary> /// <summary>
/// A thread-safe cache of attributes belonging to a given key (MemberInfo or Type). /// A thread-safe cache of attributes belonging to a given key (MemberInfo or Type).
/// ///
@ -13,20 +13,14 @@ namespace Swan.Reflection
/// calls the retrieval process if the type is not contained /// calls the retrieval process if the type is not contained
/// in the cache. /// in the cache.
/// </summary> /// </summary>
public class AttributeCache public class AttributeCache {
{ private readonly Lazy<ConcurrentDictionary<Tuple<Object, Type>, IEnumerable<Object>>> _data = new Lazy<ConcurrentDictionary<Tuple<Object, Type>, IEnumerable<Object>>>(() => new ConcurrentDictionary<Tuple<Object, Type>, IEnumerable<Object>>(), true);
private readonly Lazy<ConcurrentDictionary<Tuple<object, Type>, IEnumerable<object>>> _data =
new Lazy<ConcurrentDictionary<Tuple<object, Type>, IEnumerable<object>>>(() =>
new ConcurrentDictionary<Tuple<object, Type>, IEnumerable<object>>(), true);
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="AttributeCache"/> class. /// Initializes a new instance of the <see cref="AttributeCache"/> class.
/// </summary> /// </summary>
/// <param name="propertyCache">The property cache object.</param> /// <param name="propertyCache">The property cache object.</param>
public AttributeCache(PropertyTypeCache? propertyCache = null) public AttributeCache(PropertyTypeCache? propertyCache = null) => this.PropertyTypeCache = propertyCache ?? PropertyTypeCache.DefaultCache.Value;
{
PropertyTypeCache = propertyCache ?? PropertyTypeCache.DefaultCache.Value;
}
/// <summary> /// <summary>
/// Gets the default cache. /// Gets the default cache.
@ -39,7 +33,9 @@ namespace Swan.Reflection
/// <summary> /// <summary>
/// A PropertyTypeCache object for caching properties and their attributes. /// A PropertyTypeCache object for caching properties and their attributes.
/// </summary> /// </summary>
public PropertyTypeCache PropertyTypeCache { get; } public PropertyTypeCache PropertyTypeCache {
get;
}
/// <summary> /// <summary>
/// Determines whether [contains] [the specified member]. /// Determines whether [contains] [the specified member].
@ -49,7 +45,7 @@ namespace Swan.Reflection
/// <returns> /// <returns>
/// <c>true</c> if [contains] [the specified member]; otherwise, <c>false</c>. /// <c>true</c> if [contains] [the specified member]; otherwise, <c>false</c>.
/// </returns> /// </returns>
public bool Contains<T>(MemberInfo member) => _data.Value.ContainsKey(new Tuple<object, Type>(member, typeof(T))); public Boolean Contains<T>(MemberInfo member) => this._data.Value.ContainsKey(new Tuple<Object, Type>(member, typeof(T)));
/// <summary> /// <summary>
/// Gets specific attributes from a member constrained to an attribute. /// Gets specific attributes from a member constrained to an attribute.
@ -58,13 +54,12 @@ namespace Swan.Reflection
/// <param name="member">The member.</param> /// <param name="member">The member.</param>
/// <param name="inherit"><c>true</c> to inspect the ancestors of element; otherwise, <c>false</c>.</param> /// <param name="inherit"><c>true</c> to inspect the ancestors of element; otherwise, <c>false</c>.</param>
/// <returns>An array of the attributes stored for the specified type.</returns> /// <returns>An array of the attributes stored for the specified type.</returns>
public IEnumerable<object> Retrieve<T>(MemberInfo member, bool inherit = false) public IEnumerable<Object> Retrieve<T>(MemberInfo member, Boolean inherit = false) where T : Attribute {
where T : Attribute if(member == null) {
{
if (member == null)
throw new ArgumentNullException(nameof(member)); throw new ArgumentNullException(nameof(member));
}
return Retrieve(new Tuple<object, Type>(member, typeof(T)), t => member.GetCustomAttributes<T>(inherit)); return this.Retrieve(new Tuple<Object, Type>(member, typeof(T)), t => member.GetCustomAttributes<T>(inherit));
} }
/// <summary> /// <summary>
@ -74,17 +69,16 @@ namespace Swan.Reflection
/// <param name="type">The attribute type.</param> /// <param name="type">The attribute type.</param>
/// <param name="inherit"><c>true</c> to inspect the ancestors of element; otherwise, <c>false</c>.</param> /// <param name="inherit"><c>true</c> to inspect the ancestors of element; otherwise, <c>false</c>.</param>
/// <returns>An array of the attributes stored for the specified type.</returns> /// <returns>An array of the attributes stored for the specified type.</returns>
public IEnumerable<object> Retrieve(MemberInfo member, Type type, bool inherit = false) public IEnumerable<Object> Retrieve(MemberInfo member, Type type, Boolean inherit = false) {
{ if(member == null) {
if (member == null)
throw new ArgumentNullException(nameof(member)); throw new ArgumentNullException(nameof(member));
}
if (type == null) if(type == null) {
throw new ArgumentNullException(nameof(type)); throw new ArgumentNullException(nameof(type));
}
return Retrieve( return this.Retrieve(new Tuple<Object, Type>(member, type), t => member.GetCustomAttributes(type, inherit));
new Tuple<object, Type>(member, type),
t => member.GetCustomAttributes(type, inherit));
} }
/// <summary> /// <summary>
@ -94,15 +88,12 @@ namespace Swan.Reflection
/// <param name="member">The member.</param> /// <param name="member">The member.</param>
/// <param name="inherit"><c>true</c> to inspect the ancestors of element; otherwise, <c>false</c>.</param> /// <param name="inherit"><c>true</c> to inspect the ancestors of element; otherwise, <c>false</c>.</param>
/// <returns>An attribute stored for the specified type.</returns> /// <returns>An attribute stored for the specified type.</returns>
public T RetrieveOne<T>(MemberInfo member, bool inherit = false) public T RetrieveOne<T>(MemberInfo member, Boolean inherit = false) where T : Attribute {
where T : Attribute if(member == null) {
{ return default!;
if (member == null) }
return default;
var attr = Retrieve( IEnumerable<Object> attr = this.Retrieve(new Tuple<Object, Type>(member, typeof(T)), t => member.GetCustomAttributes(typeof(T), inherit));
new Tuple<object, Type>(member, typeof(T)),
t => member.GetCustomAttributes(typeof(T), inherit));
return ConvertToAttribute<T>(attr); return ConvertToAttribute<T>(attr);
} }
@ -114,12 +105,8 @@ namespace Swan.Reflection
/// <typeparam name="T">The type to retrieve the attribute.</typeparam> /// <typeparam name="T">The type to retrieve the attribute.</typeparam>
/// <param name="inherit">if set to <c>true</c> [inherit].</param> /// <param name="inherit">if set to <c>true</c> [inherit].</param>
/// <returns>An attribute stored for the specified type.</returns> /// <returns>An attribute stored for the specified type.</returns>
public TAttribute RetrieveOne<TAttribute, T>(bool inherit = false) public TAttribute RetrieveOne<TAttribute, T>(Boolean inherit = false) where TAttribute : Attribute {
where TAttribute : Attribute IEnumerable<Object> attr = this.Retrieve(new Tuple<Object, Type>(typeof(T), typeof(TAttribute)), t => typeof(T).GetCustomAttributes(typeof(TAttribute), inherit));
{
var attr = Retrieve(
new Tuple<object, Type>(typeof(T), typeof(TAttribute)),
t => typeof(T).GetCustomAttributes(typeof(TAttribute), inherit));
return ConvertToAttribute<TAttribute>(attr); return ConvertToAttribute<TAttribute>(attr);
} }
@ -131,10 +118,7 @@ namespace Swan.Reflection
/// <param name="type">The type of the object.</param> /// <param name="type">The type of the object.</param>
/// <param name="inherit"><c>true</c> to inspect the ancestors of element; otherwise, <c>false</c>.</param> /// <param name="inherit"><c>true</c> to inspect the ancestors of element; otherwise, <c>false</c>.</param>
/// <returns>A dictionary of the properties and their attributes stored for the specified type.</returns> /// <returns>A dictionary of the properties and their attributes stored for the specified type.</returns>
public Dictionary<PropertyInfo, IEnumerable<object>> Retrieve<T>(Type type, bool inherit = false) public Dictionary<PropertyInfo, IEnumerable<Object>> Retrieve<T>(Type type, Boolean inherit = false) where T : Attribute => this.PropertyTypeCache.RetrieveAllProperties(type, true).ToDictionary(x => x, x => this.Retrieve<T>(x, inherit));
where T : Attribute =>
PropertyTypeCache.RetrieveAllProperties(type, true)
.ToDictionary(x => x, x => Retrieve<T>(x, inherit));
/// <summary> /// <summary>
/// Gets all properties and their attributes of a given type. /// Gets all properties and their attributes of a given type.
@ -145,8 +129,7 @@ namespace Swan.Reflection
/// <returns> /// <returns>
/// A dictionary of the properties and their attributes stored for the specified type. /// A dictionary of the properties and their attributes stored for the specified type.
/// </returns> /// </returns>
public Dictionary<PropertyInfo, IEnumerable<object>> RetrieveFromType<T, TAttribute>(bool inherit = false) public Dictionary<PropertyInfo, IEnumerable<Object>> RetrieveFromType<T, TAttribute>(Boolean inherit = false) => this.RetrieveFromType<T>(typeof(TAttribute), inherit);
=> RetrieveFromType<T>(typeof(TAttribute), inherit);
/// <summary> /// <summary>
/// Gets all properties and their attributes of a given type. /// Gets all properties and their attributes of a given type.
@ -157,32 +140,22 @@ namespace Swan.Reflection
/// <returns> /// <returns>
/// A dictionary of the properties and their attributes stored for the specified type. /// A dictionary of the properties and their attributes stored for the specified type.
/// </returns> /// </returns>
public Dictionary<PropertyInfo, IEnumerable<object>> RetrieveFromType<T>(Type attributeType, bool inherit = false) public Dictionary<PropertyInfo, IEnumerable<Object>> RetrieveFromType<T>(Type attributeType, Boolean inherit = false) {
{ if(attributeType == null) {
if (attributeType == null)
throw new ArgumentNullException(nameof(attributeType)); throw new ArgumentNullException(nameof(attributeType));
return PropertyTypeCache.RetrieveAllProperties<T>(true)
.ToDictionary(x => x, x => Retrieve(x, attributeType, inherit));
} }
private static T ConvertToAttribute<T>(IEnumerable<object> attr) return this.PropertyTypeCache.RetrieveAllProperties<T>(true).ToDictionary(x => x, x => this.Retrieve(x, attributeType, inherit));
where T : Attribute
{
if (attr?.Any() != true)
return default;
return attr.Count() == 1
? (T) Convert.ChangeType(attr.First(), typeof(T))
: throw new AmbiguousMatchException("Multiple custom attributes of the same type found.");
} }
private IEnumerable<object> Retrieve(Tuple<object, Type> key, Func<Tuple<object, Type>, IEnumerable<object>> factory) private static T ConvertToAttribute<T>(IEnumerable<Object> attr) where T : Attribute => attr?.Any() != true ? (default!) : attr.Count() == 1 ? (T)Convert.ChangeType(attr.First(), typeof(T)) : throw new AmbiguousMatchException("Multiple custom attributes of the same type found.");
{
if (factory == null) private IEnumerable<Object> Retrieve(Tuple<Object, Type> key, Func<Tuple<Object, Type>, IEnumerable<Object>> factory) {
if(factory == null) {
throw new ArgumentNullException(nameof(factory)); throw new ArgumentNullException(nameof(factory));
}
return _data.Value.GetOrAdd(key, k => factory.Invoke(k).Where(item => item != null)); return this._data.Value.GetOrAdd(key, k => factory.Invoke(k).Where(item => item != null));
} }
} }
} }

View File

@ -4,21 +4,18 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
namespace Swan.Lite.Reflection namespace Swan.Lite.Reflection {
{
/// <summary> /// <summary>
/// A thread-safe cache of constructors belonging to a given type. /// A thread-safe cache of constructors belonging to a given type.
/// </summary> /// </summary>
public class ConstructorTypeCache : TypeCache<Tuple<ConstructorInfo, ParameterInfo[]>> public class ConstructorTypeCache : TypeCache<Tuple<ConstructorInfo, ParameterInfo[]>> {
{
/// <summary> /// <summary>
/// Gets the default cache. /// Gets the default cache.
/// </summary> /// </summary>
/// <value> /// <value>
/// The default cache. /// The default cache.
/// </value> /// </value>
public static Lazy<ConstructorTypeCache> DefaultCache { get; } = public static Lazy<ConstructorTypeCache> DefaultCache { get; } = new Lazy<ConstructorTypeCache>(() => new ConstructorTypeCache());
new Lazy<ConstructorTypeCache>(() => new ConstructorTypeCache());
/// <summary> /// <summary>
/// Retrieves all constructors order by the number of parameters ascending. /// Retrieves all constructors order by the number of parameters ascending.
@ -28,8 +25,7 @@ namespace Swan.Lite.Reflection
/// <returns> /// <returns>
/// A collection with all the constructors in the given type. /// A collection with all the constructors in the given type.
/// </returns> /// </returns>
public IEnumerable<Tuple<ConstructorInfo, ParameterInfo[]>> RetrieveAllConstructors<T>(bool includeNonPublic = false) public IEnumerable<Tuple<ConstructorInfo, ParameterInfo[]>> RetrieveAllConstructors<T>(Boolean includeNonPublic = false) => this.Retrieve<T>(GetConstructors(includeNonPublic));
=> Retrieve<T>(GetConstructors(includeNonPublic));
/// <summary> /// <summary>
/// Retrieves all constructors order by the number of parameters ascending. /// Retrieves all constructors order by the number of parameters ascending.
@ -39,13 +35,8 @@ namespace Swan.Lite.Reflection
/// <returns> /// <returns>
/// A collection with all the constructors in the given type. /// A collection with all the constructors in the given type.
/// </returns> /// </returns>
public IEnumerable<Tuple<ConstructorInfo, ParameterInfo[]>> RetrieveAllConstructors(Type type, bool includeNonPublic = false) public IEnumerable<Tuple<ConstructorInfo, ParameterInfo[]>> RetrieveAllConstructors(Type type, Boolean includeNonPublic = false) => this.Retrieve(type, GetConstructors(includeNonPublic));
=> Retrieve(type, GetConstructors(includeNonPublic));
private static Func<Type, IEnumerable<Tuple<ConstructorInfo, ParameterInfo[]>>> GetConstructors(bool includeNonPublic) private static Func<Type, IEnumerable<Tuple<ConstructorInfo, ParameterInfo[]>>> GetConstructors(Boolean includeNonPublic) => t => t.GetConstructors(includeNonPublic ? BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance : BindingFlags.Public | BindingFlags.Instance).Select(x => Tuple.Create(x, x.GetParameters())).OrderBy(x => x.Item2.Length).ToList();
=> t => t.GetConstructors(includeNonPublic ? BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance : BindingFlags.Public | BindingFlags.Instance)
.Select(x => Tuple.Create(x, x.GetParameters()))
.OrderBy(x => x.Item2.Length)
.ToList();
} }
} }

View File

@ -2,33 +2,28 @@
using System.Reflection; using System.Reflection;
using Swan.Configuration; using Swan.Configuration;
namespace Swan.Reflection namespace Swan.Reflection {
{
/// <summary> /// <summary>
/// Represents a Property object from a Object Reflection Property with extended values. /// Represents a Property object from a Object Reflection Property with extended values.
/// </summary> /// </summary>
public class ExtendedPropertyInfo public class ExtendedPropertyInfo {
{
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ExtendedPropertyInfo"/> class. /// Initializes a new instance of the <see cref="ExtendedPropertyInfo"/> class.
/// </summary> /// </summary>
/// <param name="propertyInfo">The property information.</param> /// <param name="propertyInfo">The property information.</param>
public ExtendedPropertyInfo(PropertyInfo propertyInfo) public ExtendedPropertyInfo(PropertyInfo propertyInfo) {
{ if(propertyInfo == null) {
if (propertyInfo == null)
{
throw new ArgumentNullException(nameof(propertyInfo)); throw new ArgumentNullException(nameof(propertyInfo));
} }
Property = propertyInfo.Name; this.Property = propertyInfo.Name;
DataType = propertyInfo.PropertyType.Name; this.DataType = propertyInfo.PropertyType.Name;
foreach (PropertyDisplayAttribute display in AttributeCache.DefaultCache.Value.Retrieve<PropertyDisplayAttribute>(propertyInfo, true)) foreach(PropertyDisplayAttribute display in AttributeCache.DefaultCache.Value.Retrieve<PropertyDisplayAttribute>(propertyInfo, true)) {
{ this.Name = display.Name;
Name = display.Name; this.Description = display.Description;
Description = display.Description; this.GroupName = display.GroupName;
GroupName = display.GroupName; this.DefaultValue = display.DefaultValue;
DefaultValue = display.DefaultValue;
} }
} }
@ -38,7 +33,9 @@ namespace Swan.Reflection
/// <value> /// <value>
/// The property. /// The property.
/// </value> /// </value>
public string Property { get; } public String Property {
get;
}
/// <summary> /// <summary>
/// Gets or sets the type of the data. /// Gets or sets the type of the data.
@ -46,7 +43,9 @@ namespace Swan.Reflection
/// <value> /// <value>
/// The type of the data. /// The type of the data.
/// </value> /// </value>
public string DataType { get; } public String DataType {
get;
}
/// <summary> /// <summary>
/// Gets or sets the value. /// Gets or sets the value.
@ -54,7 +53,9 @@ namespace Swan.Reflection
/// <value> /// <value>
/// The value. /// The value.
/// </value> /// </value>
public object Value { get; set; } public Object Value {
get; set;
}
/// <summary> /// <summary>
/// Gets or sets the default value. /// Gets or sets the default value.
@ -62,7 +63,9 @@ namespace Swan.Reflection
/// <value> /// <value>
/// The default value. /// The default value.
/// </value> /// </value>
public object DefaultValue { get; } public Object DefaultValue {
get;
}
/// <summary> /// <summary>
/// Gets or sets the name. /// Gets or sets the name.
@ -70,7 +73,9 @@ namespace Swan.Reflection
/// <value> /// <value>
/// The name. /// The name.
/// </value> /// </value>
public string Name { get; } public String Name {
get;
}
/// <summary> /// <summary>
/// Gets or sets the description. /// Gets or sets the description.
@ -78,7 +83,9 @@ namespace Swan.Reflection
/// <value> /// <value>
/// The description. /// The description.
/// </value> /// </value>
public string Description { get; } public String Description {
get;
}
/// <summary> /// <summary>
/// Gets or sets the name of the group. /// Gets or sets the name of the group.
@ -86,22 +93,21 @@ namespace Swan.Reflection
/// <value> /// <value>
/// The name of the group. /// The name of the group.
/// </value> /// </value>
public string GroupName { get; } public String GroupName {
get;
}
} }
/// <summary> /// <summary>
/// Represents a Property object from a Object Reflection Property with extended values. /// Represents a Property object from a Object Reflection Property with extended values.
/// </summary> /// </summary>
/// <typeparam name="T">The type of the object.</typeparam> /// <typeparam name="T">The type of the object.</typeparam>
public class ExtendedPropertyInfo<T> : ExtendedPropertyInfo public class ExtendedPropertyInfo<T> : ExtendedPropertyInfo {
{
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ExtendedPropertyInfo{T}"/> class. /// Initializes a new instance of the <see cref="ExtendedPropertyInfo{T}"/> class.
/// </summary> /// </summary>
/// <param name="property">The property.</param> /// <param name="property">The property.</param>
public ExtendedPropertyInfo(string property) public ExtendedPropertyInfo(String property) : base(typeof(T).GetProperty(property)) {
: base(typeof(T).GetProperty(property))
{
} }
} }
} }

View File

@ -1,40 +1,39 @@
using System; #nullable enable
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel; using System.ComponentModel;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
namespace Swan.Reflection namespace Swan.Reflection {
{
/// <summary> /// <summary>
/// Provides extended information about a type. /// Provides extended information about a type.
/// ///
/// This class is mainly used to define sets of types within the Definition class /// This class is mainly used to define sets of types within the Definition class
/// and it is not meant for other than querying the BasicTypesInfo dictionary. /// and it is not meant for other than querying the BasicTypesInfo dictionary.
/// </summary> /// </summary>
public class ExtendedTypeInfo public class ExtendedTypeInfo {
{ private const String TryParseMethodName = nameof(Byte.TryParse);
private const string TryParseMethodName = nameof(byte.TryParse); private const String ToStringMethodName = nameof(ToString);
private const string ToStringMethodName = nameof(ToString);
private static readonly Type[] NumericTypes = private static readonly Type[] NumericTypes =
{ {
typeof(byte), typeof(Byte),
typeof(sbyte), typeof(SByte),
typeof(decimal), typeof(Decimal),
typeof(double), typeof(Double),
typeof(float), typeof(Single),
typeof(int), typeof(Int32),
typeof(uint), typeof(UInt32),
typeof(long), typeof(Int64),
typeof(ulong), typeof(UInt64),
typeof(short), typeof(Int16),
typeof(ushort), typeof(UInt16),
}; };
private readonly ParameterInfo[]? _tryParseParameters; private readonly ParameterInfo[]? _tryParseParameters;
private readonly int _toStringArgumentLength; private readonly Int32 _toStringArgumentLength;
#region Constructors #region Constructors
@ -42,47 +41,31 @@ namespace Swan.Reflection
/// Initializes a new instance of the <see cref="ExtendedTypeInfo"/> class. /// Initializes a new instance of the <see cref="ExtendedTypeInfo"/> class.
/// </summary> /// </summary>
/// <param name="t">The t.</param> /// <param name="t">The t.</param>
public ExtendedTypeInfo(Type t) public ExtendedTypeInfo(Type t) {
{ this.Type = t ?? throw new ArgumentNullException(nameof(t));
Type = t ?? throw new ArgumentNullException(nameof(t)); this.IsNullableValueType = this.Type.IsGenericType && this.Type.GetGenericTypeDefinition() == typeof(Nullable<>);
IsNullableValueType = Type.IsGenericType
&& Type.GetGenericTypeDefinition() == typeof(Nullable<>);
IsValueType = t.IsValueType; this.IsValueType = t.IsValueType;
UnderlyingType = IsNullableValueType ? this.UnderlyingType = this.IsNullableValueType ? new NullableConverter(this.Type).UnderlyingType : this.Type;
new NullableConverter(Type).UnderlyingType :
Type;
IsNumeric = NumericTypes.Contains(UnderlyingType); this.IsNumeric = NumericTypes.Contains(this.UnderlyingType);
// Extract the TryParse method info // Extract the TryParse method info
try try {
{ this.TryParseMethodInfo = this.UnderlyingType.GetMethod(TryParseMethodName, new[] { typeof(String), typeof(NumberStyles), typeof(IFormatProvider), this.UnderlyingType.MakeByRefType() }) ?? this.UnderlyingType.GetMethod(TryParseMethodName, new[] { typeof(String), this.UnderlyingType.MakeByRefType() });
TryParseMethodInfo = UnderlyingType.GetMethod(TryParseMethodName,
new[] { typeof(string), typeof(NumberStyles), typeof(IFormatProvider), UnderlyingType.MakeByRefType() }) ??
UnderlyingType.GetMethod(TryParseMethodName,
new[] { typeof(string), UnderlyingType.MakeByRefType() });
_tryParseParameters = TryParseMethodInfo?.GetParameters(); this._tryParseParameters = this.TryParseMethodInfo?.GetParameters();
} } catch {
catch
{
// ignored // ignored
} }
// Extract the ToString method Info // Extract the ToString method Info
try try {
{ this.ToStringMethodInfo = this.UnderlyingType.GetMethod(ToStringMethodName, new[] { typeof(IFormatProvider) }) ?? this.UnderlyingType.GetMethod(ToStringMethodName, Array.Empty<Type>());
ToStringMethodInfo = UnderlyingType.GetMethod(ToStringMethodName,
new[] { typeof(IFormatProvider) }) ??
UnderlyingType.GetMethod(ToStringMethodName,
Array.Empty<Type>());
_toStringArgumentLength = ToStringMethodInfo?.GetParameters().Length ?? 0; this._toStringArgumentLength = this.ToStringMethodInfo?.GetParameters().Length ?? 0;
} } catch {
catch
{
// ignored // ignored
} }
} }
@ -97,7 +80,9 @@ namespace Swan.Reflection
/// <value> /// <value>
/// The type. /// The type.
/// </value> /// </value>
public Type Type { get; } public Type Type {
get;
}
/// <summary> /// <summary>
/// Gets a value indicating whether the type is a nullable value type. /// Gets a value indicating whether the type is a nullable value type.
@ -105,7 +90,9 @@ namespace Swan.Reflection
/// <value> /// <value>
/// <c>true</c> if this instance is nullable value type; otherwise, <c>false</c>. /// <c>true</c> if this instance is nullable value type; otherwise, <c>false</c>.
/// </value> /// </value>
public bool IsNullableValueType { get; } public Boolean IsNullableValueType {
get;
}
/// <summary> /// <summary>
/// Gets a value indicating whether the type or underlying type is numeric. /// Gets a value indicating whether the type or underlying type is numeric.
@ -113,13 +100,17 @@ namespace Swan.Reflection
/// <value> /// <value>
/// <c>true</c> if this instance is numeric; otherwise, <c>false</c>. /// <c>true</c> if this instance is numeric; otherwise, <c>false</c>.
/// </value> /// </value>
public bool IsNumeric { get; } public Boolean IsNumeric {
get;
}
/// <summary> /// <summary>
/// Gets a value indicating whether the type is value type. /// Gets a value indicating whether the type is value type.
/// Nullable value types have this property set to False. /// Nullable value types have this property set to False.
/// </summary> /// </summary>
public bool IsValueType { get; } public Boolean IsValueType {
get;
}
/// <summary> /// <summary>
/// When dealing with nullable value types, this property will /// When dealing with nullable value types, this property will
@ -129,7 +120,9 @@ namespace Swan.Reflection
/// <value> /// <value>
/// The type of the underlying. /// The type of the underlying.
/// </value> /// </value>
public Type UnderlyingType { get; } public Type UnderlyingType {
get;
}
/// <summary> /// <summary>
/// Gets the try parse method information. If the type does not contain /// Gets the try parse method information. If the type does not contain
@ -138,7 +131,9 @@ namespace Swan.Reflection
/// <value> /// <value>
/// The try parse method information. /// The try parse method information.
/// </value> /// </value>
public MethodInfo TryParseMethodInfo { get; } public MethodInfo? TryParseMethodInfo {
get;
}
/// <summary> /// <summary>
/// Gets the ToString method info /// Gets the ToString method info
@ -147,7 +142,9 @@ namespace Swan.Reflection
/// <value> /// <value>
/// To string method information. /// To string method information.
/// </value> /// </value>
public MethodInfo ToStringMethodInfo { get; } public MethodInfo? ToStringMethodInfo {
get;
}
/// <summary> /// <summary>
/// Gets a value indicating whether the type contains a suitable TryParse method. /// Gets a value indicating whether the type contains a suitable TryParse method.
@ -155,7 +152,7 @@ namespace Swan.Reflection
/// <value> /// <value>
/// <c>true</c> if this instance can parse natively; otherwise, <c>false</c>. /// <c>true</c> if this instance can parse natively; otherwise, <c>false</c>.
/// </value> /// </value>
public bool CanParseNatively => TryParseMethodInfo != null; public Boolean CanParseNatively => this.TryParseMethodInfo != null;
#endregion #endregion
@ -169,48 +166,42 @@ namespace Swan.Reflection
/// <param name="s">The s.</param> /// <param name="s">The s.</param>
/// <param name="result">The result.</param> /// <param name="result">The result.</param>
/// <returns><c>true</c> if parse was converted successfully; otherwise, <c>false</c>.</returns> /// <returns><c>true</c> if parse was converted successfully; otherwise, <c>false</c>.</returns>
public bool TryParse(string s, out object? result) public Boolean TryParse(String s, out Object? result) {
{ result = this.Type.GetDefault();
result = Type.GetDefault();
try try {
{ if(this.Type == typeof(String)) {
if (Type == typeof(string)) result = Convert.ChangeType(s, this.Type, CultureInfo.InvariantCulture);
{
result = Convert.ChangeType(s, Type, CultureInfo.InvariantCulture);
return true; return true;
} }
if ((IsNullableValueType && string.IsNullOrEmpty(s)) || !CanParseNatively) if(this.IsNullableValueType && String.IsNullOrEmpty(s) || !this.CanParseNatively) {
{
return true; return true;
} }
// Build the arguments of the TryParse method // Build the arguments of the TryParse method
var dynamicArguments = new List<object?> { s }; List<Object?> dynamicArguments = new List<Object?> { s };
if(this._tryParseParameters != null) {
for (var pi = 1; pi < _tryParseParameters.Length - 1; pi++) for(Int32 pi = 1; pi < this._tryParseParameters.Length - 1; pi++) {
{ ParameterInfo argInfo = this._tryParseParameters[pi];
var argInfo = _tryParseParameters[pi]; if(argInfo.ParameterType == typeof(IFormatProvider)) {
if (argInfo.ParameterType == typeof(IFormatProvider))
dynamicArguments.Add(CultureInfo.InvariantCulture); dynamicArguments.Add(CultureInfo.InvariantCulture);
else if (argInfo.ParameterType == typeof(NumberStyles)) } else if(argInfo.ParameterType == typeof(NumberStyles)) {
dynamicArguments.Add(NumberStyles.Any); dynamicArguments.Add(NumberStyles.Any);
else } else {
dynamicArguments.Add(null); dynamicArguments.Add(null);
} }
}
}
dynamicArguments.Add(null); dynamicArguments.Add(null);
var parseArguments = dynamicArguments.ToArray(); Object?[] parseArguments = dynamicArguments.ToArray();
if ((bool) TryParseMethodInfo.Invoke(null, parseArguments)) if((Boolean)this.TryParseMethodInfo?.Invoke(null, parseArguments)!) {
{ result = parseArguments[^1];
result = parseArguments[parseArguments.Length - 1];
return true; return true;
} }
} } catch {
catch
{
// Ignore // Ignore
} }
@ -224,15 +215,7 @@ namespace Swan.Reflection
/// </summary> /// </summary>
/// <param name="instance">The instance.</param> /// <param name="instance">The instance.</param>
/// <returns>A <see cref="System.String" /> that represents the current object.</returns> /// <returns>A <see cref="System.String" /> that represents the current object.</returns>
public string ToStringInvariant(object instance) public String ToStringInvariant(Object? instance) => instance == null ? String.Empty : this._toStringArgumentLength != 1 ? instance.ToString()! : this.ToStringMethodInfo?.Invoke(instance, new Object[] { CultureInfo.InvariantCulture }) as String ?? String.Empty;
{
if (instance == null)
return string.Empty;
return _toStringArgumentLength != 1
? instance.ToString()
: ToStringMethodInfo.Invoke(instance, new object[] {CultureInfo.InvariantCulture}) as string ?? string.Empty;
}
#endregion #endregion
} }
@ -244,14 +227,11 @@ namespace Swan.Reflection
/// and it is not meant for other than querying the BasicTypesInfo dictionary. /// and it is not meant for other than querying the BasicTypesInfo dictionary.
/// </summary> /// </summary>
/// <typeparam name="T">The type of extended type information.</typeparam> /// <typeparam name="T">The type of extended type information.</typeparam>
public class ExtendedTypeInfo<T> : ExtendedTypeInfo public class ExtendedTypeInfo<T> : ExtendedTypeInfo {
{
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ExtendedTypeInfo{T}"/> class. /// Initializes a new instance of the <see cref="ExtendedTypeInfo{T}"/> class.
/// </summary> /// </summary>
public ExtendedTypeInfo() public ExtendedTypeInfo() : base(typeof(T)) {
: base(typeof(T))
{
// placeholder // placeholder
} }
@ -262,6 +242,6 @@ namespace Swan.Reflection
/// </summary> /// </summary>
/// <param name="instance">The instance.</param> /// <param name="instance">The instance.</param>
/// <returns>A <see cref="System.String" /> that represents the current object.</returns> /// <returns>A <see cref="System.String" /> that represents the current object.</returns>
public string ToStringInvariant(T instance) => base.ToStringInvariant(instance); public String ToStringInvariant(T instance) => base.ToStringInvariant(instance);
} }
} }

View File

@ -1,22 +1,22 @@
namespace Swan.Reflection using System;
{
namespace Swan.Reflection {
/// <summary> /// <summary>
/// Represents a generic interface to store getters and setters. /// Represents a generic interface to store getters and setters.
/// </summary> /// </summary>
public interface IPropertyProxy public interface IPropertyProxy {
{
/// <summary> /// <summary>
/// Gets the property value via a stored delegate. /// Gets the property value via a stored delegate.
/// </summary> /// </summary>
/// <param name="instance">The instance.</param> /// <param name="instance">The instance.</param>
/// <returns>The property value.</returns> /// <returns>The property value.</returns>
object GetValue(object instance); Object GetValue(Object instance);
/// <summary> /// <summary>
/// Sets the property value via a stored delegate. /// Sets the property value via a stored delegate.
/// </summary> /// </summary>
/// <param name="instance">The instance.</param> /// <param name="instance">The instance.</param>
/// <param name="value">The value.</param> /// <param name="value">The value.</param>
void SetValue(object instance, object value); void SetValue(Object instance, Object value);
} }
} }

View File

@ -2,13 +2,11 @@
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Reflection; using System.Reflection;
namespace Swan.Reflection namespace Swan.Reflection {
{
/// <summary> /// <summary>
/// Represents a Method Info Cache. /// Represents a Method Info Cache.
/// </summary> /// </summary>
public class MethodInfoCache : ConcurrentDictionary<string, MethodInfo> public class MethodInfoCache : ConcurrentDictionary<String, MethodInfo> {
{
/// <summary> /// <summary>
/// Retrieves the properties stored for the specified type. /// Retrieves the properties stored for the specified type.
/// If the properties are not available, it calls the factory method to retrieve them /// If the properties are not available, it calls the factory method to retrieve them
@ -24,8 +22,7 @@ namespace Swan.Reflection
/// <exception cref="ArgumentNullException">name /// <exception cref="ArgumentNullException">name
/// or /// or
/// factory.</exception> /// factory.</exception>
public MethodInfo Retrieve<T>(string name, string alias, params Type[] types) public MethodInfo Retrieve<T>(String name, String alias, params Type[] types) => this.Retrieve(typeof(T), name, alias, types);
=> Retrieve(typeof(T), name, alias, types);
/// <summary> /// <summary>
/// Retrieves the specified name. /// Retrieves the specified name.
@ -36,8 +33,7 @@ namespace Swan.Reflection
/// <returns> /// <returns>
/// The cached MethodInfo. /// The cached MethodInfo.
/// </returns> /// </returns>
public MethodInfo Retrieve<T>(string name, params Type[] types) public MethodInfo Retrieve<T>(String name, params Type[] types) => this.Retrieve(typeof(T), name, name, types);
=> Retrieve(typeof(T), name, name, types);
/// <summary> /// <summary>
/// Retrieves the specified type. /// Retrieves the specified type.
@ -48,8 +44,7 @@ namespace Swan.Reflection
/// <returns> /// <returns>
/// An array of the properties stored for the specified type. /// An array of the properties stored for the specified type.
/// </returns> /// </returns>
public MethodInfo Retrieve(Type type, string name, params Type[] types) public MethodInfo Retrieve(Type type, String name, params Type[] types) => this.Retrieve(type, name, name, types);
=> Retrieve(type, name, name, types);
/// <summary> /// <summary>
/// Retrieves the specified type. /// Retrieves the specified type.
@ -61,20 +56,20 @@ namespace Swan.Reflection
/// <returns> /// <returns>
/// The cached MethodInfo. /// The cached MethodInfo.
/// </returns> /// </returns>
public MethodInfo Retrieve(Type type, string name, string alias, params Type[] types) public MethodInfo Retrieve(Type type, String name, String alias, params Type[] types) {
{ if(type == null) {
if (type == null)
throw new ArgumentNullException(nameof(type)); throw new ArgumentNullException(nameof(type));
}
if (alias == null) if(alias == null) {
throw new ArgumentNullException(nameof(alias)); throw new ArgumentNullException(nameof(alias));
}
if (name == null) if(name == null) {
throw new ArgumentNullException(nameof(name)); throw new ArgumentNullException(nameof(name));
}
return GetOrAdd( return this.GetOrAdd(alias, x => type.GetMethod(name, types ?? Array.Empty<Type>()));
alias,
x => type.GetMethod(name, types ?? Array.Empty<Type>()));
} }
/// <summary> /// <summary>
@ -85,8 +80,7 @@ namespace Swan.Reflection
/// <returns> /// <returns>
/// The cached MethodInfo. /// The cached MethodInfo.
/// </returns> /// </returns>
public MethodInfo Retrieve<T>(string name) public MethodInfo Retrieve<T>(String name) => this.Retrieve(typeof(T), name);
=> Retrieve(typeof(T), name);
/// <summary> /// <summary>
/// Retrieves the specified type. /// Retrieves the specified type.
@ -101,17 +95,16 @@ namespace Swan.Reflection
/// or /// or
/// name. /// name.
/// </exception> /// </exception>
public MethodInfo Retrieve(Type type, string name) public MethodInfo Retrieve(Type type, String name) {
{ if(type == null) {
if (type == null)
throw new ArgumentNullException(nameof(type)); throw new ArgumentNullException(nameof(type));
}
if (name == null) if(name == null) {
throw new ArgumentNullException(nameof(name)); throw new ArgumentNullException(nameof(name));
}
return GetOrAdd( return this.GetOrAdd(name, type.GetMethod);
name,
type.GetMethod);
} }
} }
} }

View File

@ -2,17 +2,14 @@
using System.Reflection; using System.Reflection;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
namespace Swan.Reflection namespace Swan.Reflection {
{
/// <summary> /// <summary>
/// Represents a generic class to store getters and setters. /// Represents a generic class to store getters and setters.
/// </summary> /// </summary>
/// <typeparam name="TClass">The type of the class.</typeparam> /// <typeparam name="TClass">The type of the class.</typeparam>
/// <typeparam name="TProperty">The type of the property.</typeparam> /// <typeparam name="TProperty">The type of the property.</typeparam>
/// <seealso cref="IPropertyProxy" /> /// <seealso cref="IPropertyProxy" />
public sealed class PropertyProxy<TClass, TProperty> : IPropertyProxy public sealed class PropertyProxy<TClass, TProperty> : IPropertyProxy where TClass : class {
where TClass : class
{
private readonly Func<TClass, TProperty> _getter; private readonly Func<TClass, TProperty> _getter;
private readonly Action<TClass, TProperty> _setter; private readonly Action<TClass, TProperty> _setter;
@ -20,28 +17,28 @@ namespace Swan.Reflection
/// Initializes a new instance of the <see cref="PropertyProxy{TClass, TProperty}"/> class. /// Initializes a new instance of the <see cref="PropertyProxy{TClass, TProperty}"/> class.
/// </summary> /// </summary>
/// <param name="property">The property.</param> /// <param name="property">The property.</param>
public PropertyProxy(PropertyInfo property) public PropertyProxy(PropertyInfo property) {
{ if(property == null) {
if (property == null)
throw new ArgumentNullException(nameof(property)); throw new ArgumentNullException(nameof(property));
}
var getterInfo = property.GetGetMethod(false); MethodInfo getterInfo = property.GetGetMethod(false);
if (getterInfo != null) if(getterInfo != null) {
_getter = (Func<TClass, TProperty>)Delegate.CreateDelegate(typeof(Func<TClass, TProperty>), getterInfo); this._getter = (Func<TClass, TProperty>)Delegate.CreateDelegate(typeof(Func<TClass, TProperty>), getterInfo);
}
var setterInfo = property.GetSetMethod(false); MethodInfo setterInfo = property.GetSetMethod(false);
if (setterInfo != null) if(setterInfo != null) {
_setter = (Action<TClass, TProperty>)Delegate.CreateDelegate(typeof(Action<TClass, TProperty>), setterInfo); this._setter = (Action<TClass, TProperty>)Delegate.CreateDelegate(typeof(Action<TClass, TProperty>), setterInfo);
}
} }
/// <inheritdoc /> /// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
object IPropertyProxy.GetValue(object instance) => Object IPropertyProxy.GetValue(Object instance) => this._getter(instance as TClass);
_getter(instance as TClass);
/// <inheritdoc /> /// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
void IPropertyProxy.SetValue(object instance, object value) => void IPropertyProxy.SetValue(Object instance, Object value) => this._setter(instance as TClass, (TProperty)value);
_setter(instance as TClass, (TProperty)value);
} }
} }

View File

@ -3,13 +3,11 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
namespace Swan.Reflection namespace Swan.Reflection {
{
/// <summary> /// <summary>
/// A thread-safe cache of properties belonging to a given type. /// A thread-safe cache of properties belonging to a given type.
/// </summary> /// </summary>
public class PropertyTypeCache : TypeCache<PropertyInfo> public class PropertyTypeCache : TypeCache<PropertyInfo> {
{
/// <summary> /// <summary>
/// Gets the default cache. /// Gets the default cache.
/// </summary> /// </summary>
@ -26,8 +24,7 @@ namespace Swan.Reflection
/// <returns> /// <returns>
/// A collection with all the properties in the given type. /// A collection with all the properties in the given type.
/// </returns> /// </returns>
public IEnumerable<PropertyInfo> RetrieveAllProperties<T>(bool onlyPublic = false) public IEnumerable<PropertyInfo> RetrieveAllProperties<T>(Boolean onlyPublic = false) => this.Retrieve<T>(onlyPublic ? GetAllPublicPropertiesFunc() : GetAllPropertiesFunc());
=> Retrieve<T>(onlyPublic ? GetAllPublicPropertiesFunc() : GetAllPropertiesFunc());
/// <summary> /// <summary>
/// Retrieves all properties. /// Retrieves all properties.
@ -37,8 +34,7 @@ namespace Swan.Reflection
/// <returns> /// <returns>
/// A collection with all the properties in the given type. /// A collection with all the properties in the given type.
/// </returns> /// </returns>
public IEnumerable<PropertyInfo> RetrieveAllProperties(Type type, bool onlyPublic = false) public IEnumerable<PropertyInfo> RetrieveAllProperties(Type type, Boolean onlyPublic = false) => this.Retrieve(type, onlyPublic ? GetAllPublicPropertiesFunc() : GetAllPropertiesFunc());
=> Retrieve(type, onlyPublic ? GetAllPublicPropertiesFunc() : GetAllPropertiesFunc());
/// <summary> /// <summary>
/// Retrieves the filtered properties. /// Retrieves the filtered properties.
@ -49,26 +45,12 @@ namespace Swan.Reflection
/// <returns> /// <returns>
/// A collection with all the properties in the given type. /// A collection with all the properties in the given type.
/// </returns> /// </returns>
public IEnumerable<PropertyInfo> RetrieveFilteredProperties( public IEnumerable<PropertyInfo> RetrieveFilteredProperties(Type type, Boolean onlyPublic, Func<PropertyInfo, Boolean> filter) => this.Retrieve(type, onlyPublic ? GetAllPublicPropertiesFunc(filter) : GetAllPropertiesFunc(filter));
Type type,
bool onlyPublic,
Func<PropertyInfo, bool> filter)
=> Retrieve(type,
onlyPublic ? GetAllPublicPropertiesFunc(filter) : GetAllPropertiesFunc(filter));
private static Func<Type, IEnumerable<PropertyInfo>> GetAllPropertiesFunc( private static Func<Type, IEnumerable<PropertyInfo>> GetAllPropertiesFunc(Func<PropertyInfo, Boolean> filter = null) => GetPropertiesFunc(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, filter);
Func<PropertyInfo, bool> filter = null)
=> GetPropertiesFunc(
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance,
filter);
private static Func<Type, IEnumerable<PropertyInfo>> GetAllPublicPropertiesFunc( private static Func<Type, IEnumerable<PropertyInfo>> GetAllPublicPropertiesFunc(Func<PropertyInfo, Boolean> filter = null) => GetPropertiesFunc(BindingFlags.Public | BindingFlags.Instance, filter);
Func<PropertyInfo, bool> filter = null)
=> GetPropertiesFunc(BindingFlags.Public | BindingFlags.Instance, filter);
private static Func<Type, IEnumerable<PropertyInfo>> GetPropertiesFunc(BindingFlags flags, private static Func<Type, IEnumerable<PropertyInfo>> GetPropertiesFunc(BindingFlags flags, Func<PropertyInfo, Boolean> filter = null) => t => t.GetProperties(flags).Where(filter ?? (p => p.CanRead || p.CanWrite));
Func<PropertyInfo, bool> filter = null)
=> t => t.GetProperties(flags)
.Where(filter ?? (p => p.CanRead || p.CanWrite));
} }
} }

View File

@ -3,8 +3,7 @@ using System.Collections.Generic;
using System.Reflection; using System.Reflection;
using Swan.Collections; using Swan.Collections;
namespace Swan.Reflection namespace Swan.Reflection {
{
/// <summary> /// <summary>
/// A thread-safe cache of members belonging to a given type. /// A thread-safe cache of members belonging to a given type.
/// ///
@ -13,8 +12,7 @@ namespace Swan.Reflection
/// in the cache. /// in the cache.
/// </summary> /// </summary>
/// <typeparam name="T">The type of Member to be cached.</typeparam> /// <typeparam name="T">The type of Member to be cached.</typeparam>
public abstract class TypeCache<T> : CollectionCacheRepository<T> public abstract class TypeCache<T> : CollectionCacheRepository<T> {
{
/// <summary> /// <summary>
/// Determines whether the cache contains the specified type. /// Determines whether the cache contains the specified type.
/// </summary> /// </summary>
@ -22,7 +20,7 @@ namespace Swan.Reflection
/// <returns> /// <returns>
/// <c>true</c> if [contains]; otherwise, <c>false</c>. /// <c>true</c> if [contains]; otherwise, <c>false</c>.
/// </returns> /// </returns>
public bool Contains<TOut>() => ContainsKey(typeof(TOut)); public Boolean Contains<TOut>() => this.ContainsKey(typeof(TOut));
/// <summary> /// <summary>
/// Retrieves the properties stored for the specified type. /// Retrieves the properties stored for the specified type.
@ -32,8 +30,7 @@ namespace Swan.Reflection
/// <typeparam name="TOut">The type of the out.</typeparam> /// <typeparam name="TOut">The type of the out.</typeparam>
/// <param name="factory">The factory.</param> /// <param name="factory">The factory.</param>
/// <returns>An array of the properties stored for the specified type.</returns> /// <returns>An array of the properties stored for the specified type.</returns>
public IEnumerable<T> Retrieve<TOut>(Func<Type, IEnumerable<T>> factory) public IEnumerable<T> Retrieve<TOut>(Func<Type, IEnumerable<T>> factory) => this.Retrieve(typeof(TOut), factory);
=> Retrieve(typeof(TOut), factory);
} }
/// <summary> /// <summary>
@ -42,8 +39,7 @@ namespace Swan.Reflection
/// calls the retrieval process if the type is not contained /// calls the retrieval process if the type is not contained
/// in the cache. /// in the cache.
/// </summary> /// </summary>
public class FieldTypeCache : TypeCache<FieldInfo> public class FieldTypeCache : TypeCache<FieldInfo> {
{
/// <summary> /// <summary>
/// Gets the default cache. /// Gets the default cache.
/// </summary> /// </summary>
@ -59,8 +55,7 @@ namespace Swan.Reflection
/// <returns> /// <returns>
/// A collection with all the fields in the given type. /// A collection with all the fields in the given type.
/// </returns> /// </returns>
public IEnumerable<FieldInfo> RetrieveAllFields<T>() public IEnumerable<FieldInfo> RetrieveAllFields<T>() => this.Retrieve<T>(GetAllFieldsFunc());
=> Retrieve<T>(GetAllFieldsFunc());
/// <summary> /// <summary>
/// Retrieves all fields. /// Retrieves all fields.
@ -69,10 +64,8 @@ namespace Swan.Reflection
/// <returns> /// <returns>
/// A collection with all the fields in the given type. /// A collection with all the fields in the given type.
/// </returns> /// </returns>
public IEnumerable<FieldInfo> RetrieveAllFields(Type type) public IEnumerable<FieldInfo> RetrieveAllFields(Type type) => this.Retrieve(type, GetAllFieldsFunc());
=> Retrieve(type, GetAllFieldsFunc());
private static Func<Type, IEnumerable<FieldInfo>> GetAllFieldsFunc() private static Func<Type, IEnumerable<FieldInfo>> GetAllFieldsFunc() => t => t.GetFields(BindingFlags.Public | BindingFlags.Instance);
=> t => t.GetFields(BindingFlags.Public | BindingFlags.Instance);
} }
} }

View File

@ -1,22 +1,17 @@
using System; using System;
namespace Swan namespace Swan {
{
/// <summary> /// <summary>
/// Represents a singleton pattern abstract class. /// Represents a singleton pattern abstract class.
/// </summary> /// </summary>
/// <typeparam name="T">The type of class.</typeparam> /// <typeparam name="T">The type of class.</typeparam>
public abstract class SingletonBase<T> : IDisposable public abstract class SingletonBase<T> : IDisposable where T : class {
where T : class
{
/// <summary> /// <summary>
/// The static, singleton instance reference. /// The static, singleton instance reference.
/// </summary> /// </summary>
protected static readonly Lazy<T> LazyInstance = new Lazy<T>( protected static readonly Lazy<T> LazyInstance = new Lazy<T>(valueFactory: () => Activator.CreateInstance(typeof(T), true) as T, isThreadSafe: true);
valueFactory: () => Activator.CreateInstance(typeof(T), true) as T,
isThreadSafe: true);
private bool _isDisposing; // To detect redundant calls private Boolean _isDisposing; // To detect redundant calls
/// <summary> /// <summary>
/// Gets the instance that this singleton represents. /// Gets the instance that this singleton represents.
@ -28,7 +23,7 @@ namespace Swan
public static T Instance => LazyInstance.Value; public static T Instance => LazyInstance.Value;
/// <inheritdoc /> /// <inheritdoc />
public void Dispose() => Dispose(true); public void Dispose() => this.Dispose(true);
/// <summary> /// <summary>
/// Releases unmanaged and - optionally - managed resources. /// Releases unmanaged and - optionally - managed resources.
@ -36,22 +31,22 @@ namespace Swan
/// a non-default class finalizer (destructor). /// a non-default class finalizer (destructor).
/// </summary> /// </summary>
/// <param name="disposeManaged"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param> /// <param name="disposeManaged"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
protected virtual void Dispose(bool disposeManaged) protected virtual void Dispose(Boolean disposeManaged) {
{ if(this._isDisposing) {
if (_isDisposing) return; return;
}
_isDisposing = true; this._isDisposing = true;
// free managed resources // free managed resources
if (LazyInstance == null) return; if(LazyInstance == null) {
return;
try
{
var disposableInstance = LazyInstance.Value as IDisposable;
disposableInstance?.Dispose();
} }
catch
{ try {
IDisposable disposableInstance = LazyInstance.Value as IDisposable;
disposableInstance?.Dispose();
} catch {
// swallow // swallow
} }
} }

View File

@ -1,30 +1,25 @@
using System; using System;
using System.Runtime.Serialization; using System.Runtime.Serialization;
namespace Swan namespace Swan {
{
/// <summary> /// <summary>
/// The exception that is thrown when a conversion from a string to a /// The exception that is thrown when a conversion from a string to a
/// specified type fails. /// specified type fails.
/// </summary> /// </summary>
/// <seealso cref="FromString" /> /// <seealso cref="FromString" />
[Serializable] [Serializable]
public class StringConversionException : Exception public class StringConversionException : Exception {
{
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="StringConversionException"/> class. /// Initializes a new instance of the <see cref="StringConversionException"/> class.
/// </summary> /// </summary>
public StringConversionException() public StringConversionException() {
{
} }
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="StringConversionException"/> class. /// Initializes a new instance of the <see cref="StringConversionException"/> class.
/// </summary> /// </summary>
/// <param name="message">The error message that explains the reason for the exception.</param> /// <param name="message">The error message that explains the reason for the exception.</param>
public StringConversionException(string message) public StringConversionException(String message) : base(message) {
: base(message)
{
} }
/// <summary> /// <summary>
@ -33,18 +28,14 @@ namespace Swan
/// <param name="message">The error message that explains the reason for the exception.</param> /// <param name="message">The error message that explains the reason for the exception.</param>
/// <param name="innerException">The exception that is the cause of the current exception, /// <param name="innerException">The exception that is the cause of the current exception,
/// or <see langword="null" /> if no inner exception is specified.</param> /// or <see langword="null" /> if no inner exception is specified.</param>
public StringConversionException(string message, Exception innerException) public StringConversionException(String message, Exception innerException) : base(message, innerException) {
: base(message, innerException)
{
} }
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="StringConversionException"/> class. /// Initializes a new instance of the <see cref="StringConversionException"/> class.
/// </summary> /// </summary>
/// <param name="type">The desired resulting type of the attempted conversion.</param> /// <param name="type">The desired resulting type of the attempted conversion.</param>
public StringConversionException(Type type) public StringConversionException(Type type) : base(BuildStandardMessageForType(type)) {
: base(BuildStandardMessageForType(type))
{
} }
/// <summary> /// <summary>
@ -53,9 +44,7 @@ namespace Swan
/// <param name="type">The desired resulting type of the attempted conversion.</param> /// <param name="type">The desired resulting type of the attempted conversion.</param>
/// <param name="innerException">The exception that is the cause of the current exception, /// <param name="innerException">The exception that is the cause of the current exception,
/// or <see langword="null" /> if no inner exception is specified.</param> /// or <see langword="null" /> if no inner exception is specified.</param>
public StringConversionException(Type type, Exception innerException) public StringConversionException(Type type, Exception innerException) : base(BuildStandardMessageForType(type), innerException) {
: base(BuildStandardMessageForType(type), innerException)
{
} }
/// <summary> /// <summary>
@ -65,12 +54,9 @@ namespace Swan
/// about the exception being thrown.</param> /// about the exception being thrown.</param>
/// <param name="context">The <see cref="StreamingContext" /> that contains contextual information /// <param name="context">The <see cref="StreamingContext" /> that contains contextual information
/// about the source or destination.</param> /// about the source or destination.</param>
protected StringConversionException(SerializationInfo info, StreamingContext context) protected StringConversionException(SerializationInfo info, StreamingContext context) : base(info, context) {
: base(info, context)
{
} }
private static string BuildStandardMessageForType(Type type) private static String BuildStandardMessageForType(Type type) => $"Cannot convert a string to an instance of {type.FullName}";
=> $"Cannot convert a string to an instance of {type.FullName}";
} }
} }

View File

@ -1,23 +1,18 @@
using System; using System;
namespace Swan namespace Swan {
{
/// <summary> /// <summary>
/// An attribute used to help conversion structs back and forth into arrays of bytes via /// An attribute used to help conversion structs back and forth into arrays of bytes via
/// extension methods included in this library ToStruct and ToBytes. /// extension methods included in this library ToStruct and ToBytes.
/// </summary> /// </summary>
/// <seealso cref="Attribute" /> /// <seealso cref="Attribute" />
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Struct)] [AttributeUsage(AttributeTargets.Field | AttributeTargets.Struct)]
public class StructEndiannessAttribute : Attribute public class StructEndiannessAttribute : Attribute {
{
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="StructEndiannessAttribute"/> class. /// Initializes a new instance of the <see cref="StructEndiannessAttribute"/> class.
/// </summary> /// </summary>
/// <param name="endianness">The endianness.</param> /// <param name="endianness">The endianness.</param>
public StructEndiannessAttribute(Endianness endianness) public StructEndiannessAttribute(Endianness endianness) => this.Endianness = endianness;
{
Endianness = endianness;
}
/// <summary> /// <summary>
/// Gets the endianness. /// Gets the endianness.
@ -25,6 +20,8 @@ namespace Swan
/// <value> /// <value>
/// The endianness. /// The endianness.
/// </value> /// </value>
public Endianness Endianness { get; } public Endianness Endianness {
get;
}
} }
} }

View File

@ -13,6 +13,5 @@
<PackageLicenseUrl>https://raw.githubusercontent.com/unosquare/swan/master/LICENSE</PackageLicenseUrl> <PackageLicenseUrl>https://raw.githubusercontent.com/unosquare/swan/master/LICENSE</PackageLicenseUrl>
<PackageTags>best-practices netcore network objectmapper json-serialization</PackageTags> <PackageTags>best-practices netcore network objectmapper json-serialization</PackageTags>
<LangVersion>8.0</LangVersion> <LangVersion>8.0</LangVersion>
<Nullable>enable</Nullable>
</PropertyGroup> </PropertyGroup>
</Project> </Project>

View File

@ -4,39 +4,31 @@ using System.IO;
using System.Reflection; using System.Reflection;
using System.Threading; using System.Threading;
namespace Swan namespace Swan {
{
/// <summary> /// <summary>
/// Provides utility methods to retrieve information about the current application. /// Provides utility methods to retrieve information about the current application.
/// </summary> /// </summary>
public static class SwanRuntime public static class SwanRuntime {
{
private static readonly Lazy<Assembly> EntryAssemblyLazy = new Lazy<Assembly>(Assembly.GetEntryAssembly); private static readonly Lazy<Assembly> EntryAssemblyLazy = new Lazy<Assembly>(Assembly.GetEntryAssembly);
private static readonly Lazy<string> CompanyNameLazy = new Lazy<string>(() => private static readonly Lazy<String> CompanyNameLazy = new Lazy<String>(() => {
{ AssemblyCompanyAttribute attribute = EntryAssembly.GetCustomAttribute(typeof(AssemblyCompanyAttribute)) as AssemblyCompanyAttribute;
var attribute = return attribute?.Company ?? String.Empty;
EntryAssembly.GetCustomAttribute(typeof(AssemblyCompanyAttribute)) as AssemblyCompanyAttribute;
return attribute?.Company ?? string.Empty;
}); });
private static readonly Lazy<string> ProductNameLazy = new Lazy<string>(() => private static readonly Lazy<String> ProductNameLazy = new Lazy<String>(() => {
{ AssemblyProductAttribute attribute = EntryAssembly.GetCustomAttribute(typeof(AssemblyProductAttribute)) as AssemblyProductAttribute;
var attribute = return attribute?.Product ?? String.Empty;
EntryAssembly.GetCustomAttribute(typeof(AssemblyProductAttribute)) as AssemblyProductAttribute;
return attribute?.Product ?? string.Empty;
}); });
private static readonly Lazy<string> ProductTrademarkLazy = new Lazy<string>(() => private static readonly Lazy<String> ProductTrademarkLazy = new Lazy<String>(() => {
{ AssemblyTrademarkAttribute attribute = EntryAssembly.GetCustomAttribute(typeof(AssemblyTrademarkAttribute)) as AssemblyTrademarkAttribute;
var attribute = return attribute?.Trademark ?? String.Empty;
EntryAssembly.GetCustomAttribute(typeof(AssemblyTrademarkAttribute)) as AssemblyTrademarkAttribute;
return attribute?.Trademark ?? string.Empty;
}); });
private static readonly string ApplicationMutexName = "Global\\{{" + EntryAssembly.FullName + "}}"; private static readonly String ApplicationMutexName = "Global\\{{" + EntryAssembly.FullName + "}}";
private static readonly object SyncLock = new object(); private static readonly Object SyncLock = new Object();
private static OperatingSystem? _oS; private static OperatingSystem? _oS;
@ -48,60 +40,42 @@ namespace Swan
/// <value> /// <value>
/// The os. /// The os.
/// </value> /// </value>
public static OperatingSystem OS public static OperatingSystem OS {
{ get {
get if(_oS.HasValue == false) {
{ String windowsDirectory = Environment.GetEnvironmentVariable("windir");
if (_oS.HasValue == false) _oS = String.IsNullOrEmpty(windowsDirectory) == false && windowsDirectory.Contains(@"\") && Directory.Exists(windowsDirectory)
{ ? (OperatingSystem?)OperatingSystem.Windows
var windowsDirectory = Environment.GetEnvironmentVariable("windir"); : (OperatingSystem?)(File.Exists(@"/proc/sys/kernel/ostype") ? OperatingSystem.Unix : OperatingSystem.Osx);
if (string.IsNullOrEmpty(windowsDirectory) == false
&& windowsDirectory.Contains(@"\")
&& Directory.Exists(windowsDirectory))
{
_oS = OperatingSystem.Windows;
}
else
{
_oS = File.Exists(@"/proc/sys/kernel/ostype") ? OperatingSystem.Unix : OperatingSystem.Osx;
}
} }
return _oS ?? OperatingSystem.Unknown; return _oS ?? OperatingSystem.Unknown;
} }
} }
/// <summary> /// <summary>
/// Checks if this application (including version number) is the only instance currently running. /// Checks if this application (including version number) is the only instance currently running.
/// </summary> /// </summary>
/// <value> /// <value>
/// <c>true</c> if this instance is the only instance; otherwise, <c>false</c>. /// <c>true</c> if this instance is the only instance; otherwise, <c>false</c>.
/// </value> /// </value>
public static bool IsTheOnlyInstance [System.Diagnostics.CodeAnalysis.SuppressMessage("Codequalität", "IDE0067:Objekte verwerfen, bevor Bereich verloren geht", Justification = "<Ausstehend>")]
{ public static Boolean IsTheOnlyInstance {
get get {
{ lock(SyncLock) {
lock (SyncLock) try {
{
try
{
// Try to open existing mutex. // Try to open existing mutex.
Mutex.OpenExisting(ApplicationMutexName); _ = Mutex.OpenExisting(ApplicationMutexName);
} } catch {
catch try {
{
try
{
// If exception occurred, there is no such mutex. // If exception occurred, there is no such mutex.
var appMutex = new Mutex(true, ApplicationMutexName); Mutex appMutex = new Mutex(true, ApplicationMutexName);
$"Application Mutex created {appMutex} named '{ApplicationMutexName}'".Debug( $"Application Mutex created {appMutex} named '{ApplicationMutexName}'".Debug(typeof(SwanRuntime));
typeof(SwanRuntime));
// Only one instance. // Only one instance.
return true; return true;
} } catch {
catch
{
// Sometimes the user can't create the Global Mutex // Sometimes the user can't create the Global Mutex
} }
} }
@ -118,7 +92,7 @@ namespace Swan
/// <value> /// <value>
/// <c>true</c> if this instance is using MONO runtime; otherwise, <c>false</c>. /// <c>true</c> if this instance is using MONO runtime; otherwise, <c>false</c>.
/// </value> /// </value>
public static bool IsUsingMonoRuntime => Type.GetType("Mono.Runtime") != null; public static Boolean IsUsingMonoRuntime => Type.GetType("Mono.Runtime") != null;
/// <summary> /// <summary>
/// Gets the assembly that started the application. /// Gets the assembly that started the application.
@ -147,12 +121,10 @@ namespace Swan
/// <value> /// <value>
/// The entry assembly directory. /// The entry assembly directory.
/// </value> /// </value>
public static string EntryAssemblyDirectory public static String EntryAssemblyDirectory {
{ get {
get UriBuilder uri = new UriBuilder(EntryAssembly.CodeBase);
{ String path = Uri.UnescapeDataString(uri.Path);
var uri = new UriBuilder(EntryAssembly.CodeBase);
var path = Uri.UnescapeDataString(uri.Path);
return Path.GetDirectoryName(path); return Path.GetDirectoryName(path);
} }
} }
@ -163,7 +135,7 @@ namespace Swan
/// <value> /// <value>
/// The name of the company. /// The name of the company.
/// </value> /// </value>
public static string CompanyName => CompanyNameLazy.Value; public static String CompanyName => CompanyNameLazy.Value;
/// <summary> /// <summary>
/// Gets the name of the product. /// Gets the name of the product.
@ -171,7 +143,7 @@ namespace Swan
/// <value> /// <value>
/// The name of the product. /// The name of the product.
/// </value> /// </value>
public static string ProductName => ProductNameLazy.Value; public static String ProductName => ProductNameLazy.Value;
/// <summary> /// <summary>
/// Gets the trademark. /// Gets the trademark.
@ -179,7 +151,7 @@ namespace Swan
/// <value> /// <value>
/// The product trademark. /// The product trademark.
/// </value> /// </value>
public static string ProductTrademark => ProductTrademarkLazy.Value; public static String ProductTrademark => ProductTrademarkLazy.Value;
/// <summary> /// <summary>
/// Gets a local storage path with a version. /// Gets a local storage path with a version.
@ -187,18 +159,14 @@ namespace Swan
/// <value> /// <value>
/// The local storage path. /// The local storage path.
/// </value> /// </value>
public static string LocalStoragePath public static String LocalStoragePath {
{ get {
get String localAppDataPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), EntryAssemblyName.Name);
{
var localAppDataPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
EntryAssemblyName.Name);
var returnPath = Path.Combine(localAppDataPath, EntryAssemblyVersion.ToString()); String returnPath = Path.Combine(localAppDataPath, EntryAssemblyVersion.ToString());
if (!Directory.Exists(returnPath)) if(!Directory.Exists(returnPath)) {
{ _ = Directory.CreateDirectory(returnPath);
Directory.CreateDirectory(returnPath);
} }
return returnPath; return returnPath;
@ -217,13 +185,12 @@ namespace Swan
/// The fully qualified location of path, such as "C:\MyFile.txt". /// The fully qualified location of path, such as "C:\MyFile.txt".
/// </returns> /// </returns>
/// <exception cref="ArgumentNullException">filename.</exception> /// <exception cref="ArgumentNullException">filename.</exception>
public static string GetDesktopFilePath(string filename) public static String GetDesktopFilePath(String filename) {
{ if(String.IsNullOrWhiteSpace(filename)) {
if (string.IsNullOrWhiteSpace(filename))
throw new ArgumentNullException(nameof(filename)); throw new ArgumentNullException(nameof(filename));
}
var pathWithFilename = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory), String pathWithFilename = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory), filename);
filename);
return Path.GetFullPath(pathWithFilename); return Path.GetFullPath(pathWithFilename);
} }

View File

@ -1,16 +1,15 @@
namespace Swan using System;
{
namespace Swan {
/// <summary> /// <summary>
/// A console terminal helper to create nicer output and receive input from the user /// A console terminal helper to create nicer output and receive input from the user
/// This class is thread-safe :). /// This class is thread-safe :).
/// </summary> /// </summary>
public static partial class Terminal public static partial class Terminal {
{
/// <summary> /// <summary>
/// Represents a Table to print in console. /// Represents a Table to print in console.
/// </summary> /// </summary>
private static class Table private static class Table {
{
public static void Vertical() => Write('\u2502', Settings.BorderColor); public static void Vertical() => Write('\u2502', Settings.BorderColor);
public static void RightTee() => Write('\u2524', Settings.BorderColor); public static void RightTee() => Write('\u2524', Settings.BorderColor);
@ -25,7 +24,7 @@
public static void LeftTee() => Write('\u251c', Settings.BorderColor); public static void LeftTee() => Write('\u251c', Settings.BorderColor);
public static void Horizontal(int length) => Write(new string('\u2500', length), Settings.BorderColor); public static void Horizontal(Int32 length) => Write(new String('\u2500', length), Settings.BorderColor);
public static void Tee() => Write('\u253c', Settings.BorderColor); public static void Tee() => Write('\u253c', Settings.BorderColor);

View File

@ -1,17 +1,16 @@
using System; #nullable enable
using System;
using System.Collections.Generic; using System.Collections.Generic;
using Swan.Lite.Logging;
using System.Globalization; using System.Globalization;
using Swan.Logging;
namespace Swan using Swan.Lite.Logging;
{
namespace Swan {
/// <summary> /// <summary>
/// A console terminal helper to create nicer output and receive input from the user /// A console terminal helper to create nicer output and receive input from the user
/// This class is thread-safe :). /// This class is thread-safe :).
/// </summary> /// </summary>
public static partial class Terminal public static partial class Terminal {
{
#region ReadKey #region ReadKey
/// <summary> /// <summary>
@ -21,23 +20,23 @@ namespace Swan
/// <param name="disableLocking">if set to <c>true</c> the output will continue to be shown. /// <param name="disableLocking">if set to <c>true</c> the output will continue to be shown.
/// This is useful for services and daemons that are running as console applications and wait for a key to exit the program.</param> /// This is useful for services and daemons that are running as console applications and wait for a key to exit the program.</param>
/// <returns>The console key information.</returns> /// <returns>The console key information.</returns>
public static ConsoleKeyInfo ReadKey(bool intercept, bool disableLocking = false) public static ConsoleKeyInfo ReadKey(Boolean intercept, Boolean disableLocking = false) {
{ if(!IsConsolePresent) {
if (!IsConsolePresent) return default; return default;
if (disableLocking) return Console.ReadKey(intercept); }
lock (SyncLock) if(disableLocking) {
{ return Console.ReadKey(intercept);
}
lock(SyncLock) {
Flush(); Flush();
InputDone.Reset(); InputDone.Reset();
try try {
{
Console.CursorVisible = true; Console.CursorVisible = true;
return Console.ReadKey(intercept); return Console.ReadKey(intercept);
} } finally {
finally
{
Console.CursorVisible = false; Console.CursorVisible = false;
InputDone.Set(); InputDone.Set();
} }
@ -50,19 +49,18 @@ namespace Swan
/// <param name="prompt">The prompt.</param> /// <param name="prompt">The prompt.</param>
/// <param name="preventEcho">if set to <c>true</c> [prevent echo].</param> /// <param name="preventEcho">if set to <c>true</c> [prevent echo].</param>
/// <returns>The console key information.</returns> /// <returns>The console key information.</returns>
public static ConsoleKeyInfo ReadKey(string prompt, bool preventEcho = true) public static ConsoleKeyInfo ReadKey(String prompt, Boolean preventEcho = true) {
{ if(!IsConsolePresent) {
if (!IsConsolePresent) return default; return default;
}
lock (SyncLock) lock(SyncLock) {
{ if(prompt != null) {
if (prompt != null)
{
Write($"{GetNowFormatted()}{Settings.UserInputPrefix} << {prompt} ", ConsoleColor.White); Write($"{GetNowFormatted()}{Settings.UserInputPrefix} << {prompt} ", ConsoleColor.White);
} }
var input = ReadKey(true); ConsoleKeyInfo input = ReadKey(true);
var echo = preventEcho ? string.Empty : input.Key.ToString(); String echo = preventEcho ? String.Empty : input.Key.ToString();
WriteLine(echo); WriteLine(echo);
return input; return input;
} }
@ -75,8 +73,7 @@ namespace Swan
/// <summary> /// <summary>
/// Clears the screen. /// Clears the screen.
/// </summary> /// </summary>
public static void Clear() public static void Clear() {
{
Flush(); Flush();
Console.Clear(); Console.Clear();
} }
@ -85,22 +82,19 @@ namespace Swan
/// Reads a line of text from the console. /// Reads a line of text from the console.
/// </summary> /// </summary>
/// <returns>The read line.</returns> /// <returns>The read line.</returns>
public static string? ReadLine() public static String? ReadLine() {
{ if(IsConsolePresent == false) {
if (IsConsolePresent == false) return default; return default;
}
lock (SyncLock) lock(SyncLock) {
{
Flush(); Flush();
InputDone.Reset(); InputDone.Reset();
try try {
{
Console.CursorVisible = true; Console.CursorVisible = true;
return Console.ReadLine(); return Console.ReadLine();
} } finally {
finally
{
Console.CursorVisible = false; Console.CursorVisible = false;
InputDone.Set(); InputDone.Set();
} }
@ -112,12 +106,12 @@ namespace Swan
/// </summary> /// </summary>
/// <param name="prompt">The prompt.</param> /// <param name="prompt">The prompt.</param>
/// <returns>The read line.</returns> /// <returns>The read line.</returns>
public static string? ReadLine(string prompt) public static String? ReadLine(String prompt) {
{ if(!IsConsolePresent) {
if (!IsConsolePresent) return null; return null;
}
lock (SyncLock) lock(SyncLock) {
{
Write($"{GetNowFormatted()}{Settings.UserInputPrefix} << {prompt}: ", ConsoleColor.White); Write($"{GetNowFormatted()}{Settings.UserInputPrefix} << {prompt}: ", ConsoleColor.White);
return ReadLine(); return ReadLine();
@ -132,17 +126,16 @@ namespace Swan
/// <returns> /// <returns>
/// Conversions of string representation of a number to its 32-bit signed integer equivalent. /// Conversions of string representation of a number to its 32-bit signed integer equivalent.
/// </returns> /// </returns>
public static int ReadNumber(string prompt, int defaultNumber) public static Int32 ReadNumber(String prompt, Int32 defaultNumber) {
{ if(!IsConsolePresent) {
if (!IsConsolePresent) return defaultNumber; return defaultNumber;
}
lock (SyncLock) lock(SyncLock) {
{ Write($"{GetNowFormatted()}{Settings.UserInputPrefix} << {prompt} (default is {defaultNumber}): ", ConsoleColor.White);
Write($"{GetNowFormatted()}{Settings.UserInputPrefix} << {prompt} (default is {defaultNumber}): ",
ConsoleColor.White);
var input = ReadLine(); String? input = ReadLine();
return int.TryParse(input, out var parsedInt) ? parsedInt : defaultNumber; return Int32.TryParse(input, out Int32 parsedInt) ? parsedInt : defaultNumber;
} }
} }
@ -156,24 +149,22 @@ namespace Swan
/// A value that identifies the console key that was pressed. /// A value that identifies the console key that was pressed.
/// </returns> /// </returns>
/// <exception cref="ArgumentNullException">options.</exception> /// <exception cref="ArgumentNullException">options.</exception>
public static ConsoleKeyInfo ReadPrompt( public static ConsoleKeyInfo ReadPrompt(String title, IDictionary<ConsoleKey, String> options, String anyKeyOption) {
string title, if(!IsConsolePresent) {
IDictionary<ConsoleKey, string> options, return default;
string anyKeyOption) }
{
if (!IsConsolePresent) return default;
if (options == null) if(options == null) {
throw new ArgumentNullException(nameof(options)); throw new ArgumentNullException(nameof(options));
}
const ConsoleColor textColor = ConsoleColor.White; const ConsoleColor textColor = ConsoleColor.White;
var lineLength = Console.WindowWidth; Int32 lineLength = Console.WindowWidth;
var lineAlign = -(lineLength - 2); Int32 lineAlign = -(lineLength - 2);
var textFormat = "{0," + lineAlign + "}"; String textFormat = "{0," + lineAlign + "}";
// lock the output as an atomic operation // lock the output as an atomic operation
lock (SyncLock) lock(SyncLock) {
{
{ {
// Top border // Top border
Table.TopLeft(); Table.TopLeft();
@ -184,9 +175,7 @@ namespace Swan
{ {
// Title // Title
Table.Vertical(); Table.Vertical();
var titleText = string.Format(CultureInfo.CurrentCulture, String titleText = String.Format(CultureInfo.CurrentCulture, textFormat, String.IsNullOrWhiteSpace(title) ? " Select an option from the list below." : $" {title}");
textFormat,
string.IsNullOrWhiteSpace(title) ? " Select an option from the list below." : $" {title}");
Write(titleText, textColor); Write(titleText, textColor);
Table.Vertical(); Table.Vertical();
} }
@ -199,30 +188,20 @@ namespace Swan
} }
// Options // Options
foreach (var kvp in options) foreach(KeyValuePair<ConsoleKey, String> kvp in options) {
{
Table.Vertical(); Table.Vertical();
Write(string.Format( Write(String.Format(CultureInfo.CurrentCulture, textFormat, $" {"[ " + kvp.Key + " ]",-10} {kvp.Value}"), textColor);
CultureInfo.CurrentCulture,
textFormat,
$" {"[ " + kvp.Key + " ]",-10} {kvp.Value}"),
textColor);
Table.Vertical(); Table.Vertical();
} }
// Any Key Options // Any Key Options
if (string.IsNullOrWhiteSpace(anyKeyOption) == false) if(String.IsNullOrWhiteSpace(anyKeyOption) == false) {
{
Table.Vertical(); Table.Vertical();
Write(string.Format(CultureInfo.CurrentCulture, textFormat, " "), ConsoleColor.Gray); Write(String.Format(CultureInfo.CurrentCulture, textFormat, " "), ConsoleColor.Gray);
Table.Vertical(); Table.Vertical();
Table.Vertical(); Table.Vertical();
Write(string.Format( Write(String.Format(CultureInfo.CurrentCulture, textFormat, $" {" ",-10} {anyKeyOption}"), ConsoleColor.Gray);
CultureInfo.CurrentCulture,
textFormat,
$" {" ",-10} {anyKeyOption}"),
ConsoleColor.Gray);
Table.Vertical(); Table.Vertical();
} }
@ -233,8 +212,7 @@ namespace Swan
Table.RightTee(); Table.RightTee();
Table.Vertical(); Table.Vertical();
Write(string.Format(CultureInfo.CurrentCulture, textFormat, Settings.UserOptionText), Write(String.Format(CultureInfo.CurrentCulture, textFormat, Settings.UserOptionText), ConsoleColor.Green);
ConsoleColor.Green);
Table.Vertical(); Table.Vertical();
Table.BottomLeft(); Table.BottomLeft();
@ -243,10 +221,10 @@ namespace Swan
} }
} }
var inputLeft = Settings.UserOptionText.Length + 3; Int32 inputLeft = Settings.UserOptionText.Length + 3;
SetCursorPosition(inputLeft, CursorTop - 1); SetCursorPosition(inputLeft, CursorTop - 1);
var userInput = ReadKey(true); ConsoleKeyInfo userInput = ReadKey(true);
Write(userInput.Key.ToString(), ConsoleColor.Gray); Write(userInput.Key.ToString(), ConsoleColor.Gray);
SetCursorPosition(0, CursorTop + 2); SetCursorPosition(0, CursorTop + 2);
@ -255,7 +233,6 @@ namespace Swan
#endregion #endregion
private static string GetNowFormatted() => private static String GetNowFormatted() => $" {(String.IsNullOrWhiteSpace(TextLogger.LoggingTimeFormat) ? String.Empty : DateTime.Now.ToString(TextLogger.LoggingTimeFormat) + " ")}";
$" {(string.IsNullOrWhiteSpace(TextLogger.LoggingTimeFormat) ? string.Empty : DateTime.Now.ToString(TextLogger.LoggingTimeFormat) + " ")}";
} }
} }

View File

@ -1,13 +1,12 @@
using System; #nullable enable
using System;
namespace Swan namespace Swan {
{
/// <summary> /// <summary>
/// A console terminal helper to create nicer output and receive input from the user /// A console terminal helper to create nicer output and receive input from the user
/// This class is thread-safe :). /// This class is thread-safe :).
/// </summary> /// </summary>
public static partial class Terminal public static partial class Terminal {
{
/// <summary> /// <summary>
/// Writes a character a number of times, optionally adding a new line at the end. /// Writes a character a number of times, optionally adding a new line at the end.
/// </summary> /// </summary>
@ -16,20 +15,16 @@ namespace Swan
/// <param name="count">The count.</param> /// <param name="count">The count.</param>
/// <param name="newLine">if set to <c>true</c> [new line].</param> /// <param name="newLine">if set to <c>true</c> [new line].</param>
/// <param name="writerFlags">The writer flags.</param> /// <param name="writerFlags">The writer flags.</param>
public static void Write(char charCode, ConsoleColor? color = null, int count = 1, bool newLine = false, TerminalWriters writerFlags = TerminalWriters.StandardOutput) public static void Write(Char charCode, ConsoleColor? color = null, Int32 count = 1, Boolean newLine = false, TerminalWriters writerFlags = TerminalWriters.StandardOutput) {
{ lock(SyncLock) {
lock (SyncLock) String text = new String(charCode, count);
{
var text = new string(charCode, count);
if (newLine) if(newLine) {
{
text += Environment.NewLine; text += Environment.NewLine;
} }
var buffer = OutputEncoding.GetBytes(text); Byte[] buffer = OutputEncoding.GetBytes(text);
var context = new OutputContext OutputContext context = new OutputContext {
{
OutputColor = color ?? Settings.DefaultColor, OutputColor = color ?? Settings.DefaultColor,
OutputText = OutputEncoding.GetChars(buffer), OutputText = OutputEncoding.GetChars(buffer),
OutputWriters = writerFlags, OutputWriters = writerFlags,
@ -45,15 +40,14 @@ namespace Swan
/// <param name="text">The text.</param> /// <param name="text">The text.</param>
/// <param name="color">The color.</param> /// <param name="color">The color.</param>
/// <param name="writerFlags">The writer flags.</param> /// <param name="writerFlags">The writer flags.</param>
public static void Write(string? text, ConsoleColor? color = null, TerminalWriters writerFlags = TerminalWriters.StandardOutput) public static void Write(String? text, ConsoleColor? color = null, TerminalWriters writerFlags = TerminalWriters.StandardOutput) {
{ if(text == null) {
if (text == null) return; return;
}
lock (SyncLock) lock(SyncLock) {
{ Byte[] buffer = OutputEncoding.GetBytes(text);
var buffer = OutputEncoding.GetBytes(text); OutputContext context = new OutputContext {
var context = new OutputContext
{
OutputColor = color ?? Settings.DefaultColor, OutputColor = color ?? Settings.DefaultColor,
OutputText = OutputEncoding.GetChars(buffer), OutputText = OutputEncoding.GetChars(buffer),
OutputWriters = writerFlags, OutputWriters = writerFlags,
@ -67,8 +61,7 @@ namespace Swan
/// Writes a New Line Sequence to the standard output. /// Writes a New Line Sequence to the standard output.
/// </summary> /// </summary>
/// <param name="writerFlags">The writer flags.</param> /// <param name="writerFlags">The writer flags.</param>
public static void WriteLine(TerminalWriters writerFlags = TerminalWriters.StandardOutput) public static void WriteLine(TerminalWriters writerFlags = TerminalWriters.StandardOutput) => Write(Environment.NewLine, Settings.DefaultColor, writerFlags);
=> Write(Environment.NewLine, Settings.DefaultColor, writerFlags);
/// <summary> /// <summary>
/// Writes a line of text in the current console foreground color /// Writes a line of text in the current console foreground color
@ -77,8 +70,7 @@ namespace Swan
/// <param name="text">The text.</param> /// <param name="text">The text.</param>
/// <param name="color">The color.</param> /// <param name="color">The color.</param>
/// <param name="writerFlags">The writer flags.</param> /// <param name="writerFlags">The writer flags.</param>
public static void WriteLine(string text, ConsoleColor? color = null, TerminalWriters writerFlags = TerminalWriters.StandardOutput) public static void WriteLine(String text, ConsoleColor? color = null, TerminalWriters writerFlags = TerminalWriters.StandardOutput) => Write($"{text ?? String.Empty}{Environment.NewLine}", color, writerFlags);
=> Write($"{text ?? string.Empty}{Environment.NewLine}", color, writerFlags);
/// <summary> /// <summary>
/// As opposed to WriteLine methods, it prepends a Carriage Return character to the text /// As opposed to WriteLine methods, it prepends a Carriage Return character to the text
@ -87,9 +79,8 @@ namespace Swan
/// <param name="text">The text.</param> /// <param name="text">The text.</param>
/// <param name="color">The color.</param> /// <param name="color">The color.</param>
/// <param name="writerFlags">The writer flags.</param> /// <param name="writerFlags">The writer flags.</param>
public static void OverwriteLine(string text, ConsoleColor? color = null, TerminalWriters writerFlags = TerminalWriters.StandardOutput) public static void OverwriteLine(String text, ConsoleColor? color = null, TerminalWriters writerFlags = TerminalWriters.StandardOutput) {
{ Write($"\r{text ?? String.Empty}", color, writerFlags);
Write($"\r{text ?? string.Empty}", color, writerFlags);
Flush(); Flush();
CursorLeft = 0; CursorLeft = 0;
} }

View File

@ -1,18 +1,15 @@
using System; using System;
namespace Swan namespace Swan {
{
/// <summary> /// <summary>
/// A console terminal helper to create nicer output and receive input from the user /// A console terminal helper to create nicer output and receive input from the user
/// This class is thread-safe :). /// This class is thread-safe :).
/// </summary> /// </summary>
public static partial class Terminal public static partial class Terminal {
{
/// <summary> /// <summary>
/// Terminal global settings. /// Terminal global settings.
/// </summary> /// </summary>
public static class Settings public static class Settings {
{
/// <summary> /// <summary>
/// Gets or sets the default output color. /// Gets or sets the default output color.
/// </summary> /// </summary>
@ -35,7 +32,7 @@ namespace Swan
/// <value> /// <value>
/// The user input prefix. /// The user input prefix.
/// </value> /// </value>
public static string UserInputPrefix { get; set; } = "USR"; public static String UserInputPrefix { get; set; } = "USR";
/// <summary> /// <summary>
/// Gets or sets the user option text. /// Gets or sets the user option text.
@ -43,7 +40,7 @@ namespace Swan
/// <value> /// <value>
/// The user option text. /// The user option text.
/// </value> /// </value>
public static string UserOptionText { get; set; } = " Option: "; public static String UserOptionText { get; set; } = " Option: ";
} }
} }
} }

View File

@ -2,27 +2,26 @@
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
using Swan.Threading; using Swan.Threading;
namespace Swan namespace Swan {
{
/// <summary> /// <summary>
/// A console terminal helper to create nicer output and receive input from the user. /// A console terminal helper to create nicer output and receive input from the user.
/// This class is thread-safe :). /// This class is thread-safe :).
/// </summary> /// </summary>
public static partial class Terminal public static partial class Terminal {
{
#region Private Declarations #region Private Declarations
private const int OutputFlushInterval = 15; private const Int32 OutputFlushInterval = 15;
private static readonly ExclusiveTimer DequeueOutputTimer; private static readonly ExclusiveTimer DequeueOutputTimer;
private static readonly object SyncLock = new object(); private static readonly Object SyncLock = new Object();
private static readonly ConcurrentQueue<OutputContext> OutputQueue = new ConcurrentQueue<OutputContext>(); private static readonly ConcurrentQueue<OutputContext> OutputQueue = new ConcurrentQueue<OutputContext>();
private static readonly ManualResetEventSlim OutputDone = new ManualResetEventSlim(false); private static readonly ManualResetEventSlim OutputDone = new ManualResetEventSlim(false);
private static readonly ManualResetEventSlim InputDone = new ManualResetEventSlim(true); private static readonly ManualResetEventSlim InputDone = new ManualResetEventSlim(true);
private static bool? _isConsolePresent; private static Boolean? _isConsolePresent;
#endregion #endregion
@ -31,14 +30,13 @@ namespace Swan
/// <summary> /// <summary>
/// Initializes static members of the <see cref="Terminal"/> class. /// Initializes static members of the <see cref="Terminal"/> class.
/// </summary> /// </summary>
static Terminal() static Terminal() {
{ lock(SyncLock) {
lock (SyncLock) if(DequeueOutputTimer != null) {
{ return;
if (DequeueOutputTimer != null) return; }
if (IsConsolePresent) if(IsConsolePresent) {
{
Console.CursorVisible = false; Console.CursorVisible = false;
} }
@ -58,22 +56,23 @@ namespace Swan
/// <value> /// <value>
/// The cursor left. /// The cursor left.
/// </value> /// </value>
public static int CursorLeft public static Int32 CursorLeft {
{ get {
get if(IsConsolePresent == false) {
{ return -1;
if (IsConsolePresent == false) return -1; }
lock (SyncLock)
{ lock(SyncLock) {
Flush(); Flush();
return Console.CursorLeft; return Console.CursorLeft;
} }
} }
set set {
{ if(IsConsolePresent == false) {
if (IsConsolePresent == false) return; return;
lock (SyncLock) }
{
lock(SyncLock) {
Flush(); Flush();
Console.CursorLeft = value; Console.CursorLeft = value;
} }
@ -86,24 +85,23 @@ namespace Swan
/// <value> /// <value>
/// The cursor top. /// The cursor top.
/// </value> /// </value>
public static int CursorTop public static Int32 CursorTop {
{ get {
get if(IsConsolePresent == false) {
{ return -1;
if (IsConsolePresent == false) return -1; }
lock (SyncLock) lock(SyncLock) {
{
Flush(); Flush();
return Console.CursorTop; return Console.CursorTop;
} }
} }
set set {
{ if(IsConsolePresent == false) {
if (IsConsolePresent == false) return; return;
}
lock (SyncLock) lock(SyncLock) {
{
Flush(); Flush();
Console.CursorTop = value; Console.CursorTop = value;
} }
@ -120,20 +118,14 @@ namespace Swan
/// <value> /// <value>
/// <c>true</c> if this instance is console present; otherwise, <c>false</c>. /// <c>true</c> if this instance is console present; otherwise, <c>false</c>.
/// </value> /// </value>
public static bool IsConsolePresent public static Boolean IsConsolePresent {
{ get {
get if(_isConsolePresent == null) {
{
if (_isConsolePresent == null)
{
_isConsolePresent = true; _isConsolePresent = true;
try try {
{ Int32 windowHeight = Console.WindowHeight;
var windowHeight = Console.WindowHeight;
_isConsolePresent = windowHeight >= 0; _isConsolePresent = windowHeight >= 0;
} } catch {
catch
{
_isConsolePresent = false; _isConsolePresent = false;
} }
} }
@ -148,10 +140,7 @@ namespace Swan
/// <value> /// <value>
/// The available writers. /// The available writers.
/// </value> /// </value>
public static TerminalWriters AvailableWriters => public static TerminalWriters AvailableWriters => IsConsolePresent ? TerminalWriters.StandardError | TerminalWriters.StandardOutput : TerminalWriters.None;
IsConsolePresent
? TerminalWriters.StandardError | TerminalWriters.StandardOutput
: TerminalWriters.None;
/// <summary> /// <summary>
/// Gets or sets the output encoding for the current console. /// Gets or sets the output encoding for the current console.
@ -159,8 +148,7 @@ namespace Swan
/// <value> /// <value>
/// The output encoding. /// The output encoding.
/// </value> /// </value>
public static Encoding OutputEncoding public static Encoding OutputEncoding {
{
get => Console.OutputEncoding; get => Console.OutputEncoding;
set => Console.OutputEncoding = value; set => Console.OutputEncoding = value;
} }
@ -176,41 +164,45 @@ namespace Swan
/// Set the timeout to null or TimeSpan.Zero to wait indefinitely. /// Set the timeout to null or TimeSpan.Zero to wait indefinitely.
/// </summary> /// </summary>
/// <param name="timeout">The timeout. Set the amount of time to black before this method exits.</param> /// <param name="timeout">The timeout. Set the amount of time to black before this method exits.</param>
public static void Flush(TimeSpan? timeout = null) public static void Flush(TimeSpan? timeout = null) {
{ if(timeout == null) {
if (timeout == null) timeout = TimeSpan.Zero; timeout = TimeSpan.Zero;
var startTime = DateTime.UtcNow; }
while (OutputQueue.Count > 0) DateTime startTime = DateTime.UtcNow;
{
while(OutputQueue.Count > 0) {
// Manually trigger a timer cycle to run immediately // Manually trigger a timer cycle to run immediately
DequeueOutputTimer.Change(0, OutputFlushInterval); DequeueOutputTimer.Change(0, OutputFlushInterval);
// Wait for the output to finish // Wait for the output to finish
if (OutputDone.Wait(OutputFlushInterval)) if(OutputDone.Wait(OutputFlushInterval)) {
break; break;
}
// infinite timeout // infinite timeout
if (timeout.Value == TimeSpan.Zero) if(timeout.Value == TimeSpan.Zero) {
continue; continue;
}
// break if we have reached a timeout condition // break if we have reached a timeout condition
if (DateTime.UtcNow.Subtract(startTime) >= timeout.Value) if(DateTime.UtcNow.Subtract(startTime) >= timeout.Value) {
break; break;
} }
} }
}
/// <summary> /// <summary>
/// Sets the cursor position. /// Sets the cursor position.
/// </summary> /// </summary>
/// <param name="left">The left.</param> /// <param name="left">The left.</param>
/// <param name="top">The top.</param> /// <param name="top">The top.</param>
public static void SetCursorPosition(int left, int top) public static void SetCursorPosition(Int32 left, Int32 top) {
{ if(!IsConsolePresent) {
if (!IsConsolePresent) return; return;
}
lock (SyncLock) lock(SyncLock) {
{
Flush(); Flush();
Console.SetCursorPosition(left.Clamp(0, left), top.Clamp(0, top)); Console.SetCursorPosition(left.Clamp(0, left), top.Clamp(0, top));
} }
@ -229,8 +221,7 @@ namespace Swan
/// containing the company name, product name, assembly version and trademark. /// containing the company name, product name, assembly version and trademark.
/// </summary> /// </summary>
/// <param name="color">The color.</param> /// <param name="color">The color.</param>
public static void WriteWelcomeBanner(ConsoleColor color = ConsoleColor.Gray) public static void WriteWelcomeBanner(ConsoleColor color = ConsoleColor.Gray) {
{
WriteLine($"{SwanRuntime.CompanyName} {SwanRuntime.ProductName} [Version {SwanRuntime.EntryAssemblyVersion}]", color); WriteLine($"{SwanRuntime.CompanyName} {SwanRuntime.ProductName} [Version {SwanRuntime.EntryAssemblyVersion}]", color);
WriteLine($"{SwanRuntime.ProductTrademark}", color); WriteLine($"{SwanRuntime.ProductTrademark}", color);
} }
@ -241,20 +232,18 @@ namespace Swan
/// Please note that if AvailableWriters is None, then no output will be enqueued. /// Please note that if AvailableWriters is None, then no output will be enqueued.
/// </summary> /// </summary>
/// <param name="context">The context.</param> /// <param name="context">The context.</param>
private static void EnqueueOutput(OutputContext context) private static void EnqueueOutput(OutputContext context) {
{ lock(SyncLock) {
lock (SyncLock) TerminalWriters availableWriters = AvailableWriters;
{
var availableWriters = AvailableWriters;
if (availableWriters == TerminalWriters.None || context.OutputWriters == TerminalWriters.None) if(availableWriters == TerminalWriters.None || context.OutputWriters == TerminalWriters.None) {
{
OutputDone.Set(); OutputDone.Set();
return; return;
} }
if ((context.OutputWriters & availableWriters) == TerminalWriters.None) if((context.OutputWriters & availableWriters) == TerminalWriters.None) {
return; return;
}
OutputDone.Reset(); OutputDone.Reset();
OutputQueue.Enqueue(context); OutputQueue.Enqueue(context);
@ -264,42 +253,40 @@ namespace Swan
/// <summary> /// <summary>
/// Runs a Terminal I/O cycle in the <see cref="ThreadPool"/> thread. /// Runs a Terminal I/O cycle in the <see cref="ThreadPool"/> thread.
/// </summary> /// </summary>
private static void DequeueOutputCycle() private static void DequeueOutputCycle() {
{ if(AvailableWriters == TerminalWriters.None) {
if (AvailableWriters == TerminalWriters.None)
{
OutputDone.Set(); OutputDone.Set();
return; return;
} }
InputDone.Wait(); InputDone.Wait();
if (OutputQueue.Count <= 0) if(OutputQueue.Count <= 0) {
{
OutputDone.Set(); OutputDone.Set();
return; return;
} }
OutputDone.Reset(); OutputDone.Reset();
while (OutputQueue.Count > 0) while(OutputQueue.Count > 0) {
{ if(!OutputQueue.TryDequeue(out OutputContext context)) {
if (!OutputQueue.TryDequeue(out var context)) continue; continue;
}
// Process Console output and Skip over stuff we can't display so we don't stress the output too much. // Process Console output and Skip over stuff we can't display so we don't stress the output too much.
if (!IsConsolePresent) continue; if(!IsConsolePresent) {
continue;
}
Console.ForegroundColor = context.OutputColor; Console.ForegroundColor = context.OutputColor;
// Output to the standard output // Output to the standard output
if (context.OutputWriters.HasFlag(TerminalWriters.StandardOutput)) if(context.OutputWriters.HasFlag(TerminalWriters.StandardOutput)) {
{
Console.Out.Write(context.OutputText); Console.Out.Write(context.OutputText);
} }
// output to the standard error // output to the standard error
if (context.OutputWriters.HasFlag(TerminalWriters.StandardError)) if(context.OutputWriters.HasFlag(TerminalWriters.StandardError)) {
{
Console.Error.Write(context.OutputText); Console.Error.Write(context.OutputText);
} }
@ -315,23 +302,27 @@ namespace Swan
/// <summary> /// <summary>
/// Represents an asynchronous output context. /// Represents an asynchronous output context.
/// </summary> /// </summary>
private sealed class OutputContext private sealed class OutputContext {
{
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="OutputContext"/> class. /// Initializes a new instance of the <see cref="OutputContext"/> class.
/// </summary> /// </summary>
public OutputContext() public OutputContext() {
{ this.OriginalColor = Settings.DefaultColor;
OriginalColor = Settings.DefaultColor; this.OutputWriters = IsConsolePresent ? TerminalWriters.StandardOutput : TerminalWriters.None;
OutputWriters = IsConsolePresent
? TerminalWriters.StandardOutput
: TerminalWriters.None;
} }
public ConsoleColor OriginalColor { get; } public ConsoleColor OriginalColor {
public ConsoleColor OutputColor { get; set; } get;
public char[] OutputText { get; set; } }
public TerminalWriters OutputWriters { get; set; } public ConsoleColor OutputColor {
get; set;
}
public Char[] OutputText {
get; set;
}
public TerminalWriters OutputWriters {
get; set;
}
} }
#endregion #endregion

View File

@ -1,13 +1,11 @@
using System; using System;
namespace Swan namespace Swan {
{
/// <summary> /// <summary>
/// Defines a set of bitwise standard terminal writers. /// Defines a set of bitwise standard terminal writers.
/// </summary> /// </summary>
[Flags] [Flags]
public enum TerminalWriters public enum TerminalWriters {
{
/// <summary> /// <summary>
/// Prevents output /// Prevents output
/// </summary> /// </summary>

View File

@ -1,24 +1,22 @@
namespace Swan.Threading using System;
{
namespace Swan.Threading {
/// <summary> /// <summary>
/// Fast, atomic boolean combining interlocked to write value and volatile to read values. /// Fast, atomic boolean combining interlocked to write value and volatile to read values.
/// </summary> /// </summary>
public sealed class AtomicBoolean : AtomicTypeBase<bool> public sealed class AtomicBoolean : AtomicTypeBase<Boolean> {
{
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="AtomicBoolean"/> class. /// Initializes a new instance of the <see cref="AtomicBoolean"/> class.
/// </summary> /// </summary>
/// <param name="initialValue">if set to <c>true</c> [initial value].</param> /// <param name="initialValue">if set to <c>true</c> [initial value].</param>
public AtomicBoolean(bool initialValue = default) public AtomicBoolean(Boolean initialValue = default) : base(initialValue ? 1 : 0) {
: base(initialValue ? 1 : 0)
{
// placeholder // placeholder
} }
/// <inheritdoc/> /// <inheritdoc/>
protected override bool FromLong(long backingValue) => backingValue != 0; protected override Boolean FromLong(Int64 backingValue) => backingValue != 0;
/// <inheritdoc/> /// <inheritdoc/>
protected override long ToLong(bool value) => value ? 1 : 0; protected override Int64 ToLong(Boolean value) => value ? 1 : 0;
} }
} }

View File

@ -1,26 +1,22 @@
using System; using System;
namespace Swan.Threading namespace Swan.Threading {
{
/// <summary> /// <summary>
/// Defines an atomic DateTime. /// Defines an atomic DateTime.
/// </summary> /// </summary>
public sealed class AtomicDateTime : AtomicTypeBase<DateTime> public sealed class AtomicDateTime : AtomicTypeBase<DateTime> {
{
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="AtomicDateTime"/> class. /// Initializes a new instance of the <see cref="AtomicDateTime"/> class.
/// </summary> /// </summary>
/// <param name="initialValue">The initial value.</param> /// <param name="initialValue">The initial value.</param>
public AtomicDateTime(DateTime initialValue) public AtomicDateTime(DateTime initialValue) : base(initialValue.Ticks) {
: base(initialValue.Ticks)
{
// placeholder // placeholder
} }
/// <inheritdoc /> /// <inheritdoc />
protected override DateTime FromLong(long backingValue) => new DateTime(backingValue); protected override DateTime FromLong(Int64 backingValue) => new DateTime(backingValue);
/// <inheritdoc /> /// <inheritdoc />
protected override long ToLong(DateTime value) => value.Ticks; protected override Int64 ToLong(DateTime value) => value.Ticks;
} }
} }

View File

@ -1,28 +1,22 @@
using System; using System;
namespace Swan.Threading namespace Swan.Threading {
{
/// <summary> /// <summary>
/// Fast, atomic double combining interlocked to write value and volatile to read values. /// Fast, atomic double combining interlocked to write value and volatile to read values.
/// </summary> /// </summary>
public sealed class AtomicDouble : AtomicTypeBase<double> public sealed class AtomicDouble : AtomicTypeBase<Double> {
{
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="AtomicDouble"/> class. /// Initializes a new instance of the <see cref="AtomicDouble"/> class.
/// </summary> /// </summary>
/// <param name="initialValue">if set to <c>true</c> [initial value].</param> /// <param name="initialValue">if set to <c>true</c> [initial value].</param>
public AtomicDouble(double initialValue = default) public AtomicDouble(Double initialValue = default) : base(BitConverter.DoubleToInt64Bits(initialValue)) {
: base(BitConverter.DoubleToInt64Bits(initialValue))
{
// placeholder // placeholder
} }
/// <inheritdoc/> /// <inheritdoc/>
protected override double FromLong(long backingValue) => protected override Double FromLong(Int64 backingValue) => BitConverter.Int64BitsToDouble(backingValue);
BitConverter.Int64BitsToDouble(backingValue);
/// <inheritdoc/> /// <inheritdoc/>
protected override long ToLong(double value) => protected override Int64 ToLong(Double value) => BitConverter.DoubleToInt64Bits(value);
BitConverter.DoubleToInt64Bits(value);
} }
} }

View File

@ -1,43 +1,38 @@
using System; using System;
using System.Threading; using System.Threading;
namespace Swan.Threading namespace Swan.Threading {
{
/// <summary> /// <summary>
/// Defines an atomic generic Enum. /// Defines an atomic generic Enum.
/// </summary> /// </summary>
/// <typeparam name="T">The type of enum.</typeparam> /// <typeparam name="T">The type of enum.</typeparam>
public sealed class AtomicEnum<T> public sealed class AtomicEnum<T> where T : struct, IConvertible {
where T : struct, IConvertible private Int64 _backingValue;
{
private long _backingValue;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="AtomicEnum{T}"/> class. /// Initializes a new instance of the <see cref="AtomicEnum{T}"/> class.
/// </summary> /// </summary>
/// <param name="initialValue">The initial value.</param> /// <param name="initialValue">The initial value.</param>
/// <exception cref="ArgumentException">T must be an enumerated type.</exception> /// <exception cref="ArgumentException">T must be an enumerated type.</exception>
public AtomicEnum(T initialValue) public AtomicEnum(T initialValue) {
{ if(!Enum.IsDefined(typeof(T), initialValue)) {
if (!Enum.IsDefined(typeof(T), initialValue))
throw new ArgumentException("T must be an enumerated type"); throw new ArgumentException("T must be an enumerated type");
}
Value = initialValue; this.Value = initialValue;
} }
/// <summary> /// <summary>
/// Gets or sets the value. /// Gets or sets the value.
/// </summary> /// </summary>
public T Value public T Value {
{ get => (T)Enum.ToObject(typeof(T), this.BackingValue);
get => (T)Enum.ToObject(typeof(T), BackingValue); set => this.BackingValue = Convert.ToInt64(value);
set => BackingValue = Convert.ToInt64(value);
} }
private long BackingValue private Int64 BackingValue {
{ get => Interlocked.Read(ref this._backingValue);
get => Interlocked.Read(ref _backingValue); set => Interlocked.Exchange(ref this._backingValue, value);
set => Interlocked.Exchange(ref _backingValue, value);
} }
} }
} }

View File

@ -1,28 +1,22 @@
using System; using System;
namespace Swan.Threading namespace Swan.Threading {
{
/// <summary> /// <summary>
/// Represents an atomically readable or writable integer. /// Represents an atomically readable or writable integer.
/// </summary> /// </summary>
public class AtomicInteger : AtomicTypeBase<int> public class AtomicInteger : AtomicTypeBase<Int32> {
{
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="AtomicInteger"/> class. /// Initializes a new instance of the <see cref="AtomicInteger"/> class.
/// </summary> /// </summary>
/// <param name="initialValue">if set to <c>true</c> [initial value].</param> /// <param name="initialValue">if set to <c>true</c> [initial value].</param>
public AtomicInteger(int initialValue = default) public AtomicInteger(Int32 initialValue = default) : base(Convert.ToInt64(initialValue)) {
: base(Convert.ToInt64(initialValue))
{
// placeholder // placeholder
} }
/// <inheritdoc/> /// <inheritdoc/>
protected override int FromLong(long backingValue) => protected override Int32 FromLong(Int64 backingValue) => Convert.ToInt32(backingValue);
Convert.ToInt32(backingValue);
/// <inheritdoc/> /// <inheritdoc/>
protected override long ToLong(int value) => protected override Int64 ToLong(Int32 value) => Convert.ToInt64(value);
Convert.ToInt64(value);
} }
} }

View File

@ -1,24 +1,22 @@
namespace Swan.Threading using System;
{
namespace Swan.Threading {
/// <summary> /// <summary>
/// Fast, atomic long combining interlocked to write value and volatile to read values. /// Fast, atomic long combining interlocked to write value and volatile to read values.
/// </summary> /// </summary>
public sealed class AtomicLong : AtomicTypeBase<long> public sealed class AtomicLong : AtomicTypeBase<Int64> {
{
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="AtomicLong"/> class. /// Initializes a new instance of the <see cref="AtomicLong"/> class.
/// </summary> /// </summary>
/// <param name="initialValue">if set to <c>true</c> [initial value].</param> /// <param name="initialValue">if set to <c>true</c> [initial value].</param>
public AtomicLong(long initialValue = default) public AtomicLong(Int64 initialValue = default) : base(initialValue) {
: base(initialValue)
{
// placeholder // placeholder
} }
/// <inheritdoc /> /// <inheritdoc />
protected override long FromLong(long backingValue) => backingValue; protected override Int64 FromLong(Int64 backingValue) => backingValue;
/// <inheritdoc /> /// <inheritdoc />
protected override long ToLong(long value) => value; protected override Int64 ToLong(Int64 value) => value;
} }
} }

View File

@ -1,26 +1,22 @@
using System; using System;
namespace Swan.Threading namespace Swan.Threading {
{
/// <summary> /// <summary>
/// Represents an atomic TimeSpan type. /// Represents an atomic TimeSpan type.
/// </summary> /// </summary>
public sealed class AtomicTimeSpan : AtomicTypeBase<TimeSpan> public sealed class AtomicTimeSpan : AtomicTypeBase<TimeSpan> {
{
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="AtomicTimeSpan" /> class. /// Initializes a new instance of the <see cref="AtomicTimeSpan" /> class.
/// </summary> /// </summary>
/// <param name="initialValue">The initial value.</param> /// <param name="initialValue">The initial value.</param>
public AtomicTimeSpan(TimeSpan initialValue) public AtomicTimeSpan(TimeSpan initialValue) : base(initialValue.Ticks) {
: base(initialValue.Ticks)
{
// placeholder // placeholder
} }
/// <inheritdoc /> /// <inheritdoc />
protected override TimeSpan FromLong(long backingValue) => TimeSpan.FromTicks(backingValue); protected override TimeSpan FromLong(Int64 backingValue) => TimeSpan.FromTicks(backingValue);
/// <inheritdoc /> /// <inheritdoc />
protected override long ToLong(TimeSpan value) => value.Ticks < 0 ? 0 : value.Ticks; protected override Int64 ToLong(TimeSpan value) => value.Ticks < 0 ? 0 : value.Ticks;
} }
} }

View File

@ -1,8 +1,7 @@
using System; using System;
using System.Threading; using System.Threading;
namespace Swan.Threading namespace Swan.Threading {
{
/// <summary> /// <summary>
/// Provides a generic implementation of an Atomic (interlocked) type /// Provides a generic implementation of an Atomic (interlocked) type
/// ///
@ -10,36 +9,29 @@ namespace Swan.Threading
/// http://igoro.com/archive/volatile-keyword-in-c-memory-model-explained/. /// http://igoro.com/archive/volatile-keyword-in-c-memory-model-explained/.
/// </summary> /// </summary>
/// <typeparam name="T">The structure type backed by a 64-bit value.</typeparam> /// <typeparam name="T">The structure type backed by a 64-bit value.</typeparam>
public abstract class AtomicTypeBase<T> : IComparable, IComparable<T>, IComparable<AtomicTypeBase<T>>, IEquatable<T>, IEquatable<AtomicTypeBase<T>> public abstract class AtomicTypeBase<T> : IComparable, IComparable<T>, IComparable<AtomicTypeBase<T>>, IEquatable<T>, IEquatable<AtomicTypeBase<T>> where T : struct, IComparable, IComparable<T>, IEquatable<T> {
where T : struct, IComparable, IComparable<T>, IEquatable<T> private Int64 _backingValue;
{
private long _backingValue;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="AtomicTypeBase{T}"/> class. /// Initializes a new instance of the <see cref="AtomicTypeBase{T}"/> class.
/// </summary> /// </summary>
/// <param name="initialValue">The initial value.</param> /// <param name="initialValue">The initial value.</param>
protected AtomicTypeBase(long initialValue) protected AtomicTypeBase(Int64 initialValue) => this.BackingValue = initialValue;
{
BackingValue = initialValue;
}
/// <summary> /// <summary>
/// Gets or sets the value. /// Gets or sets the value.
/// </summary> /// </summary>
public T Value public T Value {
{ get => this.FromLong(this.BackingValue);
get => FromLong(BackingValue); set => this.BackingValue = this.ToLong(value);
set => BackingValue = ToLong(value);
} }
/// <summary> /// <summary>
/// Gets or sets the backing value. /// Gets or sets the backing value.
/// </summary> /// </summary>
protected long BackingValue protected Int64 BackingValue {
{ get => Interlocked.Read(ref this._backingValue);
get => Interlocked.Read(ref _backingValue); set => Interlocked.Exchange(ref this._backingValue, value);
set => Interlocked.Exchange(ref _backingValue, value);
} }
/// <summary> /// <summary>
@ -50,7 +42,7 @@ namespace Swan.Threading
/// <returns> /// <returns>
/// The result of the operator. /// The result of the operator.
/// </returns> /// </returns>
public static bool operator ==(AtomicTypeBase<T> a, T b) => a?.Equals(b) == true; public static Boolean operator ==(AtomicTypeBase<T> a, T b) => a?.Equals(b) == true;
/// <summary> /// <summary>
/// Implements the operator !=. /// Implements the operator !=.
@ -60,7 +52,7 @@ namespace Swan.Threading
/// <returns> /// <returns>
/// The result of the operator. /// The result of the operator.
/// </returns> /// </returns>
public static bool operator !=(AtomicTypeBase<T> a, T b) => a?.Equals(b) == false; public static Boolean operator !=(AtomicTypeBase<T> a, T b) => a?.Equals(b) == false;
/// <summary> /// <summary>
/// Implements the operator &gt;. /// Implements the operator &gt;.
@ -70,7 +62,7 @@ namespace Swan.Threading
/// <returns> /// <returns>
/// The result of the operator. /// The result of the operator.
/// </returns> /// </returns>
public static bool operator >(AtomicTypeBase<T> a, T b) => a.CompareTo(b) > 0; public static Boolean operator >(AtomicTypeBase<T> a, T b) => a.CompareTo(b) > 0;
/// <summary> /// <summary>
/// Implements the operator &lt;. /// Implements the operator &lt;.
@ -80,7 +72,7 @@ namespace Swan.Threading
/// <returns> /// <returns>
/// The result of the operator. /// The result of the operator.
/// </returns> /// </returns>
public static bool operator <(AtomicTypeBase<T> a, T b) => a.CompareTo(b) < 0; public static Boolean operator <(AtomicTypeBase<T> a, T b) => a.CompareTo(b) < 0;
/// <summary> /// <summary>
/// Implements the operator &gt;=. /// Implements the operator &gt;=.
@ -90,7 +82,7 @@ namespace Swan.Threading
/// <returns> /// <returns>
/// The result of the operator. /// The result of the operator.
/// </returns> /// </returns>
public static bool operator >=(AtomicTypeBase<T> a, T b) => a.CompareTo(b) >= 0; public static Boolean operator >=(AtomicTypeBase<T> a, T b) => a.CompareTo(b) >= 0;
/// <summary> /// <summary>
/// Implements the operator &lt;=. /// Implements the operator &lt;=.
@ -100,7 +92,7 @@ namespace Swan.Threading
/// <returns> /// <returns>
/// The result of the operator. /// The result of the operator.
/// </returns> /// </returns>
public static bool operator <=(AtomicTypeBase<T> a, T b) => a.CompareTo(b) <= 0; public static Boolean operator <=(AtomicTypeBase<T> a, T b) => a.CompareTo(b) <= 0;
/// <summary> /// <summary>
/// Implements the operator ++. /// Implements the operator ++.
@ -109,9 +101,8 @@ namespace Swan.Threading
/// <returns> /// <returns>
/// The result of the operator. /// The result of the operator.
/// </returns> /// </returns>
public static AtomicTypeBase<T> operator ++(AtomicTypeBase<T> instance) public static AtomicTypeBase<T> operator ++(AtomicTypeBase<T> instance) {
{ _ = Interlocked.Increment(ref instance._backingValue);
Interlocked.Increment(ref instance._backingValue);
return instance; return instance;
} }
@ -122,9 +113,8 @@ namespace Swan.Threading
/// <returns> /// <returns>
/// The result of the operator. /// The result of the operator.
/// </returns> /// </returns>
public static AtomicTypeBase<T> operator --(AtomicTypeBase<T> instance) public static AtomicTypeBase<T> operator --(AtomicTypeBase<T> instance) {
{ _ = Interlocked.Decrement(ref instance._backingValue);
Interlocked.Decrement(ref instance._backingValue);
return instance; return instance;
} }
@ -136,9 +126,8 @@ namespace Swan.Threading
/// <returns> /// <returns>
/// The result of the operator. /// The result of the operator.
/// </returns> /// </returns>
public static AtomicTypeBase<T> operator +(AtomicTypeBase<T> instance, long operand) public static AtomicTypeBase<T> operator +(AtomicTypeBase<T> instance, Int64 operand) {
{ instance.BackingValue += operand;
instance.BackingValue = instance.BackingValue + operand;
return instance; return instance;
} }
@ -150,9 +139,8 @@ namespace Swan.Threading
/// <returns> /// <returns>
/// The result of the operator. /// The result of the operator.
/// </returns> /// </returns>
public static AtomicTypeBase<T> operator -(AtomicTypeBase<T> instance, long operand) public static AtomicTypeBase<T> operator -(AtomicTypeBase<T> instance, Int64 operand) {
{ instance.BackingValue -= operand;
instance.BackingValue = instance.BackingValue - operand;
return instance; return instance;
} }
@ -162,54 +150,43 @@ namespace Swan.Threading
/// <param name="other">The other instance.</param> /// <param name="other">The other instance.</param>
/// <returns>0 if equal, 1 if this instance is greater, -1 if this instance is less than.</returns> /// <returns>0 if equal, 1 if this instance is greater, -1 if this instance is less than.</returns>
/// <exception cref="ArgumentException">When types are incompatible.</exception> /// <exception cref="ArgumentException">When types are incompatible.</exception>
public int CompareTo(object other) public Int32 CompareTo(Object other) => other switch
{ {
switch (other) null => 1,
{ AtomicTypeBase<T> atomic => this.BackingValue.CompareTo(atomic.BackingValue),
case null: T variable => this.Value.CompareTo(variable),
return 1;
case AtomicTypeBase<T> atomic:
return BackingValue.CompareTo(atomic.BackingValue);
case T variable:
return Value.CompareTo(variable);
}
throw new ArgumentException("Incompatible comparison types"); _ => throw new ArgumentException("Incompatible comparison types"),
} };
/// <summary> /// <summary>
/// Compares the value to the other instance. /// Compares the value to the other instance.
/// </summary> /// </summary>
/// <param name="other">The other instance.</param> /// <param name="other">The other instance.</param>
/// <returns>0 if equal, 1 if this instance is greater, -1 if this instance is less than.</returns> /// <returns>0 if equal, 1 if this instance is greater, -1 if this instance is less than.</returns>
public int CompareTo(T other) => Value.CompareTo(other); public Int32 CompareTo(T other) => this.Value.CompareTo(other);
/// <summary> /// <summary>
/// Compares the value to the other instance. /// Compares the value to the other instance.
/// </summary> /// </summary>
/// <param name="other">The other instance.</param> /// <param name="other">The other instance.</param>
/// <returns>0 if equal, 1 if this instance is greater, -1 if this instance is less than.</returns> /// <returns>0 if equal, 1 if this instance is greater, -1 if this instance is less than.</returns>
public int CompareTo(AtomicTypeBase<T> other) => BackingValue.CompareTo(other?.BackingValue ?? default); public Int32 CompareTo(AtomicTypeBase<T> other) => this.BackingValue.CompareTo(other?.BackingValue ?? default);
/// <summary> /// <summary>
/// Determines whether the specified <see cref="object" />, is equal to this instance. /// Determines whether the specified <see cref="Object" />, is equal to this instance.
/// </summary> /// </summary>
/// <param name="other">The <see cref="object" /> to compare with this instance.</param> /// <param name="other">The <see cref="Object" /> to compare with this instance.</param>
/// <returns> /// <returns>
/// <c>true</c> if the specified <see cref="object" /> is equal to this instance; otherwise, <c>false</c>. /// <c>true</c> if the specified <see cref="Object" /> is equal to this instance; otherwise, <c>false</c>.
/// </returns> /// </returns>
public override bool Equals(object other) public override Boolean Equals(Object other) => other switch
{ {
switch (other) AtomicTypeBase<T> atomic => this.Equals(atomic),
{ T variable => this.Equals(variable),
case AtomicTypeBase<T> atomic:
return Equals(atomic);
case T variable:
return Equals(variable);
}
return false; _ => false,
} };
/// <summary> /// <summary>
/// Returns a hash code for this instance. /// Returns a hash code for this instance.
@ -217,27 +194,26 @@ namespace Swan.Threading
/// <returns> /// <returns>
/// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table.
/// </returns> /// </returns>
public override int GetHashCode() => BackingValue.GetHashCode(); public override Int32 GetHashCode() => this.BackingValue.GetHashCode();
/// <inheritdoc /> /// <inheritdoc />
public bool Equals(AtomicTypeBase<T> other) => public Boolean Equals(AtomicTypeBase<T> other) => this.BackingValue == (other?.BackingValue ?? default);
BackingValue == (other?.BackingValue ?? default);
/// <inheritdoc /> /// <inheritdoc />
public bool Equals(T other) => Equals(Value, other); public Boolean Equals(T other) => Equals(this.Value, other);
/// <summary> /// <summary>
/// Converts from a long value to the target type. /// Converts from a long value to the target type.
/// </summary> /// </summary>
/// <param name="backingValue">The backing value.</param> /// <param name="backingValue">The backing value.</param>
/// <returns>The value converted form a long value.</returns> /// <returns>The value converted form a long value.</returns>
protected abstract T FromLong(long backingValue); protected abstract T FromLong(Int64 backingValue);
/// <summary> /// <summary>
/// Converts from the target type to a long value. /// Converts from the target type to a long value.
/// </summary> /// </summary>
/// <param name="value">The value.</param> /// <param name="value">The value.</param>
/// <returns>The value converted to a long value.</returns> /// <returns>The value converted to a long value.</returns>
protected abstract long ToLong(T value); protected abstract Int64 ToLong(T value);
} }
} }

View File

@ -1,29 +1,22 @@
using System; using System;
using System.Threading; using System.Threading;
namespace Swan.Threading namespace Swan.Threading {
{
/// <summary> /// <summary>
/// Acts as a <see cref="CancellationTokenSource"/> but with reusable tokens. /// Acts as a <see cref="CancellationTokenSource"/> but with reusable tokens.
/// </summary> /// </summary>
public sealed class CancellationTokenOwner : IDisposable public sealed class CancellationTokenOwner : IDisposable {
{ private readonly Object _syncLock = new Object();
private readonly object _syncLock = new object(); private Boolean _isDisposed;
private bool _isDisposed;
private CancellationTokenSource _tokenSource = new CancellationTokenSource(); private CancellationTokenSource _tokenSource = new CancellationTokenSource();
/// <summary> /// <summary>
/// Gets the token of the current. /// Gets the token of the current.
/// </summary> /// </summary>
public CancellationToken Token public CancellationToken Token {
{ get {
get lock(this._syncLock) {
{ return this._isDisposed ? CancellationToken.None : this._tokenSource.Token;
lock (_syncLock)
{
return _isDisposed
? CancellationToken.None
: _tokenSource.Token;
} }
} }
} }
@ -31,37 +24,37 @@ namespace Swan.Threading
/// <summary> /// <summary>
/// Cancels the last referenced token and creates a new token source. /// Cancels the last referenced token and creates a new token source.
/// </summary> /// </summary>
public void Cancel() public void Cancel() {
{ lock(this._syncLock) {
lock (_syncLock) if(this._isDisposed) {
{ return;
if (_isDisposed) return; }
_tokenSource.Cancel();
_tokenSource.Dispose(); this._tokenSource.Cancel();
_tokenSource = new CancellationTokenSource(); this._tokenSource.Dispose();
this._tokenSource = new CancellationTokenSource();
} }
} }
/// <inheritdoc /> /// <inheritdoc />
public void Dispose() => Dispose(true); public void Dispose() => this.Dispose(true);
/// <summary> /// <summary>
/// Releases unmanaged and - optionally - managed resources. /// Releases unmanaged and - optionally - managed resources.
/// </summary> /// </summary>
/// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param> /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
private void Dispose(bool disposing) private void Dispose(Boolean disposing) {
{ lock(this._syncLock) {
lock (_syncLock) if(this._isDisposed) {
{ return;
if (_isDisposed) return;
if (disposing)
{
_tokenSource.Cancel();
_tokenSource.Dispose();
} }
_isDisposed = true; if(disposing) {
this._tokenSource.Cancel();
this._tokenSource.Dispose();
}
this._isDisposed = true;
} }
} }
} }

View File

@ -1,22 +1,20 @@
using System; using System;
using System.Threading; using System.Threading;
namespace Swan.Threading namespace Swan.Threading {
{
/// <summary> /// <summary>
/// A threading <see cref="_backingTimer"/> implementation that executes at most one cycle at a time /// A threading <see cref="_backingTimer"/> implementation that executes at most one cycle at a time
/// in a <see cref="ThreadPool"/> thread. Callback execution is NOT guaranteed to be carried out /// in a <see cref="ThreadPool"/> thread. Callback execution is NOT guaranteed to be carried out
/// on the same <see cref="ThreadPool"/> thread every time the timer fires. /// on the same <see cref="ThreadPool"/> thread every time the timer fires.
/// </summary> /// </summary>
public sealed class ExclusiveTimer : IDisposable public sealed class ExclusiveTimer : IDisposable {
{ private readonly Object _syncLock = new Object();
private readonly object _syncLock = new object();
private readonly ManualResetEventSlim _cycleDoneEvent = new ManualResetEventSlim(true); private readonly ManualResetEventSlim _cycleDoneEvent = new ManualResetEventSlim(true);
private readonly Timer _backingTimer; private readonly Timer _backingTimer;
private readonly TimerCallback _userCallback; private readonly TimerCallback _userCallback;
private readonly AtomicBoolean _isDisposing = new AtomicBoolean(); private readonly AtomicBoolean _isDisposing = new AtomicBoolean();
private readonly AtomicBoolean _isDisposed = new AtomicBoolean(); private readonly AtomicBoolean _isDisposed = new AtomicBoolean();
private int _period; private Int32 _period;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ExclusiveTimer"/> class. /// Initializes a new instance of the <see cref="ExclusiveTimer"/> class.
@ -25,11 +23,10 @@ namespace Swan.Threading
/// <param name="state">The state.</param> /// <param name="state">The state.</param>
/// <param name="dueTime">The due time.</param> /// <param name="dueTime">The due time.</param>
/// <param name="period">The period.</param> /// <param name="period">The period.</param>
public ExclusiveTimer(TimerCallback timerCallback, object state, int dueTime, int period) public ExclusiveTimer(TimerCallback timerCallback, Object state, Int32 dueTime, Int32 period) {
{ this._period = period;
_period = period; this._userCallback = timerCallback;
_userCallback = timerCallback; this._backingTimer = new Timer(this.InternalCallback, state ?? this, dueTime, Timeout.Infinite);
_backingTimer = new Timer(InternalCallback, state ?? this, dueTime, Timeout.Infinite);
} }
/// <summary> /// <summary>
@ -39,9 +36,7 @@ namespace Swan.Threading
/// <param name="state">The state.</param> /// <param name="state">The state.</param>
/// <param name="dueTime">The due time.</param> /// <param name="dueTime">The due time.</param>
/// <param name="period">The period.</param> /// <param name="period">The period.</param>
public ExclusiveTimer(TimerCallback timerCallback, object state, TimeSpan dueTime, TimeSpan period) public ExclusiveTimer(TimerCallback timerCallback, Object state, TimeSpan dueTime, TimeSpan period) : this(timerCallback, state, Convert.ToInt32(dueTime.TotalMilliseconds), Convert.ToInt32(period.TotalMilliseconds)) {
: this(timerCallback, state, Convert.ToInt32(dueTime.TotalMilliseconds), Convert.ToInt32(period.TotalMilliseconds))
{
// placeholder // placeholder
} }
@ -49,9 +44,7 @@ namespace Swan.Threading
/// Initializes a new instance of the <see cref="ExclusiveTimer"/> class. /// Initializes a new instance of the <see cref="ExclusiveTimer"/> class.
/// </summary> /// </summary>
/// <param name="timerCallback">The timer callback.</param> /// <param name="timerCallback">The timer callback.</param>
public ExclusiveTimer(TimerCallback timerCallback) public ExclusiveTimer(TimerCallback timerCallback) : this(timerCallback, null, Timeout.Infinite, Timeout.Infinite) {
: this(timerCallback, null, Timeout.Infinite, Timeout.Infinite)
{
// placeholder // placeholder
} }
@ -61,9 +54,7 @@ namespace Swan.Threading
/// <param name="timerCallback">The timer callback.</param> /// <param name="timerCallback">The timer callback.</param>
/// <param name="dueTime">The due time.</param> /// <param name="dueTime">The due time.</param>
/// <param name="period">The period.</param> /// <param name="period">The period.</param>
public ExclusiveTimer(Action timerCallback, int dueTime, int period) public ExclusiveTimer(Action timerCallback, Int32 dueTime, Int32 period) : this(s => timerCallback?.Invoke(), null, dueTime, period) {
: this(s => { timerCallback?.Invoke(); }, null, dueTime, period)
{
// placeholder // placeholder
} }
@ -73,9 +64,7 @@ namespace Swan.Threading
/// <param name="timerCallback">The timer callback.</param> /// <param name="timerCallback">The timer callback.</param>
/// <param name="dueTime">The due time.</param> /// <param name="dueTime">The due time.</param>
/// <param name="period">The period.</param> /// <param name="period">The period.</param>
public ExclusiveTimer(Action timerCallback, TimeSpan dueTime, TimeSpan period) public ExclusiveTimer(Action timerCallback, TimeSpan dueTime, TimeSpan period) : this(s => timerCallback?.Invoke(), null, dueTime, period) {
: this(s => { timerCallback?.Invoke(); }, null, dueTime, period)
{
// placeholder // placeholder
} }
@ -83,9 +72,7 @@ namespace Swan.Threading
/// Initializes a new instance of the <see cref="ExclusiveTimer"/> class. /// Initializes a new instance of the <see cref="ExclusiveTimer"/> class.
/// </summary> /// </summary>
/// <param name="timerCallback">The timer callback.</param> /// <param name="timerCallback">The timer callback.</param>
public ExclusiveTimer(Action timerCallback) public ExclusiveTimer(Action timerCallback) : this(timerCallback, Timeout.Infinite, Timeout.Infinite) {
: this(timerCallback, Timeout.Infinite, Timeout.Infinite)
{
// placeholder // placeholder
} }
@ -95,7 +82,7 @@ namespace Swan.Threading
/// <value> /// <value>
/// <c>true</c> if this instance is disposing; otherwise, <c>false</c>. /// <c>true</c> if this instance is disposing; otherwise, <c>false</c>.
/// </value> /// </value>
public bool IsDisposing => _isDisposing.Value; public Boolean IsDisposing => this._isDisposing.Value;
/// <summary> /// <summary>
/// Gets a value indicating whether this instance is disposed. /// Gets a value indicating whether this instance is disposed.
@ -103,37 +90,29 @@ namespace Swan.Threading
/// <value> /// <value>
/// <c>true</c> if this instance is disposed; otherwise, <c>false</c>. /// <c>true</c> if this instance is disposed; otherwise, <c>false</c>.
/// </value> /// </value>
public bool IsDisposed => _isDisposed.Value; public Boolean IsDisposed => this._isDisposed.Value;
/// <summary> /// <summary>
/// Waits until the time is elapsed. /// Waits until the time is elapsed.
/// </summary> /// </summary>
/// <param name="untilDate">The until date.</param> /// <param name="untilDate">The until date.</param>
/// <param name="cancellationToken">The cancellation token.</param> /// <param name="cancellationToken">The cancellation token.</param>
public static void WaitUntil(DateTime untilDate, CancellationToken cancellationToken = default) public static void WaitUntil(DateTime untilDate, CancellationToken cancellationToken = default) {
{ static void Callback(IWaitEvent waitEvent) {
void Callback(IWaitEvent waitEvent) try {
{
try
{
waitEvent.Complete(); waitEvent.Complete();
waitEvent.Begin(); waitEvent.Begin();
} } catch {
catch
{
// ignore // ignore
} }
} }
using (var delayLock = WaitEventFactory.Create(true)) using IWaitEvent delayLock = WaitEventFactory.Create(true);
{ using ExclusiveTimer _ = new ExclusiveTimer(() => Callback(delayLock), 0, 15);
using (var _ = new ExclusiveTimer(() => Callback(delayLock), 0, 15)) while(!cancellationToken.IsCancellationRequested && DateTime.UtcNow < untilDate) {
{
while (!cancellationToken.IsCancellationRequested && DateTime.UtcNow < untilDate)
delayLock.Wait(); delayLock.Wait();
} }
} }
}
/// <summary> /// <summary>
/// Waits the specified wait time. /// Waits the specified wait time.
@ -148,11 +127,10 @@ namespace Swan.Threading
/// </summary> /// </summary>
/// <param name="dueTime">The due time.</param> /// <param name="dueTime">The due time.</param>
/// <param name="period">The period.</param> /// <param name="period">The period.</param>
public void Change(int dueTime, int period) public void Change(Int32 dueTime, Int32 period) {
{ this._period = period;
_period = period;
_backingTimer.Change(dueTime, Timeout.Infinite); _ = this._backingTimer.Change(dueTime, Timeout.Infinite);
} }
/// <summary> /// <summary>
@ -160,48 +138,43 @@ namespace Swan.Threading
/// </summary> /// </summary>
/// <param name="dueTime">The due time.</param> /// <param name="dueTime">The due time.</param>
/// <param name="period">The period.</param> /// <param name="period">The period.</param>
public void Change(TimeSpan dueTime, TimeSpan period) public void Change(TimeSpan dueTime, TimeSpan period) => this.Change(Convert.ToInt32(dueTime.TotalMilliseconds), Convert.ToInt32(period.TotalMilliseconds));
=> Change(Convert.ToInt32(dueTime.TotalMilliseconds), Convert.ToInt32(period.TotalMilliseconds));
/// <summary> /// <summary>
/// Changes the interval between method invocations for the internal timer. /// Changes the interval between method invocations for the internal timer.
/// </summary> /// </summary>
/// <param name="period">The period.</param> /// <param name="period">The period.</param>
public void Resume(int period) => Change(0, period); public void Resume(Int32 period) => this.Change(0, period);
/// <summary> /// <summary>
/// Changes the interval between method invocations for the internal timer. /// Changes the interval between method invocations for the internal timer.
/// </summary> /// </summary>
/// <param name="period">The period.</param> /// <param name="period">The period.</param>
public void Resume(TimeSpan period) => Change(TimeSpan.Zero, period); public void Resume(TimeSpan period) => this.Change(TimeSpan.Zero, period);
/// <summary> /// <summary>
/// Pauses this instance. /// Pauses this instance.
/// </summary> /// </summary>
public void Pause() => Change(Timeout.Infinite, Timeout.Infinite); public void Pause() => this.Change(Timeout.Infinite, Timeout.Infinite);
/// <inheritdoc /> /// <inheritdoc />
public void Dispose() public void Dispose() {
{ lock(this._syncLock) {
lock (_syncLock) if(this._isDisposed == true || this._isDisposing == true) {
{
if (_isDisposed == true || _isDisposing == true)
return; return;
_isDisposing.Value = true;
} }
try this._isDisposing.Value = true;
{
_cycleDoneEvent.Wait();
_cycleDoneEvent.Dispose();
Pause();
_backingTimer.Dispose();
} }
finally
{ try {
_isDisposed.Value = true; this._cycleDoneEvent.Wait();
_isDisposing.Value = false; this._cycleDoneEvent.Dispose();
this.Pause();
this._backingTimer.Dispose();
} finally {
this._isDisposed.Value = true;
this._isDisposing.Value = false;
} }
} }
@ -209,27 +182,24 @@ namespace Swan.Threading
/// Logic that runs every time the timer hits the due time. /// Logic that runs every time the timer hits the due time.
/// </summary> /// </summary>
/// <param name="state">The state.</param> /// <param name="state">The state.</param>
private void InternalCallback(object state) private void InternalCallback(Object state) {
{ lock(this._syncLock) {
lock (_syncLock) if(this.IsDisposed || this.IsDisposing) {
{ return;
if (IsDisposed || IsDisposing) }
}
if(this._cycleDoneEvent.IsSet == false) {
return; return;
} }
if (_cycleDoneEvent.IsSet == false) this._cycleDoneEvent.Reset();
return;
_cycleDoneEvent.Reset(); try {
this._userCallback(state);
try } finally {
{ this._cycleDoneEvent?.Set();
_userCallback(state); _ = this._backingTimer?.Change(this._period, Timeout.Infinite);
}
finally
{
_cycleDoneEvent?.Set();
_backingTimer?.Change(_period, Timeout.Infinite);
} }
} }
} }

View File

@ -1,12 +1,10 @@
using System; using System;
namespace Swan.Threading namespace Swan.Threading {
{
/// <summary> /// <summary>
/// Defines a generic interface for synchronized locking mechanisms. /// Defines a generic interface for synchronized locking mechanisms.
/// </summary> /// </summary>
public interface ISyncLocker : IDisposable public interface ISyncLocker : IDisposable {
{
/// <summary> /// <summary>
/// Acquires a writer lock. /// Acquires a writer lock.
/// The lock is released when the returned locking object is disposed. /// The lock is released when the returned locking object is disposed.

View File

@ -1,33 +1,39 @@
using System; using System;
namespace Swan.Threading namespace Swan.Threading {
{
/// <summary> /// <summary>
/// Provides a generalized API for ManualResetEvent and ManualResetEventSlim. /// Provides a generalized API for ManualResetEvent and ManualResetEventSlim.
/// </summary> /// </summary>
/// <seealso cref="IDisposable" /> /// <seealso cref="IDisposable" />
public interface IWaitEvent : IDisposable public interface IWaitEvent : IDisposable {
{
/// <summary> /// <summary>
/// Gets a value indicating whether the event is in the completed state. /// Gets a value indicating whether the event is in the completed state.
/// </summary> /// </summary>
bool IsCompleted { get; } Boolean IsCompleted {
get;
}
/// <summary> /// <summary>
/// Gets a value indicating whether the Begin method has been called. /// Gets a value indicating whether the Begin method has been called.
/// It returns false after the Complete method is called. /// It returns false after the Complete method is called.
/// </summary> /// </summary>
bool IsInProgress { get; } Boolean IsInProgress {
get;
}
/// <summary> /// <summary>
/// Returns true if the underlying handle is not closed and it is still valid. /// Returns true if the underlying handle is not closed and it is still valid.
/// </summary> /// </summary>
bool IsValid { get; } Boolean IsValid {
get;
}
/// <summary> /// <summary>
/// Gets a value indicating whether this instance is disposed. /// Gets a value indicating whether this instance is disposed.
/// </summary> /// </summary>
bool IsDisposed { get; } Boolean IsDisposed {
get;
}
/// <summary> /// <summary>
/// Enters the state in which waiters need to wait. /// Enters the state in which waiters need to wait.
@ -52,6 +58,6 @@ namespace Swan.Threading
/// </summary> /// </summary>
/// <param name="timeout">The maximum amount of time to wait for.</param> /// <param name="timeout">The maximum amount of time to wait for.</param>
/// <returns><c>true</c> when there was no timeout. <c>false</c> if the timeout was reached.</returns> /// <returns><c>true</c> when there was no timeout. <c>false</c> if the timeout was reached.</returns>
bool Wait(TimeSpan timeout); Boolean Wait(TimeSpan timeout);
} }
} }

View File

@ -1,17 +1,17 @@
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Swan.Threading namespace Swan.Threading {
{
/// <summary> /// <summary>
/// Defines a standard API to control background application workers. /// Defines a standard API to control background application workers.
/// </summary> /// </summary>
public interface IWorker public interface IWorker {
{
/// <summary> /// <summary>
/// Gets the current state of the worker. /// Gets the current state of the worker.
/// </summary> /// </summary>
WorkerState WorkerState { get; } WorkerState WorkerState {
get;
}
/// <summary> /// <summary>
/// Gets a value indicating whether this instance is disposed. /// Gets a value indicating whether this instance is disposed.
@ -19,7 +19,9 @@ namespace Swan.Threading
/// <value> /// <value>
/// <c>true</c> if this instance is disposed; otherwise, <c>false</c>. /// <c>true</c> if this instance is disposed; otherwise, <c>false</c>.
/// </value> /// </value>
bool IsDisposed { get; } Boolean IsDisposed {
get;
}
/// <summary> /// <summary>
/// Gets a value indicating whether this instance is currently being disposed. /// Gets a value indicating whether this instance is currently being disposed.
@ -27,17 +29,23 @@ namespace Swan.Threading
/// <value> /// <value>
/// <c>true</c> if this instance is disposing; otherwise, <c>false</c>. /// <c>true</c> if this instance is disposing; otherwise, <c>false</c>.
/// </value> /// </value>
bool IsDisposing { get; } Boolean IsDisposing {
get;
}
/// <summary> /// <summary>
/// Gets or sets the time interval used to execute cycles. /// Gets or sets the time interval used to execute cycles.
/// </summary> /// </summary>
TimeSpan Period { get; set; } TimeSpan Period {
get; set;
}
/// <summary> /// <summary>
/// Gets the name identifier of this worker. /// Gets the name identifier of this worker.
/// </summary> /// </summary>
string Name { get; } String Name {
get;
}
/// <summary> /// <summary>
/// Starts execution of worker cycles. /// Starts execution of worker cycles.

View File

@ -1,13 +1,12 @@
using System.Threading; using System;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Swan.Threading namespace Swan.Threading {
{
/// <summary> /// <summary>
/// An interface for a worker cycle delay provider. /// An interface for a worker cycle delay provider.
/// </summary> /// </summary>
public interface IWorkerDelayProvider public interface IWorkerDelayProvider {
{
/// <summary> /// <summary>
/// Suspends execution queues a new cycle for execution. The delay is given in /// Suspends execution queues a new cycle for execution. The delay is given in
/// milliseconds. When overridden in a derived class the wait handle will be set /// milliseconds. When overridden in a derived class the wait handle will be set
@ -16,6 +15,6 @@ namespace Swan.Threading
/// <param name="wantedDelay">The remaining delay to wait for in the cycle.</param> /// <param name="wantedDelay">The remaining delay to wait for in the cycle.</param>
/// <param name="delayTask">Contains a reference to a task with the scheduled period delay.</param> /// <param name="delayTask">Contains a reference to a task with the scheduled period delay.</param>
/// <param name="token">The cancellation token to cancel waiting.</param> /// <param name="token">The cancellation token to cancel waiting.</param>
void ExecuteCycleDelay(int wantedDelay, Task delayTask, CancellationToken token); void ExecuteCycleDelay(Int32 wantedDelay, Task delayTask, CancellationToken token);
} }
} }

Some files were not shown because too many files have changed in this diff Show More