Coding style

This commit is contained in:
BlubbFish 2019-12-04 17:10:06 +01:00
parent c1e8637516
commit 2f74732924
72 changed files with 12543 additions and 13087 deletions

View File

@ -1,243 +1,228 @@
namespace Unosquare.Swan.Abstractions using System;
{ using System.Threading;
using System;
using System.Threading; namespace Unosquare.Swan.Abstractions {
/// <summary>
/// Provides a generic implementation of an Atomic (interlocked) type
///
/// Idea taken from Memory model and .NET operations in article:
/// http://igoro.com/archive/volatile-keyword-in-c-memory-model-explained/.
/// </summary>
/// <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>>
where T : struct, IComparable, IComparable<T>, IEquatable<T> {
private Int64 _backingValue;
/// <summary> /// <summary>
/// Provides a generic implementation of an Atomic (interlocked) type /// Initializes a new instance of the <see cref="AtomicTypeBase{T}"/> class.
///
/// Idea taken from Memory model and .NET operations in article:
/// 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> /// <param name="initialValue">The initial value.</param>
public abstract class AtomicTypeBase<T> : IComparable, IComparable<T>, IComparable<AtomicTypeBase<T>>, IEquatable<T>, IEquatable<AtomicTypeBase<T>> protected AtomicTypeBase(Int64 initialValue) => this.BackingValue = initialValue;
where T : struct, IComparable, IComparable<T>, IEquatable<T>
{
private long _backingValue;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="AtomicTypeBase{T}"/> class. /// Gets or sets the value.
/// </summary> /// </summary>
/// <param name="initialValue">The initial value.</param> public T Value {
protected AtomicTypeBase(long initialValue) get => this.FromLong(this.BackingValue);
{ set => this.BackingValue = this.ToLong(value);
BackingValue = initialValue;
}
/// <summary>
/// Gets or sets the value.
/// </summary>
public T Value
{
get => FromLong(BackingValue);
set => BackingValue = ToLong(value);
}
/// <summary>
/// Gets or sets the backing value.
/// </summary>
protected long BackingValue
{
get => Interlocked.Read(ref _backingValue);
set => Interlocked.Exchange(ref _backingValue, value);
}
/// <summary>
/// Implements the operator ==.
/// </summary>
/// <param name="a">a.</param>
/// <param name="b">The b.</param>
/// <returns>
/// The result of the operator.
/// </returns>
public static bool operator ==(AtomicTypeBase<T> a, T b) => a?.Equals(b) == true;
/// <summary>
/// Implements the operator !=.
/// </summary>
/// <param name="a">a.</param>
/// <param name="b">The b.</param>
/// <returns>
/// The result of the operator.
/// </returns>
public static bool operator !=(AtomicTypeBase<T> a, T b) => a?.Equals(b) == false;
/// <summary>
/// Implements the operator &gt;.
/// </summary>
/// <param name="a">a.</param>
/// <param name="b">The b.</param>
/// <returns>
/// The result of the operator.
/// </returns>
public static bool operator >(AtomicTypeBase<T> a, T b) => a.CompareTo(b) > 0;
/// <summary>
/// Implements the operator &lt;.
/// </summary>
/// <param name="a">a.</param>
/// <param name="b">The b.</param>
/// <returns>
/// The result of the operator.
/// </returns>
public static bool operator <(AtomicTypeBase<T> a, T b) => a.CompareTo(b) < 0;
/// <summary>
/// Implements the operator &gt;=.
/// </summary>
/// <param name="a">a.</param>
/// <param name="b">The b.</param>
/// <returns>
/// The result of the operator.
/// </returns>
public static bool operator >=(AtomicTypeBase<T> a, T b) => a.CompareTo(b) >= 0;
/// <summary>
/// Implements the operator &lt;=.
/// </summary>
/// <param name="a">a.</param>
/// <param name="b">The b.</param>
/// <returns>
/// The result of the operator.
/// </returns>
public static bool operator <=(AtomicTypeBase<T> a, T b) => a.CompareTo(b) <= 0;
/// <summary>
/// Implements the operator ++.
/// </summary>
/// <param name="instance">The instance.</param>
/// <returns>
/// The result of the operator.
/// </returns>
public static AtomicTypeBase<T> operator ++(AtomicTypeBase<T> instance)
{
Interlocked.Increment(ref instance._backingValue);
return instance;
}
/// <summary>
/// Implements the operator --.
/// </summary>
/// <param name="instance">The instance.</param>
/// <returns>
/// The result of the operator.
/// </returns>
public static AtomicTypeBase<T> operator --(AtomicTypeBase<T> instance)
{
Interlocked.Decrement(ref instance._backingValue);
return instance;
}
/// <summary>
/// Implements the operator -&lt;.
/// </summary>
/// <param name="instance">The instance.</param>
/// <param name="operand">The operand.</param>
/// <returns>
/// The result of the operator.
/// </returns>
public static AtomicTypeBase<T> operator +(AtomicTypeBase<T> instance, long operand)
{
instance.BackingValue = instance.BackingValue + operand;
return instance;
}
/// <summary>
/// Implements the operator -.
/// </summary>
/// <param name="instance">The instance.</param>
/// <param name="operand">The operand.</param>
/// <returns>
/// The result of the operator.
/// </returns>
public static AtomicTypeBase<T> operator -(AtomicTypeBase<T> instance, long operand)
{
instance.BackingValue = instance.BackingValue - operand;
return instance;
}
/// <summary>
/// Compares the value to the other instance.
/// </summary>
/// <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>
/// <exception cref="ArgumentException">When types are incompatible.</exception>
public int CompareTo(object other)
{
switch (other)
{
case null:
return 1;
case AtomicTypeBase<T> atomic:
return BackingValue.CompareTo(atomic.BackingValue);
case T variable:
return Value.CompareTo(variable);
}
throw new ArgumentException("Incompatible comparison types");
}
/// <summary>
/// Compares the value to the other instance.
/// </summary>
/// <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>
public int CompareTo(T other) => Value.CompareTo(other);
/// <summary>
/// Compares the value to the other instance.
/// </summary>
/// <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>
public int CompareTo(AtomicTypeBase<T> other) => BackingValue.CompareTo(other?.BackingValue ?? default);
/// <summary>
/// Determines whether the specified <see cref="object" />, is equal to this instance.
/// </summary>
/// <param name="other">The <see cref="object" /> to compare with this instance.</param>
/// <returns>
/// <c>true</c> if the specified <see cref="object" /> is equal to this instance; otherwise, <c>false</c>.
/// </returns>
public override bool Equals(object other)
{
switch (other)
{
case AtomicTypeBase<T> atomic:
return Equals(atomic);
case T variable:
return Equals(variable);
}
return false;
}
/// <summary>
/// Returns a hash code for this instance.
/// </summary>
/// <returns>
/// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table.
/// </returns>
public override int GetHashCode() => BackingValue.GetHashCode();
/// <inheritdoc />
public bool Equals(AtomicTypeBase<T> other) =>
BackingValue == (other?.BackingValue ?? default);
/// <inheritdoc />
public bool Equals(T other) => Equals(Value, other);
/// <summary>
/// Converts from a long value to the target type.
/// </summary>
/// <param name="backingValue">The backing value.</param>
/// <returns>The value converted form a long value.</returns>
protected abstract T FromLong(long backingValue);
/// <summary>
/// Converts from the target type to a long value.
/// </summary>
/// <param name="value">The value.</param>
/// <returns>The value converted to a long value.</returns>
protected abstract long ToLong(T value);
} }
/// <summary>
/// Gets or sets the backing value.
/// </summary>
protected Int64 BackingValue {
get => Interlocked.Read(ref this._backingValue);
set => Interlocked.Exchange(ref this._backingValue, value);
}
/// <summary>
/// Implements the operator ==.
/// </summary>
/// <param name="a">a.</param>
/// <param name="b">The b.</param>
/// <returns>
/// The result of the operator.
/// </returns>
public static Boolean operator ==(AtomicTypeBase<T> a, T b) => a?.Equals(b) == true;
/// <summary>
/// Implements the operator !=.
/// </summary>
/// <param name="a">a.</param>
/// <param name="b">The b.</param>
/// <returns>
/// The result of the operator.
/// </returns>
public static Boolean operator !=(AtomicTypeBase<T> a, T b) => a?.Equals(b) == false;
/// <summary>
/// Implements the operator &gt;.
/// </summary>
/// <param name="a">a.</param>
/// <param name="b">The b.</param>
/// <returns>
/// The result of the operator.
/// </returns>
public static Boolean operator >(AtomicTypeBase<T> a, T b) => a.CompareTo(b) > 0;
/// <summary>
/// Implements the operator &lt;.
/// </summary>
/// <param name="a">a.</param>
/// <param name="b">The b.</param>
/// <returns>
/// The result of the operator.
/// </returns>
public static Boolean operator <(AtomicTypeBase<T> a, T b) => a.CompareTo(b) < 0;
/// <summary>
/// Implements the operator &gt;=.
/// </summary>
/// <param name="a">a.</param>
/// <param name="b">The b.</param>
/// <returns>
/// The result of the operator.
/// </returns>
public static Boolean operator >=(AtomicTypeBase<T> a, T b) => a.CompareTo(b) >= 0;
/// <summary>
/// Implements the operator &lt;=.
/// </summary>
/// <param name="a">a.</param>
/// <param name="b">The b.</param>
/// <returns>
/// The result of the operator.
/// </returns>
public static Boolean operator <=(AtomicTypeBase<T> a, T b) => a.CompareTo(b) <= 0;
/// <summary>
/// Implements the operator ++.
/// </summary>
/// <param name="instance">The instance.</param>
/// <returns>
/// The result of the operator.
/// </returns>
public static AtomicTypeBase<T> operator ++(AtomicTypeBase<T> instance) {
_ = Interlocked.Increment(ref instance._backingValue);
return instance;
}
/// <summary>
/// Implements the operator --.
/// </summary>
/// <param name="instance">The instance.</param>
/// <returns>
/// The result of the operator.
/// </returns>
public static AtomicTypeBase<T> operator --(AtomicTypeBase<T> instance) {
_ = Interlocked.Decrement(ref instance._backingValue);
return instance;
}
/// <summary>
/// Implements the operator -&lt;.
/// </summary>
/// <param name="instance">The instance.</param>
/// <param name="operand">The operand.</param>
/// <returns>
/// The result of the operator.
/// </returns>
public static AtomicTypeBase<T> operator +(AtomicTypeBase<T> instance, Int64 operand) {
instance.BackingValue += operand;
return instance;
}
/// <summary>
/// Implements the operator -.
/// </summary>
/// <param name="instance">The instance.</param>
/// <param name="operand">The operand.</param>
/// <returns>
/// The result of the operator.
/// </returns>
public static AtomicTypeBase<T> operator -(AtomicTypeBase<T> instance, Int64 operand) {
instance.BackingValue -= operand;
return instance;
}
/// <summary>
/// Compares the value to the other instance.
/// </summary>
/// <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>
/// <exception cref="ArgumentException">When types are incompatible.</exception>
public Int32 CompareTo(Object other) {
switch(other) {
case null:
return 1;
case AtomicTypeBase<T> atomic:
return this.BackingValue.CompareTo(atomic.BackingValue);
case T variable:
return this.Value.CompareTo(variable);
}
throw new ArgumentException("Incompatible comparison types");
}
/// <summary>
/// Compares the value to the other instance.
/// </summary>
/// <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>
public Int32 CompareTo(T other) => this.Value.CompareTo(other);
/// <summary>
/// Compares the value to the other instance.
/// </summary>
/// <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>
public Int32 CompareTo(AtomicTypeBase<T> other) => this.BackingValue.CompareTo(other?.BackingValue ?? default);
/// <summary>
/// Determines whether the specified <see cref="Object" />, is equal to this instance.
/// </summary>
/// <param name="other">The <see cref="Object" /> to compare with this instance.</param>
/// <returns>
/// <c>true</c> if the specified <see cref="Object" /> is equal to this instance; otherwise, <c>false</c>.
/// </returns>
public override Boolean Equals(Object other) {
switch(other) {
case AtomicTypeBase<T> atomic:
return this.Equals(atomic);
case T variable:
return this.Equals(variable);
}
return false;
}
/// <summary>
/// Returns a hash code for this instance.
/// </summary>
/// <returns>
/// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table.
/// </returns>
public override Int32 GetHashCode() => this.BackingValue.GetHashCode();
/// <inheritdoc />
public Boolean Equals(AtomicTypeBase<T> other) =>
this.BackingValue == (other?.BackingValue ?? default);
/// <inheritdoc />
public Boolean Equals(T other) => Equals(this.Value, other);
/// <summary>
/// Converts from a long value to the target type.
/// </summary>
/// <param name="backingValue">The backing value.</param>
/// <returns>The value converted form a long value.</returns>
protected abstract T FromLong(Int64 backingValue);
/// <summary>
/// Converts from the target type to a long value.
/// </summary>
/// <param name="value">The value.</param>
/// <returns>The value converted to a long value.</returns>
protected abstract Int64 ToLong(T value);
}
} }

View File

@ -1,197 +1,181 @@
namespace Unosquare.Swan.Abstractions using System;
{ using System.Threading;
using System;
using System.Threading; namespace Unosquare.Swan.Abstractions {
/// <summary>
/// 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
/// on the same <see cref="ThreadPool"/> thread every time the timer fires.
/// </summary>
public sealed class ExclusiveTimer : IDisposable {
private readonly Object _syncLock = new Object();
private readonly ManualResetEventSlim _cycleDoneEvent = new ManualResetEventSlim(true);
private readonly Timer _backingTimer;
private readonly TimerCallback _userCallback;
private readonly AtomicBoolean _isDisposing = new AtomicBoolean();
private readonly AtomicBoolean _isDisposed = new AtomicBoolean();
private Int32 _period;
/// <summary> /// <summary>
/// A threading <see cref="_backingTimer"/> implementation that executes at most one cycle at a time /// Initializes a new instance of the <see cref="ExclusiveTimer"/> class.
/// 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.
/// </summary> /// </summary>
public sealed class ExclusiveTimer : IDisposable /// <param name="timerCallback">The timer callback.</param>
{ /// <param name="state">The state.</param>
private readonly object _syncLock = new object(); /// <param name="dueTime">The due time.</param>
private readonly ManualResetEventSlim _cycleDoneEvent = new ManualResetEventSlim(true); /// <param name="period">The period.</param>
private readonly Timer _backingTimer; public ExclusiveTimer(TimerCallback timerCallback, Object state, Int32 dueTime, Int32 period) {
private readonly TimerCallback _userCallback; this._period = period;
private readonly AtomicBoolean _isDisposing = new AtomicBoolean(); this._userCallback = timerCallback;
private readonly AtomicBoolean _isDisposed = new AtomicBoolean(); this._backingTimer = new Timer(this.InternalCallback, state ?? this, dueTime, Timeout.Infinite);
private int _period;
/// <summary>
/// Initializes a new instance of the <see cref="ExclusiveTimer"/> class.
/// </summary>
/// <param name="timerCallback">The timer callback.</param>
/// <param name="state">The state.</param>
/// <param name="dueTime">The due time.</param>
/// <param name="period">The period.</param>
public ExclusiveTimer(TimerCallback timerCallback, object state, int dueTime, int period)
{
_period = period;
_userCallback = timerCallback;
_backingTimer = new Timer(InternalCallback, state ?? this, dueTime, Timeout.Infinite);
}
/// <summary>
/// Initializes a new instance of the <see cref="ExclusiveTimer"/> class.
/// </summary>
/// <param name="timerCallback">The timer callback.</param>
/// <param name="state">The state.</param>
/// <param name="dueTime">The due time.</param>
/// <param name="period">The period.</param>
public ExclusiveTimer(TimerCallback timerCallback, object state, TimeSpan dueTime, TimeSpan period)
: this(timerCallback, state, Convert.ToInt32(dueTime.TotalMilliseconds), Convert.ToInt32(period.TotalMilliseconds))
{
// placeholder
}
/// <summary>
/// Initializes a new instance of the <see cref="ExclusiveTimer"/> class.
/// </summary>
/// <param name="timerCallback">The timer callback.</param>
public ExclusiveTimer(TimerCallback timerCallback)
: this(timerCallback, null, Timeout.Infinite, Timeout.Infinite)
{
// placholder
}
/// <summary>
/// Initializes a new instance of the <see cref="ExclusiveTimer"/> class.
/// </summary>
/// <param name="timerCallback">The timer callback.</param>
/// <param name="dueTime">The due time.</param>
/// <param name="period">The period.</param>
public ExclusiveTimer(Action timerCallback, int dueTime, int period)
: this(s => { timerCallback?.Invoke(); }, null, dueTime, period)
{
// placeholder
}
/// <summary>
/// Initializes a new instance of the <see cref="ExclusiveTimer"/> class.
/// </summary>
/// <param name="timerCallback">The timer callback.</param>
/// <param name="dueTime">The due time.</param>
/// <param name="period">The period.</param>
public ExclusiveTimer(Action timerCallback, TimeSpan dueTime, TimeSpan period)
: this(s => { timerCallback?.Invoke(); }, null, dueTime, period)
{
// placeholder
}
/// <summary>
/// Initializes a new instance of the <see cref="ExclusiveTimer"/> class.
/// </summary>
/// <param name="timerCallback">The timer callback.</param>
public ExclusiveTimer(Action timerCallback)
: this(timerCallback, Timeout.Infinite, Timeout.Infinite)
{
// placeholder
}
/// <summary>
/// Gets a value indicating whether this instance is disposing.
/// </summary>
/// <value>
/// <c>true</c> if this instance is disposing; otherwise, <c>false</c>.
/// </value>
public bool IsDisposing => _isDisposing.Value;
/// <summary>
/// Gets a value indicating whether this instance is disposed.
/// </summary>
/// <value>
/// <c>true</c> if this instance is disposed; otherwise, <c>false</c>.
/// </value>
public bool IsDisposed => _isDisposed.Value;
/// <summary>
/// Changes the start time and the interval between method invocations for the internal timer.
/// </summary>
/// <param name="dueTime">The due time.</param>
/// <param name="period">The period.</param>
public void Change(int dueTime, int period)
{
_period = period;
_backingTimer.Change(dueTime, Timeout.Infinite);
}
/// <summary>
/// Changes the start time and the interval between method invocations for the internal timer.
/// </summary>
/// <param name="dueTime">The due time.</param>
/// <param name="period">The period.</param>
public void Change(TimeSpan dueTime, TimeSpan period)
=> Change(Convert.ToInt32(dueTime.TotalMilliseconds), Convert.ToInt32(period.TotalMilliseconds));
/// <summary>
/// Changes the interval between method invocations for the internal timer.
/// </summary>
/// <param name="period">The period.</param>
public void Resume(int period) => Change(0, period);
/// <summary>
/// Changes the interval between method invocations for the internal timer.
/// </summary>
/// <param name="period">The period.</param>
public void Resume(TimeSpan period) => Change(TimeSpan.Zero, period);
/// <summary>
/// Pauses this instance.
/// </summary>
public void Pause() => Change(Timeout.Infinite, Timeout.Infinite);
/// <inheritdoc />
public void Dispose()
{
lock (_syncLock)
{
if (_isDisposed == true || _isDisposing == true)
return;
_isDisposing.Value = true;
}
try
{
_backingTimer.Dispose();
_cycleDoneEvent.Wait();
_cycleDoneEvent.Dispose();
}
finally
{
_isDisposed.Value = true;
_isDisposing.Value = false;
}
}
/// <summary>
/// Logic that runs every time the timer hits the due time.
/// </summary>
/// <param name="state">The state.</param>
private void InternalCallback(object state)
{
lock (_syncLock)
{
if (IsDisposed || IsDisposing)
return;
}
if (_cycleDoneEvent.IsSet == false)
return;
_cycleDoneEvent.Reset();
try
{
_userCallback(state);
}
finally
{
_cycleDoneEvent?.Set();
_backingTimer?.Change(_period, Timeout.Infinite);
}
}
} }
/// <summary>
/// Initializes a new instance of the <see cref="ExclusiveTimer"/> class.
/// </summary>
/// <param name="timerCallback">The timer callback.</param>
/// <param name="state">The state.</param>
/// <param name="dueTime">The due time.</param>
/// <param name="period">The period.</param>
public ExclusiveTimer(TimerCallback timerCallback, Object state, TimeSpan dueTime, TimeSpan period)
: this(timerCallback, state, Convert.ToInt32(dueTime.TotalMilliseconds), Convert.ToInt32(period.TotalMilliseconds)) {
// placeholder
}
/// <summary>
/// Initializes a new instance of the <see cref="ExclusiveTimer"/> class.
/// </summary>
/// <param name="timerCallback">The timer callback.</param>
public ExclusiveTimer(TimerCallback timerCallback)
: this(timerCallback, null, Timeout.Infinite, Timeout.Infinite) {
// placholder
}
/// <summary>
/// Initializes a new instance of the <see cref="ExclusiveTimer"/> class.
/// </summary>
/// <param name="timerCallback">The timer callback.</param>
/// <param name="dueTime">The due time.</param>
/// <param name="period">The period.</param>
public ExclusiveTimer(Action timerCallback, Int32 dueTime, Int32 period)
: this(s => timerCallback?.Invoke(), null, dueTime, period) {
// placeholder
}
/// <summary>
/// Initializes a new instance of the <see cref="ExclusiveTimer"/> class.
/// </summary>
/// <param name="timerCallback">The timer callback.</param>
/// <param name="dueTime">The due time.</param>
/// <param name="period">The period.</param>
public ExclusiveTimer(Action timerCallback, TimeSpan dueTime, TimeSpan period)
: this(s => timerCallback?.Invoke(), null, dueTime, period) {
// placeholder
}
/// <summary>
/// Initializes a new instance of the <see cref="ExclusiveTimer"/> class.
/// </summary>
/// <param name="timerCallback">The timer callback.</param>
public ExclusiveTimer(Action timerCallback)
: this(timerCallback, Timeout.Infinite, Timeout.Infinite) {
// placeholder
}
/// <summary>
/// Gets a value indicating whether this instance is disposing.
/// </summary>
/// <value>
/// <c>true</c> if this instance is disposing; otherwise, <c>false</c>.
/// </value>
public Boolean IsDisposing => this._isDisposing.Value;
/// <summary>
/// Gets a value indicating whether this instance is disposed.
/// </summary>
/// <value>
/// <c>true</c> if this instance is disposed; otherwise, <c>false</c>.
/// </value>
public Boolean IsDisposed => this._isDisposed.Value;
/// <summary>
/// Changes the start time and the interval between method invocations for the internal timer.
/// </summary>
/// <param name="dueTime">The due time.</param>
/// <param name="period">The period.</param>
public void Change(Int32 dueTime, Int32 period) {
this._period = period;
_ = this._backingTimer.Change(dueTime, Timeout.Infinite);
}
/// <summary>
/// Changes the start time and the interval between method invocations for the internal timer.
/// </summary>
/// <param name="dueTime">The due time.</param>
/// <param name="period">The period.</param>
public void Change(TimeSpan dueTime, TimeSpan period)
=> this.Change(Convert.ToInt32(dueTime.TotalMilliseconds), Convert.ToInt32(period.TotalMilliseconds));
/// <summary>
/// Changes the interval between method invocations for the internal timer.
/// </summary>
/// <param name="period">The period.</param>
public void Resume(Int32 period) => this.Change(0, period);
/// <summary>
/// Changes the interval between method invocations for the internal timer.
/// </summary>
/// <param name="period">The period.</param>
public void Resume(TimeSpan period) => this.Change(TimeSpan.Zero, period);
/// <summary>
/// Pauses this instance.
/// </summary>
public void Pause() => this.Change(Timeout.Infinite, Timeout.Infinite);
/// <inheritdoc />
public void Dispose() {
lock(this._syncLock) {
if(this._isDisposed == true || this._isDisposing == true) {
return;
}
this._isDisposing.Value = true;
}
try {
this._backingTimer.Dispose();
this._cycleDoneEvent.Wait();
this._cycleDoneEvent.Dispose();
} finally {
this._isDisposed.Value = true;
this._isDisposing.Value = false;
}
}
/// <summary>
/// Logic that runs every time the timer hits the due time.
/// </summary>
/// <param name="state">The state.</param>
private void InternalCallback(Object state) {
lock(this._syncLock) {
if(this.IsDisposed || this.IsDisposing) {
return;
}
}
if(this._cycleDoneEvent.IsSet == false) {
return;
}
this._cycleDoneEvent.Reset();
try {
this._userCallback(state);
} finally {
this._cycleDoneEvent?.Set();
_ = this._backingTimer?.Change(this._period, Timeout.Infinite);
}
}
}
} }

View File

@ -1,94 +1,88 @@
namespace Unosquare.Swan.Abstractions using System;
{ using System.Linq;
using System; using System.Collections.Generic;
using System.Linq; using System.Linq.Expressions;
using System.Collections.Generic;
using System.Linq.Expressions; namespace Unosquare.Swan.Abstractions {
/// <summary>
/// Represents a generic expression parser.
/// </summary>
public abstract class ExpressionParser {
/// <summary>
/// Resolves the expression.
/// </summary>
/// <typeparam name="T">The type of expression result.</typeparam>
/// <param name="tokens">The tokens.</param>
/// <returns>The representation of the expression parsed.</returns>
public virtual T ResolveExpression<T>(IEnumerable<Token> tokens) {
UnaryExpression conversion = Expression.Convert(this.Parse(tokens), typeof(T));
return Expression.Lambda<Func<T>>(conversion).Compile()();
}
/// <summary> /// <summary>
/// Represents a generic expression parser. /// Parses the specified tokens.
/// </summary> /// </summary>
public abstract class ExpressionParser /// <param name="tokens">The tokens.</param>
{ /// <returns>The final expression.</returns>
/// <summary> public virtual Expression Parse(IEnumerable<Token> tokens) {
/// Resolves the expression. List<Stack<Expression>> expressionStack = new List<Stack<Expression>>();
/// </summary>
/// <typeparam name="T">The type of expression result.</typeparam> foreach(Token token in tokens) {
/// <param name="tokens">The tokens.</param> if(expressionStack.Any() == false) {
/// <returns>The representation of the expression parsed.</returns> expressionStack.Add(new Stack<Expression>());
public virtual T ResolveExpression<T>(IEnumerable<Token> tokens)
{
var conversion = Expression.Convert(Parse(tokens), typeof(T));
return Expression.Lambda<Func<T>>(conversion).Compile()();
} }
/// <summary> switch(token.Type) {
/// Parses the specified tokens. case TokenType.Wall:
/// </summary> expressionStack.Add(new Stack<Expression>());
/// <param name="tokens">The tokens.</param> break;
/// <returns>The final expression.</returns> case TokenType.Number:
public virtual Expression Parse(IEnumerable<Token> tokens) expressionStack.Last().Push(Expression.Constant(Convert.ToDecimal(token.Value)));
{ break;
var expressionStack = new List<Stack<Expression>>(); case TokenType.Variable:
this.ResolveVariable(token.Value, expressionStack.Last());
break;
case TokenType.String:
expressionStack.Last().Push(Expression.Constant(token.Value));
break;
case TokenType.Operator:
this.ResolveOperator(token.Value, expressionStack.Last());
break;
case TokenType.Function:
this.ResolveFunction(token.Value, expressionStack.Last());
foreach (var token in tokens) if(expressionStack.Count > 1 && expressionStack.Last().Count == 1) {
{ Expression lastValue = expressionStack.Last().Pop();
if (expressionStack.Any() == false) _ = expressionStack.Remove(expressionStack.Last());
expressionStack.Add(new Stack<Expression>()); expressionStack.Last().Push(lastValue);
switch (token.Type)
{
case TokenType.Wall:
expressionStack.Add(new Stack<Expression>());
break;
case TokenType.Number:
expressionStack.Last().Push(Expression.Constant(Convert.ToDecimal(token.Value)));
break;
case TokenType.Variable:
ResolveVariable(token.Value, expressionStack.Last());
break;
case TokenType.String:
expressionStack.Last().Push(Expression.Constant(token.Value));
break;
case TokenType.Operator:
ResolveOperator(token.Value, expressionStack.Last());
break;
case TokenType.Function:
ResolveFunction(token.Value, expressionStack.Last());
if (expressionStack.Count > 1 && expressionStack.Last().Count == 1)
{
var lastValue = expressionStack.Last().Pop();
expressionStack.Remove(expressionStack.Last());
expressionStack.Last().Push(lastValue);
}
break;
}
} }
return expressionStack.Last().Pop(); break;
} }
}
/// <summary> return expressionStack.Last().Pop();
/// Resolves the variable.
/// </summary>
/// <param name="value">The value.</param>
/// <param name="expressionStack">The expression stack.</param>
public abstract void ResolveVariable(string value, Stack<Expression> expressionStack);
/// <summary>
/// Resolves the operator.
/// </summary>
/// <param name="value">The value.</param>
/// <param name="expressionStack">The expression stack.</param>
public abstract void ResolveOperator(string value, Stack<Expression> expressionStack);
/// <summary>
/// Resolves the function.
/// </summary>
/// <param name="value">The value.</param>
/// <param name="expressionStack">The expression stack.</param>
public abstract void ResolveFunction(string value, Stack<Expression> expressionStack);
} }
/// <summary>
/// Resolves the variable.
/// </summary>
/// <param name="value">The value.</param>
/// <param name="expressionStack">The expression stack.</param>
public abstract void ResolveVariable(String value, Stack<Expression> expressionStack);
/// <summary>
/// Resolves the operator.
/// </summary>
/// <param name="value">The value.</param>
/// <param name="expressionStack">The expression stack.</param>
public abstract void ResolveOperator(String value, Stack<Expression> expressionStack);
/// <summary>
/// Resolves the function.
/// </summary>
/// <param name="value">The value.</param>
/// <param name="expressionStack">The expression stack.</param>
public abstract void ResolveFunction(String value, Stack<Expression> expressionStack);
}
} }

View File

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

View File

@ -1,24 +1,22 @@
namespace Unosquare.Swan.Abstractions using System;
{
using System; namespace Unosquare.Swan.Abstractions {
/// <summary>
/// Defines a generic interface for synchronized locking mechanisms.
/// </summary>
public interface ISyncLocker : IDisposable {
/// <summary>
/// Acquires a writer lock.
/// The lock is released when the returned locking object is disposed.
/// </summary>
/// <returns>A disposable locking object.</returns>
IDisposable AcquireWriterLock();
/// <summary> /// <summary>
/// Defines a generic interface for synchronized locking mechanisms. /// Acquires a reader lock.
/// The lock is released when the returned locking object is disposed.
/// </summary> /// </summary>
public interface ISyncLocker : IDisposable /// <returns>A disposable locking object.</returns>
{ IDisposable AcquireReaderLock();
/// <summary> }
/// Acquires a writer lock.
/// The lock is released when the returned locking object is disposed.
/// </summary>
/// <returns>A disposable locking object.</returns>
IDisposable AcquireWriterLock();
/// <summary>
/// Acquires a reader lock.
/// The lock is released when the returned locking object is disposed.
/// </summary>
/// <returns>A disposable locking object.</returns>
IDisposable AcquireReaderLock();
}
} }

View File

@ -1,21 +1,23 @@
namespace Unosquare.Swan.Abstractions using System;
{
/// <summary>
/// A simple Validator interface.
/// </summary>
public interface IValidator
{
/// <summary>
/// The error message.
/// </summary>
string ErrorMessage { get; }
/// <summary> namespace Unosquare.Swan.Abstractions {
/// Checks if a value is valid. /// <summary>
/// </summary> /// A simple Validator interface.
/// <typeparam name="T">The type.</typeparam> /// </summary>
/// <param name="value"> The value.</param> public interface IValidator {
/// <returns>True if it is valid.False if it is not.</returns> /// <summary>
bool IsValid<T>(T value); /// The error message.
/// </summary>
String ErrorMessage {
get;
} }
/// <summary>
/// Checks if a value is valid.
/// </summary>
/// <typeparam name="T">The type.</typeparam>
/// <param name="value"> The value.</param>
/// <returns>True if it is valid.False if it is not.</returns>
Boolean IsValid<T>(T value);
}
} }

View File

@ -1,57 +1,63 @@
namespace Unosquare.Swan.Abstractions using System;
{
using System; namespace Unosquare.Swan.Abstractions {
/// <summary>
/// Provides a generalized API for ManualResetEvent and ManualResetEventSlim.
/// </summary>
/// <seealso cref="IDisposable" />
public interface IWaitEvent : IDisposable {
/// <summary>
/// Gets a value indicating whether the event is in the completed state.
/// </summary>
Boolean IsCompleted {
get;
}
/// <summary> /// <summary>
/// Provides a generalized API for ManualResetEvent and ManualResetEventSlim. /// Gets a value indicating whether the Begin method has been called.
/// It returns false after the Complete method is called.
/// </summary> /// </summary>
/// <seealso cref="IDisposable" /> Boolean IsInProgress {
public interface IWaitEvent : IDisposable get;
{
/// <summary>
/// Gets a value indicating whether the event is in the completed state.
/// </summary>
bool IsCompleted { get; }
/// <summary>
/// Gets a value indicating whether the Begin method has been called.
/// It returns false after the Complete method is called.
/// </summary>
bool IsInProgress { get; }
/// <summary>
/// Returns true if the underlying handle is not closed and it is still valid.
/// </summary>
bool IsValid { get; }
/// <summary>
/// Gets a value indicating whether this instance is disposed.
/// </summary>
bool IsDisposed { get; }
/// <summary>
/// Enters the state in which waiters need to wait.
/// All future waiters will block when they call the Wait method.
/// </summary>
void Begin();
/// <summary>
/// Leaves the state in which waiters need to wait.
/// All current waiters will continue.
/// </summary>
void Complete();
/// <summary>
/// Waits for the event to be completed.
/// </summary>
void Wait();
/// <summary>
/// Waits for the event to be completed.
/// Returns <c>true</c> when there was no timeout. False if the timeout was reached.
/// </summary>
/// <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>
bool Wait(TimeSpan timeout);
} }
/// <summary>
/// Returns true if the underlying handle is not closed and it is still valid.
/// </summary>
Boolean IsValid {
get;
}
/// <summary>
/// Gets a value indicating whether this instance is disposed.
/// </summary>
Boolean IsDisposed {
get;
}
/// <summary>
/// Enters the state in which waiters need to wait.
/// All future waiters will block when they call the Wait method.
/// </summary>
void Begin();
/// <summary>
/// Leaves the state in which waiters need to wait.
/// All current waiters will continue.
/// </summary>
void Complete();
/// <summary>
/// Waits for the event to be completed.
/// </summary>
void Wait();
/// <summary>
/// Waits for the event to be completed.
/// Returns <c>true</c> when there was no timeout. False if the timeout was reached.
/// </summary>
/// <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>
Boolean Wait(TimeSpan timeout);
}
} }

View File

@ -1,18 +1,16 @@
namespace Unosquare.Swan.Abstractions namespace Unosquare.Swan.Abstractions {
{ /// <summary>
/// A simple interface for application workers.
/// </summary>
public interface IWorker {
/// <summary> /// <summary>
/// A simple interface for application workers. /// Should start the task immediately and asynchronously.
/// </summary> /// </summary>
public interface IWorker void Start();
{
/// <summary>
/// Should start the task immediately and asynchronously.
/// </summary>
void Start();
/// <summary> /// <summary>
/// Should stop the task immediately and synchronously. /// Should stop the task immediately and synchronously.
/// </summary> /// </summary>
void Stop(); void Stop();
} }
} }

View File

@ -1,169 +1,155 @@
#if !NETSTANDARD1_3 #if !NETSTANDARD1_3
namespace Unosquare.Swan.Abstractions using System;
{ using System.Collections.Generic;
using System; using System.Threading;
using System.Collections.Generic; namespace Unosquare.Swan.Abstractions {
using System.Threading; /// <summary>
using Swan; /// Represents an background worker abstraction with a life cycle and running at a independent thread.
/// </summary>
public abstract class RunnerBase {
private Thread _worker;
private CancellationTokenSource _cancelTokenSource;
private ManualResetEvent _workFinished;
/// <summary> /// <summary>
/// Represents an background worker abstraction with a life cycle and running at a independent thread. /// Initializes a new instance of the <see cref="RunnerBase"/> class.
/// </summary> /// </summary>
public abstract class RunnerBase /// <param name="isEnabled">if set to <c>true</c> [is enabled].</param>
{ protected RunnerBase(Boolean isEnabled) {
private Thread _worker; this.Name = this.GetType().Name;
private CancellationTokenSource _cancelTokenSource; this.IsEnabled = isEnabled;
private ManualResetEvent _workFinished;
/// <summary>
/// Initializes a new instance of the <see cref="RunnerBase"/> class.
/// </summary>
/// <param name="isEnabled">if set to <c>true</c> [is enabled].</param>
protected RunnerBase(bool isEnabled)
{
Name = GetType().Name;
IsEnabled = isEnabled;
}
/// <summary>
/// Gets the error messages.
/// </summary>
/// <value>
/// The error messages.
/// </value>
public List<string> ErrorMessages { get; } = new List<string>();
/// <summary>
/// Gets the name.
/// </summary>
/// <value>
/// The name.
/// </value>
public string Name { get; }
/// <summary>
/// Gets a value indicating whether this instance is running.
/// </summary>
/// <value>
/// <c>true</c> if this instance is running; otherwise, <c>false</c>.
/// </value>
public bool IsRunning { get; private set; }
/// <summary>
/// Gets a value indicating whether this instance is enabled.
/// </summary>
/// <value>
/// <c>true</c> if this instance is enabled; otherwise, <c>false</c>.
/// </value>
public bool IsEnabled { get; }
/// <summary>
/// Starts this instance.
/// </summary>
public virtual void Start()
{
if (IsEnabled == false)
return;
$"Start Requested".Debug(Name);
_cancelTokenSource = new CancellationTokenSource();
_workFinished = new ManualResetEvent(false);
_worker = new Thread(() =>
{
_workFinished.Reset();
IsRunning = true;
try
{
Setup();
DoBackgroundWork(_cancelTokenSource.Token);
}
catch (ThreadAbortException)
{
$"{nameof(ThreadAbortException)} caught.".Warn(Name);
}
catch (Exception ex)
{
$"{ex.GetType()}: {ex.Message}\r\n{ex.StackTrace}".Error(Name);
}
finally
{
Cleanup();
_workFinished?.Set();
IsRunning = false;
"Stopped Completely".Debug(Name);
}
})
{
IsBackground = true,
Name = $"{Name}Thread",
};
_worker.Start();
}
/// <summary>
/// Stops this instance.
/// </summary>
public virtual void Stop()
{
if (IsEnabled == false || IsRunning == false)
return;
$"Stop Requested".Debug(Name);
_cancelTokenSource.Cancel();
var waitRetries = 5;
while (waitRetries >= 1)
{
if (_workFinished.WaitOne(250))
{
waitRetries = -1;
break;
}
waitRetries--;
}
if (waitRetries < 0)
{
"Workbench stopped gracefully".Debug(Name);
}
else
{
"Did not respond to stop request. Aborting thread and waiting . . .".Warn(Name);
_worker.Abort();
if (_workFinished.WaitOne(5000) == false)
"Waited and no response. Worker might have been left in an inconsistent state.".Error(Name);
else
"Waited for worker and it finally responded (OK).".Debug(Name);
}
_workFinished.Dispose();
_workFinished = null;
}
/// <summary>
/// Setups this instance.
/// </summary>
protected virtual void Setup()
{
// empty
}
/// <summary>
/// Cleanups this instance.
/// </summary>
protected virtual void Cleanup()
{
// empty
}
/// <summary>
/// Does the background work.
/// </summary>
/// <param name="ct">The ct.</param>
protected abstract void DoBackgroundWork(CancellationToken ct);
} }
/// <summary>
/// Gets the error messages.
/// </summary>
/// <value>
/// The error messages.
/// </value>
public List<String> ErrorMessages { get; } = new List<String>();
/// <summary>
/// Gets the name.
/// </summary>
/// <value>
/// The name.
/// </value>
public String Name {
get;
}
/// <summary>
/// Gets a value indicating whether this instance is running.
/// </summary>
/// <value>
/// <c>true</c> if this instance is running; otherwise, <c>false</c>.
/// </value>
public Boolean IsRunning {
get; private set;
}
/// <summary>
/// Gets a value indicating whether this instance is enabled.
/// </summary>
/// <value>
/// <c>true</c> if this instance is enabled; otherwise, <c>false</c>.
/// </value>
public Boolean IsEnabled {
get;
}
/// <summary>
/// Starts this instance.
/// </summary>
public virtual void Start() {
if(this.IsEnabled == false) {
return;
}
$"Start Requested".Debug(this.Name);
this._cancelTokenSource = new CancellationTokenSource();
this._workFinished = new ManualResetEvent(false);
this._worker = new Thread(() => {
_ = this._workFinished.Reset();
this.IsRunning = true;
try {
this.Setup();
this.DoBackgroundWork(this._cancelTokenSource.Token);
} catch(ThreadAbortException) {
$"{nameof(ThreadAbortException)} caught.".Warn(this.Name);
} catch(Exception ex) {
$"{ex.GetType()}: {ex.Message}\r\n{ex.StackTrace}".Error(this.Name);
} finally {
this.Cleanup();
_ = this._workFinished?.Set();
this.IsRunning = false;
"Stopped Completely".Debug(this.Name);
}
}) {
IsBackground = true,
Name = $"{this.Name}Thread",
};
this._worker.Start();
}
/// <summary>
/// Stops this instance.
/// </summary>
public virtual void Stop() {
if(this.IsEnabled == false || this.IsRunning == false) {
return;
}
$"Stop Requested".Debug(this.Name);
this._cancelTokenSource.Cancel();
Int32 waitRetries = 5;
while(waitRetries >= 1) {
if(this._workFinished.WaitOne(250)) {
waitRetries = -1;
break;
}
waitRetries--;
}
if(waitRetries < 0) {
"Workbench stopped gracefully".Debug(this.Name);
} else {
"Did not respond to stop request. Aborting thread and waiting . . .".Warn(this.Name);
this._worker.Abort();
if(this._workFinished.WaitOne(5000) == false) {
"Waited and no response. Worker might have been left in an inconsistent state.".Error(this.Name);
} else {
"Waited for worker and it finally responded (OK).".Debug(this.Name);
}
}
this._workFinished.Dispose();
this._workFinished = null;
}
/// <summary>
/// Setups this instance.
/// </summary>
protected virtual void Setup() {
// empty
}
/// <summary>
/// Cleanups this instance.
/// </summary>
protected virtual void Cleanup() {
// empty
}
/// <summary>
/// Does the background work.
/// </summary>
/// <param name="ct">The ct.</param>
protected abstract void DoBackgroundWork(CancellationToken ct);
}
} }
#endif #endif

View File

@ -1,188 +1,184 @@
namespace Unosquare.Swan.Abstractions using Unosquare.Swan.Formatters;
{ using Unosquare.Swan.Reflection;
using Formatters; using System;
using Reflection; using System.Collections;
using System; using System.Collections.Generic;
using System.Collections; using System.IO;
using System.Collections.Generic; using System.Linq;
using System.IO; using System.Reflection;
using System.Linq;
using System.Reflection; namespace Unosquare.Swan.Abstractions {
/// <summary>
/// Represents a provider to save and load settings using a plain JSON file.
/// </summary>
/// <example>
/// The following example shows how to save and load settings.
/// <code>
/// using Unosquare.Swan.Abstractions;
///
/// public class Example
/// {
/// public static void Main()
/// {
/// // get user from settings
/// var user = SettingsProvider&lt;Settings&gt;.Instance.Global.User;
///
/// // modify the port
/// SettingsProvider&lt;Settings&gt;.Instance.Global.Port = 20;
///
/// // if we want these settings to persist
/// SettingsProvider&lt;Settings&gt;.Instance.PersistGlobalSettings();
/// }
///
/// public class Settings
/// {
/// public int Port { get; set; } = 9696;
///
/// public string User { get; set; } = "User";
/// }
/// }
/// </code>
/// </example>
/// <typeparam name="T">The type of settings model.</typeparam>
public sealed class SettingsProvider<T>
: SingletonBase<SettingsProvider<T>> {
private readonly Object _syncRoot = new Object();
private T _global;
/// <summary> /// <summary>
/// Represents a provider to save and load settings using a plain JSON file. /// Gets or sets the configuration file path. By default the entry assembly directory is used
/// and the filename is 'appsettings.json'.
/// </summary> /// </summary>
/// <example> /// <value>
/// The following example shows how to save and load settings. /// The configuration file path.
/// <code> /// </value>
/// using Unosquare.Swan.Abstractions; public String ConfigurationFilePath {
/// get; set;
/// public class Example } =
/// {
/// public static void Main()
/// {
/// // get user from settings
/// var user = SettingsProvider&lt;Settings&gt;.Instance.Global.User;
///
/// // modify the port
/// SettingsProvider&lt;Settings&gt;.Instance.Global.Port = 20;
///
/// // if we want these settings to persist
/// SettingsProvider&lt;Settings&gt;.Instance.PersistGlobalSettings();
/// }
///
/// public class Settings
/// {
/// public int Port { get; set; } = 9696;
///
/// public string User { get; set; } = "User";
/// }
/// }
/// </code>
/// </example>
/// <typeparam name="T">The type of settings model.</typeparam>
public sealed class SettingsProvider<T>
: SingletonBase<SettingsProvider<T>>
{
private readonly object _syncRoot = new object();
private T _global;
/// <summary>
/// Gets or sets the configuration file path. By default the entry assembly directory is used
/// and the filename is 'appsettings.json'.
/// </summary>
/// <value>
/// The configuration file path.
/// </value>
public string ConfigurationFilePath { get; set; } =
#if NETSTANDARD1_3 #if NETSTANDARD1_3
Path.Combine(Runtime.LocalStoragePath, "appsettings.json"); Path.Combine(Runtime.LocalStoragePath, "appsettings.json");
#else #else
Path.Combine(Runtime.EntryAssemblyDirectory, "appsettings.json"); Path.Combine(Runtime.EntryAssemblyDirectory, "appsettings.json");
#endif #endif
/// <summary> /// <summary>
/// Gets the global settings object. /// Gets the global settings object.
/// </summary> /// </summary>
/// <value> /// <value>
/// The global settings object. /// The global settings object.
/// </value> /// </value>
public T Global public T Global {
{ get {
get lock(this._syncRoot) {
{ if(Equals(this._global, default(T))) {
lock (_syncRoot) this.ReloadGlobalSettings();
{ }
if (Equals(_global, default(T)))
ReloadGlobalSettings();
return _global; return this._global;
}
}
}
/// <summary>
/// Reloads the global settings.
/// </summary>
public void ReloadGlobalSettings()
{
if (File.Exists(ConfigurationFilePath) == false || File.ReadAllText(ConfigurationFilePath).Length == 0)
{
ResetGlobalSettings();
return;
}
lock (_syncRoot)
_global = Json.Deserialize<T>(File.ReadAllText(ConfigurationFilePath));
}
/// <summary>
/// Persists the global settings.
/// </summary>
public void PersistGlobalSettings() => File.WriteAllText(ConfigurationFilePath, Json.Serialize(Global, true));
/// <summary>
/// Updates settings from list.
/// </summary>
/// <param name="propertyList">The list.</param>
/// <returns>
/// A list of settings of type ref="ExtendedPropertyInfo".
/// </returns>
/// <exception cref="ArgumentNullException">propertyList.</exception>
public List<string> RefreshFromList(List<ExtendedPropertyInfo<T>> propertyList)
{
if (propertyList == null)
throw new ArgumentNullException(nameof(propertyList));
var changedSettings = new List<string>();
var globalProps = Runtime.PropertyTypeCache.RetrieveAllProperties<T>();
foreach (var property in propertyList)
{
var propertyInfo = globalProps.FirstOrDefault(x => x.Name == property.Property);
if (propertyInfo == null) continue;
var originalValue = propertyInfo.GetValue(Global);
var isChanged = propertyInfo.PropertyType.IsArray
? property.Value is IEnumerable enumerable && propertyInfo.TrySetArray(enumerable.Cast<object>(), Global)
: SetValue(property.Value, originalValue, propertyInfo);
if (!isChanged) continue;
changedSettings.Add(property.Property);
PersistGlobalSettings();
}
return changedSettings;
}
/// <summary>
/// Gets the list.
/// </summary>
/// <returns>A List of ExtendedPropertyInfo of the type T.</returns>
public List<ExtendedPropertyInfo<T>> GetList()
{
var jsonData = Json.Deserialize(Json.Serialize(Global)) as Dictionary<string, object>;
return jsonData?.Keys
.Select(p => new ExtendedPropertyInfo<T>(p) { Value = jsonData[p] })
.ToList();
}
/// <summary>
/// Resets the global settings.
/// </summary>
public void ResetGlobalSettings()
{
lock (_syncRoot)
_global = Activator.CreateInstance<T>();
PersistGlobalSettings();
}
private bool SetValue(object property, object originalValue, PropertyInfo propertyInfo)
{
switch (property)
{
case null when originalValue == null:
break;
case null:
propertyInfo.SetValue(Global, null);
return true;
default:
if (propertyInfo.PropertyType.TryParseBasicType(property, out var propertyValue) &&
!propertyValue.Equals(originalValue))
{
propertyInfo.SetValue(Global, propertyValue);
return true;
}
break;
}
return false;
} }
}
} }
/// <summary>
/// Reloads the global settings.
/// </summary>
public void ReloadGlobalSettings() {
if(File.Exists(this.ConfigurationFilePath) == false || File.ReadAllText(this.ConfigurationFilePath).Length == 0) {
this.ResetGlobalSettings();
return;
}
lock(this._syncRoot) {
this._global = Json.Deserialize<T>(File.ReadAllText(this.ConfigurationFilePath));
}
}
/// <summary>
/// Persists the global settings.
/// </summary>
public void PersistGlobalSettings() => File.WriteAllText(this.ConfigurationFilePath, Json.Serialize(this.Global, true));
/// <summary>
/// Updates settings from list.
/// </summary>
/// <param name="propertyList">The list.</param>
/// <returns>
/// A list of settings of type ref="ExtendedPropertyInfo".
/// </returns>
/// <exception cref="ArgumentNullException">propertyList.</exception>
public List<String> RefreshFromList(List<ExtendedPropertyInfo<T>> propertyList) {
if(propertyList == null) {
throw new ArgumentNullException(nameof(propertyList));
}
List<String> changedSettings = new List<String>();
IEnumerable<PropertyInfo> globalProps = Runtime.PropertyTypeCache.RetrieveAllProperties<T>();
foreach(ExtendedPropertyInfo<T> property in propertyList) {
PropertyInfo propertyInfo = globalProps.FirstOrDefault(x => x.Name == property.Property);
if(propertyInfo == null) {
continue;
}
Object originalValue = propertyInfo.GetValue(this.Global);
Boolean isChanged = propertyInfo.PropertyType.IsArray
? property.Value is IEnumerable enumerable && propertyInfo.TrySetArray(enumerable.Cast<Object>(), this.Global)
: this.SetValue(property.Value, originalValue, propertyInfo);
if(!isChanged) {
continue;
}
changedSettings.Add(property.Property);
this.PersistGlobalSettings();
}
return changedSettings;
}
/// <summary>
/// Gets the list.
/// </summary>
/// <returns>A List of ExtendedPropertyInfo of the type T.</returns>
public List<ExtendedPropertyInfo<T>> GetList() {
Dictionary<String, Object> jsonData = Json.Deserialize(Json.Serialize(this.Global)) as Dictionary<String, Object>;
return jsonData?.Keys
.Select(p => new ExtendedPropertyInfo<T>(p) { Value = jsonData[p] })
.ToList();
}
/// <summary>
/// Resets the global settings.
/// </summary>
public void ResetGlobalSettings() {
lock(this._syncRoot) {
this._global = Activator.CreateInstance<T>();
}
this.PersistGlobalSettings();
}
private Boolean SetValue(Object property, Object originalValue, PropertyInfo propertyInfo) {
switch(property) {
case null when originalValue == null:
break;
case null:
propertyInfo.SetValue(this.Global, null);
return true;
default:
if(propertyInfo.PropertyType.TryParseBasicType(property, out Object propertyValue) &&
!propertyValue.Equals(originalValue)) {
propertyInfo.SetValue(this.Global, propertyValue);
return true;
}
break;
}
return false;
}
}
} }

View File

@ -1,59 +1,57 @@
namespace Unosquare.Swan.Abstractions using System;
{
using System; namespace Unosquare.Swan.Abstractions {
/// <summary>
/// Represents a singleton pattern abstract class.
/// </summary>
/// <typeparam name="T">The type of class.</typeparam>
public abstract class SingletonBase<T> : IDisposable
where T : class {
/// <summary>
/// The static, singleton instance reference.
/// </summary>
protected static readonly Lazy<T> LazyInstance = new Lazy<T>(
valueFactory: () => Activator.CreateInstance(typeof(T), true) as T,
isThreadSafe: true);
private Boolean _isDisposing; // To detect redundant calls
/// <summary> /// <summary>
/// Represents a singleton pattern abstract class. /// Gets the instance that this singleton represents.
/// If the instance is null, it is constructed and assigned when this member is accessed.
/// </summary> /// </summary>
/// <typeparam name="T">The type of class.</typeparam> /// <value>
public abstract class SingletonBase<T> : IDisposable /// The instance.
where T : class /// </value>
{ public static T Instance => LazyInstance.Value;
/// <summary>
/// The static, singleton instance reference.
/// </summary>
protected static readonly Lazy<T> LazyInstance = new Lazy<T>(
valueFactory: () => Activator.CreateInstance(typeof(T), true) as T,
isThreadSafe: true);
private bool _isDisposing; // To detect redundant calls /// <inheritdoc />
public void Dispose() => this.Dispose(true);
/// <summary> /// <summary>
/// Gets the instance that this singleton represents. /// Releases unmanaged and - optionally - managed resources.
/// If the instance is null, it is constructed and assigned when this member is accessed. /// Call the GC.SuppressFinalize if you override this method and use
/// </summary> /// a non-default class finalizer (destructor).
/// <value> /// </summary>
/// The instance. /// <param name="disposeManaged"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
/// </value> protected virtual void Dispose(Boolean disposeManaged) {
public static T Instance => LazyInstance.Value; if(this._isDisposing) {
return;
}
/// <inheritdoc /> this._isDisposing = true;
public void Dispose() => Dispose(true);
/// <summary> // free managed resources
/// Releases unmanaged and - optionally - managed resources. if(LazyInstance == null) {
/// Call the GC.SuppressFinalize if you override this method and use return;
/// a non-default class finalizer (destructor). }
/// </summary>
/// <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)
{
if (_isDisposing) return;
_isDisposing = true; try {
IDisposable disposableInstance = LazyInstance.Value as IDisposable;
// free managed resources disposableInstance?.Dispose();
if (LazyInstance == null) return; } catch {
// swallow
try }
{
var disposableInstance = LazyInstance.Value as IDisposable;
disposableInstance?.Dispose();
}
catch
{
// swallow
}
}
} }
}
} }

View File

@ -1,143 +1,139 @@
namespace Unosquare.Swan.Abstractions using System;
{ using System.Collections.Generic;
using System; using System.Linq;
using System.Collections.Generic;
using System.Linq; namespace Unosquare.Swan.Abstractions {
/// <summary>
/// Represents a generic tokenizer.
/// </summary>
public abstract class Tokenizer {
private const Char PeriodChar = '.';
private const Char CommaChar = ',';
private const Char StringQuotedChar = '"';
private const Char OpenFuncChar = '(';
private const Char CloseFuncChar = ')';
private const Char NegativeChar = '-';
private const String OpenFuncStr = "(";
private readonly List<Operator> _operators = new List<Operator>();
/// <summary> /// <summary>
/// Represents a generic tokenizer. /// Initializes a new instance of the <see cref="Tokenizer"/> class.
/// This constructor will use the following default operators:
///
/// <list type="table">
/// <listheader>
/// <term>Operator</term>
/// <description>Precedence</description>
/// </listheader>
/// <item>
/// <term>=</term>
/// <description>1</description>
/// </item>
/// <item>
/// <term>!=</term>
/// <description>1</description>
/// </item>
/// <item>
/// <term>&gt;</term>
/// <description>2</description>
/// </item>
/// <item>
/// <term>&lt;</term>
/// <description>2</description>
/// </item>
/// <item>
/// <term>&gt;=</term>
/// <description>2</description>
/// </item>
/// <item>
/// <term>&lt;=</term>
/// <description>2</description>
/// </item>
/// <item>
/// <term>+</term>
/// <description>3</description>
/// </item>
/// <item>
/// <term>&amp;</term>
/// <description>3</description>
/// </item>
/// <item>
/// <term>-</term>
/// <description>3</description>
/// </item>
/// <item>
/// <term>*</term>
/// <description>4</description>
/// </item>
/// <item>
/// <term>(backslash)</term>
/// <description>4</description>
/// </item>
/// <item>
/// <term>/</term>
/// <description>4</description>
/// </item>
/// <item>
/// <term>^</term>
/// <description>4</description>
/// </item>
/// </list>
/// </summary> /// </summary>
public abstract class Tokenizer /// <param name="input">The input.</param>
protected Tokenizer(String input) {
this._operators.AddRange(this.GetDefaultOperators());
this.Tokenize(input);
}
/// <summary>
/// Initializes a new instance of the <see cref="Tokenizer" /> class.
/// </summary>
/// <param name="input">The input.</param>
/// <param name="operators">The operators to use.</param>
protected Tokenizer(String input, IEnumerable<Operator> operators) {
this._operators.AddRange(operators);
this.Tokenize(input);
}
/// <summary>
/// Gets the tokens.
/// </summary>
/// <value>
/// The tokens.
/// </value>
public List<Token> Tokens { get; } = new List<Token>();
/// <summary>
/// Validates the input and return the start index for tokenizer.
/// </summary>
/// <param name="input">The input.</param>
/// <param name="startIndex">The start index.</param>
/// <returns><c>true</c> if the input is valid, otherwise <c>false</c>.</returns>
public abstract Boolean ValidateInput(String input, out Int32 startIndex);
/// <summary>
/// Resolves the type of the function or member.
/// </summary>
/// <param name="input">The input.</param>
/// <returns>The token type.</returns>
public abstract TokenType ResolveFunctionOrMemberType(String input);
/// <summary>
/// Evaluates the function or member.
/// </summary>
/// <param name="input">The input.</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>
public virtual Boolean EvaluateFunctionOrMember(String input, Int32 position) => false;
/// <summary>
/// Gets the default operators.
/// </summary>
/// <returns>An array with the operators to use for the tokenizer.</returns>
public virtual Operator[] GetDefaultOperators() => new[]
{ {
private const char PeriodChar = '.';
private const char CommaChar = ',';
private const char StringQuotedChar = '"';
private const char OpenFuncChar = '(';
private const char CloseFuncChar = ')';
private const char NegativeChar = '-';
private const string OpenFuncStr = "(";
private readonly List<Operator> _operators = new List<Operator>();
/// <summary>
/// Initializes a new instance of the <see cref="Tokenizer"/> class.
/// This constructor will use the following default operators:
///
/// <list type="table">
/// <listheader>
/// <term>Operator</term>
/// <description>Precedence</description>
/// </listheader>
/// <item>
/// <term>=</term>
/// <description>1</description>
/// </item>
/// <item>
/// <term>!=</term>
/// <description>1</description>
/// </item>
/// <item>
/// <term>&gt;</term>
/// <description>2</description>
/// </item>
/// <item>
/// <term>&lt;</term>
/// <description>2</description>
/// </item>
/// <item>
/// <term>&gt;=</term>
/// <description>2</description>
/// </item>
/// <item>
/// <term>&lt;=</term>
/// <description>2</description>
/// </item>
/// <item>
/// <term>+</term>
/// <description>3</description>
/// </item>
/// <item>
/// <term>&amp;</term>
/// <description>3</description>
/// </item>
/// <item>
/// <term>-</term>
/// <description>3</description>
/// </item>
/// <item>
/// <term>*</term>
/// <description>4</description>
/// </item>
/// <item>
/// <term>(backslash)</term>
/// <description>4</description>
/// </item>
/// <item>
/// <term>/</term>
/// <description>4</description>
/// </item>
/// <item>
/// <term>^</term>
/// <description>4</description>
/// </item>
/// </list>
/// </summary>
/// <param name="input">The input.</param>
protected Tokenizer(string input)
{
_operators.AddRange(GetDefaultOperators());
Tokenize(input);
}
/// <summary>
/// Initializes a new instance of the <see cref="Tokenizer" /> class.
/// </summary>
/// <param name="input">The input.</param>
/// <param name="operators">The operators to use.</param>
protected Tokenizer(string input, IEnumerable<Operator> operators)
{
_operators.AddRange(operators);
Tokenize(input);
}
/// <summary>
/// Gets the tokens.
/// </summary>
/// <value>
/// The tokens.
/// </value>
public List<Token> Tokens { get; } = new List<Token>();
/// <summary>
/// Validates the input and return the start index for tokenizer.
/// </summary>
/// <param name="input">The input.</param>
/// <param name="startIndex">The start index.</param>
/// <returns><c>true</c> if the input is valid, otherwise <c>false</c>.</returns>
public abstract bool ValidateInput(string input, out int startIndex);
/// <summary>
/// Resolves the type of the function or member.
/// </summary>
/// <param name="input">The input.</param>
/// <returns>The token type.</returns>
public abstract TokenType ResolveFunctionOrMemberType(string input);
/// <summary>
/// Evaluates the function or member.
/// </summary>
/// <param name="input">The input.</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>
public virtual bool EvaluateFunctionOrMember(string input, int position) => false;
/// <summary>
/// Gets the default operators.
/// </summary>
/// <returns>An array with the operators to use for the tokenizer.</returns>
public virtual Operator[] GetDefaultOperators() => new[]
{
new Operator {Name = "=", Precedence = 1}, new Operator {Name = "=", Precedence = 1},
new Operator {Name = "!=", Precedence = 1}, new Operator {Name = "!=", Precedence = 1},
new Operator {Name = ">", Precedence = 2}, new Operator {Name = ">", Precedence = 2},
@ -153,307 +149,302 @@
new Operator {Name = "^", Precedence = 4}, new Operator {Name = "^", Precedence = 4},
}; };
/// <summary> /// <summary>
/// Shunting the yard. /// Shunting the yard.
/// </summary> /// </summary>
/// <param name="includeFunctionStopper">if set to <c>true</c> [include function stopper] (Token type <c>Wall</c>).</param> /// <param name="includeFunctionStopper">if set to <c>true</c> [include function stopper] (Token type <c>Wall</c>).</param>
/// <returns> /// <returns>
/// Enumerable of the token in in. /// Enumerable of the token in in.
/// </returns> /// </returns>
/// <exception cref="InvalidOperationException"> /// <exception cref="InvalidOperationException">
/// Wrong token /// Wrong token
/// 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.Variable:
case TokenType.Number: case TokenType.String:
case TokenType.Variable: yield return tok;
case TokenType.String: break;
yield return tok; case TokenType.Function:
break; stack.Push(tok);
case TokenType.Function: break;
stack.Push(tok); case TokenType.Operator:
break; while(stack.Any() && stack.Peek().Type == TokenType.Operator &&
case TokenType.Operator: this.CompareOperators(tok.Value, stack.Peek().Value)) {
while (stack.Any() && stack.Peek().Type == TokenType.Operator && yield return stack.Pop();
CompareOperators(tok.Value, stack.Peek().Value)) }
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) {
{ yield return new Token(TokenType.Wall, tok.Value);
if (includeFunctionStopper)
yield return new Token(TokenType.Wall, tok.Value);
}
stack.Push(tok);
}
else
{
while (stack.Peek().Value != OpenFuncStr)
yield return stack.Pop();
stack.Pop();
if (stack.Any() && stack.Peek().Type == TokenType.Function)
{
yield return stack.Pop();
}
}
break;
default:
throw new InvalidOperationException("Wrong token");
} }
}
stack.Push(tok);
} else {
while(stack.Peek().Value != OpenFuncStr) {
yield return stack.Pop();
}
_ = stack.Pop();
if(stack.Any() && stack.Peek().Type == TokenType.Function) {
yield return stack.Pop();
}
} }
while (stack.Any()) break;
{ default:
var tok = stack.Pop(); throw new InvalidOperationException("Wrong token");
if (tok.Type == TokenType.Parenthesis) }
throw new InvalidOperationException("Mismatched parenthesis"); }
yield return tok; while(stack.Any()) {
} Token tok = stack.Pop();
if(tok.Type == TokenType.Parenthesis) {
throw new InvalidOperationException("Mismatched parenthesis");
} }
private static bool CompareOperators(Operator op1, Operator op2) => op1.RightAssociative yield return tok;
? op1.Precedence < op2.Precedence }
: op1.Precedence <= op2.Precedence; }
private void Tokenize(string input) private static Boolean CompareOperators(Operator op1, Operator op2) => op1.RightAssociative
{ ? op1.Precedence < op2.Precedence
if (!ValidateInput(input, out var startIndex)) : op1.Precedence <= op2.Precedence;
{
return;
}
for (var i = startIndex; i < input.Length; i++) private void Tokenize(String input) {
{ if(!this.ValidateInput(input, out Int32 startIndex)) {
if (char.IsWhiteSpace(input, i)) continue; return;
}
if (input[i] == CommaChar) for(Int32 i = startIndex; i < input.Length; i++) {
{ if(Char.IsWhiteSpace(input, i)) {
Tokens.Add(new Token(TokenType.Comma, new string(new[] { input[i] }))); continue;
continue;
}
if (input[i] == StringQuotedChar)
{
i = ExtractString(input, i);
continue;
}
if (char.IsLetter(input, i) || EvaluateFunctionOrMember(input, i))
{
i = ExtractFunctionOrMember(input, i);
continue;
}
if (char.IsNumber(input, i) || (
input[i] == NegativeChar &&
((Tokens.Any() && Tokens.Last().Type != TokenType.Number) || !Tokens.Any())))
{
i = ExtractNumber(input, i);
continue;
}
if (input[i] == OpenFuncChar ||
input[i] == CloseFuncChar)
{
Tokens.Add(new Token(TokenType.Parenthesis, new string(new[] { input[i] })));
continue;
}
i = ExtractOperator(input, i);
}
} }
private int ExtractData( if(input[i] == CommaChar) {
string input, this.Tokens.Add(new Token(TokenType.Comma, new String(new[] { input[i] })));
int i, continue;
Func<string, TokenType> tokenTypeEvaluation,
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;
charCount++;
}
// Extract and set the value
var value = input.SliceLength(i + right, charCount);
Tokens.Add(new Token(tokenTypeEvaluation(value), value));
i += charCount + left;
return i;
} }
private int ExtractOperator(string input, int i) => if(input[i] == StringQuotedChar) {
ExtractData(input, i, x => TokenType.Operator, x => x == OpenFuncChar || i = this.ExtractString(input, i);
continue;
}
if(Char.IsLetter(input, i) || this.EvaluateFunctionOrMember(input, i)) {
i = this.ExtractFunctionOrMember(input, i);
continue;
}
if(Char.IsNumber(input, i) ||
input[i] == NegativeChar &&
(this.Tokens.Any() && this.Tokens.Last().Type != TokenType.Number || !this.Tokens.Any())) {
i = this.ExtractNumber(input, i);
continue;
}
if(input[i] == OpenFuncChar ||
input[i] == CloseFuncChar) {
this.Tokens.Add(new Token(TokenType.Parenthesis, new String(new[] { input[i] })));
continue;
}
i = this.ExtractOperator(input, i);
}
}
private Int32 ExtractData(
String input,
Int32 i,
Func<String, TokenType> tokenTypeEvaluation,
Func<Char, Boolean> evaluation,
Int32 right = 0,
Int32 left = -1) {
Int32 charCount = 0;
for(Int32 j = i + right; j < input.Length; j++) {
if(evaluation(input[j])) {
break;
}
charCount++;
}
// Extract and set the value
String value = input.SliceLength(i + right, charCount);
this.Tokens.Add(new Token(tokenTypeEvaluation(value), value));
i += charCount + left;
return 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));
private Int32 ExtractFunctionOrMember(String input, Int32 i) =>
this.ExtractData(input, i, this.ResolveFunctionOrMemberType, x => x == OpenFuncChar ||
x == CloseFuncChar ||
x == CommaChar || x == CommaChar ||
x == PeriodChar || Char.IsWhiteSpace(x));
x == StringQuotedChar ||
char.IsWhiteSpace(x) ||
char.IsNumber(x));
private int ExtractFunctionOrMember(string input, int i) => private Int32 ExtractNumber(String input, Int32 i) =>
ExtractData(input, i, ResolveFunctionOrMemberType, x => x == OpenFuncChar || this.ExtractData(input, i, x => TokenType.Number,
x == CloseFuncChar || x => !Char.IsNumber(x) && x != PeriodChar && x != NegativeChar);
x == CommaChar ||
char.IsWhiteSpace(x));
private int ExtractNumber(string input, int i) => private Int32 ExtractString(String input, Int32 i) {
ExtractData(input, i, x => TokenType.Number, Int32 length = this.ExtractData(input, i, x => TokenType.String, x => x == StringQuotedChar, 1, 1);
x => !char.IsNumber(x) && x != PeriodChar && x != NegativeChar);
private int ExtractString(string input, int i) // open string, report issue
{ if(length == input.Length && input[length - 1] != StringQuotedChar) {
var length = ExtractData(input, i, x => TokenType.String, x => x == StringQuotedChar, 1, 1); throw new FormatException($"Parser error (Position {i}): Expected '\"' but got '{input[length - 1]}'.");
}
// open string, report issue return length;
if (length == input.Length && input[length - 1] != StringQuotedChar) }
throw new FormatException($"Parser error (Position {i}): Expected '\"' but got '{input[length - 1]}'.");
return length; private Boolean CompareOperators(String op1, String op2)
} => CompareOperators(this.GetOperatorOrDefault(op1), this.GetOperatorOrDefault(op2));
private bool CompareOperators(string op1, string op2) private Operator GetOperatorOrDefault(String op)
=> CompareOperators(GetOperatorOrDefault(op1), GetOperatorOrDefault(op2)); => this._operators.FirstOrDefault(x => x.Name == op) ?? new Operator { Name = op, Precedence = 0 };
}
private Operator GetOperatorOrDefault(string op) /// <summary>
=> _operators.FirstOrDefault(x => x.Name == op) ?? new Operator { Name = op, Precedence = 0 }; /// Represents an operator with precedence.
/// </summary>
public class Operator {
/// <summary>
/// Gets or sets the name.
/// </summary>
/// <value>
/// The name.
/// </value>
public String Name {
get; set;
} }
/// <summary> /// <summary>
/// Represents an operator with precedence. /// Gets or sets the precedence.
/// </summary> /// </summary>
public class Operator /// <value>
{ /// The precedence.
/// <summary> /// </value>
/// Gets or sets the name. public Int32 Precedence {
/// </summary> get; set;
/// <value>
/// The name.
/// </value>
public string Name { get; set; }
/// <summary>
/// Gets or sets the precedence.
/// </summary>
/// <value>
/// The precedence.
/// </value>
public int Precedence { get; set; }
/// <summary>
/// Gets or sets a value indicating whether [right associative].
/// </summary>
/// <value>
/// <c>true</c> if [right associative]; otherwise, <c>false</c>.
/// </value>
public bool RightAssociative { get; set; }
} }
/// <summary> /// <summary>
/// Represents a Token structure. /// Gets or sets a value indicating whether [right associative].
/// </summary> /// </summary>
public struct Token /// <value>
{ /// <c>true</c> if [right associative]; otherwise, <c>false</c>.
/// <summary> /// </value>
/// Initializes a new instance of the <see cref="Token"/> struct. public Boolean RightAssociative {
/// </summary> get; set;
/// <param name="type">The type.</param> }
/// <param name="value">The value.</param> }
public Token(TokenType type, string value)
{
Type = type;
Value = type == TokenType.Function || type == TokenType.Operator ? value.ToLowerInvariant() : value;
}
/// <summary> /// <summary>
/// Gets or sets the type. /// Represents a Token structure.
/// </summary> /// </summary>
/// <value> public struct Token {
/// The type. /// <summary>
/// </value> /// Initializes a new instance of the <see cref="Token"/> struct.
public TokenType Type { get; set; } /// </summary>
/// <param name="type">The type.</param>
/// <summary> /// <param name="value">The value.</param>
/// Gets the value. public Token(TokenType type, String value) {
/// </summary> this.Type = type;
/// <value> this.Value = type == TokenType.Function || type == TokenType.Operator ? value.ToLowerInvariant() : value;
/// The value.
/// </value>
public string Value { get; }
} }
/// <summary> /// <summary>
/// Enums the token types. /// Gets or sets the type.
/// </summary> /// </summary>
public enum TokenType /// <value>
{ /// The type.
/// <summary> /// </value>
/// The number public TokenType Type {
/// </summary> get; set;
Number,
/// <summary>
/// The string
/// </summary>
String,
/// <summary>
/// The variable
/// </summary>
Variable,
/// <summary>
/// The function
/// </summary>
Function,
/// <summary>
/// The parenthesis
/// </summary>
Parenthesis,
/// <summary>
/// The operator
/// </summary>
Operator,
/// <summary>
/// The comma
/// </summary>
Comma,
/// <summary>
/// The wall, used to specified the end of argument list of the following function
/// </summary>
Wall,
} }
/// <summary>
/// Gets the value.
/// </summary>
/// <value>
/// The value.
/// </value>
public String Value {
get;
}
}
/// <summary>
/// Enums the token types.
/// </summary>
public enum TokenType {
/// <summary>
/// The number
/// </summary>
Number,
/// <summary>
/// The string
/// </summary>
String,
/// <summary>
/// The variable
/// </summary>
Variable,
/// <summary>
/// The function
/// </summary>
Function,
/// <summary>
/// The parenthesis
/// </summary>
Parenthesis,
/// <summary>
/// The operator
/// </summary>
Operator,
/// <summary>
/// The comma
/// </summary>
Comma,
/// <summary>
/// The wall, used to specified the end of argument list of the following function
/// </summary>
Wall,
}
} }

View File

@ -1,127 +1,123 @@
namespace Unosquare.Swan.Lite.Abstractions using System;
{ using System.Collections.Concurrent;
using System.Collections.Concurrent; using System.Collections.Generic;
using System.Collections.Generic; using System.ComponentModel;
using System.ComponentModel; using System.Linq;
using System.Linq; using System.Runtime.CompilerServices;
using System.Runtime.CompilerServices; using System.Threading.Tasks;
using System.Threading.Tasks;
namespace Unosquare.Swan.Lite.Abstractions {
/// <summary>
/// A base class for implementing models that fire notifications when their properties change.
/// This class is ideal for implementing MVVM driven UIs.
/// </summary>
/// <seealso cref="INotifyPropertyChanged" />
public abstract class ViewModelBase : INotifyPropertyChanged {
private readonly ConcurrentDictionary<String, Boolean> QueuedNotifications = new ConcurrentDictionary<String, Boolean>();
private readonly Boolean UseDeferredNotifications;
/// <summary> /// <summary>
/// A base class for implementing models that fire notifications when their properties change. /// Initializes a new instance of the <see cref="ViewModelBase"/> class.
/// This class is ideal for implementing MVVM driven UIs.
/// </summary> /// </summary>
/// <seealso cref="INotifyPropertyChanged" /> /// <param name="useDeferredNotifications">Set to <c>true</c> to use deferred notifications in the background.</param>
public abstract class ViewModelBase : INotifyPropertyChanged protected ViewModelBase(Boolean useDeferredNotifications) => this.UseDeferredNotifications = useDeferredNotifications;
{
private readonly ConcurrentDictionary<string, bool> QueuedNotifications = new ConcurrentDictionary<string, bool>();
private readonly bool UseDeferredNotifications;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ViewModelBase"/> class. /// Initializes a new instance of the <see cref="ViewModelBase"/> class.
/// </summary> /// </summary>
/// <param name="useDeferredNotifications">Set to <c>true</c> to use deferred notifications in the background.</param> protected ViewModelBase()
protected ViewModelBase(bool useDeferredNotifications) : this(false) {
{ // placeholder
UseDeferredNotifications = useDeferredNotifications;
}
/// <summary>
/// Initializes a new instance of the <see cref="ViewModelBase"/> class.
/// </summary>
protected ViewModelBase()
: this(false)
{
// placeholder
}
/// <summary>
/// Occurs when a property value changes.
/// </summary>
/// <returns></returns>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>Checks if a property already matches a desired value. Sets the property and
/// notifies listeners only when necessary.</summary>
/// <typeparam name="T">Type of the property.</typeparam>
/// <param name="storage">Reference to a property with both getter and setter.</param>
/// <param name="value">Desired value for the property.</param>
/// <param name="propertyName">Name of the property used to notify listeners. This
/// value is optional and can be provided automatically when invoked from compilers that
/// support CallerMemberName.</param>
/// <param name="notifyAlso">An rray of property names to notify in addition to notifying the changes on the current property name.</param>
/// <returns>True if the value was changed, false if the existing value matched the
/// desired value.</returns>
protected bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = "", string[] notifyAlso = null)
{
if (EqualityComparer<T>.Default.Equals(storage, value))
return false;
storage = value;
NotifyPropertyChanged(propertyName, notifyAlso);
return true;
}
/// <summary>
/// Notifies one or more properties changed.
/// </summary>
/// <param name="propertyNames">The property names.</param>
protected void NotifyPropertyChanged(params string[] propertyNames) => NotifyPropertyChanged(null, propertyNames);
/// <summary>
/// Notifies one or more properties changed.
/// </summary>
/// <param name="mainProperty">The main property.</param>
/// <param name="auxiliaryProperties">The auxiliary properties.</param>
private void NotifyPropertyChanged(string mainProperty, string[] auxiliaryProperties)
{
// Queue property notification
if (string.IsNullOrWhiteSpace(mainProperty) == false)
QueuedNotifications[mainProperty] = true;
// Set the state for notification properties
if (auxiliaryProperties != null)
{
foreach (var property in auxiliaryProperties)
{
if (string.IsNullOrWhiteSpace(property) == false)
QueuedNotifications[property] = true;
}
}
// Depending on operation mode, either fire the notifications in the background
// or fire them immediately
if (UseDeferredNotifications)
Task.Run(() => NotifyQueuedProperties());
else
NotifyQueuedProperties();
}
/// <summary>
/// Notifies the queued properties and resets the property name to a non-queued stated.
/// </summary>
private void NotifyQueuedProperties()
{
// get a snapshot of property names.
var propertyNames = QueuedNotifications.Keys.ToArray();
// Iterate through the properties
foreach (var property in propertyNames)
{
// don't notify if we don't have a change
if (!QueuedNotifications[property]) continue;
// notify and reset queued state to false
try { OnPropertyChanged(property); }
finally { QueuedNotifications[property] = false; }
}
}
/// <summary>
/// Called when a property changes its backing value.
/// </summary>
/// <param name="propertyName">Name of the property.</param>
private void OnPropertyChanged(string propertyName) =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName ?? string.Empty));
} }
/// <summary>
/// Occurs when a property value changes.
/// </summary>
/// <returns></returns>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>Checks if a property already matches a desired value. Sets the property and
/// notifies listeners only when necessary.</summary>
/// <typeparam name="T">Type of the property.</typeparam>
/// <param name="storage">Reference to a property with both getter and setter.</param>
/// <param name="value">Desired value for the property.</param>
/// <param name="propertyName">Name of the property used to notify listeners. This
/// value is optional and can be provided automatically when invoked from compilers that
/// support CallerMemberName.</param>
/// <param name="notifyAlso">An rray of property names to notify in addition to notifying the changes on the current property name.</param>
/// <returns>True if the value was changed, false if the existing value matched the
/// desired value.</returns>
protected Boolean SetProperty<T>(ref T storage, T value, [CallerMemberName] String propertyName = "", String[] notifyAlso = null) {
if(EqualityComparer<T>.Default.Equals(storage, value)) {
return false;
}
storage = value;
this.NotifyPropertyChanged(propertyName, notifyAlso);
return true;
}
/// <summary>
/// Notifies one or more properties changed.
/// </summary>
/// <param name="propertyNames">The property names.</param>
protected void NotifyPropertyChanged(params String[] propertyNames) => this.NotifyPropertyChanged(null, propertyNames);
/// <summary>
/// Notifies one or more properties changed.
/// </summary>
/// <param name="mainProperty">The main property.</param>
/// <param name="auxiliaryProperties">The auxiliary properties.</param>
private void NotifyPropertyChanged(String mainProperty, String[] auxiliaryProperties) {
// Queue property notification
if(String.IsNullOrWhiteSpace(mainProperty) == false) {
this.QueuedNotifications[mainProperty] = true;
}
// Set the state for notification properties
if(auxiliaryProperties != null) {
foreach(String property in auxiliaryProperties) {
if(String.IsNullOrWhiteSpace(property) == false) {
this.QueuedNotifications[property] = true;
}
}
}
// Depending on operation mode, either fire the notifications in the background
// or fire them immediately
if(this.UseDeferredNotifications) {
_ = Task.Run(() => this.NotifyQueuedProperties());
} else {
this.NotifyQueuedProperties();
}
}
/// <summary>
/// Notifies the queued properties and resets the property name to a non-queued stated.
/// </summary>
private void NotifyQueuedProperties() {
// get a snapshot of property names.
String[] propertyNames = this.QueuedNotifications.Keys.ToArray();
// Iterate through the properties
foreach(String property in propertyNames) {
// don't notify if we don't have a change
if(!this.QueuedNotifications[property]) {
continue;
}
// notify and reset queued state to false
try {
this.OnPropertyChanged(property);
} finally { this.QueuedNotifications[property] = false; }
}
}
/// <summary>
/// Called when a property changes its backing value.
/// </summary>
/// <param name="propertyName">Name of the property.</param>
private void OnPropertyChanged(String propertyName) =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName ?? String.Empty));
}
} }

View File

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

View File

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

View File

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

View File

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

View File

@ -1,102 +1,105 @@
namespace Unosquare.Swan.Attributes using System;
{
using System; namespace Unosquare.Swan.Attributes {
/// <summary>
/// Models an option specification.
/// Based on CommandLine (Copyright 2005-2015 Giacomo Stelluti Scala and Contributors.).
/// </summary>
[AttributeUsage(AttributeTargets.Property)]
public sealed class ArgumentOptionAttribute
: Attribute {
/// <summary>
/// Initializes a new instance of the <see cref="ArgumentOptionAttribute"/> class.
/// The default long name will be inferred from target property.
/// </summary>
public ArgumentOptionAttribute()
: this(String.Empty, String.Empty) {
}
/// <summary> /// <summary>
/// Models an option specification. /// Initializes a new instance of the <see cref="ArgumentOptionAttribute"/> class.
/// Based on CommandLine (Copyright 2005-2015 Giacomo Stelluti Scala and Contributors.).
/// </summary> /// </summary>
[AttributeUsage(AttributeTargets.Property)] /// <param name="longName">The long name of the option.</param>
public sealed class ArgumentOptionAttribute public ArgumentOptionAttribute(String longName)
: Attribute : this(String.Empty, longName) {
{
/// <summary>
/// Initializes a new instance of the <see cref="ArgumentOptionAttribute"/> class.
/// The default long name will be inferred from target property.
/// </summary>
public ArgumentOptionAttribute()
: this(string.Empty, string.Empty)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ArgumentOptionAttribute"/> class.
/// </summary>
/// <param name="longName">The long name of the option.</param>
public ArgumentOptionAttribute(string longName)
: this(string.Empty, longName)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ArgumentOptionAttribute"/> class.
/// </summary>
/// <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>
public ArgumentOptionAttribute(char shortName, string longName)
: this(new string(shortName, 1), longName)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ArgumentOptionAttribute"/> class.
/// </summary>
/// <param name="shortName">The short name of the option..</param>
public ArgumentOptionAttribute(char shortName)
: this(new string(shortName, 1), string.Empty)
{
}
private ArgumentOptionAttribute(string shortName, string longName)
{
ShortName = shortName ?? throw new ArgumentNullException(nameof(shortName));
LongName = longName ?? throw new ArgumentNullException(nameof(longName));
}
/// <summary>
/// Gets long name of this command line option. This name is usually a single English word.
/// </summary>
/// <value>
/// The long name.
/// </value>
public string LongName { get; }
/// <summary>
/// Gets a short name of this command line option, made of one character.
/// </summary>
/// <value>
/// The short name.
/// </value>
public string ShortName { get; }
/// <summary>
/// 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.
/// </summary>
public char Separator { get; set; } = '\0';
/// <summary>
/// Gets or sets mapped property default value.
/// </summary>
/// <value>
/// The default value.
/// </value>
public object DefaultValue { get; set; }
/// <summary>
/// Gets or sets a value indicating whether a command line option is required.
/// </summary>
/// <value>
/// <c>true</c> if required; otherwise, <c>false</c>.
/// </value>
public bool Required { get; set; }
/// <summary>
/// Gets or sets a short description of this command line option. Usually a sentence summary.
/// </summary>
/// <value>
/// The help text.
/// </value>
public string HelpText { get; set; }
} }
/// <summary>
/// Initializes a new instance of the <see cref="ArgumentOptionAttribute"/> class.
/// </summary>
/// <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>
public ArgumentOptionAttribute(Char shortName, String longName)
: this(new String(shortName, 1), longName) {
}
/// <summary>
/// Initializes a new instance of the <see cref="ArgumentOptionAttribute"/> class.
/// </summary>
/// <param name="shortName">The short name of the option..</param>
public ArgumentOptionAttribute(Char shortName)
: this(new String(shortName, 1), String.Empty) {
}
private ArgumentOptionAttribute(String shortName, String longName) {
this.ShortName = shortName ?? throw new ArgumentNullException(nameof(shortName));
this.LongName = longName ?? throw new ArgumentNullException(nameof(longName));
}
/// <summary>
/// Gets long name of this command line option. This name is usually a single English word.
/// </summary>
/// <value>
/// The long name.
/// </value>
public String LongName {
get;
}
/// <summary>
/// Gets a short name of this command line option, made of one character.
/// </summary>
/// <value>
/// The short name.
/// </value>
public String ShortName {
get;
}
/// <summary>
/// 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.
/// </summary>
public Char Separator { get; set; } = '\0';
/// <summary>
/// Gets or sets mapped property default value.
/// </summary>
/// <value>
/// The default value.
/// </value>
public Object DefaultValue {
get; set;
}
/// <summary>
/// Gets or sets a value indicating whether a command line option is required.
/// </summary>
/// <value>
/// <c>true</c> if required; otherwise, <c>false</c>.
/// </value>
public Boolean Required {
get; set;
}
/// <summary>
/// Gets or sets a short description of this command line option. Usually a sentence summary.
/// </summary>
/// <value>
/// The help text.
/// </value>
public String HelpText {
get; set;
}
}
} }

View File

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

View File

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

View File

@ -1,54 +1,62 @@
namespace Unosquare.Swan.Attributes using System;
{
using System; namespace Unosquare.Swan.Attributes {
/// <summary>
/// An attribute used to include additional information to a Property for serialization.
///
/// Previously we used DisplayAttribute from DataAnnotation.
/// </summary>
/// <seealso cref="System.Attribute" />
[AttributeUsage(AttributeTargets.Property)]
public sealed class PropertyDisplayAttribute : Attribute {
/// <summary>
/// Gets or sets the name.
/// </summary>
/// <value>
/// The name.
/// </value>
public String Name {
get; set;
}
/// <summary> /// <summary>
/// An attribute used to include additional information to a Property for serialization. /// Gets or sets the description.
///
/// Previously we used DisplayAttribute from DataAnnotation.
/// </summary> /// </summary>
/// <seealso cref="System.Attribute" /> /// <value>
[AttributeUsage(AttributeTargets.Property)] /// The description.
public sealed class PropertyDisplayAttribute : Attribute /// </value>
{ public String Description {
/// <summary> get; set;
/// Gets or sets the name.
/// </summary>
/// <value>
/// The name.
/// </value>
public string Name { get; set; }
/// <summary>
/// Gets or sets the description.
/// </summary>
/// <value>
/// The description.
/// </value>
public string Description { get; set; }
/// <summary>
/// Gets or sets the name of the group.
/// </summary>
/// <value>
/// The name of the group.
/// </value>
public string GroupName { get; set; }
/// <summary>
/// Gets or sets the default value.
/// </summary>
/// <value>
/// The default value.
/// </value>
public object DefaultValue { get; set; }
/// <summary>
/// Gets or sets the format string to call with method <c>ToString</c>.
/// </summary>
/// <value>
/// The format.
/// </value>
public string Format { get; set; }
} }
/// <summary>
/// Gets or sets the name of the group.
/// </summary>
/// <value>
/// The name of the group.
/// </value>
public String GroupName {
get; set;
}
/// <summary>
/// Gets or sets the default value.
/// </summary>
/// <value>
/// The default value.
/// </value>
public Object DefaultValue {
get; set;
}
/// <summary>
/// Gets or sets the format string to call with method <c>ToString</c>.
/// </summary>
/// <value>
/// The format.
/// </value>
public String Format {
get; set;
}
}
} }

View File

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

View File

@ -1,133 +1,130 @@
namespace Unosquare.Swan.Attributes using System;
{ using System.Text.RegularExpressions;
using System; using Unosquare.Swan.Abstractions;
using System.Text.RegularExpressions;
using Abstractions; namespace Unosquare.Swan.Attributes {
/// <summary>
/// Regex validator.
/// </summary>
[AttributeUsage(AttributeTargets.Property)]
public class MatchAttribute : Attribute, IValidator {
/// <summary>
/// Initializes a new instance of the <see cref="MatchAttribute" /> class.
/// </summary>
/// <param name="regex">A regex string.</param>
/// <param name="errorMessage">The error message.</param>
/// <exception cref="ArgumentNullException">Expression.</exception>
public MatchAttribute(String regex, String errorMessage = null) {
this.Expression = regex ?? throw new ArgumentNullException(nameof(this.Expression));
this.ErrorMessage = errorMessage ?? "String does not match the specified regular expression";
}
/// <summary> /// <summary>
/// Regex validator. /// The string regex used to find a match.
/// </summary> /// </summary>
[AttributeUsage(AttributeTargets.Property)] public String Expression {
public class MatchAttribute : Attribute, IValidator get;
{ }
/// <summary>
/// Initializes a new instance of the <see cref="MatchAttribute" /> class.
/// </summary>
/// <param name="regex">A regex string.</param>
/// <param name="errorMessage">The error message.</param>
/// <exception cref="ArgumentNullException">Expression.</exception>
public MatchAttribute(string regex, string errorMessage = null)
{
Expression = regex ?? throw new ArgumentNullException(nameof(Expression));
ErrorMessage = errorMessage ?? "String does not match the specified regular expression";
}
/// <summary> /// <inheritdoc/>
/// The string regex used to find a match. public String ErrorMessage {
/// </summary> get; internal set;
public string Expression { get; } }
/// <inheritdoc/> /// <inheritdoc/>
public string ErrorMessage { get; internal set; } public Boolean IsValid<T>(T value) => Equals(value, default(T))
? false
/// <inheritdoc/> : !(value is String)
public bool IsValid<T>(T value)
{
if (Equals(value, default(T)))
return false;
return !(value is string)
? throw new ArgumentException("Property is not a string") ? throw new ArgumentException("Property is not a string")
: Regex.IsMatch(value.ToString(), Expression); : Regex.IsMatch(value.ToString(), this.Expression);
} }
/// <summary>
/// Email validator.
/// </summary>
[AttributeUsage(AttributeTargets.Property)]
public class EmailAttribute : MatchAttribute {
private const String EmailRegExp =
@"^(?("")("".+?(?<!\\)""@)|(([0-9a-z]((\.(?!\.))|[-!#\$%&'\*\+/=\?\^`\{\}\|~\w])*)(?<=[0-9a-z])@))" +
@"(?(\[)(\[(\d{1,3}\.){3}\d{1,3}\])|(([0-9a-z][-0-9a-z]*[0-9a-z]*\.)+[a-z0-9][\-a-z0-9]{0,22}[a-z0-9]))$";
/// <summary>
/// Initializes a new instance of the <see cref="EmailAttribute"/> class.
/// </summary>
/// <param name="errorMessage">The error message.</param>
public EmailAttribute(String errorMessage = null)
: base(EmailRegExp, errorMessage ?? "String is not an email") {
}
}
/// <summary>
/// A not null validator.
/// </summary>
[AttributeUsage(AttributeTargets.Property)]
public class NotNullAttribute : Attribute, IValidator {
/// <inheritdoc/>
public String ErrorMessage => "Value is null";
/// <inheritdoc/>
public Boolean IsValid<T>(T value) => !Equals(default(T), value);
}
/// <summary>
/// A range constraint validator.
/// </summary>
[AttributeUsage(AttributeTargets.Property)]
public class RangeAttribute : Attribute, IValidator {
/// <summary>
/// Initializes a new instance of the <see cref="RangeAttribute"/> class.
/// Constructor that takes integer minimum and maximum values.
/// </summary>
/// <param name="min">The minimum value.</param>
/// <param name="max">The maximum value.</param>
public RangeAttribute(Int32 min, Int32 max) {
if(min >= max) {
throw new InvalidOperationException("Maximum value must be greater than minimum");
}
this.Maximum = max;
this.Minimum = min;
} }
/// <summary> /// <summary>
/// Email validator. /// Initializes a new instance of the <see cref="RangeAttribute"/> class.
/// Constructor that takes double minimum and maximum values.
/// </summary> /// </summary>
[AttributeUsage(AttributeTargets.Property)] /// <param name="min">The minimum value.</param>
public class EmailAttribute : MatchAttribute /// <param name="max">The maximum value.</param>
{ public RangeAttribute(Double min, Double max) {
private const string EmailRegExp = if(min >= max) {
@"^(?("")("".+?(?<!\\)""@)|(([0-9a-z]((\.(?!\.))|[-!#\$%&'\*\+/=\?\^`\{\}\|~\w])*)(?<=[0-9a-z])@))" + throw new InvalidOperationException("Maximum value must be greater than minimum");
@"(?(\[)(\[(\d{1,3}\.){3}\d{1,3}\])|(([0-9a-z][-0-9a-z]*[0-9a-z]*\.)+[a-z0-9][\-a-z0-9]{0,22}[a-z0-9]))$"; }
/// <summary> this.Maximum = max;
/// Initializes a new instance of the <see cref="EmailAttribute"/> class. this.Minimum = min;
/// </summary> }
/// <param name="errorMessage">The error message.</param>
public EmailAttribute(string errorMessage = null) /// <inheritdoc/>
: base(EmailRegExp, errorMessage ?? "String is not an email") public String ErrorMessage => "Value is not within the specified range";
{
} /// <summary>
/// Maximum value for the range.
/// </summary>
public IComparable Maximum {
get;
} }
/// <summary> /// <summary>
/// A not null validator. /// Minimum value for the range.
/// </summary> /// </summary>
[AttributeUsage(AttributeTargets.Property)] public IComparable Minimum {
public class NotNullAttribute : Attribute, IValidator get;
{
/// <inheritdoc/>
public string ErrorMessage => "Value is null";
/// <inheritdoc/>
public bool IsValid<T>(T value) => !Equals(default(T), value);
} }
/// <summary> /// <inheritdoc/>
/// A range constraint validator. public Boolean IsValid<T>(T value)
/// </summary> => value is IComparable comparable
[AttributeUsage(AttributeTargets.Property)] ? comparable.CompareTo(this.Minimum) >= 0 && comparable.CompareTo(this.Maximum) <= 0
public class RangeAttribute : Attribute, IValidator : throw new ArgumentException(nameof(value));
{ }
/// <summary>
/// Initializes a new instance of the <see cref="RangeAttribute"/> class.
/// Constructor that takes integer minimum and maximum values.
/// </summary>
/// <param name="min">The minimum value.</param>
/// <param name="max">The maximum value.</param>
public RangeAttribute(int min, int max)
{
if (min >= max)
throw new InvalidOperationException("Maximum value must be greater than minimum");
Maximum = max;
Minimum = min;
}
/// <summary>
/// Initializes a new instance of the <see cref="RangeAttribute"/> class.
/// Constructor that takes double minimum and maximum values.
/// </summary>
/// <param name="min">The minimum value.</param>
/// <param name="max">The maximum value.</param>
public RangeAttribute(double min, double max)
{
if (min >= max)
throw new InvalidOperationException("Maximum value must be greater than minimum");
Maximum = max;
Minimum = min;
}
/// <inheritdoc/>
public string ErrorMessage => "Value is not within the specified range";
/// <summary>
/// Maximum value for the range.
/// </summary>
public IComparable Maximum { get; }
/// <summary>
/// Minimum value for the range.
/// </summary>
public IComparable Minimum { get; }
/// <inheritdoc/>
public bool IsValid<T>(T value)
=> value is IComparable comparable
? comparable.CompareTo(Minimum) >= 0 && comparable.CompareTo(Maximum) <= 0
: throw new ArgumentException(nameof(value));
}
} }

View File

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

View File

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

View File

@ -1,57 +1,52 @@
namespace Unosquare.Swan.Components using System.Linq;
{ using System.Reflection;
using System.Linq; using Unosquare.Swan.Attributes;
using System.Reflection; using System;
using Attributes;
using System;
/// <summary> namespace Unosquare.Swan.Components {
/// Provides methods to parse command line arguments. /// <summary>
/// </summary> /// Provides methods to parse command line arguments.
public partial class ArgumentParser /// </summary>
{ 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[] GetProperties() => _properties?.Any() == true ? _properties : null; public PropertyInfo[] GetProperties() => this._properties?.Any() == true ? this._properties : null;
public object GetOptionsObject(T instance) public Object GetOptionsObject(T instance) {
{ this._properties = Runtime.PropertyTypeCache.RetrieveAllProperties<T>(true).ToArray();
_properties = Runtime.PropertyTypeCache.RetrieveAllProperties<T>(true).ToArray();
if (!_properties.Any(x => x.GetCustomAttributes(typeof(VerbOptionAttribute), false).Any())) if(!this._properties.Any(x => x.GetCustomAttributes(typeof(VerbOptionAttribute), false).Any())) {
return instance; return instance;
}
var selectedVerb = string.IsNullOrWhiteSpace(_selectedVerb) PropertyInfo selectedVerb = String.IsNullOrWhiteSpace(this._selectedVerb)
? null ? null
: _properties.FirstOrDefault(x => : this._properties.FirstOrDefault(x =>
Runtime.AttributeCache.RetrieveOne<VerbOptionAttribute>(x).Name.Equals(_selectedVerb)); Runtime.AttributeCache.RetrieveOne<VerbOptionAttribute>(x).Name.Equals(this._selectedVerb));
if (selectedVerb == null) return null; if(selectedVerb == null) {
return null;
}
var type = instance.GetType(); Type type = instance.GetType();
var verbProperty = type.GetProperty(selectedVerb.Name); PropertyInfo verbProperty = type.GetProperty(selectedVerb.Name);
if (verbProperty?.GetValue(instance) == null) if(verbProperty?.GetValue(instance) == null) {
{ Object propertyInstance = Activator.CreateInstance(selectedVerb.PropertyType);
var propertyInstance = Activator.CreateInstance(selectedVerb.PropertyType); verbProperty?.SetValue(instance, propertyInstance);
verbProperty?.SetValue(instance, propertyInstance); }
}
_properties = Runtime.PropertyTypeCache.RetrieveAllProperties(selectedVerb.PropertyType, true) this._properties = Runtime.PropertyTypeCache.RetrieveAllProperties(selectedVerb.PropertyType, true)
.ToArray(); .ToArray();
return verbProperty?.GetValue(instance); return verbProperty?.GetValue(instance);
} }
}
} }
}
} }

View File

@ -1,230 +1,228 @@
namespace Unosquare.Swan.Components using System;
{ using System.Collections.Generic;
using System; using Unosquare.Swan.Attributes;
using System.Collections.Generic; using System.Linq;
using Attributes;
using System.Linq; namespace Unosquare.Swan.Components {
/// <summary>
/// Provides methods to parse command line arguments.
/// Based on CommandLine (Copyright 2005-2015 Giacomo Stelluti Scala and Contributors.).
/// </summary>
/// <example>
/// The following example shows how to parse CLI arguments into objects.
/// <code>
/// class Example
/// {
/// using System;
/// using Unosquare.Swan;
/// using Unosquare.Swan.Attributes;
///
/// static void Main(string[] args)
/// {
/// // create an instance of the Options class
/// var options = new Options();
///
/// // parse the supplied command-line arguments into the options object
/// var res = Runtime.ArgumentParser.ParseArguments(args, options);
/// }
///
/// class Options
/// {
/// [ArgumentOption('v', "verbose", HelpText = "Set verbose mode.")]
/// public bool Verbose { get; set; }
///
/// [ArgumentOption('u', Required = true, HelpText = "Set user name.")]
/// public string Username { get; set; }
///
/// [ArgumentOption('n', "names", Separator = ',',
/// Required = true, HelpText = "A list of files separated by a comma")]
/// public string[] Files { get; set; }
///
/// [ArgumentOption('p', "port", DefaultValue = 22, HelpText = "Set port.")]
/// public int Port { get; set; }
///
/// [ArgumentOption("color", DefaultValue = ConsoleColor.Red,
/// HelpText = "Set a color.")]
/// public ConsoleColor Color { get; set; }
/// }
/// }
/// </code>
/// The following code describes how to parse CLI verbs.
/// <code>
/// class Example2
/// {
/// using Unosquare.Swan;
/// using Unosquare.Swan.Attributes;
///
/// static void Main(string[] args)
/// {
/// // create an instance of the VerbOptions class
/// var options = new VerbOptions();
///
/// // parse the supplied command-line arguments into the options object
/// var res = Runtime.ArgumentParser.ParseArguments(args, options);
///
/// // if there were no errors parsing
/// if (res)
/// {
/// if(options.Run != null)
/// {
/// // run verb was selected
/// }
///
/// if(options.Print != null)
/// {
/// // print verb was selected
/// }
/// }
///
/// // flush all error messages
/// Terminal.Flush();
/// }
///
/// class VerbOptions
/// {
/// [VerbOption("run", HelpText = "Run verb.")]
/// public RunVerbOption Run { get; set; }
///
/// [VerbOption("print", HelpText = "Print verb.")]
/// public PrintVerbOption Print { get; set; }
/// }
///
/// class RunVerbOption
/// {
/// [ArgumentOption('o', "outdir", HelpText = "Output directory",
/// DefaultValue = "", Required = false)]
/// public string OutDir { get; set; }
/// }
///
/// class PrintVerbOption
/// {
/// [ArgumentOption('t', "text", HelpText = "Text to print",
/// DefaultValue = "", Required = false)]
/// public string Text { get; set; }
/// }
/// }
/// </code>
/// </example>
public partial class ArgumentParser {
/// <summary>
/// Initializes a new instance of the <see cref="ArgumentParser"/> class.
/// </summary>
public ArgumentParser()
: this(new ArgumentParserSettings()) {
}
/// <summary> /// <summary>
/// Provides methods to parse command line arguments. /// Initializes a new instance of the <see cref="ArgumentParser" /> class,
/// Based on CommandLine (Copyright 2005-2015 Giacomo Stelluti Scala and Contributors.). /// configurable with <see cref="ArgumentParserSettings" /> using a delegate.
/// </summary> /// </summary>
/// <example> /// <param name="parseSettings">The parse settings.</param>
/// The following example shows how to parse CLI arguments into objects. public ArgumentParser(ArgumentParserSettings parseSettings) => this.Settings = parseSettings ?? throw new ArgumentNullException(nameof(parseSettings));
/// <code>
/// class Example
/// {
/// using System;
/// using Unosquare.Swan;
/// using Unosquare.Swan.Attributes;
///
/// static void Main(string[] args)
/// {
/// // create an instance of the Options class
/// var options = new Options();
///
/// // parse the supplied command-line arguments into the options object
/// var res = Runtime.ArgumentParser.ParseArguments(args, options);
/// }
///
/// class Options
/// {
/// [ArgumentOption('v', "verbose", HelpText = "Set verbose mode.")]
/// public bool Verbose { get; set; }
///
/// [ArgumentOption('u', Required = true, HelpText = "Set user name.")]
/// public string Username { get; set; }
///
/// [ArgumentOption('n', "names", Separator = ',',
/// Required = true, HelpText = "A list of files separated by a comma")]
/// public string[] Files { get; set; }
///
/// [ArgumentOption('p', "port", DefaultValue = 22, HelpText = "Set port.")]
/// public int Port { get; set; }
///
/// [ArgumentOption("color", DefaultValue = ConsoleColor.Red,
/// HelpText = "Set a color.")]
/// public ConsoleColor Color { get; set; }
/// }
/// }
/// </code>
/// The following code describes how to parse CLI verbs.
/// <code>
/// class Example2
/// {
/// using Unosquare.Swan;
/// using Unosquare.Swan.Attributes;
///
/// static void Main(string[] args)
/// {
/// // create an instance of the VerbOptions class
/// var options = new VerbOptions();
///
/// // parse the supplied command-line arguments into the options object
/// var res = Runtime.ArgumentParser.ParseArguments(args, options);
///
/// // if there were no errors parsing
/// if (res)
/// {
/// if(options.Run != null)
/// {
/// // run verb was selected
/// }
///
/// if(options.Print != null)
/// {
/// // print verb was selected
/// }
/// }
///
/// // flush all error messages
/// Terminal.Flush();
/// }
///
/// class VerbOptions
/// {
/// [VerbOption("run", HelpText = "Run verb.")]
/// public RunVerbOption Run { get; set; }
///
/// [VerbOption("print", HelpText = "Print verb.")]
/// public PrintVerbOption Print { get; set; }
/// }
///
/// class RunVerbOption
/// {
/// [ArgumentOption('o', "outdir", HelpText = "Output directory",
/// DefaultValue = "", Required = false)]
/// public string OutDir { get; set; }
/// }
///
/// class PrintVerbOption
/// {
/// [ArgumentOption('t', "text", HelpText = "Text to print",
/// DefaultValue = "", Required = false)]
/// public string Text { get; set; }
/// }
/// }
/// </code>
/// </example>
public partial class ArgumentParser
{
/// <summary>
/// Initializes a new instance of the <see cref="ArgumentParser"/> class.
/// </summary>
public ArgumentParser()
: this(new ArgumentParserSettings())
{
}
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ArgumentParser" /> class, /// Gets the instance that implements <see cref="ArgumentParserSettings" /> in use.
/// configurable with <see cref="ArgumentParserSettings" /> using a delegate. /// </summary>
/// </summary> /// <value>
/// <param name="parseSettings">The parse settings.</param> /// The settings.
public ArgumentParser(ArgumentParserSettings parseSettings) /// </value>
{ public ArgumentParserSettings Settings {
Settings = parseSettings ?? throw new ArgumentNullException(nameof(parseSettings)); get;
} }
/// <summary> /// <summary>
/// Gets the instance that implements <see cref="ArgumentParserSettings" /> in use. /// Parses a string array of command line arguments constructing values in an instance of type <typeparamref name="T" />.
/// </summary> /// </summary>
/// <value> /// <typeparam name="T">The type of the options.</typeparam>
/// The settings. /// <param name="args">The arguments.</param>
/// </value> /// <param name="instance">The instance.</param>
public ArgumentParserSettings Settings { get; } /// <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));
}
/// <summary> if(Equals(instance, default(T))) {
/// Parses a string array of command line arguments constructing values in an instance of type <typeparamref name="T" />. throw new ArgumentNullException(nameof(instance));
/// </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, T instance)
{
if (args == null)
throw new ArgumentNullException(nameof(args));
if (Equals(instance, default(T))) TypeResolver<T> typeResolver = new TypeResolver<T>(args.FirstOrDefault());
throw new ArgumentNullException(nameof(instance)); Object options = typeResolver.GetOptionsObject(instance);
var typeResolver = new TypeResolver<T>(args.FirstOrDefault()); if(options == null) {
var options = typeResolver.GetOptionsObject(instance); ReportUnknownVerb<T>();
return false;
}
if (options == null) System.Reflection.PropertyInfo[] properties = typeResolver.GetProperties();
{
ReportUnknownVerb<T>();
return false;
}
var properties = typeResolver.GetProperties(); if(properties == null) {
throw new InvalidOperationException($"Type {typeof(T).Name} is not valid");
}
if (properties == null) Validator validator = new Validator(properties, args, options, this.Settings);
throw new InvalidOperationException($"Type {typeof(T).Name} is not valid");
var validator = new Validator(properties, args, options, Settings); if(validator.IsValid()) {
return true;
}
if (validator.IsValid()) this.ReportIssues(validator);
return true; return false;
}
ReportIssues(validator); private static void ReportUnknownVerb<T>() {
return false; "No verb was specified".WriteLine(ConsoleColor.Red);
} "Valid verbs:".WriteLine(ConsoleColor.Cyan);
private static void ReportUnknownVerb<T>() Runtime.PropertyTypeCache.RetrieveAllProperties<T>(true)
{ .Select(x => Runtime.AttributeCache.RetrieveOne<VerbOptionAttribute>(x))
"No verb was specified".WriteLine(ConsoleColor.Red); .Where(x => x != null)
"Valid verbs:".WriteLine(ConsoleColor.Cyan); .ToList()
.ForEach(x => x.ToString().WriteLine(ConsoleColor.Cyan));
}
Runtime.PropertyTypeCache.RetrieveAllProperties<T>(true) private void ReportIssues(Validator validator) {
.Select(x => Runtime.AttributeCache.RetrieveOne<VerbOptionAttribute>(x))
.Where(x => x != null)
.ToList()
.ForEach(x => x.ToString().WriteLine(ConsoleColor.Cyan));
}
private void ReportIssues(Validator validator)
{
#if !NETSTANDARD1_3 #if !NETSTANDARD1_3
if (Settings.WriteBanner) if(this.Settings.WriteBanner) {
Runtime.WriteWelcomeBanner(); Runtime.WriteWelcomeBanner();
}
#endif #endif
var options = validator.GetPropertiesOptions(); IEnumerable<ArgumentOptionAttribute> options = validator.GetPropertiesOptions();
foreach (var option in options) foreach(ArgumentOptionAttribute option in options) {
{ String.Empty.WriteLine();
string.Empty.WriteLine();
// 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
: ", "; : ", ";
var defaultValue = option.DefaultValue == null ? string.Empty : $"(Default: {option.DefaultValue}) "; String defaultValue = option.DefaultValue == null ? String.Empty : $"(Default: {option.DefaultValue}) ";
$" {shortName}{comma}{longName}\t\t{defaultValue}{option.HelpText}".WriteLine(ConsoleColor.Cyan); $" {shortName}{comma}{longName}\t\t{defaultValue}{option.HelpText}".WriteLine(ConsoleColor.Cyan);
} }
string.Empty.WriteLine(); String.Empty.WriteLine();
" --help\t\tDisplay this help screen.".WriteLine(ConsoleColor.Cyan); " --help\t\tDisplay this help screen.".WriteLine(ConsoleColor.Cyan);
if (validator.UnknownList.Any()) if(validator.UnknownList.Any()) {
$"Unknown arguments: {string.Join(", ", validator.UnknownList)}".WriteLine(ConsoleColor.Red); $"Unknown arguments: {String.Join(", ", validator.UnknownList)}".WriteLine(ConsoleColor.Red);
}
if (validator.RequiredList.Any()) if(validator.RequiredList.Any()) {
$"Required arguments: {string.Join(", ", validator.RequiredList)}".WriteLine(ConsoleColor.Red); $"Required arguments: {String.Join(", ", validator.RequiredList)}".WriteLine(ConsoleColor.Red);
} }
} }
}
} }

View File

@ -1,53 +1,51 @@
namespace Unosquare.Swan.Components using System;
{
using System; namespace Unosquare.Swan.Components {
/// <summary>
/// Provides settings for <see cref="ArgumentParser"/>.
/// Based on CommandLine (Copyright 2005-2015 Giacomo Stelluti Scala and Contributors.).
/// </summary>
public class ArgumentParserSettings {
/// <summary>
/// Gets or sets a value indicating whether [write banner].
/// </summary>
/// <value>
/// <c>true</c> if [write banner]; otherwise, <c>false</c>.
/// </value>
public Boolean WriteBanner { get; set; } = true;
/// <summary> /// <summary>
/// Provides settings for <see cref="ArgumentParser"/>. /// Gets or sets a value indicating whether perform case sensitive comparisons.
/// Based on CommandLine (Copyright 2005-2015 Giacomo Stelluti Scala and Contributors.). /// Note that case insensitivity only applies to <i>parameters</i>, not the values
/// assigned to them (for example, enum parsing).
/// </summary> /// </summary>
public class ArgumentParserSettings /// <value>
{ /// <c>true</c> if [case sensitive]; otherwise, <c>false</c>.
/// <summary> /// </value>
/// Gets or sets a value indicating whether [write banner]. public Boolean CaseSensitive { get; set; } = false;
/// </summary>
/// <value>
/// <c>true</c> if [write banner]; otherwise, <c>false</c>.
/// </value>
public bool 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 of <i>values</i>.
/// Note that case insensitivity only applies to <i>parameters</i>, not the values /// Note that case insensitivity only applies to <i>values</i>, not the parameters.
/// assigned to them (for example, enum parsing). /// </summary>
/// </summary> /// <value>
/// <value> /// <c>true</c> if [case insensitive enum values]; otherwise, <c>false</c>.
/// <c>true</c> if [case sensitive]; otherwise, <c>false</c>. /// </value>
/// </value> public Boolean CaseInsensitiveEnumValues { get; set; } = true;
public bool 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 the parser shall move on to the next argument and ignore the given argument if it
/// Note that case insensitivity only applies to <i>values</i>, not the parameters. /// encounter an unknown arguments.
/// </summary> /// </summary>
/// <value> /// <value>
/// <c>true</c> if [case insensitive enum values]; otherwise, <c>false</c>. /// <c>true</c> to allow parsing the arguments with different class options that do not have all the arguments.
/// </value> /// </value>
public bool CaseInsensitiveEnumValues { get; set; } = true; /// <remarks>
/// 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.
/// </remarks>
public Boolean IgnoreUnknownArguments { get; set; } = true;
/// <summary> internal StringComparison NameComparer => this.CaseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase;
/// Gets or sets a value indicating whether the parser shall move on to the next argument and ignore the given argument if it }
/// encounter an unknown arguments.
/// </summary>
/// <value>
/// <c>true</c> to allow parsing the arguments with different class options that do not have all the arguments.
/// </value>
/// <remarks>
/// 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.
/// </remarks>
public bool IgnoreUnknownArguments { get; set; } = true;
internal StringComparison NameComparer => CaseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase;
}
} }

View File

@ -1,130 +1,122 @@
namespace Unosquare.Swan.Components using System;
{ using System.Collections.Generic;
using System; using System.Diagnostics;
using System.Collections.Generic; using System.Linq;
using System.Diagnostics; using System.Text;
using System.Linq;
using System.Text; namespace Unosquare.Swan.Components {
/// <summary>
/// A simple benchmarking class.
/// </summary>
/// <example>
/// The following code demonstrates how to create a simple benchmark.
/// <code>
/// namespace Examples.Benchmark.Simple
/// {
/// using Unosquare.Swan.Components;
///
/// public class SimpleBenchmark
/// {
/// public static void Main()
/// {
/// using (Benchmark.Start("Test"))
/// {
/// // do some logic in here
/// }
///
/// // dump results into a string
/// var results = Benchmark.Dump();
/// }
/// }
///
/// }
/// </code>
/// </example>
public static class Benchmark {
private static readonly Object SyncLock = new Object();
private static readonly Dictionary<String, List<TimeSpan>> Measures = new Dictionary<String, List<TimeSpan>>();
/// <summary> /// <summary>
/// A simple benchmarking class. /// Starts measuring with the given identifier.
/// </summary> /// </summary>
/// <example> /// <param name="identifier">The identifier.</param>
/// The following code demonstrates how to create a simple benchmark. /// <returns>A disposable object that when disposed, adds a benchmark result.</returns>
/// <code> public static IDisposable Start(String identifier) => new BenchmarkUnit(identifier);
/// namespace Examples.Benchmark.Simple
/// {
/// using Unosquare.Swan.Components;
///
/// public class SimpleBenchmark
/// {
/// public static void Main()
/// {
/// using (Benchmark.Start("Test"))
/// {
/// // do some logic in here
/// }
///
/// // dump results into a string
/// var results = Benchmark.Dump();
/// }
/// }
///
/// }
/// </code>
/// </example>
public static class Benchmark
{
private static readonly object SyncLock = new object();
private static readonly Dictionary<string, List<TimeSpan>> Measures = new Dictionary<string, List<TimeSpan>>();
/// <summary> /// <summary>
/// Starts measuring with the given identifier. /// Outputs the benchmark statistics.
/// </summary> /// </summary>
/// <param name="identifier">The identifier.</param> /// <returns>A string containing human-readable statistics.</returns>
/// <returns>A disposable object that when disposed, adds a benchmark result.</returns> public static String Dump() {
public static IDisposable Start(string identifier) => new BenchmarkUnit(identifier); StringBuilder builder = new StringBuilder();
/// <summary> lock(SyncLock) {
/// Outputs the benchmark statistics. foreach(KeyValuePair<String, List<TimeSpan>> kvp in Measures) {
/// </summary> _ = builder.Append($"BID: {kvp.Key,-30} | ")
/// <returns>A string containing human-readable statistics.</returns> .Append($"CNT: {kvp.Value.Count,6} | ")
public static string Dump() .Append($"AVG: {kvp.Value.Average(t => t.TotalMilliseconds),8:0.000} ms. | ")
{ .Append($"MAX: {kvp.Value.Max(t => t.TotalMilliseconds),8:0.000} ms. | ")
var builder = new StringBuilder(); .Append($"MIN: {kvp.Value.Min(t => t.TotalMilliseconds),8:0.000} ms. | ")
.Append(Environment.NewLine);
lock (SyncLock)
{
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);
}
}
return builder.ToString().TrimEnd();
} }
}
/// <summary> return builder.ToString().TrimEnd();
/// Adds the specified result to the given identifier.
/// </summary>
/// <param name="identifier">The identifier.</param>
/// <param name="elapsed">The elapsed.</param>
private static void Add(string identifier, TimeSpan elapsed)
{
lock (SyncLock)
{
if (Measures.ContainsKey(identifier) == false)
Measures[identifier] = new List<TimeSpan>(1024 * 1024);
Measures[identifier].Add(elapsed);
}
}
/// <summary>
/// Represents a disposable benchmark unit.
/// </summary>
/// <seealso cref="IDisposable" />
private sealed class BenchmarkUnit : IDisposable
{
private readonly string _identifier;
private bool _isDisposed; // To detect redundant calls
private Stopwatch _stopwatch = new Stopwatch();
/// <summary>
/// Initializes a new instance of the <see cref="BenchmarkUnit" /> class.
/// </summary>
/// <param name="identifier">The identifier.</param>
public BenchmarkUnit(string identifier)
{
_identifier = identifier;
_stopwatch.Start();
}
/// <inheritdoc />
public void Dispose() => Dispose(true);
/// <summary>
/// Releases unmanaged and - optionally - managed resources.
/// </summary>
/// <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)
{
if (_isDisposed) return;
if (alsoManaged)
{
Add(_identifier, _stopwatch.Elapsed);
_stopwatch?.Stop();
}
_stopwatch = null;
_isDisposed = true;
}
}
} }
/// <summary>
/// Adds the specified result to the given identifier.
/// </summary>
/// <param name="identifier">The identifier.</param>
/// <param name="elapsed">The elapsed.</param>
private static void Add(String identifier, TimeSpan elapsed) {
lock(SyncLock) {
if(Measures.ContainsKey(identifier) == false) {
Measures[identifier] = new List<TimeSpan>(1024 * 1024);
}
Measures[identifier].Add(elapsed);
}
}
/// <summary>
/// Represents a disposable benchmark unit.
/// </summary>
/// <seealso cref="IDisposable" />
private sealed class BenchmarkUnit : IDisposable {
private readonly String _identifier;
private Boolean _isDisposed; // To detect redundant calls
private Stopwatch _stopwatch = new Stopwatch();
/// <summary>
/// Initializes a new instance of the <see cref="BenchmarkUnit" /> class.
/// </summary>
/// <param name="identifier">The identifier.</param>
public BenchmarkUnit(String identifier) {
this._identifier = identifier;
this._stopwatch.Start();
}
/// <inheritdoc />
public void Dispose() => this.Dispose(true);
/// <summary>
/// Releases unmanaged and - optionally - managed resources.
/// </summary>
/// <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(Boolean alsoManaged) {
if(this._isDisposed) {
return;
}
if(alsoManaged) {
Add(this._identifier, this._stopwatch.Elapsed);
this._stopwatch?.Stop();
}
this._stopwatch = null;
this._isDisposed = true;
}
}
}
} }

View File

@ -1,44 +1,42 @@
namespace Unosquare.Swan.Components using System;
{ using System.Collections.Concurrent;
using System; using System.Collections.Generic;
using System.Collections.Concurrent; using System.Linq;
using System.Collections.Generic;
using System.Linq; namespace Unosquare.Swan.Components {
/// <summary>
/// A thread-safe collection cache repository for types.
/// </summary>
/// <typeparam name="TValue">The type of member to cache.</typeparam>
public class CollectionCacheRepository<TValue> {
private readonly Lazy<ConcurrentDictionary<Type, IEnumerable<TValue>>> _data =
new Lazy<ConcurrentDictionary<Type, IEnumerable<TValue>>>(() =>
new ConcurrentDictionary<Type, IEnumerable<TValue>>(), true);
/// <summary> /// <summary>
/// A thread-safe collection cache repository for types. /// Determines whether the cache contains the specified key.
/// </summary> /// </summary>
/// <typeparam name="TValue">The type of member to cache.</typeparam> /// <param name="key">The key.</param>
public class CollectionCacheRepository<TValue> /// <returns><c>true</c> if the cache contains the key, otherwise <c>false</c>.</returns>
{ public Boolean ContainsKey(Type key) => this._data.Value.ContainsKey(key);
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. /// Retrieves the properties stored for the specified type.
/// </summary> /// If the properties are not available, it calls the factory method to retrieve them
/// <param name="key">The key.</param> /// and returns them as an array of PropertyInfo.
/// <returns><c>true</c> if the cache contains the key, otherwise <c>false</c>.</returns> /// </summary>
public bool ContainsKey(Type key) => _data.Value.ContainsKey(key); /// <param name="key">The key.</param>
/// <param name="factory">The factory.</param>
/// <returns>
/// An array of the properties stored for the specified type.
/// </returns>
/// <exception cref="System.ArgumentNullException">type.</exception>
public IEnumerable<TValue> Retrieve(Type key, Func<Type, IEnumerable<TValue>> factory) {
if(factory == null) {
throw new ArgumentNullException(nameof(factory));
}
/// <summary> return this._data.Value.GetOrAdd(key, k => factory.Invoke(k).Where(item => item != null));
/// Retrieves the properties stored for the specified type.
/// If the properties are not available, it calls the factory method to retrieve them
/// and returns them as an array of PropertyInfo.
/// </summary>
/// <param name="key">The key.</param>
/// <param name="factory">The factory.</param>
/// <returns>
/// An array of the properties stored for the specified type.
/// </returns>
/// <exception cref="System.ArgumentNullException">type.</exception>
public IEnumerable<TValue> Retrieve(Type key, Func<Type, IEnumerable<TValue>> factory)
{
if (factory == null)
throw new ArgumentNullException(nameof(factory));
return _data.Value.GetOrAdd(key, k => factory.Invoke(k).Where(item => item != null));
}
} }
}
} }

View File

@ -1,171 +1,144 @@
namespace Unosquare.Swan.Components using System;
{ using System.Collections.Generic;
using System; using System.Linq;
using System.Collections.Generic; using Unosquare.Swan.Abstractions;
using System.Linq;
using Abstractions; namespace Unosquare.Swan.Components {
/// <summary>
/// Provide Enumerations helpers with internal cache.
/// </summary>
public class EnumHelper
: SingletonBase<CollectionCacheRepository<Tuple<String, Object>>> {
/// <summary>
/// Gets all the names and enumerators from a specific Enum type.
/// </summary>
/// <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>
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)));
/// <summary> /// <summary>
/// Provide Enumerations helpers with internal cache. /// Gets the cached items with the enum item value.
/// </summary> /// </summary>
public class EnumHelper /// <typeparam name="T">The type of enumeration.</typeparam>
: SingletonBase<CollectionCacheRepository<Tuple<string, object>>> /// <param name="humanize">if set to <c>true</c> [humanize].</param>
{ /// <returns>
/// <summary> /// A collection of Type/Tuple pairs
/// Gets all the names and enumerators from a specific Enum type. /// that represents items with the enum item value.
/// </summary> /// </returns>
/// <typeparam name="T">The type of the attribute to be retrieved.</typeparam> public static IEnumerable<Tuple<Int32, String>> GetItemsWithValue<T>(Boolean humanize = true)
/// <returns>A tuple of enumerator names and their value stored for the specified type.</returns> where T : struct, IConvertible => Retrieve<T>()
public static IEnumerable<Tuple<string, object>> Retrieve<T>() .Select(x => Tuple.Create((Int32)x.Item2, humanize ? x.Item1.Humanize() : x.Item1));
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 flag values.
/// </summary> /// </summary>
/// <typeparam name="T">The type of enumeration.</typeparam> /// <typeparam name="TEnum">The type of the enum.</typeparam>
/// <param name="humanize">if set to <c>true</c> [humanize].</param> /// <param name="value">The value.</param>
/// <returns> /// <param name="ignoreZero">if set to <c>true</c> [ignore zero].</param>
/// A collection of Type/Tuple pairs /// <returns>
/// that represents items with the enum item value. /// A list of values in the flag.
/// </returns> /// </returns>
public static IEnumerable<Tuple<int, string>> GetItemsWithValue<T>(bool humanize = true) public static IEnumerable<Int32> GetFlagValues<TEnum>(Int32 value, Boolean ignoreZero = false)
where T : struct, IConvertible where TEnum : struct, IConvertible => Retrieve<TEnum>()
{ .Select(x => (Int32)x.Item2)
return Retrieve<T>() .When(() => ignoreZero, q => q.Where(f => f != 0))
.Select(x => Tuple.Create((int) x.Item2, humanize ? x.Item1.Humanize() : x.Item1)); .Where(x => (x & value) == x);
}
/// <summary> /// <summary>
/// Gets the flag values. /// Gets the flag values.
/// </summary> /// </summary>
/// <typeparam name="TEnum">The type of the enum.</typeparam> /// <typeparam name="TEnum">The type of the enum.</typeparam>
/// <param name="value">The value.</param> /// <param name="value">The value.</param>
/// <param name="ignoreZero">if set to <c>true</c> [ignore zero].</param> /// <param name="ignoreZero">if set to <c>true</c> [ignore zero].</param>
/// <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<Int64> GetFlagValues<TEnum>(Int64 value, Boolean ignoreZero = false)
where TEnum : struct, IConvertible where TEnum : struct, IConvertible => Retrieve<TEnum>()
{ .Select(x => (Int64)x.Item2)
return Retrieve<TEnum>() .When(() => ignoreZero, q => q.Where(f => f != 0))
.Select(x => (int) x.Item2) .Where(x => (x & value) == x);
.When(() => ignoreZero, q => q.Where(f => f != 0))
.Where(x => (x & value) == x);
}
/// <summary> /// <summary>
/// Gets the flag values. /// Gets the flag values.
/// </summary> /// </summary>
/// <typeparam name="TEnum">The type of the enum.</typeparam> /// <typeparam name="TEnum">The type of the enum.</typeparam>
/// <param name="value">The value.</param> /// <param name="value">The value.</param>
/// <param name="ignoreZero">if set to <c>true</c> [ignore zero].</param> /// <param name="ignoreZero">if set to <c>true</c> [ignore zero].</param>
/// <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<Byte> GetFlagValues<TEnum>(Byte value, Boolean ignoreZero = false)
where TEnum : struct, IConvertible where TEnum : struct, IConvertible => Retrieve<TEnum>()
{ .Select(x => (Byte)x.Item2)
return Retrieve<TEnum>() .When(() => ignoreZero, q => q.Where(f => f != 0))
.Select(x => (long) x.Item2) .Where(x => (x & value) == x);
.When(() => ignoreZero, q => q.Where(f => f != 0))
.Where(x => (x & value) == x);
}
/// <summary> /// <summary>
/// Gets the flag values. /// Gets the flag names.
/// </summary> /// </summary>
/// <typeparam name="TEnum">The type of the enum.</typeparam> /// <typeparam name="TEnum">The type of the enum.</typeparam>
/// <param name="value">The value.</param> /// <param name="value">the value.</param>
/// <param name="ignoreZero">if set to <c>true</c> [ignore zero].</param> /// <param name="ignoreZero">if set to <c>true</c> [ignore zero].</param>
/// <returns> /// <param name="humanize">if set to <c>true</c> [humanize].</param>
/// A list of values in the flag. /// <returns>
/// </returns> /// A list of flag names.
public static IEnumerable<byte> GetFlagValues<TEnum>(byte value, bool ignoreZero = false) /// </returns>
where TEnum : struct, IConvertible public static IEnumerable<String> GetFlagNames<TEnum>(Int32 value, Boolean ignoreZero = false, Boolean humanize = true)
{ where TEnum : struct, IConvertible => Retrieve<TEnum>()
return Retrieve<TEnum>() .When(() => ignoreZero, q => q.Where(f => (Int32)f.Item2 != 0))
.Select(x => (byte) x.Item2) .Where(x => ((Int32)x.Item2 & value) == (Int32)x.Item2)
.When(() => ignoreZero, q => q.Where(f => f != 0)) .Select(x => humanize ? x.Item1.Humanize() : x.Item1);
.Where(x => (x & value) == x);
}
/// <summary> /// <summary>
/// Gets the flag names. /// Gets the flag names.
/// </summary> /// </summary>
/// <typeparam name="TEnum">The type of the enum.</typeparam> /// <typeparam name="TEnum">The type of the enum.</typeparam>
/// <param name="value">the value.</param> /// <param name="value">The value.</param>
/// <param name="ignoreZero">if set to <c>true</c> [ignore zero].</param> /// <param name="ignoreZero">if set to <c>true</c> [ignore zero].</param>
/// <param name="humanize">if set to <c>true</c> [humanize].</param> /// <param name="humanize">if set to <c>true</c> [humanize].</param>
/// <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>(Int64 value, Boolean ignoreZero = false, Boolean humanize = true)
where TEnum : struct, IConvertible where TEnum : struct, IConvertible => Retrieve<TEnum>()
{ .When(() => ignoreZero, q => q.Where(f => (Int64)f.Item2 != 0))
return Retrieve<TEnum>() .Where(x => ((Int64)x.Item2 & value) == (Int64)x.Item2)
.When(() => ignoreZero, q => q.Where(f => (int) f.Item2 != 0)) .Select(x => humanize ? x.Item1.Humanize() : x.Item1);
.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.
/// </summary> /// </summary>
/// <typeparam name="TEnum">The type of the enum.</typeparam> /// <typeparam name="TEnum">The type of the enum.</typeparam>
/// <param name="value">The value.</param> /// <param name="value">The value.</param>
/// <param name="ignoreZero">if set to <c>true</c> [ignore zero].</param> /// <param name="ignoreZero">if set to <c>true</c> [ignore zero].</param>
/// <param name="humanize">if set to <c>true</c> [humanize].</param> /// <param name="humanize">if set to <c>true</c> [humanize].</param>
/// <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>(Byte value, Boolean ignoreZero = false, Boolean humanize = true)
where TEnum : struct, IConvertible where TEnum : struct, IConvertible => Retrieve<TEnum>()
{ .When(() => ignoreZero, q => q.Where(f => (Byte)f.Item2 != 0))
return Retrieve<TEnum>() .Where(x => ((Byte)x.Item2 & value) == (Byte)x.Item2)
.When(() => ignoreZero, q => q.Where(f => (long) f.Item2 != 0)) .Select(x => humanize ? x.Item1.Humanize() : x.Item1);
.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 cached items with the enum item index.
/// </summary> /// </summary>
/// <typeparam name="TEnum">The type of the enum.</typeparam> /// <typeparam name="T">The type of enumeration.</typeparam>
/// <param name="value">The value.</param> /// <param name="humanize">if set to <c>true</c> [humanize].</param>
/// <param name="ignoreZero">if set to <c>true</c> [ignore zero].</param> /// <returns>
/// <param name="humanize">if set to <c>true</c> [humanize].</param> /// A collection of Type/Tuple pairs that represents items with the enum item value.
/// <returns> /// </returns>
/// A list of flag names. public static IEnumerable<Tuple<Int32, String>> GetItemsWithIndex<T>(Boolean humanize = true)
/// </returns> where T : struct, IConvertible {
public static IEnumerable<string> GetFlagNames<TEnum>(byte value, bool ignoreZero = false, bool humanize = true) Int32 i = 0;
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> return Retrieve<T>()
/// Gets the cached items with the enum item index. .Select(x => Tuple.Create(i++, humanize ? x.Item1.Humanize() : x.Item1));
/// </summary>
/// <typeparam name="T">The type of enumeration.</typeparam>
/// <param name="humanize">if set to <c>true</c> [humanize].</param>
/// <returns>
/// A collection of Type/Tuple pairs that represents items with the enum item value.
/// </returns>
public static IEnumerable<Tuple<int, string>> GetItemsWithIndex<T>(bool humanize = true)
where T : struct, IConvertible
{
var i = 0;
return Retrieve<T>()
.Select(x => Tuple.Create(i++, humanize ? x.Item1.Humanize() : x.Item1));
}
} }
}
} }

View File

@ -1,193 +1,183 @@
namespace Unosquare.Swan.Components using System;
{ using System.Collections;
using System; using System.Collections.Generic;
using System.Collections; using System.Linq;
using System.Collections.Generic; using System.Reflection;
using System.Linq;
using System.Reflection; namespace Unosquare.Swan.Components {
/// <summary>
/// Represents a quick object comparer using the public properties of an object
/// or the public members in a structure.
/// </summary>
public static class ObjectComparer {
/// <summary>
/// Compare if two variables of the same type are equal.
/// </summary>
/// <typeparam name="T">The type of objects to compare.</typeparam>
/// <param name="left">The left.</param>
/// <param name="right">The right.</param>
/// <returns><c>true</c> if the variables are equal; otherwise, <c>false</c>.</returns>
public static Boolean AreEqual<T>(T left, T right) => AreEqual(left, right, typeof(T));
/// <summary> /// <summary>
/// Represents a quick object comparer using the public properties of an object /// Compare if two variables of the same type are equal.
/// or the public members in a structure.
/// </summary> /// </summary>
public static class ObjectComparer /// <param name="left">The left.</param>
{ /// <param name="right">The right.</param>
/// <summary> /// <param name="targetType">Type of the target.</param>
/// Compare if two variables of the same type are equal. /// <returns>
/// </summary> /// <c>true</c> if the variables are equal; otherwise, <c>false</c>.
/// <typeparam name="T">The type of objects to compare.</typeparam> /// </returns>
/// <param name="left">The left.</param> /// <exception cref="ArgumentNullException">targetType.</exception>
/// <param name="right">The right.</param> public static Boolean AreEqual(Object left, Object right, Type targetType) {
/// <returns><c>true</c> if the variables are equal; otherwise, <c>false</c>.</returns> if(targetType == null) {
public static bool AreEqual<T>(T left, T right) => AreEqual(left, right, typeof(T)); throw new ArgumentNullException(nameof(targetType));
}
/// <summary> return Definitions.BasicTypesInfo.ContainsKey(targetType)
/// Compare if two variables of the same type are equal. ? Equals(left, right)
/// </summary> : targetType.IsValueType() || targetType.IsArray
/// <param name="left">The left.</param> ? AreStructsEqual(left, right, targetType)
/// <param name="right">The right.</param> : AreObjectsEqual(left, right, targetType);
/// <param name="targetType">Type of the target.</param> }
/// <returns>
/// <c>true</c> if the variables are equal; otherwise, <c>false</c>.
/// </returns>
/// <exception cref="ArgumentNullException">targetType.</exception>
public static bool AreEqual(object left, object right, Type targetType)
{
if (targetType == null)
throw new ArgumentNullException(nameof(targetType));
if (Definitions.BasicTypesInfo.ContainsKey(targetType)) /// <summary>
return Equals(left, right); /// Compare if two objects of the same type are equal.
/// </summary>
/// <typeparam name="T">The type of objects to compare.</typeparam>
/// <param name="left">The left.</param>
/// <param name="right">The right.</param>
/// <returns><c>true</c> if the objects are equal; otherwise, <c>false</c>.</returns>
public static Boolean AreObjectsEqual<T>(T left, T right)
where T : class => AreObjectsEqual(left, right, typeof(T));
if (targetType.IsValueType() || targetType.IsArray) /// <summary>
return AreStructsEqual(left, right, targetType); /// Compare if two objects of the same type are equal.
/// </summary>
/// <param name="left">The left.</param>
/// <param name="right">The right.</param>
/// <param name="targetType">Type of the target.</param>
/// <returns><c>true</c> if the objects are equal; otherwise, <c>false</c>.</returns>
/// <exception cref="ArgumentNullException">targetType.</exception>
public static Boolean AreObjectsEqual(Object left, Object right, Type targetType) {
if(targetType == null) {
throw new ArgumentNullException(nameof(targetType));
}
return AreObjectsEqual(left, right, targetType); PropertyInfo[] properties = Runtime.PropertyTypeCache.RetrieveAllProperties(targetType).ToArray();
foreach(PropertyInfo propertyTarget in properties) {
Func<Object, Object> targetPropertyGetMethod = propertyTarget.GetCacheGetMethod();
if(propertyTarget.PropertyType.IsArray) {
IEnumerable leftObj = targetPropertyGetMethod(left) as IEnumerable;
IEnumerable rightObj = targetPropertyGetMethod(right) as IEnumerable;
if(!AreEnumerationsEquals(leftObj, rightObj)) {
return false;
}
} else {
if(!Equals(targetPropertyGetMethod(left), targetPropertyGetMethod(right))) {
return false;
}
} }
}
/// <summary> return true;
/// Compare if two objects of the same type are equal. }
/// </summary>
/// <typeparam name="T">The type of objects to compare.</typeparam>
/// <param name="left">The left.</param>
/// <param name="right">The right.</param>
/// <returns><c>true</c> if the objects are equal; otherwise, <c>false</c>.</returns>
public static bool AreObjectsEqual<T>(T left, T right)
where T : class
{
return AreObjectsEqual(left, right, typeof(T));
}
/// <summary> /// <summary>
/// Compare if two objects of the same type are equal. /// Compare if two structures of the same type are equal.
/// </summary> /// </summary>
/// <param name="left">The left.</param> /// <typeparam name="T">The type of structs to compare.</typeparam>
/// <param name="right">The right.</param> /// <param name="left">The left.</param>
/// <param name="targetType">Type of the target.</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 structs are equal; otherwise, <c>false</c>.</returns>
/// <exception cref="ArgumentNullException">targetType.</exception> public static Boolean AreStructsEqual<T>(T left, T right)
public static bool AreObjectsEqual(object left, object right, Type targetType) where T : struct => AreStructsEqual(left, right, typeof(T));
{
if (targetType == null)
throw new ArgumentNullException(nameof(targetType));
var properties = Runtime.PropertyTypeCache.RetrieveAllProperties(targetType).ToArray(); /// <summary>
/// Compare if two structures of the same type are equal.
/// </summary>
/// <param name="left">The left.</param>
/// <param name="right">The right.</param>
/// <param name="targetType">Type of the target.</param>
/// <returns>
/// <c>true</c> if the structs are equal; otherwise, <c>false</c>.
/// </returns>
/// <exception cref="ArgumentNullException">targetType.</exception>
public static Boolean AreStructsEqual(Object left, Object right, Type targetType) {
if(targetType == null) {
throw new ArgumentNullException(nameof(targetType));
}
foreach (var propertyTarget in properties) IEnumerable<MemberInfo> fields = new List<MemberInfo>(Runtime.FieldTypeCache.RetrieveAllFields(targetType))
{
var targetPropertyGetMethod = propertyTarget.GetCacheGetMethod();
if (propertyTarget.PropertyType.IsArray)
{
var leftObj = targetPropertyGetMethod(left) as IEnumerable;
var rightObj = targetPropertyGetMethod(right) as IEnumerable;
if (!AreEnumerationsEquals(leftObj, rightObj))
return false;
}
else
{
if (!Equals(targetPropertyGetMethod(left), targetPropertyGetMethod(right)))
return false;
}
}
return true;
}
/// <summary>
/// Compare if two structures of the same type are equal.
/// </summary>
/// <typeparam name="T">The type of structs to compare.</typeparam>
/// <param name="left">The left.</param>
/// <param name="right">The right.</param>
/// <returns><c>true</c> if the structs are equal; otherwise, <c>false</c>.</returns>
public static bool AreStructsEqual<T>(T left, T right)
where T : struct
{
return AreStructsEqual(left, right, typeof(T));
}
/// <summary>
/// Compare if two structures of the same type are equal.
/// </summary>
/// <param name="left">The left.</param>
/// <param name="right">The right.</param>
/// <param name="targetType">Type of the target.</param>
/// <returns>
/// <c>true</c> if the structs are equal; otherwise, <c>false</c>.
/// </returns>
/// <exception cref="ArgumentNullException">targetType.</exception>
public static bool AreStructsEqual(object left, object right, Type targetType)
{
if (targetType == null)
throw new ArgumentNullException(nameof(targetType));
var fields = new List<MemberInfo>(Runtime.FieldTypeCache.RetrieveAllFields(targetType))
.Union(Runtime.PropertyTypeCache.RetrieveAllProperties(targetType)); .Union(Runtime.PropertyTypeCache.RetrieveAllProperties(targetType));
foreach (var targetMember in fields) foreach(MemberInfo targetMember in fields) {
{ switch(targetMember) {
switch (targetMember) case FieldInfo field:
{ if(Equals(field.GetValue(left), field.GetValue(right)) == false) {
case FieldInfo field: return false;
if (Equals(field.GetValue(left), field.GetValue(right)) == false)
return false;
break;
case PropertyInfo property:
var targetPropertyGetMethod = property.GetCacheGetMethod();
if (targetPropertyGetMethod != null &&
!Equals(targetPropertyGetMethod(left), targetPropertyGetMethod(right)))
return false;
break;
}
} }
return true; break;
} case PropertyInfo property:
Func<Object, Object> targetPropertyGetMethod = property.GetCacheGetMethod();
/// <summary> if(targetPropertyGetMethod != null &&
/// Compare if two enumerables are equal. !Equals(targetPropertyGetMethod(left), targetPropertyGetMethod(right))) {
/// </summary> return false;
/// <typeparam name="T">The type of enums to compare.</typeparam>
/// <param name="left">The left.</param>
/// <param name="right">The right.</param>
/// <returns>
/// True if two specified types are equal; otherwise, false.
/// </returns>
/// <exception cref="ArgumentNullException">
/// left
/// or
/// right.
/// </exception>
public static bool AreEnumerationsEquals<T>(T left, T right)
where T : IEnumerable
{
if (Equals(left, default(T)))
throw new ArgumentNullException(nameof(left));
if (Equals(right, default(T)))
throw new ArgumentNullException(nameof(right));
var leftEnumerable = left.Cast<object>().ToArray();
var rightEnumerable = right.Cast<object>().ToArray();
if (leftEnumerable.Length != rightEnumerable.Length)
return false;
for (var i = 0; i < leftEnumerable.Length; i++)
{
var leftEl = leftEnumerable[i];
var rightEl = rightEnumerable[i];
if (!AreEqual(leftEl, rightEl, leftEl.GetType()))
{
return false;
}
} }
return true; break;
} }
}
return true;
} }
/// <summary>
/// Compare if two enumerables are equal.
/// </summary>
/// <typeparam name="T">The type of enums to compare.</typeparam>
/// <param name="left">The left.</param>
/// <param name="right">The right.</param>
/// <returns>
/// True if two specified types are equal; otherwise, false.
/// </returns>
/// <exception cref="ArgumentNullException">
/// left
/// or
/// right.
/// </exception>
public static Boolean AreEnumerationsEquals<T>(T left, T right)
where T : IEnumerable {
if(Equals(left, default(T))) {
throw new ArgumentNullException(nameof(left));
}
if(Equals(right, default(T))) {
throw new ArgumentNullException(nameof(right));
}
Object[] leftEnumerable = left.Cast<Object>().ToArray();
Object[] rightEnumerable = right.Cast<Object>().ToArray();
if(leftEnumerable.Length != rightEnumerable.Length) {
return false;
}
for(Int32 i = 0; i < leftEnumerable.Length; i++) {
Object leftEl = leftEnumerable[i];
Object rightEl = rightEnumerable[i];
if(!AreEqual(leftEl, rightEl, leftEl.GetType())) {
return false;
}
}
return true;
}
}
} }

View File

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

View File

@ -1,411 +1,385 @@
namespace Unosquare.Swan.Components using System;
{ using System.Collections;
using System; using System.Collections.Generic;
using System.Collections; using System.Linq;
using System.Collections.Generic; using System.Reflection;
using System.Linq; using Unosquare.Swan.Abstractions;
using System.Reflection;
using Abstractions; namespace Unosquare.Swan.Components {
/// <summary>
/// Represents an AutoMapper-like object to map from one object type
/// to another using defined properties map or using the default behaviour
/// to copy same named properties from one object to another.
///
/// The extension methods like CopyPropertiesTo use the default behaviour.
/// </summary>
/// <example>
/// The following code explains how to map an object's properties into an instance of type T.
/// <code>
/// using Unosquare.Swan
///
/// class Example
/// {
/// class Person
/// {
/// public string Name { get; set; }
/// public int Age { get; set; }
/// }
///
/// static void Main()
/// {
/// var obj = new { Name = "Søren", Age = 42 };
///
/// var person = Runtime.ObjectMapper.Map&lt;Person&gt;(obj);
/// }
/// }
/// </code>
/// The following code explains how to explicitly map certain properties.
/// <code>
/// using Unosquare.Swan
///
/// class Example
/// {
/// class User
/// {
/// public string Name { get; set; }
/// public Role Role { get; set; }
/// }
///
/// public class Role
/// {
/// public string Name { get; set; }
/// }
///
/// class UserDto
/// {
/// public string Name { get; set; }
/// public string Role { get; set; }
/// }
///
/// static void Main()
/// {
/// // create a User object
/// var person =
/// new User { Name = "Phillip", Role = new Role { Name = "Admin" } };
///
/// // create an Object Mapper
/// var mapper = new ObjectMapper();
///
/// // map the User's Role.Name to UserDto's Role
/// mapper.CreateMap&lt;User, UserDto&gt;()
/// .MapProperty(d => d.Role, x => x.Role.Name);
///
/// // apply the previous map and retrieve a UserDto object
/// var destination = mapper.Map&lt;UserDto&gt;(person);
/// }
/// }
/// </code>
/// </example>
public class ObjectMapper {
private readonly List<IObjectMap> _maps = new List<IObjectMap>();
/// <summary> /// <summary>
/// Represents an AutoMapper-like object to map from one object type /// Copies the specified source.
/// to another using defined properties map or using the default behaviour
/// to copy same named properties from one object to another.
///
/// The extension methods like CopyPropertiesTo use the default behaviour.
/// </summary> /// </summary>
/// <example> /// <param name="source">The source.</param>
/// The following code explains how to map an object's properties into an instance of type T. /// <param name="target">The target.</param>
/// <code> /// <param name="propertiesToCopy">The properties to copy.</param>
/// using Unosquare.Swan /// <param name="ignoreProperties">The ignore properties.</param>
/// /// <returns>
/// class Example /// Copied properties count.
/// { /// </returns>
/// class Person /// <exception cref="ArgumentNullException">
/// { /// source
/// public string Name { get; set; } /// or
/// public int Age { get; set; } /// target.
/// } /// </exception>
/// public static Int32 Copy(
/// static void Main() Object source,
/// { Object target,
/// var obj = new { Name = "Søren", Age = 42 }; String[] propertiesToCopy = null,
/// String[] ignoreProperties = null) {
/// var person = Runtime.ObjectMapper.Map&lt;Person&gt;(obj); if(source == null) {
/// } throw new ArgumentNullException(nameof(source));
/// } }
/// </code>
/// The following code explains how to explicitly map certain properties.
/// <code>
/// using Unosquare.Swan
///
/// class Example
/// {
/// class User
/// {
/// public string Name { get; set; }
/// public Role Role { get; set; }
/// }
///
/// public class Role
/// {
/// public string Name { get; set; }
/// }
///
/// class UserDto
/// {
/// public string Name { get; set; }
/// public string Role { get; set; }
/// }
///
/// static void Main()
/// {
/// // create a User object
/// var person =
/// new User { Name = "Phillip", Role = new Role { Name = "Admin" } };
///
/// // create an Object Mapper
/// var mapper = new ObjectMapper();
///
/// // map the User's Role.Name to UserDto's Role
/// mapper.CreateMap&lt;User, UserDto&gt;()
/// .MapProperty(d => d.Role, x => x.Role.Name);
///
/// // apply the previous map and retrieve a UserDto object
/// var destination = mapper.Map&lt;UserDto&gt;(person);
/// }
/// }
/// </code>
/// </example>
public class ObjectMapper
{
private readonly List<IObjectMap> _maps = new List<IObjectMap>();
/// <summary> if(target == null) {
/// Copies the specified source. throw new ArgumentNullException(nameof(target));
/// </summary> }
/// <param name="source">The source.</param>
/// <param name="target">The target.</param>
/// <param name="propertiesToCopy">The properties to copy.</param>
/// <param name="ignoreProperties">The ignore properties.</param>
/// <returns>
/// Copied properties count.
/// </returns>
/// <exception cref="ArgumentNullException">
/// source
/// or
/// target.
/// </exception>
public static int Copy(
object source,
object target,
string[] propertiesToCopy = null,
string[] ignoreProperties = null)
{
if (source == null)
throw new ArgumentNullException(nameof(source));
if (target == null) return Copy(
throw new ArgumentNullException(nameof(target));
return Copy(
target, target,
propertiesToCopy, propertiesToCopy,
ignoreProperties, ignoreProperties,
GetSourceMap(source)); GetSourceMap(source));
} }
/// <summary> /// <summary>
/// Copies the specified source. /// Copies the specified source.
/// </summary> /// </summary>
/// <param name="source">The source.</param> /// <param name="source">The source.</param>
/// <param name="target">The target.</param> /// <param name="target">The target.</param>
/// <param name="propertiesToCopy">The properties to copy.</param> /// <param name="propertiesToCopy">The properties to copy.</param>
/// <param name="ignoreProperties">The ignore properties.</param> /// <param name="ignoreProperties">The ignore properties.</param>
/// <returns> /// <returns>
/// Copied properties count. /// Copied properties count.
/// </returns> /// </returns>
/// <exception cref="ArgumentNullException"> /// <exception cref="ArgumentNullException">
/// source /// source
/// or /// or
/// target. /// target.
/// </exception> /// </exception>
public static int Copy( public static Int32 Copy(
IDictionary<string, object> source, IDictionary<String, Object> source,
object target, Object target,
string[] propertiesToCopy = null, String[] propertiesToCopy = null,
string[] ignoreProperties = null) String[] ignoreProperties = null) {
{ if(source == null) {
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 Copy( return Copy(
target, target,
propertiesToCopy, propertiesToCopy,
ignoreProperties, ignoreProperties,
source.ToDictionary( source.ToDictionary(
x => x.Key.ToLowerInvariant(), x => x.Key.ToLowerInvariant(),
x => new TypeValuePair(typeof(object), x.Value))); x => new TypeValuePair(typeof(Object), x.Value)));
}
/// <summary>
/// Creates the map.
/// </summary>
/// <typeparam name="TSource">The type of the source.</typeparam>
/// <typeparam name="TDestination">The type of the destination.</typeparam>
/// <returns>
/// An object map representation of type of the destination property
/// and type of the source property.
/// </returns>
/// <exception cref="System.InvalidOperationException">
/// You can't create an existing map
/// or
/// Types doesn't match.
/// </exception>
public ObjectMap<TSource, TDestination> CreateMap<TSource, TDestination>()
{
if (_maps.Any(x => x.SourceType == typeof(TSource) && x.DestinationType == typeof(TDestination)))
{
throw new InvalidOperationException("You can't create an existing map");
}
var sourceType = Runtime.PropertyTypeCache.RetrieveAllProperties<TSource>(true);
var destinationType = Runtime.PropertyTypeCache.RetrieveAllProperties<TDestination>(true);
var intersect = sourceType.Intersect(destinationType, new PropertyInfoComparer()).ToArray();
if (intersect.Any() == false)
{
throw new InvalidOperationException("Types doesn't match");
}
var map = new ObjectMap<TSource, TDestination>(intersect);
_maps.Add(map);
return map;
}
/// <summary>
/// Maps the specified source.
/// </summary>
/// <typeparam name="TDestination">The type of the destination.</typeparam>
/// <param name="source">The source.</param>
/// <param name="autoResolve">if set to <c>true</c> [automatic resolve].</param>
/// <returns>
/// A new instance of the map.
/// </returns>
/// <exception cref="ArgumentNullException">source.</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)
{
if (source == null)
{
throw new ArgumentNullException(nameof(source));
}
var destination = Activator.CreateInstance<TDestination>();
var map = _maps
.FirstOrDefault(x => x.SourceType == source.GetType() && x.DestinationType == typeof(TDestination));
if (map != null)
{
foreach (var property in map.Map)
{
var finalSource = property.Value.Aggregate(source,
(current, sourceProperty) => sourceProperty.GetValue(current));
property.Key.SetValue(destination, finalSource);
}
}
else
{
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
Copy(source, destination);
}
return destination;
}
private static int Copy(
object target,
IEnumerable<string> propertiesToCopy,
IEnumerable<string> ignoreProperties,
Dictionary<string, TypeValuePair> sourceProperties)
{
// Filter properties
var requiredProperties = propertiesToCopy?
.Where(p => !string.IsNullOrWhiteSpace(p))
.Select(p => p.ToLowerInvariant());
var ignoredProperties = ignoreProperties?
.Where(p => !string.IsNullOrWhiteSpace(p))
.Select(p => p.ToLowerInvariant());
var properties = Runtime.PropertyTypeCache
.RetrieveFilteredProperties(target.GetType(), true, x => x.CanWrite);
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, target) ? 1 : 0);
}
private static bool TrySetValue(KeyValuePair<PropertyInfo, TypeValuePair> property, object target)
{
try
{
SetValue(property, target);
return true;
}
catch
{
// swallow
}
return false;
}
private static void SetValue(KeyValuePair<PropertyInfo, TypeValuePair> property, object target)
{
if (property.Value.Type.GetTypeInfo().IsEnum)
{
property.Key.SetValue(target,
Enum.ToObject(property.Key.PropertyType, property.Value.Value));
return;
}
if (!property.Value.Type.IsValueType() && property.Key.PropertyType == property.Value.Type)
{
property.Key.SetValue(target, GetValue(property.Value.Value, property.Key.PropertyType));
return;
}
if (property.Key.PropertyType == typeof(bool))
{
property.Key.SetValue(target,
Convert.ToBoolean(property.Value.Value));
return;
}
property.Key.TrySetBasicType(property.Value.Value, target);
}
private static object GetValue(object source, Type targetType)
{
if (source == null)
return null;
object target = null;
source.CreateTarget(targetType, false, ref target);
switch (source)
{
case string _:
target = source;
break;
case IList sourceList when target is Array targetArray:
for (var i = 0; i < sourceList.Count; i++)
{
try
{
targetArray.SetValue(
sourceList[i].GetType().IsValueType()
? sourceList[i]
: sourceList[i].CopyPropertiesToNew<object>(), i);
}
catch
{
// ignored
}
}
break;
case IList sourceList when target is IList targetList:
var addMethod = targetType.GetMethods()
.FirstOrDefault(
m => m.Name.Equals(Formatters.Json.AddMethodName) && m.IsPublic &&
m.GetParameters().Length == 1);
if (addMethod == null) return target;
foreach (var item in sourceList)
{
try
{
targetList.Add(item.GetType().IsValueType()
? item
: item.CopyPropertiesToNew<object>());
}
catch
{
// ignored
}
}
break;
default:
source.CopyPropertiesTo(target);
break;
}
return target;
}
private static Dictionary<string, TypeValuePair> GetSourceMap(object source)
{
// select distinct properties because they can be duplicated by inheritance
var sourceProperties = Runtime.PropertyTypeCache
.RetrieveFilteredProperties(source.GetType(), true, x => x.CanRead)
.ToArray();
return sourceProperties
.Select(x => x.Name)
.Distinct()
.ToDictionary(
x => x.ToLowerInvariant(),
x => new TypeValuePair(sourceProperties.First(y => y.Name == x).PropertyType,
sourceProperties.First(y => y.Name == x).GetValue(source)));
}
internal class TypeValuePair
{
public TypeValuePair(Type type, object value)
{
Type = type;
Value = value;
}
public Type Type { get; }
public object Value { get; }
}
internal class PropertyInfoComparer : IEqualityComparer<PropertyInfo>
{
public bool Equals(PropertyInfo x, PropertyInfo y)
=> x != null && y != null && x.Name == y.Name && x.PropertyType == y.PropertyType;
public int GetHashCode(PropertyInfo obj)
=> obj.Name.GetHashCode() + obj.PropertyType.Name.GetHashCode();
}
} }
/// <summary>
/// Creates the map.
/// </summary>
/// <typeparam name="TSource">The type of the source.</typeparam>
/// <typeparam name="TDestination">The type of the destination.</typeparam>
/// <returns>
/// An object map representation of type of the destination property
/// and type of the source property.
/// </returns>
/// <exception cref="System.InvalidOperationException">
/// You can't create an existing map
/// or
/// Types doesn't match.
/// </exception>
public ObjectMap<TSource, TDestination> CreateMap<TSource, TDestination>() {
if(this._maps.Any(x => x.SourceType == typeof(TSource) && x.DestinationType == typeof(TDestination))) {
throw new InvalidOperationException("You can't create an existing map");
}
IEnumerable<PropertyInfo> sourceType = Runtime.PropertyTypeCache.RetrieveAllProperties<TSource>(true);
IEnumerable<PropertyInfo> destinationType = Runtime.PropertyTypeCache.RetrieveAllProperties<TDestination>(true);
PropertyInfo[] intersect = sourceType.Intersect(destinationType, new PropertyInfoComparer()).ToArray();
if(intersect.Any() == false) {
throw new InvalidOperationException("Types doesn't match");
}
ObjectMap<TSource, TDestination> map = new ObjectMap<TSource, TDestination>(intersect);
this._maps.Add(map);
return map;
}
/// <summary>
/// Maps the specified source.
/// </summary>
/// <typeparam name="TDestination">The type of the destination.</typeparam>
/// <param name="source">The source.</param>
/// <param name="autoResolve">if set to <c>true</c> [automatic resolve].</param>
/// <returns>
/// A new instance of the map.
/// </returns>
/// <exception cref="ArgumentNullException">source.</exception>
/// <exception cref="InvalidOperationException">You can't map from type {source.GetType().Name} to {typeof(TDestination).Name}.</exception>
public TDestination Map<TDestination>(Object source, Boolean autoResolve = true) {
if(source == null) {
throw new ArgumentNullException(nameof(source));
}
TDestination destination = Activator.CreateInstance<TDestination>();
IObjectMap map = this._maps
.FirstOrDefault(x => x.SourceType == source.GetType() && x.DestinationType == typeof(TDestination));
if(map != null) {
foreach(KeyValuePair<PropertyInfo, List<PropertyInfo>> property in map.Map) {
Object finalSource = property.Value.Aggregate(source,
(current, sourceProperty) => sourceProperty.GetValue(current));
property.Key.SetValue(destination, finalSource);
}
} else {
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
_ = Copy(source, destination);
}
return destination;
}
private static Int32 Copy(
Object target,
IEnumerable<String> propertiesToCopy,
IEnumerable<String> ignoreProperties,
Dictionary<String, TypeValuePair> sourceProperties) {
// Filter properties
IEnumerable<String> requiredProperties = propertiesToCopy?
.Where(p => !String.IsNullOrWhiteSpace(p))
.Select(p => p.ToLowerInvariant());
IEnumerable<String> ignoredProperties = ignoreProperties?
.Where(p => !String.IsNullOrWhiteSpace(p))
.Select(p => p.ToLowerInvariant());
IEnumerable<PropertyInfo> properties = Runtime.PropertyTypeCache
.RetrieveFilteredProperties(target.GetType(), true, x => x.CanWrite);
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, target) ? 1 : 0);
}
private static Boolean TrySetValue(KeyValuePair<PropertyInfo, TypeValuePair> property, Object target) {
try {
SetValue(property, target);
return true;
} catch {
// swallow
}
return false;
}
private static void SetValue(KeyValuePair<PropertyInfo, TypeValuePair> property, Object target) {
if(property.Value.Type.GetTypeInfo().IsEnum) {
property.Key.SetValue(target,
Enum.ToObject(property.Key.PropertyType, property.Value.Value));
return;
}
if(!property.Value.Type.IsValueType() && property.Key.PropertyType == property.Value.Type) {
property.Key.SetValue(target, GetValue(property.Value.Value, property.Key.PropertyType));
return;
}
if(property.Key.PropertyType == typeof(Boolean)) {
property.Key.SetValue(target,
Convert.ToBoolean(property.Value.Value));
return;
}
_ = property.Key.TrySetBasicType(property.Value.Value, target);
}
private static Object GetValue(Object source, Type targetType) {
if(source == null) {
return null;
}
Object target = null;
source.CreateTarget(targetType, false, ref target);
switch(source) {
case String _:
target = source;
break;
case IList sourceList when target is Array targetArray:
for(Int32 i = 0; i < sourceList.Count; i++) {
try {
targetArray.SetValue(
sourceList[i].GetType().IsValueType()
? sourceList[i]
: sourceList[i].CopyPropertiesToNew<Object>(), i);
} catch {
// ignored
}
}
break;
case IList sourceList when target is IList targetList:
MethodInfo addMethod = targetType.GetMethods()
.FirstOrDefault(
m => m.Name.Equals(Formatters.Json.AddMethodName) && m.IsPublic &&
m.GetParameters().Length == 1);
if(addMethod == null) {
return target;
}
foreach(Object item in sourceList) {
try {
_ = targetList.Add(item.GetType().IsValueType()
? item
: item.CopyPropertiesToNew<Object>());
} catch {
// ignored
}
}
break;
default:
_ = source.CopyPropertiesTo(target);
break;
}
return target;
}
private static Dictionary<String, TypeValuePair> GetSourceMap(Object source) {
// select distinct properties because they can be duplicated by inheritance
PropertyInfo[] sourceProperties = Runtime.PropertyTypeCache
.RetrieveFilteredProperties(source.GetType(), true, x => x.CanRead)
.ToArray();
return sourceProperties
.Select(x => x.Name)
.Distinct()
.ToDictionary(
x => x.ToLowerInvariant(),
x => new TypeValuePair(sourceProperties.First(y => y.Name == x).PropertyType,
sourceProperties.First(y => y.Name == x).GetValue(source)));
}
internal class TypeValuePair {
public TypeValuePair(Type type, Object value) {
this.Type = type;
this.Value = value;
}
public Type Type {
get;
}
public Object Value {
get;
}
}
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 Int32 GetHashCode(PropertyInfo obj)
=> obj.Name.GetHashCode() + obj.PropertyType.Name.GetHashCode();
}
}
} }

View File

@ -1,204 +1,207 @@
namespace Unosquare.Swan.Components using System;
{ using System.Linq;
using System; using System.Collections.Concurrent;
using System.Linq; using System.Collections.Generic;
using System.Collections.Concurrent; using Unosquare.Swan.Abstractions;
using System.Collections.Generic;
using Abstractions; namespace Unosquare.Swan.Components {
/// <summary>
/// Represents an object validator.
/// </summary>
/// <example>
/// The following code describes how to perform a simple object validation.
/// <code>
/// using Unosquare.Swan.Components;
///
/// class Example
/// {
/// public static void Main()
/// {
/// // create an instance of ObjectValidator
/// var obj = new ObjectValidator();
///
/// // Add a validation to the 'Simple' class with a custom error message
/// obj.AddValidator&lt;Simple&gt;(x =>
/// !string.IsNullOrEmpty(x.Name), "Name must not be empty");
///
/// // check if object is valid
/// var res = obj.IsValid(new Simple { Name = "Name" });
/// }
///
/// class Simple
/// {
/// public string Name { get; set; }
/// }
/// }
/// </code>
///
/// The following code shows of to validate an object with a custom validator and some attributes using the Runtime ObjectValidator singleton.
/// <code>
/// using Unosquare.Swan.Components;
///
/// class Example
/// {
/// public static void Main()
/// {
/// // create an instance of ObjectValidator
/// Runtime.ObjectValidator
/// .AddValidator&lt;Simple&gt;(x =>
/// !x.Name.Equals("Name"), "Name must not be 'Name'");
///
/// // validate object
/// var res = Runtime.ObjectValidator
/// .Validate(new Simple{ Name = "name", Number = 5, Email ="email@mail.com"})
/// }
///
/// class Simple
/// {
/// [NotNull]
/// public string Name { get; set; }
///
/// [Range(1, 10)]
/// public int Number { get; set; }
///
/// [Email]
/// public string Email { get; set; }
/// }
/// }
/// </code>
/// </example>
public class ObjectValidator {
private readonly ConcurrentDictionary<Type, List<Tuple<Delegate, String>>> _predicates =
new ConcurrentDictionary<Type, List<Tuple<Delegate, String>>>();
/// <summary> /// <summary>
/// Represents an object validator. /// Validates an object given the specified validators and attributes.
/// </summary> /// </summary>
/// <example> /// <typeparam name="T">The type of the object.</typeparam>
/// The following code describes how to perform a simple object validation. /// <param name="obj">The object.</param>
/// <code> /// <returns cref="ObjectValidationResult">A validation result. </returns>
/// using Unosquare.Swan.Components; public ObjectValidationResult Validate<T>(T obj) {
/// ObjectValidationResult errorList = new ObjectValidationResult();
/// class Example _ = this.ValidateObject(obj, false, errorList.Add);
/// {
/// public static void Main()
/// {
/// // create an instance of ObjectValidator
/// var obj = new ObjectValidator();
///
/// // Add a validation to the 'Simple' class with a custom error message
/// obj.AddValidator&lt;Simple&gt;(x =>
/// !string.IsNullOrEmpty(x.Name), "Name must not be empty");
///
/// // check if object is valid
/// var res = obj.IsValid(new Simple { Name = "Name" });
/// }
///
/// class Simple
/// {
/// public string Name { get; set; }
/// }
/// }
/// </code>
///
/// The following code shows of to validate an object with a custom validator and some attributes using the Runtime ObjectValidator singleton.
/// <code>
/// using Unosquare.Swan.Components;
///
/// class Example
/// {
/// public static void Main()
/// {
/// // create an instance of ObjectValidator
/// Runtime.ObjectValidator
/// .AddValidator&lt;Simple&gt;(x =>
/// !x.Name.Equals("Name"), "Name must not be 'Name'");
///
/// // validate object
/// var res = Runtime.ObjectValidator
/// .Validate(new Simple{ Name = "name", Number = 5, Email ="email@mail.com"})
/// }
///
/// class Simple
/// {
/// [NotNull]
/// public string Name { get; set; }
///
/// [Range(1, 10)]
/// public int Number { get; set; }
///
/// [Email]
/// public string Email { get; set; }
/// }
/// }
/// </code>
/// </example>
public class ObjectValidator
{
private readonly ConcurrentDictionary<Type, List<Tuple<Delegate, string>>> _predicates =
new ConcurrentDictionary<Type, List<Tuple<Delegate, string>>>();
/// <summary> return errorList;
/// Validates an object given the specified validators and attributes.
/// </summary>
/// <typeparam name="T">The type of the object.</typeparam>
/// <param name="obj">The object.</param>
/// <returns cref="ObjectValidationResult">A validation result. </returns>
public ObjectValidationResult Validate<T>(T obj)
{
var errorList = new ObjectValidationResult();
ValidateObject(obj, false, errorList.Add);
return errorList;
}
/// <summary>
/// Validates an object given the specified validators and attributes.
/// </summary>
/// <typeparam name="T">The type.</typeparam>
/// <param name="obj">The object.</param>
/// <returns>
/// <c>true</c> if the specified object is valid; otherwise, <c>false</c>.
/// </returns>
/// <exception cref="ArgumentNullException">obj.</exception>
public bool IsValid<T>(T obj) => ValidateObject(obj);
/// <summary>
/// Adds a validator to a specific class.
/// </summary>
/// <typeparam name="T">The type of the object.</typeparam>
/// <param name="predicate">The predicate that will be evaluated.</param>
/// <param name="message">The message.</param>
/// <exception cref="ArgumentNullException">
/// predicate
/// or
/// message.
/// </exception>
public void AddValidator<T>(Predicate<T> predicate, string message)
where T : class
{
if (predicate == null)
throw new ArgumentNullException(nameof(predicate));
if (string.IsNullOrEmpty(message))
throw new ArgumentNullException(message);
if (!_predicates.TryGetValue(typeof(T), out var existing))
{
existing = new List<Tuple<Delegate, string>>();
_predicates[typeof(T)] = existing;
}
existing.Add(Tuple.Create((Delegate) predicate, message));
}
private bool ValidateObject<T>(T obj, bool returnOnError = true, Action<string, string> action = null)
{
if (Equals(obj, null))
throw new ArgumentNullException(nameof(obj));
if (_predicates.ContainsKey(typeof(T)))
{
foreach (var validation in _predicates[typeof(T)])
{
if ((bool) validation.Item1.DynamicInvoke(obj)) continue;
action?.Invoke(string.Empty, validation.Item2);
if (returnOnError) return false;
}
}
var properties = Runtime.AttributeCache.RetrieveFromType<T>(typeof(IValidator));
foreach (var prop in properties)
{
foreach (var attribute in prop.Value)
{
var val = (IValidator) attribute;
if (val.IsValid(prop.Key.GetValue(obj, null))) continue;
action?.Invoke(prop.Key.Name, val.ErrorMessage);
if (returnOnError) return false;
}
}
return true;
}
} }
/// <summary> /// <summary>
/// Defines a validation result containing all validation errors and their properties. /// Validates an object given the specified validators and attributes.
/// </summary> /// </summary>
public class ObjectValidationResult /// <typeparam name="T">The type.</typeparam>
{ /// <param name="obj">The object.</param>
/// <summary> /// <returns>
/// A list of errors. /// <c>true</c> if the specified object is valid; otherwise, <c>false</c>.
/// </summary> /// </returns>
public List<ValidationError> Errors { get; set; } = new List<ValidationError>(); /// <exception cref="ArgumentNullException">obj.</exception>
public Boolean IsValid<T>(T obj) => this.ValidateObject(obj);
/// <summary> /// <summary>
/// <c>true</c> if there are no errors; otherwise, <c>false</c>. /// Adds a validator to a specific class.
/// </summary> /// </summary>
public bool IsValid => !Errors.Any(); /// <typeparam name="T">The type of the object.</typeparam>
/// <param name="predicate">The predicate that will be evaluated.</param>
/// <param name="message">The message.</param>
/// <exception cref="ArgumentNullException">
/// predicate
/// or
/// message.
/// </exception>
public void AddValidator<T>(Predicate<T> predicate, String message)
where T : class {
if(predicate == null) {
throw new ArgumentNullException(nameof(predicate));
}
/// <summary> if(String.IsNullOrEmpty(message)) {
/// Adds an error with a specified property name. throw new ArgumentNullException(message);
/// </summary> }
/// <param name="propertyName">The property name.</param>
/// <param name="errorMessage">The error message.</param>
public void Add(string propertyName, string errorMessage) =>
Errors.Add(new ValidationError {ErrorMessage = errorMessage, PropertyName = propertyName});
/// <summary> if(!this._predicates.TryGetValue(typeof(T), out List<Tuple<Delegate, String>> existing)) {
/// Defines a validation error. existing = new List<Tuple<Delegate, String>>();
/// </summary> this._predicates[typeof(T)] = existing;
public class ValidationError }
{
/// <summary>
/// The property name.
/// </summary>
public string PropertyName { get; set; }
/// <summary> existing.Add(Tuple.Create((Delegate)predicate, message));
/// The message error.
/// </summary>
public string ErrorMessage { get; set; }
}
} }
private Boolean ValidateObject<T>(T obj, Boolean returnOnError = true, Action<String, String> action = null) {
if(Equals(obj, null)) {
throw new ArgumentNullException(nameof(obj));
}
if(this._predicates.ContainsKey(typeof(T))) {
foreach(Tuple<Delegate, String> validation in this._predicates[typeof(T)]) {
if((Boolean)validation.Item1.DynamicInvoke(obj)) {
continue;
}
action?.Invoke(String.Empty, validation.Item2);
if(returnOnError) {
return false;
}
}
}
Dictionary<System.Reflection.PropertyInfo, IEnumerable<Object>> properties = Runtime.AttributeCache.RetrieveFromType<T>(typeof(IValidator));
foreach(KeyValuePair<System.Reflection.PropertyInfo, IEnumerable<Object>> prop in properties) {
foreach(Object attribute in prop.Value) {
IValidator val = (IValidator)attribute;
if(val.IsValid(prop.Key.GetValue(obj, null))) {
continue;
}
action?.Invoke(prop.Key.Name, val.ErrorMessage);
if(returnOnError) {
return false;
}
}
}
return true;
}
}
/// <summary>
/// Defines a validation result containing all validation errors and their properties.
/// </summary>
public class ObjectValidationResult {
/// <summary>
/// A list of errors.
/// </summary>
public List<ValidationError> Errors { get; set; } = new List<ValidationError>();
/// <summary>
/// <c>true</c> if there are no errors; otherwise, <c>false</c>.
/// </summary>
public Boolean IsValid => !this.Errors.Any();
/// <summary>
/// Adds an error with a specified property name.
/// </summary>
/// <param name="propertyName">The property name.</param>
/// <param name="errorMessage">The error message.</param>
public void Add(String propertyName, String errorMessage) =>
this.Errors.Add(new ValidationError { ErrorMessage = errorMessage, PropertyName = propertyName });
/// <summary>
/// Defines a validation error.
/// </summary>
public class ValidationError {
/// <summary>
/// The property name.
/// </summary>
public String PropertyName {
get; set;
}
/// <summary>
/// The message error.
/// </summary>
public String ErrorMessage {
get; set;
}
}
}
} }

View File

@ -1,52 +1,48 @@
namespace Unosquare.Swan.Components using System;
{ using System.Threading;
using System; using Unosquare.Swan.Abstractions;
using System.Threading;
using Abstractions; namespace Unosquare.Swan.Components {
/// <summary>
/// Provides factory methods to create synchronized reader-writer locks
/// that support a generalized locking and releasing api and syntax.
/// </summary>
public static class SyncLockerFactory {
#region Enums and Interfaces
/// <summary> /// <summary>
/// Provides factory methods to create synchronized reader-writer locks /// Enumerates the locking operations.
/// that support a generalized locking and releasing api and syntax.
/// </summary> /// </summary>
public static class SyncLockerFactory private enum LockHolderType {
{ Read,
#region Enums and Interfaces Write,
}
/// <summary> /// <summary>
/// Enumerates the locking operations. /// Defines methods for releasing locks.
/// </summary> /// </summary>
private enum LockHolderType private interface ISyncReleasable {
{ /// <summary>
Read, /// Releases the writer lock.
Write, /// </summary>
} void ReleaseWriterLock();
/// <summary> /// <summary>
/// Defines methods for releasing locks. /// Releases the reader lock.
/// </summary> /// </summary>
private interface ISyncReleasable void ReleaseReaderLock();
{ }
/// <summary>
/// Releases the writer lock.
/// </summary>
void ReleaseWriterLock();
/// <summary> #endregion
/// Releases the reader lock.
/// </summary>
void ReleaseReaderLock();
}
#endregion #region Factory Methods
#region Factory Methods
#if !NETSTANDARD1_3 #if !NETSTANDARD1_3
/// <summary> /// <summary>
/// Creates a reader-writer lock backed by a standard ReaderWriterLock. /// Creates a reader-writer lock backed by a standard ReaderWriterLock.
/// </summary> /// </summary>
/// <returns>The synchronized locker.</returns> /// <returns>The synchronized locker.</returns>
public static ISyncLocker Create() => new SyncLocker(); public static ISyncLocker Create() => new SyncLocker();
#else #else
/// <summary> /// <summary>
/// Creates a reader-writer lock backed by a standard ReaderWriterLockSlim when /// Creates a reader-writer lock backed by a standard ReaderWriterLockSlim when
@ -56,142 +52,141 @@
public static ISyncLocker Create() => new SyncLockerSlim(); public static ISyncLocker Create() => new SyncLockerSlim();
#endif #endif
/// <summary> /// <summary>
/// Creates a reader-writer lock backed by a ReaderWriterLockSlim. /// Creates a reader-writer lock backed by a ReaderWriterLockSlim.
/// </summary> /// </summary>
/// <returns>The synchronized locker.</returns> /// <returns>The synchronized locker.</returns>
public static ISyncLocker CreateSlim() => new SyncLockerSlim(); public static ISyncLocker CreateSlim() => new SyncLockerSlim();
/// <summary> /// <summary>
/// Creates a reader-writer lock. /// Creates a reader-writer lock.
/// </summary> /// </summary>
/// <param name="useSlim">if set to <c>true</c> it uses the Slim version of a reader-writer lock.</param> /// <param name="useSlim">if set to <c>true</c> it uses the Slim version of a reader-writer lock.</param>
/// <returns>The Sync Locker.</returns> /// <returns>The Sync Locker.</returns>
public static ISyncLocker Create(bool useSlim) => useSlim ? CreateSlim() : Create(); public static ISyncLocker Create(Boolean useSlim) => useSlim ? CreateSlim() : Create();
#endregion #endregion
#region Private Classes #region Private Classes
/// <summary> /// <summary>
/// The lock releaser. Calling the dispose method releases the lock entered by the parent SyncLocker. /// The lock releaser. Calling the dispose method releases the lock entered by the parent SyncLocker.
/// </summary> /// </summary>
/// <seealso cref="System.IDisposable" /> /// <seealso cref="System.IDisposable" />
private sealed class SyncLockReleaser : IDisposable private sealed class SyncLockReleaser : IDisposable {
{ private readonly ISyncReleasable _parent;
private readonly ISyncReleasable _parent; private readonly LockHolderType _operation;
private readonly LockHolderType _operation;
private bool _isDisposed; private Boolean _isDisposed;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="SyncLockReleaser"/> class. /// Initializes a new instance of the <see cref="SyncLockReleaser"/> class.
/// </summary> /// </summary>
/// <param name="parent">The parent.</param> /// <param name="parent">The parent.</param>
/// <param name="operation">The operation.</param> /// <param name="operation">The operation.</param>
public SyncLockReleaser(ISyncReleasable parent, LockHolderType operation) public SyncLockReleaser(ISyncReleasable parent, LockHolderType operation) {
{ this._parent = parent;
_parent = parent; this._operation = operation;
_operation = operation; }
}
/// <inheritdoc /> /// <inheritdoc />
public void Dispose() public void Dispose() {
{ if(this._isDisposed) {
if (_isDisposed) return; return;
_isDisposed = true;
if (_operation == LockHolderType.Read)
_parent.ReleaseReaderLock();
else
_parent.ReleaseWriterLock();
}
} }
this._isDisposed = true;
if(this._operation == LockHolderType.Read) {
this._parent.ReleaseReaderLock();
} else {
this._parent.ReleaseWriterLock();
}
}
}
#if !NETSTANDARD1_3 #if !NETSTANDARD1_3
/// <summary> /// <summary>
/// The Sync Locker backed by a ReaderWriterLock. /// The Sync Locker backed by a ReaderWriterLock.
/// </summary> /// </summary>
/// <seealso cref="ISyncLocker" /> /// <seealso cref="ISyncLocker" />
/// <seealso cref="ISyncReleasable" /> /// <seealso cref="ISyncReleasable" />
private sealed class SyncLocker : ISyncLocker, ISyncReleasable private sealed class SyncLocker : ISyncLocker, ISyncReleasable {
{ private Boolean _isDisposed;
private bool _isDisposed; private ReaderWriterLock _locker = new ReaderWriterLock();
private ReaderWriterLock _locker = new ReaderWriterLock();
/// <inheritdoc /> /// <inheritdoc />
public IDisposable AcquireReaderLock() public IDisposable AcquireReaderLock() {
{ this._locker?.AcquireReaderLock(Timeout.Infinite);
_locker?.AcquireReaderLock(Timeout.Infinite); return new SyncLockReleaser(this, LockHolderType.Read);
return new SyncLockReleaser(this, LockHolderType.Read); }
}
/// <inheritdoc /> /// <inheritdoc />
public IDisposable AcquireWriterLock() public IDisposable AcquireWriterLock() {
{ this._locker?.AcquireWriterLock(Timeout.Infinite);
_locker?.AcquireWriterLock(Timeout.Infinite); return new SyncLockReleaser(this, LockHolderType.Write);
return new SyncLockReleaser(this, LockHolderType.Write); }
}
/// <inheritdoc /> /// <inheritdoc />
public void ReleaseWriterLock() => _locker?.ReleaseWriterLock(); public void ReleaseWriterLock() => this._locker?.ReleaseWriterLock();
/// <inheritdoc /> /// <inheritdoc />
public void ReleaseReaderLock() => _locker?.ReleaseReaderLock(); public void ReleaseReaderLock() => this._locker?.ReleaseReaderLock();
/// <inheritdoc /> /// <inheritdoc />
public void Dispose() public void Dispose() {
{ if(this._isDisposed) {
if (_isDisposed) return; return;
_isDisposed = true;
_locker?.ReleaseLock();
_locker = null;
}
} }
this._isDisposed = true;
_ = this._locker?.ReleaseLock();
this._locker = null;
}
}
#endif #endif
/// <summary> /// <summary>
/// The Sync Locker backed by ReaderWriterLockSlim. /// The Sync Locker backed by ReaderWriterLockSlim.
/// </summary> /// </summary>
/// <seealso cref="ISyncLocker" /> /// <seealso cref="ISyncLocker" />
/// <seealso cref="ISyncReleasable" /> /// <seealso cref="ISyncReleasable" />
private sealed class SyncLockerSlim : ISyncLocker, ISyncReleasable private sealed class SyncLockerSlim : ISyncLocker, ISyncReleasable {
{ private Boolean _isDisposed;
private bool _isDisposed;
private ReaderWriterLockSlim _locker private ReaderWriterLockSlim _locker
= new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion); = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);
/// <inheritdoc /> /// <inheritdoc />
public IDisposable AcquireReaderLock() public IDisposable AcquireReaderLock() {
{ this._locker?.EnterReadLock();
_locker?.EnterReadLock(); return new SyncLockReleaser(this, LockHolderType.Read);
return new SyncLockReleaser(this, LockHolderType.Read); }
}
/// <inheritdoc /> /// <inheritdoc />
public IDisposable AcquireWriterLock() public IDisposable AcquireWriterLock() {
{ this._locker?.EnterWriteLock();
_locker?.EnterWriteLock(); return new SyncLockReleaser(this, LockHolderType.Write);
return new SyncLockReleaser(this, LockHolderType.Write); }
}
/// <inheritdoc /> /// <inheritdoc />
public void ReleaseWriterLock() => _locker?.ExitWriteLock(); public void ReleaseWriterLock() => this._locker?.ExitWriteLock();
/// <inheritdoc /> /// <inheritdoc />
public void ReleaseReaderLock() => _locker?.ExitReadLock(); public void ReleaseReaderLock() => this._locker?.ExitReadLock();
/// <inheritdoc /> /// <inheritdoc />
public void Dispose() public void Dispose() {
{ if(this._isDisposed) {
if (_isDisposed) return; return;
_isDisposed = true;
_locker?.Dispose();
_locker = null;
}
} }
#endregion this._isDisposed = true;
this._locker?.Dispose();
this._locker = null;
}
} }
#endregion
}
} }

View File

@ -1,63 +1,55 @@
#if !NETSTANDARD1_3 #if !NETSTANDARD1_3
namespace Unosquare.Swan.Components
{ using System;
using System; using System.Threading;
using System.Threading; using Unosquare.Swan.Abstractions;
using Abstractions; namespace Unosquare.Swan.Components {
/// <summary>
/// Use this singleton to wait for a specific <c>TimeSpan</c> or time.
///
/// Internally this class will use a <c>Timer</c> and a <c>ManualResetEvent</c> to block until
/// the time condition is satisfied.
/// </summary>
/// <seealso cref="SingletonBase{TimerControl}" />
public class TimerControl : SingletonBase<TimerControl> {
[System.Diagnostics.CodeAnalysis.SuppressMessage("Codequalität", "IDE0052:Ungelesene private Member entfernen", Justification = "<Ausstehend>")]
private readonly Timer _innerTimer;
private readonly IWaitEvent _delayLock = WaitEventFactory.Create(true);
/// <summary> /// <summary>
/// Use this singleton to wait for a specific <c>TimeSpan</c> or time. /// Initializes a new instance of the <see cref="TimerControl"/> class.
///
/// Internally this class will use a <c>Timer</c> and a <c>ManualResetEvent</c> to block until
/// the time condition is satisfied.
/// </summary> /// </summary>
/// <seealso cref="SingletonBase{TimerControl}" /> protected TimerControl() => this._innerTimer = new Timer(
public class TimerControl : SingletonBase<TimerControl> x => {
{ try {
private readonly Timer _innerTimer; this._delayLock.Complete();
private readonly IWaitEvent _delayLock = WaitEventFactory.Create(true); this._delayLock.Begin();
} catch {
/// <summary> // ignore
/// Initializes a new instance of the <see cref="TimerControl"/> class. }
/// </summary>
protected TimerControl()
{
_innerTimer = new Timer(
x =>
{
try
{
_delayLock.Complete();
_delayLock.Begin();
}
catch
{
// ignore
}
}, },
null, null,
0, 0,
15); 15);
}
/// <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="ct">The cancellation token.</param> /// <param name="ct">The cancellation token.</param>
public void WaitUntil(DateTime untilDate, CancellationToken ct = default) public void WaitUntil(DateTime untilDate, CancellationToken ct = default) {
{ while(!ct.IsCancellationRequested && DateTime.UtcNow < untilDate) {
while (!ct.IsCancellationRequested && DateTime.UtcNow < untilDate) this._delayLock.Wait();
_delayLock.Wait(); }
}
/// <summary>
/// Waits the specified wait time.
/// </summary>
/// <param name="waitTime">The wait time.</param>
/// <param name="ct">The cancellation token.</param>
public void Wait(TimeSpan waitTime, CancellationToken ct = default) =>
WaitUntil(DateTime.UtcNow.Add(waitTime), ct);
} }
/// <summary>
/// Waits the specified wait time.
/// </summary>
/// <param name="waitTime">The wait time.</param>
/// <param name="ct">The cancellation token.</param>
public void Wait(TimeSpan waitTime, CancellationToken ct = default) =>
this.WaitUntil(DateTime.UtcNow.Add(waitTime), ct);
}
} }
#endif #endif

View File

@ -1,222 +1,196 @@
#if !NETSTANDARD1_3 
namespace Unosquare.Swan.Components
{ #if !NETSTANDARD1_3
using System; using System;
using System.Threading; using System.Threading;
using Abstractions; using Unosquare.Swan.Abstractions;
namespace Unosquare.Swan.Components {
/// <summary>
/// Provides a Manual Reset Event factory with a unified API.
/// </summary>
/// <example>
/// The following example shows how to use the WaitEventFactory class.
/// <code>
/// using Unosquare.Swan.Components;
///
/// public class Example
/// {
/// // create a WaitEvent using the slim version
/// private static readonly IWaitEvent waitEvent = WaitEventFactory.CreateSlim(false);
///
/// public static void Main()
/// {
/// Task.Factory.StartNew(() =>
/// {
/// DoWork(1);
/// });
///
/// Task.Factory.StartNew(() =>
/// {
/// DoWork(2);
/// });
///
/// // send first signal
/// waitEvent.Complete();
/// waitEvent.Begin();
///
/// Thread.Sleep(TimeSpan.FromSeconds(2));
///
/// // send second signal
/// waitEvent.Complete();
///
/// Console.Readline();
/// }
///
/// public static void DoWork(int taskNumber)
/// {
/// $"Data retrieved:{taskNumber}".WriteLine();
/// waitEvent.Wait();
///
/// Thread.Sleep(TimeSpan.FromSeconds(2));
/// $"All finished up {taskNumber}".WriteLine();
/// }
/// }
/// </code>
/// </example>
public static class WaitEventFactory {
#region Factory Methods
/// <summary> /// <summary>
/// Provides a Manual Reset Event factory with a unified API. /// Creates a Wait Event backed by a standard ManualResetEvent.
/// </summary> /// </summary>
/// <example> /// <param name="isCompleted">if initially set to completed. Generally true.</param>
/// The following example shows how to use the WaitEventFactory class. /// <returns>The Wait Event.</returns>
/// <code> public static IWaitEvent Create(Boolean isCompleted) => new WaitEvent(isCompleted);
/// using Unosquare.Swan.Components;
///
/// public class Example
/// {
/// // create a WaitEvent using the slim version
/// private static readonly IWaitEvent waitEvent = WaitEventFactory.CreateSlim(false);
///
/// public static void Main()
/// {
/// Task.Factory.StartNew(() =>
/// {
/// DoWork(1);
/// });
///
/// Task.Factory.StartNew(() =>
/// {
/// DoWork(2);
/// });
///
/// // send first signal
/// waitEvent.Complete();
/// waitEvent.Begin();
///
/// Thread.Sleep(TimeSpan.FromSeconds(2));
///
/// // send second signal
/// waitEvent.Complete();
///
/// Console.Readline();
/// }
///
/// public static void DoWork(int taskNumber)
/// {
/// $"Data retrieved:{taskNumber}".WriteLine();
/// waitEvent.Wait();
///
/// Thread.Sleep(TimeSpan.FromSeconds(2));
/// $"All finished up {taskNumber}".WriteLine();
/// }
/// }
/// </code>
/// </example>
public static class WaitEventFactory
{
#region Factory Methods
/// <summary> /// <summary>
/// Creates a Wait Event backed by a standard ManualResetEvent. /// Creates a Wait Event backed by a ManualResetEventSlim.
/// </summary> /// </summary>
/// <param name="isCompleted">if initially set to completed. Generally true.</param> /// <param name="isCompleted">if initially set to completed. Generally true.</param>
/// <returns>The Wait Event.</returns> /// <returns>The Wait Event.</returns>
public static IWaitEvent Create(bool isCompleted) => new WaitEvent(isCompleted); public static IWaitEvent CreateSlim(Boolean isCompleted) => new WaitEventSlim(isCompleted);
/// <summary> /// <summary>
/// Creates a Wait Event backed by a ManualResetEventSlim. /// Creates a Wait Event backed by a ManualResetEventSlim.
/// </summary> /// </summary>
/// <param name="isCompleted">if initially set to completed. Generally true.</param> /// <param name="isCompleted">if initially set to completed. Generally true.</param>
/// <returns>The Wait Event.</returns> /// <param name="useSlim">if set to <c>true</c> creates a slim version of the wait event.</param>
public static IWaitEvent CreateSlim(bool isCompleted) => new WaitEventSlim(isCompleted); /// <returns>The Wait Event.</returns>
public static IWaitEvent Create(Boolean isCompleted, Boolean useSlim) => useSlim ? CreateSlim(isCompleted) : Create(isCompleted);
/// <summary> #endregion
/// Creates a Wait Event backed by a ManualResetEventSlim.
/// </summary>
/// <param name="isCompleted">if initially set to completed. Generally true.</param>
/// <param name="useSlim">if set to <c>true</c> creates a slim version of the wait event.</param>
/// <returns>The Wait Event.</returns>
public static IWaitEvent Create(bool isCompleted, bool useSlim) => useSlim ? CreateSlim(isCompleted) : Create(isCompleted);
#endregion #region Backing Classes
#region Backing Classes /// <summary>
/// Defines a WaitEvent backed by a ManualResetEvent.
/// </summary>
private class WaitEvent : IWaitEvent {
private ManualResetEvent _event;
/// <summary> /// <summary>
/// Defines a WaitEvent backed by a ManualResetEvent. /// Initializes a new instance of the <see cref="WaitEvent"/> class.
/// </summary> /// </summary>
private class WaitEvent : IWaitEvent /// <param name="isCompleted">if set to <c>true</c> [is completed].</param>
{ public WaitEvent(Boolean isCompleted) => this._event = new ManualResetEvent(isCompleted);
private ManualResetEvent _event;
/// <summary> /// <inheritdoc />
/// Initializes a new instance of the <see cref="WaitEvent"/> class. public Boolean IsDisposed {
/// </summary> get; private set;
/// <param name="isCompleted">if set to <c>true</c> [is completed].</param> }
public WaitEvent(bool isCompleted)
{
_event = new ManualResetEvent(isCompleted);
}
/// <inheritdoc /> /// <inheritdoc />
public bool IsDisposed { get; private set; } public Boolean IsValid => this.IsDisposed || this._event == null
? false
: this._event?.SafeWaitHandle?.IsClosed ?? true ? false : !(this._event?.SafeWaitHandle?.IsInvalid ?? true);
/// <inheritdoc /> /// <inheritdoc />
public bool IsValid public Boolean IsCompleted => this.IsValid == false ? true : this._event?.WaitOne(0) ?? true;
{
get
{
if (IsDisposed || _event == null)
return false;
if (_event?.SafeWaitHandle?.IsClosed ?? true) /// <inheritdoc />
return false; public Boolean IsInProgress => !this.IsCompleted;
return !(_event?.SafeWaitHandle?.IsInvalid ?? true); /// <inheritdoc />
} public void Begin() => this._event?.Reset();
}
/// <inheritdoc /> /// <inheritdoc />
public bool IsCompleted public void Complete() => this._event?.Set();
{
get
{
if (IsValid == false)
return true;
return _event?.WaitOne(0) ?? true; /// <inheritdoc />
} void IDisposable.Dispose() {
} if(this.IsDisposed) {
return;
/// <inheritdoc />
public bool IsInProgress => !IsCompleted;
/// <inheritdoc />
public void Begin() => _event?.Reset();
/// <inheritdoc />
public void Complete() => _event?.Set();
/// <inheritdoc />
void IDisposable.Dispose()
{
if (IsDisposed) return;
IsDisposed = true;
_event?.Set();
_event?.Dispose();
_event = null;
}
/// <inheritdoc />
public void Wait() => _event?.WaitOne();
/// <inheritdoc />
public bool Wait(TimeSpan timeout) => _event?.WaitOne(timeout) ?? true;
} }
/// <summary> this.IsDisposed = true;
/// Defines a WaitEvent backed by a ManualResetEventSlim.
/// </summary>
private class WaitEventSlim : IWaitEvent
{
private ManualResetEventSlim _event;
/// <summary> _ = this._event?.Set();
/// Initializes a new instance of the <see cref="WaitEventSlim"/> class. this._event?.Dispose();
/// </summary> this._event = null;
/// <param name="isCompleted">if set to <c>true</c> [is completed].</param> }
public WaitEventSlim(bool isCompleted)
{
_event = new ManualResetEventSlim(isCompleted);
}
/// <inheritdoc /> /// <inheritdoc />
public bool IsDisposed { get; private set; } public void Wait() => this._event?.WaitOne();
/// <inheritdoc /> /// <inheritdoc />
public bool IsValid public Boolean Wait(TimeSpan timeout) => this._event?.WaitOne(timeout) ?? true;
{
get
{
if (IsDisposed || _event?.WaitHandle?.SafeWaitHandle == null) return false;
return !_event.WaitHandle.SafeWaitHandle.IsClosed && !_event.WaitHandle.SafeWaitHandle.IsInvalid;
}
}
/// <inheritdoc />
public bool IsCompleted => IsValid == false || _event.IsSet;
/// <inheritdoc />
public bool IsInProgress => !IsCompleted;
/// <inheritdoc />
public void Begin() => _event?.Reset();
/// <inheritdoc />
public void Complete() => _event?.Set();
/// <inheritdoc />
void IDisposable.Dispose()
{
if (IsDisposed) return;
IsDisposed = true;
_event?.Set();
_event?.Dispose();
_event = null;
}
/// <inheritdoc />
public void Wait() => _event?.Wait();
/// <inheritdoc />
public bool Wait(TimeSpan timeout) => _event?.Wait(timeout) ?? true;
}
#endregion
} }
/// <summary>
/// Defines a WaitEvent backed by a ManualResetEventSlim.
/// </summary>
private class WaitEventSlim : IWaitEvent {
private ManualResetEventSlim _event;
/// <summary>
/// Initializes a new instance of the <see cref="WaitEventSlim"/> class.
/// </summary>
/// <param name="isCompleted">if set to <c>true</c> [is completed].</param>
public WaitEventSlim(Boolean isCompleted) => this._event = new ManualResetEventSlim(isCompleted);
/// <inheritdoc />
public Boolean IsDisposed {
get; private set;
}
/// <inheritdoc />
public Boolean IsValid => this.IsDisposed || this._event?.WaitHandle?.SafeWaitHandle == null
? false
: !this._event.WaitHandle.SafeWaitHandle.IsClosed && !this._event.WaitHandle.SafeWaitHandle.IsInvalid;
/// <inheritdoc />
public Boolean IsCompleted => this.IsValid == false || this._event.IsSet;
/// <inheritdoc />
public Boolean IsInProgress => !this.IsCompleted;
/// <inheritdoc />
public void Begin() => this._event?.Reset();
/// <inheritdoc />
public void Complete() => this._event?.Set();
/// <inheritdoc />
void IDisposable.Dispose() {
if(this.IsDisposed) {
return;
}
this.IsDisposed = true;
this._event?.Set();
this._event?.Dispose();
this._event = null;
}
/// <inheritdoc />
public void Wait() => this._event?.Wait();
/// <inheritdoc />
public Boolean Wait(TimeSpan timeout) => this._event?.Wait(timeout) ?? true;
}
#endregion
}
} }
#endif #endif

View File

@ -1,174 +1,172 @@
namespace Unosquare.Swan using System;
{
using System; namespace Unosquare.Swan {
/// <summary>
/// Represents a struct of DateTimeSpan to compare dates and get in
/// separate fields the amount of time between those dates.
///
/// Based on https://stackoverflow.com/a/9216404/1096693.
/// </summary>
public struct DateTimeSpan {
/// <summary>
/// Initializes a new instance of the <see cref="DateTimeSpan"/> struct.
/// </summary>
/// <param name="years">The years.</param>
/// <param name="months">The months.</param>
/// <param name="days">The days.</param>
/// <param name="hours">The hours.</param>
/// <param name="minutes">The minutes.</param>
/// <param name="seconds">The seconds.</param>
/// <param name="milliseconds">The milliseconds.</param>
public DateTimeSpan(Int32 years, Int32 months, Int32 days, Int32 hours, Int32 minutes, Int32 seconds, Int32 milliseconds) {
this.Years = years;
this.Months = months;
this.Days = days;
this.Hours = hours;
this.Minutes = minutes;
this.Seconds = seconds;
this.Milliseconds = milliseconds;
}
/// <summary> /// <summary>
/// Represents a struct of DateTimeSpan to compare dates and get in /// Gets the years.
/// separate fields the amount of time between those dates.
///
/// Based on https://stackoverflow.com/a/9216404/1096693.
/// </summary> /// </summary>
public struct DateTimeSpan /// <value>
{ /// The years.
/// <summary> /// </value>
/// Initializes a new instance of the <see cref="DateTimeSpan"/> struct. public Int32 Years {
/// </summary> get;
/// <param name="years">The years.</param>
/// <param name="months">The months.</param>
/// <param name="days">The days.</param>
/// <param name="hours">The hours.</param>
/// <param name="minutes">The minutes.</param>
/// <param name="seconds">The seconds.</param>
/// <param name="milliseconds">The milliseconds.</param>
public DateTimeSpan(int years, int months, int days, int hours, int minutes, int seconds, int milliseconds)
{
Years = years;
Months = months;
Days = days;
Hours = hours;
Minutes = minutes;
Seconds = seconds;
Milliseconds = milliseconds;
}
/// <summary>
/// Gets the years.
/// </summary>
/// <value>
/// The years.
/// </value>
public int Years { get; }
/// <summary>
/// Gets the months.
/// </summary>
/// <value>
/// The months.
/// </value>
public int Months { get; }
/// <summary>
/// Gets the days.
/// </summary>
/// <value>
/// The days.
/// </value>
public int Days { get; }
/// <summary>
/// Gets the hours.
/// </summary>
/// <value>
/// The hours.
/// </value>
public int Hours { get; }
/// <summary>
/// Gets the minutes.
/// </summary>
/// <value>
/// The minutes.
/// </value>
public int Minutes { get; }
/// <summary>
/// Gets the seconds.
/// </summary>
/// <value>
/// The seconds.
/// </value>
public int Seconds { get; }
/// <summary>
/// Gets the milliseconds.
/// </summary>
/// <value>
/// The milliseconds.
/// </value>
public int Milliseconds { get; }
internal static DateTimeSpan CompareDates(DateTime date1, DateTime date2)
{
if (date2 < date1)
{
var sub = date1;
date1 = date2;
date2 = sub;
}
var current = date1;
var years = 0;
var months = 0;
var days = 0;
var phase = Phase.Years;
var span = new DateTimeSpan();
var officialDay = current.Day;
while (phase != Phase.Done)
{
switch (phase)
{
case Phase.Years:
if (current.AddYears(years + 1) > date2)
{
phase = Phase.Months;
current = current.AddYears(years);
}
else
{
years++;
}
break;
case Phase.Months:
if (current.AddMonths(months + 1) > date2)
{
phase = Phase.Days;
current = current.AddMonths(months);
if (current.Day < officialDay &&
officialDay <= DateTime.DaysInMonth(current.Year, current.Month))
current = current.AddDays(officialDay - current.Day);
}
else
{
months++;
}
break;
case Phase.Days:
if (current.AddDays(days + 1) > date2)
{
current = current.AddDays(days);
var timespan = date2 - current;
span = new DateTimeSpan(
years,
months,
days,
timespan.Hours,
timespan.Minutes,
timespan.Seconds,
timespan.Milliseconds);
phase = Phase.Done;
}
else
{
days++;
}
break;
}
}
return span;
}
private enum Phase
{
Years,
Months,
Days,
Done,
}
} }
/// <summary>
/// Gets the months.
/// </summary>
/// <value>
/// The months.
/// </value>
public Int32 Months {
get;
}
/// <summary>
/// Gets the days.
/// </summary>
/// <value>
/// The days.
/// </value>
public Int32 Days {
get;
}
/// <summary>
/// Gets the hours.
/// </summary>
/// <value>
/// The hours.
/// </value>
public Int32 Hours {
get;
}
/// <summary>
/// Gets the minutes.
/// </summary>
/// <value>
/// The minutes.
/// </value>
public Int32 Minutes {
get;
}
/// <summary>
/// Gets the seconds.
/// </summary>
/// <value>
/// The seconds.
/// </value>
public Int32 Seconds {
get;
}
/// <summary>
/// Gets the milliseconds.
/// </summary>
/// <value>
/// The milliseconds.
/// </value>
public Int32 Milliseconds {
get;
}
internal static DateTimeSpan CompareDates(DateTime date1, DateTime date2) {
if(date2 < date1) {
DateTime sub = date1;
date1 = date2;
date2 = sub;
}
DateTime current = date1;
Int32 years = 0;
Int32 months = 0;
Int32 days = 0;
Phase phase = Phase.Years;
DateTimeSpan span = new DateTimeSpan();
Int32 officialDay = current.Day;
while(phase != Phase.Done) {
switch(phase) {
case Phase.Years:
if(current.AddYears(years + 1) > date2) {
phase = Phase.Months;
current = current.AddYears(years);
} else {
years++;
}
break;
case Phase.Months:
if(current.AddMonths(months + 1) > date2) {
phase = Phase.Days;
current = current.AddMonths(months);
if(current.Day < officialDay &&
officialDay <= DateTime.DaysInMonth(current.Year, current.Month)) {
current = current.AddDays(officialDay - current.Day);
}
} else {
months++;
}
break;
case Phase.Days:
if(current.AddDays(days + 1) > date2) {
current = current.AddDays(days);
TimeSpan timespan = date2 - current;
span = new DateTimeSpan(
years,
months,
days,
timespan.Hours,
timespan.Minutes,
timespan.Seconds,
timespan.Milliseconds);
phase = Phase.Done;
} else {
days++;
}
break;
}
}
return span;
}
private enum Phase {
Years,
Months,
Days,
Done,
}
}
} }

View File

@ -1,132 +1,140 @@
namespace Unosquare.Swan using Unosquare.Swan.Reflection;
{ using System;
using Reflection; using System.Collections.Generic;
using System; using System.Linq;
using System.Collections.Generic; using System.Net;
using System.Linq;
using System.Net; namespace Unosquare.Swan {
/// <summary>
/// Contains useful constants and definitions.
/// </summary>
public static partial class Definitions {
#region Main Dictionary Definition
/// <summary> /// <summary>
/// Contains useful constants and definitions. /// The basic types information.
/// </summary> /// </summary>
public static partial class Definitions public static readonly Dictionary<Type, ExtendedTypeInfo> BasicTypesInfo =
{ new Dictionary<Type, ExtendedTypeInfo>
#region Main Dictionary Definition {
/// <summary>
/// The basic types information.
/// </summary>
public static readonly Dictionary<Type, ExtendedTypeInfo> BasicTypesInfo =
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
{typeof(TimeSpan), new ExtendedTypeInfo<TimeSpan>()}, {typeof(TimeSpan), new ExtendedTypeInfo<TimeSpan>()},
{typeof(TimeSpan?), new ExtendedTypeInfo<TimeSpan?>()}, {typeof(TimeSpan?), new ExtendedTypeInfo<TimeSpan?>()},
{typeof(IPAddress), new ExtendedTypeInfo<IPAddress>()}, {typeof(IPAddress), new ExtendedTypeInfo<IPAddress>()},
}; };
#endregion #endregion
/// <summary> /// <summary>
/// Contains all basic types, including string, date time, and all of their nullable counterparts. /// Contains all basic types, including string, date time, and all of their nullable counterparts.
/// </summary> /// </summary>
/// <value> /// <value>
/// All basic types. /// All basic types.
/// </value> /// </value>
public static List<Type> AllBasicTypes { get; } = new List<Type>(BasicTypesInfo.Keys.ToArray()); public static List<Type> AllBasicTypes { get; } = new List<Type>(BasicTypesInfo.Keys.ToArray());
/// <summary> /// <summary>
/// Gets all numeric types including their nullable counterparts. /// Gets all numeric types including their nullable counterparts.
/// Note that Booleans and Guids are not considered numeric types. /// Note that Booleans and Guids are not considered numeric types.
/// </summary> /// </summary>
/// <value> /// <value>
/// All numeric types. /// All numeric types.
/// </value> /// </value>
public static List<Type> AllNumericTypes { get; } = new List<Type>( public static List<Type> AllNumericTypes {
BasicTypesInfo get;
.Where(kvp => kvp.Value.IsNumeric) } = new List<Type>(
.Select(kvp => kvp.Key).ToArray()); BasicTypesInfo
.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.
/// Note that Booleans and Guids are not considered numeric types. /// Note that Booleans and Guids are not considered numeric types.
/// </summary> /// </summary>
/// <value> /// <value>
/// All numeric value types. /// All numeric value types.
/// </value> /// </value>
public static List<Type> AllNumericValueTypes { get; } = new List<Type>( public static List<Type> AllNumericValueTypes {
BasicTypesInfo get;
.Where(kvp => kvp.Value.IsNumeric && kvp.Value.IsNullableValueType == false) } = new List<Type>(
.Select(kvp => kvp.Key).ToArray()); BasicTypesInfo
.Where(kvp => kvp.Value.IsNumeric && kvp.Value.IsNullableValueType == false)
.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.
/// </summary> /// </summary>
/// <value> /// <value>
/// All basic value types. /// All basic value types.
/// </value> /// </value>
public static List<Type> AllBasicValueTypes { get; } = new List<Type>( public static List<Type> AllBasicValueTypes {
BasicTypesInfo get;
.Where(kvp => kvp.Value.IsValueType) } = new List<Type>(
.Select(kvp => kvp.Key).ToArray()); BasicTypesInfo
.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.
/// </summary> /// </summary>
/// <value> /// <value>
/// All basic value and string types. /// All basic value and string types.
/// </value> /// </value>
public static List<Type> AllBasicValueAndStringTypes { get; } = new List<Type>( public static List<Type> AllBasicValueAndStringTypes {
BasicTypesInfo get;
.Where(kvp => kvp.Value.IsValueType || kvp.Key == typeof(string)) } = new List<Type>(
.Select(kvp => kvp.Key).ToArray()); BasicTypesInfo
.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.
/// </summary> /// </summary>
/// <value> /// <value>
/// All basic nullable value types. /// All basic nullable value types.
/// </value> /// </value>
public static List<Type> AllBasicNullableValueTypes { get; } = new List<Type>( public static List<Type> AllBasicNullableValueTypes {
BasicTypesInfo get;
.Where(kvp => kvp.Value.IsNullableValueType) } = new List<Type>(
.Select(kvp => kvp.Key).ToArray()); BasicTypesInfo
} .Where(kvp => kvp.Value.IsNullableValueType)
.Select(kvp => kvp.Key).ToArray());
}
} }

View File

@ -1,39 +1,34 @@
namespace Unosquare.Swan using System;
{ using System.Text;
using System.Text;
namespace Unosquare.Swan {
/// <summary>
/// Contains useful constants and definitions.
/// </summary>
public static partial class Definitions {
/// <summary>
/// The MS Windows codepage 1252 encoding used in some legacy scenarios
/// such as default CSV text encoding from Excel.
/// </summary>
public static readonly Encoding Windows1252Encoding;
/// <summary> /// <summary>
/// Contains useful constants and definitions. /// The encoding associated with the default ANSI code page in the operating
/// system's regional and language settings.
/// </summary> /// </summary>
public static partial class Definitions public static readonly Encoding CurrentAnsiEncoding;
{
/// <summary>
/// The MS Windows codepage 1252 encoding used in some legacy scenarios
/// such as default CSV text encoding from Excel.
/// </summary>
public static readonly Encoding Windows1252Encoding;
/// <summary> /// <summary>
/// The encoding associated with the default ANSI code page in the operating /// Initializes the <see cref="Definitions"/> class.
/// system's regional and language settings. /// </summary>
/// </summary> static Definitions() {
public static readonly Encoding CurrentAnsiEncoding; CurrentAnsiEncoding = Encoding.GetEncoding(default(Int32));
try {
/// <summary> Windows1252Encoding = Encoding.GetEncoding(1252);
/// Initializes the <see cref="Definitions"/> class. } catch {
/// </summary> // ignore, the codepage is not available use default
static Definitions() Windows1252Encoding = CurrentAnsiEncoding;
{ }
CurrentAnsiEncoding = Encoding.GetEncoding(default(int));
try
{
Windows1252Encoding = Encoding.GetEncoding(1252);
}
catch
{
// ignore, the codepage is not available use default
Windows1252Encoding = CurrentAnsiEncoding;
}
}
} }
}
} }

View File

@ -1,60 +1,56 @@
namespace Unosquare.Swan namespace Unosquare.Swan {
{ /// <summary>
/// Enumeration of Operating Systems.
/// </summary>
public enum OperatingSystem {
/// <summary> /// <summary>
/// Enumeration of Operating Systems. /// Unknown OS
/// </summary> /// </summary>
public enum OperatingSystem Unknown,
{
/// <summary>
/// Unknown OS
/// </summary>
Unknown,
/// <summary>
/// Windows
/// </summary>
Windows,
/// <summary>
/// UNIX/Linux
/// </summary>
Unix,
/// <summary>
/// macOS (OSX)
/// </summary>
Osx,
}
/// <summary> /// <summary>
/// Enumerates the different Application Worker States. /// Windows
/// </summary> /// </summary>
public enum AppWorkerState Windows,
{
/// <summary>
/// The stopped
/// </summary>
Stopped,
/// <summary>
/// The running
/// </summary>
Running,
}
/// <summary> /// <summary>
/// Defines Endianness, big or little. /// UNIX/Linux
/// </summary> /// </summary>
public enum Endianness Unix,
{
/// <summary>
/// In big endian, you store the most significant byte in the smallest address.
/// </summary>
Big,
/// <summary> /// <summary>
/// In little endian, you store the least significant byte in the smallest address. /// macOS (OSX)
/// </summary> /// </summary>
Little, Osx,
} }
/// <summary>
/// Enumerates the different Application Worker States.
/// </summary>
public enum AppWorkerState {
/// <summary>
/// The stopped
/// </summary>
Stopped,
/// <summary>
/// The running
/// </summary>
Running,
}
/// <summary>
/// Defines Endianness, big or little.
/// </summary>
public enum Endianness {
/// <summary>
/// In big endian, you store the most significant byte in the smallest address.
/// </summary>
Big,
/// <summary>
/// In little endian, you store the least significant byte in the smallest address.
/// </summary>
Little,
}
} }

View File

@ -1,168 +1,181 @@
namespace Unosquare.Swan using System;
{
using System;
namespace Unosquare.Swan {
/// <summary>
/// Event arguments representing the message that is logged
/// on to the terminal. Use the properties to forward the data to
/// your logger of choice.
/// </summary>
/// <seealso cref="System.EventArgs" />
public class LogMessageReceivedEventArgs : EventArgs {
/// <summary> /// <summary>
/// Event arguments representing the message that is logged /// Initializes a new instance of the <see cref="LogMessageReceivedEventArgs" /> class.
/// on to the terminal. Use the properties to forward the data to
/// your logger of choice.
/// </summary> /// </summary>
/// <seealso cref="System.EventArgs" /> /// <param name="sequence">The sequence.</param>
public class LogMessageReceivedEventArgs : EventArgs /// <param name="messageType">Type of the message.</param>
{ /// <param name="utcDate">The UTC date.</param>
/// <summary> /// <param name="source">The source.</param>
/// Initializes a new instance of the <see cref="LogMessageReceivedEventArgs" /> class. /// <param name="message">The message.</param>
/// </summary> /// <param name="extendedData">The extended data.</param>
/// <param name="sequence">The sequence.</param> /// <param name="callerMemberName">Name of the caller member.</param>
/// <param name="messageType">Type of the message.</param> /// <param name="callerFilePath">The caller file path.</param>
/// <param name="utcDate">The UTC date.</param> /// <param name="callerLineNumber">The caller line number.</param>
/// <param name="source">The source.</param> public LogMessageReceivedEventArgs(
/// <param name="message">The message.</param> UInt64 sequence,
/// <param name="extendedData">The extended data.</param> LogMessageType messageType,
/// <param name="callerMemberName">Name of the caller member.</param> DateTime utcDate,
/// <param name="callerFilePath">The caller file path.</param> String source,
/// <param name="callerLineNumber">The caller line number.</param> String message,
public LogMessageReceivedEventArgs( Object extendedData,
ulong sequence, String callerMemberName,
LogMessageType messageType, String callerFilePath,
DateTime utcDate, Int32 callerLineNumber) {
string source, this.Sequence = sequence;
string message, this.MessageType = messageType;
object extendedData, this.UtcDate = utcDate;
string callerMemberName, this.Source = source;
string callerFilePath, this.Message = message;
int callerLineNumber) this.CallerMemberName = callerMemberName;
{ this.CallerFilePath = callerFilePath;
Sequence = sequence; this.CallerLineNumber = callerLineNumber;
MessageType = messageType; this.ExtendedData = extendedData;
UtcDate = utcDate;
Source = source;
Message = message;
CallerMemberName = callerMemberName;
CallerFilePath = callerFilePath;
CallerLineNumber = callerLineNumber;
ExtendedData = extendedData;
}
/// <summary>
/// Gets logging message sequence.
/// </summary>
/// <value>
/// The sequence.
/// </value>
public ulong Sequence { get; }
/// <summary>
/// Gets the type of the message.
/// It can be a combination as the enumeration is a set of bitwise flags.
/// </summary>
/// <value>
/// The type of the message.
/// </value>
public LogMessageType MessageType { get; }
/// <summary>
/// Gets the UTC date at which the event at which the message was logged.
/// </summary>
/// <value>
/// The UTC date.
/// </value>
public DateTime UtcDate { get; }
/// <summary>
/// Gets the name of the source where the logging message
/// came from. This can come empty if the logger did not set it.
/// </summary>
/// <value>
/// The source.
/// </value>
public string Source { get; }
/// <summary>
/// Gets the body of the message.
/// </summary>
/// <value>
/// The message.
/// </value>
public string Message { get; }
/// <summary>
/// Gets the name of the caller member.
/// </summary>
/// <value>
/// The name of the caller member.
/// </value>
public string CallerMemberName { get; }
/// <summary>
/// Gets the caller file path.
/// </summary>
/// <value>
/// The caller file path.
/// </value>
public string CallerFilePath { get; }
/// <summary>
/// Gets the caller line number.
/// </summary>
/// <value>
/// The caller line number.
/// </value>
public int CallerLineNumber { get; }
/// <summary>
/// Gets an object representing extended data.
/// It could be an exception or anything else.
/// </summary>
/// <value>
/// The extended data.
/// </value>
public object ExtendedData { get; }
/// <summary>
/// Gets the Extended Data properties cast as an Exception (if possible)
/// Otherwise, it return null.
/// </summary>
/// <value>
/// The exception.
/// </value>
public Exception Exception => ExtendedData as Exception;
} }
/// <summary> /// <summary>
/// Event arguments representing a message logged and about to be /// Gets logging message sequence.
/// displayed on the terminal (console). Set the CancelOutput property in the
/// event handler to prevent the terminal from displaying the message.
/// </summary> /// </summary>
/// <seealso cref="LogMessageReceivedEventArgs" /> /// <value>
public class LogMessageDisplayingEventArgs : LogMessageReceivedEventArgs /// The sequence.
{ /// </value>
/// <summary> public UInt64 Sequence {
/// Initializes a new instance of the <see cref="LogMessageDisplayingEventArgs"/> class. get;
/// </summary>
/// <param name="data">The <see cref="LogMessageReceivedEventArgs"/> instance containing the event data.</param>
public LogMessageDisplayingEventArgs(LogMessageReceivedEventArgs data)
: base(
data.Sequence,
data.MessageType,
data.UtcDate,
data.Source,
data.Message,
data.ExtendedData,
data.CallerMemberName,
data.CallerFilePath,
data.CallerLineNumber)
{
CancelOutput = false;
}
/// <summary>
/// Gets or sets a value indicating whether the displaying of the
/// logging message should be canceled.
/// </summary>
/// <value>
/// <c>true</c> if [cancel output]; otherwise, <c>false</c>.
/// </value>
public bool CancelOutput { get; set; }
} }
/// <summary>
/// Gets the type of the message.
/// It can be a combination as the enumeration is a set of bitwise flags.
/// </summary>
/// <value>
/// The type of the message.
/// </value>
public LogMessageType MessageType {
get;
}
/// <summary>
/// Gets the UTC date at which the event at which the message was logged.
/// </summary>
/// <value>
/// The UTC date.
/// </value>
public DateTime UtcDate {
get;
}
/// <summary>
/// Gets the name of the source where the logging message
/// came from. This can come empty if the logger did not set it.
/// </summary>
/// <value>
/// The source.
/// </value>
public String Source {
get;
}
/// <summary>
/// Gets the body of the message.
/// </summary>
/// <value>
/// The message.
/// </value>
public String Message {
get;
}
/// <summary>
/// Gets the name of the caller member.
/// </summary>
/// <value>
/// The name of the caller member.
/// </value>
public String CallerMemberName {
get;
}
/// <summary>
/// Gets the caller file path.
/// </summary>
/// <value>
/// The caller file path.
/// </value>
public String CallerFilePath {
get;
}
/// <summary>
/// Gets the caller line number.
/// </summary>
/// <value>
/// The caller line number.
/// </value>
public Int32 CallerLineNumber {
get;
}
/// <summary>
/// Gets an object representing extended data.
/// It could be an exception or anything else.
/// </summary>
/// <value>
/// The extended data.
/// </value>
public Object ExtendedData {
get;
}
/// <summary>
/// Gets the Extended Data properties cast as an Exception (if possible)
/// Otherwise, it return null.
/// </summary>
/// <value>
/// The exception.
/// </value>
public Exception Exception => this.ExtendedData as Exception;
}
/// <summary>
/// Event arguments representing a message logged and about to be
/// displayed on the terminal (console). Set the CancelOutput property in the
/// event handler to prevent the terminal from displaying the message.
/// </summary>
/// <seealso cref="LogMessageReceivedEventArgs" />
public class LogMessageDisplayingEventArgs : LogMessageReceivedEventArgs {
/// <summary>
/// Initializes a new instance of the <see cref="LogMessageDisplayingEventArgs"/> class.
/// </summary>
/// <param name="data">The <see cref="LogMessageReceivedEventArgs"/> instance containing the event data.</param>
public LogMessageDisplayingEventArgs(LogMessageReceivedEventArgs data)
: base(
data.Sequence,
data.MessageType,
data.UtcDate,
data.Source,
data.Message,
data.ExtendedData,
data.CallerMemberName,
data.CallerFilePath,
data.CallerLineNumber) => this.CancelOutput = false;
/// <summary>
/// Gets or sets a value indicating whether the displaying of the
/// logging message should be canceled.
/// </summary>
/// <value>
/// <c>true</c> if [cancel output]; otherwise, <c>false</c>.
/// </value>
public Boolean CancelOutput {
get; set;
}
}
} }

File diff suppressed because it is too large Load Diff

View File

@ -1,16 +1,14 @@
namespace Unosquare.Swan using System;
{ using System.Collections.Generic;
using System; using System.Linq;
using System.Collections.Generic;
using System.Linq;
/// <summary> namespace Unosquare.Swan {
/// Provides various extension methods for dates. /// <summary>
/// </summary> /// Provides various extension methods for dates.
public static class DateExtensions /// </summary>
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},
@ -18,213 +16,216 @@
{ "dayOfWeek", 6}, { "dayOfWeek", 6},
}; };
/// <summary> /// <summary>
/// Converts the date to a YYYY-MM-DD string. /// Converts the date to a YYYY-MM-DD string.
/// </summary> /// </summary>
/// <param name="date">The date.</param> /// <param name="date">The date.</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 date) public static String ToSortableDate(this DateTime date)
=> $"{date.Year:0000}-{date.Month:00}-{date.Day:00}"; => $"{date.Year:0000}-{date.Month:00}-{date.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="date">The date.</param> /// <param name="date">The date.</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 date) public static String ToSortableDateTime(this DateTime date)
=> $"{date.Year:0000}-{date.Month:00}-{date.Day:00} {date.Hour:00}:{date.Minute:00}:{date.Second:00}"; => $"{date.Year:0000}-{date.Month:00}-{date.Day:00} {date.Hour:00}:{date.Minute:00}:{date.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.
/// </summary> /// </summary>
/// <param name="sortableDate">The sortable date.</param> /// <param name="sortableDate">The sortable date.</param>
/// <returns> /// <returns>
/// A new instance of the DateTime structure to /// A new instance of the DateTime structure to
/// the specified year, month, day, hour, minute and second. /// the specified year, month, day, hour, minute and second.
/// </returns> /// </returns>
/// <exception cref="ArgumentNullException">sortableDate.</exception> /// <exception cref="ArgumentNullException">sortableDate.</exception>
/// <exception cref="Exception"> /// <exception cref="Exception">
/// Represents errors that occur during application execution. /// Represents errors that occur during application execution.
/// </exception> /// </exception>
/// <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 sortableDate) public static DateTime ToDateTime(this String sortableDate) {
{ if(String.IsNullOrWhiteSpace(sortableDate)) {
if (string.IsNullOrWhiteSpace(sortableDate)) throw new ArgumentNullException(nameof(sortableDate));
throw new ArgumentNullException(nameof(sortableDate)); }
var hour = 0; Int32 hour = 0;
var minute = 0; Int32 minute = 0;
var second = 0; Int32 second = 0;
var dateTimeParts = sortableDate.Split(' '); String[] dateTimeParts = sortableDate.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('-');
if (dateParts.Length != 3) throw new Exception();
var year = int.Parse(dateParts[0]);
var month = int.Parse(dateParts[1]);
var day = int.Parse(dateParts[2]);
if (dateTimeParts.Length > 1)
{
var timeParts = dateTimeParts[1].Split(':');
if (timeParts.Length != 3) throw new Exception();
hour = int.Parse(timeParts[0]);
minute = int.Parse(timeParts[1]);
second = int.Parse(timeParts[2]);
}
return new DateTime(year, month, day, hour, minute, second);
}
catch (Exception)
{
throw new ArgumentException("Unable to parse sortable date and time.", nameof(sortableDate));
}
} }
/// <summary> String[] dateParts = dateTimeParts[0].Split('-');
/// Creates a date's range. if(dateParts.Length != 3) {
/// </summary> throw new Exception();
/// <param name="startDate">The start date.</param> }
/// <param name="endDate">The end date.</param>
/// <returns>
/// A sequence of integral numbers within a specified date's range.
/// </returns>
public static IEnumerable<DateTime> DateRange(this DateTime startDate, DateTime endDate)
=> Enumerable.Range(0, (endDate - startDate).Days + 1).Select(d => startDate.AddDays(d));
/// <summary> Int32 year = Int32.Parse(dateParts[0]);
/// Rounds up a date to match a timespan. Int32 month = Int32.Parse(dateParts[1]);
/// </summary> Int32 day = Int32.Parse(dateParts[2]);
/// <param name="date">The datetime.</param>
/// <param name="timeSpan">The timespan to match.</param>
/// <returns>
/// A new instance of the DateTime structure to the specified datetime and timespan ticks.
/// </returns>
public static DateTime RoundUp(this DateTime date, TimeSpan timeSpan)
=> new DateTime(((date.Ticks + timeSpan.Ticks - 1) / timeSpan.Ticks) * timeSpan.Ticks);
/// <summary> if(dateTimeParts.Length > 1) {
/// Get this datetime as a Unix epoch timestamp (seconds since Jan 1, 1970, midnight UTC). String[] timeParts = dateTimeParts[1].Split(':');
/// </summary> if(timeParts.Length != 3) {
/// <param name="date">The date to convert.</param> throw new Exception();
/// <returns>Seconds since Unix epoch.</returns> }
public static long ToUnixEpochDate(this DateTime date)
{ hour = Int32.Parse(timeParts[0]);
minute = Int32.Parse(timeParts[1]);
second = Int32.Parse(timeParts[2]);
}
return new DateTime(year, month, day, hour, minute, second);
} catch(Exception) {
throw new ArgumentException("Unable to parse sortable date and time.", nameof(sortableDate));
}
}
/// <summary>
/// Creates a date's range.
/// </summary>
/// <param name="startDate">The start date.</param>
/// <param name="endDate">The end date.</param>
/// <returns>
/// A sequence of integral numbers within a specified date's range.
/// </returns>
public static IEnumerable<DateTime> DateRange(this DateTime startDate, DateTime endDate)
=> Enumerable.Range(0, (endDate - startDate).Days + 1).Select(d => startDate.AddDays(d));
/// <summary>
/// Rounds up a date to match a timespan.
/// </summary>
/// <param name="date">The datetime.</param>
/// <param name="timeSpan">The timespan to match.</param>
/// <returns>
/// A new instance of the DateTime structure to the specified datetime and timespan ticks.
/// </returns>
public static DateTime RoundUp(this DateTime date, TimeSpan timeSpan)
=> new DateTime((date.Ticks + timeSpan.Ticks - 1) / timeSpan.Ticks * timeSpan.Ticks);
/// <summary>
/// Get this datetime as a Unix epoch timestamp (seconds since Jan 1, 1970, midnight UTC).
/// </summary>
/// <param name="date">The date to convert.</param>
/// <returns>Seconds since Unix epoch.</returns>
public static Int64 ToUnixEpochDate(this DateTime date) {
#if NETSTANDARD2_0 #if NETSTANDARD2_0
return new DateTimeOffset(date).ToUniversalTime().ToUnixTimeSeconds(); return new DateTimeOffset(date).ToUniversalTime().ToUnixTimeSeconds();
#else #else
var epochTicks = new DateTime(1970, 1, 1).Ticks; Int64 epochTicks = new DateTime(1970, 1, 1).Ticks;
return (date.Ticks - epochTicks) / TimeSpan.TicksPerSecond; return (date.Ticks - epochTicks) / TimeSpan.TicksPerSecond;
#endif #endif
} }
/// <summary> /// <summary>
/// Compares a Date to another and returns a <c>DateTimeSpan</c>. /// Compares a Date to another and returns a <c>DateTimeSpan</c>.
/// </summary> /// </summary>
/// <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).
/// </summary> /// </summary>
/// <param name="date">The date.</param> /// <param name="date">The date.</param>
/// <param name="minute">The minute (0-59).</param> /// <param name="minute">The minute (0-59).</param>
/// <param name="hour">The hour. (0-23).</param> /// <param name="hour">The hour. (0-23).</param>
/// <param name="dayOfMonth">The day of month. (1-31).</param> /// <param name="dayOfMonth">The day of month. (1-31).</param>
/// <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 date, int? minute = null, int? hour = null, int? dayOfMonth = null, int? month = null, int? dayOfWeek = null) public static Boolean AsCronCanRun(this DateTime date, 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, date.Minute), GetElementParts(minute, date.Minute),
GetElementParts(hour, date.Hour), GetElementParts(hour, date.Hour),
GetElementParts(dayOfMonth, date.Day), GetElementParts(dayOfMonth, date.Day),
GetElementParts(month, date.Month), GetElementParts(month, date.Month),
GetElementParts(dayOfWeek, (int) date.DayOfWeek), GetElementParts(dayOfWeek, (Int32) date.DayOfWeek),
}; };
return results.Any(x => x != false); return results.Any(x => x != false);
} }
/// <summary> /// <summary>
/// Compare the Date elements(Months, Days, Hours, Minutes). /// Compare the Date elements(Months, Days, Hours, Minutes).
/// </summary> /// </summary>
/// <param name="date">The date.</param> /// <param name="date">The date.</param>
/// <param name="minute">The minute (0-59).</param> /// <param name="minute">The minute (0-59).</param>
/// <param name="hour">The hour. (0-23).</param> /// <param name="hour">The hour. (0-23).</param>
/// <param name="dayOfMonth">The day of month. (1-31).</param> /// <param name="dayOfMonth">The day of month. (1-31).</param>
/// <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 date, string minute = "*", string hour = "*", string dayOfMonth = "*", string month = "*", string dayOfWeek = "*") public static Boolean AsCronCanRun(this DateTime date, 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), date.Minute), GetElementParts(minute, nameof(minute), date.Minute),
GetElementParts(hour, nameof(hour), date.Hour), GetElementParts(hour, nameof(hour), date.Hour),
GetElementParts(dayOfMonth, nameof(dayOfMonth), date.Day), GetElementParts(dayOfMonth, nameof(dayOfMonth), date.Day),
GetElementParts(month, nameof(month), date.Month), GetElementParts(month, nameof(month), date.Month),
GetElementParts(dayOfWeek, nameof(dayOfWeek), (int) date.DayOfWeek), GetElementParts(dayOfWeek, nameof(dayOfWeek), (Int32) date.DayOfWeek),
}; };
return results.Any(x => x != false); return results.Any(x => x != false);
}
private static bool? GetElementParts(int? status, int value) => status.HasValue ? status.Value == value : (bool?) null;
private static bool? GetElementParts(string parts, string type, int value)
{
if (string.IsNullOrWhiteSpace(parts) || parts == "*") return null;
if (parts.Contains(","))
{
return parts.Split(',').Select(int.Parse).Contains(value);
}
var stop = DateRanges[type];
if (parts.Contains("/"))
{
var multiple = int.Parse(parts.Split('/').Last());
var start = type == "dayOfMonth" || type == "month" ? 1 : 0;
for (var i = start; i <= stop; i += multiple)
if (i == value) return true;
return false;
}
if (parts.Contains("-"))
{
var range = parts.Split('-');
var start = int.Parse(range.First());
stop = Math.Max(stop, int.Parse(range.Last()));
if ((type == "dayOfMonth" || type == "month") && start == 0)
start = 1;
for (var i = start; i <= stop; i++)
if (i == value) return true;
return false;
}
return int.Parse(parts) == value;
}
} }
private static Boolean? GetElementParts(Int32? status, Int32 value) => status.HasValue ? status.Value == value : (Boolean?)null;
private static Boolean? GetElementParts(String parts, String type, Int32 value) {
if(String.IsNullOrWhiteSpace(parts) || parts == "*") {
return null;
}
if(parts.Contains(",")) {
return parts.Split(',').Select(Int32.Parse).Contains(value);
}
Int32 stop = DateRanges[type];
if(parts.Contains("/")) {
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;
}
if(parts.Contains("-")) {
String[] range = parts.Split('-');
Int32 start = Int32.Parse(range.First());
stop = Math.Max(stop, Int32.Parse(range.Last()));
if((type == "dayOfMonth" || type == "month") && start == 0) {
start = 1;
}
for(Int32 i = start; i <= stop; i++) {
if(i == value) {
return true;
}
}
return false;
}
return Int32.Parse(parts) == value;
}
}
} }

View File

@ -1,77 +1,76 @@
namespace Unosquare.Swan using System;
{ using System.Collections.Generic;
using System;
using System.Collections.Generic; namespace Unosquare.Swan {
/// <summary>
/// Extension methods.
/// </summary>
public static partial class Extensions {
/// <summary>
/// Gets the value if exists or default.
/// </summary>
/// <typeparam name="TKey">The type of the key.</typeparam>
/// <typeparam name="TValue">The type of the value.</typeparam>
/// <param name="dict">The dictionary.</param>
/// <param name="key">The key.</param>
/// <param name="defaultValue">The default value.</param>
/// <returns>
/// The value of the provided key or default.
/// </returns>
/// <exception cref="ArgumentNullException">dict.</exception>
public static TValue GetValueOrDefault<TKey, TValue>(this IDictionary<TKey, TValue> dict, TKey key, TValue defaultValue = default) {
if(dict == null) {
throw new ArgumentNullException(nameof(dict));
}
return dict.ContainsKey(key) ? dict[key] : defaultValue;
}
/// <summary> /// <summary>
/// Extension methods. /// Adds a key/value pair to the Dictionary if the key does not already exist.
/// If the value is null, the key will not be updated.
///
/// Based on <c>ConcurrentDictionary.GetOrAdd</c> method.
/// </summary> /// </summary>
public static partial class Extensions /// <typeparam name="TKey">The type of the key.</typeparam>
{ /// <typeparam name="TValue">The type of the value.</typeparam>
/// <summary> /// <param name="dict">The dictionary.</param>
/// Gets the value if exists or default. /// <param name="key">The key.</param>
/// </summary> /// <param name="valueFactory">The value factory.</param>
/// <typeparam name="TKey">The type of the key.</typeparam> /// <returns>The value for the key.</returns>
/// <typeparam name="TValue">The type of the value.</typeparam> public static TValue GetOrAdd<TKey, TValue>(this IDictionary<TKey, TValue> dict, TKey key, Func<TKey, TValue> valueFactory) {
/// <param name="dict">The dictionary.</param> if(dict == null) {
/// <param name="key">The key.</param> throw new ArgumentNullException(nameof(dict));
/// <param name="defaultValue">The default value.</param> }
/// <returns>
/// The value of the provided key or default.
/// </returns>
/// <exception cref="ArgumentNullException">dict.</exception>
public static TValue GetValueOrDefault<TKey, TValue>(this IDictionary<TKey, TValue> dict, TKey key, TValue defaultValue = default)
{
if (dict == null)
throw new ArgumentNullException(nameof(dict));
return dict.ContainsKey(key) ? dict[key] : defaultValue; if(!dict.ContainsKey(key)) {
TValue value = valueFactory(key);
if(Equals(value, default)) {
return default;
} }
/// <summary> dict[key] = value;
/// Adds a key/value pair to the Dictionary if the key does not already exist. }
/// If the value is null, the key will not be updated.
///
/// Based on <c>ConcurrentDictionary.GetOrAdd</c> method.
/// </summary>
/// <typeparam name="TKey">The type of the key.</typeparam>
/// <typeparam name="TValue">The type of the value.</typeparam>
/// <param name="dict">The dictionary.</param>
/// <param name="key">The key.</param>
/// <param name="valueFactory">The value factory.</param>
/// <returns>The value for the key.</returns>
public static TValue GetOrAdd<TKey, TValue>(this IDictionary<TKey, TValue> dict, TKey key, Func<TKey, TValue> valueFactory)
{
if (dict == null)
throw new ArgumentNullException(nameof(dict));
if (!dict.ContainsKey(key)) return dict[key];
{
var value = valueFactory(key);
if (Equals(value, default)) return default;
dict[key] = value;
}
return dict[key];
}
/// <summary>
/// Executes the item action for each element in the Dictionary.
/// </summary>
/// <typeparam name="TKey">The type of the key.</typeparam>
/// <typeparam name="TValue">The type of the value.</typeparam>
/// <param name="dict">The dictionary.</param>
/// <param name="itemAction">The item action.</param>
/// <exception cref="ArgumentNullException">dict.</exception>
public static void ForEach<TKey, TValue>(this IDictionary<TKey, TValue> dict, Action<TKey, TValue> itemAction)
{
if (dict == null)
throw new ArgumentNullException(nameof(dict));
foreach (var kvp in dict)
{
itemAction(kvp.Key, kvp.Value);
}
}
} }
/// <summary>
/// Executes the item action for each element in the Dictionary.
/// </summary>
/// <typeparam name="TKey">The type of the key.</typeparam>
/// <typeparam name="TValue">The type of the value.</typeparam>
/// <param name="dict">The dictionary.</param>
/// <param name="itemAction">The item action.</param>
/// <exception cref="ArgumentNullException">dict.</exception>
public static void ForEach<TKey, TValue>(this IDictionary<TKey, TValue> dict, Action<TKey, TValue> itemAction) {
if(dict == null) {
throw new ArgumentNullException(nameof(dict));
}
foreach(KeyValuePair<TKey, TValue> kvp in dict) {
itemAction(kvp.Key, kvp.Value);
}
}
}
} }

View File

@ -1,179 +1,188 @@
namespace Unosquare.Swan using System;
{ using System.Collections.Generic;
using System; using System.Linq;
using System.Collections.Generic;
using System.Linq; namespace Unosquare.Swan {
/// <summary>
/// Functional programming extension methods.
/// </summary>
public static class FunctionalExtensions {
/// <summary>
/// Whens the specified condition.
/// </summary>
/// <typeparam name="T">The type of IQueryable.</typeparam>
/// <param name="list">The list.</param>
/// <param name="condition">The condition.</param>
/// <param name="fn">The function.</param>
/// <returns>
/// The IQueryable.
/// </returns>
/// <exception cref="ArgumentNullException">
/// this
/// or
/// condition
/// or
/// fn.
/// </exception>
public static IQueryable<T> When<T>(
this IQueryable<T> list,
Func<Boolean> condition,
Func<IQueryable<T>, IQueryable<T>> fn) {
if(list == null) {
throw new ArgumentNullException(nameof(list));
}
if(condition == null) {
throw new ArgumentNullException(nameof(condition));
}
if(fn == null) {
throw new ArgumentNullException(nameof(fn));
}
return condition() ? fn(list) : list;
}
/// <summary> /// <summary>
/// Functional programming extension methods. /// Whens the specified condition.
/// </summary> /// </summary>
public static class FunctionalExtensions /// <typeparam name="T">The type of IEnumerable.</typeparam>
{ /// <param name="list">The list.</param>
/// <summary> /// <param name="condition">The condition.</param>
/// Whens the specified condition. /// <param name="fn">The function.</param>
/// </summary> /// <returns>
/// <typeparam name="T">The type of IQueryable.</typeparam> /// The IEnumerable.
/// <param name="list">The list.</param> /// </returns>
/// <param name="condition">The condition.</param> /// <exception cref="ArgumentNullException">
/// <param name="fn">The function.</param> /// this
/// <returns> /// or
/// The IQueryable. /// condition
/// </returns> /// or
/// <exception cref="ArgumentNullException"> /// fn.
/// this /// </exception>
/// or public static IEnumerable<T> When<T>(
/// condition this IEnumerable<T> list,
/// or Func<Boolean> condition,
/// fn. Func<IEnumerable<T>, IEnumerable<T>> fn) {
/// </exception> if(list == null) {
public static IQueryable<T> When<T>( throw new ArgumentNullException(nameof(list));
this IQueryable<T> list, }
Func<bool> condition,
Func<IQueryable<T>, IQueryable<T>> fn)
{
if (list == null)
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;
}
/// <summary>
/// Whens the specified condition.
/// </summary>
/// <typeparam name="T">The type of IEnumerable.</typeparam>
/// <param name="list">The list.</param>
/// <param name="condition">The condition.</param>
/// <param name="fn">The function.</param>
/// <returns>
/// The IEnumerable.
/// </returns>
/// <exception cref="ArgumentNullException">
/// this
/// or
/// condition
/// or
/// fn.
/// </exception>
public static IEnumerable<T> When<T>(
this IEnumerable<T> list,
Func<bool> condition,
Func<IEnumerable<T>, IEnumerable<T>> fn)
{
if (list == null)
throw new ArgumentNullException(nameof(list));
if (condition == null)
throw new ArgumentNullException(nameof(condition));
if (fn == null)
throw new ArgumentNullException(nameof(fn));
return condition() ? fn(list) : list;
}
/// <summary>
/// Adds the value when the condition is true.
/// </summary>
/// <typeparam name="T">The type of IList element.</typeparam>
/// <param name="list">The list.</param>
/// <param name="condition">The condition.</param>
/// <param name="value">The value.</param>
/// <returns>
/// The IList.
/// </returns>
/// <exception cref="ArgumentNullException">
/// this
/// or
/// condition
/// or
/// value.
/// </exception>
public static IList<T> AddWhen<T>(
this IList<T> list,
Func<bool> condition,
Func<T> value)
{
if (list == null)
throw new ArgumentNullException(nameof(list));
if (condition == null)
throw new ArgumentNullException(nameof(condition));
if (value == null)
throw new ArgumentNullException(nameof(value));
if (condition())
list.Add(value());
return list;
}
/// <summary>
/// Adds the value when the condition is true.
/// </summary>
/// <typeparam name="T">The type of IList element.</typeparam>
/// <param name="list">The list.</param>
/// <param name="condition">if set to <c>true</c> [condition].</param>
/// <param name="value">The value.</param>
/// <returns>
/// The IList.
/// </returns>
/// <exception cref="ArgumentNullException">list.</exception>
public static IList<T> AddWhen<T>(
this IList<T> list,
bool condition,
T value)
{
if (list == null)
throw new ArgumentNullException(nameof(list));
if (condition)
list.Add(value);
return list;
}
/// <summary>
/// Adds the range when the condition is true.
/// </summary>
/// <typeparam name="T">The type of List element.</typeparam>
/// <param name="list">The list.</param>
/// <param name="condition">The condition.</param>
/// <param name="value">The value.</param>
/// <returns>
/// The List.
/// </returns>
/// <exception cref="ArgumentNullException">
/// this
/// or
/// condition
/// or
/// value.
/// </exception>
public static List<T> AddRangeWhen<T>(
this List<T> list,
Func<bool> condition,
Func<IEnumerable<T>> value)
{
if (list == null)
throw new ArgumentNullException(nameof(list));
if (condition == null)
throw new ArgumentNullException(nameof(condition));
if (value == null)
throw new ArgumentNullException(nameof(value));
if (condition())
list.AddRange(value());
return list;
}
} }
/// <summary>
/// Adds the value when the condition is true.
/// </summary>
/// <typeparam name="T">The type of IList element.</typeparam>
/// <param name="list">The list.</param>
/// <param name="condition">The condition.</param>
/// <param name="value">The value.</param>
/// <returns>
/// The IList.
/// </returns>
/// <exception cref="ArgumentNullException">
/// this
/// or
/// condition
/// or
/// value.
/// </exception>
public static IList<T> AddWhen<T>(
this IList<T> list,
Func<Boolean> condition,
Func<T> value) {
if(list == null) {
throw new ArgumentNullException(nameof(list));
}
if(condition == null) {
throw new ArgumentNullException(nameof(condition));
}
if(value == null) {
throw new ArgumentNullException(nameof(value));
}
if(condition()) {
list.Add(value());
}
return list;
}
/// <summary>
/// Adds the value when the condition is true.
/// </summary>
/// <typeparam name="T">The type of IList element.</typeparam>
/// <param name="list">The list.</param>
/// <param name="condition">if set to <c>true</c> [condition].</param>
/// <param name="value">The value.</param>
/// <returns>
/// The IList.
/// </returns>
/// <exception cref="ArgumentNullException">list.</exception>
public static IList<T> AddWhen<T>(
this IList<T> list,
Boolean condition,
T value) {
if(list == null) {
throw new ArgumentNullException(nameof(list));
}
if(condition) {
list.Add(value);
}
return list;
}
/// <summary>
/// Adds the range when the condition is true.
/// </summary>
/// <typeparam name="T">The type of List element.</typeparam>
/// <param name="list">The list.</param>
/// <param name="condition">The condition.</param>
/// <param name="value">The value.</param>
/// <returns>
/// The List.
/// </returns>
/// <exception cref="ArgumentNullException">
/// this
/// or
/// condition
/// or
/// value.
/// </exception>
public static List<T> AddRangeWhen<T>(
this List<T> list,
Func<Boolean> condition,
Func<IEnumerable<T>> value) {
if(list == null) {
throw new ArgumentNullException(nameof(list));
}
if(condition == null) {
throw new ArgumentNullException(nameof(condition));
}
if(value == null) {
throw new ArgumentNullException(nameof(value));
}
if(condition()) {
list.AddRange(value());
}
return list;
}
}
} }

View File

@ -1,123 +1,121 @@
namespace Unosquare.Swan using System;
{ using System.Collections.Concurrent;
using System; using System.Collections;
using System.Collections.Concurrent; using System.Linq;
using System.Collections; using System.Reflection;
using System.Linq; using System.Collections.Generic;
using System.Reflection; using Unosquare.Swan.Attributes;
using System.Collections.Generic;
using Attributes; namespace Unosquare.Swan {
/// <summary>
/// Provides various extension methods for Reflection and Types.
/// </summary>
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<Boolean, PropertyInfo>, Action<Object, Object[]>>> CacheSetMethods =
new Lazy<ConcurrentDictionary<Tuple<Boolean, PropertyInfo>, Action<Object, Object[]>>>(() => new ConcurrentDictionary<Tuple<Boolean, PropertyInfo>, Action<Object, Object[]>>(), true);
#region Assembly Extensions
/// <summary> /// <summary>
/// Provides various extension methods for Reflection and Types. /// Gets all types within an assembly in a safe manner.
/// </summary> /// </summary>
public static class ReflectionExtensions /// <param name="assembly">The assembly.</param>
{ /// <returns>
private static readonly Lazy<ConcurrentDictionary<Tuple<bool, PropertyInfo>, Func<object, object>>> CacheGetMethods = /// Array of Type objects representing the types specified by an assembly.
new Lazy<ConcurrentDictionary<Tuple<bool, PropertyInfo>, Func<object, object>>>(() => new ConcurrentDictionary<Tuple<bool, PropertyInfo>, Func<object, object>>(), true); /// </returns>
/// <exception cref="ArgumentNullException">assembly.</exception>
public static IEnumerable<Type> GetAllTypes(this Assembly assembly) {
if(assembly == null) {
throw new ArgumentNullException(nameof(assembly));
}
private static readonly Lazy<ConcurrentDictionary<Tuple<bool, PropertyInfo>, Action<object, object[]>>> CacheSetMethods = try {
new Lazy<ConcurrentDictionary<Tuple<bool, PropertyInfo>, Action<object, object[]>>>(() => new ConcurrentDictionary<Tuple<bool, PropertyInfo>, Action<object, object[]>>(), true); return assembly.GetTypes();
} catch(ReflectionTypeLoadException e) {
return e.Types.Where(t => t != null);
}
}
#region Assembly Extensions #endregion
/// <summary> #region Type Extensions
/// Gets all types within an assembly in a safe manner.
/// </summary>
/// <param name="assembly">The assembly.</param>
/// <returns>
/// Array of Type objects representing the types specified by an assembly.
/// </returns>
/// <exception cref="ArgumentNullException">assembly.</exception>
public static IEnumerable<Type> GetAllTypes(this Assembly assembly)
{
if (assembly == null)
throw new ArgumentNullException(nameof(assembly));
try /// <summary>
{ /// The closest programmatic equivalent of default(T).
return assembly.GetTypes(); /// </summary>
} /// <param name="type">The type.</param>
catch (ReflectionTypeLoadException e) /// <returns>
{ /// Default value of this type.
return e.Types.Where(t => t != null); /// </returns>
} /// <exception cref="ArgumentNullException">type.</exception>
} public static Object GetDefault(this Type type) {
if(type == null) {
throw new ArgumentNullException(nameof(type));
}
#endregion return type.IsValueType() ? Activator.CreateInstance(type) : null;
}
#region Type Extensions /// <summary>
/// Determines whether this type is compatible with ICollection.
/// </summary>
/// <param name="sourceType">The type.</param>
/// <returns>
/// <c>true</c> if the specified source type is collection; otherwise, <c>false</c>.
/// </returns>
/// <exception cref="ArgumentNullException">sourceType.</exception>
public static Boolean IsCollection(this Type sourceType) {
if(sourceType == null) {
throw new ArgumentNullException(nameof(sourceType));
}
/// <summary> return sourceType != typeof(String) &&
/// The closest programmatic equivalent of default(T).
/// </summary>
/// <param name="type">The type.</param>
/// <returns>
/// Default value of this type.
/// </returns>
/// <exception cref="ArgumentNullException">type.</exception>
public static object GetDefault(this Type type)
{
if (type == null)
throw new ArgumentNullException(nameof(type));
return type.IsValueType() ? Activator.CreateInstance(type) : null;
}
/// <summary>
/// Determines whether this type is compatible with ICollection.
/// </summary>
/// <param name="sourceType">The type.</param>
/// <returns>
/// <c>true</c> if the specified source type is collection; otherwise, <c>false</c>.
/// </returns>
/// <exception cref="ArgumentNullException">sourceType.</exception>
public static bool IsCollection(this Type sourceType)
{
if (sourceType == null)
throw new ArgumentNullException(nameof(sourceType));
return sourceType != typeof(string) &&
typeof(IEnumerable).IsAssignableFrom(sourceType); typeof(IEnumerable).IsAssignableFrom(sourceType);
} }
/// <summary> /// <summary>
/// Gets a method from a type given the method name, binding flags, generic types and parameter types. /// Gets a method from a type given the method name, binding flags, generic types and parameter types.
/// </summary> /// </summary>
/// <param name="type">Type of the source.</param> /// <param name="type">Type of the source.</param>
/// <param name="bindingFlags">The binding flags.</param> /// <param name="bindingFlags">The binding flags.</param>
/// <param name="methodName">Name of the method.</param> /// <param name="methodName">Name of the method.</param>
/// <param name="genericTypes">The generic types.</param> /// <param name="genericTypes">The generic types.</param>
/// <param name="parameterTypes">The parameter types.</param> /// <param name="parameterTypes">The parameter types.</param>
/// <returns> /// <returns>
/// An object that represents the method with the specified name. /// An object that represents the method with the specified name.
/// </returns> /// </returns>
/// <exception cref="System.Reflection.AmbiguousMatchException"> /// <exception cref="System.Reflection.AmbiguousMatchException">
/// 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, this Type type,
BindingFlags bindingFlags, BindingFlags bindingFlags,
string methodName, String methodName,
Type[] genericTypes, Type[] genericTypes,
Type[] parameterTypes) Type[] parameterTypes) {
{ if(type == null) {
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)
@ -125,345 +123,325 @@
.Where(mi => mi.GetParameters().Select(pi => pi.ParameterType).SequenceEqual(parameterTypes)) .Where(mi => mi.GetParameters().Select(pi => pi.ParameterType).SequenceEqual(parameterTypes))
.ToList(); .ToList();
return methods.Count > 1 ? throw new AmbiguousMatchException() : methods.FirstOrDefault(); return methods.Count > 1 ? throw new AmbiguousMatchException() : methods.FirstOrDefault();
}
/// <summary>
/// Determines whether this instance is class.
/// </summary>
/// <param name="type">The type.</param>
/// <returns>
/// <c>true</c> if the specified type is class; otherwise, <c>false</c>.
/// </returns>
public static bool IsClass(this Type type) => type.GetTypeInfo().IsClass;
/// <summary>
/// Determines whether this instance is abstract.
/// </summary>
/// <param name="type">The type.</param>
/// <returns>
/// <c>true</c> if the specified type is abstract; otherwise, <c>false</c>.
/// </returns>
public static bool IsAbstract(this Type type) => type.GetTypeInfo().IsAbstract;
/// <summary>
/// Determines whether this instance is interface.
/// </summary>
/// <param name="type">The type.</param>
/// <returns>
/// <c>true</c> if the specified type is interface; otherwise, <c>false</c>.
/// </returns>
public static bool IsInterface(this Type type) => type.GetTypeInfo().IsInterface;
/// <summary>
/// Determines whether this instance is primitive.
/// </summary>
/// <param name="type">The type.</param>
/// <returns>
/// <c>true</c> if the specified type is primitive; otherwise, <c>false</c>.
/// </returns>
public static bool IsPrimitive(this Type type) => type.GetTypeInfo().IsPrimitive;
/// <summary>
/// Determines whether [is value type].
/// </summary>
/// <param name="type">The type.</param>
/// <returns>
/// <c>true</c> if [is value type] [the specified type]; otherwise, <c>false</c>.
/// </returns>
public static bool IsValueType(this Type type) => type.GetTypeInfo().IsValueType;
/// <summary>
/// Determines whether [is generic type].
/// </summary>
/// <param name="type">The type.</param>
/// <returns>
/// <c>true</c> if [is generic type] [the specified type]; otherwise, <c>false</c>.
/// </returns>
public static bool IsGenericType(this Type type) => type.GetTypeInfo().IsGenericType;
/// <summary>
/// Determines whether [is generic parameter].
/// </summary>
/// <param name="type">The type.</param>
/// <returns>
/// <c>true</c> if [is generic parameter] [the specified type]; otherwise, <c>false</c>.
/// </returns>
public static bool IsGenericParameter(this Type type) => type.IsGenericParameter;
/// <summary>
/// Determines whether the specified attribute type is defined.
/// </summary>
/// <param name="type">The type.</param>
/// <param name="attributeType">Type of the attribute.</param>
/// <param name="inherit">if set to <c>true</c> [inherit].</param>
/// <returns>
/// <c>true</c> if the specified attribute type is defined; otherwise, <c>false</c>.
/// </returns>
public static bool IsDefined(this Type type, Type attributeType, bool inherit) =>
type.GetTypeInfo().IsDefined(attributeType, inherit);
/// <summary>
/// Gets the custom attributes.
/// </summary>
/// <param name="type">The type.</param>
/// <param name="attributeType">Type of the attribute.</param>
/// <param name="inherit">if set to <c>true</c> [inherit].</param>
/// <returns>
/// Attributes associated with the property represented by this PropertyInfo object.
/// </returns>
public static Attribute[] GetCustomAttributes(this Type type, Type attributeType, bool inherit) =>
type.GetTypeInfo().GetCustomAttributes(attributeType, inherit).Cast<Attribute>().ToArray();
/// <summary>
/// Determines whether [is generic type definition].
/// </summary>
/// <param name="type">The type.</param>
/// <returns>
/// <c>true</c> if [is generic type definition] [the specified type]; otherwise, <c>false</c>.
/// </returns>
public static bool IsGenericTypeDefinition(this Type type) => type.GetTypeInfo().IsGenericTypeDefinition;
/// <summary>
/// Bases the type.
/// </summary>
/// <param name="type">The type.</param>
/// <returns>returns a type of data.</returns>
public static Type BaseType(this Type type) => type.GetTypeInfo().BaseType;
/// <summary>
/// Assemblies the specified type.
/// </summary>
/// <param name="type">The type.</param>
/// <returns>returns an Assembly object.</returns>
public static Assembly Assembly(this Type type) => type.GetTypeInfo().Assembly;
/// <summary>
/// Determines whether [is i enumerable request].
/// </summary>
/// <param name="type">The type.</param>
/// <returns>
/// <c>true</c> if [is i enumerable request] [the specified type]; otherwise, <c>false</c>.
/// </returns>
/// <exception cref="ArgumentNullException">type.</exception>
public static bool IsIEnumerable(this Type type)
=> type == null
? throw new ArgumentNullException(nameof(type))
: type.IsGenericType() && type.GetGenericTypeDefinition() == typeof(IEnumerable<>);
#endregion
/// <summary>
/// Tries to parse using the basic types.
/// </summary>
/// <param name="type">The type.</param>
/// <param name="value">The value.</param>
/// <param name="result">The result.</param>
/// <returns>
/// <c>true</c> if parsing was successful; otherwise, <c>false</c>.
/// </returns>
public static bool TryParseBasicType(this Type type, object value, out object result)
=> TryParseBasicType(type, value.ToStringInvariant(), out result);
/// <summary>
/// Tries to parse using the basic types.
/// </summary>
/// <param name="type">The type.</param>
/// <param name="value">The value.</param>
/// <param name="result">The result.</param>
/// <returns>
/// <c>true</c> if parsing was successful; otherwise, <c>false</c>.
/// </returns>
public static bool TryParseBasicType(this Type type, string value, out object result)
{
result = null;
return Definitions.BasicTypesInfo.ContainsKey(type) && Definitions.BasicTypesInfo[type].TryParse(value, out result);
}
/// <summary>
/// Tries the type of the set basic value to a property.
/// </summary>
/// <param name="property">The property.</param>
/// <param name="value">The value.</param>
/// <param name="obj">The object.</param>
/// <returns>
/// <c>true</c> if parsing was successful; otherwise, <c>false</c>.
/// </returns>
public static bool TrySetBasicType(this PropertyInfo property, object value, object obj)
{
try
{
if (property.PropertyType.TryParseBasicType(value, out var propertyValue))
{
property.SetValue(obj, propertyValue);
return true;
}
}
catch
{
// swallow
}
return false;
}
/// <summary>
/// Tries the type of the set to an array a basic type.
/// </summary>
/// <param name="type">The type.</param>
/// <param name="value">The value.</param>
/// <param name="array">The array.</param>
/// <param name="index">The index.</param>
/// <returns>
/// <c>true</c> if parsing was successful; otherwise, <c>false</c>.
/// </returns>
public static bool TrySetArrayBasicType(this Type type, object value, Array array, int index)
{
try
{
if (value == null)
{
array.SetValue(null, index);
return true;
}
if (type.TryParseBasicType(value, out var propertyValue))
{
array.SetValue(propertyValue, index);
return true;
}
if (type.GetTypeInfo().IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>))
{
array.SetValue(null, index);
return true;
}
}
catch
{
// swallow
}
return false;
}
/// <summary>
/// Tries to set a property array with another array.
/// </summary>
/// <param name="propertyInfo">The property.</param>
/// <param name="value">The value.</param>
/// <param name="obj">The object.</param>
/// <returns>
/// <c>true</c> if parsing was successful; otherwise, <c>false</c>.
/// </returns>
public static bool TrySetArray(this PropertyInfo propertyInfo, IEnumerable<object> value, object obj)
{
var elementType = propertyInfo.PropertyType.GetElementType();
if (elementType == null)
return false;
var targetArray = Array.CreateInstance(elementType, value.Count());
var i = 0;
foreach (var sourceElement in value)
{
var result = elementType.TrySetArrayBasicType(sourceElement, targetArray, i++);
if (!result) return false;
}
propertyInfo.SetValue(obj, targetArray);
return true;
}
/// <summary>
/// Gets property actual value or <c>PropertyDisplayAttribute.DefaultValue</c> if presented.
///
/// If the <c>PropertyDisplayAttribute.Format</c> value is presented, the property value
/// will be formatted accordingly.
///
/// If the object contains a null value, a empty string will be returned.
/// </summary>
/// <param name="propertyInfo">The property information.</param>
/// <param name="obj">The object.</param>
/// <returns>The property value or null.</returns>
public static string ToFormattedString(this PropertyInfo propertyInfo, object obj)
{
try
{
var value = propertyInfo.GetValue(obj);
var attr = Runtime.AttributeCache.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
{
return null;
}
}
/// <summary>
/// Gets a MethodInfo from a Property Get method.
/// </summary>
/// <param name="propertyInfo">The property information.</param>
/// <param name="nonPublic">if set to <c>true</c> [non public].</param>
/// <returns>
/// The cached MethodInfo.
/// </returns>
public static Func<object, object> GetCacheGetMethod(this PropertyInfo propertyInfo, bool nonPublic = false)
{
var key = Tuple.Create(!nonPublic, propertyInfo);
return !nonPublic && !CacheGetMethods.Value.ContainsKey(key) && !propertyInfo.GetGetMethod(true).IsPublic
? null
: CacheGetMethods.Value
.GetOrAdd(key,
x => y => x.Item2.GetGetMethod(nonPublic).Invoke(y, null));
}
/// <summary>
/// Gets a MethodInfo from a Property Set method.
/// </summary>
/// <param name="propertyInfo">The property information.</param>
/// <param name="nonPublic">if set to <c>true</c> [non public].</param>
/// <returns>
/// The cached MethodInfo.
/// </returns>
public static Action<object, object[]> GetCacheSetMethod(this PropertyInfo propertyInfo, bool nonPublic = false)
{
var key = Tuple.Create(!nonPublic, propertyInfo);
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));
}
private static string ConvertObjectAndFormat(Type propertyType, object value, string format)
{
if (propertyType == typeof(DateTime) || propertyType == typeof(DateTime?))
return Convert.ToDateTime(value).ToString(format);
if (propertyType == typeof(int) || propertyType == typeof(int?))
return Convert.ToInt32(value).ToString(format);
if (propertyType == typeof(decimal) || propertyType == typeof(decimal?))
return Convert.ToDecimal(value).ToString(format);
if (propertyType == typeof(double) || propertyType == typeof(double?))
return Convert.ToDouble(value).ToString(format);
if (propertyType == typeof(byte) || propertyType == typeof(byte?))
return Convert.ToByte(value).ToString(format);
return value?.ToString() ?? string.Empty;
}
} }
/// <summary>
/// Determines whether this instance is class.
/// </summary>
/// <param name="type">The type.</param>
/// <returns>
/// <c>true</c> if the specified type is class; otherwise, <c>false</c>.
/// </returns>
public static Boolean IsClass(this Type type) => type.GetTypeInfo().IsClass;
/// <summary>
/// Determines whether this instance is abstract.
/// </summary>
/// <param name="type">The type.</param>
/// <returns>
/// <c>true</c> if the specified type is abstract; otherwise, <c>false</c>.
/// </returns>
public static Boolean IsAbstract(this Type type) => type.GetTypeInfo().IsAbstract;
/// <summary>
/// Determines whether this instance is interface.
/// </summary>
/// <param name="type">The type.</param>
/// <returns>
/// <c>true</c> if the specified type is interface; otherwise, <c>false</c>.
/// </returns>
public static Boolean IsInterface(this Type type) => type.GetTypeInfo().IsInterface;
/// <summary>
/// Determines whether this instance is primitive.
/// </summary>
/// <param name="type">The type.</param>
/// <returns>
/// <c>true</c> if the specified type is primitive; otherwise, <c>false</c>.
/// </returns>
public static Boolean IsPrimitive(this Type type) => type.GetTypeInfo().IsPrimitive;
/// <summary>
/// Determines whether [is value type].
/// </summary>
/// <param name="type">The type.</param>
/// <returns>
/// <c>true</c> if [is value type] [the specified type]; otherwise, <c>false</c>.
/// </returns>
public static Boolean IsValueType(this Type type) => type.GetTypeInfo().IsValueType;
/// <summary>
/// Determines whether [is generic type].
/// </summary>
/// <param name="type">The type.</param>
/// <returns>
/// <c>true</c> if [is generic type] [the specified type]; otherwise, <c>false</c>.
/// </returns>
public static Boolean IsGenericType(this Type type) => type.GetTypeInfo().IsGenericType;
/// <summary>
/// Determines whether [is generic parameter].
/// </summary>
/// <param name="type">The type.</param>
/// <returns>
/// <c>true</c> if [is generic parameter] [the specified type]; otherwise, <c>false</c>.
/// </returns>
public static Boolean IsGenericParameter(this Type type) => type.IsGenericParameter;
/// <summary>
/// Determines whether the specified attribute type is defined.
/// </summary>
/// <param name="type">The type.</param>
/// <param name="attributeType">Type of the attribute.</param>
/// <param name="inherit">if set to <c>true</c> [inherit].</param>
/// <returns>
/// <c>true</c> if the specified attribute type is defined; otherwise, <c>false</c>.
/// </returns>
public static Boolean IsDefined(this Type type, Type attributeType, Boolean inherit) =>
type.GetTypeInfo().IsDefined(attributeType, inherit);
/// <summary>
/// Gets the custom attributes.
/// </summary>
/// <param name="type">The type.</param>
/// <param name="attributeType">Type of the attribute.</param>
/// <param name="inherit">if set to <c>true</c> [inherit].</param>
/// <returns>
/// Attributes associated with the property represented by this PropertyInfo object.
/// </returns>
public static Attribute[] GetCustomAttributes(this Type type, Type attributeType, Boolean inherit) =>
type.GetTypeInfo().GetCustomAttributes(attributeType, inherit).Cast<Attribute>().ToArray();
/// <summary>
/// Determines whether [is generic type definition].
/// </summary>
/// <param name="type">The type.</param>
/// <returns>
/// <c>true</c> if [is generic type definition] [the specified type]; otherwise, <c>false</c>.
/// </returns>
public static Boolean IsGenericTypeDefinition(this Type type) => type.GetTypeInfo().IsGenericTypeDefinition;
/// <summary>
/// Bases the type.
/// </summary>
/// <param name="type">The type.</param>
/// <returns>returns a type of data.</returns>
public static Type BaseType(this Type type) => type.GetTypeInfo().BaseType;
/// <summary>
/// Assemblies the specified type.
/// </summary>
/// <param name="type">The type.</param>
/// <returns>returns an Assembly object.</returns>
public static Assembly Assembly(this Type type) => type.GetTypeInfo().Assembly;
/// <summary>
/// Determines whether [is i enumerable request].
/// </summary>
/// <param name="type">The type.</param>
/// <returns>
/// <c>true</c> if [is i enumerable request] [the specified type]; otherwise, <c>false</c>.
/// </returns>
/// <exception cref="ArgumentNullException">type.</exception>
public static Boolean IsIEnumerable(this Type type)
=> type == null
? throw new ArgumentNullException(nameof(type))
: type.IsGenericType() && type.GetGenericTypeDefinition() == typeof(IEnumerable<>);
#endregion
/// <summary>
/// Tries to parse using the basic types.
/// </summary>
/// <param name="type">The type.</param>
/// <param name="value">The value.</param>
/// <param name="result">The result.</param>
/// <returns>
/// <c>true</c> if parsing was successful; otherwise, <c>false</c>.
/// </returns>
public static Boolean TryParseBasicType(this Type type, Object value, out Object result)
=> TryParseBasicType(type, value.ToStringInvariant(), out result);
/// <summary>
/// Tries to parse using the basic types.
/// </summary>
/// <param name="type">The type.</param>
/// <param name="value">The value.</param>
/// <param name="result">The result.</param>
/// <returns>
/// <c>true</c> if parsing was successful; otherwise, <c>false</c>.
/// </returns>
public static Boolean TryParseBasicType(this Type type, String value, out Object result) {
result = null;
return Definitions.BasicTypesInfo.ContainsKey(type) && Definitions.BasicTypesInfo[type].TryParse(value, out result);
}
/// <summary>
/// Tries the type of the set basic value to a property.
/// </summary>
/// <param name="property">The property.</param>
/// <param name="value">The value.</param>
/// <param name="obj">The object.</param>
/// <returns>
/// <c>true</c> if parsing was successful; otherwise, <c>false</c>.
/// </returns>
public static Boolean TrySetBasicType(this PropertyInfo property, Object value, Object obj) {
try {
if(property.PropertyType.TryParseBasicType(value, out Object propertyValue)) {
property.SetValue(obj, propertyValue);
return true;
}
} catch {
// swallow
}
return false;
}
/// <summary>
/// Tries the type of the set to an array a basic type.
/// </summary>
/// <param name="type">The type.</param>
/// <param name="value">The value.</param>
/// <param name="array">The array.</param>
/// <param name="index">The index.</param>
/// <returns>
/// <c>true</c> if parsing was successful; otherwise, <c>false</c>.
/// </returns>
public static Boolean TrySetArrayBasicType(this Type type, Object value, Array array, Int32 index) {
try {
if(value == null) {
array.SetValue(null, index);
return true;
}
if(type.TryParseBasicType(value, out Object propertyValue)) {
array.SetValue(propertyValue, index);
return true;
}
if(type.GetTypeInfo().IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>)) {
array.SetValue(null, index);
return true;
}
} catch {
// swallow
}
return false;
}
/// <summary>
/// Tries to set a property array with another array.
/// </summary>
/// <param name="propertyInfo">The property.</param>
/// <param name="value">The value.</param>
/// <param name="obj">The object.</param>
/// <returns>
/// <c>true</c> if parsing was successful; otherwise, <c>false</c>.
/// </returns>
public static Boolean TrySetArray(this PropertyInfo propertyInfo, IEnumerable<Object> value, Object obj) {
Type elementType = propertyInfo.PropertyType.GetElementType();
if(elementType == null) {
return false;
}
Array targetArray = Array.CreateInstance(elementType, value.Count());
Int32 i = 0;
foreach(Object sourceElement in value) {
Boolean result = elementType.TrySetArrayBasicType(sourceElement, targetArray, i++);
if(!result) {
return false;
}
}
propertyInfo.SetValue(obj, targetArray);
return true;
}
/// <summary>
/// Gets property actual value or <c>PropertyDisplayAttribute.DefaultValue</c> if presented.
///
/// If the <c>PropertyDisplayAttribute.Format</c> value is presented, the property value
/// will be formatted accordingly.
///
/// If the object contains a null value, a empty string will be returned.
/// </summary>
/// <param name="propertyInfo">The property information.</param>
/// <param name="obj">The object.</param>
/// <returns>The property value or null.</returns>
public static String ToFormattedString(this PropertyInfo propertyInfo, Object obj) {
try {
Object value = propertyInfo.GetValue(obj);
PropertyDisplayAttribute attr = Runtime.AttributeCache.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;
}
}
/// <summary>
/// Gets a MethodInfo from a Property Get method.
/// </summary>
/// <param name="propertyInfo">The property information.</param>
/// <param name="nonPublic">if set to <c>true</c> [non public].</param>
/// <returns>
/// The cached MethodInfo.
/// </returns>
public static Func<Object, Object> GetCacheGetMethod(this PropertyInfo propertyInfo, Boolean nonPublic = false) {
Tuple<Boolean, PropertyInfo> key = Tuple.Create(!nonPublic, propertyInfo);
return !nonPublic && !CacheGetMethods.Value.ContainsKey(key) && !propertyInfo.GetGetMethod(true).IsPublic
? null
: CacheGetMethods.Value
.GetOrAdd(key,
x => y => x.Item2.GetGetMethod(nonPublic).Invoke(y, null));
}
/// <summary>
/// Gets a MethodInfo from a Property Set method.
/// </summary>
/// <param name="propertyInfo">The property information.</param>
/// <param name="nonPublic">if set to <c>true</c> [non public].</param>
/// <returns>
/// The cached MethodInfo.
/// </returns>
public static Action<Object, Object[]> GetCacheSetMethod(this PropertyInfo propertyInfo, Boolean nonPublic = false) {
Tuple<Boolean, PropertyInfo> key = Tuple.Create(!nonPublic, propertyInfo);
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));
}
private static String ConvertObjectAndFormat(Type propertyType, Object value, String format) => propertyType == typeof(DateTime) || propertyType == typeof(DateTime?)
? Convert.ToDateTime(value).ToString(format)
: propertyType == typeof(Int32) || propertyType == typeof(Int32?)
? Convert.ToInt32(value).ToString(format)
: propertyType == typeof(Decimal) || propertyType == typeof(Decimal?)
? Convert.ToDecimal(value).ToString(format)
: propertyType == typeof(Double) || propertyType == typeof(Double?)
? Convert.ToDouble(value).ToString(format)
: propertyType == typeof(Byte) || propertyType == typeof(Byte?)
? Convert.ToByte(value).ToString(format)
: value?.ToString() ?? String.Empty;
}
} }

View File

@ -1,96 +1,91 @@
namespace Unosquare.Swan using Unosquare.Swan.Formatters;
{ using System;
using Formatters; using System.IO;
using System; using System.Linq;
using System.IO; using System.Security.Cryptography;
using System.Linq; using System.Text;
using System.Security.Cryptography; using System.Text.RegularExpressions;
using System.Text;
using System.Text.RegularExpressions; namespace Unosquare.Swan {
/// <summary>
/// String related extension methods.
/// </summary>
public static class StringExtensions {
#region Private Declarations
private const RegexOptions StandardRegexOptions =
RegexOptions.Multiline | RegexOptions.Compiled | RegexOptions.CultureInvariant;
private static readonly String[] ByteSuffixes = { "B", "KB", "MB", "GB", "TB" };
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<SHA256> SHA256Hasher = new Lazy<SHA256>(SHA256.Create, true);
private static readonly Lazy<SHA512> SHA512Hasher = new Lazy<SHA512>(SHA512.Create, true);
private static readonly Lazy<Regex> SplitLinesRegex =
new Lazy<Regex>(
() => new Regex("\r\n|\r|\n", StandardRegexOptions));
private static readonly Lazy<Regex> UnderscoreRegex =
new Lazy<Regex>(
() => new Regex(@"_", StandardRegexOptions));
private static readonly Lazy<Regex> CamelCaseRegEx =
new Lazy<Regex>(
() =>
new Regex(@"[a-z][A-Z]",
StandardRegexOptions));
private static readonly Lazy<MatchEvaluator> SplitCamelCaseString = new Lazy<MatchEvaluator>(() => m => {
String x = m.ToString();
return x[0] + " " + x.Substring(1, x.Length - 1);
});
private static readonly Lazy<String[]> InvalidFilenameChars =
new Lazy<String[]>(() => Path.GetInvalidFileNameChars().Select(c => c.ToString()).ToArray());
#endregion
/// <summary> /// <summary>
/// String related extension methods. /// Computes the MD5 hash of the given stream.
/// Do not use for large streams as this reads ALL bytes at once.
/// </summary> /// </summary>
public static class StringExtensions /// <param name="stream">The stream.</param>
{ /// <param name="createHasher">if set to <c>true</c> [create hasher].</param>
#region Private Declarations /// <returns>
/// The computed hash code.
private const RegexOptions StandardRegexOptions = /// </returns>
RegexOptions.Multiline | RegexOptions.Compiled | RegexOptions.CultureInvariant; /// <exception cref="ArgumentNullException">stream.</exception>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Codequalität", "IDE0067:Objekte verwerfen, bevor Bereich verloren geht", Justification = "<Ausstehend>")]
private static readonly string[] ByteSuffixes = {"B", "KB", "MB", "GB", "TB"}; public static Byte[] ComputeMD5(this Stream stream, Boolean createHasher = false) {
if(stream == null) {
private static readonly Lazy<MD5> Md5Hasher = new Lazy<MD5>(MD5.Create, true); throw new ArgumentNullException(nameof(stream));
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<SHA512> SHA512Hasher = new Lazy<SHA512>(SHA512.Create, true);
private static readonly Lazy<Regex> SplitLinesRegex =
new Lazy<Regex>(
() => new Regex("\r\n|\r|\n", StandardRegexOptions));
private static readonly Lazy<Regex> UnderscoreRegex =
new Lazy<Regex>(
() => new Regex(@"_", StandardRegexOptions));
private static readonly Lazy<Regex> CamelCaseRegEx =
new Lazy<Regex>(
() =>
new Regex(@"[a-z][A-Z]",
StandardRegexOptions));
private static readonly Lazy<MatchEvaluator> SplitCamelCaseString = new Lazy<MatchEvaluator>(() =>
{
return m =>
{
var x = m.ToString();
return x[0] + " " + x.Substring(1, x.Length - 1);
};
});
private static readonly Lazy<string[]> InvalidFilenameChars =
new Lazy<string[]>(() => Path.GetInvalidFileNameChars().Select(c => c.ToString()).ToArray());
#endregion
/// <summary>
/// Computes the MD5 hash of the given stream.
/// Do not use for large streams as this reads ALL bytes at once.
/// </summary>
/// <param name="stream">The stream.</param>
/// <param name="createHasher">if set to <c>true</c> [create hasher].</param>
/// <returns>
/// The computed hash code.
/// </returns>
/// <exception cref="ArgumentNullException">stream.</exception>
public static byte[] ComputeMD5(this Stream stream, bool createHasher = false)
{
if (stream == null)
throw new ArgumentNullException(nameof(stream));
#if NET452 #if NET452
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 = stream.Read(readAheadBuffer, 0, readAheadBuffer.Length); Int32 readAheadBytesRead = stream.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 = stream.Read(readAheadBuffer, 0, readAheadBuffer.Length); readAheadBytesRead = stream.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);
return md5.Hash; return md5.Hash;
#else #else
using (var ms = new MemoryStream()) using (var ms = new MemoryStream())
{ {
@ -100,421 +95,407 @@
return (createHasher ? MD5.Create() : Md5Hasher.Value).ComputeHash(ms.ToArray()); return (createHasher ? MD5.Create() : Md5Hasher.Value).ComputeHash(ms.ToArray());
} }
#endif #endif
} }
/// <summary> /// <summary>
/// Computes the MD5 hash of the given string using UTF8 byte encoding. /// Computes the MD5 hash of the given string using UTF8 byte encoding.
/// </summary> /// </summary>
/// <param name="value">The input string.</param> /// <param name="value">The input string.</param>
/// <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>
public static byte[] ComputeMD5(this string value, bool createHasher = false) => public static Byte[] ComputeMD5(this String value, Boolean createHasher = false) =>
Encoding.UTF8.GetBytes(value).ComputeMD5(createHasher); Encoding.UTF8.GetBytes(value).ComputeMD5(createHasher);
/// <summary>
/// Computes the MD5 hash of the given byte array.
/// </summary>
/// <param name="data">The data.</param>
/// <param name="createHasher">if set to <c>true</c> [create hasher].</param>
/// <returns>The computed hash code.</returns>
public static byte[] ComputeMD5(this byte[] data, bool 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 MD5 hash of the given byte array.
/// </summary> /// </summary>
/// <param name="value">The input string.</param> /// <param name="data">The data.</param>
/// <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> /// <returns>The computed hash code.</returns>
/// The computes a Hash-based Message Authentication Code (HMAC) [System.Diagnostics.CodeAnalysis.SuppressMessage("Codequalität", "IDE0067:Objekte verwerfen, bevor Bereich verloren geht", Justification = "<Ausstehend>")]
/// using the SHA1 hash function. public static Byte[] ComputeMD5(this Byte[] data, Boolean createHasher = false) =>
/// </returns> (createHasher ? MD5.Create() : Md5Hasher.Value).ComputeHash(data);
public static byte[] ComputeSha1(this string value, bool createHasher = false)
{
var inputBytes = Encoding.UTF8.GetBytes(value);
return (createHasher ? SHA1.Create() : SHA1Hasher.Value).ComputeHash(inputBytes);
}
/// <summary>
/// Computes the SHA-256 hash of the given string using UTF8 byte encoding.
/// </summary>
/// <param name="value">The input string.</param>
/// <param name="createHasher">if set to <c>true</c> [create hasher].</param>
/// <returns>
/// The computes a Hash-based Message Authentication Code (HMAC)
/// by using the SHA256 hash function.
/// </returns>
public static byte[] ComputeSha256(this string value, bool createHasher = false)
{
var inputBytes = Encoding.UTF8.GetBytes(value);
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-1 hash of the given string using UTF8 byte encoding.
/// </summary> /// </summary>
/// <param name="value">The input string.</param> /// <param name="value">The input string.</param>
/// <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> /// <returns>
/// 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 SHA1 hash function.
/// </returns> /// </returns>
public static byte[] ComputeSha512(this string value, bool createHasher = false) [System.Diagnostics.CodeAnalysis.SuppressMessage("Codequalität", "IDE0067:Objekte verwerfen, bevor Bereich verloren geht", Justification = "<Ausstehend>")]
{ public static Byte[] ComputeSha1(this 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 ? SHA1.Create() : SHA1Hasher.Value).ComputeHash(inputBytes);
} }
/// <summary>
/// Returns a string that represents the given item
/// It tries to use InvariantCulture if the ToString(IFormatProvider)
/// overload exists.
/// </summary>
/// <param name="obj">The item.</param>
/// <returns>A <see cref="System.String" /> that represents the current object.</returns>
public static string ToStringInvariant(this object obj)
{
if (obj == null)
return string.Empty;
var itemType = obj.GetType(); /// <summary>
/// Computes the SHA-256 hash of the given string using UTF8 byte encoding.
/// </summary>
/// <param name="value">The input string.</param>
/// <param name="createHasher">if set to <c>true</c> [create hasher].</param>
/// <returns>
/// The computes a Hash-based Message Authentication Code (HMAC)
/// by using the SHA256 hash function.
/// </returns>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Codequalität", "IDE0067:Objekte verwerfen, bevor Bereich verloren geht", Justification = "<Ausstehend>")]
public static Byte[] ComputeSha256(this String value, Boolean createHasher = false) {
Byte[] inputBytes = Encoding.UTF8.GetBytes(value);
return (createHasher ? SHA256.Create() : SHA256Hasher.Value).ComputeHash(inputBytes);
}
if (itemType == typeof(string))
return obj as string;
return Definitions.BasicTypesInfo.ContainsKey(itemType) /// <summary>
/// Computes the SHA-512 hash of the given string using UTF8 byte encoding.
/// </summary>
/// <param name="value">The input string.</param>
/// <param name="createHasher">if set to <c>true</c> [create hasher].</param>
/// <returns>
/// The computes a Hash-based Message Authentication Code (HMAC)
/// using the SHA512 hash function.
/// </returns>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Codequalität", "IDE0067:Objekte verwerfen, bevor Bereich verloren geht", Justification = "<Ausstehend>")]
public static Byte[] ComputeSha512(this String value, Boolean createHasher = false) {
Byte[] inputBytes = Encoding.UTF8.GetBytes(value);
return (createHasher ? SHA512.Create() : SHA512Hasher.Value).ComputeHash(inputBytes);
}
/// <summary>
/// Returns a string that represents the given item
/// It tries to use InvariantCulture if the ToString(IFormatProvider)
/// overload exists.
/// </summary>
/// <param name="obj">The item.</param>
/// <returns>A <see cref="System.String" /> that represents the current object.</returns>
public static String ToStringInvariant(this Object obj) {
if(obj == null) {
return String.Empty;
}
Type itemType = obj.GetType();
return itemType == typeof(String)
? obj as String
: Definitions.BasicTypesInfo.ContainsKey(itemType)
? Definitions.BasicTypesInfo[itemType].ToStringInvariant(obj) ? Definitions.BasicTypesInfo[itemType].ToStringInvariant(obj)
: obj.ToString(); : obj.ToString();
}
/// <summary>
/// Returns a string that represents the given item
/// It tries to use InvariantCulture if the ToString(IFormatProvider)
/// overload exists.
/// </summary>
/// <typeparam name="T">The type to get the string.</typeparam>
/// <param name="item">The item.</param>
/// <returns>A <see cref="System.String" /> that represents the current object.</returns>
public static string ToStringInvariant<T>(this T item)
{
if (typeof(string) == typeof(T))
return Equals(item, default(T)) ? string.Empty : item as string;
return ToStringInvariant(item as object);
}
/// <summary>
/// Removes the control characters from a string except for those specified.
/// </summary>
/// <param name="value">The input.</param>
/// <param name="excludeChars">When specified, these characters will not be removed.</param>
/// <returns>
/// A string that represents the current object.
/// </returns>
/// <exception cref="ArgumentNullException">input.</exception>
public static string RemoveControlCharsExcept(this string value, params char[] excludeChars)
{
if (value == null)
throw new ArgumentNullException(nameof(value));
if (excludeChars == null)
excludeChars = new char[] { };
return new string(value
.Where(c => char.IsControl(c) == false || excludeChars.Contains(c))
.ToArray());
}
/// <summary>
/// Removes all control characters from a string, including new line sequences.
/// </summary>
/// <param name="value">The input.</param>
/// <returns>A <see cref="System.String" /> that represents the current object.</returns>
/// <exception cref="ArgumentNullException">input.</exception>
public static string RemoveControlChars(this string value) => value.RemoveControlCharsExcept(null);
/// <summary>
/// Outputs JSON string representing this object.
/// </summary>
/// <param name="obj">The object.</param>
/// <param name="format">if set to <c>true</c> format the output.</param>
/// <returns>A <see cref="System.String" /> that represents the current object.</returns>
public static string ToJson(this object obj, bool format = true) =>
obj == null ? string.Empty : Json.Serialize(obj, format);
/// <summary>
/// Returns text representing the properties of the specified object in a human-readable format.
/// While this method is fairly expensive computationally speaking, it provides an easy way to
/// examine objects.
/// </summary>
/// <param name="obj">The object.</param>
/// <returns>A <see cref="System.String" /> that represents the current object.</returns>
public static string Stringify(this object obj)
{
if (obj == null)
return "(null)";
try
{
var jsonText = Json.Serialize(obj, false, "$type");
var jsonData = Json.Deserialize(jsonText);
return new HumanizeJson(jsonData, 0).GetResult();
}
catch
{
return obj.ToStringInvariant();
}
}
/// <summary>
/// Retrieves a section of the string, inclusive of both, the start and end indexes.
/// This behavior is unlike JavaScript's Slice behavior where the end index is non-inclusive
/// If the string is null it returns an empty string.
/// </summary>
/// <param name="value">The string.</param>
/// <param name="startIndex">The start index.</param>
/// <param name="endIndex">The end index.</param>
/// <returns>Retrieves a substring from this instance.</returns>
public static string Slice(this string value, int startIndex, int endIndex)
{
if (value == null)
return string.Empty;
var end = endIndex.Clamp(startIndex, value.Length - 1);
return startIndex >= end ? string.Empty : value.Substring(startIndex, (end - startIndex) + 1);
}
/// <summary>
/// Gets a part of the string clamping the length and startIndex parameters to safe values.
/// If the string is null it returns an empty string. This is basically just a safe version
/// of string.Substring.
/// </summary>
/// <param name="str">The string.</param>
/// <param name="startIndex">The start index.</param>
/// <param name="length">The length.</param>
/// <returns>Retrieves a substring from this instance.</returns>
public static string SliceLength(this string str, int startIndex, int length)
{
if (str == null)
return string.Empty;
var start = startIndex.Clamp(0, str.Length - 1);
var len = length.Clamp(0, str.Length - start);
return len == 0 ? string.Empty : str.Substring(start, len);
}
/// <summary>
/// Splits the specified text into r, n or rn separated lines.
/// </summary>
/// <param name="value">The text.</param>
/// <returns>
/// An array whose elements contain the substrings from this instance
/// that are delimited by one or more characters in separator.
/// </returns>
public static string[] ToLines(this string value) =>
value == null ? new string[] { } : SplitLinesRegex.Value.Split(value);
/// <summary>
/// Humanizes (make more human-readable) an identifier-style string
/// in either camel case or snake case. For example, CamelCase will be converted to
/// Camel Case and Snake_Case will be converted to Snake Case.
/// </summary>
/// <param name="value">The identifier-style string.</param>
/// <returns>A <see cref="System.String" /> that represents the current object.</returns>
public static string Humanize(this string value)
{
if (value == null)
return string.Empty;
var returnValue = UnderscoreRegex.Value.Replace(value, " ");
returnValue = CamelCaseRegEx.Value.Replace(returnValue, SplitCamelCaseString.Value);
return returnValue;
}
/// <summary>
/// Indents the specified multi-line text with the given amount of leading spaces
/// per line.
/// </summary>
/// <param name="value">The text.</param>
/// <param name="spaces">The spaces.</param>
/// <returns>A <see cref="System.String" /> that represents the current object.</returns>
public static string Indent(this string value, int spaces = 4)
{
if (value == null) value = string.Empty;
if (spaces <= 0) return value;
var lines = value.ToLines();
var builder = new StringBuilder();
var indentStr = new string(' ', spaces);
foreach (var line in lines)
{
builder.AppendLine($"{indentStr}{line}");
}
return builder.ToString().TrimEnd();
}
/// <summary>
/// Gets the line and column number (i.e. not index) of the
/// specified character index. Useful to locate text in a multi-line
/// string the same way a text editor does.
/// Please not that the tuple contains first the line number and then the
/// column number.
/// </summary>
/// <param name="value">The string.</param>
/// <param name="charIndex">Index of the character.</param>
/// <returns>A 2-tuple whose value is (item1, item2).</returns>
public static Tuple<int, int> TextPositionAt(this string value, int charIndex)
{
if (value == null)
return Tuple.Create(0, 0);
var index = charIndex.Clamp(0, value.Length - 1);
var lineIndex = 0;
var colNumber = 0;
for (var i = 0; i <= index; i++)
{
if (value[i] == '\n')
{
lineIndex++;
colNumber = 0;
continue;
}
if (value[i] != '\r')
colNumber++;
}
return Tuple.Create(lineIndex + 1, colNumber);
}
/// <summary>
/// Makes the file name system safe.
/// </summary>
/// <param name="value">The s.</param>
/// <returns>
/// A string with a safe file name.
/// </returns>
/// <exception cref="ArgumentNullException">s.</exception>
public static string ToSafeFilename(this string value)
{
return value == null
? throw new ArgumentNullException(nameof(value))
: InvalidFilenameChars.Value
.Aggregate(value, (current, c) => current.Replace(c, string.Empty))
.Slice(0, 220);
}
/// <summary>
/// Formats a long into the closest bytes string.
/// </summary>
/// <param name="bytes">The bytes length.</param>
/// <returns>
/// The string representation of the current Byte object, formatted as specified by the format parameter.
/// </returns>
public static string FormatBytes(this long bytes) => ((ulong) bytes).FormatBytes();
/// <summary>
/// Formats a long into the closest bytes string.
/// </summary>
/// <param name="bytes">The bytes length.</param>
/// <returns>
/// A copy of format in which the format items have been replaced by the string
/// representations of the corresponding arguments.
/// </returns>
public static string FormatBytes(this ulong bytes)
{
int i;
double dblSByte = bytes;
for (i = 0; i < ByteSuffixes.Length && bytes >= 1024; i++, bytes /= 1024)
{
dblSByte = bytes / 1024.0;
}
return $"{dblSByte:0.##} {ByteSuffixes[i]}";
}
/// <summary>
/// Truncates the specified value.
/// </summary>
/// <param name="value">The value.</param>
/// <param name="maximumLength">The maximum length.</param>
/// <returns>
/// Retrieves a substring from this instance.
/// The substring starts at a specified character position and has a specified length.
/// </returns>
public static string Truncate(this string value, int maximumLength) =>
Truncate(value, maximumLength, string.Empty);
/// <summary>
/// Truncates the specified value and append the omission last.
/// </summary>
/// <param name="value">The value.</param>
/// <param name="maximumLength">The maximum length.</param>
/// <param name="omission">The omission.</param>
/// <returns>
/// Retrieves a substring from this instance.
/// The substring starts at a specified character position and has a specified length.
/// </returns>
public static string Truncate(this string value, int maximumLength, string omission)
{
if (value == null)
return null;
return value.Length > maximumLength
? value.Substring(0, maximumLength) + (omission ?? string.Empty)
: value;
}
/// <summary>
/// Determines whether the specified <see cref="string"/> contains any of characters in
/// the specified array of <see cref="char"/>.
/// </summary>
/// <returns>
/// <c>true</c> if <paramref name="value"/> contains any of <paramref name="chars"/>;
/// otherwise, <c>false</c>.
/// </returns>
/// <param name="value">
/// A <see cref="string"/> to test.
/// </param>
/// <param name="chars">
/// An array of <see cref="char"/> that contains characters to find.
/// </param>
public static bool Contains(this string value, params char[] chars) =>
chars?.Length == 0 || (!string.IsNullOrEmpty(value) && value.IndexOfAny(chars) > -1);
/// <summary>
/// Replaces all chars in a string.
/// </summary>
/// <param name="value">The value.</param>
/// <param name="replaceValue">The replace value.</param>
/// <param name="chars">The chars.</param>
/// <returns>The string with the characters replaced.</returns>
public static string ReplaceAll(this string value, string replaceValue, params char[] chars) =>
chars.Aggregate(value, (current, c) => current.Replace(new string(new[] {c}), replaceValue));
/// <summary>
/// Convert hex character to an integer. Return -1 if char is something
/// other than a hex char.
/// </summary>
/// <param name="value">The c.</param>
/// <returns>Converted integer.</returns>
public static int Hex2Int(this char value)
{
return value >= '0' && value <= '9'
? value - '0'
: value >= 'A' && value <= 'F'
? value - 'A' + 10
: value >= 'a' && value <= 'f'
? value - 'a' + 10
: -1;
}
} }
/// <summary>
/// Returns a string that represents the given item
/// It tries to use InvariantCulture if the ToString(IFormatProvider)
/// overload exists.
/// </summary>
/// <typeparam name="T">The type to get the string.</typeparam>
/// <param name="item">The item.</param>
/// <returns>A <see cref="System.String" /> that represents the current object.</returns>
public static String ToStringInvariant<T>(this T item) => typeof(String) == typeof(T) ? Equals(item, default(T)) ? String.Empty : item as String : ToStringInvariant(item as Object);
/// <summary>
/// Removes the control characters from a string except for those specified.
/// </summary>
/// <param name="value">The input.</param>
/// <param name="excludeChars">When specified, these characters will not be removed.</param>
/// <returns>
/// A string that represents the current object.
/// </returns>
/// <exception cref="ArgumentNullException">input.</exception>
public static String RemoveControlCharsExcept(this String value, params Char[] excludeChars) {
if(value == null) {
throw new ArgumentNullException(nameof(value));
}
if(excludeChars == null) {
excludeChars = new Char[] { };
}
return new String(value
.Where(c => Char.IsControl(c) == false || excludeChars.Contains(c))
.ToArray());
}
/// <summary>
/// Removes all control characters from a string, including new line sequences.
/// </summary>
/// <param name="value">The input.</param>
/// <returns>A <see cref="System.String" /> that represents the current object.</returns>
/// <exception cref="ArgumentNullException">input.</exception>
public static String RemoveControlChars(this String value) => value.RemoveControlCharsExcept(null);
/// <summary>
/// Outputs JSON string representing this object.
/// </summary>
/// <param name="obj">The object.</param>
/// <param name="format">if set to <c>true</c> format the output.</param>
/// <returns>A <see cref="System.String" /> that represents the current object.</returns>
public static String ToJson(this Object obj, Boolean format = true) =>
obj == null ? String.Empty : Json.Serialize(obj, format);
/// <summary>
/// Returns text representing the properties of the specified object in a human-readable format.
/// While this method is fairly expensive computationally speaking, it provides an easy way to
/// examine objects.
/// </summary>
/// <param name="obj">The object.</param>
/// <returns>A <see cref="System.String" /> that represents the current object.</returns>
public static String Stringify(this Object obj) {
if(obj == null) {
return "(null)";
}
try {
String jsonText = Json.Serialize(obj, false, "$type");
Object jsonData = Json.Deserialize(jsonText);
return new HumanizeJson(jsonData, 0).GetResult();
} catch {
return obj.ToStringInvariant();
}
}
/// <summary>
/// Retrieves a section of the string, inclusive of both, the start and end indexes.
/// This behavior is unlike JavaScript's Slice behavior where the end index is non-inclusive
/// If the string is null it returns an empty string.
/// </summary>
/// <param name="value">The string.</param>
/// <param name="startIndex">The start index.</param>
/// <param name="endIndex">The end index.</param>
/// <returns>Retrieves a substring from this instance.</returns>
public static String Slice(this String value, Int32 startIndex, Int32 endIndex) {
if(value == null) {
return String.Empty;
}
Int32 end = endIndex.Clamp(startIndex, value.Length - 1);
return startIndex >= end ? String.Empty : value.Substring(startIndex, end - startIndex + 1);
}
/// <summary>
/// Gets a part of the string clamping the length and startIndex parameters to safe values.
/// If the string is null it returns an empty string. This is basically just a safe version
/// of string.Substring.
/// </summary>
/// <param name="str">The string.</param>
/// <param name="startIndex">The start index.</param>
/// <param name="length">The length.</param>
/// <returns>Retrieves a substring from this instance.</returns>
public static String SliceLength(this String str, Int32 startIndex, Int32 length) {
if(str == null) {
return String.Empty;
}
Int32 start = startIndex.Clamp(0, str.Length - 1);
Int32 len = length.Clamp(0, str.Length - start);
return len == 0 ? String.Empty : str.Substring(start, len);
}
/// <summary>
/// Splits the specified text into r, n or rn separated lines.
/// </summary>
/// <param name="value">The text.</param>
/// <returns>
/// An array whose elements contain the substrings from this instance
/// that are delimited by one or more characters in separator.
/// </returns>
public static String[] ToLines(this String value) =>
value == null ? new String[] { } : SplitLinesRegex.Value.Split(value);
/// <summary>
/// Humanizes (make more human-readable) an identifier-style string
/// in either camel case or snake case. For example, CamelCase will be converted to
/// Camel Case and Snake_Case will be converted to Snake Case.
/// </summary>
/// <param name="value">The identifier-style string.</param>
/// <returns>A <see cref="System.String" /> that represents the current object.</returns>
public static String Humanize(this String value) {
if(value == null) {
return String.Empty;
}
String returnValue = UnderscoreRegex.Value.Replace(value, " ");
returnValue = CamelCaseRegEx.Value.Replace(returnValue, SplitCamelCaseString.Value);
return returnValue;
}
/// <summary>
/// Indents the specified multi-line text with the given amount of leading spaces
/// per line.
/// </summary>
/// <param name="value">The text.</param>
/// <param name="spaces">The spaces.</param>
/// <returns>A <see cref="System.String" /> that represents the current object.</returns>
public static String Indent(this String value, Int32 spaces = 4) {
if(value == null) {
value = String.Empty;
}
if(spaces <= 0) {
return value;
}
String[] lines = value.ToLines();
StringBuilder builder = new StringBuilder();
String indentStr = new String(' ', spaces);
foreach(String line in lines) {
_ = builder.AppendLine($"{indentStr}{line}");
}
return builder.ToString().TrimEnd();
}
/// <summary>
/// Gets the line and column number (i.e. not index) of the
/// specified character index. Useful to locate text in a multi-line
/// string the same way a text editor does.
/// Please not that the tuple contains first the line number and then the
/// column number.
/// </summary>
/// <param name="value">The string.</param>
/// <param name="charIndex">Index of the character.</param>
/// <returns>A 2-tuple whose value is (item1, item2).</returns>
public static Tuple<Int32, Int32> TextPositionAt(this String value, Int32 charIndex) {
if(value == null) {
return Tuple.Create(0, 0);
}
Int32 index = charIndex.Clamp(0, value.Length - 1);
Int32 lineIndex = 0;
Int32 colNumber = 0;
for(Int32 i = 0; i <= index; i++) {
if(value[i] == '\n') {
lineIndex++;
colNumber = 0;
continue;
}
if(value[i] != '\r') {
colNumber++;
}
}
return Tuple.Create(lineIndex + 1, colNumber);
}
/// <summary>
/// Makes the file name system safe.
/// </summary>
/// <param name="value">The s.</param>
/// <returns>
/// A string with a safe file name.
/// </returns>
/// <exception cref="ArgumentNullException">s.</exception>
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);
/// <summary>
/// Formats a long into the closest bytes string.
/// </summary>
/// <param name="bytes">The bytes length.</param>
/// <returns>
/// The string representation of the current Byte object, formatted as specified by the format parameter.
/// </returns>
public static String FormatBytes(this Int64 bytes) => ((UInt64)bytes).FormatBytes();
/// <summary>
/// Formats a long into the closest bytes string.
/// </summary>
/// <param name="bytes">The bytes length.</param>
/// <returns>
/// A copy of format in which the format items have been replaced by the string
/// representations of the corresponding arguments.
/// </returns>
public static String FormatBytes(this UInt64 bytes) {
Int32 i;
Double dblSByte = bytes;
for(i = 0; i < ByteSuffixes.Length && bytes >= 1024; i++, bytes /= 1024) {
dblSByte = bytes / 1024.0;
}
return $"{dblSByte:0.##} {ByteSuffixes[i]}";
}
/// <summary>
/// Truncates the specified value.
/// </summary>
/// <param name="value">The value.</param>
/// <param name="maximumLength">The maximum length.</param>
/// <returns>
/// Retrieves a substring from this instance.
/// The substring starts at a specified character position and has a specified length.
/// </returns>
public static String Truncate(this String value, Int32 maximumLength) =>
Truncate(value, maximumLength, String.Empty);
/// <summary>
/// Truncates the specified value and append the omission last.
/// </summary>
/// <param name="value">The value.</param>
/// <param name="maximumLength">The maximum length.</param>
/// <param name="omission">The omission.</param>
/// <returns>
/// Retrieves a substring from this instance.
/// The substring starts at a specified character position and has a specified length.
/// </returns>
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;
/// <summary>
/// Determines whether the specified <see cref="String"/> contains any of characters in
/// the specified array of <see cref="Char"/>.
/// </summary>
/// <returns>
/// <c>true</c> if <paramref name="value"/> contains any of <paramref name="chars"/>;
/// otherwise, <c>false</c>.
/// </returns>
/// <param name="value">
/// A <see cref="String"/> to test.
/// </param>
/// <param name="chars">
/// An array of <see cref="Char"/> that contains characters to find.
/// </param>
public static Boolean Contains(this String value, params Char[] chars) =>
chars?.Length == 0 || !String.IsNullOrEmpty(value) && value.IndexOfAny(chars) > -1;
/// <summary>
/// Replaces all chars in a string.
/// </summary>
/// <param name="value">The value.</param>
/// <param name="replaceValue">The replace value.</param>
/// <param name="chars">The chars.</param>
/// <returns>The string with the characters replaced.</returns>
public static String ReplaceAll(this String value, String replaceValue, params Char[] chars) =>
chars.Aggregate(value, (current, c) => current.Replace(new String(new[] { c }), replaceValue));
/// <summary>
/// Convert hex character to an integer. Return -1 if char is something
/// other than a hex char.
/// </summary>
/// <param name="value">The c.</param>
/// <returns>Converted integer.</returns>
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;
}
} }

View File

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

View File

@ -1,331 +1,305 @@
namespace Unosquare.Swan using Unosquare.Swan.Attributes;
{ using System;
using Attributes; using System.Collections;
using System; using System.Collections.Generic;
using System.Collections; using System.Diagnostics;
using System.Collections.Generic; using System.Linq;
using System.Diagnostics; using System.Reflection;
using System.Linq; using System.Threading.Tasks;
using System.Reflection;
using System.Threading.Tasks; namespace Unosquare.Swan {
/// <summary>
/// Extension methods.
/// </summary>
public static partial class Extensions {
/// <summary>
/// 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.
/// </summary>
/// <typeparam name="T">The type of the source.</typeparam>
/// <param name="source">The source.</param>
/// <param name="target">The target.</param>
/// <returns>Number of properties that was copied successful.</returns>
public static Int32 CopyPropertiesTo<T>(this T source, Object target)
where T : class {
IEnumerable<String> copyable = GetCopyableProperties(target);
return copyable.Any()
? CopyOnlyPropertiesTo(source, target, copyable.ToArray())
: CopyPropertiesTo(source, target, null);
}
/// <summary> /// <summary>
/// Extension methods. /// 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.
/// </summary> /// </summary>
public static partial class Extensions /// <param name="source">The source.</param>
{ /// <param name="target">The destination.</param>
/// <summary> /// <param name="ignoreProperties">The ignore properties.</param>
/// Iterates over the public, instance, readable properties of the source and /// <returns>
/// tries to write a compatible value to a public, instance, writable property in the destination. /// Number of properties that were successfully copied.
/// </summary> /// </returns>
/// <typeparam name="T">The type of the source.</typeparam> public static Int32 CopyPropertiesTo(this Object source, Object target, String[] ignoreProperties = null)
/// <param name="source">The source.</param> => Components.ObjectMapper.Copy(source, target, null, ignoreProperties);
/// <param name="target">The target.</param>
/// <returns>Number of properties that was copied successful.</returns> /// <summary>
public static int CopyPropertiesTo<T>(this T source, object target) /// Iterates over the public, instance, readable properties of the source and
where T : class /// tries to write a compatible value to a public, instance, writable property in the destination.
{ /// </summary>
var copyable = GetCopyableProperties(target); /// <typeparam name="T">The type of the source.</typeparam>
return copyable.Any() /// <param name="source">The source.</param>
? CopyOnlyPropertiesTo(source, target, copyable.ToArray()) /// <param name="target">The target.</param>
: CopyPropertiesTo(source, target, null); /// <returns>Number of properties that was copied successful.</returns>
public static Int32 CopyOnlyPropertiesTo<T>(this T source, Object target)
where T : class => CopyOnlyPropertiesTo(source, target, null);
/// <summary>
/// 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.
/// </summary>
/// <param name="source">The source.</param>
/// <param name="target">The destination.</param>
/// <param name="propertiesToCopy">Properties to copy.</param>
/// <returns>
/// Number of properties that were successfully copied.
/// </returns>
public static Int32 CopyOnlyPropertiesTo(this Object source, Object target, String[] propertiesToCopy) => Components.ObjectMapper.Copy(source, target, propertiesToCopy);
/// <summary>
/// Copies the properties to new instance of T.
/// </summary>
/// <typeparam name="T">The new object type.</typeparam>
/// <param name="source">The source.</param>
/// <param name="ignoreProperties">The ignore properties.</param>
/// <returns>
/// The specified type with properties copied.
/// </returns>
/// <exception cref="ArgumentNullException">source.</exception>
public static T DeepClone<T>(this T source, String[] ignoreProperties = null)
where T : class => source.CopyPropertiesToNew<T>(ignoreProperties);
/// <summary>
/// Copies the properties to new instance of T.
/// </summary>
/// <typeparam name="T">The new object type.</typeparam>
/// <param name="source">The source.</param>
/// <param name="ignoreProperties">The ignore properties.</param>
/// <returns>
/// The specified type with properties copied.
/// </returns>
/// <exception cref="ArgumentNullException">source.</exception>
public static T CopyPropertiesToNew<T>(this Object source, String[] ignoreProperties = null)
where T : class {
if(source == null) {
throw new ArgumentNullException(nameof(source));
}
T target = Activator.CreateInstance<T>();
IEnumerable<String> copyable = target.GetCopyableProperties();
_ = copyable.Any() ? source.CopyOnlyPropertiesTo(target, copyable.ToArray()) : source.CopyPropertiesTo(target, ignoreProperties);
return target;
}
/// <summary>
/// Copies the only properties to new instance of T.
/// </summary>
/// <typeparam name="T">Object Type.</typeparam>
/// <param name="source">The source.</param>
/// <param name="propertiesToCopy">The properties to copy.</param>
/// <returns>
/// The specified type with properties copied.
/// </returns>
/// <exception cref="ArgumentNullException">source.</exception>
public static T CopyOnlyPropertiesToNew<T>(this Object source, String[] propertiesToCopy)
where T : class {
if(source == null) {
throw new ArgumentNullException(nameof(source));
}
T target = Activator.CreateInstance<T>();
_ = source.CopyOnlyPropertiesTo(target, propertiesToCopy);
return target;
}
/// <summary>
/// Iterates over the keys of the source and tries to write a compatible value to a public,
/// instance, writable property in the destination.
/// </summary>
/// <param name="source">The source.</param>
/// <param name="target">The target.</param>
/// <param name="ignoreKeys">The ignore keys.</param>
/// <returns>Number of properties that was copied successful.</returns>
public static Int32 CopyKeyValuePairTo(
this IDictionary<String, Object> source,
Object target,
String[] ignoreKeys = null) => Components.ObjectMapper.Copy(source, target, null, ignoreKeys);
/// <summary>
/// Measures the elapsed time of the given action as a TimeSpan
/// This method uses a high precision Stopwatch.
/// </summary>
/// <param name="target">The target.</param>
/// <returns>
/// A time interval that represents a specified time, where the specification is in units of ticks.
/// </returns>
/// <exception cref="ArgumentNullException">target.</exception>
public static TimeSpan Benchmark(this Action target) {
if(target == null) {
throw new ArgumentNullException(nameof(target));
}
Stopwatch sw = new Stopwatch();
try {
sw.Start();
target.Invoke();
} catch {
// swallow
} finally {
sw.Stop();
}
return TimeSpan.FromTicks(sw.ElapsedTicks);
}
/// <summary>
/// Does the specified action.
/// </summary>
/// <param name="action">The action.</param>
/// <param name="retryInterval">The retry interval.</param>
/// <param name="retryCount">The retry count.</param>
public static void Retry(
this Action action,
TimeSpan retryInterval = default,
Int32 retryCount = 3) {
if(action == null) {
throw new ArgumentNullException(nameof(action));
}
_ = Retry<Object>(() => {
action();
return null;
},
retryInterval,
retryCount);
}
/// <summary>
/// Does the specified action.
/// </summary>
/// <typeparam name="T">The type of the source.</typeparam>
/// <param name="action">The action.</param>
/// <param name="retryInterval">The retry interval.</param>
/// <param name="retryCount">The retry count.</param>
/// <returns>
/// The return value of the method that this delegate encapsulates.
/// </returns>
/// <exception cref="ArgumentNullException">action.</exception>
/// <exception cref="AggregateException">Represents one or many errors that occur during application execution.</exception>
public static T Retry<T>(
this Func<T> action,
TimeSpan retryInterval = default,
Int32 retryCount = 3) {
if(action == null) {
throw new ArgumentNullException(nameof(action));
}
if(retryInterval == default) {
retryInterval = TimeSpan.FromSeconds(1);
}
List<Exception> exceptions = new List<Exception>();
for(Int32 retry = 0; retry < retryCount; retry++) {
try {
if(retry > 0) {
Task.Delay(retryInterval).Wait();
}
return action();
} catch(Exception ex) {
exceptions.Add(ex);
}
}
throw new AggregateException(exceptions);
}
/// <summary>
/// Retrieves the exception message, plus all the inner exception messages separated by new lines.
/// </summary>
/// <param name="ex">The ex.</param>
/// <param name="priorMessage">The prior message.</param>
/// <returns>A <see cref="System.String" /> that represents this instance.</returns>
public static String ExceptionMessage(this Exception ex, String priorMessage = "") {
while(true) {
if(ex == null) {
throw new ArgumentNullException(nameof(ex));
} }
/// <summary> String fullMessage = String.IsNullOrWhiteSpace(priorMessage)
/// 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.
/// </summary>
/// <param name="source">The source.</param>
/// <param name="target">The destination.</param>
/// <param name="ignoreProperties">The ignore properties.</param>
/// <returns>
/// Number of properties that were successfully copied.
/// </returns>
public static int CopyPropertiesTo(this object source, object target, string[] ignoreProperties = null)
=> Components.ObjectMapper.Copy(source, target, null, ignoreProperties);
/// <summary>
/// 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.
/// </summary>
/// <typeparam name="T">The type of the source.</typeparam>
/// <param name="source">The source.</param>
/// <param name="target">The target.</param>
/// <returns>Number of properties that was copied successful.</returns>
public static int CopyOnlyPropertiesTo<T>(this T source, object target)
where T : class
{
return CopyOnlyPropertiesTo(source, target, null);
}
/// <summary>
/// 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.
/// </summary>
/// <param name="source">The source.</param>
/// <param name="target">The destination.</param>
/// <param name="propertiesToCopy">Properties to copy.</param>
/// <returns>
/// Number of properties that were successfully copied.
/// </returns>
public static int CopyOnlyPropertiesTo(this object source, object target, string[] propertiesToCopy)
{
return Components.ObjectMapper.Copy(source, target, propertiesToCopy);
}
/// <summary>
/// Copies the properties to new instance of T.
/// </summary>
/// <typeparam name="T">The new object type.</typeparam>
/// <param name="source">The source.</param>
/// <param name="ignoreProperties">The ignore properties.</param>
/// <returns>
/// The specified type with properties copied.
/// </returns>
/// <exception cref="ArgumentNullException">source.</exception>
public static T DeepClone<T>(this T source, string[] ignoreProperties = null)
where T : class
{
return source.CopyPropertiesToNew<T>(ignoreProperties);
}
/// <summary>
/// Copies the properties to new instance of T.
/// </summary>
/// <typeparam name="T">The new object type.</typeparam>
/// <param name="source">The source.</param>
/// <param name="ignoreProperties">The ignore properties.</param>
/// <returns>
/// The specified type with properties copied.
/// </returns>
/// <exception cref="ArgumentNullException">source.</exception>
public static T CopyPropertiesToNew<T>(this object source, string[] ignoreProperties = null)
where T : class
{
if (source == null)
throw new ArgumentNullException(nameof(source));
var target = Activator.CreateInstance<T>();
var copyable = target.GetCopyableProperties();
if (copyable.Any())
source.CopyOnlyPropertiesTo(target, copyable.ToArray());
else
source.CopyPropertiesTo(target, ignoreProperties);
return target;
}
/// <summary>
/// Copies the only properties to new instance of T.
/// </summary>
/// <typeparam name="T">Object Type.</typeparam>
/// <param name="source">The source.</param>
/// <param name="propertiesToCopy">The properties to copy.</param>
/// <returns>
/// The specified type with properties copied.
/// </returns>
/// <exception cref="ArgumentNullException">source.</exception>
public static T CopyOnlyPropertiesToNew<T>(this object source, string[] propertiesToCopy)
where T : class
{
if (source == null)
throw new ArgumentNullException(nameof(source));
var target = Activator.CreateInstance<T>();
source.CopyOnlyPropertiesTo(target, propertiesToCopy);
return target;
}
/// <summary>
/// Iterates over the keys of the source and tries to write a compatible value to a public,
/// instance, writable property in the destination.
/// </summary>
/// <param name="source">The source.</param>
/// <param name="target">The target.</param>
/// <param name="ignoreKeys">The ignore keys.</param>
/// <returns>Number of properties that was copied successful.</returns>
public static int CopyKeyValuePairTo(
this IDictionary<string, object> source,
object target,
string[] ignoreKeys = null)
{
return Components.ObjectMapper.Copy(source, target, null, ignoreKeys);
}
/// <summary>
/// Measures the elapsed time of the given action as a TimeSpan
/// This method uses a high precision Stopwatch.
/// </summary>
/// <param name="target">The target.</param>
/// <returns>
/// A time interval that represents a specified time, where the specification is in units of ticks.
/// </returns>
/// <exception cref="ArgumentNullException">target.</exception>
public static TimeSpan Benchmark(this Action target)
{
if (target == null)
throw new ArgumentNullException(nameof(target));
var sw = new Stopwatch();
try
{
sw.Start();
target.Invoke();
}
catch
{
// swallow
}
finally
{
sw.Stop();
}
return TimeSpan.FromTicks(sw.ElapsedTicks);
}
/// <summary>
/// Does the specified action.
/// </summary>
/// <param name="action">The action.</param>
/// <param name="retryInterval">The retry interval.</param>
/// <param name="retryCount">The retry count.</param>
public static void Retry(
this Action action,
TimeSpan retryInterval = default,
int retryCount = 3)
{
if (action == null)
throw new ArgumentNullException(nameof(action));
Retry<object>(() =>
{
action();
return null;
},
retryInterval,
retryCount);
}
/// <summary>
/// Does the specified action.
/// </summary>
/// <typeparam name="T">The type of the source.</typeparam>
/// <param name="action">The action.</param>
/// <param name="retryInterval">The retry interval.</param>
/// <param name="retryCount">The retry count.</param>
/// <returns>
/// The return value of the method that this delegate encapsulates.
/// </returns>
/// <exception cref="ArgumentNullException">action.</exception>
/// <exception cref="AggregateException">Represents one or many errors that occur during application execution.</exception>
public static T Retry<T>(
this Func<T> action,
TimeSpan retryInterval = default,
int retryCount = 3)
{
if (action == null)
throw new ArgumentNullException(nameof(action));
if (retryInterval == default)
retryInterval = TimeSpan.FromSeconds(1);
var exceptions = new List<Exception>();
for (var retry = 0; retry < retryCount; retry++)
{
try
{
if (retry > 0)
Task.Delay(retryInterval).Wait();
return action();
}
catch (Exception ex)
{
exceptions.Add(ex);
}
}
throw new AggregateException(exceptions);
}
/// <summary>
/// Retrieves the exception message, plus all the inner exception messages separated by new lines.
/// </summary>
/// <param name="ex">The ex.</param>
/// <param name="priorMessage">The prior message.</param>
/// <returns>A <see cref="System.String" /> that represents this instance.</returns>
public static string ExceptionMessage(this Exception ex, string priorMessage = "")
{
while (true)
{
if (ex == null)
throw new ArgumentNullException(nameof(ex));
var fullMessage = string.IsNullOrWhiteSpace(priorMessage)
? ex.Message ? ex.Message
: priorMessage + "\r\n" + ex.Message; : priorMessage + "\r\n" + ex.Message;
if (string.IsNullOrWhiteSpace(ex.InnerException?.Message)) if(String.IsNullOrWhiteSpace(ex.InnerException?.Message)) {
return fullMessage; return fullMessage;
ex = ex.InnerException;
priorMessage = fullMessage;
}
} }
/// <summary> ex = ex.InnerException;
/// Gets the copyable properties. priorMessage = fullMessage;
/// </summary> }
/// <param name="obj">The object.</param>
/// <returns>
/// Array of properties.
/// </returns>
/// <exception cref="ArgumentNullException">model.</exception>
public static IEnumerable<string> GetCopyableProperties(this object obj)
{
if (obj == null)
throw new ArgumentNullException(nameof(obj));
return Runtime.PropertyTypeCache
.RetrieveAllProperties(obj.GetType(), true)
.Select(x => new { x.Name, HasAttribute = Runtime.AttributeCache.RetrieveOne<CopyableAttribute>(x) != null})
.Where(x => x.HasAttribute)
.Select(x => x.Name);
}
/// <summary>
/// Returns true if the object is valid.
/// </summary>
/// <param name="obj">The object.</param>
/// <returns>
/// <c>true</c> if the specified model is valid; otherwise, <c>false</c>.
/// </returns>
public static bool IsValid(this object obj) => Runtime.ObjectValidator.IsValid(obj);
internal static void CreateTarget(
this object source,
Type targetType,
bool includeNonPublic,
ref object target)
{
switch (source)
{
case string _:
break; // do nothing. Simply skip creation
case IList sourceObjectList when targetType.IsArray: // When using arrays, there is no default constructor, attempt to build a compatible array
var elementType = targetType.GetElementType();
if (elementType != null)
target = Array.CreateInstance(elementType, sourceObjectList.Count);
break;
default:
target = Activator.CreateInstance(targetType, includeNonPublic);
break;
}
}
} }
/// <summary>
/// Gets the copyable properties.
/// </summary>
/// <param name="obj">The object.</param>
/// <returns>
/// Array of properties.
/// </returns>
/// <exception cref="ArgumentNullException">model.</exception>
public static IEnumerable<String> GetCopyableProperties(this Object obj) {
if(obj == null) {
throw new ArgumentNullException(nameof(obj));
}
return Runtime.PropertyTypeCache
.RetrieveAllProperties(obj.GetType(), true)
.Select(x => new { x.Name, HasAttribute = Runtime.AttributeCache.RetrieveOne<CopyableAttribute>(x) != null })
.Where(x => x.HasAttribute)
.Select(x => x.Name);
}
/// <summary>
/// Returns true if the object is valid.
/// </summary>
/// <param name="obj">The object.</param>
/// <returns>
/// <c>true</c> if the specified model is valid; otherwise, <c>false</c>.
/// </returns>
public static Boolean IsValid(this Object obj) => Runtime.ObjectValidator.IsValid(obj);
internal static void CreateTarget(
this Object source,
Type targetType,
Boolean includeNonPublic,
ref Object target) {
switch(source) {
case String _:
break; // do nothing. Simply skip creation
case IList sourceObjectList when targetType.IsArray: // When using arrays, there is no default constructor, attempt to build a compatible array
Type elementType = targetType.GetElementType();
if(elementType != null) {
target = Array.CreateInstance(elementType, sourceObjectList.Count);
}
break;
default:
target = Activator.CreateInstance(targetType, includeNonPublic);
break;
}
}
}
} }

File diff suppressed because it is too large Load Diff

View File

@ -1,415 +1,401 @@
namespace Unosquare.Swan.Formatters using Unosquare.Swan.Reflection;
{ using System;
using Reflection; using System.Collections;
using System; using System.Collections.Generic;
using System.Collections; using System.IO;
using System.Collections.Generic; using System.Linq;
using System.IO; using System.Reflection;
using System.Linq; using System.Text;
using System.Reflection;
using System.Text; namespace Unosquare.Swan.Formatters {
/// <summary>
/// A CSV writer useful for exporting a set of objects.
/// </summary>
/// <example>
/// The following code describes how to save a list of objects into a CSV file.
/// <code>
/// using System.Collections.Generic;
/// using Unosquare.Swan.Formatters;
///
/// class Example
/// {
/// class Person
/// {
/// public string Name { get; set; }
/// public int Age { get; set; }
/// }
///
/// static void Main()
/// {
/// // create a list of people
/// var people = new List&lt;Person&gt;
/// {
/// new Person { Name = "Artyom", Age = 20 },
/// new Person { Name = "Aloy", Age = 18 }
/// }
///
/// // write items inside file.csv
/// CsvWriter.SaveRecords(people, "C:\\Users\\user\\Documents\\file.csv");
///
/// // output
/// // | Name | Age |
/// // | Artyom | 20 |
/// // | Aloy | 18 |
/// }
/// }
/// </code>
/// </example>
public class CsvWriter : IDisposable {
private static readonly PropertyTypeCache TypeCache = new PropertyTypeCache();
private readonly Object _syncLock = new Object();
private readonly Stream _outputStream;
private readonly Encoding _encoding;
private readonly Boolean _leaveStreamOpen;
private Boolean _isDisposing;
private UInt64 _mCount;
#region Constructors
/// <summary> /// <summary>
/// A CSV writer useful for exporting a set of objects. /// Initializes a new instance of the <see cref="CsvWriter" /> class.
/// </summary> /// </summary>
/// <example> /// <param name="outputStream">The output stream.</param>
/// The following code describes how to save a list of objects into a CSV file. /// <param name="leaveOpen">if set to <c>true</c> [leave open].</param>
/// <code> /// <param name="encoding">The encoding.</param>
/// using System.Collections.Generic; public CsvWriter(Stream outputStream, Boolean leaveOpen, Encoding encoding) {
/// using Unosquare.Swan.Formatters; this._outputStream = outputStream;
/// this._encoding = encoding;
/// class Example this._leaveStreamOpen = leaveOpen;
/// { }
/// class Person
/// {
/// public string Name { get; set; }
/// public int Age { get; set; }
/// }
///
/// static void Main()
/// {
/// // create a list of people
/// var people = new List&lt;Person&gt;
/// {
/// new Person { Name = "Artyom", Age = 20 },
/// new Person { Name = "Aloy", Age = 18 }
/// }
///
/// // write items inside file.csv
/// CsvWriter.SaveRecords(people, "C:\\Users\\user\\Documents\\file.csv");
///
/// // output
/// // | Name | Age |
/// // | Artyom | 20 |
/// // | Aloy | 18 |
/// }
/// }
/// </code>
/// </example>
public class CsvWriter : IDisposable
{
private static readonly PropertyTypeCache TypeCache = new PropertyTypeCache();
private readonly object _syncLock = new object(); /// <summary>
private readonly Stream _outputStream; /// Initializes a new instance of the <see cref="CsvWriter"/> class.
private readonly Encoding _encoding; /// It automatically closes the stream when disposing this writer.
private readonly bool _leaveStreamOpen; /// </summary>
private bool _isDisposing; /// <param name="outputStream">The output stream.</param>
private ulong _mCount; /// <param name="encoding">The encoding.</param>
public CsvWriter(Stream outputStream, Encoding encoding)
: this(outputStream, false, encoding) {
// placeholder
}
#region Constructors /// <summary>
/// Initializes a new instance of the <see cref="CsvWriter"/> class.
/// It uses the Windows 1252 encoding and automatically closes
/// the stream upon disposing this writer.
/// </summary>
/// <param name="outputStream">The output stream.</param>
public CsvWriter(Stream outputStream)
: this(outputStream, false, Definitions.Windows1252Encoding) {
// placeholder
}
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="CsvWriter" /> class. /// Initializes a new instance of the <see cref="CsvWriter"/> class.
/// </summary> /// It opens the file given file, automatically closes the stream upon
/// <param name="outputStream">The output stream.</param> /// disposing of this writer, and uses the Windows 1252 encoding.
/// <param name="leaveOpen">if set to <c>true</c> [leave open].</param> /// </summary>
/// <param name="encoding">The encoding.</param> /// <param name="filename">The filename.</param>
public CsvWriter(Stream outputStream, bool leaveOpen, Encoding encoding) public CsvWriter(String filename)
{ : this(File.OpenWrite(filename), false, Definitions.Windows1252Encoding) {
_outputStream = outputStream; // placeholder
_encoding = encoding; }
_leaveStreamOpen = leaveOpen;
/// <summary>
/// Initializes a new instance of the <see cref="CsvWriter"/> class.
/// It opens the file given file, automatically closes the stream upon
/// disposing of this writer, and uses the given text encoding for output.
/// </summary>
/// <param name="filename">The filename.</param>
/// <param name="encoding">The encoding.</param>
public CsvWriter(String filename, Encoding encoding)
: this(File.OpenWrite(filename), false, encoding) {
// placeholder
}
#endregion
#region Properties
/// <summary>
/// Gets or sets the field separator character.
/// </summary>
/// <value>
/// The separator character.
/// </value>
public Char SeparatorCharacter { get; set; } = ',';
/// <summary>
/// Gets or sets the escape character to use to escape field values.
/// </summary>
/// <value>
/// The escape character.
/// </value>
public Char EscapeCharacter { get; set; } = '"';
/// <summary>
/// Gets or sets the new line character sequence to use when writing a line.
/// </summary>
/// <value>
/// The new line sequence.
/// </value>
public String NewLineSequence { get; set; } = Environment.NewLine;
/// <summary>
/// Defines a list of properties to ignore when outputting CSV lines.
/// </summary>
/// <value>
/// The ignore property names.
/// </value>
public List<String> IgnorePropertyNames { get; } = new List<String>();
/// <summary>
/// Gets number of lines that have been written, including the headings line.
/// </summary>
/// <value>
/// The count.
/// </value>
public UInt64 Count {
get {
lock(this._syncLock) {
return this._mCount;
}
}
}
#endregion
#region Helpers
/// <summary>
/// Saves the items to a stream.
/// It uses the Windows 1252 text encoding for output.
/// </summary>
/// <typeparam name="T">The type of enumeration.</typeparam>
/// <param name="items">The items.</param>
/// <param name="stream">The stream.</param>
/// <param name="truncateData"><c>true</c> if stream is truncated, default <c>false</c>.</param>
/// <returns>Number of item saved.</returns>
public static Int32 SaveRecords<T>(IEnumerable<T> items, Stream stream, Boolean truncateData = false) {
// truncate the file if it had data
if(truncateData && stream.Length > 0) {
stream.SetLength(0);
}
using(CsvWriter writer = new CsvWriter(stream)) {
writer.WriteHeadings<T>();
writer.WriteObjects(items);
return (Int32)writer.Count;
}
}
/// <summary>
/// Saves the items to a CSV file.
/// If the file exits, it overwrites it. If it does not, it creates it.
/// It uses the Windows 1252 text encoding for output.
/// </summary>
/// <typeparam name="T">The type of enumeration.</typeparam>
/// <param name="items">The items.</param>
/// <param name="filePath">The file path.</param>
/// <returns>Number of item saved.</returns>
public static Int32 SaveRecords<T>(IEnumerable<T> items, String filePath) => SaveRecords(items, File.OpenWrite(filePath), true);
#endregion
#region Generic, main Write Line Method
/// <summary>
/// Writes a line of CSV text. Items are converted to strings.
/// If items are found to be null, empty strings are written out.
/// If items are not string, the ToStringInvariant() method is called on them.
/// </summary>
/// <param name="items">The items.</param>
public void WriteLine(params Object[] items)
=> this.WriteLine(items.Select(x => x == null ? String.Empty : x.ToStringInvariant()));
/// <summary>
/// Writes a line of CSV text. Items are converted to strings.
/// If items are found to be null, empty strings are written out.
/// If items are not string, the ToStringInvariant() method is called on them.
/// </summary>
/// <param name="items">The items.</param>
public void WriteLine(IEnumerable<Object> items)
=> this.WriteLine(items.Select(x => x == null ? String.Empty : x.ToStringInvariant()));
/// <summary>
/// Writes a line of CSV text.
/// If items are found to be null, empty strings are written out.
/// </summary>
/// <param name="items">The items.</param>
public void WriteLine(params String[] items) => this.WriteLine((IEnumerable<String>)items);
/// <summary>
/// Writes a line of CSV text.
/// If items are found to be null, empty strings are written out.
/// </summary>
/// <param name="items">The items.</param>
public void WriteLine(IEnumerable<String> items) {
lock(this._syncLock) {
Int32 length = items.Count();
Byte[] separatorBytes = this._encoding.GetBytes(new[] { this.SeparatorCharacter });
Byte[] endOfLineBytes = this._encoding.GetBytes(this.NewLineSequence);
// Declare state variables here to avoid recreation, allocation and
// reassignment in every loop
Boolean needsEnclosing;
String textValue;
Byte[] output;
for(Int32 i = 0; i < length; i++) {
textValue = items.ElementAt(i);
// Determine if we need the string to be enclosed
// (it either contains an escape, new line, or separator char)
needsEnclosing = textValue.IndexOf(this.SeparatorCharacter) >= 0
|| textValue.IndexOf(this.EscapeCharacter) >= 0
|| textValue.IndexOf('\r') >= 0
|| textValue.IndexOf('\n') >= 0;
// Escape the escape characters by repeating them twice for every instance
textValue = textValue.Replace($"{this.EscapeCharacter}",
$"{this.EscapeCharacter}{this.EscapeCharacter}");
// Enclose the text value if we need to
if(needsEnclosing) {
textValue = String.Format($"{this.EscapeCharacter}{textValue}{this.EscapeCharacter}", textValue);
}
// Get the bytes to write to the stream and write them
output = this._encoding.GetBytes(textValue);
this._outputStream.Write(output, 0, output.Length);
// only write a separator if we are moving in between values.
// the last value should not be written.
if(i < length - 1) {
this._outputStream.Write(separatorBytes, 0, separatorBytes.Length);
}
} }
/// <summary> // output the newline sequence
/// Initializes a new instance of the <see cref="CsvWriter"/> class. this._outputStream.Write(endOfLineBytes, 0, endOfLineBytes.Length);
/// It automatically closes the stream when disposing this writer. this._mCount += 1;
/// </summary> }
/// <param name="outputStream">The output stream.</param> }
/// <param name="encoding">The encoding.</param>
public CsvWriter(Stream outputStream, Encoding encoding)
: this(outputStream, false, encoding)
{
// placeholder
}
/// <summary> #endregion
/// Initializes a new instance of the <see cref="CsvWriter"/> class.
/// It uses the Windows 1252 encoding and automatically closes
/// the stream upon disposing this writer.
/// </summary>
/// <param name="outputStream">The output stream.</param>
public CsvWriter(Stream outputStream)
: this(outputStream, false, Definitions.Windows1252Encoding)
{
// placeholder
}
/// <summary> #region Write Object Method
/// Initializes a new instance of the <see cref="CsvWriter"/> class.
/// It opens the file given file, automatically closes the stream upon
/// disposing of this writer, and uses the Windows 1252 encoding.
/// </summary>
/// <param name="filename">The filename.</param>
public CsvWriter(string filename)
: this(File.OpenWrite(filename), false, Definitions.Windows1252Encoding)
{
// placeholder
}
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="CsvWriter"/> class. /// Writes a row of CSV text. It handles the special cases where the object is
/// It opens the file given file, automatically closes the stream upon /// a dynamic object or and array. It also handles non-collection objects fine.
/// disposing of this writer, and uses the given text encoding for output. /// If you do not like the way the output is handled, you can simply write an extension
/// </summary> /// method of this class and use the WriteLine method instead.
/// <param name="filename">The filename.</param> /// </summary>
/// <param name="encoding">The encoding.</param> /// <param name="item">The item.</param>
public CsvWriter(string filename, Encoding encoding) /// <exception cref="System.ArgumentNullException">item.</exception>
: this(File.OpenWrite(filename), false, encoding) public void WriteObject(Object item) {
{ if(item == null) {
// placeholder throw new ArgumentNullException(nameof(item));
} }
#endregion lock(this._syncLock) {
switch(item) {
#region Properties case IDictionary typedItem:
this.WriteLine(this.GetFilteredDictionary(typedItem));
/// <summary> return;
/// Gets or sets the field separator character. case ICollection typedItem:
/// </summary> this.WriteLine(typedItem.Cast<Object>());
/// <value> return;
/// The separator character. default:
/// </value> this.WriteLine(this.GetFilteredTypeProperties(item.GetType())
public char SeparatorCharacter { get; set; } = ',';
/// <summary>
/// Gets or sets the escape character to use to escape field values.
/// </summary>
/// <value>
/// The escape character.
/// </value>
public char EscapeCharacter { get; set; } = '"';
/// <summary>
/// Gets or sets the new line character sequence to use when writing a line.
/// </summary>
/// <value>
/// The new line sequence.
/// </value>
public string NewLineSequence { get; set; } = Environment.NewLine;
/// <summary>
/// Defines a list of properties to ignore when outputting CSV lines.
/// </summary>
/// <value>
/// The ignore property names.
/// </value>
public List<string> IgnorePropertyNames { get; } = new List<string>();
/// <summary>
/// Gets number of lines that have been written, including the headings line.
/// </summary>
/// <value>
/// The count.
/// </value>
public ulong Count
{
get
{
lock (_syncLock)
{
return _mCount;
}
}
}
#endregion
#region Helpers
/// <summary>
/// Saves the items to a stream.
/// It uses the Windows 1252 text encoding for output.
/// </summary>
/// <typeparam name="T">The type of enumeration.</typeparam>
/// <param name="items">The items.</param>
/// <param name="stream">The stream.</param>
/// <param name="truncateData"><c>true</c> if stream is truncated, default <c>false</c>.</param>
/// <returns>Number of item saved.</returns>
public static int SaveRecords<T>(IEnumerable<T> items, Stream stream, bool truncateData = false)
{
// truncate the file if it had data
if (truncateData && stream.Length > 0)
stream.SetLength(0);
using (var writer = new CsvWriter(stream))
{
writer.WriteHeadings<T>();
writer.WriteObjects(items);
return (int)writer.Count;
}
}
/// <summary>
/// Saves the items to a CSV file.
/// If the file exits, it overwrites it. If it does not, it creates it.
/// It uses the Windows 1252 text encoding for output.
/// </summary>
/// <typeparam name="T">The type of enumeration.</typeparam>
/// <param name="items">The items.</param>
/// <param name="filePath">The file path.</param>
/// <returns>Number of item saved.</returns>
public static int SaveRecords<T>(IEnumerable<T> items, string filePath) => SaveRecords(items, File.OpenWrite(filePath), true);
#endregion
#region Generic, main Write Line Method
/// <summary>
/// Writes a line of CSV text. Items are converted to strings.
/// If items are found to be null, empty strings are written out.
/// If items are not string, the ToStringInvariant() method is called on them.
/// </summary>
/// <param name="items">The items.</param>
public void WriteLine(params object[] items)
=> WriteLine(items.Select(x => x == null ? string.Empty : x.ToStringInvariant()));
/// <summary>
/// Writes a line of CSV text. Items are converted to strings.
/// If items are found to be null, empty strings are written out.
/// If items are not string, the ToStringInvariant() method is called on them.
/// </summary>
/// <param name="items">The items.</param>
public void WriteLine(IEnumerable<object> items)
=> WriteLine(items.Select(x => x == null ? string.Empty : x.ToStringInvariant()));
/// <summary>
/// Writes a line of CSV text.
/// If items are found to be null, empty strings are written out.
/// </summary>
/// <param name="items">The items.</param>
public void WriteLine(params string[] items) => WriteLine((IEnumerable<string>) items);
/// <summary>
/// Writes a line of CSV text.
/// If items are found to be null, empty strings are written out.
/// </summary>
/// <param name="items">The items.</param>
public void WriteLine(IEnumerable<string> items)
{
lock (_syncLock)
{
var length = items.Count();
var separatorBytes = _encoding.GetBytes(new[] { SeparatorCharacter });
var endOfLineBytes = _encoding.GetBytes(NewLineSequence);
// Declare state variables here to avoid recreation, allocation and
// reassignment in every loop
bool needsEnclosing;
string textValue;
byte[] output;
for (var i = 0; i < length; i++)
{
textValue = items.ElementAt(i);
// Determine if we need the string to be enclosed
// (it either contains an escape, new line, or separator char)
needsEnclosing = textValue.IndexOf(SeparatorCharacter) >= 0
|| textValue.IndexOf(EscapeCharacter) >= 0
|| textValue.IndexOf('\r') >= 0
|| textValue.IndexOf('\n') >= 0;
// Escape the escape characters by repeating them twice for every instance
textValue = textValue.Replace($"{EscapeCharacter}",
$"{EscapeCharacter}{EscapeCharacter}");
// Enclose the text value if we need to
if (needsEnclosing)
textValue = string.Format($"{EscapeCharacter}{textValue}{EscapeCharacter}", textValue);
// Get the bytes to write to the stream and write them
output = _encoding.GetBytes(textValue);
_outputStream.Write(output, 0, output.Length);
// only write a separator if we are moving in between values.
// the last value should not be written.
if (i < length - 1)
_outputStream.Write(separatorBytes, 0, separatorBytes.Length);
}
// output the newline sequence
_outputStream.Write(endOfLineBytes, 0, endOfLineBytes.Length);
_mCount += 1;
}
}
#endregion
#region Write Object Method
/// <summary>
/// Writes a row of CSV text. It handles the special cases where the object is
/// a dynamic object or and array. It also handles non-collection objects fine.
/// If you do not like the way the output is handled, you can simply write an extension
/// method of this class and use the WriteLine method instead.
/// </summary>
/// <param name="item">The item.</param>
/// <exception cref="System.ArgumentNullException">item.</exception>
public void WriteObject(object item)
{
if (item == null)
throw new ArgumentNullException(nameof(item));
lock (_syncLock)
{
switch (item)
{
case IDictionary typedItem:
WriteLine(GetFilteredDictionary(typedItem));
return;
case ICollection typedItem:
WriteLine(typedItem.Cast<object>());
return;
default:
WriteLine(GetFilteredTypeProperties(item.GetType())
.Select(x => x.ToFormattedString(item))); .Select(x => x.ToFormattedString(item)));
break; break;
}
}
} }
}
}
/// <summary> /// <summary>
/// Writes a row of CSV text. It handles the special cases where the object is /// Writes a row of CSV text. It handles the special cases where the object is
/// a dynamic object or and array. It also handles non-collection objects fine. /// a dynamic object or and array. It also handles non-collection objects fine.
/// If you do not like the way the output is handled, you can simply write an extension /// If you do not like the way the output is handled, you can simply write an extension
/// method of this class and use the WriteLine method instead. /// method of this class and use the WriteLine method instead.
/// </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
/// WriteObject method. For more info check out the description of the WriteObject /// WriteObject method. For more info check out the description of the WriteObject
/// method. /// method.
/// </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);
}
} }
}
}
#endregion #endregion
#region Write Headings Methods #region Write Headings Methods
/// <summary> /// <summary>
/// Writes the headings. /// Writes the headings.
/// </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));
} }
#if NET452 #if NET452
/// <summary> /// <summary>
/// Writes the headings. /// Writes the headings.
/// </summary> /// </summary>
/// <param name="item">The object to extract headings.</param> /// <param name="item">The object to extract headings.</param>
/// <exception cref="ArgumentNullException">item</exception> /// <exception cref="ArgumentNullException">item</exception>
/// <exception cref="ArgumentException">Unable to cast dynamic object to a suitable dictionary - item</exception> /// <exception cref="ArgumentException">Unable to cast dynamic object to a suitable dictionary - item</exception>
public void WriteHeadings(dynamic item) public void WriteHeadings(dynamic item) {
{ if(item == null) {
if (item == null) throw new ArgumentNullException(nameof(item));
throw new ArgumentNullException(nameof(item)); }
if (!(item is IDictionary<string, object> dictionary)) if(!(item is IDictionary<global::System.String, global::System.Object> dictionary)) {
throw new ArgumentException("Unable to cast dynamic object to a suitable dictionary", nameof(item)); throw new ArgumentException("Unable to cast dynamic object to a suitable dictionary", nameof(item));
}
WriteHeadings(dictionary); this.WriteHeadings(dictionary);
} }
#else #else
/// <summary> /// <summary>
/// Writes the headings. /// Writes the headings.
@ -425,54 +411,53 @@
} }
#endif #endif
#endregion #endregion
#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;
} }
}
#endregion this._isDisposing = true;
#region Support Methods
private IEnumerable<string> GetFilteredDictionary(IDictionary dictionary, bool filterKeys = false)
=> dictionary
.Keys
.Cast<object>()
.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)
=> TypeCache.Retrieve(type, t =>
t.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Where(p => p.CanRead))
.Where(p => !IgnorePropertyNames.Contains(p.Name));
#endregion
} }
#endregion
#region Support Methods
private IEnumerable<String> GetFilteredDictionary(IDictionary dictionary, Boolean filterKeys = false)
=> dictionary
.Keys
.Cast<Object>()
.Select(key => key == null ? String.Empty : key.ToStringInvariant())
.Where(stringKey => !this.IgnorePropertyNames.Contains(stringKey))
.Select(stringKey =>
filterKeys
? stringKey
: dictionary[stringKey] == null ? String.Empty : dictionary[stringKey].ToStringInvariant());
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));
#endregion
}
} }

View File

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

View File

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

View File

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

View File

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

View File

@ -1,107 +1,107 @@
namespace Unosquare.Swan.Formatters using System;
{ using System.Collections.Generic;
using System; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Linq;
using System.Collections.Concurrent; using System.Reflection;
using System.Linq; using Unosquare.Swan.Attributes;
using System.Reflection;
using Attributes;
/// <summary> namespace Unosquare.Swan.Formatters {
/// A very simple, light-weight JSON library written by Mario /// <summary>
/// to teach Geo how things are done /// A very simple, light-weight JSON library written by Mario
/// /// to teach Geo how things are done
/// This is an useful helper for small tasks but it doesn't represent a full-featured ///
/// serializer such as the beloved Json.NET. /// This is an useful helper for small tasks but it doesn't represent a full-featured
/// </summary> /// serializer such as the beloved Json.NET.
public partial class Json /// </summary>
{ public partial class Json {
private class SerializerOptions private 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>>();
public SerializerOptions( public SerializerOptions(
bool format, Boolean format,
string typeSpecifier, String typeSpecifier,
string[] includeProperties, String[] includeProperties,
string[] excludeProperties = null, String[] excludeProperties = null,
bool includeNonPublic = true, Boolean includeNonPublic = true,
IReadOnlyCollection<WeakReference> parentReferences = null) IReadOnlyCollection<WeakReference> parentReferences = null) {
{ this._includeProperties = includeProperties;
_includeProperties = includeProperties; this._excludeProperties = excludeProperties;
_excludeProperties = excludeProperties;
IncludeNonPublic = includeNonPublic; this.IncludeNonPublic = includeNonPublic;
Format = format; this.Format = format;
TypeSpecifier = typeSpecifier; this.TypeSpecifier = typeSpecifier;
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); }
} }
}
public bool Format { get; } public Boolean Format {
public string TypeSpecifier { get; } get;
public bool IncludeNonPublic { get; } }
public String TypeSpecifier {
get;
}
public Boolean IncludeNonPublic {
get;
}
internal bool IsObjectPresent(object target) internal Boolean IsObjectPresent(Object target) {
{ Int32 hashCode = target.GetHashCode();
var 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)
=> GetPropertiesCache(targetType) => GetPropertiesCache(targetType)
.When(() => _includeProperties?.Length > 0, .When(() => this._includeProperties?.Length > 0,
query => query.Where(p => _includeProperties.Contains(p.Key.Item1))) query => query.Where(p => this._includeProperties.Contains(p.Key.Item1)))
.When(() => _excludeProperties?.Length > 0, .When(() => this._excludeProperties?.Length > 0,
query => query.Where(p => !_excludeProperties.Contains(p.Key.Item1))) query => query.Where(p => !this._excludeProperties.Contains(p.Key.Item1)))
.ToDictionary(x => x.Key.Item2, x => x.Value); .ToDictionary(x => x.Key.Item2, x => x.Value);
private static Dictionary<Tuple<string, string>, MemberInfo> GetPropertiesCache(Type targetType) private static 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.RetrieveAllProperties(targetType).Where(p => p.CanRead)); new List<MemberInfo>(PropertyTypeCache.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.RetrieveAllFields(targetType));
fields.AddRange(FieldTypeCache.RetrieveAllFields(targetType));
}
var value = fields
.ToDictionary(
x => Tuple.Create(x.Name,
x.GetCustomAttribute<JsonPropertyAttribute>()?.PropertyName ?? x.Name),
x => x);
TypeCache.TryAdd(targetType, value);
return value;
}
} }
Dictionary<Tuple<String, String>, MemberInfo> value = fields
.ToDictionary(
x => Tuple.Create(x.Name,
x.GetCustomAttribute<JsonPropertyAttribute>()?.PropertyName ?? x.Name),
x => x);
_ = TypeCache.TryAdd(targetType, value);
return value;
}
} }
}
} }

View File

@ -1,331 +1,319 @@
namespace Unosquare.Swan.Formatters using Unosquare.Swan.Reflection;
{ using System;
using Reflection; using Unosquare.Swan.Components;
using System; using System.Collections.Generic;
using Components; using System.Linq;
using System.Collections.Generic; using System.Reflection;
using System.Linq; using Unosquare.Swan.Attributes;
using System.Reflection;
using Attributes; namespace Unosquare.Swan.Formatters {
/// <summary>
/// A very simple, light-weight JSON library written by Mario
/// to teach Geo how things are done
///
/// This is an useful helper for small tasks but it doesn't represent a full-featured
/// serializer such as the beloved Json.NET.
/// </summary>
public static partial class Json {
#region Constants
internal const String AddMethodName = "Add";
private const Char OpenObjectChar = '{';
private const Char CloseObjectChar = '}';
private const Char OpenArrayChar = '[';
private const Char CloseArrayChar = ']';
private const Char FieldSeparatorChar = ',';
private const Char ValueSeparatorChar = ':';
private const Char StringEscapeChar = '\\';
private const Char StringQuotedChar = '"';
private const String EmptyObjectLiteral = "{ }";
private const String EmptyArrayLiteral = "[ ]";
private const String TrueLiteral = "true";
private const String FalseLiteral = "false";
private const String NullLiteral = "null";
#endregion
private static readonly PropertyTypeCache PropertyTypeCache = new PropertyTypeCache();
private static readonly FieldTypeCache FieldTypeCache = new FieldTypeCache();
private static readonly CollectionCacheRepository<String> IgnoredPropertiesCache = new CollectionCacheRepository<String>();
#region Public API
/// <summary> /// <summary>
/// A very simple, light-weight JSON library written by Mario /// Serializes the specified object into a JSON string.
/// to teach Geo how things are done
///
/// This is an useful helper for small tasks but it doesn't represent a full-featured
/// serializer such as the beloved Json.NET.
/// </summary> /// </summary>
public static partial class Json /// <param name="obj">The object.</param>
{ /// <param name="format">if set to <c>true</c> it formats and indents the output.</param>
#region Constants /// <param name="typeSpecifier">The type specifier. Leave null or empty to avoid setting.</param>
/// <param name="includeNonPublic">if set to <c>true</c> non-public getters will be also read.</param>
/// <param name="includedNames">The included property names.</param>
/// <param name="excludedNames">The excluded property names.</param>
/// <returns>
/// A <see cref="System.String" /> that represents the current object.
/// </returns>
/// <example>
/// The following example describes how to serialize a simple object.
/// <code>
/// using Unosquare.Swan.Formatters;
///
/// class Example
/// {
/// static void Main()
/// {
/// var obj = new { One = "One", Two = "Two" };
///
/// var serial = Json.Serialize(obj); // {"One": "One","Two": "Two"}
/// }
/// }
/// </code>
/// The following example details how to serialize an object using the <see cref="JsonPropertyAttribute"/>.
/// <code>
/// using Unosquare.Swan.Attributes;
/// using Unosquare.Swan.Formatters;
///
/// class Example
/// {
/// class JsonPropertyExample
/// {
/// [JsonProperty("data")]
/// public string Data { get; set; }
///
/// [JsonProperty("ignoredData", true)]
/// public string IgnoredData { get; set; }
/// }
///
/// static void Main()
/// {
/// var obj = new JsonPropertyExample() { Data = "OK", IgnoredData = "OK" };
///
/// // {"data": "OK"}
/// var serializedObj = Json.Serialize(obj);
/// }
/// }
/// </code>
/// </example>
public static String Serialize(
Object obj,
Boolean format = false,
String typeSpecifier = null,
Boolean includeNonPublic = false,
String[] includedNames = null,
String[] excludedNames = null) => Serialize(obj, format, typeSpecifier, includeNonPublic, includedNames, excludedNames, null);
internal const string AddMethodName = "Add"; /// <summary>
/// Serializes the specified object into a JSON string.
/// </summary>
/// <param name="obj">The object.</param>
/// <param name="format">if set to <c>true</c> it formats and indents the output.</param>
/// <param name="typeSpecifier">The type specifier. Leave null or empty to avoid setting.</param>
/// <param name="includeNonPublic">if set to <c>true</c> non-public getters will be also read.</param>
/// <param name="includedNames">The included property names.</param>
/// <param name="excludedNames">The excluded property names.</param>
/// <param name="parentReferences">The parent references.</param>
/// <returns>
/// A <see cref="System.String" /> that represents the current object.
/// </returns>
public static String Serialize(
Object obj,
Boolean format,
String typeSpecifier,
Boolean includeNonPublic,
String[] includedNames,
String[] excludedNames,
List<WeakReference> parentReferences) {
if(obj != null && (obj is String || Definitions.AllBasicValueTypes.Contains(obj.GetType()))) {
return SerializePrimitiveValue(obj);
}
private const char OpenObjectChar = '{'; SerializerOptions options = new SerializerOptions(
private const char CloseObjectChar = '}'; format,
typeSpecifier,
includedNames,
GetExcludedNames(obj?.GetType(), excludedNames),
includeNonPublic,
parentReferences);
private const char OpenArrayChar = '['; return Serializer.Serialize(obj, 0, options);
private const char CloseArrayChar = ']'; }
private const char FieldSeparatorChar = ','; /// <summary>
private const char ValueSeparatorChar = ':'; /// Serializes the specified object only including the specified property names.
/// </summary>
/// <param name="obj">The object.</param>
/// <param name="format">if set to <c>true</c> it formats and indents the output.</param>
/// <param name="includeNames">The include names.</param>
/// <returns>A <see cref="System.String" /> that represents the current object.</returns>
/// <example>
/// The following example shows how to serialize a simple object including the specified properties.
/// <code>
/// using Unosquare.Swan.Formatters;
///
/// class Example
/// {
/// static void Main()
/// {
/// // object to serialize
/// var obj = new { One = "One", Two = "Two", Three = "Three" };
///
/// // the included names
/// var includedNames = new[] { "Two", "Three" };
///
/// // serialize only the included names
/// var data = Json.SerializeOnly(basicObject, true, includedNames);
/// // {"Two": "Two","Three": "Three" }
/// }
/// }
/// </code>
/// </example>
public static String SerializeOnly(Object obj, Boolean format, params String[] includeNames) {
SerializerOptions options = new SerializerOptions(format, null, includeNames);
private const char StringEscapeChar = '\\'; return Serializer.Serialize(obj, 0, options);
private const char StringQuotedChar = '"'; }
private const string EmptyObjectLiteral = "{ }"; /// <summary>
private const string EmptyArrayLiteral = "[ ]"; /// Serializes the specified object excluding the specified property names.
private const string TrueLiteral = "true"; /// </summary>
private const string FalseLiteral = "false"; /// <param name="obj">The object.</param>
private const string NullLiteral = "null"; /// <param name="format">if set to <c>true</c> it formats and indents the output.</param>
/// <param name="excludeNames">The exclude names.</param>
/// <returns>A <see cref="System.String" /> that represents the current object.</returns>
/// <example>
/// The following code shows how to serialize a simple object exluding the specified properties.
/// <code>
/// using Unosquare.Swan.Formatters;
///
/// class Example
/// {
/// static void Main()
/// {
/// // object to serialize
/// var obj = new { One = "One", Two = "Two", Three = "Three" };
///
/// // the excluded names
/// var excludeNames = new[] { "Two", "Three" };
///
/// // serialize excluding
/// var data = Json.SerializeExcluding(basicObject, false, includedNames);
/// // {"One": "One"}
/// }
/// }
/// </code>
/// </example>
public static String SerializeExcluding(Object obj, Boolean format, params String[] excludeNames) {
SerializerOptions options = new SerializerOptions(format, null, null, excludeNames);
#endregion return Serializer.Serialize(obj, 0, options);
}
private static readonly PropertyTypeCache PropertyTypeCache = new PropertyTypeCache(); /// <summary>
private static readonly FieldTypeCache FieldTypeCache = new FieldTypeCache(); /// Deserializes the specified json string as either a Dictionary[string, object] or as a List[object]
private static readonly CollectionCacheRepository<string> IgnoredPropertiesCache = new CollectionCacheRepository<string>(); /// depending on the syntax of the JSON string.
/// </summary>
/// <param name="json">The json.</param>
/// <returns>Type of the current deserializes.</returns>
/// <example>
/// The following code shows how to deserialize a JSON string into a Dictionary.
/// <code>
/// using Unosquare.Swan.Formatters;
///
/// class Example
/// {
/// static void Main()
/// {
/// // json to deserialize
/// var basicJson = "{\"One\":\"One\",\"Two\":\"Two\",\"Three\":\"Three\"}";
///
/// // deserializes the specified json into a Dictionary&lt;string, object&gt;.
/// var data = Json.Deserialize(basicJson);
/// }
/// }
/// </code>
/// </example>
public static Object Deserialize(String json) => Deserializer.DeserializeInternal(json);
#region Public API /// <summary>
/// Deserializes the specified json string and converts it to the specified object type.
/// Non-public constructors and property setters are ignored.
/// </summary>
/// <typeparam name="T">The type of object to deserialize.</typeparam>
/// <param name="json">The json.</param>
/// <returns>The deserialized specified type object.</returns>
/// <example>
/// The following code describes how to deserialize a JSON string into an object of type T.
/// <code>
/// using Unosquare.Swan.Formatters;
///
/// class Example
/// {
/// static void Main()
/// {
/// // json type BasicJson to serialize
/// var basicJson = "{\"One\":\"One\",\"Two\":\"Two\",\"Three\":\"Three\"}";
///
/// // deserializes the specified string in a new instance of the type BasicJson.
/// var data = Json.Deserialize&lt;BasicJson&gt;(basicJson);
/// }
/// }
/// </code>
/// </example>
public static T Deserialize<T>(String json) => (T)Deserialize(json, typeof(T));
/// <summary> /// <summary>
/// Serializes the specified object into a JSON string. /// Deserializes the specified json string and converts it to the specified object type.
/// </summary> /// </summary>
/// <param name="obj">The object.</param> /// <typeparam name="T">The type of object to deserialize.</typeparam>
/// <param name="format">if set to <c>true</c> it formats and indents the output.</param> /// <param name="json">The json.</param>
/// <param name="typeSpecifier">The type specifier. Leave null or empty to avoid setting.</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 <c>true</c> non-public getters will be also read.</param> /// <returns>The deserialized specified type object.</returns>
/// <param name="includedNames">The included property names.</param> public static T Deserialize<T>(String json, Boolean includeNonPublic) => (T)Deserialize(json, typeof(T), includeNonPublic);
/// <param name="excludedNames">The excluded property names.</param>
/// <returns>
/// A <see cref="System.String" /> that represents the current object.
/// </returns>
/// <example>
/// The following example describes how to serialize a simple object.
/// <code>
/// using Unosquare.Swan.Formatters;
///
/// class Example
/// {
/// static void Main()
/// {
/// var obj = new { One = "One", Two = "Two" };
///
/// var serial = Json.Serialize(obj); // {"One": "One","Two": "Two"}
/// }
/// }
/// </code>
/// The following example details how to serialize an object using the <see cref="JsonPropertyAttribute"/>.
/// <code>
/// using Unosquare.Swan.Attributes;
/// using Unosquare.Swan.Formatters;
///
/// class Example
/// {
/// class JsonPropertyExample
/// {
/// [JsonProperty("data")]
/// public string Data { get; set; }
///
/// [JsonProperty("ignoredData", true)]
/// public string IgnoredData { get; set; }
/// }
///
/// static void Main()
/// {
/// var obj = new JsonPropertyExample() { Data = "OK", IgnoredData = "OK" };
///
/// // {"data": "OK"}
/// var serializedObj = Json.Serialize(obj);
/// }
/// }
/// </code>
/// </example>
public static string Serialize(
object obj,
bool format = false,
string typeSpecifier = null,
bool includeNonPublic = false,
string[] includedNames = null,
string[] excludedNames = null)
{
return Serialize(obj, format, typeSpecifier, includeNonPublic, includedNames, excludedNames, null);
}
/// <summary> /// <summary>
/// Serializes the specified object into a JSON string. /// Deserializes the specified json string and converts it to the specified object type.
/// </summary> /// </summary>
/// <param name="obj">The object.</param> /// <param name="json">The json.</param>
/// <param name="format">if set to <c>true</c> it formats and indents the output.</param> /// <param name="resultType">Type of the result.</param>
/// <param name="typeSpecifier">The type specifier. Leave null or empty to avoid setting.</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 <c>true</c> non-public getters will be also read.</param> /// <returns>Type of the current conversion from json result.</returns>
/// <param name="includedNames">The included property names.</param> public static Object Deserialize(String json, Type resultType, Boolean includeNonPublic = false)
/// <param name="excludedNames">The excluded property names.</param> => Converter.FromJsonResult(Deserializer.DeserializeInternal(json), resultType, includeNonPublic);
/// <param name="parentReferences">The parent references.</param>
/// <returns>
/// A <see cref="System.String" /> that represents the current object.
/// </returns>
public static string Serialize(
object obj,
bool format,
string typeSpecifier,
bool includeNonPublic,
string[] includedNames,
string[] excludedNames,
List<WeakReference> parentReferences)
{
if (obj != null && (obj is string || Definitions.AllBasicValueTypes.Contains(obj.GetType())))
{
return SerializePrimitiveValue(obj);
}
var options = new SerializerOptions( #endregion
format,
typeSpecifier,
includedNames,
GetExcludedNames(obj?.GetType(), excludedNames),
includeNonPublic,
parentReferences);
return Serializer.Serialize(obj, 0, options); #region Private API
}
/// <summary> private static String[] GetExcludedNames(Type type, String[] excludedNames) {
/// Serializes the specified object only including the specified property names. if(type == null) {
/// </summary> return excludedNames;
/// <param name="obj">The object.</param> }
/// <param name="format">if set to <c>true</c> it formats and indents the output.</param>
/// <param name="includeNames">The include names.</param>
/// <returns>A <see cref="System.String" /> that represents the current object.</returns>
/// <example>
/// The following example shows how to serialize a simple object including the specified properties.
/// <code>
/// using Unosquare.Swan.Formatters;
///
/// class Example
/// {
/// static void Main()
/// {
/// // object to serialize
/// var obj = new { One = "One", Two = "Two", Three = "Three" };
///
/// // the included names
/// var includedNames = new[] { "Two", "Three" };
///
/// // serialize only the included names
/// var data = Json.SerializeOnly(basicObject, true, includedNames);
/// // {"Two": "Two","Three": "Three" }
/// }
/// }
/// </code>
/// </example>
public static string SerializeOnly(object obj, bool format, params string[] includeNames)
{
var options = new SerializerOptions(format, null, includeNames);
return Serializer.Serialize(obj, 0, options); IEnumerable<String> excludedByAttr = IgnoredPropertiesCache.Retrieve(type, t => t.GetProperties()
}
/// <summary>
/// Serializes the specified object excluding the specified property names.
/// </summary>
/// <param name="obj">The object.</param>
/// <param name="format">if set to <c>true</c> it formats and indents the output.</param>
/// <param name="excludeNames">The exclude names.</param>
/// <returns>A <see cref="System.String" /> that represents the current object.</returns>
/// <example>
/// The following code shows how to serialize a simple object exluding the specified properties.
/// <code>
/// using Unosquare.Swan.Formatters;
///
/// class Example
/// {
/// static void Main()
/// {
/// // object to serialize
/// var obj = new { One = "One", Two = "Two", Three = "Three" };
///
/// // the excluded names
/// var excludeNames = new[] { "Two", "Three" };
///
/// // serialize excluding
/// var data = Json.SerializeExcluding(basicObject, false, includedNames);
/// // {"One": "One"}
/// }
/// }
/// </code>
/// </example>
public static string SerializeExcluding(object obj, bool format, params string[] excludeNames)
{
var options = new SerializerOptions(format, null, null, excludeNames);
return Serializer.Serialize(obj, 0, options);
}
/// <summary>
/// Deserializes the specified json string as either a Dictionary[string, object] or as a List[object]
/// depending on the syntax of the JSON string.
/// </summary>
/// <param name="json">The json.</param>
/// <returns>Type of the current deserializes.</returns>
/// <example>
/// The following code shows how to deserialize a JSON string into a Dictionary.
/// <code>
/// using Unosquare.Swan.Formatters;
///
/// class Example
/// {
/// static void Main()
/// {
/// // json to deserialize
/// var basicJson = "{\"One\":\"One\",\"Two\":\"Two\",\"Three\":\"Three\"}";
///
/// // deserializes the specified json into a Dictionary&lt;string, object&gt;.
/// var data = Json.Deserialize(basicJson);
/// }
/// }
/// </code>
/// </example>
public static object Deserialize(string json) => Deserializer.DeserializeInternal(json);
/// <summary>
/// Deserializes the specified json string and converts it to the specified object type.
/// Non-public constructors and property setters are ignored.
/// </summary>
/// <typeparam name="T">The type of object to deserialize.</typeparam>
/// <param name="json">The json.</param>
/// <returns>The deserialized specified type object.</returns>
/// <example>
/// The following code describes how to deserialize a JSON string into an object of type T.
/// <code>
/// using Unosquare.Swan.Formatters;
///
/// class Example
/// {
/// static void Main()
/// {
/// // json type BasicJson to serialize
/// var basicJson = "{\"One\":\"One\",\"Two\":\"Two\",\"Three\":\"Three\"}";
///
/// // deserializes the specified string in a new instance of the type BasicJson.
/// var data = Json.Deserialize&lt;BasicJson&gt;(basicJson);
/// }
/// }
/// </code>
/// </example>
public static T Deserialize<T>(string json) => (T)Deserialize(json, typeof(T));
/// <summary>
/// Deserializes the specified json string and converts it to the specified object type.
/// </summary>
/// <typeparam name="T">The type of object to deserialize.</typeparam>
/// <param name="json">The json.</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>
public static T Deserialize<T>(string json, bool includeNonPublic) => (T)Deserialize(json, typeof(T), includeNonPublic);
/// <summary>
/// Deserializes the specified json string and converts it to the specified object type.
/// </summary>
/// <param name="json">The json.</param>
/// <param name="resultType">Type of the result.</param>
/// <param name="includeNonPublic">if set to true, it also uses the non-public constructors and property setters.</param>
/// <returns>Type of the current conversion from json result.</returns>
public static object Deserialize(string json, Type resultType, bool includeNonPublic = false)
=> Converter.FromJsonResult(Deserializer.DeserializeInternal(json), resultType, includeNonPublic);
#endregion
#region Private API
private static string[] GetExcludedNames(Type type, string[] excludedNames)
{
if (type == null)
return excludedNames;
var excludedByAttr = IgnoredPropertiesCache.Retrieve(type, t => t.GetProperties()
.Where(x => Runtime.AttributeCache.RetrieveOne<JsonPropertyAttribute>(x)?.Ignored == true) .Where(x => Runtime.AttributeCache.RetrieveOne<JsonPropertyAttribute>(x)?.Ignored == true)
.Select(x => x.Name)); .Select(x => x.Name));
if (excludedByAttr?.Any() != true) return excludedByAttr?.Any() != true
return excludedNames; ? excludedNames
: excludedNames == null
return excludedNames == null
? excludedByAttr.ToArray() ? excludedByAttr.ToArray()
: excludedByAttr.Intersect(excludedNames).ToArray(); : excludedByAttr.Intersect(excludedNames).ToArray();
}
private static string SerializePrimitiveValue(object obj)
{
switch (obj)
{
case string stringValue:
return stringValue;
case bool boolValue:
return boolValue ? TrueLiteral : FalseLiteral;
default:
return obj.ToString();
}
}
#endregion
} }
private static String SerializePrimitiveValue(Object obj) {
switch(obj) {
case String stringValue:
return stringValue;
case Boolean boolValue:
return boolValue ? TrueLiteral : FalseLiteral;
default:
return obj.ToString();
}
}
#endregion
}
} }

View File

@ -1,174 +1,172 @@
namespace Unosquare.Swan.Reflection using System;
{ using System.Collections.Generic;
using System; using System.Reflection;
using System.Collections.Generic; using System.Collections.Concurrent;
using System.Reflection; using System.Linq;
using System.Collections.Concurrent;
using System.Linq; namespace Unosquare.Swan.Reflection {
/// <summary>
/// A thread-safe cache of attributes belonging to a given key (MemberInfo or Type).
///
/// The Retrieve method is the most useful one in this class as it
/// calls the retrieval process if the type is not contained
/// in the cache.
/// </summary>
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);
/// <summary> /// <summary>
/// A thread-safe cache of attributes belonging to a given key (MemberInfo or Type). /// Initializes a new instance of the <see cref="AttributeCache"/> class.
///
/// The Retrieve method is the most useful one in this class as it
/// calls the retrieval process if the type is not contained
/// in the cache.
/// </summary> /// </summary>
public class AttributeCache /// <param name="propertyCache">The property cache object.</param>
{ public AttributeCache(PropertyTypeCache propertyCache = null) => this.PropertyTypeCache = propertyCache ?? Runtime.PropertyTypeCache;
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. /// A PropertyTypeCache object for caching properties and their attributes.
/// </summary> /// </summary>
/// <param name="propertyCache">The property cache object.</param> public PropertyTypeCache PropertyTypeCache {
public AttributeCache(PropertyTypeCache propertyCache = null) get;
{ }
PropertyTypeCache = propertyCache ?? Runtime.PropertyTypeCache;
}
/// <summary> /// <summary>
/// A PropertyTypeCache object for caching properties and their attributes. /// Determines whether [contains] [the specified member].
/// </summary> /// </summary>
public PropertyTypeCache PropertyTypeCache { get; } /// <typeparam name="T">The type of the attribute to be retrieved.</typeparam>
/// <param name="member">The member.</param>
/// <returns>
/// <c>true</c> if [contains] [the specified member]; otherwise, <c>false</c>.
/// </returns>
public Boolean Contains<T>(MemberInfo member) => this._data.Value.ContainsKey(new Tuple<Object, Type>(member, typeof(T)));
/// <summary> /// <summary>
/// Determines whether [contains] [the specified member]. /// Gets specific attributes from a member constrained to an attribute.
/// </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>
/// <param name="member">The member.</param> /// <param name="member">The member.</param>
/// <returns> /// <param name="inherit"><c>true</c> to inspect the ancestors of element; otherwise, <c>false</c>.</param>
/// <c>true</c> if [contains] [the specified member]; otherwise, <c>false</c>. /// <returns>An array of the attributes stored for the specified type.</returns>
/// </returns> public IEnumerable<Object> Retrieve<T>(MemberInfo member, Boolean inherit = false)
public bool Contains<T>(MemberInfo member) => _data.Value.ContainsKey(new Tuple<object, Type>(member, typeof(T))); where T : Attribute {
if(member == null) {
throw new ArgumentNullException(nameof(member));
}
/// <summary> return this.Retrieve(new Tuple<Object, Type>(member, typeof(T)), t => member.GetCustomAttributes<T>(inherit));
/// Gets specific attributes from a member constrained to an attribute. }
/// </summary>
/// <typeparam name="T">The type of the attribute to be retrieved.</typeparam>
/// <param name="member">The member.</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>
public IEnumerable<object> Retrieve<T>(MemberInfo member, bool inherit = false)
where T : Attribute
{
if (member == null)
throw new ArgumentNullException(nameof(member));
return Retrieve(new Tuple<object, Type>(member, typeof(T)), t => member.GetCustomAttributes<T>(inherit)); /// <summary>
} /// Gets all attributes of a specific type from a member.
/// </summary>
/// <param name="member">The member.</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>
/// <returns>An array of the attributes stored for the specified type.</returns>
public IEnumerable<Object> Retrieve(MemberInfo member, Type type, Boolean inherit = false) {
if(member == null) {
throw new ArgumentNullException(nameof(member));
}
/// <summary> if(type == null) {
/// Gets all attributes of a specific type from a member. throw new ArgumentNullException(nameof(type));
/// </summary> }
/// <param name="member">The member.</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>
/// <returns>An array of the attributes stored for the specified type.</returns>
public IEnumerable<object> Retrieve(MemberInfo member, Type type, bool inherit = false)
{
if (member == null)
throw new ArgumentNullException(nameof(member));
if (type == null) return this.Retrieve(
throw new ArgumentNullException(nameof(type)); new Tuple<Object, Type>(member, type),
return Retrieve(
new Tuple<object, Type>(member, type),
t => member.GetCustomAttributes(type, inherit)); t => member.GetCustomAttributes(type, inherit));
} }
/// <summary> /// <summary>
/// Gets one attribute of a specific type from a member. /// Gets one attribute of a specific type from a member.
/// </summary> /// </summary>
/// <typeparam name="T">The attribute type.</typeparam> /// <typeparam name="T">The attribute type.</typeparam>
/// <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) {
if (member == null) return default;
return default; }
var attr = Retrieve( IEnumerable<Object> attr = this.Retrieve(
new Tuple<object, Type>(member, typeof(T)), new Tuple<Object, Type>(member, typeof(T)),
t => member.GetCustomAttributes(typeof(T), inherit)); t => member.GetCustomAttributes(typeof(T), inherit));
return ConvertToAttribute<T>(attr); return ConvertToAttribute<T>(attr);
}
/// <summary>
/// Gets one attribute of a specific type from a generic type.
/// </summary>
/// <typeparam name="TAttribute">The type of the attribute.</typeparam>
/// <typeparam name="T">The type to retrieve the attribute.</typeparam>
/// <param name="inherit">if set to <c>true</c> [inherit].</param>
/// <returns>An attribute stored for the specified type.</returns>
public TAttribute RetrieveOne<TAttribute, T>(bool inherit = false)
where TAttribute : Attribute
{
var attr = Retrieve(
new Tuple<object, Type>(typeof(T), typeof(TAttribute)),
t => typeof(T).GetCustomAttributes(typeof(TAttribute), inherit));
return ConvertToAttribute<TAttribute>(attr);
}
/// <summary>
/// Gets all properties an their attributes of a given type constrained to only attributes.
/// </summary>
/// <typeparam name="T">The type of the attribute to retrieve.</typeparam>
/// <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>
/// <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)
where T : Attribute
{
if (type == null)
throw new ArgumentNullException(nameof(type));
return PropertyTypeCache.RetrieveAllProperties(type, true)
.ToDictionary(x => x, x => Retrieve<T>(x, inherit));
}
/// <summary>
/// Gets all properties and their attributes of a given type.
/// </summary>
/// <typeparam name="T">The object type used to extract the properties from.</typeparam>
/// <param name="attributeType">Type of the attribute.</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>
public Dictionary<PropertyInfo, IEnumerable<object>> RetrieveFromType<T>(Type attributeType, bool inherit = false)
{
if (attributeType == null)
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)
where T : Attribute
{
if (attr?.Any() != true)
return default;
if (attr.Count() == 1)
return (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)
{
if (factory == null)
throw new ArgumentNullException(nameof(factory));
return _data.Value.GetOrAdd(key, k => factory.Invoke(k).Where(item => item != null));
}
} }
/// <summary>
/// Gets one attribute of a specific type from a generic type.
/// </summary>
/// <typeparam name="TAttribute">The type of the attribute.</typeparam>
/// <typeparam name="T">The type to retrieve the attribute.</typeparam>
/// <param name="inherit">if set to <c>true</c> [inherit].</param>
/// <returns>An attribute stored for the specified type.</returns>
public TAttribute RetrieveOne<TAttribute, T>(Boolean inherit = false)
where TAttribute : Attribute {
IEnumerable<Object> attr = this.Retrieve(
new Tuple<Object, Type>(typeof(T), typeof(TAttribute)),
t => typeof(T).GetCustomAttributes(typeof(TAttribute), inherit));
return ConvertToAttribute<TAttribute>(attr);
}
/// <summary>
/// Gets all properties an their attributes of a given type constrained to only attributes.
/// </summary>
/// <typeparam name="T">The type of the attribute to retrieve.</typeparam>
/// <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>
/// <returns>A dictionary of the properties and their attributes stored for the specified type.</returns>
public Dictionary<PropertyInfo, IEnumerable<Object>> Retrieve<T>(Type type, Boolean inherit = false)
where T : Attribute {
if(type == null) {
throw new ArgumentNullException(nameof(type));
}
return this.PropertyTypeCache.RetrieveAllProperties(type, true)
.ToDictionary(x => x, x => this.Retrieve<T>(x, inherit));
}
/// <summary>
/// Gets all properties and their attributes of a given type.
/// </summary>
/// <typeparam name="T">The object type used to extract the properties from.</typeparam>
/// <param name="attributeType">Type of the attribute.</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>
public Dictionary<PropertyInfo, IEnumerable<Object>> RetrieveFromType<T>(Type attributeType, Boolean inherit = false) {
if(attributeType == null) {
throw new ArgumentNullException(nameof(attributeType));
}
return this.PropertyTypeCache.RetrieveAllProperties<T>(true)
.ToDictionary(x => x, x => this.Retrieve(x, attributeType, inherit));
}
private static T ConvertToAttribute<T>(IEnumerable<Object> attr)
where T : Attribute {
if(attr?.Any() != true) {
return default;
}
if(attr.Count() == 1) {
return (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) {
if(factory == null) {
throw new ArgumentNullException(nameof(factory));
}
return this._data.Value.GetOrAdd(key, k => factory.Invoke(k).Where(item => item != null));
}
}
} }

View File

@ -1,107 +1,114 @@
namespace Unosquare.Swan.Reflection using System;
{ using System.Reflection;
using System; using Unosquare.Swan.Attributes;
using System.Reflection;
using Attributes;
namespace Unosquare.Swan.Reflection {
/// <summary>
/// Represents a Property object from a Object Reflection Property with extended values.
/// </summary>
public class ExtendedPropertyInfo {
/// <summary> /// <summary>
/// Represents a Property object from a Object Reflection Property with extended values. /// Initializes a new instance of the <see cref="ExtendedPropertyInfo"/> class.
/// </summary> /// </summary>
public class ExtendedPropertyInfo /// <param name="propertyInfo">The property information.</param>
{ public ExtendedPropertyInfo(PropertyInfo propertyInfo) {
/// <summary> if(propertyInfo == null) {
/// Initializes a new instance of the <see cref="ExtendedPropertyInfo"/> class. throw new ArgumentNullException(nameof(propertyInfo));
/// </summary> }
/// <param name="propertyInfo">The property information.</param>
public ExtendedPropertyInfo(PropertyInfo propertyInfo)
{
if (propertyInfo == null)
{
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 Runtime.AttributeCache.Retrieve<PropertyDisplayAttribute>(propertyInfo, true)) foreach(PropertyDisplayAttribute display in Runtime.AttributeCache.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; }
}
}
/// <summary>
/// Gets or sets the property.
/// </summary>
/// <value>
/// The property.
/// </value>
public string Property { get; }
/// <summary>
/// Gets or sets the type of the data.
/// </summary>
/// <value>
/// The type of the data.
/// </value>
public string DataType { get; }
/// <summary>
/// Gets or sets the value.
/// </summary>
/// <value>
/// The value.
/// </value>
public object Value { get; set; }
/// <summary>
/// Gets or sets the default value.
/// </summary>
/// <value>
/// The default value.
/// </value>
public object DefaultValue { get; }
/// <summary>
/// Gets or sets the name.
/// </summary>
/// <value>
/// The name.
/// </value>
public string Name { get; }
/// <summary>
/// Gets or sets the description.
/// </summary>
/// <value>
/// The description.
/// </value>
public string Description { get; }
/// <summary>
/// Gets or sets the name of the group.
/// </summary>
/// <value>
/// The name of the group.
/// </value>
public string GroupName { get; }
} }
/// <summary> /// <summary>
/// Represents a Property object from a Object Reflection Property with extended values. /// Gets or sets the property.
/// </summary> /// </summary>
/// <typeparam name="T">The type of the object.</typeparam> /// <value>
public class ExtendedPropertyInfo<T> : ExtendedPropertyInfo /// The property.
{ /// </value>
/// <summary> public String Property {
/// Initializes a new instance of the <see cref="ExtendedPropertyInfo{T}"/> class. get;
/// </summary>
/// <param name="property">The property.</param>
public ExtendedPropertyInfo(string property)
: base(typeof(T).GetProperty(property))
{
}
} }
/// <summary>
/// Gets or sets the type of the data.
/// </summary>
/// <value>
/// The type of the data.
/// </value>
public String DataType {
get;
}
/// <summary>
/// Gets or sets the value.
/// </summary>
/// <value>
/// The value.
/// </value>
public Object Value {
get; set;
}
/// <summary>
/// Gets or sets the default value.
/// </summary>
/// <value>
/// The default value.
/// </value>
public Object DefaultValue {
get;
}
/// <summary>
/// Gets or sets the name.
/// </summary>
/// <value>
/// The name.
/// </value>
public String Name {
get;
}
/// <summary>
/// Gets or sets the description.
/// </summary>
/// <value>
/// The description.
/// </value>
public String Description {
get;
}
/// <summary>
/// Gets or sets the name of the group.
/// </summary>
/// <value>
/// The name of the group.
/// </value>
public String GroupName {
get;
}
}
/// <summary>
/// Represents a Property object from a Object Reflection Property with extended values.
/// </summary>
/// <typeparam name="T">The type of the object.</typeparam>
public class ExtendedPropertyInfo<T> : ExtendedPropertyInfo {
/// <summary>
/// Initializes a new instance of the <see cref="ExtendedPropertyInfo{T}"/> class.
/// </summary>
/// <param name="property">The property.</param>
public ExtendedPropertyInfo(String property)
: base(typeof(T).GetProperty(property)) {
}
}
} }

View File

@ -1,290 +1,281 @@
namespace Unosquare.Swan.Reflection using System;
{ using System.Collections.Generic;
using System; using System.ComponentModel;
using System.Collections.Generic; using System.Globalization;
using System.ComponentModel; using System.Linq;
using System.Globalization; using System.Reflection;
using System.Linq;
using System.Reflection;
/// <summary> namespace Unosquare.Swan.Reflection {
/// Provides extended information about a type. /// <summary>
/// /// Provides extended information about a type.
/// 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. /// This class is mainly used to define sets of types within the Definition class
/// </summary> /// and it is not meant for other than querying the BasicTypesInfo dictionary.
public class ExtendedTypeInfo /// </summary>
public class ExtendedTypeInfo {
#region Static Declarations
private const String TryParseMethodName = nameof(Byte.TryParse);
private const String ToStringMethodName = nameof(ToString);
private static readonly Type[] NumericTypes =
{ {
#region Static Declarations typeof(Byte),
typeof(SByte),
private const string TryParseMethodName = nameof(byte.TryParse); typeof(Decimal),
private const string ToStringMethodName = nameof(ToString); typeof(Double),
typeof(Single),
private static readonly Type[] NumericTypes = typeof(Int32),
{ typeof(UInt32),
typeof(byte), typeof(Int64),
typeof(sbyte), typeof(UInt64),
typeof(decimal), typeof(Int16),
typeof(double), typeof(UInt16),
typeof(float),
typeof(int),
typeof(uint),
typeof(long),
typeof(ulong),
typeof(short),
typeof(ushort),
}; };
#endregion #endregion
#region State Management #region State Management
private readonly ParameterInfo[] _tryParseParameters; private readonly ParameterInfo[] _tryParseParameters;
private readonly int _toStringArgumentLength; private readonly Int32 _toStringArgumentLength;
#endregion #endregion
#region Constructors #region Constructors
/// <summary> /// <summary>
/// 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.GetTypeInfo().IsGenericType
IsNullableValueType = Type.GetTypeInfo().IsGenericType && this.Type.GetGenericTypeDefinition() == typeof(Nullable<>);
&& Type.GetGenericTypeDefinition() == typeof(Nullable<>);
IsValueType = t.GetTypeInfo().IsValueType; this.IsValueType = t.GetTypeInfo().IsValueType;
UnderlyingType = IsNullableValueType ? this.UnderlyingType = this.IsNullableValueType ?
new NullableConverter(Type).UnderlyingType : new NullableConverter(this.Type).UnderlyingType :
Type; this.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,
TryParseMethodInfo = UnderlyingType.GetMethod(TryParseMethodName, new[] { typeof(String), typeof(NumberStyles), typeof(IFormatProvider), this.UnderlyingType.MakeByRefType() }) ??
new[] { typeof(string), typeof(NumberStyles), typeof(IFormatProvider), UnderlyingType.MakeByRefType() }) ?? this.UnderlyingType.GetMethod(TryParseMethodName,
UnderlyingType.GetMethod(TryParseMethodName, new[] { typeof(String), this.UnderlyingType.MakeByRefType() });
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,
ToStringMethodInfo = UnderlyingType.GetMethod(ToStringMethodName,
new[] { typeof(IFormatProvider) }) ?? new[] { typeof(IFormatProvider) }) ??
UnderlyingType.GetMethod(ToStringMethodName, this.UnderlyingType.GetMethod(ToStringMethodName,
new Type[] { }); new Type[] { });
_toStringArgumentLength = ToStringMethodInfo?.GetParameters().Length ?? 0; this._toStringArgumentLength = this.ToStringMethodInfo?.GetParameters().Length ?? 0;
} } catch {
catch // ignored
{ }
// ignored }
}
}
#endregion #endregion
#region Properties #region Properties
/// <summary> /// <summary>
/// Gets the type this extended info class provides for. /// Gets the type this extended info class provides for.
/// </summary> /// </summary>
/// <value> /// <value>
/// The type. /// The type.
/// </value> /// </value>
public Type Type { get; } public Type Type {
get;
/// <summary>
/// Gets a value indicating whether the type is a nullable value type.
/// </summary>
/// <value>
/// <c>true</c> if this instance is nullable value type; otherwise, <c>false</c>.
/// </value>
public bool IsNullableValueType { get; }
/// <summary>
/// Gets a value indicating whether the type or underlying type is numeric.
/// </summary>
/// <value>
/// <c>true</c> if this instance is numeric; otherwise, <c>false</c>.
/// </value>
public bool IsNumeric { get; }
/// <summary>
/// Gets a value indicating whether the type is value type.
/// Nullable value types have this property set to False.
/// </summary>
public bool IsValueType { get; }
/// <summary>
/// When dealing with nullable value types, this property will
/// return the underlying value type of the nullable,
/// Otherwise it will return the same type as the Type property.
/// </summary>
/// <value>
/// The type of the underlying.
/// </value>
public Type UnderlyingType { get; }
/// <summary>
/// Gets the try parse method information. If the type does not contain
/// a suitable TryParse static method, it will return null.
/// </summary>
/// <value>
/// The try parse method information.
/// </value>
public MethodInfo TryParseMethodInfo { get; }
/// <summary>
/// Gets the ToString method info
/// It will prefer the overload containing the IFormatProvider argument.
/// </summary>
/// <value>
/// To string method information.
/// </value>
public MethodInfo ToStringMethodInfo { get; }
/// <summary>
/// Gets a value indicating whether the type contains a suitable TryParse method.
/// </summary>
/// <value>
/// <c>true</c> if this instance can parse natively; otherwise, <c>false</c>.
/// </value>
public bool CanParseNatively => TryParseMethodInfo != null;
#endregion
#region Methods
/// <summary>
/// Gets the default value of this type. For reference types it return null.
/// For value types it returns the default value.
/// </summary>
/// <returns>Default value of this type.</returns>
public object GetDefault() => Type.GetTypeInfo().IsValueType ? Activator.CreateInstance(Type) : null;
/// <summary>
/// Tries to parse the string into an object of the type this instance represents.
/// Returns false when no suitable TryParse methods exists for the type or when parsing fails
/// for any reason. When possible, this method uses CultureInfo.InvariantCulture and NumberStyles.Any.
/// </summary>
/// <param name="s">The s.</param>
/// <param name="result">The result.</param>
/// <returns><c>true</c> if parse was converted successfully; otherwise, <c>false</c>.</returns>
public bool TryParse(string s, out object result)
{
result = GetDefault();
try
{
if (Type == typeof(string))
{
result = Convert.ChangeType(s, Type);
return true;
}
if (IsNullableValueType && string.IsNullOrEmpty(s))
{
result = GetDefault();
return true;
}
if (CanParseNatively == false)
{
result = GetDefault();
return false;
}
// Build the arguments of the TryParse method
var dynamicArguments = new List<object> { s };
for (var pi = 1; pi < _tryParseParameters.Length - 1; pi++)
{
var argInfo = _tryParseParameters[pi];
if (argInfo.ParameterType == typeof(IFormatProvider))
dynamicArguments.Add(CultureInfo.InvariantCulture);
else if (argInfo.ParameterType == typeof(NumberStyles))
dynamicArguments.Add(NumberStyles.Any);
else
dynamicArguments.Add(null);
}
dynamicArguments.Add(null);
var parseArguments = dynamicArguments.ToArray();
if ((bool)TryParseMethodInfo.Invoke(null, parseArguments) == false)
{
result = GetDefault();
return false;
}
result = parseArguments[parseArguments.Length - 1];
return true;
}
catch
{
return false;
}
}
/// <summary>
/// Converts this instance to its string representation,
/// trying to use the CultureInfo.InvariantCulture
/// IFormat provider if the overload is available.
/// </summary>
/// <param name="instance">The instance.</param>
/// <returns>A <see cref="System.String" /> that represents the current object.</returns>
public string ToStringInvariant(object instance)
{
if (instance == null)
return string.Empty;
return _toStringArgumentLength != 1
? instance.ToString()
: ToStringMethodInfo.Invoke(instance, new object[] {CultureInfo.InvariantCulture}) as string;
}
#endregion
} }
/// <summary> /// <summary>
/// Provides extended information about a type. /// Gets a value indicating whether the type is a nullable value type.
///
/// This class is mainly used to define sets of types within the Constants class
/// and it is not meant for other than querying the BasicTypesInfo dictionary.
/// </summary> /// </summary>
/// <typeparam name="T">The type of extended type information.</typeparam> /// <value>
public class ExtendedTypeInfo<T> : ExtendedTypeInfo /// <c>true</c> if this instance is nullable value type; otherwise, <c>false</c>.
{ /// </value>
/// <summary> public Boolean IsNullableValueType {
/// Initializes a new instance of the <see cref="ExtendedTypeInfo{T}"/> class. get;
/// </summary> }
public ExtendedTypeInfo()
: base(typeof(T)) /// <summary>
{ /// Gets a value indicating whether the type or underlying type is numeric.
// placeholder /// </summary>
/// <value>
/// <c>true</c> if this instance is numeric; otherwise, <c>false</c>.
/// </value>
public Boolean IsNumeric {
get;
}
/// <summary>
/// Gets a value indicating whether the type is value type.
/// Nullable value types have this property set to False.
/// </summary>
public Boolean IsValueType {
get;
}
/// <summary>
/// When dealing with nullable value types, this property will
/// return the underlying value type of the nullable,
/// Otherwise it will return the same type as the Type property.
/// </summary>
/// <value>
/// The type of the underlying.
/// </value>
public Type UnderlyingType {
get;
}
/// <summary>
/// Gets the try parse method information. If the type does not contain
/// a suitable TryParse static method, it will return null.
/// </summary>
/// <value>
/// The try parse method information.
/// </value>
public MethodInfo TryParseMethodInfo {
get;
}
/// <summary>
/// Gets the ToString method info
/// It will prefer the overload containing the IFormatProvider argument.
/// </summary>
/// <value>
/// To string method information.
/// </value>
public MethodInfo ToStringMethodInfo {
get;
}
/// <summary>
/// Gets a value indicating whether the type contains a suitable TryParse method.
/// </summary>
/// <value>
/// <c>true</c> if this instance can parse natively; otherwise, <c>false</c>.
/// </value>
public Boolean CanParseNatively => this.TryParseMethodInfo != null;
#endregion
#region Methods
/// <summary>
/// Gets the default value of this type. For reference types it return null.
/// For value types it returns the default value.
/// </summary>
/// <returns>Default value of this type.</returns>
public Object GetDefault() => this.Type.GetTypeInfo().IsValueType ? Activator.CreateInstance(this.Type) : null;
/// <summary>
/// Tries to parse the string into an object of the type this instance represents.
/// Returns false when no suitable TryParse methods exists for the type or when parsing fails
/// for any reason. When possible, this method uses CultureInfo.InvariantCulture and NumberStyles.Any.
/// </summary>
/// <param name="s">The s.</param>
/// <param name="result">The result.</param>
/// <returns><c>true</c> if parse was converted successfully; otherwise, <c>false</c>.</returns>
public Boolean TryParse(String s, out Object result) {
result = this.GetDefault();
try {
if(this.Type == typeof(String)) {
result = Convert.ChangeType(s, this.Type);
return true;
} }
/// <summary> if(this.IsNullableValueType && String.IsNullOrEmpty(s)) {
/// Converts this instance to its string representation, result = this.GetDefault();
/// trying to use the CultureInfo.InvariantCulture return true;
/// IFormat provider if the overload is available. }
/// </summary>
/// <param name="instance">The instance.</param> if(this.CanParseNatively == false) {
/// <returns>A <see cref="System.String" /> that represents the current object.</returns> result = this.GetDefault();
public string ToStringInvariant(T instance) => base.ToStringInvariant(instance); return false;
}
// Build the arguments of the TryParse method
List<Object> dynamicArguments = new List<Object> { s };
for(Int32 pi = 1; pi < this._tryParseParameters.Length - 1; pi++) {
ParameterInfo argInfo = this._tryParseParameters[pi];
if(argInfo.ParameterType == typeof(IFormatProvider)) {
dynamicArguments.Add(CultureInfo.InvariantCulture);
} else if(argInfo.ParameterType == typeof(NumberStyles)) {
dynamicArguments.Add(NumberStyles.Any);
} else {
dynamicArguments.Add(null);
}
}
dynamicArguments.Add(null);
Object[] parseArguments = dynamicArguments.ToArray();
if((Boolean)this.TryParseMethodInfo.Invoke(null, parseArguments) == false) {
result = this.GetDefault();
return false;
}
result = parseArguments[parseArguments.Length - 1];
return true;
} catch {
return false;
}
} }
/// <summary>
/// Converts this instance to its string representation,
/// trying to use the CultureInfo.InvariantCulture
/// IFormat provider if the overload is available.
/// </summary>
/// <param name="instance">The instance.</param>
/// <returns>A <see cref="System.String" /> that represents the current object.</returns>
public String ToStringInvariant(Object instance) => instance == null
? String.Empty
: this._toStringArgumentLength != 1
? instance.ToString()
: this.ToStringMethodInfo.Invoke(instance, new Object[] { CultureInfo.InvariantCulture }) as String;
#endregion
}
/// <summary>
/// Provides extended information about a type.
///
/// This class is mainly used to define sets of types within the Constants class
/// and it is not meant for other than querying the BasicTypesInfo dictionary.
/// </summary>
/// <typeparam name="T">The type of extended type information.</typeparam>
public class ExtendedTypeInfo<T> : ExtendedTypeInfo {
/// <summary>
/// Initializes a new instance of the <see cref="ExtendedTypeInfo{T}"/> class.
/// </summary>
public ExtendedTypeInfo()
: base(typeof(T)) {
// placeholder
}
/// <summary>
/// Converts this instance to its string representation,
/// trying to use the CultureInfo.InvariantCulture
/// IFormat provider if the overload is available.
/// </summary>
/// <param name="instance">The instance.</param>
/// <returns>A <see cref="System.String" /> that represents the current object.</returns>
public String ToStringInvariant(T instance) => base.ToStringInvariant(instance);
}
} }

View File

@ -1,118 +1,119 @@
namespace Unosquare.Swan.Reflection using System;
{ using System.Reflection;
using System; using System.Collections.Concurrent;
using System.Reflection;
using System.Collections.Concurrent; namespace Unosquare.Swan.Reflection {
/// <summary>
/// Represents a Method Info Cache.
/// </summary>
public class MethodInfoCache : ConcurrentDictionary<String, MethodInfo> {
/// <summary>
/// Retrieves the properties stored for the specified type.
/// If the properties are not available, it calls the factory method to retrieve them
/// and returns them as an array of PropertyInfo.
/// </summary>
/// <typeparam name="T">The type of type.</typeparam>
/// <param name="name">The name.</param>
/// <param name="alias">The alias.</param>
/// <param name="types">The types.</param>
/// <returns>
/// The cached MethodInfo.
/// </returns>
/// <exception cref="ArgumentNullException">name
/// or
/// factory.</exception>
/// <exception cref="System.ArgumentNullException">type.</exception>
public MethodInfo Retrieve<T>(String name, String alias, params Type[] types)
=> this.Retrieve(typeof(T), name, alias, types);
/// <summary> /// <summary>
/// Represents a Method Info Cache. /// Retrieves the specified name.
/// </summary> /// </summary>
public class MethodInfoCache : ConcurrentDictionary<string, MethodInfo> /// <typeparam name="T">The type of type.</typeparam>
{ /// <param name="name">The name.</param>
/// <summary> /// <param name="types">The types.</param>
/// Retrieves the properties stored for the specified type. /// <returns>
/// If the properties are not available, it calls the factory method to retrieve them /// The cached MethodInfo.
/// and returns them as an array of PropertyInfo. /// </returns>
/// </summary> public MethodInfo Retrieve<T>(String name, params Type[] types)
/// <typeparam name="T">The type of type.</typeparam> => this.Retrieve(typeof(T), name, name, types);
/// <param name="name">The name.</param>
/// <param name="alias">The alias.</param>
/// <param name="types">The types.</param>
/// <returns>
/// The cached MethodInfo.
/// </returns>
/// <exception cref="ArgumentNullException">name
/// or
/// factory.</exception>
/// <exception cref="System.ArgumentNullException">type.</exception>
public MethodInfo Retrieve<T>(string name, string alias, params Type[] types)
=> Retrieve(typeof(T), name, alias, types);
/// <summary> /// <summary>
/// Retrieves the specified name. /// Retrieves the specified type.
/// </summary> /// </summary>
/// <typeparam name="T">The type of type.</typeparam> /// <param name="type">The type.</param>
/// <param name="name">The name.</param> /// <param name="name">The name.</param>
/// <param name="types">The types.</param> /// <param name="types">The types.</param>
/// <returns> /// <returns>
/// The cached MethodInfo. /// An array of the properties stored for the specified type.
/// </returns> /// </returns>
public MethodInfo Retrieve<T>(string name, params Type[] types) public MethodInfo Retrieve(Type type, String name, params Type[] types)
=> Retrieve(typeof(T), name, name, types); => this.Retrieve(type, name, name, types);
/// <summary> /// <summary>
/// Retrieves the specified type. /// Retrieves the specified type.
/// </summary> /// </summary>
/// <param name="type">The type.</param> /// <param name="type">The type.</param>
/// <param name="name">The name.</param> /// <param name="name">The name.</param>
/// <param name="types">The types.</param> /// <param name="alias">The alias.</param>
/// <returns> /// <param name="types">The types.</param>
/// An array of the properties stored for the specified type. /// <returns>
/// </returns> /// The cached MethodInfo.
public MethodInfo Retrieve(Type type, string name, params Type[] types) /// </returns>
=> Retrieve(type, name, name, types); public MethodInfo Retrieve(Type type, String name, String alias, params Type[] types) {
if(type == null) {
throw new ArgumentNullException(nameof(type));
}
/// <summary> if(alias == null) {
/// Retrieves the specified type. throw new ArgumentNullException(nameof(alias));
/// </summary> }
/// <param name="type">The type.</param>
/// <param name="name">The name.</param>
/// <param name="alias">The alias.</param>
/// <param name="types">The types.</param>
/// <returns>
/// The cached MethodInfo.
/// </returns>
public MethodInfo Retrieve(Type type, string name, string alias, params Type[] types)
{
if (type == null)
throw new ArgumentNullException(nameof(type));
if (alias == null) if(name == null) {
throw new ArgumentNullException(nameof(alias)); throw new ArgumentNullException(nameof(name));
}
if (name == null) return this.GetOrAdd(
throw new ArgumentNullException(nameof(name));
return GetOrAdd(
alias, alias,
x => type.GetMethod(name, types ?? new Type[0])); x => type.GetMethod(name, types ?? new Type[0]));
} }
/// <summary> /// <summary>
/// Retrieves the specified name. /// Retrieves the specified name.
/// </summary> /// </summary>
/// <typeparam name="T">The type of type.</typeparam> /// <typeparam name="T">The type of type.</typeparam>
/// <param name="name">The name.</param> /// <param name="name">The name.</param>
/// <returns> /// <returns>
/// The cached MethodInfo. /// The cached MethodInfo.
/// </returns> /// </returns>
public MethodInfo Retrieve<T>(string name) public MethodInfo Retrieve<T>(String name)
=> Retrieve(typeof(T), name); => this.Retrieve(typeof(T), name);
/// <summary> /// <summary>
/// Retrieves the specified type. /// Retrieves the specified type.
/// </summary> /// </summary>
/// <param name="type">The type.</param> /// <param name="type">The type.</param>
/// <param name="name">The name.</param> /// <param name="name">The name.</param>
/// <returns> /// <returns>
/// The cached MethodInfo. /// The cached MethodInfo.
/// </returns> /// </returns>
/// <exception cref="ArgumentNullException"> /// <exception cref="ArgumentNullException">
/// type /// type
/// 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, name,
type.GetMethod); type.GetMethod);
}
} }
}
} }

View File

@ -1,135 +1,131 @@
namespace Unosquare.Swan.Reflection using System.Linq;
{ using System;
using System.Linq; using System.Collections.Generic;
using System; using System.Reflection;
using System.Collections.Generic; using Unosquare.Swan.Components;
using System.Reflection;
using Components; namespace Unosquare.Swan.Reflection {
/// <summary>
/// A thread-safe cache of members belonging to a given type.
///
/// The Retrieve method is the most useful one in this class as it
/// calls the retrieval process if the type is not contained
/// in the cache.
/// </summary>
/// <typeparam name="T">The type of Member to be cached.</typeparam>
public abstract class TypeCache<T> : CollectionCacheRepository<T>
where T : MemberInfo {
/// <summary>
/// Determines whether the cache contains the specified type.
/// </summary>
/// <typeparam name="TOut">The type of the out.</typeparam>
/// <returns>
/// <c>true</c> if [contains]; otherwise, <c>false</c>.
/// </returns>
public Boolean Contains<TOut>() => this.ContainsKey(typeof(TOut));
/// <summary> /// <summary>
/// A thread-safe cache of members belonging to a given type. /// Retrieves the properties stored for the specified type.
/// /// If the properties are not available, it calls the factory method to retrieve them
/// The Retrieve method is the most useful one in this class as it /// and returns them as an array of PropertyInfo.
/// calls the retrieval process if the type is not contained
/// in the cache.
/// </summary> /// </summary>
/// <typeparam name="T">The type of Member to be cached.</typeparam> /// <typeparam name="TOut">The type of the out.</typeparam>
public abstract class TypeCache<T> : CollectionCacheRepository<T> /// <param name="factory">The factory.</param>
where T : MemberInfo /// <returns>An array of the properties stored for the specified type.</returns>
{ public IEnumerable<T> Retrieve<TOut>(Func<Type, IEnumerable<T>> factory)
/// <summary> => this.Retrieve(typeof(TOut), factory);
/// Determines whether the cache contains the specified type. }
/// </summary>
/// <typeparam name="TOut">The type of the out.</typeparam>
/// <returns>
/// <c>true</c> if [contains]; otherwise, <c>false</c>.
/// </returns>
public bool Contains<TOut>() => ContainsKey(typeof(TOut));
/// <summary> /// <summary>
/// Retrieves the properties stored for the specified type. /// A thread-safe cache of properties belonging to a given type.
/// If the properties are not available, it calls the factory method to retrieve them ///
/// and returns them as an array of PropertyInfo. /// The Retrieve method is the most useful one in this class as it
/// </summary> /// calls the retrieval process if the type is not contained
/// <typeparam name="TOut">The type of the out.</typeparam> /// in the cache.
/// <param name="factory">The factory.</param> /// </summary>
/// <returns>An array of the properties stored for the specified type.</returns> public class PropertyTypeCache : TypeCache<PropertyInfo> {
public IEnumerable<T> Retrieve<TOut>(Func<Type, IEnumerable<T>> factory) /// <summary>
=> Retrieve(typeof(TOut), factory); /// Retrieves all properties.
} /// </summary>
/// <typeparam name="T">The type to inspect.</typeparam>
/// <param name="onlyPublic">if set to <c>true</c> [only public].</param>
/// <returns>
/// A collection with all the properties in the given type.
/// </returns>
public IEnumerable<PropertyInfo> RetrieveAllProperties<T>(Boolean onlyPublic = false)
=> this.Retrieve<T>(onlyPublic ? GetAllPublicPropertiesFunc() : GetAllPropertiesFunc());
/// <summary> /// <summary>
/// A thread-safe cache of properties belonging to a given type. /// Retrieves all properties.
///
/// The Retrieve method is the most useful one in this class as it
/// calls the retrieval process if the type is not contained
/// in the cache.
/// </summary> /// </summary>
public class PropertyTypeCache : TypeCache<PropertyInfo> /// <param name="type">The type.</param>
{ /// <param name="onlyPublic">if set to <c>true</c> [only public].</param>
/// <summary> /// <returns>
/// Retrieves all properties. /// A collection with all the properties in the given type.
/// </summary> /// </returns>
/// <typeparam name="T">The type to inspect.</typeparam> public IEnumerable<PropertyInfo> RetrieveAllProperties(Type type, Boolean onlyPublic = false)
/// <param name="onlyPublic">if set to <c>true</c> [only public].</param> => this.Retrieve(type, onlyPublic ? GetAllPublicPropertiesFunc() : GetAllPropertiesFunc());
/// <returns>
/// A collection with all the properties in the given type.
/// </returns>
public IEnumerable<PropertyInfo> RetrieveAllProperties<T>(bool onlyPublic = false)
=> Retrieve<T>(onlyPublic ? GetAllPublicPropertiesFunc() : GetAllPropertiesFunc());
/// <summary>
/// Retrieves all properties.
/// </summary>
/// <param name="type">The type.</param>
/// <param name="onlyPublic">if set to <c>true</c> [only public].</param>
/// <returns>
/// A collection with all the properties in the given type.
/// </returns>
public IEnumerable<PropertyInfo> RetrieveAllProperties(Type type, bool onlyPublic = false)
=> Retrieve(type, onlyPublic ? GetAllPublicPropertiesFunc() : GetAllPropertiesFunc());
/// <summary>
/// Retrieves the filtered properties.
/// </summary>
/// <param name="type">The type.</param>
/// <param name="onlyPublic">if set to <c>true</c> [only public].</param>
/// <param name="filter">The filter.</param>
/// <returns>
/// A collection with all the properties in the given type.
/// </returns>
public IEnumerable<PropertyInfo> RetrieveFilteredProperties(
Type type,
bool onlyPublic,
Func<PropertyInfo, bool> filter)
=> Retrieve(type,
onlyPublic ? GetAllPublicPropertiesFunc(filter) : GetAllPropertiesFunc(filter));
private static Func<Type, IEnumerable<PropertyInfo>> GetAllPropertiesFunc(
Func<PropertyInfo, bool> filter = null)
=> GetPropertiesFunc(
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance,
filter);
private static Func<Type, IEnumerable<PropertyInfo>> GetAllPublicPropertiesFunc(
Func<PropertyInfo, bool> filter = null)
=> GetPropertiesFunc(BindingFlags.Public | BindingFlags.Instance, filter);
private static Func<Type, IEnumerable<PropertyInfo>> GetPropertiesFunc(BindingFlags flags,
Func<PropertyInfo, bool> filter = null)
=> t => t.GetProperties(flags)
.Where(filter ?? (p => p.CanRead || p.CanWrite));
}
/// <summary> /// <summary>
/// A thread-safe cache of fields belonging to a given type /// Retrieves the filtered properties.
/// The Retrieve method is the most useful one in this class as it
/// calls the retrieval process if the type is not contained
/// in the cache.
/// </summary> /// </summary>
public class FieldTypeCache : TypeCache<FieldInfo> /// <param name="type">The type.</param>
{ /// <param name="onlyPublic">if set to <c>true</c> [only public].</param>
/// <summary> /// <param name="filter">The filter.</param>
/// Retrieves all fields. /// <returns>
/// </summary> /// A collection with all the properties in the given type.
/// <typeparam name="T">The type to inspect.</typeparam> /// </returns>
/// <returns> public IEnumerable<PropertyInfo> RetrieveFilteredProperties(
/// A collection with all the fields in the given type. Type type,
/// </returns> Boolean onlyPublic,
public IEnumerable<FieldInfo> RetrieveAllFields<T>() Func<PropertyInfo, Boolean> filter)
=> Retrieve<T>(GetAllFieldsFunc()); => this.Retrieve(type,
onlyPublic ? GetAllPublicPropertiesFunc(filter) : GetAllPropertiesFunc(filter));
/// <summary> private static Func<Type, IEnumerable<PropertyInfo>> GetAllPropertiesFunc(
/// Retrieves all fields. Func<PropertyInfo, Boolean> filter = null)
/// </summary> => GetPropertiesFunc(
/// <param name="type">The type.</param> BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance,
/// <returns> filter);
/// A collection with all the fields in the given type.
/// </returns>
public IEnumerable<FieldInfo> RetrieveAllFields(Type type)
=> Retrieve(type, GetAllFieldsFunc());
private static Func<Type, IEnumerable<FieldInfo>> GetAllFieldsFunc() private static Func<Type, IEnumerable<PropertyInfo>> GetAllPublicPropertiesFunc(
=> t => t.GetFields(BindingFlags.Public | BindingFlags.Instance); Func<PropertyInfo, Boolean> filter = null)
} => GetPropertiesFunc(BindingFlags.Public | BindingFlags.Instance, filter);
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));
}
/// <summary>
/// A thread-safe cache of fields belonging to a given type
/// The Retrieve method is the most useful one in this class as it
/// calls the retrieval process if the type is not contained
/// in the cache.
/// </summary>
public class FieldTypeCache : TypeCache<FieldInfo> {
/// <summary>
/// Retrieves all fields.
/// </summary>
/// <typeparam name="T">The type to inspect.</typeparam>
/// <returns>
/// A collection with all the fields in the given type.
/// </returns>
public IEnumerable<FieldInfo> RetrieveAllFields<T>()
=> this.Retrieve<T>(GetAllFieldsFunc());
/// <summary>
/// Retrieves all fields.
/// </summary>
/// <param name="type">The type.</param>
/// <returns>
/// A collection with all the fields in the given type.
/// </returns>
public IEnumerable<FieldInfo> RetrieveAllFields(Type type)
=> this.Retrieve(type, GetAllFieldsFunc());
private static Func<Type, IEnumerable<FieldInfo>> GetAllFieldsFunc()
=> t => t.GetFields(BindingFlags.Public | BindingFlags.Instance);
}
} }

View File

@ -1,35 +1,36 @@
namespace Unosquare.Swan using Unosquare.Swan.Components;
{ using System;
using Components; using System.IO;
using System; using System.Threading;
using System.IO; using Unosquare.Swan.Reflection;
using System.Threading;
using Reflection;
#if !NETSTANDARD1_3 #if !NETSTANDARD1_3
using System.Reflection; using System.Reflection;
#endif #endif
/// <summary> namespace Unosquare.Swan {
/// Provides utility methods to retrieve information about the current application.
/// </summary>
/// <summary>
/// Provides utility methods to retrieve information about the current application.
/// </summary>
#if !NETSTANDARD1_3 #if !NETSTANDARD1_3
public class Runtime : MarshalByRefObject public class Runtime : MarshalByRefObject
#else #else
public static class Runtime public static class Runtime
#endif #endif
{ {
private static readonly Lazy<PropertyTypeCache> _propertyTypeCache = new Lazy<PropertyTypeCache>(() => new PropertyTypeCache()); private static readonly Lazy<PropertyTypeCache> _propertyTypeCache = new Lazy<PropertyTypeCache>(() => new PropertyTypeCache());
private static readonly Lazy<AttributeCache> _attributeCache = new Lazy<AttributeCache>(() => new AttributeCache()); private static readonly Lazy<AttributeCache> _attributeCache = new Lazy<AttributeCache>(() => new AttributeCache());
private static readonly Lazy<ObjectValidator> _objectValidator = new Lazy<ObjectValidator>(() => new ObjectValidator()); private static readonly Lazy<ObjectValidator> _objectValidator = new Lazy<ObjectValidator>(() => new ObjectValidator());
private static readonly Lazy<FieldTypeCache> _fieldTypeCache = new Lazy<FieldTypeCache>(() => new FieldTypeCache()); private static readonly Lazy<FieldTypeCache> _fieldTypeCache = new Lazy<FieldTypeCache>(() => new FieldTypeCache());
private static readonly Lazy<MethodInfoCache> _methodInfoCache = new Lazy<MethodInfoCache>(() => new MethodInfoCache()); private static readonly Lazy<MethodInfoCache> _methodInfoCache = new Lazy<MethodInfoCache>(() => new MethodInfoCache());
#if NET452 #if NET452
private static readonly Lazy<Assembly> EntryAssemblyLazy = new Lazy<Assembly>(() => Assembly.GetEntryAssembly() ?? Assembly.GetExecutingAssembly()); private static readonly Lazy<Assembly> EntryAssemblyLazy = new Lazy<Assembly>(() => Assembly.GetEntryAssembly() ?? Assembly.GetExecutingAssembly());
#endif #endif
#if NETSTANDARD2_0 #if NETSTANDARD2_0
@ -37,330 +38,305 @@
#endif #endif
#if NET452 #if NET452
private static readonly Lazy<System.Diagnostics.Process> ProcessLazy = new Lazy<System.Diagnostics.Process>(System.Diagnostics.Process.GetCurrentProcess); private static readonly Lazy<System.Diagnostics.Process> ProcessLazy = new Lazy<System.Diagnostics.Process>(System.Diagnostics.Process.GetCurrentProcess);
#endif #endif
#if !NETSTANDARD1_3 #if !NETSTANDARD1_3
private static readonly Lazy<string> CompanyNameLazy = new Lazy<string>(() => private static readonly Lazy<String> CompanyNameLazy = new Lazy<String>(() => {
{ AssemblyCompanyAttribute attribute =
var attribute = EntryAssembly.GetCustomAttribute(typeof(AssemblyCompanyAttribute)) as AssemblyCompanyAttribute;
EntryAssembly.GetCustomAttribute(typeof(AssemblyCompanyAttribute)) as AssemblyCompanyAttribute; return attribute?.Company ?? String.Empty;
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 =
var attribute = EntryAssembly.GetCustomAttribute(typeof(AssemblyProductAttribute)) as AssemblyProductAttribute;
EntryAssembly.GetCustomAttribute(typeof(AssemblyProductAttribute)) as AssemblyProductAttribute; return attribute?.Product ?? String.Empty;
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 =
var attribute = EntryAssembly.GetCustomAttribute(typeof(AssemblyTrademarkAttribute)) as AssemblyTrademarkAttribute;
EntryAssembly.GetCustomAttribute(typeof(AssemblyTrademarkAttribute)) as AssemblyTrademarkAttribute; return attribute?.Trademark ?? String.Empty;
return attribute?.Trademark ?? string.Empty; });
});
#endif #endif
private static readonly Lazy<ArgumentParser> _argumentParser = private static readonly Lazy<ArgumentParser> _argumentParser =
new Lazy<ArgumentParser>(() => new ArgumentParser()); new Lazy<ArgumentParser>(() => new ArgumentParser());
private static readonly Lazy<ObjectMapper> _objectMapper = new Lazy<ObjectMapper>(() => new ObjectMapper()); private static readonly Lazy<ObjectMapper> _objectMapper = new Lazy<ObjectMapper>(() => new ObjectMapper());
#if !NETSTANDARD1_3 #if !NETSTANDARD1_3
private static readonly string ApplicationMutexName = "Global\\{{" + EntryAssembly.FullName + "}}"; private static readonly String ApplicationMutexName = "Global\\{{" + EntryAssembly.FullName + "}}";
#else #else
private const string ApplicationMutexName = "Global\\{{SWANINSTANCE}}"; private const string ApplicationMutexName = "Global\\{{SWANINSTANCE}}";
#endif #endif
private static readonly object SyncLock = new object(); private static readonly Object SyncLock = new Object();
private static OperatingSystem? _oS; private static OperatingSystem? _oS;
#region Properties #region Properties
/// <summary> /// <summary>
/// Gets the current Operating System. /// Gets the current Operating System.
/// </summary> /// </summary>
/// <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(@"\")
var windowsDirectory = Environment.GetEnvironmentVariable("windir"); && Directory.Exists(windowsDirectory)
if (string.IsNullOrEmpty(windowsDirectory) == false ? (OperatingSystem?)OperatingSystem.Windows
&& windowsDirectory.Contains(@"\") : (OperatingSystem?)(File.Exists(@"/proc/sys/kernel/ostype") ? OperatingSystem.Unix : OperatingSystem.Osx);
&& 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;
}
}
#if NET452 #if NET452
/// <summary> /// <summary>
/// Gets the process associated with the current application. /// Gets the process associated with the current application.
/// </summary> /// </summary>
/// <value> /// <value>
/// The process. /// The process.
/// </value> /// </value>
public static System.Diagnostics.Process Process => ProcessLazy.Value; public static System.Diagnostics.Process Process => ProcessLazy.Value;
#endif #endif
/// <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 to open existing mutex.
try _ = Mutex.OpenExisting(ApplicationMutexName);
{ } catch {
// Try to open existing mutex. try {
Mutex.OpenExisting(ApplicationMutexName); // If exception occurred, there is no such mutex.
} Mutex appMutex = new Mutex(true, ApplicationMutexName);
catch $"Application Mutex created {appMutex} named '{ApplicationMutexName}'".Debug(
{ typeof(Runtime));
try
{
// If exception occurred, there is no such mutex.
var appMutex = new Mutex(true, ApplicationMutexName);
$"Application Mutex created {appMutex} named '{ApplicationMutexName}'".Debug(
typeof(Runtime));
// 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
}
}
// More than one instance.
return false;
}
} }
}
// More than one instance.
return false;
} }
}
}
/// <summary> /// <summary>
/// Gets a value indicating whether this application instance is using the MONO runtime. /// Gets a value indicating whether this application instance is using the MONO runtime.
/// </summary> /// </summary>
/// <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 property type cache. /// Gets the property type cache.
/// </summary> /// </summary>
/// <value> /// <value>
/// The property type cache. /// The property type cache.
/// </value> /// </value>
public static PropertyTypeCache PropertyTypeCache => _propertyTypeCache.Value; public static PropertyTypeCache PropertyTypeCache => _propertyTypeCache.Value;
/// <summary> /// <summary>
/// Gets the attribute cache. /// Gets the attribute cache.
/// </summary> /// </summary>
/// <value> /// <value>
/// The attribute cache. /// The attribute cache.
/// </value> /// </value>
public static AttributeCache AttributeCache => _attributeCache.Value; public static AttributeCache AttributeCache => _attributeCache.Value;
/// <summary> /// <summary>
/// Gets the object validator. /// Gets the object validator.
/// </summary> /// </summary>
/// <value> /// <value>
/// The object validator. /// The object validator.
/// </value> /// </value>
public static ObjectValidator ObjectValidator => _objectValidator.Value; public static ObjectValidator ObjectValidator => _objectValidator.Value;
/// <summary> /// <summary>
/// Gets the field type cache. /// Gets the field type cache.
/// </summary> /// </summary>
/// <value> /// <value>
/// The field type cache. /// The field type cache.
/// </value> /// </value>
public static FieldTypeCache FieldTypeCache => _fieldTypeCache.Value; public static FieldTypeCache FieldTypeCache => _fieldTypeCache.Value;
/// <summary> /// <summary>
/// Gets the method information cache. /// Gets the method information cache.
/// </summary> /// </summary>
/// <value> /// <value>
/// The method information cache. /// The method information cache.
/// </value> /// </value>
public static MethodInfoCache MethodInfoCache => _methodInfoCache.Value; public static MethodInfoCache MethodInfoCache => _methodInfoCache.Value;
#if !NETSTANDARD1_3 #if !NETSTANDARD1_3
/// <summary> /// <summary>
/// Gets the assembly that started the application. /// Gets the assembly that started the application.
/// </summary> /// </summary>
/// <value> /// <value>
/// The entry assembly. /// The entry assembly.
/// </value> /// </value>
public static Assembly EntryAssembly => EntryAssemblyLazy.Value; public static Assembly EntryAssembly => EntryAssemblyLazy.Value;
/// <summary> /// <summary>
/// Gets the name of the entry assembly. /// Gets the name of the entry assembly.
/// </summary> /// </summary>
/// <value> /// <value>
/// The name of the entry assembly. /// The name of the entry assembly.
/// </value> /// </value>
public static AssemblyName EntryAssemblyName => EntryAssemblyLazy.Value.GetName(); public static AssemblyName EntryAssemblyName => EntryAssemblyLazy.Value.GetName();
/// <summary> /// <summary>
/// Gets the entry assembly version. /// Gets the entry assembly version.
/// </summary> /// </summary>
public static Version EntryAssemblyVersion => EntryAssemblyName.Version; public static Version EntryAssemblyVersion => EntryAssemblyName.Version;
/// <summary> /// <summary>
/// Gets the full path to the folder containing the assembly that started the application. /// Gets the full path to the folder containing the assembly that started the application.
/// </summary> /// </summary>
/// <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); return Path.GetDirectoryName(path);
var path = Uri.UnescapeDataString(uri.Path); }
return Path.GetDirectoryName(path); }
}
}
/// <summary> /// <summary>
/// Gets the name of the company. /// Gets the name of the company.
/// </summary> /// </summary>
/// <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.
/// </summary> /// </summary>
/// <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.
/// </summary> /// </summary>
/// <value> /// <value>
/// The product trademark. /// The product trademark.
/// </value> /// </value>
public static string ProductTrademark => ProductTrademarkLazy.Value; public static String ProductTrademark => ProductTrademarkLazy.Value;
#endif #endif
/// <summary> /// <summary>
/// Gets a local storage path with a version. /// Gets a local storage path with a version.
/// </summary> /// </summary>
/// <value> /// <value>
/// The local storage path. /// The local storage path.
/// </value> /// </value>
public static string LocalStoragePath public static String LocalStoragePath {
{ get {
get
{
#if !NETSTANDARD1_3 #if !NETSTANDARD1_3
var localAppDataPath = String localAppDataPath =
#if NET452 #if NET452
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
EntryAssemblyName.Name); EntryAssemblyName.Name);
#else #else
Path.GetDirectoryName(EntryAssembly.Location); Path.GetDirectoryName(EntryAssembly.Location);
#endif #endif
var returnPath = Path.Combine(localAppDataPath, EntryAssemblyVersion.ToString()); String returnPath = Path.Combine(localAppDataPath, EntryAssemblyVersion.ToString());
#else #else
var returnPath = Directory.GetCurrentDirectory(); // Use current path... var returnPath = Directory.GetCurrentDirectory(); // Use current path...
#endif #endif
if (Directory.Exists(returnPath) == false) if(Directory.Exists(returnPath) == false) {
{ Directory.CreateDirectory(returnPath);
Directory.CreateDirectory(returnPath);
}
return returnPath;
}
} }
/// <summary> return returnPath;
/// Gets the singleton instance created with basic defaults. }
/// </summary> }
/// <value>
/// The argument parser.
/// </value>
public static ArgumentParser ArgumentParser => _argumentParser.Value;
/// <summary> /// <summary>
/// Gets the object mapper instance created with basic defaults. /// Gets the singleton instance created with basic defaults.
/// </summary> /// </summary>
/// <value> /// <value>
/// The object mapper. /// The argument parser.
/// </value> /// </value>
public static ObjectMapper ObjectMapper => _objectMapper.Value; public static ArgumentParser ArgumentParser => _argumentParser.Value;
#endregion /// <summary>
/// Gets the object mapper instance created with basic defaults.
/// </summary>
/// <value>
/// The object mapper.
/// </value>
public static ObjectMapper ObjectMapper => _objectMapper.Value;
#region Methods #endregion
#region Methods
#if !NETSTANDARD1_3 #if !NETSTANDARD1_3
/// <summary> /// <summary>
/// Writes a standard banner to the standard output /// Writes a standard banner to the standard output
/// 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) {
{ $"{CompanyName} {ProductName} [Version {EntryAssemblyVersion}]".WriteLine(color);
$"{CompanyName} {ProductName} [Version {EntryAssemblyVersion}]".WriteLine(color); $"{ProductTrademark}".WriteLine(color);
$"{ProductTrademark}".WriteLine(color); }
}
/// <summary> /// <summary>
/// Gets all the loaded assemblies in the current application domain. /// Gets all the loaded assemblies in the current application domain.
/// </summary> /// </summary>
/// <returns>An array of assemblies.</returns> /// <returns>An array of assemblies.</returns>
public static Assembly[] GetAssemblies() => AppDomain.CurrentDomain.GetAssemblies(); public static Assembly[] GetAssemblies() => AppDomain.CurrentDomain.GetAssemblies();
/// <summary> /// <summary>
/// Build a full path pointing to the current user's desktop with the given filename. /// Build a full path pointing to the current user's desktop with the given filename.
/// </summary> /// </summary>
/// <param name="filename">The filename.</param> /// <param name="filename">The filename.</param>
/// <returns> /// <returns>
/// 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);
} }
#endif #endif
#endregion #endregion
} }
} }

View File

@ -1,89 +1,86 @@
namespace Unosquare.Swan using System;
{
using System; namespace Unosquare.Swan {
/// <summary>
/// Defines a set of bitwise standard terminal writers.
/// </summary>
[Flags]
public enum TerminalWriters {
/// <summary>
/// Prevents output
/// </summary>
None = 0,
/// <summary> /// <summary>
/// Defines a set of bitwise standard terminal writers. /// Writes to the Console.Out
/// </summary> /// </summary>
[Flags] StandardOutput = 1,
public enum TerminalWriters
{
/// <summary>
/// Prevents output
/// </summary>
None = 0,
/// <summary>
/// Writes to the Console.Out
/// </summary>
StandardOutput = 1,
/// <summary>
/// Writes to the Console.Error
/// </summary>
StandardError = 2,
/// <summary>
/// Writes to the System.Diagnostics.Debug
/// </summary>
Diagnostics = 4,
/// <summary>
/// Writes to all possible terminal writers
/// </summary>
All = StandardOutput | Diagnostics | StandardError,
/// <summary>
/// The error and debug writers
/// </summary>
ErrorAndDebug = StandardError | Diagnostics,
/// <summary>
/// The output and debug writers
/// </summary>
OutputAndDebug = StandardOutput | Diagnostics,
}
/// <summary> /// <summary>
/// Defines the bitwise flags to determine /// Writes to the Console.Error
/// which types of messages get printed on the current console.
/// </summary> /// </summary>
[Flags] StandardError = 2,
public enum LogMessageType
{
/// <summary>
/// The none message type
/// </summary>
None = 0,
/// <summary> /// <summary>
/// The information message type /// Writes to the System.Diagnostics.Debug
/// </summary> /// </summary>
Info = 1, Diagnostics = 4,
/// <summary> /// <summary>
/// The debug message type /// Writes to all possible terminal writers
/// </summary> /// </summary>
Debug = 2, All = StandardOutput | Diagnostics | StandardError,
/// <summary> /// <summary>
/// The trace message type /// The error and debug writers
/// </summary> /// </summary>
Trace = 4, ErrorAndDebug = StandardError | Diagnostics,
/// <summary> /// <summary>
/// The error message type /// The output and debug writers
/// </summary> /// </summary>
Error = 8, OutputAndDebug = StandardOutput | Diagnostics,
}
/// <summary> /// <summary>
/// The warning message type /// Defines the bitwise flags to determine
/// </summary> /// which types of messages get printed on the current console.
Warning = 16, /// </summary>
[Flags]
public enum LogMessageType {
/// <summary>
/// The none message type
/// </summary>
None = 0,
/// <summary> /// <summary>
/// The fatal message type /// The information message type
/// </summary> /// </summary>
Fatal = 32, Info = 1,
}
/// <summary>
/// The debug message type
/// </summary>
Debug = 2,
/// <summary>
/// The trace message type
/// </summary>
Trace = 4,
/// <summary>
/// The error message type
/// </summary>
Error = 8,
/// <summary>
/// The warning message type
/// </summary>
Warning = 16,
/// <summary>
/// The fatal message type
/// </summary>
Fatal = 32,
}
} }

View File

@ -1,47 +1,44 @@
namespace Unosquare.Swan using System;
{
using System;
namespace Unosquare.Swan {
/// <summary>
/// A console terminal helper to create nicer output and receive input from the user
/// This class is thread-safe :).
/// </summary>
public static partial class Terminal {
/// <summary> /// <summary>
/// A console terminal helper to create nicer output and receive input from the user /// Represents a Table to print in console.
/// This class is thread-safe :).
/// </summary> /// </summary>
public static partial class Terminal private static class Table {
{ /// <summary>
/// <summary> /// Gets or sets the color of the border.
/// Represents a Table to print in console. /// </summary>
/// </summary> /// <value>
private static class Table /// The color of the border.
{ /// </value>
/// <summary> private static ConsoleColor BorderColor { get; } = ConsoleColor.DarkGreen;
/// Gets or sets the color of the border.
/// </summary>
/// <value>
/// The color of the border.
/// </value>
private static ConsoleColor BorderColor { get; } = ConsoleColor.DarkGreen;
public static void Vertical() => ((byte)179).Write(BorderColor); public static void Vertical() => ((Byte)179).Write(BorderColor);
public static void RightTee() => ((byte)180).Write(BorderColor); public static void RightTee() => ((Byte)180).Write(BorderColor);
public static void TopRight() => ((byte)191).Write(BorderColor); public static void TopRight() => ((Byte)191).Write(BorderColor);
public static void BottomLeft() => ((byte)192).Write(BorderColor); public static void BottomLeft() => ((Byte)192).Write(BorderColor);
public static void BottomTee() => ((byte)193).Write(BorderColor); public static void BottomTee() => ((Byte)193).Write(BorderColor);
public static void TopTee() => ((byte)194).Write(BorderColor); public static void TopTee() => ((Byte)194).Write(BorderColor);
public static void LeftTee() => ((byte)195).Write(BorderColor); public static void LeftTee() => ((Byte)195).Write(BorderColor);
public static void Horizontal(int length) => ((byte)196).Write(BorderColor, length); public static void Horizontal(Int32 length) => ((Byte)196).Write(BorderColor, length);
public static void Tee() => ((byte)197).Write(BorderColor); public static void Tee() => ((Byte)197).Write(BorderColor);
public static void BottomRight() => ((byte)217).Write(BorderColor); public static void BottomRight() => ((Byte)217).Write(BorderColor);
public static void TopLeft() => ((byte)218).Write(BorderColor); public static void TopLeft() => ((Byte)218).Write(BorderColor);
}
} }
}
} }

View File

@ -1,216 +1,208 @@
namespace Unosquare.Swan using System;
{ using System.Collections.Generic;
using System;
using System.Collections.Generic; namespace Unosquare.Swan {
/// <summary>
/// A console terminal helper to create nicer output and receive input from the user
/// This class is thread-safe :).
/// </summary>
public static partial class Terminal {
#region ReadKey
/// <summary> /// <summary>
/// A console terminal helper to create nicer output and receive input from the user /// Reads a key from the Terminal. This is the closest equivalent to Console.ReadKey.
/// This class is thread-safe :).
/// </summary> /// </summary>
public static partial class Terminal /// <param name="intercept">if set to <c>true</c> the pressed key will not be rendered to the output.</param>
{ /// <param name="disableLocking">if set to <c>true</c> the output will continue to be shown.
#region ReadKey /// 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>
public static ConsoleKeyInfo ReadKey(Boolean intercept, Boolean disableLocking = false) {
if(IsConsolePresent == false) {
return default;
}
/// <summary> if(disableLocking) {
/// Reads a key from the Terminal. This is the closest equivalent to Console.ReadKey. return Console.ReadKey(intercept);
/// </summary> }
/// <param name="intercept">if set to <c>true</c> the pressed key will not be rendered to the output.</param>
/// <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>
/// <returns>The console key information.</returns>
public static ConsoleKeyInfo ReadKey(bool intercept, bool disableLocking = false)
{
if (IsConsolePresent == false) return default;
if (disableLocking) return Console.ReadKey(intercept);
lock (SyncLock) lock(SyncLock) {
{ Flush();
Flush(); InputDone.Reset();
InputDone.Reset(); try {
try Console.CursorVisible = true;
{ return Console.ReadKey(intercept);
Console.CursorVisible = true; } finally {
return Console.ReadKey(intercept); Console.CursorVisible = false;
} InputDone.Set();
finally
{
Console.CursorVisible = false;
InputDone.Set();
}
}
} }
}
/// <summary>
/// Reads a key from the Terminal.
/// </summary>
/// <param name="prompt">The prompt.</param>
/// <param name="preventEcho">if set to <c>true</c> [prevent echo].</param>
/// <returns>The console key information.</returns>
public static ConsoleKeyInfo ReadKey(this string prompt, bool preventEcho)
{
if (IsConsolePresent == false) return default;
lock (SyncLock)
{
if (prompt != null)
{
($" {(string.IsNullOrWhiteSpace(Settings.LoggingTimeFormat) ? string.Empty : DateTime.Now.ToString(Settings.LoggingTimeFormat) + " ")}" +
$"{Settings.UserInputPrefix} << {prompt} ").Write(ConsoleColor.White);
}
var input = ReadKey(true);
var echo = preventEcho ? string.Empty : input.Key.ToString();
echo.WriteLine();
return input;
}
}
/// <summary>
/// Reads a key from the terminal preventing the key from being echoed.
/// </summary>
/// <param name="prompt">The prompt.</param>
/// <returns>A value that identifies the console key.</returns>
public static ConsoleKeyInfo ReadKey(this string prompt) => prompt.ReadKey(true);
#endregion
#region Other Terminal Read Methods
/// <summary>
/// Reads a line of text from the console.
/// </summary>
/// <returns>The read line.</returns>
public static string ReadLine()
{
if (IsConsolePresent == false) return default;
lock (SyncLock)
{
Flush();
InputDone.Reset();
try
{
Console.CursorVisible = true;
return Console.ReadLine();
}
finally
{
Console.CursorVisible = false;
InputDone.Set();
}
}
}
/// <summary>
/// Reads a number from the input. If unable to parse, it returns the default number.
/// </summary>
/// <param name="prompt">The prompt.</param>
/// <param name="defaultNumber">The default number.</param>
/// <returns>
/// Conversions of string representation of a number to its 32-bit signed integer equivalent.
/// </returns>
public static int ReadNumber(this string prompt, int defaultNumber)
{
if (IsConsolePresent == false) return defaultNumber;
lock (SyncLock)
{
$" {DateTime.Now:HH:mm:ss} USR << {prompt} (default is {defaultNumber}): ".Write(ConsoleColor.White);
var input = ReadLine();
return int.TryParse(input, out var parsedInt) ? parsedInt : defaultNumber;
}
}
/// <summary>
/// Creates a table prompt where the user can enter an option based on the options dictionary provided.
/// </summary>
/// <param name="title">The title.</param>
/// <param name="options">The options.</param>
/// <param name="anyKeyOption">Any key option.</param>
/// <returns>A value that identifies the console key that was pressed.</returns>
public static ConsoleKeyInfo ReadPrompt(this string title, Dictionary<ConsoleKey, string> options, string anyKeyOption)
{
if (IsConsolePresent == false) return default;
const ConsoleColor textColor = ConsoleColor.White;
var lineLength = Console.BufferWidth;
var lineAlign = -(lineLength - 2);
var textFormat = "{0," + lineAlign + "}";
// lock the output as an atomic operation
lock (SyncLock)
{
{ // Top border
Table.TopLeft();
Table.Horizontal(-lineAlign);
Table.TopRight();
}
{ // Title
Table.Vertical();
var titleText = string.Format(
textFormat,
string.IsNullOrWhiteSpace(title) ? " Select an option from the list below." : $" {title}");
titleText.Write(textColor);
Table.Vertical();
}
{ // Title Bottom
Table.LeftTee();
Table.Horizontal(lineLength - 2);
Table.RightTee();
}
// Options
foreach (var kvp in options)
{
Table.Vertical();
string.Format(textFormat,
$" {"[ " + kvp.Key + " ]",-10} {kvp.Value}").Write(textColor);
Table.Vertical();
}
// Any Key Options
if (string.IsNullOrWhiteSpace(anyKeyOption) == false)
{
Table.Vertical();
string.Format(textFormat, " ").Write(ConsoleColor.Gray);
Table.Vertical();
Table.Vertical();
string.Format(textFormat,
$" {" ",-10} {anyKeyOption}").Write(ConsoleColor.Gray);
Table.Vertical();
}
{ // Input section
Table.LeftTee();
Table.Horizontal(lineLength - 2);
Table.RightTee();
Table.Vertical();
string.Format(textFormat,
Settings.UserOptionText).Write(ConsoleColor.Green);
Table.Vertical();
Table.BottomLeft();
Table.Horizontal(lineLength - 2);
Table.BottomRight();
}
}
var inputLeft = Settings.UserOptionText.Length + 3;
SetCursorPosition(inputLeft, CursorTop - 2);
var userInput = ReadKey(true);
userInput.Key.ToString().Write(ConsoleColor.Gray);
SetCursorPosition(0, CursorTop + 2);
return userInput;
}
#endregion
} }
/// <summary>
/// Reads a key from the Terminal.
/// </summary>
/// <param name="prompt">The prompt.</param>
/// <param name="preventEcho">if set to <c>true</c> [prevent echo].</param>
/// <returns>The console key information.</returns>
public static ConsoleKeyInfo ReadKey(this String prompt, Boolean preventEcho) {
if(IsConsolePresent == false) {
return default;
}
lock(SyncLock) {
if(prompt != null) {
($" {(String.IsNullOrWhiteSpace(Settings.LoggingTimeFormat) ? String.Empty : DateTime.Now.ToString(Settings.LoggingTimeFormat) + " ")}" +
$"{Settings.UserInputPrefix} << {prompt} ").Write(ConsoleColor.White);
}
ConsoleKeyInfo input = ReadKey(true);
String echo = preventEcho ? String.Empty : input.Key.ToString();
echo.WriteLine();
return input;
}
}
/// <summary>
/// Reads a key from the terminal preventing the key from being echoed.
/// </summary>
/// <param name="prompt">The prompt.</param>
/// <returns>A value that identifies the console key.</returns>
public static ConsoleKeyInfo ReadKey(this String prompt) => prompt.ReadKey(true);
#endregion
#region Other Terminal Read Methods
/// <summary>
/// Reads a line of text from the console.
/// </summary>
/// <returns>The read line.</returns>
public static String ReadLine() {
if(IsConsolePresent == false) {
return default;
}
lock(SyncLock) {
Flush();
InputDone.Reset();
try {
Console.CursorVisible = true;
return Console.ReadLine();
} finally {
Console.CursorVisible = false;
InputDone.Set();
}
}
}
/// <summary>
/// Reads a number from the input. If unable to parse, it returns the default number.
/// </summary>
/// <param name="prompt">The prompt.</param>
/// <param name="defaultNumber">The default number.</param>
/// <returns>
/// Conversions of string representation of a number to its 32-bit signed integer equivalent.
/// </returns>
public static Int32 ReadNumber(this String prompt, Int32 defaultNumber) {
if(IsConsolePresent == false) {
return defaultNumber;
}
lock(SyncLock) {
$" {DateTime.Now:HH:mm:ss} USR << {prompt} (default is {defaultNumber}): ".Write(ConsoleColor.White);
String input = ReadLine();
return Int32.TryParse(input, out Int32 parsedInt) ? parsedInt : defaultNumber;
}
}
/// <summary>
/// Creates a table prompt where the user can enter an option based on the options dictionary provided.
/// </summary>
/// <param name="title">The title.</param>
/// <param name="options">The options.</param>
/// <param name="anyKeyOption">Any key option.</param>
/// <returns>A value that identifies the console key that was pressed.</returns>
public static ConsoleKeyInfo ReadPrompt(this String title, Dictionary<ConsoleKey, String> options, String anyKeyOption) {
if(IsConsolePresent == false) {
return default;
}
const ConsoleColor textColor = ConsoleColor.White;
Int32 lineLength = Console.BufferWidth;
Int32 lineAlign = -(lineLength - 2);
String textFormat = "{0," + lineAlign + "}";
// lock the output as an atomic operation
lock(SyncLock) {
{ // Top border
Table.TopLeft();
Table.Horizontal(-lineAlign);
Table.TopRight();
}
{ // Title
Table.Vertical();
String titleText = String.Format(
textFormat,
String.IsNullOrWhiteSpace(title) ? " Select an option from the list below." : $" {title}");
titleText.Write(textColor);
Table.Vertical();
}
{ // Title Bottom
Table.LeftTee();
Table.Horizontal(lineLength - 2);
Table.RightTee();
}
// Options
foreach(KeyValuePair<ConsoleKey, String> kvp in options) {
Table.Vertical();
String.Format(textFormat,
$" {"[ " + kvp.Key + " ]",-10} {kvp.Value}").Write(textColor);
Table.Vertical();
}
// Any Key Options
if(String.IsNullOrWhiteSpace(anyKeyOption) == false) {
Table.Vertical();
String.Format(textFormat, " ").Write(ConsoleColor.Gray);
Table.Vertical();
Table.Vertical();
String.Format(textFormat,
$" {" ",-10} {anyKeyOption}").Write(ConsoleColor.Gray);
Table.Vertical();
}
{ // Input section
Table.LeftTee();
Table.Horizontal(lineLength - 2);
Table.RightTee();
Table.Vertical();
String.Format(textFormat,
Settings.UserOptionText).Write(ConsoleColor.Green);
Table.Vertical();
Table.BottomLeft();
Table.Horizontal(lineLength - 2);
Table.BottomRight();
}
}
Int32 inputLeft = Settings.UserOptionText.Length + 3;
SetCursorPosition(inputLeft, CursorTop - 2);
ConsoleKeyInfo userInput = ReadKey(true);
userInput.Key.ToString().Write(ConsoleColor.Gray);
SetCursorPosition(0, CursorTop + 2);
return userInput;
}
#endregion
}
} }

File diff suppressed because it is too large Load Diff

View File

@ -1,174 +1,163 @@
namespace Unosquare.Swan using System;
{ using System.Linq;
using System;
using System.Linq; namespace Unosquare.Swan {
/// <summary>
/// A console terminal helper to create nicer output and receive input from the user
/// This class is thread-safe :).
/// </summary>
public static partial class Terminal {
#region Helper Methods
/// <summary> /// <summary>
/// A console terminal helper to create nicer output and receive input from the user /// Prints all characters in the current code page.
/// This class is thread-safe :). /// This is provided for debugging purposes only.
/// </summary> /// </summary>
public static partial class Terminal public static void PrintCurrentCodePage() {
{ if(!IsConsolePresent) {
#region Helper Methods return;
}
/// <summary> lock(SyncLock) {
/// Prints all characters in the current code page. $"Output Encoding: {OutputEncoding}".WriteLine();
/// This is provided for debugging purposes only. for(Byte byteValue = 0; byteValue < Byte.MaxValue; byteValue++) {
/// </summary> Char charValue = OutputEncoding.GetChars(new[] { byteValue })[0];
public static void PrintCurrentCodePage()
{
if (!IsConsolePresent) return;
lock (SyncLock) switch(byteValue) {
{ case 8: // Backspace
$"Output Encoding: {OutputEncoding}".WriteLine(); case 9: // Tab
for (byte byteValue = 0; byteValue < byte.MaxValue; byteValue++) case 10: // Line feed
{ case 13: // Carriage return
var charValue = OutputEncoding.GetChars(new[] { byteValue })[0]; charValue = '.';
break;
}
switch (byteValue) $"{byteValue:000} {charValue} ".Write();
{
case 8: // Backspace
case 9: // Tab
case 10: // Line feed
case 13: // Carriage return
charValue = '.';
break;
}
$"{byteValue:000} {charValue} ".Write(); // 7 is a beep -- Console.Beep() also works
if(byteValue == 7) {
" ".Write();
}
// 7 is a beep -- Console.Beep() also works if((byteValue + 1) % 8 == 0) {
if (byteValue == 7) " ".Write(); WriteLine();
}
if ((byteValue + 1) % 8 == 0)
WriteLine();
}
WriteLine();
}
} }
#endregion WriteLine();
}
#region Write Methods
/// <summary>
/// Writes a character a number of times, optionally adding a new line at the end.
/// </summary>
/// <param name="charCode">The character code.</param>
/// <param name="color">The color.</param>
/// <param name="count">The count.</param>
/// <param name="newLine">if set to <c>true</c> [new line].</param>
/// <param name="writerFlags">The writer flags.</param>
public static void Write(this byte charCode, ConsoleColor? color = null, int count = 1, bool newLine = false, TerminalWriters writerFlags = TerminalWriters.StandardOutput)
{
lock (SyncLock)
{
var bytes = new byte[count];
for (var i = 0; i < bytes.Length; i++)
{
bytes[i] = charCode;
}
if (newLine)
{
var newLineBytes = OutputEncoding.GetBytes(Environment.NewLine);
bytes = bytes.Union(newLineBytes).ToArray();
}
var buffer = OutputEncoding.GetChars(bytes);
var context = new OutputContext
{
OutputColor = color ?? Settings.DefaultColor,
OutputText = buffer,
OutputWriters = writerFlags,
};
EnqueueOutput(context);
}
}
/// <summary>
/// Writes the specified character in the default color.
/// </summary>
/// <param name="charCode">The character code.</param>
/// <param name="color">The color.</param>
/// <param name="writerFlags">The writer flags.</param>
public static void Write(this char charCode, ConsoleColor? color = null, TerminalWriters writerFlags = TerminalWriters.StandardOutput)
{
lock (SyncLock)
{
var context = new OutputContext
{
OutputColor = color ?? Settings.DefaultColor,
OutputText = new[] { charCode },
OutputWriters = writerFlags,
};
EnqueueOutput(context);
}
}
/// <summary>
/// Writes the specified text in the given color.
/// </summary>
/// <param name="text">The text.</param>
/// <param name="color">The color.</param>
/// <param name="writerFlags">The writer flags.</param>
public static void Write(this string text, ConsoleColor? color = null, TerminalWriters writerFlags = TerminalWriters.StandardOutput)
{
if (text == null) return;
lock (SyncLock)
{
var buffer = OutputEncoding.GetBytes(text);
var context = new OutputContext
{
OutputColor = color ?? Settings.DefaultColor,
OutputText = OutputEncoding.GetChars(buffer),
OutputWriters = writerFlags,
};
EnqueueOutput(context);
}
}
#endregion
#region WriteLine Methods
/// <summary>
/// Writes a New Line Sequence to the standard output.
/// </summary>
/// <param name="writerFlags">The writer flags.</param>
public static void WriteLine(TerminalWriters writerFlags = TerminalWriters.StandardOutput)
=> Environment.NewLine.Write(Settings.DefaultColor, writerFlags);
/// <summary>
/// Writes a line of text in the current console foreground color
/// to the standard output.
/// </summary>
/// <param name="text">The text.</param>
/// <param name="color">The color.</param>
/// <param name="writerFlags">The writer flags.</param>
public static void WriteLine(this string text, ConsoleColor? color = null, TerminalWriters writerFlags = TerminalWriters.StandardOutput)
=> Write($"{text ?? string.Empty}{Environment.NewLine}", color, writerFlags);
/// <summary>
/// As opposed to WriteLine methods, it prepends a Carriage Return character to the text
/// so that the console moves the cursor one position up after the text has been written out.
/// </summary>
/// <param name="text">The text.</param>
/// <param name="color">The color.</param>
/// <param name="writerFlags">The writer flags.</param>
public static void OverwriteLine(this string text, ConsoleColor? color = null, TerminalWriters writerFlags = TerminalWriters.StandardOutput)
{
Write($"\r{text ?? string.Empty}", color, writerFlags);
Flush();
CursorLeft = 0;
}
#endregion
} }
#endregion
#region Write Methods
/// <summary>
/// Writes a character a number of times, optionally adding a new line at the end.
/// </summary>
/// <param name="charCode">The character code.</param>
/// <param name="color">The color.</param>
/// <param name="count">The count.</param>
/// <param name="newLine">if set to <c>true</c> [new line].</param>
/// <param name="writerFlags">The writer flags.</param>
public static void Write(this Byte charCode, ConsoleColor? color = null, Int32 count = 1, Boolean newLine = false, TerminalWriters writerFlags = TerminalWriters.StandardOutput) {
lock(SyncLock) {
Byte[] bytes = new Byte[count];
for(Int32 i = 0; i < bytes.Length; i++) {
bytes[i] = charCode;
}
if(newLine) {
Byte[] newLineBytes = OutputEncoding.GetBytes(Environment.NewLine);
bytes = bytes.Union(newLineBytes).ToArray();
}
Char[] buffer = OutputEncoding.GetChars(bytes);
OutputContext context = new OutputContext {
OutputColor = color ?? Settings.DefaultColor,
OutputText = buffer,
OutputWriters = writerFlags,
};
EnqueueOutput(context);
}
}
/// <summary>
/// Writes the specified character in the default color.
/// </summary>
/// <param name="charCode">The character code.</param>
/// <param name="color">The color.</param>
/// <param name="writerFlags">The writer flags.</param>
public static void Write(this Char charCode, ConsoleColor? color = null, TerminalWriters writerFlags = TerminalWriters.StandardOutput) {
lock(SyncLock) {
OutputContext context = new OutputContext {
OutputColor = color ?? Settings.DefaultColor,
OutputText = new[] { charCode },
OutputWriters = writerFlags,
};
EnqueueOutput(context);
}
}
/// <summary>
/// Writes the specified text in the given color.
/// </summary>
/// <param name="text">The text.</param>
/// <param name="color">The color.</param>
/// <param name="writerFlags">The writer flags.</param>
public static void Write(this String text, ConsoleColor? color = null, TerminalWriters writerFlags = TerminalWriters.StandardOutput) {
if(text == null) {
return;
}
lock(SyncLock) {
Byte[] buffer = OutputEncoding.GetBytes(text);
OutputContext context = new OutputContext {
OutputColor = color ?? Settings.DefaultColor,
OutputText = OutputEncoding.GetChars(buffer),
OutputWriters = writerFlags,
};
EnqueueOutput(context);
}
}
#endregion
#region WriteLine Methods
/// <summary>
/// Writes a New Line Sequence to the standard output.
/// </summary>
/// <param name="writerFlags">The writer flags.</param>
public static void WriteLine(TerminalWriters writerFlags = TerminalWriters.StandardOutput)
=> Environment.NewLine.Write(Settings.DefaultColor, writerFlags);
/// <summary>
/// Writes a line of text in the current console foreground color
/// to the standard output.
/// </summary>
/// <param name="text">The text.</param>
/// <param name="color">The color.</param>
/// <param name="writerFlags">The writer flags.</param>
public static void WriteLine(this String text, ConsoleColor? color = null, TerminalWriters writerFlags = TerminalWriters.StandardOutput)
=> Write($"{text ?? String.Empty}{Environment.NewLine}", color, writerFlags);
/// <summary>
/// As opposed to WriteLine methods, it prepends a Carriage Return character to the text
/// so that the console moves the cursor one position up after the text has been written out.
/// </summary>
/// <param name="text">The text.</param>
/// <param name="color">The color.</param>
/// <param name="writerFlags">The writer flags.</param>
public static void OverwriteLine(this String text, ConsoleColor? color = null, TerminalWriters writerFlags = TerminalWriters.StandardOutput) {
Write($"\r{text ?? String.Empty}", color, writerFlags);
Flush();
CursorLeft = 0;
}
#endregion
}
} }

View File

@ -1,195 +1,190 @@
namespace Unosquare.Swan using System;
{
using System;
namespace Unosquare.Swan {
/// <summary>
/// A console terminal helper to create nicer output and receive input from the user
/// This class is thread-safe :).
/// </summary>
public static partial class Terminal {
/// <summary> /// <summary>
/// A console terminal helper to create nicer output and receive input from the user /// Terminal global settings.
/// This class is thread-safe :).
/// </summary> /// </summary>
public static partial class Terminal public static class Settings {
{ static Settings() {
/// <summary> DisplayLoggingMessageType = IsDebuggerAttached
/// Terminal global settings. ? LogMessageType.Debug |
/// </summary> LogMessageType.Error |
public static class Settings LogMessageType.Info |
{ LogMessageType.Trace |
static Settings() LogMessageType.Warning |
{ LogMessageType.Fatal
if (IsDebuggerAttached) : LogMessageType.Error |
{ LogMessageType.Info |
DisplayLoggingMessageType = LogMessageType.Warning |
LogMessageType.Debug | LogMessageType.Fatal;
LogMessageType.Error |
LogMessageType.Info |
LogMessageType.Trace |
LogMessageType.Warning |
LogMessageType.Fatal;
}
else
{
DisplayLoggingMessageType =
LogMessageType.Error |
LogMessageType.Info |
LogMessageType.Warning |
LogMessageType.Fatal;
}
GlobalLoggingMessageType = DisplayLoggingMessageType; GlobalLoggingMessageType = DisplayLoggingMessageType;
} }
/// <summary> /// <summary>
/// Gets or sets the default output color. /// Gets or sets the default output color.
/// </summary> /// </summary>
/// <value> /// <value>
/// The default color. /// The default color.
/// </value> /// </value>
public static ConsoleColor DefaultColor { get; set; } = Console.ForegroundColor; public static ConsoleColor DefaultColor { get; set; } = Console.ForegroundColor;
/// <summary> /// <summary>
/// Gets or sets the color of the information output logging. /// Gets or sets the color of the information output logging.
/// </summary> /// </summary>
/// <value> /// <value>
/// The color of the information. /// The color of the information.
/// </value> /// </value>
public static ConsoleColor InfoColor { get; set; } = ConsoleColor.Cyan; public static ConsoleColor InfoColor { get; set; } = ConsoleColor.Cyan;
/// <summary> /// <summary>
/// Gets or sets the color of the debug output logging. /// Gets or sets the color of the debug output logging.
/// </summary> /// </summary>
/// <value> /// <value>
/// The color of the debug. /// The color of the debug.
/// </value> /// </value>
public static ConsoleColor DebugColor { get; set; } = ConsoleColor.Gray; public static ConsoleColor DebugColor { get; set; } = ConsoleColor.Gray;
/// <summary> /// <summary>
/// Gets or sets the color of the trace output logging. /// Gets or sets the color of the trace output logging.
/// </summary> /// </summary>
/// <value> /// <value>
/// The color of the trace. /// The color of the trace.
/// </value> /// </value>
public static ConsoleColor TraceColor { get; set; } = ConsoleColor.DarkGray; public static ConsoleColor TraceColor { get; set; } = ConsoleColor.DarkGray;
/// <summary> /// <summary>
/// Gets or sets the color of the warning logging. /// Gets or sets the color of the warning logging.
/// </summary> /// </summary>
/// <value> /// <value>
/// The color of the warn. /// The color of the warn.
/// </value> /// </value>
public static ConsoleColor WarnColor { get; set; } = ConsoleColor.Yellow; public static ConsoleColor WarnColor { get; set; } = ConsoleColor.Yellow;
/// <summary> /// <summary>
/// Gets or sets the color of the error logging. /// Gets or sets the color of the error logging.
/// </summary> /// </summary>
/// <value> /// <value>
/// The color of the error. /// The color of the error.
/// </value> /// </value>
public static ConsoleColor ErrorColor { get; set; } = ConsoleColor.DarkRed; public static ConsoleColor ErrorColor { get; set; } = ConsoleColor.DarkRed;
/// <summary> /// <summary>
/// Gets or sets the color of the error logging. /// Gets or sets the color of the error logging.
/// </summary> /// </summary>
/// <value> /// <value>
/// The color of the error. /// The color of the error.
/// </value> /// </value>
public static ConsoleColor FatalColor { get; set; } = ConsoleColor.Red; public static ConsoleColor FatalColor { get; set; } = ConsoleColor.Red;
/// <summary> /// <summary>
/// Gets or sets the information logging prefix. /// Gets or sets the information logging prefix.
/// </summary> /// </summary>
/// <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 user input prefix. /// Gets or sets the user input prefix.
/// </summary> /// </summary>
/// <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.
/// </summary> /// </summary>
/// <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: ";
/// <summary> /// <summary>
/// Gets or sets the debug logging prefix. /// Gets or sets the debug logging prefix.
/// </summary> /// </summary>
/// <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.
/// </summary> /// </summary>
/// <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.
/// </summary> /// </summary>
/// <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.
/// </summary> /// </summary>
/// <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.
/// </summary> /// </summary>
/// <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 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.
/// </summary> /// </summary>
/// <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 or sets the logging message types (in a bitwise mask) /// Gets or sets the logging message types (in a bitwise mask)
/// to display in the console. /// to display in the console.
/// </summary> /// </summary>
/// <value> /// <value>
/// The console options. /// The console options.
/// </value> /// </value>
public static LogMessageType DisplayLoggingMessageType { get; set; } public static LogMessageType DisplayLoggingMessageType {
get; set;
}
/// <summary> /// <summary>
/// Gets or sets the logging message types (in a bitwise mask) to global logging. /// Gets or sets the logging message types (in a bitwise mask) to global logging.
/// </summary> /// </summary>
/// <value> /// <value>
/// The type of the global logging message. /// The type of the global logging message.
/// </value> /// </value>
public static LogMessageType GlobalLoggingMessageType { get; set; } public static LogMessageType GlobalLoggingMessageType {
get; set;
}
/// <summary> /// <summary>
/// Gets or sets a value indicating whether [override is console present]. /// Gets or sets a value indicating whether [override is console present].
/// </summary> /// </summary>
/// <value> /// <value>
/// <c>true</c> if [override is console present]; otherwise, <c>false</c>. /// <c>true</c> if [override is console present]; otherwise, <c>false</c>.
/// </value> /// </value>
public static bool OverrideIsConsolePresent { get; set; } public static Boolean OverrideIsConsolePresent {
} get; set;
}
} }
}
} }

View File

@ -1,360 +1,355 @@
namespace Unosquare.Swan using Unosquare.Swan.Abstractions;
{ using System;
using Abstractions; using System.Collections.Concurrent;
using System; using System.Text;
using System.Collections.Concurrent; using System.Threading;
using System.Text;
using System.Threading; namespace Unosquare.Swan {
/// <summary>
/// A console terminal helper to create nicer output and receive input from the user.
/// This class is thread-safe :).
/// </summary>
public static partial class Terminal {
#region Private Declarations
private const Int32 OutputFlushInterval = 15;
private static readonly ExclusiveTimer DequeueOutputTimer;
private static readonly Object SyncLock = new Object();
private static readonly ConcurrentQueue<OutputContext> OutputQueue = new ConcurrentQueue<OutputContext>();
private static readonly ManualResetEventSlim OutputDone = new ManualResetEventSlim(false);
private static readonly ManualResetEventSlim InputDone = new ManualResetEventSlim(true);
private static Boolean? _isConsolePresent;
#endregion
#region Constructors
/// <summary> /// <summary>
/// A console terminal helper to create nicer output and receive input from the user. /// Initializes static members of the <see cref="Terminal"/> class.
/// This class is thread-safe :).
/// </summary> /// </summary>
public static partial class Terminal static Terminal() {
{ lock(SyncLock) {
#region Private Declarations if(DequeueOutputTimer != null) {
return;
}
private const int OutputFlushInterval = 15; if(IsConsolePresent) {
private static readonly ExclusiveTimer DequeueOutputTimer;
private static readonly object SyncLock = new object();
private static readonly ConcurrentQueue<OutputContext> OutputQueue = new ConcurrentQueue<OutputContext>();
private static readonly ManualResetEventSlim OutputDone = new ManualResetEventSlim(false);
private static readonly ManualResetEventSlim InputDone = new ManualResetEventSlim(true);
private static bool? _isConsolePresent;
#endregion
#region Constructors
/// <summary>
/// Initializes static members of the <see cref="Terminal"/> class.
/// </summary>
static Terminal()
{
lock (SyncLock)
{
if (DequeueOutputTimer != null) return;
if (IsConsolePresent)
{
#if !NET452 #if !NET452
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
#endif #endif
Console.CursorVisible = false; Console.CursorVisible = false;
}
// Here we start the output task, fire-and-forget
DequeueOutputTimer = new ExclusiveTimer(DequeueOutputCycle);
DequeueOutputTimer.Resume(OutputFlushInterval);
}
} }
#endregion // Here we start the output task, fire-and-forget
DequeueOutputTimer = new ExclusiveTimer(DequeueOutputCycle);
#region Synchronized Cursor Movement DequeueOutputTimer.Resume(OutputFlushInterval);
}
/// <summary>
/// Gets or sets the cursor left position.
/// </summary>
/// <value>
/// The cursor left.
/// </value>
public static int CursorLeft
{
get
{
if (IsConsolePresent == false) return -1;
lock (SyncLock)
{
Flush();
return Console.CursorLeft;
}
}
set
{
if (IsConsolePresent == false) return;
lock (SyncLock)
{
Flush();
Console.CursorLeft = value;
}
}
}
/// <summary>
/// Gets or sets the cursor top position.
/// </summary>
/// <value>
/// The cursor top.
/// </value>
public static int CursorTop
{
get
{
if (IsConsolePresent == false) return -1;
lock (SyncLock)
{
Flush();
return Console.CursorTop;
}
}
set
{
if (IsConsolePresent == false) return;
lock (SyncLock)
{
Flush();
Console.CursorTop = value;
}
}
}
#endregion
#region Properties
/// <summary>
/// Gets a value indicating whether the Console is present.
/// </summary>
/// <value>
/// <c>true</c> if this instance is console present; otherwise, <c>false</c>.
/// </value>
public static bool IsConsolePresent
{
get
{
if (Settings.OverrideIsConsolePresent) return true;
if (_isConsolePresent == null)
{
_isConsolePresent = true;
try
{
var windowHeight = Console.WindowHeight;
_isConsolePresent = windowHeight >= 0;
}
catch
{
_isConsolePresent = false;
}
}
return _isConsolePresent.Value;
}
}
/// <summary>
/// Gets a value indicating whether a debugger is attached.
/// </summary>
/// <value>
/// <c>true</c> if this instance is debugger attached; otherwise, <c>false</c>.
/// </value>
public static bool IsDebuggerAttached => System.Diagnostics.Debugger.IsAttached;
/// <summary>
/// Gets the available output writers in a bitwise mask.
/// </summary>
/// <value>
/// The available writers.
/// </value>
public static TerminalWriters AvailableWriters
{
get
{
var writers = TerminalWriters.None;
if (IsConsolePresent)
writers = TerminalWriters.StandardError | TerminalWriters.StandardOutput;
if (IsDebuggerAttached)
writers = writers | TerminalWriters.Diagnostics;
return writers;
}
}
/// <summary>
/// Gets or sets the output encoding for the current console.
/// </summary>
/// <value>
/// The output encoding.
/// </value>
public static Encoding OutputEncoding
{
get => Console.OutputEncoding;
set => Console.OutputEncoding = value;
}
#endregion
#region Methods
/// <summary>
/// Waits for all of the queued output messages to be written out to the console.
/// Call this method if it is important to display console text before
/// quitting the application such as showing usage or help.
/// Set the timeout to null or TimeSpan.Zero to wait indefinitely.
/// </summary>
/// <param name="timeout">The timeout. Set the amount of time to black before this method exits.</param>
public static void Flush(TimeSpan? timeout = null)
{
if (timeout == null) timeout = TimeSpan.Zero;
var startTime = DateTime.UtcNow;
while (OutputQueue.Count > 0)
{
// Manually trigger a timer cycle to run immediately
DequeueOutputTimer.Change(0, OutputFlushInterval);
// Wait for the output to finish
if (OutputDone.Wait(OutputFlushInterval))
break;
// infinite timeout
if (timeout.Value == TimeSpan.Zero)
continue;
// break if we have reached a timeout condition
if (DateTime.UtcNow.Subtract(startTime) >= timeout.Value)
break;
}
}
/// <summary>
/// Sets the cursor position.
/// </summary>
/// <param name="left">The left.</param>
/// <param name="top">The top.</param>
public static void SetCursorPosition(int left, int top)
{
if (IsConsolePresent == false) return;
lock (SyncLock)
{
Flush();
Console.SetCursorPosition(left.Clamp(0, left), top.Clamp(0, top));
}
}
/// <summary>
/// Moves the output cursor one line up starting at left position 0
/// Please note that backlining the cursor does not clear the contents of the
/// previous line so you might need to clear it by writing an empty string the
/// length of the console width.
/// </summary>
public static void BacklineCursor() => SetCursorPosition(0, CursorTop - 1);
/// <summary>
/// Enqueues the output to be written to the console
/// This is the only method that should enqueue to the output
/// Please note that if AvailableWriters is None, then no output will be enqueued.
/// </summary>
/// <param name="context">The context.</param>
private static void EnqueueOutput(OutputContext context)
{
lock (SyncLock)
{
var availableWriters = AvailableWriters;
if (availableWriters == TerminalWriters.None || context.OutputWriters == TerminalWriters.None)
{
OutputDone.Set();
return;
}
if ((context.OutputWriters & availableWriters) == TerminalWriters.None)
return;
OutputDone.Reset();
OutputQueue.Enqueue(context);
}
}
/// <summary>
/// Runs a Terminal I/O cycle in the <see cref="ThreadPool"/> thread.
/// </summary>
private static void DequeueOutputCycle()
{
if (AvailableWriters == TerminalWriters.None)
{
OutputDone.Set();
return;
}
InputDone.Wait();
if (OutputQueue.Count <= 0)
{
OutputDone.Set();
return;
}
OutputDone.Reset();
while (OutputQueue.Count > 0)
{
if (OutputQueue.TryDequeue(out var context) == false) continue;
// Process Console output and Skip over stuff we can't display so we don't stress the output too much.
if (IsConsolePresent && (Settings.OverrideIsConsolePresent || OutputQueue.Count <= Console.BufferHeight))
{
// Output to the standard output
if (context.OutputWriters.HasFlag(TerminalWriters.StandardOutput))
{
Console.ForegroundColor = context.OutputColor;
Console.Out.Write(context.OutputText);
Console.ResetColor();
Console.ForegroundColor = context.OriginalColor;
}
// output to the standard error
if (context.OutputWriters.HasFlag(TerminalWriters.StandardError))
{
Console.ForegroundColor = context.OutputColor;
Console.Error.Write(context.OutputText);
Console.ResetColor();
Console.ForegroundColor = context.OriginalColor;
}
}
// Process Debugger output
if (IsDebuggerAttached && context.OutputWriters.HasFlag(TerminalWriters.Diagnostics))
{
System.Diagnostics.Debug.Write(new string(context.OutputText));
}
}
}
#endregion
#region Output Context
/// <summary>
/// Represents an asynchronous output context.
/// </summary>
private sealed class OutputContext
{
/// <summary>
/// Initializes a new instance of the <see cref="OutputContext"/> class.
/// </summary>
public OutputContext()
{
OriginalColor = Settings.DefaultColor;
OutputWriters = IsConsolePresent
? TerminalWriters.StandardOutput
: IsDebuggerAttached
? TerminalWriters.Diagnostics
: TerminalWriters.None;
}
public ConsoleColor OriginalColor { get; }
public ConsoleColor OutputColor { get; set; }
public char[] OutputText { get; set; }
public TerminalWriters OutputWriters { get; set; }
}
#endregion
} }
#endregion
#region Synchronized Cursor Movement
/// <summary>
/// Gets or sets the cursor left position.
/// </summary>
/// <value>
/// The cursor left.
/// </value>
public static Int32 CursorLeft {
get {
if(IsConsolePresent == false) {
return -1;
}
lock(SyncLock) {
Flush();
return Console.CursorLeft;
}
}
set {
if(IsConsolePresent == false) {
return;
}
lock(SyncLock) {
Flush();
Console.CursorLeft = value;
}
}
}
/// <summary>
/// Gets or sets the cursor top position.
/// </summary>
/// <value>
/// The cursor top.
/// </value>
public static Int32 CursorTop {
get {
if(IsConsolePresent == false) {
return -1;
}
lock(SyncLock) {
Flush();
return Console.CursorTop;
}
}
set {
if(IsConsolePresent == false) {
return;
}
lock(SyncLock) {
Flush();
Console.CursorTop = value;
}
}
}
#endregion
#region Properties
/// <summary>
/// Gets a value indicating whether the Console is present.
/// </summary>
/// <value>
/// <c>true</c> if this instance is console present; otherwise, <c>false</c>.
/// </value>
public static Boolean IsConsolePresent {
get {
if(Settings.OverrideIsConsolePresent) {
return true;
}
if(_isConsolePresent == null) {
_isConsolePresent = true;
try {
Int32 windowHeight = Console.WindowHeight;
_isConsolePresent = windowHeight >= 0;
} catch {
_isConsolePresent = false;
}
}
return _isConsolePresent.Value;
}
}
/// <summary>
/// Gets a value indicating whether a debugger is attached.
/// </summary>
/// <value>
/// <c>true</c> if this instance is debugger attached; otherwise, <c>false</c>.
/// </value>
public static Boolean IsDebuggerAttached => System.Diagnostics.Debugger.IsAttached;
/// <summary>
/// Gets the available output writers in a bitwise mask.
/// </summary>
/// <value>
/// The available writers.
/// </value>
public static TerminalWriters AvailableWriters {
get {
TerminalWriters writers = TerminalWriters.None;
if(IsConsolePresent) {
writers = TerminalWriters.StandardError | TerminalWriters.StandardOutput;
}
if(IsDebuggerAttached) {
writers |= TerminalWriters.Diagnostics;
}
return writers;
}
}
/// <summary>
/// Gets or sets the output encoding for the current console.
/// </summary>
/// <value>
/// The output encoding.
/// </value>
public static Encoding OutputEncoding {
get => Console.OutputEncoding;
set => Console.OutputEncoding = value;
}
#endregion
#region Methods
/// <summary>
/// Waits for all of the queued output messages to be written out to the console.
/// Call this method if it is important to display console text before
/// quitting the application such as showing usage or help.
/// Set the timeout to null or TimeSpan.Zero to wait indefinitely.
/// </summary>
/// <param name="timeout">The timeout. Set the amount of time to black before this method exits.</param>
public static void Flush(TimeSpan? timeout = null) {
if(timeout == null) {
timeout = TimeSpan.Zero;
}
DateTime startTime = DateTime.UtcNow;
while(OutputQueue.Count > 0) {
// Manually trigger a timer cycle to run immediately
DequeueOutputTimer.Change(0, OutputFlushInterval);
// Wait for the output to finish
if(OutputDone.Wait(OutputFlushInterval)) {
break;
}
// infinite timeout
if(timeout.Value == TimeSpan.Zero) {
continue;
}
// break if we have reached a timeout condition
if(DateTime.UtcNow.Subtract(startTime) >= timeout.Value) {
break;
}
}
}
/// <summary>
/// Sets the cursor position.
/// </summary>
/// <param name="left">The left.</param>
/// <param name="top">The top.</param>
public static void SetCursorPosition(Int32 left, Int32 top) {
if(IsConsolePresent == false) {
return;
}
lock(SyncLock) {
Flush();
Console.SetCursorPosition(left.Clamp(0, left), top.Clamp(0, top));
}
}
/// <summary>
/// Moves the output cursor one line up starting at left position 0
/// Please note that backlining the cursor does not clear the contents of the
/// previous line so you might need to clear it by writing an empty string the
/// length of the console width.
/// </summary>
public static void BacklineCursor() => SetCursorPosition(0, CursorTop - 1);
/// <summary>
/// Enqueues the output to be written to the console
/// This is the only method that should enqueue to the output
/// Please note that if AvailableWriters is None, then no output will be enqueued.
/// </summary>
/// <param name="context">The context.</param>
private static void EnqueueOutput(OutputContext context) {
lock(SyncLock) {
TerminalWriters availableWriters = AvailableWriters;
if(availableWriters == TerminalWriters.None || context.OutputWriters == TerminalWriters.None) {
OutputDone.Set();
return;
}
if((context.OutputWriters & availableWriters) == TerminalWriters.None) {
return;
}
OutputDone.Reset();
OutputQueue.Enqueue(context);
}
}
/// <summary>
/// Runs a Terminal I/O cycle in the <see cref="ThreadPool"/> thread.
/// </summary>
private static void DequeueOutputCycle() {
if(AvailableWriters == TerminalWriters.None) {
OutputDone.Set();
return;
}
InputDone.Wait();
if(OutputQueue.Count <= 0) {
OutputDone.Set();
return;
}
OutputDone.Reset();
while(OutputQueue.Count > 0) {
if(OutputQueue.TryDequeue(out OutputContext context) == false) {
continue;
}
// Process Console output and Skip over stuff we can't display so we don't stress the output too much.
if(IsConsolePresent && (Settings.OverrideIsConsolePresent || OutputQueue.Count <= Console.BufferHeight)) {
// Output to the standard output
if(context.OutputWriters.HasFlag(TerminalWriters.StandardOutput)) {
Console.ForegroundColor = context.OutputColor;
Console.Out.Write(context.OutputText);
Console.ResetColor();
Console.ForegroundColor = context.OriginalColor;
}
// output to the standard error
if(context.OutputWriters.HasFlag(TerminalWriters.StandardError)) {
Console.ForegroundColor = context.OutputColor;
Console.Error.Write(context.OutputText);
Console.ResetColor();
Console.ForegroundColor = context.OriginalColor;
}
}
// Process Debugger output
if(IsDebuggerAttached && context.OutputWriters.HasFlag(TerminalWriters.Diagnostics)) {
System.Diagnostics.Debug.Write(new String(context.OutputText));
}
}
}
#endregion
#region Output Context
/// <summary>
/// Represents an asynchronous output context.
/// </summary>
private sealed class OutputContext {
/// <summary>
/// Initializes a new instance of the <see cref="OutputContext"/> class.
/// </summary>
public OutputContext() {
this.OriginalColor = Settings.DefaultColor;
this.OutputWriters = IsConsolePresent
? TerminalWriters.StandardOutput
: IsDebuggerAttached
? TerminalWriters.Diagnostics
: TerminalWriters.None;
}
public ConsoleColor OriginalColor {
get;
}
public ConsoleColor OutputColor {
get; set;
}
public Char[] OutputText {
get; set;
}
public TerminalWriters OutputWriters {
get; set;
}
}
#endregion
}
} }