Init RaspberryIO

This commit is contained in:
2019-02-17 14:08:57 +01:00
commit e7c3f7af91
193 changed files with 36743 additions and 0 deletions
@@ -0,0 +1,159 @@
namespace Unosquare.Swan.Components
{
using System.Linq;
using System.Reflection;
using Attributes;
using System;
using System.Collections.Generic;
/// <summary>
/// Provides methods to parse command line arguments.
/// Based on CommandLine (Copyright 2005-2015 Giacomo Stelluti Scala and Contributors.).
/// </summary>
public partial class ArgumentParser
{
private sealed class Validator
{
private readonly object _instance;
private readonly IEnumerable<string> _args;
private readonly List<PropertyInfo> _updatedList = new List<PropertyInfo>();
private readonly ArgumentParserSettings _settings;
private readonly PropertyInfo[] _properties;
public Validator(
PropertyInfo[] properties,
IEnumerable<string> args,
object instance,
ArgumentParserSettings settings)
{
_args = args;
_instance = instance;
_settings = settings;
_properties = properties;
PopulateInstance();
SetDefaultValues();
GetRequiredList();
}
public List<string> UnknownList { get; } = new List<string>();
public List<string> RequiredList { get; } = new List<string>();
public bool IsValid() => (_settings.IgnoreUnknownArguments || !UnknownList.Any()) && !RequiredList.Any();
public IEnumerable<ArgumentOptionAttribute> GetPropertiesOptions()
=> _properties.Select(p => Runtime.AttributeCache.RetrieveOne<ArgumentOptionAttribute>(p))
.Where(x => x != null);
private void GetRequiredList()
{
foreach (var targetProperty in _properties)
{
var optionAttr = Runtime.AttributeCache.RetrieveOne<ArgumentOptionAttribute>(targetProperty);
if (optionAttr == null || optionAttr.Required == false)
continue;
if (targetProperty.GetValue(_instance) == null)
{
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));
}
}
}
@@ -0,0 +1,57 @@
namespace Unosquare.Swan.Components
{
using System.Linq;
using System.Reflection;
using Attributes;
using System;
/// <summary>
/// Provides methods to parse command line arguments.
/// </summary>
public partial class ArgumentParser
{
private sealed class TypeResolver<T>
{
private readonly string _selectedVerb;
private PropertyInfo[] _properties;
public TypeResolver(string selectedVerb)
{
_selectedVerb = selectedVerb;
}
public PropertyInfo[] GetProperties() => _properties?.Any() == true ? _properties : null;
public object GetOptionsObject(T instance)
{
_properties = Runtime.PropertyTypeCache.RetrieveAllProperties<T>(true).ToArray();
if (!_properties.Any(x => x.GetCustomAttributes(typeof(VerbOptionAttribute), false).Any()))
return instance;
var selectedVerb = string.IsNullOrWhiteSpace(_selectedVerb)
? null
: _properties.FirstOrDefault(x =>
Runtime.AttributeCache.RetrieveOne<VerbOptionAttribute>(x).Name.Equals(_selectedVerb));
if (selectedVerb == null) return null;
var type = instance.GetType();
var verbProperty = type.GetProperty(selectedVerb.Name);
if (verbProperty?.GetValue(instance) == null)
{
var propertyInstance = Activator.CreateInstance(selectedVerb.PropertyType);
verbProperty?.SetValue(instance, propertyInstance);
}
_properties = Runtime.PropertyTypeCache.RetrieveAllProperties(selectedVerb.PropertyType, true)
.ToArray();
return verbProperty?.GetValue(instance);
}
}
}
}
@@ -0,0 +1,230 @@
namespace Unosquare.Swan.Components
{
using System;
using System.Collections.Generic;
using Attributes;
using System.Linq;
/// <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>
/// Initializes a new instance of the <see cref="ArgumentParser" /> class,
/// configurable with <see cref="ArgumentParserSettings" /> using a delegate.
/// </summary>
/// <param name="parseSettings">The parse settings.</param>
public ArgumentParser(ArgumentParserSettings parseSettings)
{
Settings = parseSettings ?? throw new ArgumentNullException(nameof(parseSettings));
}
/// <summary>
/// Gets the instance that implements <see cref="ArgumentParserSettings" /> in use.
/// </summary>
/// <value>
/// The settings.
/// </value>
public ArgumentParserSettings Settings { get; }
/// <summary>
/// Parses a string array of command line arguments constructing values in an instance of type <typeparamref name="T" />.
/// </summary>
/// <typeparam name="T">The type of the options.</typeparam>
/// <param name="args">The arguments.</param>
/// <param name="instance">The instance.</param>
/// <returns>
/// <c>true</c> if was converted successfully; otherwise, <c>false</c>.
/// </returns>
/// <exception cref="ArgumentNullException">
/// The exception that is thrown when a null reference (Nothing in Visual Basic)
/// is passed to a method that does not accept it as a valid argument.
/// </exception>
/// <exception cref="InvalidOperationException">
/// The exception that is thrown when a method call is invalid for the object's current state.
/// </exception>
public bool ParseArguments<T>(IEnumerable<string> args, T instance)
{
if (args == null)
throw new ArgumentNullException(nameof(args));
if (Equals(instance, default(T)))
throw new ArgumentNullException(nameof(instance));
var typeResolver = new TypeResolver<T>(args.FirstOrDefault());
var options = typeResolver.GetOptionsObject(instance);
if (options == null)
{
ReportUnknownVerb<T>();
return false;
}
var properties = typeResolver.GetProperties();
if (properties == null)
throw new InvalidOperationException($"Type {typeof(T).Name} is not valid");
var validator = new Validator(properties, args, options, Settings);
if (validator.IsValid())
return true;
ReportIssues(validator);
return false;
}
private static void ReportUnknownVerb<T>()
{
"No verb was specified".WriteLine(ConsoleColor.Red);
"Valid verbs:".WriteLine(ConsoleColor.Cyan);
Runtime.PropertyTypeCache.RetrieveAllProperties<T>(true)
.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 (Settings.WriteBanner)
Runtime.WriteWelcomeBanner();
#endif
var options = validator.GetPropertiesOptions();
foreach (var option in options)
{
string.Empty.WriteLine();
// TODO: If Enum list values
var shortName = string.IsNullOrWhiteSpace(option.ShortName) ? string.Empty : $"-{option.ShortName}";
var longName = string.IsNullOrWhiteSpace(option.LongName) ? string.Empty : $"--{option.LongName}";
var comma = string.IsNullOrWhiteSpace(shortName) || string.IsNullOrWhiteSpace(longName)
? string.Empty
: ", ";
var defaultValue = option.DefaultValue == null ? string.Empty : $"(Default: {option.DefaultValue}) ";
$" {shortName}{comma}{longName}\t\t{defaultValue}{option.HelpText}".WriteLine(ConsoleColor.Cyan);
}
string.Empty.WriteLine();
" --help\t\tDisplay this help screen.".WriteLine(ConsoleColor.Cyan);
if (validator.UnknownList.Any())
$"Unknown arguments: {string.Join(", ", validator.UnknownList)}".WriteLine(ConsoleColor.Red);
if (validator.RequiredList.Any())
$"Required arguments: {string.Join(", ", validator.RequiredList)}".WriteLine(ConsoleColor.Red);
}
}
}
@@ -0,0 +1,53 @@
namespace Unosquare.Swan.Components
{
using System;
/// <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 bool WriteBanner { get; set; } = true;
/// <summary>
/// Gets or sets a value indicating whether perform case sensitive comparisons.
/// Note that case insensitivity only applies to <i>parameters</i>, not the values
/// assigned to them (for example, enum parsing).
/// </summary>
/// <value>
/// <c>true</c> if [case sensitive]; otherwise, <c>false</c>.
/// </value>
public bool CaseSensitive { get; set; } = false;
/// <summary>
/// Gets or sets a value indicating whether perform case sensitive comparisons of <i>values</i>.
/// Note that case insensitivity only applies to <i>values</i>, not the parameters.
/// </summary>
/// <value>
/// <c>true</c> if [case insensitive enum values]; otherwise, <c>false</c>.
/// </value>
public bool CaseInsensitiveEnumValues { get; set; } = true;
/// <summary>
/// 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;
}
}
+130
View File
@@ -0,0 +1,130 @@
namespace Unosquare.Swan.Components
{
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
/// <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>
/// Starts measuring with the given identifier.
/// </summary>
/// <param name="identifier">The identifier.</param>
/// <returns>A disposable object that when disposed, adds a benchmark result.</returns>
public static IDisposable Start(string identifier) => new BenchmarkUnit(identifier);
/// <summary>
/// Outputs the benchmark statistics.
/// </summary>
/// <returns>A string containing human-readable statistics.</returns>
public static string Dump()
{
var builder = new StringBuilder();
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>
/// 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;
}
}
}
}
@@ -0,0 +1,44 @@
namespace Unosquare.Swan.Components
{
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
/// <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>
/// Determines whether the cache contains the specified key.
/// </summary>
/// <param name="key">The key.</param>
/// <returns><c>true</c> if the cache contains the key, otherwise <c>false</c>.</returns>
public bool ContainsKey(Type key) => _data.Value.ContainsKey(key);
/// <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>
/// <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));
}
}
}
@@ -0,0 +1,171 @@
namespace Unosquare.Swan.Components
{
using System;
using System.Collections.Generic;
using System.Linq;
using Abstractions;
/// <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
{
return Instance.Retrieve(typeof(T), t => Enum.GetValues(t)
.Cast<object>()
.Select(item => Tuple.Create(Enum.GetName(t, item), item)));
}
/// <summary>
/// Gets the cached items with the enum item value.
/// </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>> GetItemsWithValue<T>(bool humanize = true)
where T : struct, IConvertible
{
return Retrieve<T>()
.Select(x => Tuple.Create((int) x.Item2, humanize ? x.Item1.Humanize() : x.Item1));
}
/// <summary>
/// Gets the flag values.
/// </summary>
/// <typeparam name="TEnum">The type of the enum.</typeparam>
/// <param name="value">The value.</param>
/// <param name="ignoreZero">if set to <c>true</c> [ignore zero].</param>
/// <returns>
/// A list of values in the flag.
/// </returns>
public static IEnumerable<int> GetFlagValues<TEnum>(int value, bool ignoreZero = false)
where TEnum : struct, IConvertible
{
return Retrieve<TEnum>()
.Select(x => (int) x.Item2)
.When(() => ignoreZero, q => q.Where(f => f != 0))
.Where(x => (x & value) == x);
}
/// <summary>
/// Gets the flag values.
/// </summary>
/// <typeparam name="TEnum">The type of the enum.</typeparam>
/// <param name="value">The value.</param>
/// <param name="ignoreZero">if set to <c>true</c> [ignore zero].</param>
/// <returns>
/// A list of values in the flag.
/// </returns>
public static IEnumerable<long> GetFlagValues<TEnum>(long value, bool ignoreZero = false)
where TEnum : struct, IConvertible
{
return Retrieve<TEnum>()
.Select(x => (long) x.Item2)
.When(() => ignoreZero, q => q.Where(f => f != 0))
.Where(x => (x & value) == x);
}
/// <summary>
/// Gets the flag values.
/// </summary>
/// <typeparam name="TEnum">The type of the enum.</typeparam>
/// <param name="value">The value.</param>
/// <param name="ignoreZero">if set to <c>true</c> [ignore zero].</param>
/// <returns>
/// A list of values in the flag.
/// </returns>
public static IEnumerable<byte> GetFlagValues<TEnum>(byte value, bool ignoreZero = false)
where TEnum : struct, IConvertible
{
return Retrieve<TEnum>()
.Select(x => (byte) x.Item2)
.When(() => ignoreZero, q => q.Where(f => f != 0))
.Where(x => (x & value) == x);
}
/// <summary>
/// Gets the flag names.
/// </summary>
/// <typeparam name="TEnum">The type of the enum.</typeparam>
/// <param name="value">the value.</param>
/// <param name="ignoreZero">if set to <c>true</c> [ignore zero].</param>
/// <param name="humanize">if set to <c>true</c> [humanize].</param>
/// <returns>
/// A list of flag names.
/// </returns>
public static IEnumerable<string> GetFlagNames<TEnum>(int value, bool ignoreZero = false, bool humanize = true)
where TEnum : struct, IConvertible
{
return Retrieve<TEnum>()
.When(() => ignoreZero, q => q.Where(f => (int) f.Item2 != 0))
.Where(x => ((int) x.Item2 & value) == (int) x.Item2)
.Select(x => humanize ? x.Item1.Humanize() : x.Item1);
}
/// <summary>
/// Gets the flag names.
/// </summary>
/// <typeparam name="TEnum">The type of the enum.</typeparam>
/// <param name="value">The value.</param>
/// <param name="ignoreZero">if set to <c>true</c> [ignore zero].</param>
/// <param name="humanize">if set to <c>true</c> [humanize].</param>
/// <returns>
/// A list of flag names.
/// </returns>
public static IEnumerable<string> GetFlagNames<TEnum>(long value, bool ignoreZero = false, bool humanize = true)
where TEnum : struct, IConvertible
{
return Retrieve<TEnum>()
.When(() => ignoreZero, q => q.Where(f => (long) f.Item2 != 0))
.Where(x => ((long) x.Item2 & value) == (long) x.Item2)
.Select(x => humanize ? x.Item1.Humanize() : x.Item1);
}
/// <summary>
/// Gets the flag names.
/// </summary>
/// <typeparam name="TEnum">The type of the enum.</typeparam>
/// <param name="value">The value.</param>
/// <param name="ignoreZero">if set to <c>true</c> [ignore zero].</param>
/// <param name="humanize">if set to <c>true</c> [humanize].</param>
/// <returns>
/// A list of flag names.
/// </returns>
public static IEnumerable<string> GetFlagNames<TEnum>(byte value, bool ignoreZero = false, bool humanize = true)
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>
/// Gets the cached items with the enum item index.
/// </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));
}
}
}
@@ -0,0 +1,193 @@
namespace Unosquare.Swan.Components
{
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
/// <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 bool AreEqual<T>(T left, T right) => AreEqual(left, right, typeof(T));
/// <summary>
/// Compare if two variables 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 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))
return Equals(left, right);
if (targetType.IsValueType() || targetType.IsArray)
return AreStructsEqual(left, right, targetType);
return AreObjectsEqual(left, right, targetType);
}
/// <summary>
/// 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>
/// 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 bool AreObjectsEqual(object left, object right, Type targetType)
{
if (targetType == null)
throw new ArgumentNullException(nameof(targetType));
var properties = Runtime.PropertyTypeCache.RetrieveAllProperties(targetType).ToArray();
foreach (var propertyTarget in properties)
{
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));
foreach (var targetMember in fields)
{
switch (targetMember)
{
case FieldInfo field:
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;
}
/// <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 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;
}
}
}
+116
View File
@@ -0,0 +1,116 @@
namespace Unosquare.Swan.Components
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using Abstractions;
/// <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)
{
SourceType = typeof(TSource);
DestinationType = typeof(TDestination);
Map = intersect.ToDictionary(
property => DestinationType.GetProperty(property.Name),
property => new List<PropertyInfo> {SourceType.GetProperty(property.Name)});
}
/// <inheritdoc/>
public Dictionary<PropertyInfo, List<PropertyInfo>> Map { get; }
/// <inheritdoc/>
public Type SourceType { get; }
/// <inheritdoc/>
public Type DestinationType { get; }
/// <summary>
/// Maps the property.
/// </summary>
/// <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)
{
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;
}
}
}
@@ -0,0 +1,411 @@
namespace Unosquare.Swan.Components
{
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Abstractions;
/// <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>
/// Copies the specified source.
/// </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)
throw new ArgumentNullException(nameof(target));
return Copy(
target,
propertiesToCopy,
ignoreProperties,
GetSourceMap(source));
}
/// <summary>
/// Copies the specified source.
/// </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(
IDictionary<string, object> source,
object target,
string[] propertiesToCopy = null,
string[] ignoreProperties = null)
{
if (source == null)
throw new ArgumentNullException(nameof(source));
if (target == null)
throw new ArgumentNullException(nameof(target));
return Copy(
target,
propertiesToCopy,
ignoreProperties,
source.ToDictionary(
x => x.Key.ToLowerInvariant(),
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();
}
}
}
@@ -0,0 +1,204 @@
namespace Unosquare.Swan.Components
{
using System;
using System.Linq;
using System.Collections.Concurrent;
using System.Collections.Generic;
using Abstractions;
/// <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>
/// 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>
/// 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 bool IsValid => !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) =>
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; }
}
}
}
@@ -0,0 +1,197 @@
namespace Unosquare.Swan.Components
{
using System;
using System.Threading;
using Abstractions;
/// <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>
/// Enumerates the locking operations.
/// </summary>
private enum LockHolderType
{
Read,
Write,
}
/// <summary>
/// Defines methods for releasing locks.
/// </summary>
private interface ISyncReleasable
{
/// <summary>
/// Releases the writer lock.
/// </summary>
void ReleaseWriterLock();
/// <summary>
/// Releases the reader lock.
/// </summary>
void ReleaseReaderLock();
}
#endregion
#region Factory Methods
#if !NETSTANDARD1_3
/// <summary>
/// Creates a reader-writer lock backed by a standard ReaderWriterLock.
/// </summary>
/// <returns>The synchronized locker.</returns>
public static ISyncLocker Create() => new SyncLocker();
#else
/// <summary>
/// Creates a reader-writer lock backed by a standard ReaderWriterLockSlim when
/// running at NETSTANDARD 1.3.
/// </summary>
/// <returns>The synchronized locker</returns>
public static ISyncLocker Create() => new SyncLockerSlim();
#endif
/// <summary>
/// Creates a reader-writer lock backed by a ReaderWriterLockSlim.
/// </summary>
/// <returns>The synchronized locker.</returns>
public static ISyncLocker CreateSlim() => new SyncLockerSlim();
/// <summary>
/// Creates a reader-writer lock.
/// </summary>
/// <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>
public static ISyncLocker Create(bool useSlim) => useSlim ? CreateSlim() : Create();
#endregion
#region Private Classes
/// <summary>
/// The lock releaser. Calling the dispose method releases the lock entered by the parent SyncLocker.
/// </summary>
/// <seealso cref="System.IDisposable" />
private sealed class SyncLockReleaser : IDisposable
{
private readonly ISyncReleasable _parent;
private readonly LockHolderType _operation;
private bool _isDisposed;
/// <summary>
/// Initializes a new instance of the <see cref="SyncLockReleaser"/> class.
/// </summary>
/// <param name="parent">The parent.</param>
/// <param name="operation">The operation.</param>
public SyncLockReleaser(ISyncReleasable parent, LockHolderType operation)
{
_parent = parent;
_operation = operation;
}
/// <inheritdoc />
public void Dispose()
{
if (_isDisposed) return;
_isDisposed = true;
if (_operation == LockHolderType.Read)
_parent.ReleaseReaderLock();
else
_parent.ReleaseWriterLock();
}
}
#if !NETSTANDARD1_3
/// <summary>
/// The Sync Locker backed by a ReaderWriterLock.
/// </summary>
/// <seealso cref="ISyncLocker" />
/// <seealso cref="ISyncReleasable" />
private sealed class SyncLocker : ISyncLocker, ISyncReleasable
{
private bool _isDisposed;
private ReaderWriterLock _locker = new ReaderWriterLock();
/// <inheritdoc />
public IDisposable AcquireReaderLock()
{
_locker?.AcquireReaderLock(Timeout.Infinite);
return new SyncLockReleaser(this, LockHolderType.Read);
}
/// <inheritdoc />
public IDisposable AcquireWriterLock()
{
_locker?.AcquireWriterLock(Timeout.Infinite);
return new SyncLockReleaser(this, LockHolderType.Write);
}
/// <inheritdoc />
public void ReleaseWriterLock() => _locker?.ReleaseWriterLock();
/// <inheritdoc />
public void ReleaseReaderLock() => _locker?.ReleaseReaderLock();
/// <inheritdoc />
public void Dispose()
{
if (_isDisposed) return;
_isDisposed = true;
_locker?.ReleaseLock();
_locker = null;
}
}
#endif
/// <summary>
/// The Sync Locker backed by ReaderWriterLockSlim.
/// </summary>
/// <seealso cref="ISyncLocker" />
/// <seealso cref="ISyncReleasable" />
private sealed class SyncLockerSlim : ISyncLocker, ISyncReleasable
{
private bool _isDisposed;
private ReaderWriterLockSlim _locker
= new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);
/// <inheritdoc />
public IDisposable AcquireReaderLock()
{
_locker?.EnterReadLock();
return new SyncLockReleaser(this, LockHolderType.Read);
}
/// <inheritdoc />
public IDisposable AcquireWriterLock()
{
_locker?.EnterWriteLock();
return new SyncLockReleaser(this, LockHolderType.Write);
}
/// <inheritdoc />
public void ReleaseWriterLock() => _locker?.ExitWriteLock();
/// <inheritdoc />
public void ReleaseReaderLock() => _locker?.ExitReadLock();
/// <inheritdoc />
public void Dispose()
{
if (_isDisposed) return;
_isDisposed = true;
_locker?.Dispose();
_locker = null;
}
}
#endregion
}
}
@@ -0,0 +1,63 @@
#if !NETSTANDARD1_3
namespace Unosquare.Swan.Components
{
using System;
using System.Threading;
using Abstractions;
/// <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>
{
private readonly Timer _innerTimer;
private readonly IWaitEvent _delayLock = WaitEventFactory.Create(true);
/// <summary>
/// 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,
0,
15);
}
/// <summary>
/// Waits until the time is elapsed.
/// </summary>
/// <param name="untilDate">The until date.</param>
/// <param name="ct">The cancellation token.</param>
public void WaitUntil(DateTime untilDate, CancellationToken ct = default)
{
while (!ct.IsCancellationRequested && DateTime.UtcNow < untilDate)
_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);
}
}
#endif
@@ -0,0 +1,222 @@
#if !NETSTANDARD1_3
namespace Unosquare.Swan.Components
{
using System;
using System.Threading;
using Abstractions;
/// <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>
/// Creates a Wait Event backed by a standard ManualResetEvent.
/// </summary>
/// <param name="isCompleted">if initially set to completed. Generally true.</param>
/// <returns>The Wait Event.</returns>
public static IWaitEvent Create(bool isCompleted) => new WaitEvent(isCompleted);
/// <summary>
/// Creates a Wait Event backed by a ManualResetEventSlim.
/// </summary>
/// <param name="isCompleted">if initially set to completed. Generally true.</param>
/// <returns>The Wait Event.</returns>
public static IWaitEvent CreateSlim(bool isCompleted) => new WaitEventSlim(isCompleted);
/// <summary>
/// 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
/// <summary>
/// Defines a WaitEvent backed by a ManualResetEvent.
/// </summary>
private class WaitEvent : IWaitEvent
{
private ManualResetEvent _event;
/// <summary>
/// Initializes a new instance of the <see cref="WaitEvent"/> class.
/// </summary>
/// <param name="isCompleted">if set to <c>true</c> [is completed].</param>
public WaitEvent(bool isCompleted)
{
_event = new ManualResetEvent(isCompleted);
}
/// <inheritdoc />
public bool IsDisposed { get; private set; }
/// <inheritdoc />
public bool IsValid
{
get
{
if (IsDisposed || _event == null)
return false;
if (_event?.SafeWaitHandle?.IsClosed ?? true)
return false;
return !(_event?.SafeWaitHandle?.IsInvalid ?? true);
}
}
/// <inheritdoc />
public bool IsCompleted
{
get
{
if (IsValid == false)
return true;
return _event?.WaitOne(0) ?? true;
}
}
/// <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>
/// 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(bool isCompleted)
{
_event = new ManualResetEventSlim(isCompleted);
}
/// <inheritdoc />
public bool IsDisposed { get; private set; }
/// <inheritdoc />
public bool IsValid
{
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
}
}
#endif